├── .gitignore ├── README.md ├── package.json ├── src ├── format.js ├── opentype.js ├── readbuffer.js ├── sfnt.js ├── tables │ ├── cmap.js │ ├── common.js │ ├── gasp.js │ ├── gdef.js │ ├── gpos.js │ ├── gsub.js │ ├── head.js │ ├── hhea.js │ ├── hmtx.js │ ├── maxp.js │ ├── name.js │ ├── os2.js │ └── post.js ├── type.js ├── util.js ├── woff.js └── woff2.js └── test ├── basic-test.js ├── fonts ├── sourcesanspro-regular.otf ├── sourcesanspro-regular.ttf ├── sourcesanspro-regular.ttx ├── sourcesanspro-regular.woff └── sourcesanspro-regular.woff2 ├── readbuffer-test.js ├── tables ├── cmap-test.js ├── common-test.js ├── gasp-test.js ├── gdef-test.js ├── gsub-test.js ├── head-test.js ├── hhea-test.js ├── hmtx-test.js ├── maxp-test.js ├── name-test.js ├── os2-test.js └── post-test.js ├── type-test.js └── util-test.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | build 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## DEPRECATED: An OpenType font parser in JavaScript 2 | 3 | *This package is deprecated, please use [OpenType.js](https://opentype.js.org/), [Fontkit](https://github.com/foliojs/fontkit), or [lib-font](https://github.com/Pomax/lib-font) instead.* 4 | 5 | This is a pure JavaScript parser for OpenType font files. It supports fonts with CFF and TrueType outlines, and can read fonts wrapped as WOFF and WOFF2. 6 | 7 | The following OpenType tables are currently supported: 8 | 9 | * CMAP (only format 0, 4, 12, and 13) 10 | * head 11 | * hhea 12 | * maxp 13 | * hmtx 14 | * name 15 | * OS/2 16 | * post 17 | * GSUB (excluding LookupType 5, 6, 7, and 8) 18 | * GDEF (only the Glyph Class Definitions) 19 | * gasp 20 | 21 | This roughly corresponds to all the metadata available in most fonts. I'm hoping to add support for glyf, CFF, and related tables time permitting. 22 | 23 | ## Usage 24 | 25 | ``` 26 | npm install opentype 27 | ``` 28 | 29 | ``` 30 | var opentype = require('opentype'); 31 | var fs = require('fs'); 32 | 33 | fs.readFile('font.otf', function (err, data) { 34 | var font = opentype.parse(data); 35 | }); 36 | 37 | ``` 38 | 39 | ## Copyright and License 40 | 41 | This library is licensed under the three-clause BSD license. Copyright 2013-2016 Bram Stein. All rights reserved. 42 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "opentype", 3 | "version": "1.0.0", 4 | "description": "Parse WOFF, WOFF2, TrueType and OpenType fonts", 5 | "directories": { 6 | "test": "test" 7 | }, 8 | "scripts": { 9 | "test": "mocha -R spec test/*" 10 | }, 11 | "main": "src/opentype.js", 12 | "repository": "git@github.com:bramstein/opentype.git", 13 | "keywords": [ 14 | "font", 15 | "parser", 16 | "woff", 17 | "woff2", 18 | "truetype", 19 | "opentype", 20 | "ttf", 21 | "otf" 22 | ], 23 | "author": "Bram Stein (http://www.bramstein.com)", 24 | "license": "BSD-3-Clause", 25 | "devDependencies": { 26 | "concentrate": "^0.2.3", 27 | "mocha": "^2.5.3", 28 | "unexpected": "^10.13.3", 29 | "unexpected-stream": "^2.0.4" 30 | }, 31 | "dependencies": { 32 | "brotli": "^1.2.0", 33 | "iconv-lite": "^0.4.13", 34 | "node-int64": "^0.4.0" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/format.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | WOFF: 0x774F4646, 3 | WOFF2: 0x774F4632, 4 | TRUETYPE: 0x00010000, 5 | OPENTYPE: 0x4F54544F 6 | }; 7 | -------------------------------------------------------------------------------- /src/opentype.js: -------------------------------------------------------------------------------- 1 | var Format = require('./format'); 2 | var Type = require('./type'); 3 | var util = require('./util'); 4 | var sfnt = require('./sfnt'); 5 | var woff = require('./woff'); 6 | var woff2 = require('./woff2'); 7 | var ReadBuffer = require('./readbuffer'); 8 | var zlib = require('zlib'); 9 | var brotli = require('brotli'); 10 | 11 | var cmap = require('./tables/cmap'); 12 | var head = require('./tables/head'); 13 | var hhea = require('./tables/hhea'); 14 | var maxp = require('./tables/maxp'); 15 | var hmtx = require('./tables/hmtx'); 16 | var name = require('./tables/name'); 17 | var os_2 = require('./tables/os2'); 18 | var post = require('./tables/post'); 19 | var gsub = require('./tables/gsub'); 20 | var gdef = require('./tables/gdef'); 21 | var gasp = require('./tables/gasp'); 22 | 23 | var TABLES = { 24 | // Note that the order of the tables 25 | // is important. The `hmtx` table requires 26 | // `hhea` and `maxp` tables to be parsed. The 27 | // `post` table (depending on its version) 28 | // requires the `maxp` table to be parsed. 29 | 'cmap': cmap, 30 | 'head': head, 31 | 'hhea': hhea, 32 | 'maxp': maxp, 33 | 'hmtx': hmtx, 34 | 'name': name, 35 | 'OS/2': os_2, 36 | 'post': post, 37 | 'GSUB': gsub, 38 | 'GDEF': gdef, 39 | 'gasp': gasp 40 | }; 41 | 42 | /** 43 | * @param {Buffer} buffer 44 | */ 45 | var parse = function (buffer) { 46 | var rb = new ReadBuffer(buffer), 47 | font = { 48 | tables: {} 49 | }; 50 | 51 | var signature = rb.read(Type.ULONG, 0); 52 | 53 | if (signature === Format.WOFF) { 54 | font.header = rb.read(woff.Header); 55 | var index = rb.readArray(woff.TableDirectory, font.header.numTables); 56 | 57 | index.forEach(function (table) { 58 | var data = null, 59 | tag = table.tag; 60 | 61 | if (table.compLength !== table.origLength) { 62 | var compressedData = buffer.slice(table.offset, table.offset + util.pad(table.compLength)); 63 | 64 | font.tables[tag] = zlib.inflateSync(compressedData); 65 | } else { 66 | font.tables[tag] = buffer.slice(table.offset, table.offset + util.pad(table.origLength)); 67 | } 68 | }); 69 | } else if (signature === Format.WOFF2) { 70 | var index = []; 71 | var totalSize = 0; 72 | 73 | font.header = rb.read(woff2.Header); 74 | 75 | for (var i = 0; i < font.header.numTables; i++) { 76 | var flag = rb.read(Type.BYTE); 77 | var tag = null; 78 | 79 | if (flag === 63) { 80 | tag = rb.read(Type.TAG); 81 | } else { 82 | tag = woff2.Flags[flag]; 83 | } 84 | 85 | var origLength = rb.read(Type.BASE128); 86 | 87 | var transformVersion = (flag >>> 6) & 0x03; 88 | if (tag === 'glyf' || tag === 'loca') { 89 | transformVersion = 0; 90 | 91 | var transformLength = rb.read(Type.BASE128); 92 | } 93 | 94 | totalSize += origLength; 95 | 96 | index.push({ 97 | flags: flag, 98 | tag: tag, 99 | origLength: origLength 100 | }); 101 | } 102 | 103 | // TODO: Upgrade to Node v6.x so we can use Buffer.from. 104 | var data = new Buffer(brotli.decompress(buffer.slice(rb.byteOffset, rb.byteOffset + totalSize))); 105 | var offset = 0; 106 | 107 | index.forEach(function (table) { 108 | font.tables[table.tag] = data.slice(offset, offset + util.pad(table.origLength)); 109 | offset += table.origLength; 110 | }); 111 | } else if (signature === Format.TRUETYPE || signature === Format.OPENTYPE) { 112 | font.header = rb.read(sfnt.Header); 113 | var index = rb.readArray(sfnt.OffsetTable, font.header.numTables); 114 | 115 | index.forEach(function (table) { 116 | font.tables[table.tag] = buffer.slice(table.offset, table.offset + util.pad(table.length)); 117 | }); 118 | } 119 | 120 | for (var table in TABLES) { 121 | if (font.tables[table]) { 122 | font.tables[table] = TABLES[table](font.tables[table], font); 123 | } 124 | } 125 | 126 | return font; 127 | }; 128 | 129 | module.exports = { 130 | parse: parse 131 | }; 132 | -------------------------------------------------------------------------------- /src/readbuffer.js: -------------------------------------------------------------------------------- 1 | var iconv = require('iconv-lite'); 2 | 3 | /** 4 | * @constructor 5 | * @param {Buffer} buffer 6 | * @param {number=} opt_byteOffset 7 | */ 8 | var ReadBuffer = function (buffer, opt_byteOffset) { 9 | this.buffer = buffer; 10 | this.byteOffset = opt_byteOffset || 0; 11 | }; 12 | 13 | /** 14 | * Jump to an offset in this buffer 15 | * @param {number} byteOffset 16 | */ 17 | ReadBuffer.prototype.goto = function (byteOffset) { 18 | this.byteOffset = byteOffset; 19 | }; 20 | 21 | /** 22 | * Read a struct from the buffer at the next 23 | * position or if byteOffset is given a specific 24 | * position. 25 | * 26 | * @param {opentype.Struct} type 27 | * @param {number=} opt_byteOffset 28 | * @return {?} 29 | */ 30 | ReadBuffer.prototype.read = function (type, opt_byteOffset) { 31 | var data = type.read(this.buffer, opt_byteOffset || this.byteOffset); 32 | 33 | if (opt_byteOffset === undefined) { 34 | this.byteOffset += type.sizeof; 35 | } 36 | 37 | return data; 38 | }; 39 | 40 | /** 41 | * Read multiple structs from the buffer at the 42 | * next position or if byteOffset is given a 43 | * specific position. 44 | * 45 | * @param {opentype.Struct} type 46 | * @param {number} count 47 | * @param {number=} opt_byteOffset 48 | * @return {Array.} 49 | */ 50 | ReadBuffer.prototype.readArray = function (type, count, opt_byteOffset) { 51 | var byteOffset = opt_byteOffset || this.byteOffset, 52 | data = []; 53 | 54 | for (var i = 0; i < count; i += 1) { 55 | data.push(type.read(this.buffer, byteOffset)); 56 | byteOffset += type.sizeof; 57 | } 58 | 59 | if (opt_byteOffset === undefined) { 60 | this.byteOffset += type.sizeof * count; 61 | } 62 | 63 | return data; 64 | }; 65 | 66 | ReadBuffer.prototype.readString = function (encoding, length, opt_byteOffset) { 67 | var byteOffset = opt_byteOffset || this.byteOffset; 68 | 69 | var result = iconv.decode(this.buffer.slice(byteOffset, byteOffset + length), encoding); 70 | 71 | this.byteOffset += length; 72 | 73 | return result; 74 | }; 75 | 76 | module.exports = ReadBuffer; 77 | -------------------------------------------------------------------------------- /src/sfnt.js: -------------------------------------------------------------------------------- 1 | var Type = require('./type'); 2 | var util = require('./util'); 3 | 4 | var Header = util.struct({ 5 | version: Type.ULONG, 6 | numTables: Type.USHORT, 7 | searchRange: Type.USHORT, 8 | entrySelector: Type.USHORT, 9 | rangeShift: Type.USHORT 10 | }); 11 | 12 | var OffsetTable = util.struct({ 13 | tag: Type.TAG, 14 | checkSum: Type.ULONG, 15 | offset: Type.ULONG, 16 | length: Type.ULONG 17 | }); 18 | 19 | module.exports = { 20 | OffsetTable: OffsetTable, 21 | Header: Header 22 | }; 23 | -------------------------------------------------------------------------------- /src/tables/cmap.js: -------------------------------------------------------------------------------- 1 | var ReadBuffer = require('../readbuffer'); 2 | var Type = require('../type'); 3 | var util = require('../util'); 4 | 5 | var cmap = function (buffer, font) { 6 | var table = new ReadBuffer(buffer), 7 | data = []; 8 | 9 | var header = table.read(util.struct({ 10 | version: Type.USHORT, 11 | numTables: Type.USHORT 12 | })); 13 | 14 | var index = table.readArray(util.struct({ 15 | platformID: Type.USHORT, 16 | encodingID: Type.USHORT, 17 | offset: Type.ULONG 18 | }), header.numTables); 19 | 20 | index.forEach(function (subTable) { 21 | var subData = {}; 22 | var charCodes = {}; 23 | var language = 0; 24 | 25 | table.goto(subTable.offset); 26 | 27 | var format = table.read(Type.USHORT, subTable.offset); 28 | 29 | // For now we support the same formats as OTS, which 30 | // only supports 0, 4, 12, 13, and 14. 31 | if (format === 0) { 32 | subData = table.read(util.struct({ 33 | format: Type.USHORT, 34 | length: Type.USHORT, 35 | language: Type.USHORT 36 | })); 37 | 38 | var glyphIdArray = table.readArray(Type.BYTE, 256); 39 | 40 | for (var i = 0; i < 256; i++) { 41 | charCodes[i] = glyphIdArray[i]; 42 | } 43 | 44 | language = subData.language; 45 | } else if (format === 4) { 46 | subData = table.read(util.struct({ 47 | format: Type.USHORT, 48 | length: Type.USHORT, 49 | language: Type.USHORT, 50 | segCountX2: Type.USHORT, 51 | searchRange: Type.USHORT, 52 | entrySelector: Type.USHORT, 53 | rangeShift: Type.USHORT 54 | })); 55 | 56 | var segCount = subData.segCountX2 / 2; 57 | 58 | var endCount = table.readArray(Type.USHORT, segCount); 59 | var reservedPad = table.read(Type.USHORT); 60 | var startCount = table.readArray(Type.USHORT, segCount); 61 | var idDelta = table.readArray(Type.SHORT, segCount); 62 | 63 | var idRangeTableOffset = table.byteOffset; 64 | 65 | var idRangeOffset = table.readArray(Type.USHORT, segCount); 66 | var glyphIdArray = []; 67 | 68 | for (var i = 0; i < segCount - 1; i += 1) { 69 | var start = startCount[i]; 70 | var end = endCount[i]; 71 | var delta = idDelta[i]; 72 | var rangeOffset = idRangeOffset[i]; 73 | var offset = idRangeTableOffset + (i * Type.USHORT.sizeof); 74 | 75 | for (var j = start; j <= end; j += 1) { 76 | var charCode = j, 77 | id = null; 78 | 79 | if (rangeOffset === 0) { 80 | id = (charCode + delta) % 0xFFFF; 81 | } else { 82 | id = (table.read(Type.USHORT, offset + rangeOffset + (charCode - start) * Type.USHORT.sizeof) + delta) % 0xFFFF; 83 | } 84 | 85 | charCodes[charCode] = id; 86 | } 87 | } 88 | 89 | language = subData.language; 90 | } else if (format === 12) { 91 | subData = table.read(util.struct({ 92 | format: Type.USHORT, 93 | reserved: Type.USHORT, 94 | length: Type.ULONG, 95 | language: Type.ULONG, 96 | nGroups: Type.ULONG 97 | })); 98 | 99 | var groups = table.readArray(util.struct({ 100 | startCharCode: Type.ULONG, 101 | endCharCode: Type.ULONG, 102 | glyphID: Type.ULONG 103 | }), subData.nGroups); 104 | 105 | for (var i = 0; i < subData.nGroups; i += 1) { 106 | var start = groups[i].startCharCode; 107 | var end = groups[i].endCharCode; 108 | 109 | for (var charCode = start, id = groups[i].glyphID; charCode <= end; charCode += 1, id++) { 110 | charCodes[charCode] = id; 111 | } 112 | } 113 | 114 | language = subData.language; 115 | 116 | } else if (format === 13) { 117 | subData = table.read(util.struct({ 118 | format: Type.USHORT, 119 | reserved: Type.USHORT, 120 | length: Type.ULONG, 121 | language: Type.ULONG, 122 | nGroups: Type.ULONG 123 | })); 124 | 125 | var groups = table.readArray(util.struct({ 126 | startCharCode: Type.ULONG, 127 | endCharCode: Type.ULONG, 128 | glyphID: Type.ULONG 129 | }), subData.nGroups); 130 | 131 | for (var i = 0; i < subData.nGroups; i += 1) { 132 | var start = groups[i].startCharCode; 133 | var end = groups[i].endCharCode; 134 | 135 | for (var j = start; j <= end; j += 1) { 136 | var charCode = j, 137 | id = groups[i].glyphID; 138 | 139 | charCodes[charCode] = id; 140 | } 141 | } 142 | 143 | language = subData.language; 144 | } else if (format === 14) { 145 | subData = table.read(util.struct({ 146 | format: Type.USHORT, 147 | length: Type.ULONG, 148 | numVarSelectorRecords: Type.ULONG 149 | })); 150 | 151 | 152 | var records = table.readArray(util.struct({ 153 | varSelector: Type.UINT24, 154 | defaultUVSOffset: Type.ULONG, 155 | nonDefaultUVSOffset: Type.ULONG 156 | }), subData.numVarSelectorRecords); 157 | 158 | records.forEach(function (record) { 159 | if (record.defaultUVSOffset !== 0) { 160 | table.goto(record.defaultUVSOffset); 161 | 162 | var ranges = table.readArray(util.struct({ 163 | startUnicodeValue: Type.UINT24, 164 | additionalCount: Type.BYTE 165 | }), table.read(Type.ULONG)); 166 | } 167 | 168 | if (record.nonDefaultUVSOffset !== 0) { 169 | table.goto(record.nonDefaultUVSOffset); 170 | 171 | var ranges = table.readArray(util.struct({ 172 | unicodeValue: Type.UINT24, 173 | glyphID: Type.BYTE 174 | }), table.read(Type.ULONG)); 175 | } 176 | }); 177 | 178 | language = 0; 179 | } 180 | 181 | data.push({ 182 | format: format, 183 | platformID: subTable.platformID, 184 | encodingID: subTable.encodingID, 185 | language: language, 186 | charCodes: charCodes 187 | }); 188 | }); 189 | 190 | return data; 191 | }; 192 | 193 | module.exports = cmap; 194 | -------------------------------------------------------------------------------- /src/tables/common.js: -------------------------------------------------------------------------------- 1 | var Type = require('../type'); 2 | var util = require('../util'); 3 | 4 | var List = function (buffer, offset, table) { 5 | buffer.goto(offset); 6 | 7 | var data = []; 8 | var count = buffer.read(Type.USHORT); 9 | 10 | var records = buffer.readArray(util.struct({ 11 | tag: Type.TAG, 12 | offset: Type.OFFSET 13 | }), count); 14 | 15 | for (var i = 0; i < count; i += 1) { 16 | data.push({ 17 | tag: records[i].tag, 18 | table: table(buffer, offset + records[i].offset) 19 | }); 20 | } 21 | 22 | return data; 23 | }; 24 | 25 | var ClassDef = function (buffer, offset) { 26 | buffer.goto(offset); 27 | 28 | var format = buffer.read(Type.USHORT); 29 | var data = {}; 30 | 31 | if (format === 1) { 32 | var startGlyph = buffer.read(Type.GLYPHID); 33 | var classValues = buffer.readArray(Type.USHORT, buffer.read(Type.USHORT)); 34 | 35 | for (var i = startGlyph, j= 0; i < (startGlyph + classValues.length); i++, j++) { 36 | data[i] = classValues[j]; 37 | } 38 | } else if (format === 2) { 39 | var classRangeRecords = buffer.readArray(util.struct({ 40 | Start: Type.GLYPHID, 41 | End: Type.GLYPHID, 42 | Class: Type.USHORT 43 | }), buffer.read(Type.USHORT)); 44 | 45 | classRangeRecords.forEach(function (record) { 46 | for (var i = record.Start; i <= record.End; i++) { 47 | data[i] = record.Class; 48 | } 49 | }); 50 | } 51 | 52 | return data; 53 | }; 54 | 55 | var Script = function (buffer, offset) { 56 | buffer.goto(offset); 57 | 58 | var data = []; 59 | 60 | var defaultLangSys = buffer.read(Type.OFFSET); 61 | var langSysCount = buffer.read(Type.USHORT); 62 | 63 | var records = buffer.readArray(util.struct({ 64 | tag: Type.TAG, 65 | offset: Type.OFFSET 66 | }), langSysCount); 67 | 68 | if (defaultLangSys) { 69 | data.push({ 70 | tag: 'DFLT', 71 | table: LangSys(buffer, offset + defaultLangSys) 72 | }); 73 | } 74 | 75 | for (var i = 0; i < records.length; i += 1) { 76 | data.push({ 77 | tag: records[i].tag, 78 | table: LangSys(buffer, offset + records[i].offset) 79 | }); 80 | } 81 | 82 | return data; 83 | }; 84 | 85 | var LangSys = function (buffer, offset) { 86 | buffer.goto(offset); 87 | 88 | var lookupOrder = buffer.read(Type.OFFSET); 89 | var reqFeatureIndex = buffer.read(Type.USHORT); 90 | var featureCount = buffer.read(Type.USHORT); 91 | var featureIndex = buffer.readArray(Type.USHORT, featureCount); 92 | 93 | return { 94 | 'LookupOrder': lookupOrder, 95 | 'ReqFeatureIndex': reqFeatureIndex, 96 | 'FeatureCount': featureCount, 97 | 'FeatureIndex': featureIndex 98 | }; 99 | }; 100 | 101 | var Feature = function (buffer, offset) { 102 | buffer.goto(offset); 103 | 104 | var featureParams = buffer.read(Type.OFFSET); 105 | var lookupCount = buffer.read(Type.USHORT); 106 | var lookupListIndex = buffer.readArray(Type.USHORT, lookupCount); 107 | 108 | return { 109 | 'FeatureParams': featureParams, 110 | 'LookupCount': lookupCount, 111 | 'LookupListIndex': lookupListIndex 112 | }; 113 | }; 114 | 115 | var LookupList = function (buffer, offset, table) { 116 | buffer.goto(offset); 117 | 118 | var data = []; 119 | var count = buffer.read(Type.USHORT); 120 | var records = buffer.readArray(Type.OFFSET, count); 121 | 122 | for (var i = 0; i < count; i += 1) { 123 | data.push(Lookup(buffer, offset + records[i], table)); 124 | } 125 | 126 | return data; 127 | }; 128 | 129 | var Lookup = function (buffer, offset, table) { 130 | buffer.goto(offset); 131 | 132 | var data = {}; 133 | 134 | var lookupType = buffer.read(Type.USHORT); 135 | var lookupFlag = buffer.read(Type.USHORT); 136 | var subTableCount = buffer.read(Type.USHORT); 137 | var subTables = buffer.readArray(Type.OFFSET, subTableCount); 138 | var markFilteringSet = buffer.read(Type.USHORT); 139 | 140 | for (var i = 0; i < subTableCount; i += 1) { 141 | subTables[i] = table(buffer, lookupType, offset + subTables[i]); 142 | } 143 | 144 | return { 145 | 'LookupType': lookupType, 146 | 'LookupFlag': lookupFlag, 147 | 'SubTable': subTables, 148 | 'MarkFilteringSet': markFilteringSet 149 | }; 150 | }; 151 | 152 | var Coverage = function (buffer, offset) { 153 | buffer.goto(offset); 154 | 155 | var format = buffer.read(Type.USHORT); 156 | var count = buffer.read(Type.USHORT); 157 | var data = []; 158 | 159 | if (format === 1) { 160 | data = buffer.readArray(Type.GLYPHID, count); 161 | } else if (format === 2) { 162 | var records = buffer.readArray(util.struct({ 163 | start: Type.GLYPHID, 164 | end: Type.GLYPHID, 165 | startCoverageIndex: Type.USHORT 166 | }), count); 167 | 168 | for (var i = 0; i < count; i += 1) { 169 | for(var glyph = records[i].start; glyph <= records[i].end; glyph += 1) { 170 | data.push(records[i].startCoverageIndex + glyph - records[i].start); 171 | } 172 | } 173 | } 174 | return data; 175 | }; 176 | 177 | module.exports = { 178 | List: List, 179 | Script: Script, 180 | LangSys: LangSys, 181 | Feature: Feature, 182 | LookupList: LookupList, 183 | Lookup: Lookup, 184 | Coverage: Coverage, 185 | ClassDef: ClassDef 186 | }; 187 | -------------------------------------------------------------------------------- /src/tables/gasp.js: -------------------------------------------------------------------------------- 1 | var ReadBuffer = require('../readbuffer'); 2 | var Type = require('../type'); 3 | var util = require('../util'); 4 | 5 | module.exports = function (buffer, font) { 6 | var table = new ReadBuffer(buffer); 7 | 8 | var version = table.read(Type.USHORT); 9 | var data = {}; 10 | 11 | data.gaspRange = table.readArray(util.struct({ 12 | rangeMaxPPEM: Type.USHORT, 13 | rangeGaspBehavior: Type.USHORT 14 | }), table.read(Type.USHORT)); 15 | 16 | return data; 17 | }; 18 | -------------------------------------------------------------------------------- /src/tables/gdef.js: -------------------------------------------------------------------------------- 1 | var ReadBuffer = require('../readbuffer'); 2 | var Type = require('../type'); 3 | var util = require('../util'); 4 | var common = require('./common'); 5 | 6 | var gdef = function (buffer, font) { 7 | var table = new ReadBuffer(buffer); 8 | 9 | var version = table.read(Type.ULONG); 10 | var glyphClassDef = table.read(Type.OFFSET); 11 | var data = {}; 12 | 13 | if (glyphClassDef !== 0) { 14 | data.GlyphClassDef = common.ClassDef(table, glyphClassDef); 15 | } 16 | return data; 17 | }; 18 | 19 | module.exports = gdef; 20 | -------------------------------------------------------------------------------- /src/tables/gpos.js: -------------------------------------------------------------------------------- 1 | var ReadBuffer = require('../readbuffer'); 2 | var Type = require('../type'); 3 | var util = require('../util'); 4 | var common = require('./common'); 5 | 6 | var gpos = function (buffer, font) { 7 | var table = new ReadBuffer(buffer), 8 | data = {}; 9 | 10 | var version = table.read(Type.FIXED); 11 | var scriptListOffset = table.read(Type.OFFSET); 12 | var featureListOffset = table.read(Type.OFFSET); 13 | var lookupListOffset = table.read(Type.OFFSET); 14 | 15 | var scriptList = common.List(table, scriptListOffset, common.Script); 16 | var featureList = common.List(table, featureListOffset, common.Feature); 17 | var lookupList = common.List(table, lookupListOffset, tables.gpos.LookupType); 18 | scriptList.forEach(function (script) { 19 | var scriptTag = script.tag, 20 | scriptTable = script.table; 21 | 22 | data[scriptTag] = {}; 23 | 24 | scriptTable.forEach(function (language) { 25 | var languageTag = language.tag, 26 | languageTable = language.table; 27 | 28 | data[scriptTag][languageTag] = {}; 29 | 30 | languageTable['FeatureIndex'].forEach(function (featureIndex) { 31 | var feature = featureList[featureIndex], 32 | featureTag = feature.tag, 33 | featureTable = feature.table; 34 | 35 | data[scriptTag][languageTag][featureTag] = {}; 36 | /* 37 | featureTable['LookupListIndex'].forEach(function (lookupIndex) { 38 | var lookup = lookupList[lookupIndex]; 39 | 40 | lookup['SubTable'].forEach(function (subTable) { 41 | Object.keys(subTable).forEach(function (glyphId) { 42 | data[scriptTag][languageTag][featureTag][glyphId] = subTable[glyphId]; 43 | }); 44 | }); 45 | });*/ 46 | }); 47 | }); 48 | }); 49 | return data; 50 | }; 51 | 52 | gpos.LookupType = function (buffer, lookupType, offset) { 53 | buffer.goto(offset); 54 | 55 | return {}; 56 | }; 57 | 58 | module.exports = gpos; 59 | -------------------------------------------------------------------------------- /src/tables/gsub.js: -------------------------------------------------------------------------------- 1 | var ReadBuffer = require('../readbuffer'); 2 | var Type = require('../type'); 3 | var util = require('../util'); 4 | var common = require('./common'); 5 | 6 | var gsub = function (buffer, font) { 7 | var table = new ReadBuffer(buffer), 8 | data = {}; 9 | 10 | var version = table.read(Type.FIXED); 11 | var scriptListOffset = table.read(Type.OFFSET); 12 | var featureListOffset = table.read(Type.OFFSET); 13 | var lookupListOffset = table.read(Type.OFFSET); 14 | 15 | var scriptList = common.List(table, scriptListOffset, common.Script); 16 | var featureList = common.List(table, featureListOffset, common.Feature); 17 | var lookupList = common.LookupList(table, lookupListOffset, gsub.LookupType); 18 | 19 | scriptList.forEach(function (script) { 20 | var scriptTag = script.tag, 21 | scriptTable = script.table; 22 | 23 | data[scriptTag] = {}; 24 | 25 | scriptTable.forEach(function (language) { 26 | var languageTag = language.tag, 27 | languageTable = language.table; 28 | 29 | data[scriptTag][languageTag] = {}; 30 | 31 | languageTable.FeatureIndex.forEach(function (featureIndex) { 32 | var feature = featureList[featureIndex], 33 | featureTag = feature.tag, 34 | featureTable = feature.table; 35 | 36 | data[scriptTag][languageTag][featureTag] = {}; 37 | 38 | featureTable.LookupListIndex.forEach(function (lookupIndex) { 39 | var lookup = lookupList[lookupIndex]; 40 | var type = gsub.LookupTypes[lookup.LookupType]; 41 | 42 | data[scriptTag][languageTag][featureTag][type] = {}; 43 | 44 | lookup.SubTable.forEach(function (subTable) { 45 | Object.keys(subTable).forEach(function (glyphId) { 46 | data[scriptTag][languageTag][featureTag][type][glyphId] = subTable[glyphId]; 47 | }); 48 | }); 49 | }); 50 | }); 51 | }); 52 | }); 53 | return data; 54 | }; 55 | 56 | gsub.LookupTypes = { 57 | 1: 'single', 58 | 2: 'multiple', 59 | 3: 'alternate', 60 | 4: 'ligature', 61 | 5: 'contextual', 62 | 6: 'chaining', 63 | 7: 'extension', 64 | 8: 'reverse' 65 | }; 66 | 67 | gsub.SubstLookupRecord = util.struct({ 68 | SequenceIndex: Type.USHORT, 69 | LookupListIndex: Type.USHORT 70 | }); 71 | 72 | gsub.LookupType = function (buffer, lookupType, offset) { 73 | buffer.goto(offset); 74 | 75 | var format = buffer.read(Type.USHORT); 76 | var data = {}; 77 | 78 | /** 79 | * substitution: { 80 | * single: { 81 | * "A": "A-caret" 82 | * }, 83 | * multiple: { 84 | * "A": ["A-caret", "A-long"] 85 | * }, 86 | * alternate: { 87 | * "A": ["A-single", "A-multi"] 88 | * }, 89 | * ligature: { 90 | * "f": [ 91 | * { components: ["i"], ligature: "fi" }, 92 | * { components: ["f"], ligature: "ff" }, 93 | * { components: ["f", "i"], ligature: "ffi" }, 94 | * { components: ["f", "l"], ligature: "ffl" }, 95 | * { components: ["l"], ligature: "fl" } 96 | * ], 97 | * "e": [ 98 | * ... 99 | * ] 100 | * }, 101 | * contextual: { 102 | * "f" 103 | * } 104 | * } 105 | */ 106 | 107 | if (lookupType === 1 && format === 1) { 108 | var coverageOffset = buffer.read(Type.OFFSET); 109 | var deltaGlyphId = buffer.read(Type.SHORT); 110 | var coverage = common.Coverage(buffer, offset + coverageOffset); 111 | for (var i = 0; i < coverage.length; i += 1) { 112 | data[coverage[i]] = coverage[i] + deltaGlyphId; 113 | } 114 | } else if (lookupType === 1 && format === 2) { 115 | var coverageOffset = buffer.read(Type.OFFSET); 116 | var glyphCount = buffer.read(Type.USHORT); 117 | var substitutes = buffer.readArray(Type.GLYPHID, glyphCount); 118 | var coverage = common.Coverage(buffer, offset + coverageOffset); 119 | 120 | for (var i = 0; i < coverage.length; i += 1) { 121 | data[coverage[i]] = substitutes[i]; 122 | } 123 | } else if (lookupType === 2 || lookupType === 3) { 124 | var coverageOffset = buffer.read(Type.OFFSET); 125 | var count = buffer.read(Type.USHORT); 126 | 127 | var setOffsets = buffer.readArray(Type.OFFSET, count); 128 | var coverage = common.Coverage(buffer, offset + coverageOffset); 129 | var sets = []; 130 | 131 | for (var i = 0; i < count; i += 1) { 132 | buffer.goto(offset + setOffsets[i]); 133 | var glyphCount = buffer.read(Type.USHORT); 134 | sets.push(buffer.readArray(Type.GLYPHID, glyphCount)); 135 | } 136 | 137 | for (var i = 0; i < coverage.length; i += 1) { 138 | if (lookupType === 2) { 139 | data[coverage[i]] = sets[i]; 140 | } else { 141 | data[coverage[i]] = sets[i]; 142 | } 143 | } 144 | } else if (lookupType === 4) { 145 | var coverageOffset = buffer.read(Type.OFFSET); 146 | var count = buffer.read(Type.USHORT); 147 | 148 | var setOffsets = buffer.readArray(Type.OFFSET, count); 149 | var coverage = common.Coverage(buffer, offset + coverageOffset); 150 | var ligatureSetOffsets = []; 151 | 152 | for (var i = 0; i < count; i += 1) { 153 | buffer.goto(offset + setOffsets[i]); 154 | var ligatureCount = buffer.read(Type.USHORT); 155 | ligatureSetOffsets.push(buffer.readArray(Type.OFFSET, ligatureCount)); 156 | } 157 | 158 | var ligatureSet = []; 159 | 160 | for (var i = 0; i < setOffsets.length; i += 1) { 161 | var ligature = []; 162 | 163 | for (var j = 0; j < ligatureSetOffsets[i].length; j += 1) { 164 | buffer.goto(offset + setOffsets[i] + ligatureSetOffsets[i][j]); 165 | var ligGlyph = buffer.read(Type.GLYPHID); 166 | var components = buffer.readArray(Type.GLYPHID, buffer.read(Type.USHORT) - 1); 167 | 168 | ligature.push({ 169 | ligature: ligGlyph, 170 | components: components 171 | }); 172 | } 173 | ligatureSet.push(ligature); 174 | } 175 | 176 | for (var i = 0; i < coverage.length; i += 1) { 177 | data[coverage[i]] = ligatureSet[i]; 178 | } 179 | } else if (lookupType === 5 && format === 1) { 180 | var coverageOffset = buffer.read(Type.OFFSET); 181 | var subRuleSetCount = buffer.read(Type.USHORT); 182 | var subRuleSetOffsets = buffer.readArray(Type.OFFSET, subRuleSetCount); 183 | 184 | var coverage = common.Coverage(buffer, offset + coverageOffset); 185 | 186 | subRuleSetOffsets.forEach(function (subRuleSetOffset) { 187 | buffer.goto(offset + subRuleSetOffset); 188 | var subRuleCount = buffer.read(Type.USHORT); 189 | var subRuleOffsets = buffer.readArray(Type.OFFSET, subRuleCount); 190 | 191 | subRuleOffsets.forEach(function (subRuleOffset) { 192 | buffer.goto(offset + subRuleSetOffset + subRuleOffset); 193 | 194 | var glyphCount = buffer.read(Type.USHORT); 195 | var substCount = buffer.read(Type.USHORT); 196 | var input = buffer.readArray(Type.GLYPHID, glyphCount - 1); 197 | var records = buffer.readArray(gsub.SubstLookupRecord, substCount); 198 | 199 | for (var i = 0; i < coverage.length; i++) { 200 | if (!data[coverage[i]]) { 201 | data[coverage[i]] = []; 202 | } 203 | 204 | data[coverage[i]].push({ 205 | input: input, 206 | records: records 207 | }); 208 | } 209 | }); 210 | }); 211 | } else if (lookupType === 5 && format === 2) { 212 | var coverageOffset = buffer.read(Type.OFFSET); 213 | var classDefOffset = buffer.read(Type.OFFSET); 214 | var subClassSetCount = buffer.read(Type.USHORT); 215 | var subClassSetOffsets = buffer.readArray(Type.OFFSET, subClassSetCount); 216 | 217 | var coverage = common.Coverage(buffer, offset + coverageOffset); 218 | var classDef = common.ClassDef(buffer, offset + classDefOffset); 219 | 220 | subClassSetOffsets.forEach(function (subClassSetOffset) { 221 | buffer.goto(offset + subClassSetOffset); 222 | 223 | var subClassRuleCount = buffer.read(Type.USHORT); 224 | var subClassRuleOffsets = buffer.readArray(Type.OFFSET, subClassRuleCount); 225 | 226 | subClassRuleOffsets.forEach(function (subClassRuleOffset) { 227 | buffer.goto(offset + subClassSetOffset + subClassRuleOffset); 228 | 229 | var glyphCount = buffer.read(Type.USHORT); 230 | var substCount = buffer.read(Type.USHORT); 231 | var classes = buffer.readArray(Type.USHORT, glyphCount - 1); 232 | var records = buffer.readArray(gsub.SubstLookupRecord, substCount); 233 | }); 234 | }); 235 | } else if (lookupType === 5 && format === 3) { 236 | var glyphCount = buffer.read(Type.USHORT); 237 | var substCount = buffer.read(Type.USHORT); 238 | var coverageOffsets = buffer.readArray(Type.OFFSET, glyphCount); 239 | var records = buffer.readArray(gsub.SubstLookupRecord, substCount); 240 | 241 | coverageOffsets.forEach(function (coverageOffset) { 242 | coverage = common.Coverage(buffer, offset + coverageOffset); 243 | }); 244 | } else if (lookupType === 6 && format === 1) { 245 | } else if (lookupType === 6 && format === 2) { 246 | } else if (lookupType === 6 && format === 3) { 247 | } else if (lookupType === 7) { 248 | var extensionLookupType = buffer.read(Type.USHORT); 249 | var extensionOffset = buffer.read(Type.ULONG); 250 | data = gsub.LookupType(buffer, extensionLookupType, extensionOffset); 251 | } else if (lookupType === 8 && format === 1) { 252 | var coverageOffset = buffer.read(Type.OFFSET); 253 | var backtrackGlyphCount = buffer.read(Type.USHORT); 254 | var backtrackCoverageOffsets = buffer.readArray(Type.OFFSET, backtrackGlyphCount); 255 | var lookaheadGlyphCount = buffer.read(Type.USHORT); 256 | var lookaheadCoverageOffsets = buffer.readArray(Type.OFFSET, lookaheadGlyphCount); 257 | var glyphCount = buffer.read(Type.USHORT); 258 | var substitutes = buffer.readArray(Type.GLYPHID, glyphCount); 259 | } 260 | return data; 261 | }; 262 | 263 | module.exports = gsub; 264 | -------------------------------------------------------------------------------- /src/tables/head.js: -------------------------------------------------------------------------------- 1 | var ReadBuffer = require('../readbuffer'); 2 | var Type = require('../type'); 3 | var util = require('../util'); 4 | 5 | module.exports = function (buffer, font) { 6 | var table = new ReadBuffer(buffer); 7 | 8 | return table.read(util.struct({ 9 | version: Type.FIXED, 10 | fontRevision: Type.FIXED, 11 | checkSumAdjustment: Type.ULONG, 12 | magicNumber: Type.ULONG, 13 | flags: Type.USHORT, 14 | unitsPerEm: Type.USHORT, 15 | created: Type.LONGDATETIME, 16 | modified: Type.LONGDATETIME, 17 | xMin: Type.SHORT, 18 | yMin: Type.SHORT, 19 | xMax: Type.SHORT, 20 | yMax: Type.SHORT, 21 | macStyle: Type.USHORT, 22 | lowestRecPPEM: Type.USHORT, 23 | fontDirectionHint: Type.SHORT, 24 | indexToLocFormat: Type.SHORT, 25 | glyphDataFormat: Type.SHORT 26 | })); 27 | }; 28 | -------------------------------------------------------------------------------- /src/tables/hhea.js: -------------------------------------------------------------------------------- 1 | var ReadBuffer = require('../readbuffer'); 2 | var Type = require('../type'); 3 | var util = require('../util'); 4 | 5 | module.exports = function (buffer, font) { 6 | var table = new ReadBuffer(buffer); 7 | 8 | return table.read(util.struct({ 9 | version: Type.FIXED, 10 | ascender: Type.FWORD, 11 | descender: Type.FWORD, 12 | lineGap: Type.FWORD, 13 | advanceWidthMax: Type.UFWORD, 14 | minLeftSideBearing: Type.FWORD, 15 | minRightSideBearing: Type.FWORD, 16 | xMaxExtent: Type.FWORD, 17 | caretSlopeRise: Type.SHORT, 18 | caretSlopeRun: Type.SHORT, 19 | caretOffset: Type.SHORT, 20 | reserved1: Type.SHORT, 21 | reserved2: Type.SHORT, 22 | reserved3: Type.SHORT, 23 | reserved4: Type.SHORT, 24 | metricDataFormat: Type.SHORT, 25 | numberOfHMetrics: Type.USHORT 26 | })); 27 | }; 28 | -------------------------------------------------------------------------------- /src/tables/hmtx.js: -------------------------------------------------------------------------------- 1 | var ReadBuffer = require('../readbuffer'); 2 | var Type = require('../type'); 3 | var util = require('../util'); 4 | 5 | module.exports = function (buffer, font) { 6 | var table = new ReadBuffer(buffer), 7 | numberOfHMetrics = font.tables.hhea.numberOfHMetrics, 8 | numGlyphs = font.tables.maxp.numGlyphs - numberOfHMetrics; 9 | 10 | var data = {}; 11 | 12 | data.hMetrics = table.readArray(util.struct({ 13 | advanceWidth: Type.USHORT, 14 | lsb: Type.SHORT 15 | }), numberOfHMetrics); 16 | 17 | data.leftSideBearing = table.readArray(Type.SHORT, numGlyphs); 18 | 19 | return data; 20 | }; 21 | -------------------------------------------------------------------------------- /src/tables/maxp.js: -------------------------------------------------------------------------------- 1 | var ReadBuffer = require('../readbuffer'); 2 | var Type = require('../type'); 3 | var util = require('../util'); 4 | 5 | module.exports = function (buffer, font) { 6 | var table = new ReadBuffer(buffer); 7 | 8 | var data = table.read(util.struct({ 9 | version: Type.FIXED, 10 | numGlyphs: Type.USHORT 11 | })); 12 | 13 | if (data.version === 0x00010000) { 14 | util.extend(data, table.read(util.struct({ 15 | maxPoints: Type.USHORT, 16 | maxContours: Type.USHORT, 17 | maxCompositePoints: Type.USHORT, 18 | maxCompositeContours: Type.USHORT, 19 | maxZones: Type.USHORT, 20 | maxTwilightPoints: Type.USHORT, 21 | maxStorage: Type.USHORT, 22 | maxFunctionDefs: Type.USHORT, 23 | maxInstructionDefs: Type.USHORT, 24 | maxStackElements: Type.USHORT, 25 | maxSizeOfInstructions: Type.USHORT, 26 | maxComponentElements: Type.USHORT, 27 | maxComponentDepth: Type.USHORT 28 | }))); 29 | } 30 | 31 | return data; 32 | }; 33 | -------------------------------------------------------------------------------- /src/tables/name.js: -------------------------------------------------------------------------------- 1 | var ReadBuffer = require('../readbuffer'); 2 | var Type = require('../type'); 3 | var util = require('../util'); 4 | 5 | module.exports = function (buffer, font) { 6 | var table = new ReadBuffer(buffer); 7 | 8 | var data = {}; 9 | var format = table.read(Type.USHORT); 10 | var count = table.read(Type.USHORT); 11 | var stringOffset = table.read(Type.OFFSET); 12 | var langTagRecords = []; 13 | 14 | var nameRecords = table.readArray(util.struct({ 15 | platformID: Type.USHORT, 16 | encodingID: Type.USHORT, 17 | languageID: Type.USHORT, 18 | nameID: Type.USHORT, 19 | length: Type.USHORT, 20 | offset: Type.OFFSET 21 | }), count); 22 | 23 | if (format === 1) { 24 | var langTagRecordOffsets = table.readArray(util.struct({ 25 | length: Type.USHORT, 26 | offset: Type.OFFSET 27 | }), table.read(Type.USHORT)); 28 | 29 | langTagRecords = langTagRecordOffsets.map(function (record) { 30 | return table.readString('ascii', record.length, stringOffset + record.offset); 31 | }); 32 | } 33 | 34 | nameRecords.forEach(function (record) { 35 | var language = null; 36 | 37 | if (record.languageID < 0x8000) { 38 | language = Languages[record.platformID][record.languageID]; 39 | } else { 40 | language = langTagRecords[record.languageID - 0x8000]; 41 | } 42 | 43 | if (language) { 44 | var encoding = Encodings[record.platformID][record.encodingID]; 45 | var name = Names[record.nameID]; 46 | 47 | if (name) { 48 | if (!data[language]) { 49 | data[language] = {}; 50 | } 51 | 52 | data[language][name] = table.readString(encoding, record.length, stringOffset + record.offset); 53 | } 54 | } 55 | }); 56 | 57 | return data; 58 | }; 59 | 60 | var Names = [ 61 | 'copyright', 62 | 'fontFamily', 63 | 'fontSubfamily', 64 | 'uniqueSubfamily', 65 | 'fullName', 66 | 'version', 67 | 'postscriptName', 68 | 'trademark', 69 | 'manufacturer', 70 | 'designer', 71 | 'description', 72 | 'vendorURL', 73 | 'designerURL', 74 | 'license', 75 | 'licenseURL', 76 | null, 77 | 'preferredFamily', 78 | 'preferredSubfamily', 79 | 'compatibleFull', 80 | 'sampleText', 81 | 'postscriptCIDFontName', 82 | 'wwsFamilyName', 83 | 'wwsSubfamilyName' 84 | ]; 85 | 86 | var Encodings = [ 87 | [ 88 | 'utf16be', 89 | 'utf16be', 90 | 'utf16be', 91 | 'utf16be', 92 | 'utf16be', 93 | 'utf16be' 94 | ], [ 95 | 'macroman', 96 | 'shift-jis', 97 | 'big5', 98 | 'euc-kr', 99 | 'iso-8859-6', 100 | 'iso-8859-8', 101 | 'macgreek', 102 | 'maccyrillic', 103 | 'symbol', 104 | 'Devanagari', 105 | 'Gurmukhi', 106 | 'Gujarati', 107 | 'Oriya', 108 | 'Bengali', 109 | 'Tamil', 110 | 'Telugu', 111 | 'Kannada', 112 | 'Malayalam', 113 | 'Sinhalese', 114 | 'Burmese', 115 | 'Khmer', 116 | 'macthai', 117 | 'Laotian', 118 | 'Georgian', 119 | 'Armenian', 120 | 'gb-2312-80', 121 | 'Tibetan', 122 | 'Mongolian', 123 | 'Geez', 124 | 'maccyrillic', 125 | 'Vietnamese', 126 | 'Sindhi' 127 | ], [ 128 | 'ascii' 129 | ], [ 130 | 'symbol', 131 | 'utf16be', 132 | 'shift-jis', 133 | 'gb18030', 134 | 'big5', 135 | 'wansung', 136 | 'johab', 137 | null, 138 | null, 139 | null, 140 | 'ucs-4' 141 | ] 142 | ]; 143 | 144 | var Languages = [ 145 | {}, 146 | 147 | { 148 | 0: 'en', 149 | 1: 'fr', 150 | 2: 'de', 151 | 3: 'it', 152 | 4: 'nl', 153 | 5: 'sv', 154 | 6: 'es', 155 | 7: 'da', 156 | 8: 'pt', 157 | 9: 'no', 158 | 10: 'he', 159 | 11: 'ja', 160 | 12: 'arb', 161 | 13: 'fi', 162 | 14: 'el', 163 | 15: 'is', 164 | 16: 'mt', 165 | 17: 'tr', 166 | 18: 'hr', 167 | 19: 'zh-Hant', 168 | 20: 'ur', 169 | 21: 'hi', 170 | 22: 'th', 171 | 23: 'ko', 172 | 24: 'lt', 173 | 25: 'pl', 174 | 26: 'hu', 175 | 27: 'et', 176 | 28: 'lv', 177 | 29: 'smi', 178 | 30: 'fo', 179 | 31: 'fa', 180 | 32: 'ru', 181 | 33: 'zh-Hans', 182 | 34: 'nl-BE', 183 | 35: 'gle', 184 | 36: 'sq', 185 | 37: 'ro', 186 | 38: 'cs', 187 | 39: 'sk', 188 | 40: 'sl', 189 | 41: 'yi', 190 | 42: 'sr', 191 | 43: 'mk', 192 | 44: 'bg', 193 | 45: 'uk', 194 | 46: 'be', 195 | 47: 'uz', 196 | 48: 'kk', 197 | 49: 'az-Cyrl', 198 | 50: 'az-Arab', 199 | 51: 'hy', 200 | 52: 'ka', 201 | 53: 'mo', 202 | 54: 'ky', 203 | 55: 'tg', 204 | 56: 'tk', 205 | 57: 'mn-Mong', 206 | 58: 'mn-Cyrl', 207 | 59: 'ps', 208 | 60: 'ku', 209 | 61: 'ks', 210 | 62: 'sd', 211 | 63: 'bo', 212 | 64: 'ne', 213 | 65: 'sa', 214 | 66: 'mr', 215 | 67: 'bn', 216 | 68: 'as', 217 | 69: 'gu', 218 | 70: 'pa', 219 | 71: 'or', 220 | 72: 'ml', 221 | 73: 'kn', 222 | 74: 'ta', 223 | 75: 'te', 224 | 76: 'se', 225 | 77: 'my', 226 | 78: 'km', 227 | 79: 'lo', 228 | 80: 'vi', 229 | 81: 'id', 230 | 82: 'tl', 231 | 83: 'ms', 232 | 84: 'ms', 233 | 85: 'am', 234 | 86: 'ti', 235 | 87: 'gax', 236 | 88: 'so', 237 | 89: 'sw', 238 | 90: 'rw', 239 | 91: 'rn', 240 | 92: 'ny', 241 | 93: 'mg', 242 | 94: 'eo', 243 | 128: 'cy', 244 | 129: 'eu', 245 | 130: 'ca', 246 | 131: 'la', 247 | 132: 'qu', 248 | 133: 'gn', 249 | 134: 'ay', 250 | 135: 'tt', 251 | 136: 'ug', 252 | 137: 'dz', 253 | 138: 'jv', 254 | 139: 'su', 255 | 140: 'gl', 256 | 141: 'af', 257 | 142: 'br', 258 | 143: 'iu', 259 | 144: 'gd', 260 | 145: 'gv', 261 | 146: 'gle', 262 | 147: 'to', 263 | 148: 'el-polyton', 264 | 149: 'kl', 265 | 150: 'az' 266 | }, 267 | 268 | {}, 269 | 270 | { 271 | 4: "zh-CHS", 272 | 1025: "ar-SA", 273 | 1026: "bg-BG", 274 | 1027: "ca-ES", 275 | 1028: "zh-TW", 276 | 1029: "cs-CZ", 277 | 1030: "da-DK", 278 | 1031: "de-DE", 279 | 1032: "el-GR", 280 | 1033: "en-US", 281 | 1035: "fi-FI", 282 | 1036: "fr-FR", 283 | 1037: "he-IL", 284 | 1038: "hu-HU", 285 | 1039: "is-IS", 286 | 1040: "it-IT", 287 | 1041: "ja-JP", 288 | 1042: "ko-KR", 289 | 1043: "nl-NL", 290 | 1044: "nb-NO", 291 | 1045: "pl-PL", 292 | 1046: "pt-BR", 293 | 1048: "ro-RO", 294 | 1049: "ru-RU", 295 | 1050: "hr-HR", 296 | 1051: "sk-SK", 297 | 1052: "sq-AL", 298 | 1053: "sv-SE", 299 | 1054: "th-TH", 300 | 1055: "tr-TR", 301 | 1056: "ur-PK", 302 | 1057: "id-ID", 303 | 1058: "uk-UA", 304 | 1059: "be-BY", 305 | 1060: "sl-SI", 306 | 1061: "et-EE", 307 | 1062: "lv-LV", 308 | 1063: "lt-LT", 309 | 1065: "fa-IR", 310 | 1066: "vi-VN", 311 | 1067: "hy-AM", 312 | 1068: "Lt-az-AZ", 313 | 1069: "eu-ES", 314 | 1071: "mk-MK", 315 | 1078: "af-ZA", 316 | 1079: "ka-GE", 317 | 1080: "fo-FO", 318 | 1081: "hi-IN", 319 | 1086: "ms-MY", 320 | 1087: "kk-KZ", 321 | 1088: "ky-KZ", 322 | 1089: "sw-KE", 323 | 1091: "Lt-uz-UZ", 324 | 1092: "tt-RU", 325 | 1094: "pa-IN", 326 | 1095: "gu-IN", 327 | 1097: "ta-IN", 328 | 1098: "te-IN", 329 | 1099: "kn-IN", 330 | 1102: "mr-IN", 331 | 1103: "sa-IN", 332 | 1104: "mn-MN", 333 | 1110: "gl-ES", 334 | 1111: "kok-IN", 335 | 1114: "syr-SY", 336 | 1125: "div-MV", 337 | 2049: "ar-IQ", 338 | 2052: "zh-CN", 339 | 2055: "de-CH", 340 | 2057: "en-GB", 341 | 2058: "es-MX", 342 | 2060: "fr-BE", 343 | 2064: "it-CH", 344 | 2067: "nl-BE", 345 | 2068: "nn-NO", 346 | 2070: "pt-PT", 347 | 2074: "Lt-sr-SP", 348 | 2077: "sv-FI", 349 | 2092: "Cy-az-AZ", 350 | 2110: "ms-BN", 351 | 2115: "Cy-uz-UZ", 352 | 3073: "ar-EG", 353 | 3076: "zh-HK", 354 | 3079: "de-AT", 355 | 3081: "en-AU", 356 | 3082: "es-ES", 357 | 3084: "fr-CA", 358 | 3098: "Cy-sr-SP", 359 | 4097: "ar-LY", 360 | 4100: "zh-SG", 361 | 4103: "de-LU", 362 | 4105: "en-CA", 363 | 4106: "es-GT", 364 | 4108: "fr-CH", 365 | 5121: "ar-DZ", 366 | 5124: "zh-MO", 367 | 5127: "de-LI", 368 | 5129: "en-NZ", 369 | 5130: "es-CR", 370 | 5132: "fr-LU", 371 | 6145: "ar-MA", 372 | 6153: "en-IE", 373 | 6154: "es-PA", 374 | 6156: "fr-MC", 375 | 7169: "ar-TN", 376 | 7177: "en-ZA", 377 | 7178: "es-DO", 378 | 8193: "ar-OM", 379 | 8201: "en-JM", 380 | 8202: "es-VE", 381 | 9217: "ar-YE", 382 | 9225: "en-CB", 383 | 9226: "es-CO", 384 | 10241: "ar-SY", 385 | 10249: "en-BZ", 386 | 10250: "es-PE", 387 | 11265: "ar-JO", 388 | 11273: "en-TT", 389 | 11274: "es-AR", 390 | 12289: "ar-LB", 391 | 12297: "en-ZW", 392 | 12298: "es-EC", 393 | 13313: "ar-KW", 394 | 13321: "en-PH", 395 | 13322: "es-CL", 396 | 14337: "ar-AE", 397 | 14346: "es-UY", 398 | 15361: "ar-BH", 399 | 15370: "es-PY", 400 | 16385: "ar-QA", 401 | 16394: "es-BO", 402 | 17418: "es-SV", 403 | 18442: "es-HN", 404 | 19466: "es-NI", 405 | 20490: "es-PR", 406 | 31748: "zh-CHT" 407 | } 408 | ]; 409 | -------------------------------------------------------------------------------- /src/tables/os2.js: -------------------------------------------------------------------------------- 1 | var ReadBuffer = require('../readbuffer'); 2 | var Type = require('../type'); 3 | var util = require('../util'); 4 | 5 | module.exports = function (buffer, font) { 6 | var table = new ReadBuffer(buffer), 7 | data = {}; 8 | 9 | util.extend(data, table.read(util.struct({ 10 | version: Type.USHORT, 11 | xAvgCharWidth: Type.SHORT, 12 | usWeightClass: Type.USHORT, 13 | usWidthClass: Type.USHORT, 14 | fsType: Type.USHORT, 15 | ySubscriptXSize: Type.SHORT, 16 | ySubscriptYSize: Type.SHORT, 17 | ySubscriptXOffset: Type.SHORT, 18 | ySubscriptYOffset: Type.SHORT, 19 | ySuperscriptXSize: Type.SHORT, 20 | ySuperscriptYSize: Type.SHORT, 21 | ySuperscriptXOffset: Type.SHORT, 22 | ySuperscriptYOffset: Type.SHORT, 23 | yStrikeoutSize: Type.SHORT, 24 | yStrikeoutPosition: Type.SHORT, 25 | sFamilyClass: Type.SHORT 26 | }))); 27 | 28 | data.panose = table.read(util.struct({ 29 | bFamilyType: Type.BYTE, 30 | bSerifStyle: Type.BYTE, 31 | bWeight: Type.BYTE, 32 | bProportion: Type.BYTE, 33 | bContrast: Type.BYTE, 34 | bStrokeVariation: Type.BYTE, 35 | bArmStyle: Type.BYTE, 36 | bLetterform: Type.BYTE, 37 | bMidline: Type.BYTE, 38 | bXHeight: Type.BYTE 39 | })); 40 | 41 | util.extend(data, table.read(util.struct({ 42 | ulUnicodeRange1: Type.ULONG, 43 | ulUnicodeRange2: Type.ULONG, 44 | ulUnicodeRange3: Type.ULONG, 45 | ulUnicodeRange4: Type.ULONG, 46 | }))); 47 | 48 | data.achVendID = table.read(Type.TAG); 49 | 50 | util.extend(data, table.read(util.struct({ 51 | fsSelection: Type.USHORT, 52 | usFirstCharIndex: Type.USHORT, 53 | usLastCharIndex: Type.USHORT, 54 | sTypoAscender: Type.SHORT, 55 | sTypoDescender: Type.SHORT, 56 | sTypoLineGap: Type.SHORT, 57 | usWinAscent: Type.USHORT, 58 | usWinDescent: Type.USHORT, 59 | }))); 60 | 61 | if (data.version >= 1) { 62 | util.extend(data, table.read(util.struct({ 63 | ulCodePageRange1: Type.ULONG, 64 | ulCodePageRange2: Type.ULONG, 65 | }))); 66 | } 67 | 68 | if (data.version >= 2) { 69 | util.extend(data, table.read(util.struct({ 70 | sxHeight: Type.SHORT, 71 | sCapHeight: Type.SHORT, 72 | usDefaultChar: Type.USHORT, 73 | usBreakChar: Type.USHORT, 74 | usMaxContext: Type.USHORT 75 | }))); 76 | } 77 | 78 | if (data.version >= 5) { 79 | util.extend(data, table.read(util.struct({ 80 | usLowerOpticalPointSize: Type.USHORT, 81 | usUpperOpticalPointSize: Type.USHORT 82 | }))); 83 | } 84 | 85 | return data; 86 | }; 87 | -------------------------------------------------------------------------------- /src/tables/post.js: -------------------------------------------------------------------------------- 1 | var ReadBuffer = require('../readbuffer'); 2 | var Type = require('../type'); 3 | var util = require('../util'); 4 | 5 | module.exports = function (buffer, font) { 6 | var table = new ReadBuffer(buffer); 7 | 8 | var data = table.read(util.struct({ 9 | version: Type.FIXED, 10 | italicAngle: Type.FIXED, 11 | underlinePosition: Type.FWORD, 12 | underlineThickness: Type.FWORD, 13 | isFixedPitch: Type.ULONG, 14 | minMemType42: Type.ULONG, 15 | maxMemType42: Type.ULONG, 16 | minMemType1: Type.ULONG, 17 | maxMemType1: Type.ULONG 18 | })); 19 | 20 | data.glyphNames = {}; 21 | 22 | if (data.version === 0x00010000) { 23 | for (var i = 0; i < 259; i += 1) { 24 | data.glyphNames[i] = MacRomanIdentifiers[i]; 25 | } 26 | } else if (data.version === 0x00020000) { 27 | var numberOfGlyphs = table.read(Type.USHORT); 28 | var glyphNameIndex = table.readArray(Type.USHORT, numberOfGlyphs); 29 | var glyphNames = [].concat(MacRomanIdentifiers); 30 | var names = []; 31 | 32 | for (var i = 0; i < numberOfGlyphs; i++) { 33 | var index = glyphNameIndex[i]; 34 | 35 | if (index >= 258) { 36 | names.push(table.readString('ascii', table.read(Type.BYTE))); 37 | } 38 | } 39 | 40 | for (var i = 0; i < numberOfGlyphs; i++) { 41 | var index = glyphNameIndex[i]; 42 | 43 | if (index < 258) { 44 | data.glyphNames[i] = glyphNames[index]; 45 | } else { 46 | data.glyphNames[i] = names[index - 258]; 47 | } 48 | } 49 | } 50 | 51 | return data; 52 | }; 53 | 54 | var MacRomanIdentifiers = ['.notdef', '.null', 'nonmarkingreturn', 'space', 'exclam', 'quotedbl', 'numbersign', 'dollar', 'percent', 'ampersand', 'quotesingle', 'parenleft', 'parenright', 'asterisk', 'plus', 'comma', 'hyphen', 'period', 'slash', 'zero', 'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'colon', 'semicolon', 'less', 'equal', 'greater', 'question', 'at', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'bracketleft', 'backslash', 'bracketright', 'asciicircum', 'underscore', 'grave', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'braceleft', 'bar', 'braceright', 'asciitilde', 'Adieresis', 'Aring', 'Ccedilla', 'Eacute', 'Ntilde', 'Odieresis', 'Udieresis', 'aacute', 'agrave', 'acircumflex', 'adieresis', 'atilde', 'aring', 'ccedilla', 'eacute', 'egrave', 'ecircumflex', 'edieresis', 'iacute', 'igrave', 'icircumflex', 'idieresis', 'ntilde', 'oacute', 'ograve', 'ocircumflex', 'odieresis', 'otilde', 'uacute', 'ugrave', 'ucircumflex', 'udieresis', 'dagger', 'degree', 'cent', 'sterling', 'section', 'bullet', 'paragraph', 'germandbls', 'registered', 'copyright', 'trademark', 'acute', 'dieresis', 'notequal', 'AE', 'Oslash', 'infinity', 'plusminus', 'lessequal', 'greaterequal', 'yen', 'mu', 'partialdiff', 'summation', 'product', 'pi', 'integral', 'ordfeminine', 'ordmasculine', 'Omega', 'ae', 'oslash', 'questiondown', 'exclamdown', 'logicalnot', 'radical', 'florin', 'approxequal', 'Delta', 'guillemotleft', 'guillemotright', 'ellipsis', 'nonbreakingspace', 'Agrave', 'Atilde', 'Otilde', 'OE', 'oe', 'endash', 'emdash', 'quotedblleft', 'quotedblright', 'quoteleft', 'quoteright', 'divide', 'lozenge', 'ydieresis', 'Ydieresis', 'fraction', 'currency', 'guilsinglleft', 'guilsinglright', 'fi', 'fl', 'daggerdbl', 'periodcentered', 'quotesinglbase', 'quotedblbase', 'perthousand', 'Acircumflex', 'Ecircumflex', 'Aacute', 'Edieresis', 'Egrave', 'Iacute', 'Icircumflex', 'Idieresis', 'Igrave', 'Oacute', 'Ocircumflex', 'apple', 'Ograve', 'Uacute', 'Ucircumflex', 'Ugrave', 'dotlessi', 'circumflex', 'tilde', 'macron', 'breve', 'dotaccent', 'ring', 'cedilla', 'hungarumlaut', 'ogonek', 'caron', 'Lslash', 'lslash', 'Scaron', 'scaron', 'Zcaron', 'zcaron', 'brokenbar', 'Eth', 'eth', 'Yacute', 'yacute', 'Thorn', 'thorn', 'minus', 'multiply', 'onesuperior', 'twosuperior', 'threesuperior', 'onehalf', 'onequarter', 'threequarters', 'franc', 'Gbreve', 'gbreve', 'Idotaccent', 'Scedilla', 'scedilla', 'Cacute', 'cacute', 'Ccaron', 'ccaron', 'dcroat']; 55 | -------------------------------------------------------------------------------- /src/type.js: -------------------------------------------------------------------------------- 1 | var Int64 = require('node-int64') 2 | 3 | /** 4 | * @enum {Struct} 5 | */ 6 | var Type = { 7 | BYTE: { 8 | sizeof: 1, 9 | 10 | /** 11 | * @param {Buffer} buffer 12 | * @param {number=} opt_byteOffset 13 | * @return {number} 14 | */ 15 | read: function (buffer, opt_byteOffset) { 16 | return buffer.readUInt8(opt_byteOffset || 0); 17 | } 18 | }, 19 | 20 | CHAR: { 21 | sizeof: 1, 22 | 23 | /** 24 | * @param {Buffer} buffer 25 | * @param {number=} opt_byteOffset 26 | * @return {number} 27 | */ 28 | read: function (buffer, opt_byteOffset) { 29 | return buffer.readInt8(opt_byteOffset || 0); 30 | } 31 | }, 32 | 33 | USHORT: { 34 | sizeof: 2, 35 | 36 | /** 37 | * @param {Buffer} buffer 38 | * @param {number=} opt_byteOffset 39 | * @return {number} 40 | */ 41 | read: function (buffer, opt_byteOffset) { 42 | return buffer.readUInt16BE(opt_byteOffset || 0); 43 | } 44 | }, 45 | 46 | SHORT: { 47 | sizeof: 2, 48 | 49 | /** 50 | * @param {Buffer} buffer 51 | * @param {number=} opt_byteOffset 52 | * @return {number} 53 | */ 54 | read: function (buffer, opt_byteOffset) { 55 | return buffer.readInt16BE(opt_byteOffset || 0); 56 | } 57 | }, 58 | 59 | ULONG: { 60 | sizeof: 4, 61 | 62 | /** 63 | * @param {Buffer} buffer 64 | * @param {number=} opt_byteOffset 65 | * @return {number} 66 | */ 67 | read: function (buffer, opt_byteOffset) { 68 | return buffer.readUInt32BE(opt_byteOffset || 0); 69 | } 70 | }, 71 | 72 | LONG: { 73 | sizeof: 4, 74 | 75 | /** 76 | * @param {Buffer} buffer 77 | * @param {number=} opt_byteOffset 78 | * @return {number} 79 | */ 80 | read: function (buffer, opt_byteOffset) { 81 | return buffer.readInt32BE(opt_byteOffset || 0); 82 | } 83 | }, 84 | 85 | TAG: { 86 | sizeof: 4, 87 | 88 | /** 89 | * @param {Buffer} buffer 90 | * @param {number=} opt_byteOffset 91 | * @return {string} 92 | */ 93 | read: function (buffer, opt_byteOffset) { 94 | return buffer.toString('ascii', opt_byteOffset || 0, (opt_byteOffset || 0) + 4); 95 | } 96 | }, 97 | 98 | FIXED: { 99 | sizeof: 4, 100 | 101 | /** 102 | * @param {Buffer} buffer 103 | * @param {number=} opt_byteOffset 104 | * @return {number} 105 | */ 106 | read: function (buffer, opt_byteOffset) { 107 | return buffer.readUInt32BE(opt_byteOffset || 0); 108 | } 109 | }, 110 | 111 | LONGDATETIME: { 112 | sizeof: 8, 113 | 114 | /** 115 | * @param {Buffer} buffer 116 | * @param {number=} opt_byteOffset 117 | * @return {Int64} 118 | */ 119 | read: function (buffer, opt_byteOffset) { 120 | return new Int64(buffer.slice(opt_byteOffset || 0, (opt_byteOffset || 0) + 8)); 121 | } 122 | }, 123 | 124 | UINT24: { 125 | sizeof: 3, 126 | 127 | /** 128 | * @param {Buffer} buffer 129 | * @param {number=} opt_byteOffset 130 | * @return {number} 131 | */ 132 | read: function (buffer, opt_byteOffset) { 133 | return buffer.readUIntBE(opt_byteOffset || 0, 3); 134 | } 135 | }, 136 | 137 | BASE128: { 138 | /** 139 | * Variable size, set automatically by the 140 | * read method. 141 | */ 142 | sizeof: 0, 143 | 144 | /** 145 | * @param {Buffer} buffer 146 | * @param {number=} opt_byteOffset 147 | * @return {number} 148 | */ 149 | read: function (buffer, opt_byteOffset) { 150 | var result = 0; 151 | this.sizeof = 0; 152 | 153 | for (var i = 0; i < 5; i++) { 154 | data = buffer.readUInt8((opt_byteOffset || 0) + i); 155 | 156 | if (result & 0xFE0000000) { 157 | throw new Error('Base 128 number overflow'); 158 | } 159 | 160 | result = (result << 7) | (data & 0x7F); 161 | 162 | if ((data & 0x80) === 0) { 163 | this.sizeof = i + 1; 164 | return result; 165 | } 166 | } 167 | throw new Error('Bad base 128 number'); 168 | } 169 | } 170 | }; 171 | 172 | // Aliases 173 | Type.FWORD = Type.SHORT; 174 | Type.UFWORD = Type.USHORT; 175 | Type.GLYPHID = Type.USHORT; 176 | Type.OFFSET = Type.USHORT; 177 | 178 | module.exports = Type; 179 | -------------------------------------------------------------------------------- /src/util.js: -------------------------------------------------------------------------------- 1 | var util = {}; 2 | 3 | /** 4 | * @param {number} bufferLength 5 | * @param {number=} opt_blockSize 6 | * @return {number} 7 | */ 8 | util.pad = function (bufferLength, opt_blockSize) { 9 | var blockSize = opt_blockSize || 4; 10 | 11 | return bufferLength % blockSize === 0 ? bufferLength : bufferLength + (blockSize - (bufferLength % blockSize)); 12 | }; 13 | 14 | /** 15 | * @param {...*} var_args 16 | * @return {*} 17 | */ 18 | util.extend = function (var_args) { 19 | for (var i = 1; i < arguments.length; i += 1) { 20 | for (var p in arguments[i]) { 21 | if (arguments[i].hasOwnProperty(p)) { 22 | arguments[0][p] = arguments[i][p]; 23 | } 24 | } 25 | } 26 | return arguments[0]; 27 | }; 28 | 29 | /** 30 | * Create a struct of a fixed size. 31 | * 32 | * @param {Object.} types 33 | * @return {opentype.Struct} 34 | */ 35 | util.struct = function (types) { 36 | var sizeof = 0; 37 | 38 | for (var key in types) { 39 | sizeof += types[key].sizeof; 40 | } 41 | 42 | return { 43 | sizeof: sizeof, 44 | read: function (buffer, opt_byteOffset) { 45 | var byteOffset = opt_byteOffset || 0, 46 | struct = {}; 47 | 48 | for (var key in types) { 49 | struct[key] = types[key].read(buffer, byteOffset); 50 | byteOffset += types[key].sizeof; 51 | } 52 | 53 | return struct; 54 | } 55 | }; 56 | }; 57 | 58 | module.exports = util; 59 | -------------------------------------------------------------------------------- /src/woff.js: -------------------------------------------------------------------------------- 1 | var Type = require('./type'); 2 | var util = require('./util'); 3 | 4 | var TableDirectory = util.struct({ 5 | tag: Type.TAG, 6 | offset: Type.ULONG, 7 | compLength: Type.ULONG, 8 | origLength: Type.ULONG, 9 | origChecksum: Type.ULONG 10 | }); 11 | 12 | var Header = util.struct({ 13 | signature: Type.ULONG, 14 | flavor: Type.ULONG, 15 | length: Type.ULONG, 16 | numTables: Type.USHORT, 17 | reserved: Type.USHORT, 18 | totalSfntSize: Type.ULONG, 19 | majorVersion: Type.USHORT, 20 | minorVersion: Type.USHORT, 21 | metaOffset: Type.ULONG, 22 | metaLength: Type.ULONG, 23 | metaOrigLength: Type.ULONG, 24 | privOffset: Type.ULONG, 25 | privLength: Type.ULONG 26 | }); 27 | 28 | module.exports = { 29 | Header: Header, 30 | TableDirectory: TableDirectory 31 | }; 32 | -------------------------------------------------------------------------------- /src/woff2.js: -------------------------------------------------------------------------------- 1 | var Type = require('./type'); 2 | var util = require('./util'); 3 | 4 | var TableDirectoryEntry = util.struct({ 5 | flags: Type.BYTE, 6 | tag: Type.TAG, 7 | origLength: Type.BASE128, 8 | transformLength: Type.BASE128 9 | }); 10 | 11 | var Flags = [ 12 | 'cmap', 13 | 'head', 14 | 'hhea', 15 | 'hmtx', 16 | 'maxp', 17 | 'name', 18 | 'OS/2', 19 | 'post', 20 | 'cvt ', 21 | 'fpgm', 22 | 'glyf', 23 | 'loca', 24 | 'prep', 25 | 'CFF ', 26 | 'VORG', 27 | 'EBDT', 28 | 'EBLC', 29 | 'gasp', 30 | 'hdmx', 31 | 'kern', 32 | 'LTSH', 33 | 'PCLT', 34 | 'VDMX', 35 | 'vhea', 36 | 'vmtx', 37 | 'BASE', 38 | 'GDEF', 39 | 'GPOS', 40 | 'GSUB', 41 | 'EBSC', 42 | 'JSTF', 43 | 'MATH', 44 | 'CBDT', 45 | 'CBLC', 46 | 'COLR', 47 | 'CPAL', 48 | 'SVG ', 49 | 'sbix', 50 | 'acnt', 51 | 'avar', 52 | 'bdat', 53 | 'bloc', 54 | 'bsln', 55 | 'cvar', 56 | 'fdsc', 57 | 'feat', 58 | 'fmtx', 59 | 'fvar', 60 | 'gvar', 61 | 'hsty', 62 | 'just', 63 | 'lcar', 64 | 'mort', 65 | 'morx', 66 | 'opbd', 67 | 'prop', 68 | 'trak', 69 | 'Zapf', 70 | 'Silf', 71 | 'Glat', 72 | 'Gloc', 73 | 'Feat', 74 | 'Sill' 75 | ]; 76 | 77 | var Header = util.struct({ 78 | signature: Type.ULONG, 79 | flavor: Type.ULONG, 80 | length: Type.ULONG, 81 | numTables: Type.USHORT, 82 | reserved: Type.USHORT, 83 | totalSfntSize: Type.ULONG, 84 | totalCompressedSize: Type.ULONG, 85 | majorVersion: Type.USHORT, 86 | minorVersion: Type.USHORT, 87 | metaOffset: Type.ULONG, 88 | metaLength: Type.ULONG, 89 | metaOrigLength: Type.ULONG, 90 | privOffset: Type.ULONG, 91 | privLength: Type.ULONG 92 | }); 93 | 94 | module.exports = { 95 | Header: Header, 96 | TableDirectoryEntry: TableDirectoryEntry, 97 | Flags: Flags 98 | }; 99 | -------------------------------------------------------------------------------- /test/basic-test.js: -------------------------------------------------------------------------------- 1 | var expect = require('unexpected'); 2 | 3 | describe('basic', function () { 4 | it('can be required without the path to the main source file', function () { 5 | var opentype = require('../') 6 | 7 | expect(typeof opentype, 'to equal', 'object'); 8 | expect(typeof opentype.parse, 'to equal', 'function'); 9 | }); 10 | }); 11 | -------------------------------------------------------------------------------- /test/fonts/sourcesanspro-regular.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bramstein/opentype/cf1319bf7876a96c6323f855cc885343d4c5258c/test/fonts/sourcesanspro-regular.otf -------------------------------------------------------------------------------- /test/fonts/sourcesanspro-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bramstein/opentype/cf1319bf7876a96c6323f855cc885343d4c5258c/test/fonts/sourcesanspro-regular.ttf -------------------------------------------------------------------------------- /test/fonts/sourcesanspro-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bramstein/opentype/cf1319bf7876a96c6323f855cc885343d4c5258c/test/fonts/sourcesanspro-regular.woff -------------------------------------------------------------------------------- /test/fonts/sourcesanspro-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bramstein/opentype/cf1319bf7876a96c6323f855cc885343d4c5258c/test/fonts/sourcesanspro-regular.woff2 -------------------------------------------------------------------------------- /test/readbuffer-test.js: -------------------------------------------------------------------------------- 1 | var ReadBuffer = require('../src/readbuffer'); 2 | var Type = require('../src/type'); 3 | var util = require('../src/util'); 4 | var c = require('concentrate'); 5 | var expect = require('unexpected'); 6 | 7 | describe('ReadBuffer', function () { 8 | var data = null; 9 | 10 | beforeEach(function () { 11 | data = c() 12 | .uint8(10) 13 | .uint8(20) 14 | .uint8(30) 15 | .uint8(40) 16 | .result(); 17 | }); 18 | 19 | it('creates a new ReadBuffer', function () { 20 | var buffer = new ReadBuffer(data); 21 | 22 | expect(buffer, 'to be an object'); 23 | expect(buffer.byteOffset, 'to equal', 0); 24 | }); 25 | 26 | it('creates a new ReadBuffer with an offset', function () { 27 | var buffer = new ReadBuffer(data, 2); 28 | 29 | expect(buffer, 'to be an object'); 30 | expect(buffer.byteOffset, 'to equal', 2); 31 | }); 32 | 33 | it('can goto any offset', function () { 34 | var buffer = new ReadBuffer(data); 35 | 36 | buffer.goto(2); 37 | expect(buffer.byteOffset, 'to equal', 2); 38 | }); 39 | 40 | it('can read bytes', function () { 41 | var buffer = new ReadBuffer(data); 42 | 43 | expect(buffer.read(Type.BYTE), 'to equal', 10); 44 | expect(buffer.read(Type.BYTE), 'to equal', 20); 45 | expect(buffer.read(Type.BYTE), 'to equal', 30); 46 | expect(buffer.read(Type.BYTE), 'to equal', 40); 47 | }); 48 | 49 | it('can goto any offset and read bytes', function () { 50 | var buffer = new ReadBuffer(data); 51 | 52 | buffer.goto(2); 53 | expect(buffer.read(Type.BYTE, 2), 'to equal', 30); 54 | expect(buffer.read(Type.BYTE, 1), 'to equal', 20); 55 | }); 56 | 57 | it('can read an array of bytes', function () { 58 | var buffer = new ReadBuffer(data); 59 | 60 | expect(buffer.readArray(Type.BYTE, 4), 'to equal', [10, 20, 30, 40]); 61 | }); 62 | 63 | it('can goto any offset and read an array of bytes', function () { 64 | var buffer = new ReadBuffer(data); 65 | 66 | expect(buffer.readArray(Type.BYTE, 2, 2), 'to equal', [30, 40]); 67 | }); 68 | 69 | it('can read a complex struct', function () { 70 | var buffer = new ReadBuffer(data); 71 | 72 | expect(buffer.read(util.struct({ first: Type.BYTE, second: Type.BYTE })), 'to equal', { first: 10, second: 20 }); 73 | }); 74 | 75 | it('can read an array of complex structs', function () { 76 | var buffer = new ReadBuffer(data); 77 | 78 | expect(buffer.readArray(util.struct({ first: Type.BYTE, second: Type.BYTE }), 2), 'to equal', [{ first: 10, second: 20 }, { first: 30, second: 40 }]); 79 | }); 80 | 81 | it('can read a string', function () { 82 | var data = c() 83 | .string('hello') 84 | .result(); 85 | 86 | expect(new ReadBuffer(data).readString('ascii', 5), 'to equal', 'hello'); 87 | }); 88 | 89 | it('can read a string at an offset', function () { 90 | var data = c() 91 | .uint8(10) 92 | .string('hello') 93 | .result(); 94 | 95 | expect(new ReadBuffer(data).readString('ascii', 5, 1), 'to equal', 'hello'); 96 | }); 97 | }); 98 | -------------------------------------------------------------------------------- /test/tables/cmap-test.js: -------------------------------------------------------------------------------- 1 | var cmap = require('../../src/tables/cmap'); 2 | var expect = require('unexpected'); 3 | var c = require('concentrate'); 4 | 5 | describe('tables.cmap', function () { 6 | it('parses format 0', function () { 7 | var buffer = c() 8 | .uint16be(0) // version (0) 9 | .uint16be(1) // numTables (2) 10 | .uint16be(1) // platformID (4) 11 | .uint16be(1) // encodingID (6) 12 | .uint32be(12) // offset (8) 13 | .uint16be(0) // format 14 | .uint16be(256) // length 15 | .uint16be(0); // language 16 | 17 | var charCodes = {}; 18 | 19 | for (var i = 0; i < 256; i++) { 20 | charCodes[i] = 255 - i; 21 | buffer.uint8(255 - i); 22 | } 23 | 24 | expect(cmap(buffer.result(), {}), 'to equal', [{ 25 | format: 0, 26 | platformID: 1, 27 | encodingID: 1, 28 | language: 0, 29 | charCodes: charCodes 30 | }]); 31 | }); 32 | 33 | it('parses format 4', function () { 34 | var buffer = c() 35 | .uint16be(0) // version (0) 36 | .uint16be(1) // numTables (2) 37 | .uint16be(1) // platformID (4) 38 | .uint16be(1) // encodingID (6) 39 | .uint32be(12) // offset (8) 40 | .uint16be(4) // format (12) 41 | .uint16be(256) // length (14) 42 | .uint16be(0) // language (16) 43 | .uint16be(8) // segCountX2 (18) 44 | .uint16be(8) // searchRange (20) 45 | .uint16be(4) // entrySelector (22) 46 | .uint16be(0) // rangeShift (24) 47 | .uint16be(20) // endCode[0] 48 | .uint16be(90) // endCode[1] 49 | .uint16be(480) // endCode[2] 50 | .uint16be(0xffff) // endCode[3] 51 | .uint16be(0) // reservedPad 52 | .uint16be(10) // startCode[0] 53 | .uint16be(30) // startCode[1] 54 | .uint16be(153) // startCode[2] 55 | .uint16be(0xffff) // startCode[3] 56 | .int16be(-9) // idDelta[0] 57 | .int16be(-18) // idDelta[1] 58 | // TODO: This example is from the spec, but the given value "-27" 59 | // does not create a contiguous range. 60 | .int16be(-80) // idDelta[2] 61 | .int16be(1) // idDelta[3] 62 | .uint16be(0) // idRangeOffset[0] 63 | .uint16be(0) // idRangeOffset[1] 64 | .uint16be(0) // idRangeOffset[2] 65 | .uint16be(0) // idRangeOffser[3] 66 | 67 | var charCodes = {}; 68 | 69 | for (var charCode = 10; charCode <= 20; charCode++) { 70 | charCodes[charCode] = charCode - 9; 71 | } 72 | 73 | for (var charCode = 30; charCode <= 90; charCode++) { 74 | charCodes[charCode] = charCode - 18; 75 | } 76 | 77 | for (var charCode = 153; charCode <= 480; charCode++) { 78 | charCodes[charCode] = charCode - 80; 79 | } 80 | 81 | expect(cmap(buffer.result(), {}), 'to equal', [{ 82 | format: 4, 83 | platformID: 1, 84 | encodingID: 1, 85 | language: 0, 86 | charCodes: charCodes 87 | }]); 88 | }); 89 | 90 | it('parses format 12', function () { 91 | var buffer = c() 92 | .uint16be(0) // version (0) 93 | .uint16be(1) // numTables (2) 94 | .uint16be(1) // platformID (4) 95 | .uint16be(1) // encodingID (6) 96 | .uint32be(12) // offset (8) 97 | .uint16be(12) // format (12) 98 | .uint16be(0) // reserved (14) 99 | .uint32be(256) // length (16) 100 | .uint32be(0) // language (20) 101 | .uint32be(2) // nGroups (24) 102 | .uint32be(32) // starCharCode[0] 103 | .uint32be(34) // endCharCode[0] 104 | .uint32be(1) // startGlyphId[0] 105 | .uint32be(50) // startCharCode[1] 106 | .uint32be(55) // endCharCode[1] 107 | .uint32be(4) // startGlyphId[1] 108 | 109 | var charCodes = { 110 | 32: 1, 111 | 33: 2, 112 | 34: 3, 113 | 50: 4, 114 | 51: 5, 115 | 52: 6, 116 | 53: 7, 117 | 54: 8, 118 | 55: 9 119 | }; 120 | 121 | expect(cmap(buffer.result(), {}), 'to equal', [{ 122 | format: 12, 123 | platformID: 1, 124 | encodingID: 1, 125 | language: 0, 126 | charCodes: charCodes 127 | }]); 128 | }); 129 | 130 | it('parses format 13', function () { 131 | var buffer = c() 132 | .uint16be(0) // version (0) 133 | .uint16be(1) // numTables (2) 134 | .uint16be(1) // platformID (4) 135 | .uint16be(1) // encodingID (6) 136 | .uint32be(12) // offset (8) 137 | .uint16be(13) // format (12) 138 | .uint16be(0) // reserved (14) 139 | .uint32be(256) // length (16) 140 | .uint32be(0) // language (20) 141 | .uint32be(2) // nGroups (24) 142 | .uint32be(32) // starCharCode[0] 143 | .uint32be(34) // endCharCode[0] 144 | .uint32be(1) // startGlyphId[0] 145 | .uint32be(50) // startCharCode[1] 146 | .uint32be(55) // endCharCode[1] 147 | .uint32be(4) // startGlyphId[1] 148 | 149 | var charCodes = { 150 | 32: 1, 151 | 33: 1, 152 | 34: 1, 153 | 50: 4, 154 | 51: 4, 155 | 52: 4, 156 | 53: 4, 157 | 54: 4, 158 | 55: 4 159 | }; 160 | 161 | expect(cmap(buffer.result(), {}), 'to equal', [{ 162 | format: 13, 163 | platformID: 1, 164 | encodingID: 1, 165 | language: 0, 166 | charCodes: charCodes 167 | }]); 168 | }); 169 | 170 | it('parses format 14', function () { 171 | var buffer = c() 172 | .uint16be(0) // version (0) 173 | .uint16be(1) // numTables (2) 174 | .uint16be(1) // platformID (4) 175 | .uint16be(1) // encodingID (6) 176 | .uint32be(12) // offset (8) 177 | .uint16be(14) // format (12) 178 | .uint32be(256) // length (14) 179 | .uint32be(1) // numVarSelectorRecords (18) 180 | .buffer(new Buffer([0, 0, 0])) // varSelector (22) 181 | .uint32be(33) // defaultUVSOffset (25) 182 | .uint32be(0) // nonDefaultUVSOffset (29) 183 | .uint32be(1) // numUnicodeValueRanges (33) 184 | .buffer(new Buffer([0, 0, 1])) // startUnicodeValue 185 | .uint8(0); 186 | 187 | expect(cmap(buffer.result(), {}), 'to equal', [{ 188 | format: 14, 189 | platformID: 1, 190 | encodingID: 1, 191 | language: 0, 192 | charCodes: {} 193 | }]); 194 | }); 195 | 196 | it('parses multiple formats', function () { 197 | var buffer = c() 198 | .uint16be(0) // version (0) 199 | .uint16be(2) // numTables (2) 200 | .uint16be(1) // platformID[0] (4) 201 | .uint16be(1) // encodingID[0] (6) 202 | .uint32be(20) // offset[0] (8) 203 | .uint16be(1) // platformID[1] (12) 204 | .uint16be(1) // encodingID[1] (14) 205 | .uint32be(48) // offset[1] (16) 206 | .uint16be(12) // format (20) 207 | .uint16be(0) // reserved (22) 208 | .uint32be(256) // length (24) 209 | .uint32be(0) // language (28) 210 | .uint32be(1) // nGroups (32) 211 | .uint32be(10) // starCharCode[0] (36) 212 | .uint32be(15) // endCharCode[0] (40) 213 | .uint32be(1) // startGlyphId[0] (44) 214 | .uint16be(13) // format (48) 215 | .uint16be(0) // reserved 216 | .uint32be(256) // length 217 | .uint32be(0) // language 218 | .uint32be(1) // nGroups 219 | .uint32be(25) // starCharCode[0] 220 | .uint32be(30) // endCharCode[0] 221 | .uint32be(1); // startGlyphId[0] 222 | 223 | expect(cmap(buffer.result(), {}), 'to equal', [{ 224 | format: 12, 225 | platformID: 1, 226 | encodingID: 1, 227 | language: 0, 228 | charCodes: { 229 | 10: 1, 230 | 11: 2, 231 | 12: 3, 232 | 13: 4, 233 | 14: 5, 234 | 15: 6 235 | } 236 | }, { 237 | format: 13, 238 | platformID: 1, 239 | encodingID: 1, 240 | language: 0, 241 | charCodes: { 242 | 25: 1, 243 | 26: 1, 244 | 27: 1, 245 | 28: 1, 246 | 29: 1, 247 | 30: 1 248 | } 249 | }]); 250 | }); 251 | }); 252 | -------------------------------------------------------------------------------- /test/tables/common-test.js: -------------------------------------------------------------------------------- 1 | var common = require('../../src/tables/common'); 2 | var expect = require('unexpected'); 3 | var c = require('concentrate'); 4 | var ReadBuffer = require('../../src/readbuffer'); 5 | 6 | describe('tables.common', function () { 7 | it('parses a List', function () { 8 | var data = c() 9 | .uint16be(2) // count 10 | .string("ONE ", "ascii") // tag[0] 11 | .uint16be(10) // offset[0] 12 | .string("TWO ", "ascii") // tag[1] 13 | .uint16be(20) // ofset[1] 14 | .result(); 15 | 16 | expect(common.List(new ReadBuffer(data), 0, function (buffer, offset) { 17 | return offset; 18 | }), 'to equal', [{ 19 | tag: "ONE ", 20 | table: 10 21 | }, { 22 | tag: "TWO ", 23 | table: 20 24 | }]); 25 | }); 26 | 27 | it('parses a Script record', function () { 28 | var data = c() 29 | .uint16be(0) // defaultLangSys (0) 30 | .uint16be(1) // langSysCount (2) 31 | .string("EN ", "ascii") // tag[0] (4) 32 | .uint16be(10) // offset[0] (8) 33 | .uint16be(20) // LookupOrder[0] (10) 34 | .uint16be(30) // ReqFeatureIndex[0] (12) 35 | .uint16be(1) // FeatureCount[0] (14) 36 | .uint16be(40) // FeatureIndex[0] (16) 37 | .result(); 38 | 39 | expect(common.Script(new ReadBuffer(data), 0), 'to equal', [{ 40 | tag: "EN ", 41 | table: { 42 | LookupOrder: 20, 43 | ReqFeatureIndex: 30, 44 | FeatureCount: 1, 45 | FeatureIndex: [40] 46 | } 47 | }]); 48 | }); 49 | 50 | it('parses two Script records', function () { 51 | var data = c() 52 | .uint16be(0) // defaultLangSys (0) 53 | .uint16be(2) // langSysCount (2) 54 | .string("EN ", "ascii") // tag[0] (4) 55 | .uint16be(16) // offset[0] (8) 56 | .string("NL ", "ascii") // tag[1] (10) 57 | .uint16be(24) // offset [1] (14) 58 | 59 | .uint16be(20) // LookupOrder[0] (16) 60 | .uint16be(30) // ReqFeatureIndex[0] (18) 61 | .uint16be(1) // FeatureCount[0] (20) 62 | .uint16be(40) // FeatureIndex[0] (22) 63 | 64 | .uint16be(60) // LookupOrder[1] (24) 65 | .uint16be(70) // ReqFeatureIndex[1] (26) 66 | .uint16be(1) // FeatureCount[1] (28) 67 | .uint16be(80) // FeatureIndex[1] (30) 68 | .result(); 69 | 70 | expect(common.Script(new ReadBuffer(data), 0), 'to equal', [{ 71 | tag: "EN ", 72 | table: { 73 | LookupOrder: 20, 74 | ReqFeatureIndex: 30, 75 | FeatureCount: 1, 76 | FeatureIndex: [40] 77 | } 78 | }, { 79 | tag: "NL ", 80 | table: { 81 | LookupOrder: 60, 82 | ReqFeatureIndex: 70, 83 | FeatureCount: 1, 84 | FeatureIndex: [80] 85 | } 86 | }]); 87 | }); 88 | 89 | it('parses a Script record with default language', function () { 90 | var data = c() 91 | .uint16be(10) // defaultLangSys (0) 92 | .uint16be(1) // langSysCount (2) 93 | .string("EN ", "ascii") // tag[0] (4) 94 | .uint16be(18) // offset[0] (8) 95 | 96 | .uint16be(20) // LookupOrder[0] (10) 97 | .uint16be(30) // ReqFeatureIndex[0] (12) 98 | .uint16be(1) // FeatureCount[0] (14) 99 | .uint16be(40) // FeatureIndex[0] (16) 100 | 101 | .uint16be(60) // LookupOrder[1] (18) 102 | .uint16be(70) // ReqFeatureIndex[1] (20) 103 | .uint16be(1) // FeatureCount[1] (22) 104 | .uint16be(80) // FeatureIndex[1] (24) 105 | .result(); 106 | 107 | expect(common.Script(new ReadBuffer(data), 0), 'to equal', [{ 108 | tag: "DFLT", 109 | table: { 110 | LookupOrder: 20, 111 | ReqFeatureIndex: 30, 112 | FeatureCount: 1, 113 | FeatureIndex: [40] 114 | } 115 | }, { 116 | tag: "EN ", 117 | table: { 118 | LookupOrder: 60, 119 | ReqFeatureIndex: 70, 120 | FeatureCount: 1, 121 | FeatureIndex: [80] 122 | } 123 | }]); 124 | }); 125 | 126 | it('parses a LangSys record', function () { 127 | var data = c() 128 | .uint16be(1) // lookupOrder (0) 129 | .uint16be(2) // reqFeatureIndex (2) 130 | .uint16be(3) // featureCount 131 | .uint16be(10) // featureIndex[0] 132 | .uint16be(20) // featureIndex[1] 133 | .uint16be(30) // featureIndex[2] 134 | .result(); 135 | 136 | expect(common.LangSys(new ReadBuffer(data), 0), 'to equal', { 137 | LookupOrder: 1, 138 | ReqFeatureIndex: 2, 139 | FeatureCount: 3, 140 | FeatureIndex: [10, 20, 30] 141 | }); 142 | }); 143 | 144 | it('parses a Feature record', function () { 145 | var data = c() 146 | .uint16be(1) // FeatureParams 147 | .uint16be(3) // LookupCount 148 | .uint16be(10) // LookupListIndex[0] 149 | .uint16be(20) // LookupListIndex[1] 150 | .uint16be(30) // LookupListIndex[2] 151 | .result(); 152 | 153 | expect(common.Feature(new ReadBuffer(data), 0), 'to equal', { 154 | FeatureParams: 1, 155 | LookupCount: 3, 156 | LookupListIndex: [10, 20, 30] 157 | }); 158 | }); 159 | 160 | it('parses a Lookup record', function () { 161 | var data = c() 162 | .uint16be(1) // LookupType 163 | .uint16be(2) // LookupFlag 164 | .uint16be(2) // SubTableCount 165 | .uint16be(10) // SubTable[0] 166 | .uint16be(20) // SubTable[1] 167 | .uint16be(3) // MarkFilteringSet 168 | .result(); 169 | 170 | expect(common.Lookup(new ReadBuffer(data), 0, function (buffer, lookupType, offset) { 171 | return offset; 172 | }), 'to equal', { 173 | LookupType: 1, 174 | LookupFlag: 2, 175 | SubTable: [10, 20], 176 | MarkFilteringSet: 3 177 | }); 178 | }); 179 | 180 | it('parses a LookupList', function () { 181 | var data = c() 182 | .uint16be(2) // Count (0) 183 | .uint16be(6) // Offset[0] (2) 184 | .uint16be(18) // Offset[1] (4) 185 | .uint16be(1) // LookupType[0] (6) 186 | .uint16be(2) // LookupFlag[0] (8) 187 | .uint16be(2) // SubTableCount[0] (10) 188 | .uint16be(10) // SubTable[0][0] (12) 189 | .uint16be(20) // SubTable[0][1] (14) 190 | .uint16be(3) // MarkFilteringSet[0] (16) 191 | .uint16be(2) // LookupType[1] (18) 192 | .uint16be(4) // LookupFlag[1] (20) 193 | .uint16be(2) // SubTableCount[1] (22) 194 | .uint16be(50) // SubTable[1][0] (24) 195 | .uint16be(60) // SubTable[1][1] (26) 196 | .uint16be(4) // MarkFilteringSet[1] (28) 197 | .result(); 198 | 199 | expect(common.LookupList(new ReadBuffer(data), 0, function (buffer, lookupType, offset) { 200 | return offset; 201 | }), 'to equal', [{ 202 | LookupType: 1, 203 | LookupFlag: 2, 204 | SubTable: [16, 26], 205 | MarkFilteringSet: 3 206 | }, { 207 | LookupType: 2, 208 | LookupFlag: 4, 209 | SubTable: [68, 78], 210 | MarkFilteringSet: 4 211 | }]); 212 | }); 213 | 214 | it('parses a Coverage record (format 1)', function () { 215 | var data = c() 216 | .uint16be(1) // Format 217 | .uint16be(2) // GlyphCount 218 | .uint16be(5) // GlyphId[0] 219 | .uint16be(6) // GlyphId[1] 220 | .result(); 221 | 222 | expect(common.Coverage(new ReadBuffer(data), 0), 'to equal', [5, 6]); 223 | }); 224 | 225 | it('parses a Coverage record (format 2)', function () { 226 | var data = c() 227 | .uint16be(2) // Format 228 | .uint16be(2) // GlyphCount 229 | .uint16be(0) // Start[0] 230 | .uint16be(5) // End[0] 231 | .uint16be(10) // StartCoverageIndex[0] 232 | .uint16be(10) // Start[1] 233 | .uint16be(15) // End[1] 234 | .uint16be(20) // StartCoverageIndex[1] 235 | .result(); 236 | 237 | expect(common.Coverage(new ReadBuffer(data), 0), 'to equal', [10, 11, 12, 13, 14, 15, 20, 21, 22, 23, 24, 25]); 238 | }); 239 | 240 | it('parses a ClassDef definition table (format 1)', function () { 241 | var data = c() 242 | .uint16be(1) // Format 243 | .uint16be(5) // StartGlyph 244 | .uint16be(3) // GlyphCount 245 | .uint16be(10) // ClassValue[0] 246 | .uint16be(20) // ClassValue[1] 247 | .uint16be(30) // ClassValue[2] 248 | .result(); 249 | 250 | expect(common.ClassDef(new ReadBuffer(data), 0), 'to equal', { 251 | 5: 10, 252 | 6: 20, 253 | 7: 30 254 | }); 255 | }); 256 | 257 | it('parses a ClassDef definition table (format 2)', function () { 258 | var data = c() 259 | .uint16be(2) // Format 260 | .uint16be(2) // ClassRangeCount 261 | .uint16be(10) // Start[0] 262 | .uint16be(15) // End[0] 263 | .uint16be(1) // Class[0] 264 | .uint16be(20) // Start[1] 265 | .uint16be(25) // End[1] 266 | .uint16be(2) // Class[1] 267 | .result(); 268 | 269 | expect(common.ClassDef(new ReadBuffer(data), 0), 'to equal', { 270 | 10: 1, 271 | 11: 1, 272 | 12: 1, 273 | 13: 1, 274 | 14: 1, 275 | 15: 1, 276 | 20: 2, 277 | 21: 2, 278 | 22: 2, 279 | 23: 2, 280 | 24: 2, 281 | 25: 2 282 | }); 283 | }); 284 | }); 285 | -------------------------------------------------------------------------------- /test/tables/gasp-test.js: -------------------------------------------------------------------------------- 1 | var gasp = require('../../src/tables/gasp'); 2 | var expect = require('unexpected'); 3 | var c = require('concentrate'); 4 | 5 | describe('tables.gasp', function () { 6 | it('reads a single gasp entry', function () { 7 | var data = c() 8 | .uint16be(1) // version 1 9 | .uint16be(1) // numRanges: 1 10 | .uint16be(1024) // rangeMaxPPEM 11 | .uint16be(0x0002) // rangeGaspBehaviour 12 | .result(); 13 | 14 | expect(gasp(data), 'to equal', { 15 | gaspRange: [{ 16 | rangeMaxPPEM: 1024, 17 | rangeGaspBehavior: 0x0002 18 | }] 19 | }); 20 | }); 21 | 22 | it('reads multiple gasp entries', function () { 23 | var data = c() 24 | .uint16be(1) 25 | .uint16be(2) 26 | .uint16be(256) 27 | .uint16be(0x0003) 28 | .uint16be(32) 29 | .uint16be(0x000C) 30 | .result(); 31 | 32 | expect(gasp(data), 'to equal', { 33 | gaspRange: [{ 34 | rangeMaxPPEM: 256, 35 | rangeGaspBehavior: 0x0003 36 | }, { 37 | rangeMaxPPEM: 32, 38 | rangeGaspBehavior: 0x000C 39 | }] 40 | }); 41 | }); 42 | }); 43 | -------------------------------------------------------------------------------- /test/tables/gdef-test.js: -------------------------------------------------------------------------------- 1 | var gdef = require('../../src/tables/gdef'); 2 | var expect = require('unexpected'); 3 | var c = require('concentrate'); 4 | 5 | describe('tables.gdef', function () { 6 | it('parses version 1.0', function () { 7 | var buffer = c() 8 | .uint32be(0x00010000) // Version (0) 9 | .uint16be(12) // GlyphClassDef (4) 10 | .uint16be(0) // AttachList (6) 11 | .uint16be(0) // LigCaretList (8) 12 | .uint16be(0) // MarkAttachClassDef (10) 13 | .uint16be(1) // Format (12) 14 | .uint16be(5) // StartGlyph (14) 15 | .uint16be(3) // GlyphCount (16) 16 | .uint16be(10) // ClassValue[0] (18) 17 | .uint16be(20) // ClassValue[1] (20) 18 | .uint16be(30) // ClassValue[2] (22) 19 | .result(); 20 | 21 | expect(gdef(buffer), 'to equal', { 22 | GlyphClassDef: { 5: 10, 6: 20, 7: 30 } 23 | }); 24 | }); 25 | 26 | it('parses version 1.2', function () { 27 | var buffer = c() 28 | .uint32be(0x00010002) // Version 29 | .uint16be(14) // GlyphClassDef (4) 30 | .uint16be(1) // AttachList (6) 31 | .uint16be(1) // LigCaretList (8) 32 | .uint16be(1) // MarkAttachClassDef (10) 33 | .uint16be(1) // MarkGlyphSetsDef (12) 34 | .uint16be(1) // Format (14) 35 | .uint16be(5) // StartGlyph (16) 36 | .uint16be(3) // GlyphCount (18) 37 | .uint16be(10) // ClassValue[0] (20) 38 | .uint16be(20) // ClassValue[1] (22) 39 | .uint16be(30) // ClassValue[2] (24) 40 | .result(); 41 | 42 | expect(gdef(buffer), 'to equal', { 43 | GlyphClassDef: { 5: 10, 6: 20, 7: 30 } 44 | }); 45 | }); 46 | }); 47 | -------------------------------------------------------------------------------- /test/tables/gsub-test.js: -------------------------------------------------------------------------------- 1 | var gsub = require('../../src/tables/gsub'); 2 | var expect = require('unexpected'); 3 | var c = require('concentrate'); 4 | var ReadBuffer = require('../../src/readbuffer'); 5 | 6 | describe('tables.GSUB', function () { 7 | it('parses version 1.0', function () { 8 | var data = c() 9 | .uint32be(0x00010000) // Version (0) 10 | .uint16be(10) // ScriptListOffset (4) 11 | .uint16be(36) // FeatureListOffset (6) 12 | .uint16be(50) // LookupListOffset (8) 13 | .uint16be(1) // ScriptCount (10) ScriptList (0) 14 | .string("EN ", "ascii") // Tag (12) ScriptList (2) 15 | .uint16be(8) // Offset (16) ScriptList (6) 16 | .uint16be(0) // defaultLangSys (18) (8) Script (0) 17 | .uint16be(1) // langSysCount (20) (10) Script (2) 18 | .string("GB ", "ascii") // tag[0] (22) (12) LangSysRec (4) 19 | .uint16be(10) // offset[0] (26) (14) LangSysRec (8) 20 | .uint16be(20) // LookupOrder[0] (28) LangSys (16) LangSys (10) 21 | .uint16be(30) // ReqFeatureIndex[0] (30) 22 | .uint16be(1) // FeatureCount[0] (32) 23 | .uint16be(0) // FeatureIndex[0] (34) 24 | .uint16be(1) // FeatureCount (36) FeatureList (0) 25 | .string("liga", "ascii") // FeatureTag (38) FeatureList (2) 26 | .uint16be(8) // FeatureOffset (42) FeatureList (6) 27 | .uint16be(0) // FeatureParams (44) Feature (0) 28 | .uint16be(1) // LookupCount (46) Feature (2) 29 | .uint16be(0) // LookupListIndex[0] (48) Feature (4) 30 | .uint16be(1) // LookupCount (50) LookupList (0) 31 | .uint16be(4) // LookupIndex (52) LookupList (2) 32 | .uint16be(1) // LookupType (54) Lookup (0) 33 | .uint16be(10) // LookupFlag (56) Lookup (2) 34 | .uint16be(1) // LookupTableCount (58) Lookup (4) 35 | .uint16be(10) // SubtableOffset (60) Lookup (6) 36 | .uint16be(0) // MarkFilteringSet (62) Lookup (8) 37 | .uint16be(1) // SubstFormat (64) Subst (0) 38 | .uint16be(6) // CoverageOffset (66) Subst (2) 39 | .int16be(10) // DeltaGlyphID (68) Subst (4) 40 | .uint16be(1) // CoverageFormat (70) Coverage (0) 41 | .uint16be(1) // GlyphCount (72) Coverage (2) 42 | .uint16be(100) // GlyphArray (74) Coverage (4) 43 | .result(); 44 | 45 | expect(gsub(data), 'to equal', { 46 | 'EN ': { 47 | 'GB ': { 48 | 'liga': { 49 | 'single': { 50 | 100: 110 51 | } 52 | } 53 | } 54 | } 55 | }); 56 | }); 57 | 58 | describe('Lookup Types', function () { 59 | it('parses lookup type 1: single substitution format 1', function () { 60 | var data = c() 61 | .uint16be(1) // SubstFormat (0) 62 | .uint16be(6) // CoverageOffset (2) 63 | .int16be(10) // DeltaGlyphID (4) 64 | .uint16be(1) // CoverageFormat (6) 65 | .uint16be(2) // GlyphCount (8) 66 | .uint16be(100) // GlyphArray[0] (10) 67 | .uint16be(101) // GlyphArray[1] (12) 68 | .result(); 69 | 70 | expect(gsub.LookupType(new ReadBuffer(data), 1, 0), 'to equal', { 71 | 100: 110, 72 | 101: 111 73 | }); 74 | }); 75 | 76 | it('parses lookup type 1: single substitution format 2', function () { 77 | var data = c() 78 | .uint16be(2) // SubstFormat (0) 79 | .uint16be(10) // CoverageOffset (2) 80 | .uint16be(2) // GlyphCount (4) 81 | .uint16be(10) // GlyphID[0] (6) 82 | .uint16be(20) // GlyphID[1] (8) 83 | .uint16be(1) // CoverageFormat (10) 84 | .uint16be(2) // GlyphCount (12) 85 | .uint16be(100) // GlyphArray[0] (14) 86 | .uint16be(101) // GlyphArray[1] (16) 87 | .result(); 88 | 89 | expect(gsub.LookupType(new ReadBuffer(data), 1, 0), 'to equal', { 90 | 100: 10, 91 | 101: 20 92 | }); 93 | }); 94 | 95 | it('parses lookup type 2: multiple substitutions subtable', function () { 96 | var data = c() 97 | .uint16be(1) // SubstFormat (0) 98 | .uint16be(10) // CoverageOffset (2) 99 | .uint16be(2) // SequenceCount (4) 100 | .uint16be(18) // SequenceOffset[0] (6) 101 | .uint16be(24) // SequenceOffset[1] (8) 102 | .uint16be(1) // CoverageFormat (10) 103 | .uint16be(2) // GlyphCount (12) 104 | .uint16be(100) // GlyphArray[0] (14) 105 | .uint16be(101) // GlyphArray[1] (16) 106 | .uint16be(2) // GlyphCount (18) 107 | .uint16be(200) // GlyphID[0] (20) 108 | .uint16be(201) // GlyphID[1] (22) 109 | .uint16be(2) // GlyphCount (24) 110 | .uint16be(300) // GlyphID[0] (26) 111 | .uint16be(301) // GlyphID[1] (28) 112 | .result(); 113 | 114 | expect(gsub.LookupType(new ReadBuffer(data), 2, 0), 'to equal', { 115 | 100: [200, 201], 116 | 101: [300, 301] 117 | }); 118 | }); 119 | 120 | it('parses lookup type 3: alternate substitution subtable', function () { 121 | var data = c() 122 | .uint16be(1) // SubstFormat (0) 123 | .uint16be(10) // CoverageOffset (2) 124 | .uint16be(2) // AlternateSetCount (4) 125 | .uint16be(18) // AlternateSetOffset[0] (6) 126 | .uint16be(24) // AlternateSetOffset[1] (8) 127 | .uint16be(1) // CoverageFormat (10) 128 | .uint16be(2) // GlyphCount (12) 129 | .uint16be(100) // GlyphArray[0] (14) 130 | .uint16be(101) // GlyphArray[1] (16) 131 | .uint16be(2) // GlyphCount (18) 132 | .uint16be(200) // Alternate[0] (20) 133 | .uint16be(201) // Alternate[1] (22) 134 | .uint16be(2) // GlyphCount (24) 135 | .uint16be(300) // Alternate[0] (26) 136 | .uint16be(301) // Alternate[1] (28) 137 | .result(); 138 | 139 | expect(gsub.LookupType(new ReadBuffer(data), 3, 0), 'to equal', { 140 | 100: [200, 201], 141 | 101: [300, 301] 142 | }); 143 | }); 144 | 145 | it('parses lookup type 4: ligature substitution subtable', function () { 146 | var data = c() 147 | .uint16be(1) // SubstFormat (0) 148 | .uint16be(8) // CoverageOffset (2) 149 | .uint16be(1) // LigSetCount (4) 150 | .uint16be(14) // LigSetOffset[0] (6) 151 | .uint16be(1) // CoverageFormat (8) 152 | .uint16be(1) // GlyphCount (10) 153 | .uint16be(100) // GlyphArray[0] (12) 154 | .uint16be(2) // LigatureCount (14) LigatureSet (0) 155 | .uint16be(6) // LigatureOffset[0] (16) LigatureSet (2) 156 | .uint16be(14) // LigatureOffset[0] (18) LigatureSet (4) 157 | .uint16be(300) // LigGlyph (20) LigatureSet (6) Ligature[0] 158 | .uint16be(3) // CompCount (22) LigatureSet (8) Ligature[0] 159 | .uint16be(301) // Component (24) LigatureSet (10) Ligature[0] 160 | .uint16be(302) // Component (26) LigatureSet (12) Ligature[0] 161 | .uint16be(400) // LigGlyph (28) LigatureSet (14) Ligature[1] 162 | .uint16be(2) // CompCount (30) LigatureSet (16) Ligature[1] 163 | .uint16be(401) // Component (32) LigatureSet (18) Ligature[1] 164 | .result(); 165 | 166 | expect(gsub.LookupType(new ReadBuffer(data), 4, 0), 'to equal', { 167 | 100: [{ 168 | components: [301, 302], 169 | ligature: 300 170 | }, { 171 | components: [401], 172 | ligature: 400 173 | }] 174 | }); 175 | }); 176 | 177 | it('parses lookup type 5: context substitution format 1', function () { 178 | var data = c() 179 | .uint16be(1) // SubstFormat (0) 180 | .uint16be(8) // CoverageOffset (2) 181 | .uint16be(1) // SubRuleSetCount (4) 182 | .uint16be(14) // SubRuleSetOffset[0] (6) 183 | .uint16be(1) // CoverageFormat (8) 184 | .uint16be(1) // GlyphCount (10) 185 | .uint16be(100) // GlyphArray[0] (12) 186 | .uint16be(2) // SubRuleCount (14) SubRuleSet (0) 187 | .uint16be(6) // SubRuleOffset[0] (16) SubRuleSet (2) 188 | .uint16be(16) // SubRuleOffset[1] (18) SubRuleSet (4) 189 | .uint16be(2) // GlyphCount (20) SubRule[0] (6) 190 | .uint16be(1) // SubstCount (22) SubRule[0] (8) 191 | .uint16be(300) // Input[0] (24) SubRule[0] (10) 192 | .uint16be(301) // SequenceIndex[0] (26) SubRule[0] (12) 193 | .uint16be(302) // LookupListIndex[0] (28) SubRule[0] (14) 194 | .uint16be(2) // GlyphCount (30) SubRule[1] (16) 195 | .uint16be(2) // SubstCount (32) SubRule[1] (18) 196 | .uint16be(400) // Input[0] (34) SubRule[1] (20) 197 | .uint16be(401) // SequenceIndex[0] (36) SubRule[1] (22) 198 | .uint16be(402) // LookupListIndex[0] (38) SubRule[2] (24) 199 | .uint16be(403) // SequenceIndex[1] (40) SubRule[2] (26) 200 | .uint16be(404) // LookupListIndex[1] (42) SubRule[2] (28) 201 | .result(); 202 | 203 | expect(gsub.LookupType(new ReadBuffer(data), 5, 0), 'to equal', { 204 | 100: [{ 205 | input: [300], 206 | records: [{ SequenceIndex: 301, LookupListIndex: 302 }] 207 | }, { 208 | input: [400], 209 | records: [{ SequenceIndex: 401, LookupListIndex: 402 }, 210 | { SequenceIndex: 403, LookupListIndex: 404 }] 211 | }] 212 | }); 213 | }); 214 | 215 | it('parses lookup type 5: context substitution format 2', function () { 216 | var data = c() 217 | .uint16be(2) // SubstFormat (0) 218 | .uint16be(10) // CoverageOffset (2) 219 | .uint16be(46) // ClassDefOffset (4) 220 | .uint16be(1) // SubClassSetCount (6) 221 | .uint16be(16) // SubClassSetOffset[0] (8) 222 | .uint16be(1) // CoverageFormat (10) 223 | .uint16be(1) // GlyphCount (12) 224 | .uint16be(100) // GlyphArray[0] (14) 225 | .uint16be(2) // SubClassCount (16) SubClassSet (0) 226 | .uint16be(6) // SubClassOffset[0] (18) SubClassSet (2) 227 | .uint16be(16) // SubClassOffset[1] (20) SubClassSet (4) 228 | .uint16be(2) // GlyphCount (22) SubClass[0] (6) 229 | .uint16be(1) // SubstCount (24) SubClass[0] (8) 230 | .uint16be(300) // Class[0] (26) SubClass[0] (10) 231 | .uint16be(301) // SequenceIndex[0] (28) SubClass[0] (12) 232 | .uint16be(302) // LookupListIndex[0] (30) SubClass[0] (14) 233 | .uint16be(2) // GlyphCount (32) SubClass[1] (16) 234 | .uint16be(2) // SubstCount (34) SubClass[1] (18) 235 | .uint16be(400) // Class[0] (36) SubClass[1] (20) 236 | .uint16be(401) // SequenceIndex[0] (38) SubClass[1] (22) 237 | .uint16be(402) // LookupListIndex[0] (40) SubClass[2] (24) 238 | .uint16be(403) // SequenceIndex[1] (42) SubClass[2] (26) 239 | .uint16be(404) // LookupListIndex[1] (44) SubClass[2] (28) 240 | .uint16be(1) // Format (46) ClassDef 241 | .uint16be(5) // StartGlyph (48) ClassDef 242 | .uint16be(3) // GlyphCount (50) ClassDef 243 | .uint16be(10) // ClassValue[0] (52) ClassDef 244 | .uint16be(20) // ClassValue[1] (54) ClassDef 245 | .uint16be(30) // ClassValue[2] (56) ClassDef 246 | .result(); 247 | 248 | expect(gsub.LookupType(new ReadBuffer(data), 5, 0), 'to equal', { 249 | }); 250 | }); 251 | 252 | it('parses lookup type 5: context substitution format 3', function () { 253 | var data = c() 254 | .uint16be(3) // SubstFormat (0) 255 | .uint16be(2) // GlyphCount (2) 256 | .uint16be(2) // SubstCount (4) 257 | .uint16be(18) // CoverageOffset[0] (6) 258 | .uint16be(24) // CoverageOffset[1] (8) 259 | .uint16be(200) // SequenceIndex[0] (10) SubstLookupRecord[0] 260 | .uint16be(201) // LookupListIndex[0] (12) SubstLookupRecord[0] 261 | .uint16be(300) // SequenceIndex[1] (14) SubstLookupRecord[1] 262 | .uint16be(301) // LookupListIndex[1] (16) SubstLookupRecord[1] 263 | .uint16be(1) // CoverageFormat (18) Coverage[0] 264 | .uint16be(1) // GlyphCount (20) Coverage[0] 265 | .uint16be(50) // GlyphArray[0] (22) Coverage[0] 266 | .uint16be(1) // CoverageFormat (24) Coverage[1] 267 | .uint16be(2) // GlyphCount (26) Coverage[1] 268 | .uint16be(100) // GlyphArray[0] (28) Coverage[1] 269 | .uint16be(101) // GlyphArray[1] (30) Coverage[1] 270 | .result(); 271 | 272 | expect(gsub.LookupType(new ReadBuffer(data), 5, 0), 'to equal', { 273 | }); 274 | }); 275 | 276 | it('parses lookup type 6: chaining context substitution format 1', function () { 277 | }); 278 | 279 | it('parses lookup type 6: chaining context substitution format 2', function () { 280 | }); 281 | 282 | it('parses lookup type 6: chaining context substitution format 3', function () { 283 | }); 284 | 285 | it('parses lookup type 7: extension substitution', function () { 286 | var data = c() 287 | .uint16be(1) // SubstFormat (0) 288 | .uint16be(1) // ExtensionLookupType (2) 289 | .uint32be(8) // ExtensionOffset (4) 290 | .uint16be(1) // SubstFormat (8) 291 | .uint16be(6) // CoverageOffset (10) 292 | .int16be(10) // DeltaGlyphID (12) 293 | .uint16be(1) // CoverageFormat (14) 294 | .uint16be(2) // GlyphCount (16) 295 | .uint16be(100) // GlyphArray[0] (18) 296 | .uint16be(101) // GlyphArray[1] (20) 297 | .result(); 298 | 299 | expect(gsub.LookupType(new ReadBuffer(data), 7, 0), 'to equal', { 300 | 100: 110, 301 | 101: 111 302 | }); 303 | }); 304 | 305 | it('parses lookup type 8: reverse chaining contextual single substitution format 1', function () { 306 | 307 | }); 308 | }); 309 | }); 310 | 311 | -------------------------------------------------------------------------------- /test/tables/head-test.js: -------------------------------------------------------------------------------- 1 | var head = require('../../src/tables/head'); 2 | var expect = require('unexpected'); 3 | var c = require('concentrate'); 4 | var Int64 = require('node-int64'); 5 | 6 | describe('tables.head', function () { 7 | it('reads head table', function () { 8 | var created = Date.now(); 9 | var modified = Date.now(); 10 | 11 | var data = c() 12 | .uint32be(0x00010000) // version 1.0 13 | .uint32be(0x00000001) // fontRevision 14 | .uint32be(0xB1B0AFBA) // checkSumAdjustment 15 | .uint32be(0x5F0F3CF5) // magicNumber 16 | .uint16be(0x1010) // flags 17 | .uint16be(32) // unitsPerEm 18 | .buffer(new Int64(created).toBuffer()) // created 19 | .buffer(new Int64(modified).toBuffer()) // modified 20 | .int16be(-10) // xMin 21 | .int16be(-20) // yMin 22 | .int16be(50) // xMax 23 | .int16be(80) // yMax 24 | .uint16be(0x1010) // macStyle 25 | .uint16be(14) // lowestRecPPEM 26 | .int16be(2) // fontDirectionHint 27 | .int16be(1) // indexToLocFormat 28 | .int16be(0) // glyphDataFormat 29 | .result(); 30 | 31 | expect(head(data), 'to equal', { 32 | version: 0x00010000, 33 | fontRevision: 0x00000001, 34 | checkSumAdjustment: 0xB1B0AFBA, 35 | magicNumber: 0x5F0F3CF5, 36 | flags: 0x1010, 37 | unitsPerEm: 32, 38 | created: new Int64(created), 39 | modified: new Int64(modified), 40 | xMin: -10, 41 | yMin: -20, 42 | xMax: 50, 43 | yMax: 80, 44 | macStyle: 0x1010, 45 | lowestRecPPEM: 14, 46 | fontDirectionHint: 2, 47 | indexToLocFormat: 1, 48 | glyphDataFormat: 0 49 | }); 50 | }); 51 | }); 52 | -------------------------------------------------------------------------------- /test/tables/hhea-test.js: -------------------------------------------------------------------------------- 1 | var hhea = require('../../src/tables/hhea'); 2 | var expect = require('unexpected'); 3 | var c = require('concentrate'); 4 | 5 | describe('tables.hhea', function () { 6 | it('reads the hhea table', function () { 7 | var data = c() 8 | .uint32be(0x00010000) // version 9 | .int16be(10) // Ascender 10 | .int16be(20) // Descender 11 | .int16be(30) // lineGap 12 | .uint16be(40) // advanceWidthMax 13 | .int16be(50) // minLeftSideBearing 14 | .int16be(60) // minRightSideBearing 15 | .int16be(70) // xMaxExtent 16 | .int16be(80) // caretSlopeRise 17 | .int16be(90) // caretSlopeRun 18 | .int16be(100) // caretOffset 19 | .int16be(0) // reserved 20 | .int16be(0) // reserved 21 | .int16be(0) // reserved 22 | .int16be(0) // reserved 23 | .int16be(0) // metricDataFormat 24 | .uint16be(200) // numberOfHMetrics 25 | .result(); 26 | 27 | expect(hhea(data), 'to equal', { 28 | version: 0x00010000, 29 | ascender: 10, 30 | descender: 20, 31 | lineGap: 30, 32 | advanceWidthMax: 40, 33 | minLeftSideBearing: 50, 34 | minRightSideBearing: 60, 35 | xMaxExtent: 70, 36 | caretSlopeRise: 80, 37 | caretSlopeRun: 90, 38 | caretOffset: 100, 39 | reserved1: 0, 40 | reserved2: 0, 41 | reserved3: 0, 42 | reserved4: 0, 43 | metricDataFormat: 0, 44 | numberOfHMetrics: 200 45 | }); 46 | }); 47 | }); 48 | 49 | -------------------------------------------------------------------------------- /test/tables/hmtx-test.js: -------------------------------------------------------------------------------- 1 | var hmtx = require('../../src/tables/hmtx'); 2 | var expect = require('unexpected'); 3 | var c = require('concentrate'); 4 | 5 | describe('tables.hmtx', function () { 6 | it('reads the hmtx table', function () { 7 | var data = c() 8 | .uint16be(1) // advanceWidth[0] 9 | .uint16be(2) // lsb[0] 10 | .uint16be(3) // advanceWidth[1] 11 | .uint16be(4) // lsb[1] 12 | .uint16be(5) // leftSideBearing[0] 13 | .uint16be(6) // leftSideBearing[1] 14 | .result(); 15 | 16 | expect(hmtx(data, { 17 | tables: { 18 | maxp: { 19 | numGlyphs: 4 20 | }, 21 | hhea: { 22 | numberOfHMetrics: 2 23 | } 24 | } 25 | }), 'to equal', { 26 | hMetrics: [{ 27 | advanceWidth: 1, 28 | lsb: 2 29 | }, { 30 | advanceWidth: 3, 31 | lsb: 4 32 | }], 33 | leftSideBearing: [5, 6] 34 | }); 35 | }); 36 | 37 | it('reads the hmtx table without leftSideBearing', function () { 38 | var data = c() 39 | .uint16be(1) // advanceWidth[0] 40 | .uint16be(2) // lsb[0] 41 | .uint16be(3) // advanceWidth[1] 42 | .uint16be(4) // lsb[1] 43 | .result(); 44 | 45 | expect(hmtx(data, { 46 | tables: { 47 | maxp: { 48 | numGlyphs: 2 49 | }, 50 | hhea: { 51 | numberOfHMetrics: 2 52 | } 53 | } 54 | }), 'to equal', { 55 | hMetrics: [{ 56 | advanceWidth: 1, 57 | lsb: 2 58 | }, { 59 | advanceWidth: 3, 60 | lsb: 4 61 | }], 62 | leftSideBearing: [] 63 | }); 64 | }); 65 | 66 | it('reads an empty hmtx table', function () { 67 | var data = c().result(); 68 | 69 | expect(hmtx(data, { 70 | tables: { 71 | maxp: { 72 | numGlyphs: 0 73 | }, 74 | hhea: { 75 | numberOfHMetrics: 0 76 | } 77 | } 78 | }), 'to equal', { 79 | hMetrics: [], 80 | leftSideBearing: [] 81 | }); 82 | }); 83 | }); 84 | -------------------------------------------------------------------------------- /test/tables/maxp-test.js: -------------------------------------------------------------------------------- 1 | var maxp = require('../../src/tables/maxp'); 2 | var expect = require('unexpected'); 3 | var c = require('concentrate'); 4 | 5 | describe('tables.maxp', function () { 6 | it('reads the maxp table (version 0.5)', function () { 7 | var data = c() 8 | .uint32be(0x00005000) // version 9 | .int16be(10) // numGlyphs 10 | .result(); 11 | 12 | expect(maxp(data), 'to equal', { 13 | version: 0x00005000, 14 | numGlyphs: 10 15 | }); 16 | }); 17 | 18 | it('reads the maxp table (version 1.0)', function () { 19 | var data = c() 20 | .uint32be(0x00010000) // version 21 | .uint16be(10) // numGlyphs 22 | .uint16be(20) // maxPoints 23 | .uint16be(30) // maxContours 24 | .uint16be(40) // maxCompositePoints 25 | .uint16be(50) // maxCompositeContours 26 | .uint16be(60) // maxZones 27 | .uint16be(70) // maxTwilightPoints 28 | .uint16be(80) // maxStorage 29 | .uint16be(90) // maxFunctionDefs 30 | .uint16be(100) // maxInstructionDefs 31 | .uint16be(110) // maxStackElements 32 | .uint16be(120) // maxSizeOfInstructions 33 | .uint16be(130) // maxComponentElements 34 | .uint16be(140) // maxComponentDepth 35 | .result(); 36 | 37 | expect(maxp(data), 'to equal', { 38 | version: 0x00010000, 39 | numGlyphs: 10, 40 | maxPoints: 20, 41 | maxContours: 30, 42 | maxCompositePoints: 40, 43 | maxCompositeContours: 50, 44 | maxZones: 60, 45 | maxTwilightPoints: 70, 46 | maxStorage: 80, 47 | maxFunctionDefs: 90, 48 | maxInstructionDefs: 100, 49 | maxStackElements: 110, 50 | maxSizeOfInstructions: 120, 51 | maxComponentElements: 130, 52 | maxComponentDepth: 140 53 | }); 54 | }); 55 | }); 56 | 57 | -------------------------------------------------------------------------------- /test/tables/name-test.js: -------------------------------------------------------------------------------- 1 | var name = require('../../src/tables/name'); 2 | var expect = require('unexpected'); 3 | var iconv = require('iconv-lite'); 4 | var c = require('concentrate'); 5 | 6 | describe('tables.name', function () { 7 | it('parses the name table', function () { 8 | var buffer = c() 9 | .uint16be(0) // format (0) 10 | .uint16be(1) // count (2) 11 | .uint16be(18) // stringOffset (4) 12 | .uint16be(3) // platformID (6) 13 | .uint16be(1) // encodingID (8) 14 | .uint16be(1033) // languageID (10) 15 | .uint16be(1) // nameID (12) 16 | .uint16be(6) // length (14) 17 | .uint16be(0) // offset (16) 18 | .string(iconv.encode('FOO', 'utf16be')); 19 | 20 | expect(name(buffer.result()), 'to equal', { 21 | 'en-US': { 22 | fontFamily: 'FOO' 23 | } 24 | }); 25 | }); 26 | }); 27 | -------------------------------------------------------------------------------- /test/tables/os2-test.js: -------------------------------------------------------------------------------- 1 | var os2 = require('../../src/tables/os2'); 2 | var expect = require('unexpected'); 3 | var c = require('concentrate'); 4 | 5 | describe('tables.os/2', function () { 6 | it('reads os/2 table', function () { 7 | 8 | var data = c() 9 | .uint16be(5) // version 5 10 | .int16be(10) // xAvgCharWidth 11 | .uint16be(20) // usWeightClass 12 | .uint16be(30) // usWidthClass 13 | .uint16be(1) // fsType 14 | .int16be(40) // ySubscriptXSize 15 | .int16be(50) // ySubscriptYSize 16 | .int16be(60) // ySubscriptXOffset 17 | .int16be(70) // ySubscriptYOffset 18 | .int16be(80) // ySuperscriptXSize 19 | .int16be(90) // ySuperscriptYSize 20 | .int16be(100) // ySuperscriptXOffset 21 | .int16be(110) // ySuperscriptYOffset 22 | .int16be(120) // yStrikeoutSize 23 | .int16be(130) // yStrikeoutPosition 24 | .int16be(140) // sFamilyClass 25 | .uint8(141) // panose[0] 26 | .uint8(142) // panose[1] 27 | .uint8(143) // panose[2] 28 | .uint8(144) // panose[3] 29 | .uint8(145) // panose[4] 30 | .uint8(146) // panose[5] 31 | .uint8(147) // panose[6] 32 | .uint8(148) // panose[7] 33 | .uint8(149) // panose[8] 34 | .uint8(150) // panose[9] 35 | .uint32be(160) // ulUnicodeRange1 36 | .uint32be(170) // ulUnicodeRange2 37 | .uint32be(180) // ulUnicodeRange3 38 | .uint32be(190) // ulUnicodeRange4 39 | .string('FTTB', 'ascii') // achVendID 40 | .uint16be(200) // fsSelection 41 | .uint16be(210) // usFirstCharIndex 42 | .uint16be(220) // usLastCharIndex 43 | .int16be(230) // sTypoAscender 44 | .int16be(240) // sTypoDescender 45 | .int16be(250) // sTypoLineGap 46 | .uint16be(260) // usWinAscent 47 | .uint16be(270) // usWinDescent 48 | .uint32be(280) // ulCodePageRange1 49 | .uint32be(290) // ulCodePageRange2 50 | .int16be(300) // sxHeight 51 | .int16be(310) // sCapHeight 52 | .uint16be(320) // usDefaultChar 53 | .uint16be(330) // usBreakChar 54 | .uint16be(340) // usMaxContext 55 | .uint16be(350) // usLowerOpticalPointSize 56 | .uint16be(360) // usUpperOpticalPointSize 57 | 58 | expect(os2(data.result()), 'to equal', { 59 | version: 5, 60 | xAvgCharWidth: 10, 61 | usWeightClass: 20, 62 | usWidthClass: 30, 63 | fsType: 1, 64 | ySubscriptXSize: 40, 65 | ySubscriptYSize: 50, 66 | ySubscriptXOffset: 60, 67 | ySubscriptYOffset: 70, 68 | ySuperscriptXSize: 80, 69 | ySuperscriptYSize: 90, 70 | ySuperscriptXOffset: 100, 71 | ySuperscriptYOffset: 110, 72 | yStrikeoutSize: 120, 73 | yStrikeoutPosition: 130, 74 | sFamilyClass: 140, 75 | panose: { 76 | bFamilyType: 141, 77 | bSerifStyle: 142, 78 | bWeight: 143, 79 | bProportion: 144, 80 | bContrast: 145, 81 | bStrokeVariation: 146, 82 | bArmStyle: 147, 83 | bLetterform: 148, 84 | bMidline: 149, 85 | bXHeight: 150 86 | }, 87 | ulUnicodeRange1: 160, 88 | ulUnicodeRange2: 170, 89 | ulUnicodeRange3: 180, 90 | ulUnicodeRange4: 190, 91 | achVendID: 'FTTB', 92 | fsSelection: 200, 93 | usFirstCharIndex: 210, 94 | usLastCharIndex: 220, 95 | sTypoAscender: 230, 96 | sTypoDescender: 240, 97 | sTypoLineGap: 250, 98 | usWinAscent: 260, 99 | usWinDescent: 270, 100 | ulCodePageRange1: 280, 101 | ulCodePageRange2: 290, 102 | sxHeight: 300, 103 | sCapHeight: 310, 104 | usDefaultChar: 320, 105 | usBreakChar: 330, 106 | usMaxContext: 340, 107 | usLowerOpticalPointSize: 350, 108 | usUpperOpticalPointSize: 360 109 | }); 110 | }); 111 | }); 112 | -------------------------------------------------------------------------------- /test/tables/post-test.js: -------------------------------------------------------------------------------- 1 | var post = require('../../src/tables/post'); 2 | var expect = require('unexpected'); 3 | var c = require('concentrate'); 4 | 5 | describe('tables.post', function () { 6 | it('reads the post table (version 1.0)', function () { 7 | var data = c() 8 | .uint32be(0x00010000) // version 9 | .uint32be(0x00010000) // italicAngle 10 | .int16be(10) // underlinePosition 11 | .int16be(4) // underlineThickness 12 | .uint32be(1) // isFixedPitch 13 | .uint32be(10) // minMemType42 14 | .uint32be(20) // maxMemType42 15 | .uint32be(30) // minMemType1 16 | .uint32be(40) // maxMemType1 17 | .result(); 18 | 19 | expect(post(data), 'to satisfy', { 20 | version: 0x00010000, 21 | italicAngle: 0x00010000, 22 | underlinePosition: 10, 23 | underlineThickness: 4, 24 | isFixedPitch: 1, 25 | minMemType42: 10, 26 | maxMemType42: 20, 27 | minMemType1: 30, 28 | maxMemType1: 40 29 | }); 30 | }); 31 | 32 | it('reads the post table (version 2.0)', function () { 33 | var data = c() 34 | .uint32be(0x00020000) // version 35 | .uint32be(0x00010000) // italicAngle 36 | .int16be(10) // underlinePosition 37 | .int16be(4) // underlineThickness 38 | .uint32be(1) // isFixedPitch 39 | .uint32be(10) // minMemType42 40 | .uint32be(20) // maxMemType42 41 | .uint32be(30) // minMemType1 42 | .uint32be(40) // maxMemType1 43 | .uint16be(4) // numGlyphs 44 | .uint16be(0) // glyphNameIndex[0] 45 | .uint16be(10) // glyphNameIndex[1] 46 | .uint16be(11) // glyphNameIndex[2] 47 | .uint16be(12) // glyphNameIndex[3] 48 | .result(); 49 | 50 | expect(post(data), 'to equal', { 51 | version: 0x00020000, 52 | italicAngle: 0x00010000, 53 | underlinePosition: 10, 54 | underlineThickness: 4, 55 | isFixedPitch: 1, 56 | minMemType42: 10, 57 | maxMemType42: 20, 58 | minMemType1: 30, 59 | maxMemType1: 40, 60 | glyphNames: { 61 | 0: '.notdef', 62 | 1: 'quotesingle', 63 | 2: 'parenleft', 64 | 3: 'parenright' 65 | } 66 | }); 67 | }); 68 | 69 | it('reads the post table (version 2.0) with custom strings', function () { 70 | var data = c() 71 | .uint32be(0x00020000) // version 72 | .uint32be(0x00010000) // italicAngle 73 | .int16be(10) // underlinePosition 74 | .int16be(4) // underlineThickness 75 | .uint32be(1) // isFixedPitch 76 | .uint32be(10) // minMemType42 77 | .uint32be(20) // maxMemType42 78 | .uint32be(30) // minMemType1 79 | .uint32be(40) // maxMemType1 80 | .uint16be(2) // numGlyphs 81 | .uint16be(258) // glyphNameIndex[0] 82 | .uint16be(259) // glyphNameIndex[0] 83 | .uint8(3).string("FOO") 84 | .uint8(3).string("BAR") 85 | .result(); 86 | 87 | expect(post(data), 'to equal', { 88 | version: 0x00020000, 89 | italicAngle: 0x00010000, 90 | underlinePosition: 10, 91 | underlineThickness: 4, 92 | isFixedPitch: 1, 93 | minMemType42: 10, 94 | maxMemType42: 20, 95 | minMemType1: 30, 96 | maxMemType1: 40, 97 | glyphNames: { 98 | 0: 'FOO', 99 | 1: 'BAR' 100 | } 101 | }); 102 | }); 103 | 104 | it('reads the post table (version 3.0)', function () { 105 | var data = c() 106 | .uint32be(0x00030000) // version 107 | .uint32be(0x00010000) // italicAngle 108 | .int16be(10) // underlinePosition 109 | .int16be(4) // underlineThickness 110 | .uint32be(1) // isFixedPitch 111 | .uint32be(10) // minMemType42 112 | .uint32be(20) // maxMemType42 113 | .uint32be(30) // minMemType1 114 | .uint32be(40) // maxMemType1 115 | .result(); 116 | 117 | expect(post(data), 'to equal', { 118 | version: 0x00030000, 119 | italicAngle: 0x00010000, 120 | underlinePosition: 10, 121 | underlineThickness: 4, 122 | isFixedPitch: 1, 123 | minMemType42: 10, 124 | maxMemType42: 20, 125 | minMemType1: 30, 126 | maxMemType1: 40, 127 | glyphNames: {} 128 | }); 129 | }); 130 | }); 131 | 132 | -------------------------------------------------------------------------------- /test/type-test.js: -------------------------------------------------------------------------------- 1 | var Type = require('../src/type'); 2 | var Int64 = require('node-int64'); 3 | var expect = require('unexpected'); 4 | var c = require('concentrate'); 5 | 6 | describe('Type', function () { 7 | describe('BYTE', function () { 8 | it('reads a byte', function () { 9 | var data = c().uint8(12).result(); 10 | 11 | expect(Type.BYTE.read(data), 'to equal', 12); 12 | }); 13 | 14 | it('reads unsigned bytes', function () { 15 | var data = c().int8(-10).result(); 16 | 17 | expect(Type.BYTE.read(data), 'to equal', 246); 18 | }); 19 | 20 | it('reads a byte at an offset', function () { 21 | var data = c().uint8(10).uint8(20).result(); 22 | 23 | expect(Type.BYTE.read(data, 1), 'to equal', 20); 24 | }); 25 | }); 26 | 27 | describe('CHAR', function () { 28 | it('reads a char', function () { 29 | var data = c().int8(12).result(); 30 | 31 | expect(Type.CHAR.read(data), 'to equal', 12); 32 | }); 33 | 34 | it('reads a signed char', function () { 35 | var data = c().int8(-10).result(); 36 | 37 | expect(Type.CHAR.read(data), 'to equal', -10); 38 | }); 39 | 40 | it('reads a char at an offset', function () { 41 | var data = c().uint8(10).uint8(20).result(); 42 | 43 | expect(Type.CHAR.read(data, 1), 'to equal', 20); 44 | }); 45 | }); 46 | 47 | describe('USHORT', function () { 48 | it('reads an ushort', function () { 49 | var data = c().uint16be(256).result(); 50 | 51 | expect(Type.USHORT.read(data), 'to equal', 256); 52 | }); 53 | 54 | it('reads unsigned shorts', function () { 55 | var data = c().int16be(-256).result(); 56 | 57 | expect(Type.USHORT.read(data), 'to equal', 65280); 58 | }); 59 | 60 | it('reads an unsigned short at an offset', function () { 61 | var data = c().uint8(10).uint16be(256).result(); 62 | 63 | expect(Type.USHORT.read(data, 1), 'to equal', 256); 64 | }); 65 | }); 66 | 67 | describe('SHORT', function () { 68 | it('reads a short', function () { 69 | var data = c().int16be(256).result(); 70 | 71 | expect(Type.SHORT.read(data), 'to equal', 256); 72 | }); 73 | 74 | it('reads unsigned shorts', function () { 75 | var data = c().int16be(-256).result(); 76 | 77 | expect(Type.SHORT.read(data), 'to equal', -256); 78 | }); 79 | 80 | it('reads a short at an offset', function () { 81 | var data = c().uint8(10).int16be(-256).result(); 82 | 83 | expect(Type.SHORT.read(data, 1), 'to equal', -256); 84 | }); 85 | }); 86 | 87 | describe('ULONG', function () { 88 | it('reads an ulong', function () { 89 | var data = c().uint32be(1024).result(); 90 | 91 | expect(Type.ULONG.read(data), 'to equal', 1024); 92 | }); 93 | 94 | it('reads unsigned shorts', function () { 95 | var data = c().int32be(-1024).result(); 96 | 97 | expect(Type.ULONG.read(data), 'to equal', 4294966272); 98 | }); 99 | 100 | it('reads an unsigned short at an offset', function () { 101 | var data = c().uint8(10).uint32be(1024).result(); 102 | 103 | expect(Type.ULONG.read(data, 1), 'to equal', 1024); 104 | }); 105 | }); 106 | 107 | describe('LONG', function () { 108 | it('reads a long', function () { 109 | var data = c().int32be(1024).result(); 110 | 111 | expect(Type.LONG.read(data), 'to equal', 1024); 112 | }); 113 | 114 | it('reads unsigned longs', function () { 115 | var data = c().int32be(-1024).result(); 116 | 117 | expect(Type.LONG.read(data), 'to equal', -1024); 118 | }); 119 | 120 | it('reads a long at an offset', function () { 121 | var data = c().uint8(10).int32be(-1024).result(); 122 | 123 | expect(Type.LONG.read(data, 1), 'to equal', -1024); 124 | }); 125 | }); 126 | 127 | describe('TAG', function () { 128 | it('reads a tag', function () { 129 | var data = c().string('cmap', 'ascii').result(); 130 | 131 | expect(Type.TAG.read(data), 'to equal', 'cmap'); 132 | 133 | var data = c().string('OS/2', 'ascii').result(); 134 | 135 | expect(Type.TAG.read(data), 'to equal', 'OS/2'); 136 | 137 | var data = c().string('CFF ', 'ascii').result(); 138 | 139 | expect(Type.TAG.read(data), 'to equal', 'CFF '); 140 | }); 141 | 142 | it('reads a tag at an offset', function () { 143 | var data = c().uint8(10).string('cmap', 'ascii').result(); 144 | 145 | expect(Type.TAG.read(data, 1), 'to equal', 'cmap'); 146 | }); 147 | }); 148 | 149 | describe('FIXED', function () { 150 | it('reads a fixed number', function () { 151 | var data = c().int32be(0x00000001).result(); 152 | 153 | expect(Type.FIXED.read(data), 'to equal', 0x00000001); 154 | 155 | var data = c().int32be(0x00010000).result(); 156 | 157 | expect(Type.FIXED.read(data), 'to equal', 0x00010000); 158 | 159 | var data = c().int32be(0x00005000).result(); 160 | 161 | expect(Type.FIXED.read(data), 'to equal', 0x00005000); 162 | }); 163 | 164 | it('reads a fixed number at an offset', function () { 165 | var data = c().uint8(10).int32be(0x00010000).result(); 166 | 167 | expect(Type.FIXED.read(data, 1), 'to equal', 0x00010000); 168 | }); 169 | }); 170 | 171 | describe('LONGDATETIME', function () { 172 | it('reads a long date time', function () { 173 | var data = c().buffer(new Int64(10).toBuffer()).result(); 174 | 175 | expect(Type.LONGDATETIME.read(data), 'to equal', new Int64(10)); 176 | }); 177 | 178 | it('reads a long date time at an offset', function () { 179 | var data = c().uint8(10).buffer(new Int64(10).toBuffer()).result(); 180 | 181 | expect(Type.LONGDATETIME.read(data, 1), 'to equal', new Int64(10)); 182 | }); 183 | }); 184 | 185 | describe('UINT24', function () { 186 | it('reads an unsigned int 24', function () { 187 | var data = new Buffer([0x01, 0x02, 0x03]); 188 | 189 | expect(Type.UINT24.read(data), 'to equal', 66051); 190 | }); 191 | 192 | it('reads an unsigned int 24 at an offset', function () { 193 | var data = c().uint8(10).buffer(new Buffer([0x01, 0x02, 0x03])).result(); 194 | 195 | expect(Type.UINT24.read(data, 1), 'to equal', 66051); 196 | }); 197 | }); 198 | }); 199 | -------------------------------------------------------------------------------- /test/util-test.js: -------------------------------------------------------------------------------- 1 | var util = require('../src/util'); 2 | var Type = require('../src/type'); 3 | var expect = require('unexpected'); 4 | 5 | describe('util', function () { 6 | describe('extend', function () { 7 | it('extends an object', function () { 8 | expect(util.extend({}, { hello: 'world' }), 'to equal', { 9 | hello: 'world' 10 | }); 11 | }); 12 | 13 | it('overwrites existing properties', function () { 14 | expect(util.extend({ hello: 'world' }, { hello: 'moon' }), 'to equal', { 15 | hello: 'moon' 16 | }); 17 | }); 18 | 19 | it('extends multiple objects', function () { 20 | expect(util.extend({ hello: 'world' }, { foo: 'bar' }, { baz: 'ble' }), 'to equal', { 21 | hello: 'world', 22 | baz: 'ble', 23 | foo: 'bar' 24 | }); 25 | }); 26 | }); 27 | 28 | describe('pad', function () { 29 | it('pads with the default block size', function () { 30 | expect(util.pad(9), 'to equal', 12); 31 | expect(util.pad(8), 'to equal', 8); 32 | }); 33 | 34 | it('pads with a custom block size', function () { 35 | expect(util.pad(12, 10), 'to equal', 20); 36 | }); 37 | }); 38 | 39 | describe('struct', function () { 40 | it('creates a new struct', function () { 41 | var s = util.struct({ 42 | first: Type.BYTE, 43 | second: Type.BYTE 44 | }); 45 | 46 | expect(s.sizeof, 'to equal', 2); 47 | expect(s.read, 'to be a function'); 48 | }); 49 | 50 | it('creates a new struct with mixed types', function () { 51 | var s = util.struct({ 52 | first: Type.BYTE, 53 | second: Type.LONG, 54 | third: Type.SHORT 55 | }); 56 | 57 | expect(s.sizeof, 'to equal', 7); 58 | expect(s.read, 'to be a function'); 59 | }); 60 | 61 | it('creates a new struct containing a sub-struct', function () { 62 | var s = util.struct({ 63 | first: Type.BYTE, 64 | second: util.struct({ 65 | first: Type.LONG 66 | }) 67 | }); 68 | 69 | expect(s.sizeof, 'to equal', 5); 70 | expect(s.read, 'to be a function'); 71 | }); 72 | }); 73 | }); 74 | --------------------------------------------------------------------------------