Read and write OpenType fonts using JavaScript. http://opentype.js.org/
This script should not be not be installed directly. It is a library for other scripts to include with the meta directive // @require https://update.greasyfork.org/scripts/20469/130870/opentypejs.js
(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.opentype = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){ var TINF_OK = 0; var TINF_DATA_ERROR = -3; function Tree() { this.table = new Uint16Array(16); /* table of code length counts */ this.trans = new Uint16Array(288); /* code -> symbol translation table */ } function Data(source, dest) { this.source = source; this.sourceIndex = 0; this.tag = 0; this.bitcount = 0; this.dest = dest; this.destLen = 0; this.ltree = new Tree(); /* dynamic length/symbol tree */ this.dtree = new Tree(); /* dynamic distance tree */ } /* --------------------------------------------------- * * -- uninitialized global data (static structures) -- * * --------------------------------------------------- */ var sltree = new Tree(); var sdtree = new Tree(); /* extra bits and base tables for length codes */ var length_bits = new Uint8Array(30); var length_base = new Uint16Array(30); /* extra bits and base tables for distance codes */ var dist_bits = new Uint8Array(30); var dist_base = new Uint16Array(30); /* special ordering of code length codes */ var clcidx = new Uint8Array([ 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 ]); /* used by tinf_decode_trees, avoids allocations every call */ var code_tree = new Tree(); var lengths = new Uint8Array(288 + 32); /* ----------------------- * * -- utility functions -- * * ----------------------- */ /* build extra bits and base tables */ function tinf_build_bits_base(bits, base, delta, first) { var i, sum; /* build bits table */ for (i = 0; i < delta; ++i) bits[i] = 0; for (i = 0; i < 30 - delta; ++i) bits[i + delta] = i / delta | 0; /* build base table */ for (sum = first, i = 0; i < 30; ++i) { base[i] = sum; sum += 1 << bits[i]; } } /* build the fixed huffman trees */ function tinf_build_fixed_trees(lt, dt) { var i; /* build fixed length tree */ for (i = 0; i < 7; ++i) lt.table[i] = 0; lt.table[7] = 24; lt.table[8] = 152; lt.table[9] = 112; for (i = 0; i < 24; ++i) lt.trans[i] = 256 + i; for (i = 0; i < 144; ++i) lt.trans[24 + i] = i; for (i = 0; i < 8; ++i) lt.trans[24 + 144 + i] = 280 + i; for (i = 0; i < 112; ++i) lt.trans[24 + 144 + 8 + i] = 144 + i; /* build fixed distance tree */ for (i = 0; i < 5; ++i) dt.table[i] = 0; dt.table[5] = 32; for (i = 0; i < 32; ++i) dt.trans[i] = i; } /* given an array of code lengths, build a tree */ var offs = new Uint16Array(16); function tinf_build_tree(t, lengths, off, num) { var i, sum; /* clear code length count table */ for (i = 0; i < 16; ++i) t.table[i] = 0; /* scan symbol lengths, and sum code length counts */ for (i = 0; i < num; ++i) t.table[lengths[off + i]]++; t.table[0] = 0; /* compute offset table for distribution sort */ for (sum = 0, i = 0; i < 16; ++i) { offs[i] = sum; sum += t.table[i]; } /* create code->symbol translation table (symbols sorted by code) */ for (i = 0; i < num; ++i) { if (lengths[off + i]) t.trans[offs[lengths[off + i]]++] = i; } } /* ---------------------- * * -- decode functions -- * * ---------------------- */ /* get one bit from source stream */ function tinf_getbit(d) { /* check if tag is empty */ if (!d.bitcount--) { /* load next tag */ d.tag = d.source[d.sourceIndex++]; d.bitcount = 7; } /* shift bit out of tag */ var bit = d.tag & 1; d.tag >>>= 1; return bit; } /* read a num bit value from a stream and add base */ function tinf_read_bits(d, num, base) { if (!num) return base; while (d.bitcount < 24) { d.tag |= d.source[d.sourceIndex++] << d.bitcount; d.bitcount += 8; } var val = d.tag & (0xffff >>> (16 - num)); d.tag >>>= num; d.bitcount -= num; return val + base; } /* given a data stream and a tree, decode a symbol */ function tinf_decode_symbol(d, t) { while (d.bitcount < 24) { d.tag |= d.source[d.sourceIndex++] << d.bitcount; d.bitcount += 8; } var sum = 0, cur = 0, len = 0; var tag = d.tag; /* get more bits while code value is above sum */ do { cur = 2 * cur + (tag & 1); tag >>>= 1; ++len; sum += t.table[len]; cur -= t.table[len]; } while (cur >= 0); d.tag = tag; d.bitcount -= len; return t.trans[sum + cur]; } /* given a data stream, decode dynamic trees from it */ function tinf_decode_trees(d, lt, dt) { var hlit, hdist, hclen; var i, num, length; /* get 5 bits HLIT (257-286) */ hlit = tinf_read_bits(d, 5, 257); /* get 5 bits HDIST (1-32) */ hdist = tinf_read_bits(d, 5, 1); /* get 4 bits HCLEN (4-19) */ hclen = tinf_read_bits(d, 4, 4); for (i = 0; i < 19; ++i) lengths[i] = 0; /* read code lengths for code length alphabet */ for (i = 0; i < hclen; ++i) { /* get 3 bits code length (0-7) */ var clen = tinf_read_bits(d, 3, 0); lengths[clcidx[i]] = clen; } /* build code length tree */ tinf_build_tree(code_tree, lengths, 0, 19); /* decode code lengths for the dynamic trees */ for (num = 0; num < hlit + hdist;) { var sym = tinf_decode_symbol(d, code_tree); switch (sym) { case 16: /* copy previous code length 3-6 times (read 2 bits) */ var prev = lengths[num - 1]; for (length = tinf_read_bits(d, 2, 3); length; --length) { lengths[num++] = prev; } break; case 17: /* repeat code length 0 for 3-10 times (read 3 bits) */ for (length = tinf_read_bits(d, 3, 3); length; --length) { lengths[num++] = 0; } break; case 18: /* repeat code length 0 for 11-138 times (read 7 bits) */ for (length = tinf_read_bits(d, 7, 11); length; --length) { lengths[num++] = 0; } break; default: /* values 0-15 represent the actual code lengths */ lengths[num++] = sym; break; } } /* build dynamic trees */ tinf_build_tree(lt, lengths, 0, hlit); tinf_build_tree(dt, lengths, hlit, hdist); } /* ----------------------------- * * -- block inflate functions -- * * ----------------------------- */ /* given a stream and two trees, inflate a block of data */ function tinf_inflate_block_data(d, lt, dt) { while (1) { var sym = tinf_decode_symbol(d, lt); /* check for end of block */ if (sym === 256) { return TINF_OK; } if (sym < 256) { d.dest[d.destLen++] = sym; } else { var length, dist, offs; var i; sym -= 257; /* possibly get more bits from length code */ length = tinf_read_bits(d, length_bits[sym], length_base[sym]); dist = tinf_decode_symbol(d, dt); /* possibly get more bits from distance code */ offs = d.destLen - tinf_read_bits(d, dist_bits[dist], dist_base[dist]); /* copy match */ for (i = offs; i < offs + length; ++i) { d.dest[d.destLen++] = d.dest[i]; } } } } /* inflate an uncompressed block of data */ function tinf_inflate_uncompressed_block(d) { var length, invlength; var i; /* unread from bitbuffer */ while (d.bitcount > 8) { d.sourceIndex--; d.bitcount -= 8; } /* get length */ length = d.source[d.sourceIndex + 1]; length = 256 * length + d.source[d.sourceIndex]; /* get one's complement of length */ invlength = d.source[d.sourceIndex + 3]; invlength = 256 * invlength + d.source[d.sourceIndex + 2]; /* check length */ if (length !== (~invlength & 0x0000ffff)) return TINF_DATA_ERROR; d.sourceIndex += 4; /* copy block */ for (i = length; i; --i) d.dest[d.destLen++] = d.source[d.sourceIndex++]; /* make sure we start next block on a byte boundary */ d.bitcount = 0; return TINF_OK; } /* inflate stream from source to dest */ function tinf_uncompress(source, dest) { var d = new Data(source, dest); var bfinal, res; do { /* read final block flag */ bfinal = tinf_getbit(d); /* read block type (2 bits) */ btype = tinf_read_bits(d, 2, 0); /* decompress block */ switch (btype) { case 0: /* decompress uncompressed block */ res = tinf_inflate_uncompressed_block(d); break; case 1: /* decompress block with fixed huffman trees */ res = tinf_inflate_block_data(d, sltree, sdtree); break; case 2: /* decompress block with dynamic huffman trees */ tinf_decode_trees(d, d.ltree, d.dtree); res = tinf_inflate_block_data(d, d.ltree, d.dtree); break; default: res = TINF_DATA_ERROR; } if (res !== TINF_OK) throw new Error('Data error'); } while (!bfinal); if (d.destLen < d.dest.length) { if (typeof d.dest.slice === 'function') return d.dest.slice(0, d.destLen); else return d.dest.subarray(0, d.destLen); } return d.dest; } /* -------------------- * * -- initialization -- * * -------------------- */ /* build fixed huffman trees */ tinf_build_fixed_trees(sltree, sdtree); /* build extra bits and base tables */ tinf_build_bits_base(length_bits, length_base, 4, 3); tinf_build_bits_base(dist_bits, dist_base, 2, 1); /* fix a special case */ length_bits[28] = 0; length_base[28] = 258; module.exports = tinf_uncompress; },{}],2:[function(require,module,exports){ // Run-time checking of preconditions. 'use strict'; // Precondition function that checks if the given predicate is true. // If not, it will throw an error. exports.argument = function(predicate, message) { if (!predicate) { throw new Error(message); } }; // Precondition function that checks if the given assertion is true. // If not, it will throw an error. exports.assert = exports.argument; },{}],3:[function(require,module,exports){ // Drawing utility functions. 'use strict'; // Draw a line on the given context from point `x1,y1` to point `x2,y2`. function line(ctx, x1, y1, x2, y2) { ctx.beginPath(); ctx.moveTo(x1, y1); ctx.lineTo(x2, y2); ctx.stroke(); } exports.line = line; },{}],4:[function(require,module,exports){ // Glyph encoding 'use strict'; var cffStandardStrings = [ '.notdef', 'space', 'exclam', 'quotedbl', 'numbersign', 'dollar', 'percent', 'ampersand', 'quoteright', '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', 'quoteleft', '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', 'exclamdown', 'cent', 'sterling', 'fraction', 'yen', 'florin', 'section', 'currency', 'quotesingle', 'quotedblleft', 'guillemotleft', 'guilsinglleft', 'guilsinglright', 'fi', 'fl', 'endash', 'dagger', 'daggerdbl', 'periodcentered', 'paragraph', 'bullet', 'quotesinglbase', 'quotedblbase', 'quotedblright', 'guillemotright', 'ellipsis', 'perthousand', 'questiondown', 'grave', 'acute', 'circumflex', 'tilde', 'macron', 'breve', 'dotaccent', 'dieresis', 'ring', 'cedilla', 'hungarumlaut', 'ogonek', 'caron', 'emdash', 'AE', 'ordfeminine', 'Lslash', 'Oslash', 'OE', 'ordmasculine', 'ae', 'dotlessi', 'lslash', 'oslash', 'oe', 'germandbls', 'on###perior', 'logicalnot', 'mu', 'trademark', 'Eth', 'onehalf', 'plusminus', 'Thorn', 'onequarter', 'divide', 'brokenbar', 'degree', 'thorn', 'threequarters', 'twosuperior', 'registered', 'minus', 'eth', 'multiply', 'thre###perior', 'copyright', 'Aacute', 'Acircumflex', 'Adieresis', 'Agrave', 'Aring', 'Atilde', 'Ccedilla', 'Eacute', 'Ecircumflex', 'Edieresis', 'Egrave', 'Iacute', 'Icircumflex', 'Idieresis', 'Igrave', 'Ntilde', 'Oacute', 'Ocircumflex', 'Odieresis', 'Ograve', 'Otilde', 'Scaron', 'Uacute', 'Ucircumflex', 'Udieresis', 'Ugrave', 'Yacute', 'Ydieresis', 'Zcaron', 'aacute', 'acircumflex', 'adieresis', 'agrave', 'aring', 'atilde', 'ccedilla', 'eacute', 'ecircumflex', 'edieresis', 'egrave', 'iacute', 'icircumflex', 'idieresis', 'igrave', 'ntilde', 'oacute', 'ocircumflex', 'odieresis', 'ograve', 'otilde', 'scaron', 'uacute', 'ucircumflex', 'udieresis', 'ugrave', 'yacute', 'ydieresis', 'zcaron', 'exclamsmall', 'Hungarumlautsmall', 'dollaroldstyle', 'dollarsuperior', 'ampersandsmall', 'Acutesmall', 'parenleftsuperior', 'parenrightsuperior', '266 ff', 'onedotenleader', 'zerooldstyle', 'oneoldstyle', 'twooldstyle', 'threeoldstyle', 'fouroldstyle', 'fiveoldstyle', 'sixoldstyle', 'sevenoldstyle', 'eightoldstyle', 'nineoldstyle', 'commasuperior', 'threequartersemdash', 'periodsuperior', 'questionsmall', 'asuperior', 'bsuperior', 'centsuperior', 'dsuperior', '###perior', 'isuperior', 'lsuperior', 'msuperior', 'nsuperior', 'osuperior', 'rsuperior', 'ssuperior', 'tsuperior', 'ff', 'ffi', 'ffl', 'parenleftinferior', 'parenrightinferior', 'Circumflexsmall', 'hyphensuperior', 'Gravesmall', 'Asmall', 'Bsmall', 'Csmall', 'Dsmall', 'Esmall', 'Fsmall', 'Gsmall', 'Hsmall', 'Ismall', 'Jsmall', 'Ksmall', 'Lsmall', 'Msmall', 'Nsmall', 'Osmall', 'Psmall', 'Qsmall', 'Rsmall', 'Ssmall', 'Tsmall', 'Usmall', 'Vsmall', 'Wsmall', 'Xsmall', 'Ysmall', 'Zsmall', 'colonmonetary', 'onefitted', 'rupiah', 'Tildesmall', 'exclamdownsmall', 'centoldstyle', 'Lslashsmall', 'Scaronsmall', 'Zcaronsmall', 'Dieresissmall', 'Brevesmall', 'Caronsmall', 'Dotaccentsmall', 'Macronsmall', 'figuredash', 'hypheninferior', 'Ogoneksmall', 'Ringsmall', 'Cedillasmall', 'questiondownsmall', 'oneeighth', 'threeeighths', 'fiveeighths', 'seveneighths', 'onethird', 'twothirds', 'zerosuperior', 'foursuperior', 'fiv###perior', 'sixsuperior', 'sevensuperior', 'eightsuperior', 'nin###perior', 'zeroinferior', 'oneinferior', 'twoinferior', 'threeinferior', 'fourinferior', 'fiveinferior', 'sixinferior', 'seveninferior', 'eightinferior', 'nineinferior', 'centinferior', 'dollarinferior', 'periodinferior', 'commainferior', 'Agravesmall', 'Aacutesmall', 'Acircumflexsmall', 'Atildesmall', 'Adieresissmall', 'Aringsmall', 'AEsmall', 'Ccedillasmall', 'Egravesmall', 'Eacutesmall', 'Ecircumflexsmall', 'Edieresissmall', 'Igravesmall', 'Iacutesmall', 'Icircumflexsmall', 'Idieresissmall', 'Ethsmall', 'Ntildesmall', 'Ogravesmall', 'Oacutesmall', 'Ocircumflexsmall', 'Otildesmall', 'Odieresissmall', 'OEsmall', 'Oslashsmall', 'Ugravesmall', 'Uacutesmall', 'Ucircumflexsmall', 'Udieresissmall', 'Yacutesmall', 'Thornsmall', 'Ydieresissmall', '001.000', '001.001', '001.002', '001.003', 'Black', 'Bold', 'Book', 'Light', 'Medium', 'Regular', 'Roman', 'Semibold']; var cffStandardEncoding = [ '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', 'space', 'exclam', 'quotedbl', 'numbersign', 'dollar', 'percent', 'ampersand', 'quoteright', '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', 'quoteleft', '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', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', 'exclamdown', 'cent', 'sterling', 'fraction', 'yen', 'florin', 'section', 'currency', 'quotesingle', 'quotedblleft', 'guillemotleft', 'guilsinglleft', 'guilsinglright', 'fi', 'fl', '', 'endash', 'dagger', 'daggerdbl', 'periodcentered', '', 'paragraph', 'bullet', 'quotesinglbase', 'quotedblbase', 'quotedblright', 'guillemotright', 'ellipsis', 'perthousand', '', 'questiondown', '', 'grave', 'acute', 'circumflex', 'tilde', 'macron', 'breve', 'dotaccent', 'dieresis', '', 'ring', 'cedilla', '', 'hungarumlaut', 'ogonek', 'caron', 'emdash', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', 'AE', '', 'ordfeminine', '', '', '', '', 'Lslash', 'Oslash', 'OE', 'ordmasculine', '', '', '', '', '', 'ae', '', '', '', 'dotlessi', '', '', 'lslash', 'oslash', 'oe', 'germandbls']; var cffExpertEncoding = [ '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', 'space', 'exclamsmall', 'Hungarumlautsmall', '', 'dollaroldstyle', 'dollarsuperior', 'ampersandsmall', 'Acutesmall', 'parenleftsuperior', 'parenrightsuperior', 'twodotenleader', 'onedotenleader', 'comma', 'hyphen', 'period', 'fraction', 'zerooldstyle', 'oneoldstyle', 'twooldstyle', 'threeoldstyle', 'fouroldstyle', 'fiveoldstyle', 'sixoldstyle', 'sevenoldstyle', 'eightoldstyle', 'nineoldstyle', 'colon', 'semicolon', 'commasuperior', 'threequartersemdash', 'periodsuperior', 'questionsmall', '', 'asuperior', 'bsuperior', 'centsuperior', 'dsuperior', '###perior', '', '', 'isuperior', '', '', 'lsuperior', 'msuperior', 'nsuperior', 'osuperior', '', '', 'rsuperior', 'ssuperior', 'tsuperior', '', 'ff', 'fi', 'fl', 'ffi', 'ffl', 'parenleftinferior', '', 'parenrightinferior', 'Circumflexsmall', 'hyphensuperior', 'Gravesmall', 'Asmall', 'Bsmall', 'Csmall', 'Dsmall', 'Esmall', 'Fsmall', 'Gsmall', 'Hsmall', 'Ismall', 'Jsmall', 'Ksmall', 'Lsmall', 'Msmall', 'Nsmall', 'Osmall', 'Psmall', 'Qsmall', 'Rsmall', 'Ssmall', 'Tsmall', 'Usmall', 'Vsmall', 'Wsmall', 'Xsmall', 'Ysmall', 'Zsmall', 'colonmonetary', 'onefitted', 'rupiah', 'Tildesmall', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', 'exclamdownsmall', 'centoldstyle', 'Lslashsmall', '', '', 'Scaronsmall', 'Zcaronsmall', 'Dieresissmall', 'Brevesmall', 'Caronsmall', '', 'Dotaccentsmall', '', '', 'Macronsmall', '', '', 'figuredash', 'hypheninferior', '', '', 'Ogoneksmall', 'Ringsmall', 'Cedillasmall', '', '', '', 'onequarter', 'onehalf', 'threequarters', 'questiondownsmall', 'oneeighth', 'threeeighths', 'fiveeighths', 'seveneighths', 'onethird', 'twothirds', '', '', 'zerosuperior', 'on###perior', 'twosuperior', 'thre###perior', 'foursuperior', 'fiv###perior', 'sixsuperior', 'sevensuperior', 'eightsuperior', 'nin###perior', 'zeroinferior', 'oneinferior', 'twoinferior', 'threeinferior', 'fourinferior', 'fiveinferior', 'sixinferior', 'seveninferior', 'eightinferior', 'nineinferior', 'centinferior', 'dollarinferior', 'periodinferior', 'commainferior', 'Agravesmall', 'Aacutesmall', 'Acircumflexsmall', 'Atildesmall', 'Adieresissmall', 'Aringsmall', 'AEsmall', 'Ccedillasmall', 'Egravesmall', 'Eacutesmall', 'Ecircumflexsmall', 'Edieresissmall', 'Igravesmall', 'Iacutesmall', 'Icircumflexsmall', 'Idieresissmall', 'Ethsmall', 'Ntildesmall', 'Ogravesmall', 'Oacutesmall', 'Ocircumflexsmall', 'Otildesmall', 'Odieresissmall', 'OEsmall', 'Oslashsmall', 'Ugravesmall', 'Uacutesmall', 'Ucircumflexsmall', 'Udieresissmall', 'Yacutesmall', 'Thornsmall', 'Ydieresissmall']; var standardNames = [ '.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', 'on###perior', 'twosuperior', 'thre###perior', 'onehalf', 'onequarter', 'threequarters', 'franc', 'Gbreve', 'gbreve', 'Idotaccent', 'Scedilla', 'scedilla', 'Cacute', 'cacute', 'Ccaron', 'ccaron', 'dcroat']; // This is the encoding used for fonts created from scratch. // It loops through all glyphs and finds the appropriate unicode value. // Since it's linear time, other encodings will be faster. function DefaultEncoding(font) { this.font = font; } DefaultEncoding.prototype.charToGlyphIndex = function(c) { var code = c.charCodeAt(0); var glyphs = this.font.glyphs; if (glyphs) { for (var i = 0; i < glyphs.length; i += 1) { var glyph = glyphs.get(i); for (var j = 0; j < glyph.unicodes.length; j += 1) { if (glyph.unicodes[j] === code) { return i; } } } } else { return null; } }; function CmapEncoding(cmap) { this.cmap = cmap; } CmapEncoding.prototype.charToGlyphIndex = function(c) { return this.cmap.glyphIndexMap[c.charCodeAt(0)] || 0; }; function CffEncoding(encoding, charset) { this.encoding = encoding; this.charset = charset; } CffEncoding.prototype.charToGlyphIndex = function(s) { var code = s.charCodeAt(0); var charName = this.encoding[code]; return this.charset.indexOf(charName); }; function GlyphNames(post) { var i; switch (post.version) { case 1: this.names = exports.standardNames.slice(); break; case 2: this.names = new Array(post.numberOfGlyphs); for (i = 0; i < post.numberOfGlyphs; i++) { if (post.glyphNameIndex[i] < exports.standardNames.length) { this.names[i] = exports.standardNames[post.glyphNameIndex[i]]; } else { this.names[i] = post.names[post.glyphNameIndex[i] - exports.standardNames.length]; } } break; case 2.5: this.names = new Array(post.numberOfGlyphs); for (i = 0; i < post.numberOfGlyphs; i++) { this.names[i] = exports.standardNames[i + post.glyphNameIndex[i]]; } break; case 3: this.names = []; break; } } GlyphNames.prototype.nameToGlyphIndex = function(name) { return this.names.indexOf(name); }; GlyphNames.prototype.glyphIndexToName = function(gid) { return this.names[gid]; }; function addGlyphNames(font) { var glyph; var glyphIndexMap = font.tables.cmap.glyphIndexMap; var charCodes = Object.keys(glyphIndexMap); for (var i = 0; i < charCodes.length; i += 1) { var c = charCodes[i]; var glyphIndex = glyphIndexMap[c]; glyph = font.glyphs.get(glyphIndex); glyph.addUnicode(parseInt(c)); } for (i = 0; i < font.glyphs.length; i += 1) { glyph = font.glyphs.get(i); if (font.cffEncoding) { glyph.name = font.cffEncoding.charset[i]; } else { glyph.name = font.glyphNames.glyphIndexToName(i); } } } exports.cffStandardStrings = cffStandardStrings; exports.cffStandardEncoding = cffStandardEncoding; exports.cffExpertEncoding = cffExpertEncoding; exports.standardNames = standardNames; exports.DefaultEncoding = DefaultEncoding; exports.CmapEncoding = CmapEncoding; exports.CffEncoding = CffEncoding; exports.GlyphNames = GlyphNames; exports.addGlyphNames = addGlyphNames; },{}],5:[function(require,module,exports){ // The Font object 'use strict'; var path = require('./path'); var sfnt = require('./tables/sfnt'); var encoding = require('./encoding'); var glyphset = require('./glyphset'); var util = require('./util'); // A Font represents a loaded OpenType font file. // It contains a set of glyphs and methods to draw text on a drawing context, // or to get a path representing the text. function Font(options) { options = options || {}; if (!options.empty) { // Check that we've provided the minimum set of names. util.checkArgument(options.familyName, 'When creating a new Font object, familyName is required.'); util.checkArgument(options.styleName, 'When creating a new Font object, styleName is required.'); util.checkArgument(options.unitsPerEm, 'When creating a new Font object, unitsPerEm is required.'); util.checkArgument(options.ascender, 'When creating a new Font object, ascender is required.'); util.checkArgument(options.descender, 'When creating a new Font object, descender is required.'); util.checkArgument(options.descender < 0, 'Descender should be negative (e.g. -512).'); // OS X will complain if the names are empty, so we put a single space everywhere by default. this.names = { fontFamily: {en: options.familyName || ' '}, fontSubfamily: {en: options.styleName || ' '}, fullName: {en: options.fullName || options.familyName + ' ' + options.styleName}, postScriptName: {en: options.postScriptName || options.familyName + options.styleName}, designer: {en: options.designer || ' '}, designerURL: {en: options.designerURL || ' '}, manufacturer: {en: options.manufacturer || ' '}, manufacturerURL: {en: options.manufacturerURL || ' '}, license: {en: options.license || ' '}, licenseURL: {en: options.licenseURL || ' '}, version: {en: options.version || 'Version 0.1'}, description: {en: options.description || ' '}, copyright: {en: options.copyright || ' '}, trademark: {en: options.trademark || ' '} }; this.unitsPerEm = options.unitsPerEm || 1000; this.ascender = options.ascender; this.descender = options.descender; } this.supported = true; // Deprecated: parseBuffer will throw an error if font is not supported. this.glyphs = new glyphset.GlyphSet(this, options.glyphs || []); this.encoding = new encoding.DefaultEncoding(this); this.tables = {}; } // Check if the font has a glyph for the given character. Font.prototype.hasChar = function(c) { return this.encoding.charToGlyphIndex(c) !== null; }; // Convert the given character to a single glyph index. // Note that this function assumes that there is a one-to-one mapping between // the given character and a glyph; for complex scripts this might not be the case. Font.prototype.charToGlyphIndex = function(s) { return this.encoding.charToGlyphIndex(s); }; // Convert the given character to a single Glyph object. // Note that this function assumes that there is a one-to-one mapping between // the given character and a glyph; for complex scripts this might not be the case. Font.prototype.charToGlyph = function(c) { var glyphIndex = this.charToGlyphIndex(c); var glyph = this.glyphs.get(glyphIndex); if (!glyph) { // .notdef glyph = this.glyphs.get(0); } return glyph; }; // Convert the given text to a list of Glyph objects. // Note that there is no strict one-to-one mapping between characters and // glyphs, so the list of returned glyphs can be larger or smaller than the // length of the given string. Font.prototype.stringToGlyphs = function(s) { var glyphs = []; for (var i = 0; i < s.length; i += 1) { var c = s[i]; glyphs.push(this.charToGlyph(c)); } return glyphs; }; Font.prototype.nameToGlyphIndex = function(name) { return this.glyphNames.nameToGlyphIndex(name); }; Font.prototype.nameToGlyph = function(name) { var glyphIndex = this.nametoGlyphIndex(name); var glyph = this.glyphs.get(glyphIndex); if (!glyph) { // .notdef glyph = this.glyphs.get(0); } return glyph; }; Font.prototype.glyphIndexToName = function(gid) { if (!this.glyphNames.glyphIndexToName) { return ''; } return this.glyphNames.glyphIndexToName(gid); }; // Retrieve the value of the kerning pair between the left glyph (or its index) // and the right glyph (or its index). If no kerning pair is found, return 0. // The kerning value gets added to the advance width when calculating the spacing // between glyphs. Font.prototype.getKerningValue = function(leftGlyph, rightGlyph) { leftGlyph = leftGlyph.index || leftGlyph; rightGlyph = rightGlyph.index || rightGlyph; var gposKerning = this.getGposKerningValue; return gposKerning ? gposKerning(leftGlyph, rightGlyph) : (this.kerningPairs[leftGlyph + ',' + rightGlyph] || 0); }; // Helper function that invokes the given callback for each glyph in the given text. // The callback gets `(glyph, x, y, fontSize, options)`. Font.prototype.forEachGlyph = function(text, x, y, fontSize, options, callback) { x = x !== undefined ? x : 0; y = y !== undefined ? y : 0; fontSize = fontSize !== undefined ? fontSize : 72; options = options || {}; var kerning = options.kerning === undefined ? true : options.kerning; var fontScale = 1 / this.unitsPerEm * fontSize; var glyphs = this.stringToGlyphs(text); for (var i = 0; i < glyphs.length; i += 1) { var glyph = glyphs[i]; callback(glyph, x, y, fontSize, options); if (glyph.advanceWidth) { x += glyph.advanceWidth * fontScale; } if (kerning && i < glyphs.length - 1) { var kerningValue = this.getKerningValue(glyph, glyphs[i + 1]); x += kerningValue * fontScale; } } }; // Create a Path object that represents the given text. // // text - The text to create. // x - Horizontal position of the beginning of the text. (default: 0) // y - Vertical position of the *baseline* of the text. (default: 0) // fontSize - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`. (default: 72) // Options is an optional object that contains: // - kerning - Whether to take kerning information into account. (default: true) // // Returns a Path object. Font.prototype.getPath = function(text, x, y, fontSize, options) { var fullPath = new path.Path(); this.forEachGlyph(text, x, y, fontSize, options, function(glyph, gX, gY, gFontSize) { var glyphPath = glyph.getPath(gX, gY, gFontSize); fullPath.extend(glyphPath); }); return fullPath; }; // Create an array of Path objects that represent the glyps of a given text. // // text - The text to create. // x - Horizontal position of the beginning of the text. (default: 0) // y - Vertical position of the *baseline* of the text. (default: 0) // fontSize - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`. (default: 72) // Options is an optional object that contains: // - kerning - Whether to take kerning information into account. (default: true) // // Returns an array of Path objects. Font.prototype.getPaths = function(text, x, y, fontSize, options) { var glyphPaths = []; this.forEachGlyph(text, x, y, fontSize, options, function(glyph, gX, gY, gFontSize) { var glyphPath = glyph.getPath(gX, gY, gFontSize); glyphPaths.push(glyphPath); }); return glyphPaths; }; // Draw the text on the given drawing context. // // ctx - A 2D drawing context, like Canvas. // text - The text to create. // x - Horizontal position of the beginning of the text. (default: 0) // y - Vertical position of the *baseline* of the text. (default: 0) // fontSize - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`. (default: 72) // Options is an optional object that contains: // - kerning - Whether to take kerning information into account. (default: true) Font.prototype.draw = function(ctx, text, x, y, fontSize, options) { this.getPath(text, x, y, fontSize, options).draw(ctx); }; // Draw the points of all glyphs in the text. // On-curve points will be drawn in blue, off-curve points will be drawn in red. // // ctx - A 2D drawing context, like Canvas. // text - The text to create. // x - Horizontal position of the beginning of the text. (default: 0) // y - Vertical position of the *baseline* of the text. (default: 0) // fontSize - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`. (default: 72) // Options is an optional object that contains: // - kerning - Whether to take kerning information into account. (default: true) Font.prototype.drawPoints = function(ctx, text, x, y, fontSize, options) { this.forEachGlyph(text, x, y, fontSize, options, function(glyph, gX, gY, gFontSize) { glyph.drawPoints(ctx, gX, gY, gFontSize); }); }; // Draw lines indicating important font measurements for all glyphs in the text. // Black lines indicate the origin of the coordinate system (point 0,0). // Blue lines indicate the glyph bounding box. // Green line indicates the advance width of the glyph. // // ctx - A 2D drawing context, like Canvas. // text - The text to create. // x - Horizontal position of the beginning of the text. (default: 0) // y - Vertical position of the *baseline* of the text. (default: 0) // fontSize - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`. (default: 72) // Options is an optional object that contains: // - kerning - Whether to take kerning information into account. (default: true) Font.prototype.drawMetrics = function(ctx, text, x, y, fontSize, options) { this.forEachGlyph(text, x, y, fontSize, options, function(glyph, gX, gY, gFontSize) { glyph.drawMetrics(ctx, gX, gY, gFontSize); }); }; Font.prototype.getEnglishName = function(name) { var translations = this.names[name]; if (translations) { return translations.en; } }; // Validate Font.prototype.validate = function() { var warnings = []; var _this = this; function assert(predicate, message) { if (!predicate) { warnings.push(message); } } function assertNamePresent(name) { var englishName = _this.getEnglishName(name); assert(englishName && englishName.trim().length > 0, 'No English ' + name + ' specified.'); } // Identification information assertNamePresent('fontFamily'); assertNamePresent('weightName'); assertNamePresent('manufacturer'); assertNamePresent('copyright'); assertNamePresent('version'); // Dimension information assert(this.unitsPerEm > 0, 'No unitsPerEm specified.'); }; // Convert the font object to a SFNT data structure. // This structure contains all the necessary tables and metadata to create a binary OTF file. Font.prototype.toTables = function() { return sfnt.fontToTable(this); }; Font.prototype.toBuffer = function() { console.warn('Font.toBuffer is deprecated. Use Font.toArrayBuffer instead.'); return this.toArrayBuffer(); }; Font.prototype.toArrayBuffer = function() { var sfntTable = this.toTables(); var bytes = sfntTable.encode(); var buffer = new ArrayBuffer(bytes.length); var intArray = new Uint8Array(buffer); for (var i = 0; i < bytes.length; i++) { intArray[i] = bytes[i]; } return buffer; }; // Initiate a download of the OpenType font. Font.prototype.download = function() { var familyName = this.getEnglishName('fontFamily'); var styleName = this.getEnglishName('fontSubfamily'); var fileName = familyName.replace(/\s/g, '') + '-' + styleName + '.otf'; var arrayBuffer = this.toArrayBuffer(); if (util.isBrowser()) { window.requestFileSystem = window.requestFileSystem || window.webkitRequestFileSystem; window.requestFileSystem(window.TEMPORARY, arrayBuffer.byteLength, function(fs) { fs.root.getFile(fileName, {create: true}, function(fileEntry) { fileEntry.createWriter(function(writer) { var dataView = new DataView(arrayBuffer); var blob = new Blob([dataView], {type: 'font/opentype'}); writer.write(blob); writer.addEventListener('writeend', function() { // Navigating to the file will download it. location.href = fileEntry.toURL(); }, false); }); }); }, function(err) { throw err; }); } else { var fs = require('fs'); var buffer = util.arrayBufferToNodeBuffer(arrayBuffer); fs.writeFileSync(fileName, buffer); } }; exports.Font = Font; },{"./encoding":4,"./glyphset":7,"./path":10,"./tables/sfnt":27,"./util":29,"fs":undefined}],6:[function(require,module,exports){ // The Glyph object 'use strict'; var check = require('./check'); var draw = require('./draw'); var path = require('./path'); function getPathDefinition(glyph, path) { var _path = path || { commands: [] }; return { configurable: true, get: function() { if (typeof _path === 'function') { _path = _path(); } return _path; }, set: function(p) { _path = p; } }; } // A Glyph is an individual mark that often corresponds to a character. // Some glyphs, such as ligatures, are a combination of many characters. // Glyphs are the basic building blocks of a font. // // The `Glyph` class contains utility methods for drawing the path and its points. function Glyph(options) { // By putting all the code on a prototype function (which is only declared once) // we reduce the memory requirements for larger fonts by some 2% this.bindConstructorValues(options); } Glyph.prototype.bindConstructorValues = function(options) { this.index = options.index || 0; // These three values cannnot be deferred for memory optimization: this.name = options.name || null; this.unicode = options.unicode || undefined; this.unicodes = options.unicodes || options.unicode !== undefined ? [options.unicode] : []; // But by binding these values only when necessary, we reduce can // the memory requirements by almost 3% for larger fonts. if (options.xMin) { this.xMin = options.xMin; } if (options.yMin) { this.yMin = options.yMin; } if (options.xMax) { this.xMax = options.xMax; } if (options.yMax) { this.yMax = options.yMax; } if (options.advanceWidth) { this.advanceWidth = options.advanceWidth; } // The path for a glyph is the most memory intensive, and is bound as a value // with a getter/setter to ensure we actually do path parsing only once the // path is actually needed by anything. Object.defineProperty(this, 'path', getPathDefinition(this, options.path)); }; Glyph.prototype.addUnicode = function(unicode) { if (this.unicodes.length === 0) { this.unicode = unicode; } this.unicodes.push(unicode); }; // Convert the glyph to a Path we can draw on a drawing context. // // x - Horizontal position of the glyph. (default: 0) // y - Vertical position of the *baseline* of the glyph. (default: 0) // fontSize - Font size, in pixels (default: 72). Glyph.prototype.getPath = function(x, y, fontSize) { x = x !== undefined ? x : 0; y = y !== undefined ? y : 0; fontSize = fontSize !== undefined ? fontSize : 72; var scale = 1 / this.path.unitsPerEm * fontSize; var p = new path.Path(); var commands = this.path.commands; for (var i = 0; i < commands.length; i += 1) { var cmd = commands[i]; if (cmd.type === 'M') { p.moveTo(x + (cmd.x * scale), y + (-cmd.y * scale)); } else if (cmd.type === 'L') { p.lineTo(x + (cmd.x * scale), y + (-cmd.y * scale)); } else if (cmd.type === 'Q') { p.quadraticCurveTo(x + (cmd.x1 * scale), y + (-cmd.y1 * scale), x + (cmd.x * scale), y + (-cmd.y * scale)); } else if (cmd.type === 'C') { p.curveTo(x + (cmd.x1 * scale), y + (-cmd.y1 * scale), x + (cmd.x2 * scale), y + (-cmd.y2 * scale), x + (cmd.x * scale), y + (-cmd.y * scale)); } else if (cmd.type === 'Z') { p.closePath(); } } return p; }; // Split the glyph into contours. // This function is here for backwards compatibility, and to // provide raw access to the TrueType glyph outlines. Glyph.prototype.getContours = function() { if (this.points === undefined) { return []; } var contours = []; var currentContour = []; for (var i = 0; i < this.points.length; i += 1) { var pt = this.points[i]; currentContour.push(pt); if (pt.lastPointOfContour) { contours.push(currentContour); currentContour = []; } } check.argument(currentContour.length === 0, 'There are still points left in the current contour.'); return contours; }; // Calculate the xMin/yMin/xMax/yMax/lsb/rsb for a Glyph. Glyph.prototype.getMetrics = function() { var commands = this.path.commands; var xCoords = []; var yCoords = []; for (var i = 0; i < commands.length; i += 1) { var cmd = commands[i]; if (cmd.type !== 'Z') { xCoords.push(cmd.x); yCoords.push(cmd.y); } if (cmd.type === 'Q' || cmd.type === 'C') { xCoords.push(cmd.x1); yCoords.push(cmd.y1); } if (cmd.type === 'C') { xCoords.push(cmd.x2); yCoords.push(cmd.y2); } } var metrics = { xMin: Math.min.apply(null, xCoords), yMin: Math.min.apply(null, yCoords), xMax: Math.max.apply(null, xCoords), yMax: Math.max.apply(null, yCoords), leftSideBearing: 0 }; if (!isFinite(metrics.xMin)) { metrics.xMin = 0; } if (!isFinite(metrics.xMax)) { metrics.xMax = this.advanceWidth; } if (!isFinite(metrics.yMin)) { metrics.yMin = 0; } if (!isFinite(metrics.yMax)) { metrics.yMax = 0; } metrics.rightSideBearing = this.advanceWidth - metrics.leftSideBearing - (metrics.xMax - metrics.xMin); return metrics; }; // Draw the glyph on the given context. // // ctx - The drawing context. // x - Horizontal position of the glyph. (default: 0) // y - Vertical position of the *baseline* of the glyph. (default: 0) // fontSize - Font size, in pixels (default: 72). Glyph.prototype.draw = function(ctx, x, y, fontSize) { this.getPath(x, y, fontSize).draw(ctx); }; // Draw the points of the glyph. // On-curve points will be drawn in blue, off-curve points will be drawn in red. // // ctx - The drawing context. // x - Horizontal position of the glyph. (default: 0) // y - Vertical position of the *baseline* of the glyph. (default: 0) // fontSize - Font size, in pixels (default: 72). Glyph.prototype.drawPoints = function(ctx, x, y, fontSize) { function drawCircles(l, x, y, scale) { var PI_SQ = Math.PI * 2; ctx.beginPath(); for (var j = 0; j < l.length; j += 1) { ctx.moveTo(x + (l[j].x * scale), y + (l[j].y * scale)); ctx.arc(x + (l[j].x * scale), y + (l[j].y * scale), 2, 0, PI_SQ, false); } ctx.closePath(); ctx.fill(); } x = x !== undefined ? x : 0; y = y !== undefined ? y : 0; fontSize = fontSize !== undefined ? fontSize : 24; var scale = 1 / this.path.unitsPerEm * fontSize; var blueCircles = []; var redCircles = []; var path = this.path; for (var i = 0; i < path.commands.length; i += 1) { var cmd = path.commands[i]; if (cmd.x !== undefined) { blueCircles.push({x: cmd.x, y: -cmd.y}); } if (cmd.x1 !== undefined) { redCircles.push({x: cmd.x1, y: -cmd.y1}); } if (cmd.x2 !== undefined) { redCircles.push({x: cmd.x2, y: -cmd.y2}); } } ctx.fillStyle = 'blue'; drawCircles(blueCircles, x, y, scale); ctx.fillStyle = 'red'; drawCircles(redCircles, x, y, scale); }; // Draw lines indicating important font measurements. // Black lines indicate the origin of the coordinate system (point 0,0). // Blue lines indicate the glyph bounding box. // Green line indicates the advance width of the glyph. // // ctx - The drawing context. // x - Horizontal position of the glyph. (default: 0) // y - Vertical position of the *baseline* of the glyph. (default: 0) // fontSize - Font size, in pixels (default: 72). Glyph.prototype.drawMetrics = function(ctx, x, y, fontSize) { var scale; x = x !== undefined ? x : 0; y = y !== undefined ? y : 0; fontSize = fontSize !== undefined ? fontSize : 24; scale = 1 / this.path.unitsPerEm * fontSize; ctx.lineWidth = 1; // Draw the origin ctx.strokeStyle = 'black'; draw.line(ctx, x, -10000, x, 10000); draw.line(ctx, -10000, y, 10000, y); // This code is here due to memory optimization: by not using // defaults in the constructor, we save a notable amount of memory. var xMin = this.xMin || 0; var yMin = this.yMin || 0; var xMax = this.xMax || 0; var yMax = this.yMax || 0; var advanceWidth = this.advanceWidth || 0; // Draw the glyph box ctx.strokeStyle = 'blue'; draw.line(ctx, x + (xMin * scale), -10000, x + (xMin * scale), 10000); draw.line(ctx, x + (xMax * scale), -10000, x + (xMax * scale), 10000); draw.line(ctx, -10000, y + (-yMin * scale), 10000, y + (-yMin * scale)); draw.line(ctx, -10000, y + (-yMax * scale), 10000, y + (-yMax * scale)); // Draw the advance width ctx.strokeStyle = 'green'; draw.line(ctx, x + (advanceWidth * scale), -10000, x + (advanceWidth * scale), 10000); }; exports.Glyph = Glyph; },{"./check":2,"./draw":3,"./path":10}],7:[function(require,module,exports){ // The GlyphSet object 'use strict'; var _glyph = require('./glyph'); // A GlyphSet represents all glyphs available in the font, but modelled using // a deferred glyph loader, for retrieving glyphs only once they are absolutely // necessary, to keep the memory footprint down. function GlyphSet(font, glyphs) { this.font = font; this.glyphs = {}; if (Array.isArray(glyphs)) { for (var i = 0; i < glyphs.length; i++) { this.glyphs[i] = glyphs[i]; } } this.length = (glyphs && glyphs.length) || 0; } GlyphSet.prototype.get = function(index) { if (typeof this.glyphs[index] === 'function') { this.glyphs[index] = this.glyphs[index](); } return this.glyphs[index]; }; GlyphSet.prototype.push = function(index, loader) { this.glyphs[index] = loader; this.length++; }; function glyphLoader(font, index) { return new _glyph.Glyph({index: index, font: font}); } /** * Generate a stub glyph that can be filled with all metadata *except* * the "points" and "path" properties, which must be loaded only once * the glyph's path is actually requested for text shaping. */ function ttfGlyphLoader(font, index, parseGlyph, data, position, buildPath) { return function() { var glyph = new _glyph.Glyph({index: index, font: font}); glyph.path = function() { parseGlyph(glyph, data, position); var path = buildPath(font.glyphs, glyph); path.unitsPerEm = font.unitsPerEm; return path; }; return glyph; }; } function cffGlyphLoader(font, index, parseCFFCharstring, charstring) { return function() { var glyph = new _glyph.Glyph({index: index, font: font}); glyph.path = function() { var path = parseCFFCharstring(font, glyph, charstring); path.unitsPerEm = font.unitsPerEm; return path; }; return glyph; }; } exports.GlyphSet = GlyphSet; exports.glyphLoader = glyphLoader; exports.ttfGlyphLoader = ttfGlyphLoader; exports.cffGlyphLoader = cffGlyphLoader; },{"./glyph":6}],8:[function(require,module,exports){ // opentype.js // https://github.com/nodebox/opentype.js // (c) 2015 Frederik De Bleser // opentype.js may be freely distributed under the MIT license. /* global DataView, Uint8Array, XMLHttpRequest */ 'use strict'; var inflate = require('tiny-inflate'); var encoding = require('./encoding'); var _font = require('./font'); var glyph = require('./glyph'); var parse = require('./parse'); var path = require('./path'); var util = require('./util'); var cmap = require('./tables/cmap'); var cff = require('./tables/cff'); var fvar = require('./tables/fvar'); var glyf = require('./tables/glyf'); var gpos = require('./tables/gpos'); var head = require('./tables/head'); var hhea = require('./tables/hhea'); var hmtx = require('./tables/hmtx'); var kern = require('./tables/kern'); var ltag = require('./tables/ltag'); var loca = require('./tables/loca'); var maxp = require('./tables/maxp'); var _name = require('./tables/name'); var os2 = require('./tables/os2'); var post = require('./tables/post'); // File loaders ///////////////////////////////////////////////////////// function loadFromFile(path, callback) { var fs = require('fs'); fs.readFile(path, function(err, buffer) { if (err) { return callback(err.message); } callback(null, util.nodeBufferToArrayBuffer(buffer)); }); } function loadFromUrl(url, callback) { var request = new XMLHttpRequest(); request.open('get', url, true); request.responseType = 'arraybuffer'; request.onload = function() { if (request.status !== 200) { return callback('Font could not be loaded: ' + request.statusText); } return callback(null, request.response); }; request.send(); } // Table Directory Entries ////////////////////////////////////////////// function parseOpenTypeTableEntries(data, numTables) { var tableEntries = []; var p = 12; for (var i = 0; i < numTables; i += 1) { var tag = parse.getTag(data, p); var offset = parse.getULong(data, p + 8); tableEntries.push({tag: tag, offset: offset, compression: false}); p += 16; } return tableEntries; } function parseWOFFTableEntries(data, numTables) { var tableEntries = []; var p = 44; // offset to the first table directory entry. for (var i = 0; i < numTables; i += 1) { var tag = parse.getTag(data, p); var offset = parse.getULong(data, p + 4); var compLength = parse.getULong(data, p + 8); var origLength = parse.getULong(data, p + 12); var compression; if (compLength < origLength) { compression = 'WOFF'; } else { compression = false; } tableEntries.push({tag: tag, offset: offset, compression: compression, compressedLength: compLength, originalLength: origLength}); p += 20; } return tableEntries; } function uncompressTable(data, tableEntry) { if (tableEntry.compression === 'WOFF') { var inBuffer = new Uint8Array(data.buffer, tableEntry.offset + 2, tableEntry.compressedLength - 2); var outBuffer = new Uint8Array(tableEntry.originalLength); inflate(inBuffer, outBuffer); if (outBuffer.byteLength !== tableEntry.originalLength) { throw new Error('Decompression error: ' + tableEntry.tag + ' decompressed length doesn\'t match recorded length'); } var view = new DataView(outBuffer.buffer, 0); return {data: view, offset: 0}; } else { return {data: data, offset: tableEntry.offset}; } } // Public API /////////////////////////////////////////////////////////// // Parse the OpenType file data (as an ArrayBuffer) and return a Font object. // Throws an error if the font could not be parsed. function parseBuffer(buffer) { var indexToLocFormat; var ltagTable; // Since the constructor can also be called to create new fonts from scratch, we indicate this // should be an empty font that we'll fill with our own data. var font = new _font.Font({empty: true}); // OpenType fonts use big endian byte ordering. // We can't rely on typed array view types, because they operate with the endianness of the host computer. // Instead we use DataViews where we can specify endianness. var data = new DataView(buffer, 0); var numTables; var tableEntries = []; var signature = parse.getTag(data, 0); if (signature === String.fromCharCode(0, 1, 0, 0)) { font.outlinesFormat = 'truetype'; numTables = parse.getUShort(data, 4); tableEntries = parseOpenTypeTableEntries(data, numTables); } else if (signature === 'OTTO') { font.outlinesFormat = 'cff'; numTables = parse.getUShort(data, 4); tableEntries = parseOpenTypeTableEntries(data, numTables); } else if (signature === 'wOFF') { var flavor = parse.getTag(data, 4); if (flavor === String.fromCharCode(0, 1, 0, 0)) { font.outlinesFormat = 'truetype'; } else if (flavor === 'OTTO') { font.outlinesFormat = 'cff'; } else { throw new Error('Unsupported OpenType flavor ' + signature); } numTables = parse.getUShort(data, 12); tableEntries = parseWOFFTableEntries(data, numTables); } else { throw new Error('Unsupported OpenType signature ' + signature); } var cffTableEntry; var fvarTableEntry; var glyfTableEntry; var gposTableEntry; var hmtxTableEntry; var kernTableEntry; var locaTableEntry; var nameTableEntry; for (var i = 0; i < numTables; i += 1) { var tableEntry = tableEntries[i]; var table; switch (tableEntry.tag) { case 'cmap': table = uncompressTable(data, tableEntry); font.tables.cmap = cmap.parse(table.data, table.offset); font.encoding = new encoding.CmapEncoding(font.tables.cmap); break; case 'fvar': fvarTableEntry = tableEntry; break; case 'head': table = uncompressTable(data, tableEntry); font.tables.head = head.parse(table.data, table.offset); font.unitsPerEm = font.tables.head.unitsPerEm; indexToLocFormat = font.tables.head.indexToLocFormat; break; case 'hhea': table = uncompressTable(data, tableEntry); font.tables.hhea = hhea.parse(table.data, table.offset); font.ascender = font.tables.hhea.ascender; font.descender = font.tables.hhea.descender; font.numberOfHMetrics = font.tables.hhea.numberOfHMetrics; break; case 'hmtx': hmtxTableEntry = tableEntry; break; case 'ltag': table = uncompressTable(data, tableEntry); ltagTable = ltag.parse(table.data, table.offset); break; case 'maxp': table = uncompressTable(data, tableEntry); font.tables.maxp = maxp.parse(table.data, table.offset); font.numGlyphs = font.tables.maxp.numGlyphs; break; case 'name': nameTableEntry = tableEntry; break; case 'OS/2': table = uncompressTable(data, tableEntry); font.tables.os2 = os2.parse(table.data, table.offset); break; case 'post': table = uncompressTable(data, tableEntry); font.tables.post = post.parse(table.data, table.offset); font.glyphNames = new encoding.GlyphNames(font.tables.post); break; case 'glyf': glyfTableEntry = tableEntry; break; case 'loca': locaTableEntry = tableEntry; break; case 'CFF ': cffTableEntry = tableEntry; break; case 'kern': kernTableEntry = tableEntry; break; case 'GPOS': gposTableEntry = tableEntry; break; } } var nameTable = uncompressTable(data, nameTableEntry); font.tables.name = _name.parse(nameTable.data, nameTable.offset, ltagTable); font.names = font.tables.name; if (glyfTableEntry && locaTableEntry) { var shortVersion = indexToLocFormat === 0; var locaTable = uncompressTable(data, locaTableEntry); var locaOffsets = loca.parse(locaTable.data, locaTable.offset, font.numGlyphs, shortVersion); var glyfTable = uncompressTable(data, glyfTableEntry); font.glyphs = glyf.parse(glyfTable.data, glyfTable.offset, locaOffsets, font); } else if (cffTableEntry) { var cffTable = uncompressTable(data, cffTableEntry); cff.parse(cffTable.data, cffTable.offset, font); } else { throw new Error('Font doesn\'t contain TrueType or CFF outlines.'); } var hmtxTable = uncompressTable(data, hmtxTableEntry); hmtx.parse(hmtxTable.data, hmtxTable.offset, font.numberOfHMetrics, font.numGlyphs, font.glyphs); encoding.addGlyphNames(font); if (kernTableEntry) { var kernTable = uncompressTable(data, kernTableEntry); font.kerningPairs = kern.parse(kernTable.data, kernTable.offset); } else { font.kerningPairs = {}; } if (gposTableEntry) { var gposTable = uncompressTable(data, gposTableEntry); gpos.parse(gposTable.data, gposTable.offset, font); } if (fvarTableEntry) { var fvarTable = uncompressTable(data, fvarTableEntry); font.tables.fvar = fvar.parse(fvarTable.data, fvarTable.offset, font.names); } return font; } // Asynchronously load the font from a URL or a filesystem. When done, call the callback // with two arguments `(err, font)`. The `err` will be null on success, // the `font` is a Font object. // // We use the node.js callback convention so that // opentype.js can integrate with frameworks like async.js. function load(url, callback) { var isNode = typeof window === 'undefined'; var loadFn = isNode ? loadFromFile : loadFromUrl; loadFn(url, function(err, arrayBuffer) { if (err) { return callback(err); } var font = parseBuffer(arrayBuffer); return callback(null, font); }); } // Syncronously load the font from a URL or file. // When done, return the font object or throw an error. function loadSync(url) { var fs = require('fs'); var buffer = fs.readFileSync(url); return parseBuffer(util.nodeBufferToArrayBuffer(buffer)); } exports._parse = parse; exports.Font = _font.Font; exports.Glyph = glyph.Glyph; exports.Path = path.Path; exports.parse = parseBuffer; exports.load = load; exports.loadSync = loadSync; },{"./encoding":4,"./font":5,"./glyph":6,"./parse":9,"./path":10,"./tables/cff":12,"./tables/cmap":13,"./tables/fvar":14,"./tables/glyf":15,"./tables/gpos":16,"./tables/head":17,"./tables/hhea":18,"./tables/hmtx":19,"./tables/kern":20,"./tables/loca":21,"./tables/ltag":22,"./tables/maxp":23,"./tables/name":24,"./tables/os2":25,"./tables/post":26,"./util":29,"fs":undefined,"tiny-inflate":1}],9:[function(require,module,exports){ // Parsing utility functions 'use strict'; // Retrieve an unsigned byte from the DataView. exports.getByte = function getByte(dataView, offset) { return dataView.getUint8(offset); }; exports.getCard8 = exports.getByte; // Retrieve an unsigned 16-bit short from the DataView. // The value is stored in big endian. exports.getUShort = function(dataView, offset) { return dataView.getUint16(offset, false); }; exports.getCard16 = exports.getUShort; // Retrieve a signed 16-bit short from the DataView. // The value is stored in big endian. exports.getShort = function(dataView, offset) { return dataView.getInt16(offset, false); }; // Retrieve an unsigned 32-bit long from the DataView. // The value is stored in big endian. exports.getULong = function(dataView, offset) { return dataView.getUint32(offset, false); }; // Retrieve a 32-bit signed fixed-point number (16.16) from the DataView. // The value is stored in big endian. exports.getFixed = function(dataView, offset) { var decimal = dataView.getInt16(offset, false); var fraction = dataView.getUint16(offset + 2, false); return decimal + fraction / 65535; }; // Retrieve a 4-character tag from the DataView. // Tags are used to identify tables. exports.getTag = function(dataView, offset) { var tag = ''; for (var i = offset; i < offset + 4; i += 1) { tag += String.fromCharCode(dataView.getInt8(i)); } return tag; }; // Retrieve an offset from the DataView. // Offsets are 1 to 4 bytes in length, depending on the offSize argument. exports.getOffset = function(dataView, offset, offSize) { var v = 0; for (var i = 0; i < offSize; i += 1) { v <<= 8; v += dataView.getUint8(offset + i); } return v; }; // Retrieve a number of bytes from start offset to the end offset from the DataView. exports.getBytes = function(dataView, startOffset, endOffset) { var bytes = []; for (var i = startOffset; i < endOffset; i += 1) { bytes.push(dataView.getUint8(i)); } return bytes; }; // Convert the list of bytes to a string. exports.bytesToString = function(bytes) { var s = ''; for (var i = 0; i < bytes.length; i += 1) { s += String.fromCharCode(bytes[i]); } return s; }; var typeOffsets = { byte: 1, uShort: 2, short: 2, uLong: 4, fixed: 4, longDateTime: 8, tag: 4 }; // A stateful parser that changes the offset whenever a value is retrieved. // The data is a DataView. function Parser(data, offset) { this.data = data; this.offset = offset; this.relativeOffset = 0; } Parser.prototype.parseByte = function() { var v = this.data.getUint8(this.offset + this.relativeOffset); this.relativeOffset += 1; return v; }; Parser.prototype.parseChar = function() { var v = this.data.getInt8(this.offset + this.relativeOffset); this.relativeOffset += 1; return v; }; Parser.prototype.parseCard8 = Parser.prototype.parseByte; Parser.prototype.parseUShort = function() { var v = this.data.getUint16(this.offset + this.relativeOffset); this.relativeOffset += 2; return v; }; Parser.prototype.parseCard16 = Parser.prototype.parseUShort; Parser.prototype.parseSID = Parser.prototype.parseUShort; Parser.prototype.parseOffset16 = Parser.prototype.parseUShort; Parser.prototype.parseShort = function() { var v = this.data.getInt16(this.offset + this.relativeOffset); this.relativeOffset += 2; return v; }; Parser.prototype.parseF2Dot14 = function() { var v = this.data.getInt16(this.offset + this.relativeOffset) / 16384; this.relativeOffset += 2; return v; }; Parser.prototype.parseULong = function() { var v = exports.getULong(this.data, this.offset + this.relativeOffset); this.relativeOffset += 4; return v; }; Parser.prototype.parseFixed = function() { var v = exports.getFixed(this.data, this.offset + this.relativeOffset); this.relativeOffset += 4; return v; }; Parser.prototype.parseOffset16List = Parser.prototype.parseUShortList = function(count) { var offsets = new Array(count); var dataView = this.data; var offset = this.offset + this.relativeOffset; for (var i = 0; i < count; i++) { offsets[i] = exports.getUShort(dataView, offset); offset += 2; } this.relativeOffset += count * 2; return offsets; }; Parser.prototype.parseString = function(length) { var dataView = this.data; var offset = this.offset + this.relativeOffset; var string = ''; this.relativeOffset += length; for (var i = 0; i < length; i++) { string += String.fromCharCode(dataView.getUint8(offset + i)); } return string; }; Parser.prototype.parseTag = function() { return this.parseString(4); }; // LONGDATETIME is a 64-bit integer. // JavaScript and unix timestamps traditionally use 32 bits, so we // only take the last 32 bits. Parser.prototype.parseLongDateTime = function() { var v = exports.getULong(this.data, this.offset + this.relativeOffset + 4); this.relativeOffset += 8; return v; }; Parser.prototype.parseFixed = function() { var v = exports.getULong(this.data, this.offset + this.relativeOffset); this.relativeOffset += 4; return v / 65536; }; Parser.prototype.parseVersion = function() { var major = exports.getUShort(this.data, this.offset + this.relativeOffset); // How to interpret the minor version is very vague in the spec. 0x5000 is 5, 0x1000 is 1 // This returns the correct number if minor = 0xN000 where N is 0-9 var minor = exports.getUShort(this.data, this.offset + this.relativeOffset + 2); this.relativeOffset += 4; return major + minor / 0x1000 / 10; }; Parser.prototype.skip = function(type, amount) { if (amount === undefined) { amount = 1; } this.relativeOffset += typeOffsets[type] * amount; }; exports.Parser = Parser; },{}],10:[function(require,module,exports){ // Geometric objects 'use strict'; // A bézier path containing a set of path commands similar to a SVG path. // Paths can be drawn on a context using `draw`. function Path() { this.commands = []; this.fill = 'black'; this.stroke = null; this.strokeWidth = 1; } Path.prototype.moveTo = function(x, y) { this.commands.push({ type: 'M', x: x, y: y }); }; Path.prototype.lineTo = function(x, y) { this.commands.push({ type: 'L', x: x, y: y }); }; Path.prototype.curveTo = Path.prototype.bezierCurveTo = function(x1, y1, x2, y2, x, y) { this.commands.push({ type: 'C', x1: x1, y1: y1, x2: x2, y2: y2, x: x, y: y }); }; Path.prototype.quadTo = Path.prototype.quadraticCurveTo = function(x1, y1, x, y) { this.commands.push({ type: 'Q', x1: x1, y1: y1, x: x, y: y }); }; Path.prototype.close = Path.prototype.closePath = function() { this.commands.push({ type: 'Z' }); }; // Add the given path or list of commands to the commands of this path. Path.prototype.extend = function(pathOrCommands) { if (pathOrCommands.commands) { pathOrCommands = pathOrCommands.commands; } Array.prototype.push.apply(this.commands, pathOrCommands); }; // Draw the path to a 2D context. Path.prototype.draw = function(ctx) { ctx.beginPath(); for (var i = 0; i < this.commands.length; i += 1) { var cmd = this.commands[i]; if (cmd.type === 'M') { ctx.moveTo(cmd.x, cmd.y); } else if (cmd.type === 'L') { ctx.lineTo(cmd.x, cmd.y); } else if (cmd.type === 'C') { ctx.bezierCurveTo(cmd.x1, cmd.y1, cmd.x2, cmd.y2, cmd.x, cmd.y); } else if (cmd.type === 'Q') { ctx.quadraticCurveTo(cmd.x1, cmd.y1, cmd.x, cmd.y); } else if (cmd.type === 'Z') { ctx.closePath(); } } if (this.fill) { ctx.fillStyle = this.fill; ctx.fill(); } if (this.stroke) { ctx.strokeStyle = this.stroke; ctx.lineWidth = this.strokeWidth; ctx.stroke(); } }; // Convert the Path to a string of path data instructions // See http://www.w3.org/TR/SVG/paths.html#PathData // Parameters: // - decimalPlaces: The amount of decimal places for floating-point values (default: 2) Path.prototype.toPathData = function(decimalPlaces) { decimalPlaces = decimalPlaces !== undefined ? decimalPlaces : 2; function floatToString(v) { if (Math.round(v) === v) { return '' + Math.round(v); } else { return v.toFixed(decimalPlaces); } } function packValues() { var s = ''; for (var i = 0; i < arguments.length; i += 1) { var v = arguments[i]; if (v >= 0 && i > 0) { s += ' '; } s += floatToString(v); } return s; } var d = ''; for (var i = 0; i < this.commands.length; i += 1) { var cmd = this.commands[i]; if (cmd.type === 'M') { d += 'M' + packValues(cmd.x, cmd.y); } else if (cmd.type === 'L') { d += 'L' + packValues(cmd.x, cmd.y); } else if (cmd.type === 'C') { d += 'C' + packValues(cmd.x1, cmd.y1, cmd.x2, cmd.y2, cmd.x, cmd.y); } else if (cmd.type === 'Q') { d += 'Q' + packValues(cmd.x1, cmd.y1, cmd.x, cmd.y); } else if (cmd.type === 'Z') { d += 'Z'; } } return d; }; // Convert the path to a SVG <path> element, as a string. // Parameters: // - decimalPlaces: The amount of decimal places for floating-point values (default: 2) Path.prototype.toSVG = function(decimalPlaces) { var svg = '<path d="'; svg += this.toPathData(decimalPlaces); svg += '"'; if (this.fill & this.fill !== 'black') { if (this.fill === null) { svg += ' fill="none"'; } else { svg += ' fill="' + this.fill + '"'; } } if (this.stroke) { svg += ' stroke="' + this.stroke + '" stroke-width="' + this.strokeWidth + '"'; } svg += '/>'; return svg; }; exports.Path = Path; },{}],11:[function(require,module,exports){ // Table metadata 'use strict'; var check = require('./check'); var encode = require('./types').encode; var sizeOf = require('./types').sizeOf; function Table(tableName, fields, options) { var i; for (i = 0; i < fields.length; i += 1) { var field = fields[i]; this[field.name] = field.value; } this.tableName = tableName; this.fields = fields; if (options) { var optionKeys = Object.keys(options); for (i = 0; i < optionKeys.length; i += 1) { var k = optionKeys[i]; var v = options[k]; if (this[k] !== undefined) { this[k] = v; } } } } Table.prototype.sizeOf = function() { var v = 0; for (var i = 0; i < this.fields.length; i += 1) { var field = this.fields[i]; var value = this[field.name]; if (value === undefined) { value = field.value; } if (typeof value.sizeOf === 'function') { v += value.sizeOf(); } else { var sizeOfFunction = sizeOf[field.type]; check.assert(typeof sizeOfFunction === 'function', 'Could not find sizeOf function for field' + field.name); v += sizeOfFunction(value); } } return v; }; Table.prototype.encode = function() { return encode.TABLE(this); }; exports.Table = Table; },{"./check":2,"./types":28}],12:[function(require,module,exports){ // The `CFF` table contains the glyph outlines in PostScript format. // https://www.microsoft.com/typography/OTSPEC/cff.htm // http://download.microsoft.com/download/8/0/1/801a191c-029d-4af3-9642-555f6fe514ee/cff.pdf // http://download.microsoft.com/download/8/0/1/801a191c-029d-4af3-9642-555f6fe514ee/type2.pdf 'use strict'; var encoding = require('../encoding'); var glyphset = require('../glyphset'); var parse = require('../parse'); var path = require('../path'); var table = require('../table'); // Custom equals function that can also check lists. function equals(a, b) { if (a === b) { return true; } else if (Array.isArray(a) && Array.isArray(b)) { if (a.length !== b.length) { return false; } for (var i = 0; i < a.length; i += 1) { if (!equals(a[i], b[i])) { return false; } } return true; } else { return false; } } // Parse a `CFF` INDEX array. // An index array consists of a list of offsets, then a list of objects at those offsets. function parseCFFIndex(data, start, conversionFn) { //var i, objectOffset, endOffset; var offsets = []; var objects = []; var count = parse.getCard16(data, start); var i; var objectOffset; var endOffset; if (count !== 0) { var offsetSize = parse.getByte(data, start + 2); objectOffset = start + ((count + 1) * offsetSize) + 2; var pos = start + 3; for (i = 0; i < count + 1; i += 1) { offsets.push(parse.getOffset(data, pos, offsetSize)); pos += offsetSize; } // The total size of the index array is 4 header bytes + the value of the last offset. endOffset = objectOffset + offsets[count]; } else { endOffset = start + 2; } for (i = 0; i < offsets.length - 1; i += 1) { var value = parse.getBytes(data, objectOffset + offsets[i], objectOffset + offsets[i + 1]); if (conversionFn) { value = conversionFn(value); } objects.push(value); } return {objects: objects, startOffset: start, endOffset: endOffset}; } // Parse a `CFF` DICT real value. function parseFloatOperand(parser) { var s = ''; var eof = 15; var lookup = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '.', 'E', 'E-', null, '-']; while (true) { var b = parser.parseByte(); var n1 = b >> 4; var n2 = b & 15; if (n1 === eof) { break; } s += lookup[n1]; if (n2 === eof) { break; } s += lookup[n2]; } return parseFloat(s); } // Parse a `CFF` DICT operand. function parseOperand(parser, b0) { var b1; var b2; var b3; var b4; if (b0 === 28) { b1 = parser.parseByte(); b2 = parser.parseByte(); return b1 << 8 | b2; } if (b0 === 29) { b1 = parser.parseByte(); b2 = parser.parseByte(); b3 = parser.parseByte(); b4 = parser.parseByte(); return b1 << 24 | b2 << 16 | b3 << 8 | b4; } if (b0 === 30) { return parseFloatOperand(parser); } if (b0 >= 32 && b0 <= 246) { return b0 - 139; } if (b0 >= 247 && b0 <= 250) { b1 = parser.parseByte(); return (b0 - 247) * 256 + b1 + 108; } if (b0 >= 251 && b0 <= 254) { b1 = parser.parseByte(); return -(b0 - 251) * 256 - b1 - 108; } throw new Error('Invalid b0 ' + b0); } // Convert the entries returned by `parseDict` to a proper dictionary. // If a value is a list of one, it is unpacked. function entriesToObject(entries) { var o = {}; for (var i = 0; i < entries.length; i += 1) { var key = entries[i][0]; var values = entries[i][1]; var value; if (values.length === 1) { value = values[0]; } else { value = values; } if (o.hasOwnProperty(key)) { throw new Error('Object ' + o + ' already has key ' + key); } o[key] = value; } return o; } // Parse a `CFF` DICT object. // A dictionary contains key-value pairs in a compact tokenized format. function parseCFFDict(data, start, size) { start = start !== undefined ? start : 0; var parser = new parse.Parser(data, start); var entries = []; var operands = []; size = size !== undefined ? size : data.length; while (parser.relativeOffset < size) { var op = parser.parseByte(); // The first byte for each dict item distinguishes between operator (key) and operand (value). // Values <= 21 are operators. if (op <= 21) { // Two-byte operators have an initial escape byte of 12. if (op === 12) { op = 1200 + parser.parseByte(); } entries.push([op, operands]); operands = []; } else { // Since the operands (values) come before the operators (keys), we store all operands in a list // until we encounter an operator. operands.push(parseOperand(parser, op)); } } return entriesToObject(entries); } // Given a String Index (SID), return the value of the string. // Strings below index 392 are standard CFF strings and are not encoded in the font. function getCFFString(strings, index) { if (index <= 390) { index = encoding.cffStandardStrings[index]; } else { index = strings[index - 391]; } return index; } // Interpret a dictionary and return a new dictionary with readable keys and values for missing entries. // This function takes `meta` which is a list of objects containing `operand`, `name` and `default`. function interpretDict(dict, meta, strings) { var newDict = {}; // Because we also want to include missing values, we start out from the meta list // and lookup values in the dict. for (var i = 0; i < meta.length; i += 1) { var m = meta[i]; var value = dict[m.op]; if (value === undefined) { value = m.value !== undefined ? m.value : null; } if (m.type === 'SID') { value = getCFFString(strings, value); } newDict[m.name] = value; } return newDict; } // Parse the CFF header. function parseCFFHeader(data, start) { var header = {}; header.formatMajor = parse.getCard8(data, start); header.formatMinor = parse.getCard8(data, start + 1); header.size = parse.getCard8(data, start + 2); header.offsetSize = parse.getCard8(data, start + 3); header.startOffset = start; header.endOffset = start + 4; return header; } var TOP_DICT_META = [ {name: 'version', op: 0, type: 'SID'}, {name: 'notice', op: 1, type: 'SID'}, {name: 'copyright', op: 1200, type: 'SID'}, {name: 'fullName', op: 2, type: 'SID'}, {name: 'familyName', op: 3, type: 'SID'}, {name: 'weight', op: 4, type: 'SID'}, {name: 'isFixedPitch', op: 1201, type: 'number', value: 0}, {name: 'italicAngle', op: 1202, type: 'number', value: 0}, {name: 'underlinePosition', op: 1203, type: 'number', value: -100}, {name: 'underlineThickness', op: 1204, type: 'number', value: 50}, {name: 'paintType', op: 1205, type: 'number', value: 0}, {name: 'charstringType', op: 1206, type: 'number', value: 2}, {name: 'fontMatrix', op: 1207, type: ['real', 'real', 'real', 'real', 'real', 'real'], value: [0.001, 0, 0, 0.001, 0, 0]}, {name: 'uniqueId', op: 13, type: 'number'}, {name: 'fontBBox', op: 5, type: ['number', 'number', 'number', 'number'], value: [0, 0, 0, 0]}, {name: 'strokeWidth', op: 1208, type: 'number', value: 0}, {name: 'xuid', op: 14, type: [], value: null}, {name: 'charset', op: 15, type: 'offset', value: 0}, {name: 'encoding', op: 16, type: 'offset', value: 0}, {name: 'charStrings', op: 17, type: 'offset', value: 0}, {name: 'private', op: 18, type: ['number', 'offset'], value: [0, 0]} ]; var PRIVATE_DICT_META = [ {name: 'subrs', op: 19, type: 'offset', value: 0}, {name: 'defaultWidthX', op: 20, type: 'number', value: 0}, {name: 'nominalWidthX', op: 21, type: 'number', value: 0} ]; // Parse the CFF top dictionary. A CFF table can contain multiple fonts, each with their own top dictionary. // The top dictionary contains the essential metadata for the font, together with the private dictionary. function parseCFFTopDict(data, strings) { var dict = parseCFFDict(data, 0, data.byteLength); return interpretDict(dict, TOP_DICT_META, strings); } // Parse the CFF private dictionary. We don't fully parse out all the values, only the ones we need. function parseCFFPrivateDict(data, start, size, strings) { var dict = parseCFFDict(data, start, size); return interpretDict(dict, PRIVATE_DICT_META, strings); } // Parse the CFF charset table, which contains internal names for all the glyphs. // This function will return a list of glyph names. // See Adobe TN #5176 chapter 13, "Charsets". function parseCFFCharset(data, start, nGlyphs, strings) { var i; var sid; var count; var parser = new parse.Parser(data, start); // The .notdef glyph is not included, so subtract 1. nGlyphs -= 1; var charset = ['.notdef']; var format = parser.parseCard8(); if (format === 0) { for (i = 0; i < nGlyphs; i += 1) { sid = parser.parseSID(); charset.push(getCFFString(strings, sid)); } } else if (format === 1) { while (charset.length <= nGlyphs) { sid = parser.parseSID(); count = parser.parseCard8(); for (i = 0; i <= count; i += 1) { charset.push(getCFFString(strings, sid)); sid += 1; } } } else if (format === 2) { while (charset.length <= nGlyphs) { sid = parser.parseSID(); count = parser.parseCard16(); for (i = 0; i <= count; i += 1) { charset.push(getCFFString(strings, sid)); sid += 1; } } } else { throw new Error('Unknown charset format ' + format); } return charset; } // Parse the CFF encoding data. Only one encoding can be specified per font. // See Adobe TN #5176 chapter 12, "Encodings". function parseCFFEncoding(data, start, charset) { var i; var code; var enc = {}; var parser = new parse.Parser(data, start); var format = parser.parseCard8(); if (format === 0) { var nCodes = parser.parseCard8(); for (i = 0; i < nCodes; i += 1) { code = parser.parseCard8(); enc[code] = i; } } else if (format === 1) { var nRanges = parser.parseCard8(); code = 1; for (i = 0; i < nRanges; i += 1) { var first = parser.parseCard8(); var nLeft = parser.parseCard8(); for (var j = first; j <= first + nLeft; j += 1) { enc[j] = code; code += 1; } } } else { throw new Error('Unknown encoding format ' + format); } return new encoding.CffEncoding(enc, charset); } // Take in charstring code and return a Glyph object. // The encoding is described in the Type 2 Charstring Format // https://www.microsoft.com/typography/OTSPEC/charstr2.htm function parseCFFCharstring(font, glyph, code) { var c1x; var c1y; var c2x; var c2y; var p = new path.Path(); var stack = []; var nStems = 0; var haveWidth = false; var width = font.defaultWidthX; var open = false; var x = 0; var y = 0; function newContour(x, y) { if (open) { p.closePath(); } p.moveTo(x, y); open = true; } function parseStems() { var hasWidthArg; // The number of stem operators on the stack is always even. // If the value is uneven, that means a width is specified. hasWidthArg = stack.length % 2 !== 0; if (hasWidthArg && !haveWidth) { width = stack.shift() + font.nominalWidthX; } nStems += stack.length >> 1; stack.length = 0; haveWidth = true; } function parse(code) { var b1; var b2; var b3; var b4; var codeIndex; var subrCode; var jpx; var jpy; var c3x; var c3y; var c4x; var c4y; var i = 0; while (i < code.length) { var v = code[i]; i += 1; switch (v) { case 1: // hstem parseStems(); break; case 3: // vstem parseStems(); break; case 4: // vmoveto if (stack.length > 1 && !haveWidth) { width = stack.shift() + font.nominalWidthX; haveWidth = true; } y += stack.pop(); newContour(x, y); break; case 5: // rlineto while (stack.length > 0) { x += stack.shift(); y += stack.shift(); p.lineTo(x, y); } break; case 6: // hlineto while (stack.length > 0) { x += stack.shift(); p.lineTo(x, y); if (stack.length === 0) { break; } y += stack.shift(); p.lineTo(x, y); } break; case 7: // vlineto while (stack.length > 0) { y += stack.shift(); p.lineTo(x, y); if (stack.length === 0) { break; } x += stack.shift(); p.lineTo(x, y); } break; case 8: // rrcurveto while (stack.length > 0) { c1x = x + stack.shift(); c1y = y + stack.shift(); c2x = c1x + stack.shift(); c2y = c1y + stack.shift(); x = c2x + stack.shift(); y = c2y + stack.shift(); p.curveTo(c1x, c1y, c2x, c2y, x, y); } break; case 10: // callsubr codeIndex = stack.pop() + font.subrsBias; subrCode = font.subrs[codeIndex]; if (subrCode) { parse(subrCode); } break; case 11: // return return; case 12: // flex operators v = code[i]; i += 1; switch (v) { case 35: // flex // |- dx1 dy1 dx2 dy2 dx3 dy3 dx4 dy4 dx5 dy5 dx6 dy6 fd flex (12 35) |- c1x = x + stack.shift(); // dx1 c1y = y + stack.shift(); // dy1 c2x = c1x + stack.shift(); // dx2 c2y = c1y + stack.shift(); // dy2 jpx = c2x + stack.shift(); // dx3 jpy = c2y + stack.shift(); // dy3 c3x = jpx + stack.shift(); // dx4 c3y = jpy + stack.shift(); // dy4 c4x = c3x + stack.shift(); // dx5 c4y = c3y + stack.shift(); // dy5 x = c4x + stack.shift(); // dx6 y = c4y + stack.shift(); // dy6 stack.shift(); // flex depth p.curveTo(c1x, c1y, c2x, c2y, jpx, jpy); p.curveTo(c3x, c3y, c4x, c4y, x, y); break; case 34: // hflex // |- dx1 dx2 dy2 dx3 dx4 dx5 dx6 hflex (12 34) |- c1x = x + stack.shift(); // dx1 c1y = y; // dy1 c2x = c1x + stack.shift(); // dx2 c2y = c1y + stack.shift(); // dy2 jpx = c2x + stack.shift(); // dx3 jpy = c2y; // dy3 c3x = jpx + stack.shift(); // dx4 c3y = c2y; // dy4 c4x = c3x + stack.shift(); // dx5 c4y = y; // dy5 x = c4x + stack.shift(); // dx6 p.curveTo(c1x, c1y, c2x, c2y, jpx, jpy); p.curveTo(c3x, c3y, c4x, c4y, x, y); break; case 36: // hflex1 // |- dx1 dy1 dx2 dy2 dx3 dx4 dx5 dy5 dx6 hflex1 (12 36) |- c1x = x + stack.shift(); // dx1 c1y = y + stack.shift(); // dy1 c2x = c1x + stack.shift(); // dx2 c2y = c1y + stack.shift(); // dy2 jpx = c2x + stack.shift(); // dx3 jpy = c2y; // dy3 c3x = jpx + stack.shift(); // dx4 c3y = c2y; // dy4 c4x = c3x + stack.shift(); // dx5 c4y = c3y + stack.shift(); // dy5 x = c4x + stack.shift(); // dx6 p.curveTo(c1x, c1y, c2x, c2y, jpx, jpy); p.curveTo(c3x, c3y, c4x, c4y, x, y); break; case 37: // flex1 // |- dx1 dy1 dx2 dy2 dx3 dy3 dx4 dy4 dx5 dy5 d6 flex1 (12 37) |- c1x = x + stack.shift(); // dx1 c1y = y + stack.shift(); // dy1 c2x = c1x + stack.shift(); // dx2 c2y = c1y + stack.shift(); // dy2 jpx = c2x + stack.shift(); // dx3 jpy = c2y + stack.shift(); // dy3 c3x = jpx + stack.shift(); // dx4 c3y = jpy + stack.shift(); // dy4 c4x = c3x + stack.shift(); // dx5 c4y = c3y + stack.shift(); // dy5 if (Math.abs(c4x - x) > Math.abs(c4y - y)) { x = c4x + stack.shift(); } else { y = c4y + stack.shift(); } p.curveTo(c1x, c1y, c2x, c2y, jpx, jpy); p.curveTo(c3x, c3y, c4x, c4y, x, y); break; default: console.log('Glyph ' + glyph.index + ': unknown operator ' + 1200 + v); stack.length = 0; } break; case 14: // endchar if (stack.length > 0 && !haveWidth) { width = stack.shift() + font.nominalWidthX; haveWidth = true; } if (open) { p.closePath(); open = false; } break; case 18: // hstemhm parseStems(); break; case 19: // hintmask case 20: // cntrmask parseStems(); i += (nStems + 7) >> 3; break; case 21: // rmoveto if (stack.length > 2 && !haveWidth) { width = stack.shift() + font.nominalWidthX; haveWidth = true; } y += stack.pop(); x += stack.pop(); newContour(x, y); break; case 22: // hmoveto if (stack.length > 1 && !haveWidth) { width = stack.shift() + font.nominalWidthX; haveWidth = true; } x += stack.pop(); newContour(x, y); break; case 23: // vstemhm parseStems(); break; case 24: // rcurveline while (stack.length > 2) { c1x = x + stack.shift(); c1y = y + stack.shift(); c2x = c1x + stack.shift(); c2y = c1y + stack.shift(); x = c2x + stack.shift(); y = c2y + stack.shift(); p.curveTo(c1x, c1y, c2x, c2y, x, y); } x += stack.shift(); y += stack.shift(); p.lineTo(x, y); break; case 25: // rlinecurve while (stack.length > 6) { x += stack.shift(); y += stack.shift(); p.lineTo(x, y); } c1x = x + stack.shift(); c1y = y + stack.shift(); c2x = c1x + stack.shift(); c2y = c1y + stack.shift(); x = c2x + stack.shift(); y = c2y + stack.shift(); p.curveTo(c1x, c1y, c2x, c2y, x, y); break; case 26: // vvcurveto if (stack.length % 2) { x += stack.shift(); } while (stack.length > 0) { c1x = x; c1y = y + stack.shift(); c2x = c1x + stack.shift(); c2y = c1y + stack.shift(); x = c2x; y = c2y + stack.shift(); p.curveTo(c1x, c1y, c2x, c2y, x, y); } break; case 27: // hhcurveto if (stack.length % 2) { y += stack.shift(); } while (stack.length > 0) { c1x = x + stack.shift(); c1y = y; c2x = c1x + stack.shift(); c2y = c1y + stack.shift(); x = c2x + stack.shift(); y = c2y; p.curveTo(c1x, c1y, c2x, c2y, x, y); } break; case 28: // shortint b1 = code[i]; b2 = code[i + 1]; stack.push(((b1 << 24) | (b2 << 16)) >> 16); i += 2; break; case 29: // callgsubr codeIndex = stack.pop() + font.gsubrsBias; subrCode = font.gsubrs[codeIndex]; if (subrCode) { parse(subrCode); } break; case 30: // vhcurveto while (stack.length > 0) { c1x = x; c1y = y + stack.shift(); c2x = c1x + stack.shift(); c2y = c1y + stack.shift(); x = c2x + stack.shift(); y = c2y + (stack.length === 1 ? stack.shift() : 0); p.curveTo(c1x, c1y, c2x, c2y, x, y); if (stack.length === 0) { break; } c1x = x + stack.shift(); c1y = y; c2x = c1x + stack.shift(); c2y = c1y + stack.shift(); y = c2y + stack.shift(); x = c2x + (stack.length === 1 ? stack.shift() : 0); p.curveTo(c1x, c1y, c2x, c2y, x, y); } break; case 31: // hvcurveto while (stack.length > 0) { c1x = x + stack.shift(); c1y = y; c2x = c1x + stack.shift(); c2y = c1y + stack.shift(); y = c2y + stack.shift(); x = c2x + (stack.length === 1 ? stack.shift() : 0); p.curveTo(c1x, c1y, c2x, c2y, x, y); if (stack.length === 0) { break; } c1x = x; c1y = y + stack.shift(); c2x = c1x + stack.shift(); c2y = c1y + stack.shift(); x = c2x + stack.shift(); y = c2y + (stack.length === 1 ? stack.shift() : 0); p.curveTo(c1x, c1y, c2x, c2y, x, y); } break; default: if (v < 32) { console.log('Glyph ' + glyph.index + ': unknown operator ' + v); } else if (v < 247) { stack.push(v - 139); } else if (v < 251) { b1 = code[i]; i += 1; stack.push((v - 247) * 256 + b1 + 108); } else if (v < 255) { b1 = code[i]; i += 1; stack.push(-(v - 251) * 256 - b1 - 108); } else { b1 = code[i]; b2 = code[i + 1]; b3 = code[i + 2]; b4 = code[i + 3]; i += 4; stack.push(((b1 << 24) | (b2 << 16) | (b3 << 8) | b4) / 65536); } } } } parse(code); glyph.advanceWidth = width; return p; } // Subroutines are encoded using the negative half of the number space. // See type 2 chapter 4.7 "Subroutine operators". function calcCFFSubroutineBias(subrs) { var bias; if (subrs.length < 1240) { bias = 107; } else if (subrs.length < 33900) { bias = 1131; } else { bias = 32768; } return bias; } // Parse the `CFF` table, which contains the glyph outlines in PostScript format. function parseCFFTable(data, start, font) { font.tables.cff = {}; var header = parseCFFHeader(data, start); var nameIndex = parseCFFIndex(data, header.endOffset, parse.bytesToString); var topDictIndex = parseCFFIndex(data, nameIndex.endOffset); var stringIndex = parseCFFIndex(data, topDictIndex.endOffset, parse.bytesToString); var globalSubrIndex = parseCFFIndex(data, stringIndex.endOffset); font.gsubrs = globalSubrIndex.objects; font.gsubrsBias = calcCFFSubroutineBias(font.gsubrs); var topDictData = new DataView(new Uint8Array(topDictIndex.objects[0]).buffer); var topDict = parseCFFTopDict(topDictData, stringIndex.objects); font.tables.cff.topDict = topDict; var privateDictOffset = start + topDict['private'][1]; var privateDict = parseCFFPrivateDict(data, privateDictOffset, topDict['private'][0], stringIndex.objects); font.defaultWidthX = privateDict.defaultWidthX; font.nominalWidthX = privateDict.nominalWidthX; if (privateDict.subrs !== 0) { var subrOffset = privateDictOffset + privateDict.subrs; var subrIndex = parseCFFIndex(data, subrOffset); font.subrs = subrIndex.objects; font.subrsBias = calcCFFSubroutineBias(font.subrs); } else { font.subrs = []; font.subrsBias = 0; } // Offsets in the top dict are relative to the beginning of the CFF data, so add the CFF start offset. var charStringsIndex = parseCFFIndex(data, start + topDict.charStrings); font.nGlyphs = charStringsIndex.objects.length; var charset = parseCFFCharset(data, start + topDict.charset, font.nGlyphs, stringIndex.objects); if (topDict.encoding === 0) { // Standard encoding font.cffEncoding = new encoding.CffEncoding(encoding.cffStandardEncoding, charset); } else if (topDict.encoding === 1) { // Expert encoding font.cffEncoding = new encoding.CffEncoding(encoding.cffExpertEncoding, charset); } else { font.cffEncoding = parseCFFEncoding(data, start + topDict.encoding, charset); } // Prefer the CMAP encoding to the CFF encoding. font.encoding = font.encoding || font.cffEncoding; font.glyphs = new glyphset.GlyphSet(font); for (var i = 0; i < font.nGlyphs; i += 1) { var charString = charStringsIndex.objects[i]; font.glyphs.push(i, glyphset.cffGlyphLoader(font, i, parseCFFCharstring, charString)); } } // Convert a string to a String ID (SID). // The list of strings is modified in place. function encodeString(s, strings) { var sid; // Is the string in the CFF standard strings? var i = encoding.cffStandardStrings.indexOf(s); if (i >= 0) { sid = i; } // Is the string already in the string index? i = strings.indexOf(s); if (i >= 0) { sid = i + encoding.cffStandardStrings.length; } else { sid = encoding.cffStandardStrings.length + strings.length; strings.push(s); } return sid; } function makeHeader() { return new table.Table('Header', [ {name: 'major', type: 'Card8', value: 1}, {name: 'minor', type: 'Card8', value: 0}, {name: 'hdrSize', type: 'Card8', value: 4}, {name: 'major', type: 'Card8', value: 1} ]); } function makeNameIndex(fontNames) { var t = new table.Table('Name INDEX', [ {name: 'names', type: 'INDEX', value: []} ]); t.names = []; for (var i = 0; i < fontNames.length; i += 1) { t.names.push({name: 'name_' + i, type: 'NAME', value: fontNames[i]}); } return t; } // Given a dictionary's metadata, create a DICT structure. function makeDict(meta, attrs, strings) { var m = {}; for (var i = 0; i < meta.length; i += 1) { var entry = meta[i]; var value = attrs[entry.name]; if (value !== undefined && !equals(value, entry.value)) { if (entry.type === 'SID') { value = encodeString(value, strings); } m[entry.op] = {name: entry.name, type: entry.type, value: value}; } } return m; } // The Top DICT houses the global font attributes. function makeTopDict(attrs, strings) { var t = new table.Table('Top DICT', [ {name: 'dict', type: 'DICT', value: {}} ]); t.dict = makeDict(TOP_DICT_META, attrs, strings); return t; } function makeTopDictIndex(topDict) { var t = new table.Table('Top DICT INDEX', [ {name: 'topDicts', type: 'INDEX', value: []} ]); t.topDicts = [{name: 'topDict_0', type: 'TABLE', value: topDict}]; return t; } function makeStringIndex(strings) { var t = new table.Table('String INDEX', [ {name: 'strings', type: 'INDEX', value: []} ]); t.strings = []; for (var i = 0; i < strings.length; i += 1) { t.strings.push({name: 'string_' + i, type: 'STRING', value: strings[i]}); } return t; } function makeGlobalSubrIndex() { // Currently we don't use subroutines. return new table.Table('Global Subr INDEX', [ {name: 'subrs', type: 'INDEX', value: []} ]); } function makeCharsets(glyphNames, strings) { var t = new table.Table('Charsets', [ {name: 'format', type: 'Card8', value: 0} ]); for (var i = 0; i < glyphNames.length; i += 1) { var glyphName = glyphNames[i]; var glyphSID = encodeString(glyphName, strings); t.fields.push({name: 'glyph_' + i, type: 'SID', value: glyphSID}); } return t; } function glyphToOps(glyph) { var ops = []; var path = glyph.path; ops.push({name: 'width', type: 'NUMBER', value: glyph.advanceWidth}); var x = 0; var y = 0; for (var i = 0; i < path.commands.length; i += 1) { var dx; var dy; var cmd = path.commands[i]; if (cmd.type === 'Q') { // CFF only supports bézier curves, so convert the quad to a bézier. var _13 = 1 / 3; var _23 = 2 / 3; // We're going to create a new command so we don't change the original path. cmd = { type: 'C', x: cmd.x, y: cmd.y, x1: _13 * x + _23 * cmd.x1, y1: _13 * y + _23 * cmd.y1, x2: _13 * cmd.x + _23 * cmd.x1, y2: _13 * cmd.y + _23 * cmd.y1 }; } if (cmd.type === 'M') { dx = Math.round(cmd.x - x); dy = Math.round(cmd.y - y); ops.push({name: 'dx', type: 'NUMBER', value: dx}); ops.push({name: 'dy', type: 'NUMBER', value: dy}); ops.push({name: 'rmoveto', type: 'OP', value: 21}); x = Math.round(cmd.x); y = Math.round(cmd.y); } else if (cmd.type === 'L') { dx = Math.round(cmd.x - x); dy = Math.round(cmd.y - y); ops.push({name: 'dx', type: 'NUMBER', value: dx}); ops.push({name: 'dy', type: 'NUMBER', value: dy}); ops.push({name: 'rlineto', type: 'OP', value: 5}); x = Math.round(cmd.x); y = Math.round(cmd.y); } else if (cmd.type === 'C') { var dx1 = Math.round(cmd.x1 - x); var dy1 = Math.round(cmd.y1 - y); var dx2 = Math.round(cmd.x2 - cmd.x1); var dy2 = Math.round(cmd.y2 - cmd.y1); dx = Math.round(cmd.x - cmd.x2); dy = Math.round(cmd.y - cmd.y2); ops.push({name: 'dx1', type: 'NUMBER', value: dx1}); ops.push({name: 'dy1', type: 'NUMBER', value: dy1}); ops.push({name: 'dx2', type: 'NUMBER', value: dx2}); ops.push({name: 'dy2', type: 'NUMBER', value: dy2}); ops.push({name: 'dx', type: 'NUMBER', value: dx}); ops.push({name: 'dy', type: 'NUMBER', value: dy}); ops.push({name: 'rrcurveto', type: 'OP', value: 8}); x = Math.round(cmd.x); y = Math.round(cmd.y); } // Contours are closed automatically. } ops.push({name: 'endchar', type: 'OP', value: 14}); return ops; } function makeCharStringsIndex(glyphs) { var t = new table.Table('CharStrings INDEX', [ {name: 'charStrings', type: 'INDEX', value: []} ]); for (var i = 0; i < glyphs.length; i += 1) { var glyph = glyphs.get(i); var ops = glyphToOps(glyph); t.charStrings.push({name: glyph.name, type: 'CHARSTRING', value: ops}); } return t; } function makePrivateDict(attrs, strings) { var t = new table.Table('Private DICT', [ {name: 'dict', type: 'DICT', value: {}} ]); t.dict = makeDict(PRIVATE_DICT_META, attrs, strings); return t; } function makeCFFTable(glyphs, options) { var t = new table.Table('CFF ', [ {name: 'header', type: 'TABLE'}, {name: 'nameIndex', type: 'TABLE'}, {name: 'topDictIndex', type: 'TABLE'}, {name: 'stringIndex', type: 'TABLE'}, {name: 'globalSubrIndex', type: 'TABLE'}, {name: 'charsets', type: 'TABLE'}, {name: 'charStringsIndex', type: 'TABLE'}, {name: 'privateDict', type: 'TABLE'} ]); var fontScale = 1 / options.unitsPerEm; // We use non-zero values for the offsets so that the DICT encodes them. // This is important because the size of the Top DICT plays a role in offset calculation, // and the size shouldn't change after we've written correct offsets. var attrs = { version: options.version, fullName: options.fullName, familyName: options.familyName, weight: options.weightName, fontBBox: options.fontBBox || [0, 0, 0, 0], fontMatrix: [fontScale, 0, 0, fontScale, 0, 0], charset: 999, encoding: 0, charStrings: 999, private: [0, 999] }; var privateAttrs = {}; var glyphNames = []; var glyph; // Skip first glyph (.notdef) for (var i = 1; i < glyphs.length; i += 1) { glyph = glyphs.get(i); glyphNames.push(glyph.name); } var strings = []; t.header = makeHeader(); t.nameIndex = makeNameIndex([options.postScriptName]); var topDict = makeTopDict(attrs, strings); t.topDictIndex = makeTopDictIndex(topDict); t.globalSubrIndex = makeGlobalSubrIndex(); t.charsets = makeCharsets(glyphNames, strings); t.charStringsIndex = makeCharStringsIndex(glyphs); t.privateDict = makePrivateDict(privateAttrs, strings); // Needs to come at the end, to encode all custom strings used in the font. t.stringIndex = makeStringIndex(strings); var startOffset = t.header.sizeOf() + t.nameIndex.sizeOf() + t.topDictIndex.sizeOf() + t.stringIndex.sizeOf() + t.globalSubrIndex.sizeOf(); attrs.charset = startOffset; // We use the CFF standard encoding; proper encoding will be handled in cmap. attrs.encoding = 0; attrs.charStrings = attrs.charset + t.charsets.sizeOf(); attrs.private[1] = attrs.charStrings + t.charStringsIndex.sizeOf(); // Recreate the Top DICT INDEX with the correct offsets. topDict = makeTopDict(attrs, strings); t.topDictIndex = makeTopDictIndex(topDict); return t; } exports.parse = parseCFFTable; exports.make = makeCFFTable; },{"../encoding":4,"../glyphset":7,"../parse":9,"../path":10,"../table":11}],13:[function(require,module,exports){ // The `cmap` table stores the mappings from characters to glyphs. // https://www.microsoft.com/typography/OTSPEC/cmap.htm 'use strict'; var check = require('../check'); var parse = require('../parse'); var table = require('../table'); // Parse the `cmap` table. This table stores the mappings from characters to glyphs. // There are many available formats, but we only support the Windows format 4. // This function returns a `CmapEncoding` object or null if no supported format could be found. function parseCmapTable(data, start) { var i; var cmap = {}; cmap.version = parse.getUShort(data, start); check.argument(cmap.version === 0, 'cmap table version should be 0.'); // The cmap table can contain many sub-tables, each with their own format. // We're only interested in a "platform 3" table. This is a Windows format. cmap.numTables = parse.getUShort(data, start + 2); var offset = -1; for (i = 0; i < cmap.numTables; i += 1) { var platformId = parse.getUShort(data, start + 4 + (i * 8)); var encodingId = parse.getUShort(data, start + 4 + (i * 8) + 2); if (platformId === 3 && (encodingId === 1 || encodingId === 0)) { offset = parse.getULong(data, start + 4 + (i * 8) + 4); break; } } if (offset === -1) { // There is no cmap table in the font that we support, so return null. // This font will be marked as unsupported. return null; } var p = new parse.Parser(data, start + offset); cmap.format = p.parseUShort(); check.argument(cmap.format === 4, 'Only format 4 cmap tables are supported.'); // Length in bytes of the sub-tables. cmap.length = p.parseUShort(); cmap.language = p.parseUShort(); // segCount is stored x 2. var segCount; cmap.segCount = segCount = p.parseUShort() >> 1; // Skip searchRange, entrySelector, rangeShift. p.skip('uShort', 3); // The "unrolled" mapping from character codes to glyph indices. cmap.glyphIndexMap = {}; var endCountParser = new parse.Parser(data, start + offset + 14); var startCountParser = new parse.Parser(data, start + offset + 16 + segCount * 2); var idDeltaParser = new parse.Parser(data, start + offset + 16 + segCount * 4); var idRangeOffsetParser = new parse.Parser(data, start + offset + 16 + segCount * 6); var glyphIndexOffset = start + offset + 16 + segCount * 8; for (i = 0; i < segCount - 1; i += 1) { var glyphIndex; var endCount = endCountParser.parseUShort(); var startCount = startCountParser.parseUShort(); var idDelta = idDeltaParser.parseShort(); var idRangeOffset = idRangeOffsetParser.parseUShort(); for (var c = startCount; c <= endCount; c += 1) { if (idRangeOffset !== 0) { // The idRangeOffset is relative to the current position in the idRangeOffset array. // Take the current offset in the idRangeOffset array. glyphIndexOffset = (idRangeOffsetParser.offset + idRangeOffsetParser.relativeOffset - 2); // Add the value of the idRangeOffset, which will move us into the glyphIndex array. glyphIndexOffset += idRangeOffset; // Then add the character index of the current segment, multiplied by 2 for USHORTs. glyphIndexOffset += (c - startCount) * 2; glyphIndex = parse.getUShort(data, glyphIndexOffset); if (glyphIndex !== 0) { glyphIndex = (glyphIndex + idDelta) & 0xFFFF; } } else { glyphIndex = (c + idDelta) & 0xFFFF; } cmap.glyphIndexMap[c] = glyphIndex; } } return cmap; } function addSegment(t, code, glyphIndex) { t.segments.push({ end: code, start: code, delta: -(code - glyphIndex), offset: 0 }); } function addTerminatorSegment(t) { t.segments.push({ end: 0xFFFF, start: 0xFFFF, delta: 1, offset: 0 }); } function makeCmapTable(glyphs) { var i; var t = new table.Table('cmap', [ {name: 'version', type: 'USHORT', value: 0}, {name: 'numTables', type: 'USHORT', value: 1}, {name: 'platformID', type: 'USHORT', value: 3}, {name: 'encodingID', type: 'USHORT', value: 1}, {name: 'offset', type: 'ULONG', value: 12}, {name: 'format', type: 'USHORT', value: 4}, {name: 'length', type: 'USHORT', value: 0}, {name: 'language', type: 'USHORT', value: 0}, {name: 'segCountX2', type: 'USHORT', value: 0}, {name: 'searchRange', type: 'USHORT', value: 0}, {name: 'entrySelector', type: 'USHORT', value: 0}, {name: 'rangeShift', type: 'USHORT', value: 0} ]); t.segments = []; for (i = 0; i < glyphs.length; i += 1) { var glyph = glyphs.get(i); for (var j = 0; j < glyph.unicodes.length; j += 1) { addSegment(t, glyph.unicodes[j], i); } t.segments = t.segments.sort(function(a, b) { return a.start - b.start; }); } addTerminatorSegment(t); var segCount; segCount = t.segments.length; t.segCountX2 = segCount * 2; t.searchRange = Math.pow(2, Math.floor(Math.log(segCount) / Math.log(2))) * 2; t.entrySelector = Math.log(t.searchRange / 2) / Math.log(2); t.rangeShift = t.segCountX2 - t.searchRange; // Set up parallel segment arrays. var endCounts = []; var startCounts = []; var idDeltas = []; var idRangeOffsets = []; var glyphIds = []; for (i = 0; i < segCount; i += 1) { var segment = t.segments[i]; endCounts = endCounts.concat({name: 'end_' + i, type: 'USHORT', value: segment.end}); startCounts = startCounts.concat({name: 'start_' + i, type: 'USHORT', value: segment.start}); idDeltas = idDeltas.concat({name: 'idDelta_' + i, type: 'SHORT', value: segment.delta}); idRangeOffsets = idRangeOffsets.concat({name: 'idRangeOffset_' + i, type: 'USHORT', value: segment.offset}); if (segment.glyphId !== undefined) { glyphIds = glyphIds.concat({name: 'glyph_' + i, type: 'USHORT', value: segment.glyphId}); } } t.fields = t.fields.concat(endCounts); t.fields.push({name: 'reservedPad', type: 'USHORT', value: 0}); t.fields = t.fields.concat(startCounts); t.fields = t.fields.concat(idDeltas); t.fields = t.fields.concat(idRangeOffsets); t.fields = t.fields.concat(glyphIds); t.length = 14 + // Subtable header endCounts.length * 2 + 2 + // reservedPad startCounts.length * 2 + idDeltas.length * 2 + idRangeOffsets.length * 2 + glyphIds.length * 2; return t; } exports.parse = parseCmapTable; exports.make = makeCmapTable; },{"../check":2,"../parse":9,"../table":11}],14:[function(require,module,exports){ // The `fvar` table stores font variation axes and instances. // https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6fvar.html 'use strict'; var check = require('../check'); var parse = require('../parse'); var table = require('../table'); function addName(name, names) { var nameString = JSON.stringify(name); var nameID = 256; for (var nameKey in names) { var n = parseInt(nameKey); if (!n || n < 256) { continue; } if (JSON.stringify(names[nameKey]) === nameString) { return n; } if (nameID <= n) { nameID = n + 1; } } names[nameID] = name; return nameID; } function makeFvarAxis(axis, names) { var nameID = addName(axis.name, names); return new table.Table('fvarAxis', [ {name: 'tag', type: 'TAG', value: axis.tag}, {name: 'minValue', type: 'FIXED', value: axis.minValue << 16}, {name: 'defaultValue', type: 'FIXED', value: axis.defaultValue << 16}, {name: 'maxValue', type: 'FIXED', value: axis.maxValue << 16}, {name: 'flags', type: 'USHORT', value: 0}, {name: 'nameID', type: 'USHORT', value: nameID} ]); } function parseFvarAxis(data, start, names) { var axis = {}; var p = new parse.Parser(data, start); axis.tag = p.parseTag(); axis.minValue = p.parseFixed(); axis.defaultValue = p.parseFixed(); axis.maxValue = p.parseFixed(); p.skip('uShort', 1); // reserved for flags; no values defined axis.name = names[p.parseUShort()] || {}; return axis; } function makeFvarInstance(inst, axes, names) { var nameID = addName(inst.name, names); var fields = [ {name: 'nameID', type: 'USHORT', value: nameID}, {name: 'flags', type: 'USHORT', value: 0} ]; for (var i = 0; i < axes.length; ++i) { var axisTag = axes[i].tag; fields.push({ name: 'axis ' + axisTag, type: 'FIXED', value: inst.coordinates[axisTag] << 16 }); } return new table.Table('fvarInstance', fields); } function parseFvarInstance(data, start, axes, names) { var inst = {}; var p = new parse.Parser(data, start); inst.name = names[p.parseUShort()] || {}; p.skip('uShort', 1); // reserved for flags; no values defined inst.coordinates = {}; for (var i = 0; i < axes.length; ++i) { inst.coordinates[axes[i].tag] = p.parseFixed(); } return inst; } function makeFvarTable(fvar, names) { var r###lt = new table.Table('fvar', [ {name: 'version', type: 'ULONG', value: 0x10000}, {name: 'offsetToData', type: 'USHORT', value: 0}, {name: 'countSizePairs', type: 'USHORT', value: 2}, {name: 'axisCount', type: 'USHORT', value: fvar.axes.length}, {name: 'axisSize', type: 'USHORT', value: 20}, {name: 'instanceCount', type: 'USHORT', value: fvar.instances.length}, {name: 'instanceSize', type: 'USHORT', value: 4 + fvar.axes.length * 4} ]); r###lt.offsetToData = r###lt.sizeOf(); for (var i = 0; i < fvar.axes.length; i++) { r###lt.fields.push({ name: 'axis ' + i, type: 'TABLE', value: makeFvarAxis(fvar.axes[i], names)}); } for (var j = 0; j < fvar.instances.length; j++) { r###lt.fields.push({ name: 'instance ' + j, type: 'TABLE', value: makeFvarInstance(fvar.instances[j], fvar.axes, names) }); } return r###lt; } function parseFvarTable(data, start, names) { var p = new parse.Parser(data, start); var tableVersion = p.parseULong(); check.argument(tableVersion === 0x00010000, 'Unsupported fvar table version.'); var offsetToData = p.parseOffset16(); // Skip countSizePairs. p.skip('uShort', 1); var axisCount = p.parseUShort(); var axisSize = p.parseUShort(); var instanceCount = p.parseUShort(); var instanceSize = p.parseUShort(); var axes = []; for (var i = 0; i < axisCount; i++) { axes.push(parseFvarAxis(data, start + offsetToData + i * axisSize, names)); } var instances = []; var instanceStart = start + offsetToData + axisCount * axisSize; for (var j = 0; j < instanceCount; j++) { instances.push(parseFvarInstance(data, instanceStart + j * instanceSize, axes, names)); } return {axes:axes, instances:instances}; } exports.make = makeFvarTable; exports.parse = parseFvarTable; },{"../check":2,"../parse":9,"../table":11}],15:[function(require,module,exports){ // The `glyf` table describes the glyphs in TrueType outline format. // http://www.microsoft.com/typography/otspec/glyf.htm 'use strict'; var check = require('../check'); var glyphset = require('../glyphset'); var parse = require('../parse'); var path = require('../path'); // Parse the coordinate data for a glyph. function parseGlyphCoordinate(p, flag, previousValue, shortVectorBitMask, sameBitMask) { var v; if ((flag & shortVectorBitMask) > 0) { // The coordinate is 1 byte long. v = p.parseByte(); // The `same` bit is re-used for short values to signify the sign of the value. if ((flag & sameBitMask) === 0) { v = -v; } v = previousValue + v; } else { // The coordinate is 2 bytes long. // If the `same` bit is set, the coordinate is the same as the previous coordinate. if ((flag & sameBitMask) > 0) { v = previousValue; } else { // Parse the coordinate as a signed 16-bit delta value. v = previousValue + p.parseShort(); } } return v; } // Parse a TrueType glyph. function parseGlyph(glyph, data, start) { var p = new parse.Parser(data, start); glyph.numberOfContours = p.parseShort(); glyph.xMin = p.parseShort(); glyph.yMin = p.parseShort(); glyph.xMax = p.parseShort(); glyph.yMax = p.parseShort(); var flags; var flag; if (glyph.numberOfContours > 0) { var i; // This glyph is not a composite. var endPointIndices = glyph.endPointIndices = []; for (i = 0; i < glyph.numberOfContours; i += 1) { endPointIndices.push(p.parseUShort()); } glyph.instructionLength = p.parseUShort(); glyph.instructions = []; for (i = 0; i < glyph.instructionLength; i += 1) { glyph.instructions.push(p.parseByte()); } var numberOfCoordinates = endPointIndices[endPointIndices.length - 1] + 1; flags = []; for (i = 0; i < numberOfCoordinates; i += 1) { flag = p.parseByte(); flags.push(flag); // If bit 3 is set, we repeat this flag n times, where n is the next byte. if ((flag & 8) > 0) { var repeatCount = p.parseByte(); for (var j = 0; j < repeatCount; j += 1) { flags.push(flag); i += 1; } } } check.argument(flags.length === numberOfCoordinates, 'Bad flags.'); if (endPointIndices.length > 0) { var points = []; var point; // X/Y coordinates are relative to the previous point, except for the first point which is relative to 0,0. if (numberOfCoordinates > 0) { for (i = 0; i < numberOfCoordinates; i += 1) { flag = flags[i]; point = {}; point.onCurve = !!(flag & 1); point.lastPointOfContour = endPointIndices.indexOf(i) >= 0; points.push(point); } var px = 0; for (i = 0; i < numberOfCoordinates; i += 1) { flag = flags[i]; point = points[i]; point.x = parseGlyphCoordinate(p, flag, px, 2, 16); px = point.x; } var py = 0; for (i = 0; i < numberOfCoordinates; i += 1) { flag = flags[i]; point = points[i]; point.y = parseGlyphCoordinate(p, flag, py, 4, 32); py = point.y; } } glyph.points = points; } else { glyph.points = []; } } else if (glyph.numberOfContours === 0) { glyph.points = []; } else { glyph.isComposite = true; glyph.points = []; glyph.components = []; var moreComponents = true; while (moreComponents) { flags = p.parseUShort(); var component = { glyphIndex: p.parseUShort(), xScale: 1, scale01: 0, scale10: 0, yScale: 1, dx: 0, dy: 0 }; if ((flags & 1) > 0) { // The arguments are words component.dx = p.parseShort(); component.dy = p.parseShort(); } else { // The arguments are bytes component.dx = p.parseChar(); component.dy = p.parseChar(); } if ((flags & 8) > 0) { // We have a scale component.xScale = component.yScale = p.parseF2Dot14(); } else if ((flags & 64) > 0) { // We have an X / Y scale component.xScale = p.parseF2Dot14(); component.yScale = p.parseF2Dot14(); } else if ((flags & 128) > 0) { // We have a 2x2 transformation component.xScale = p.parseF2Dot14(); component.scale01 = p.parseF2Dot14(); component.scale10 = p.parseF2Dot14(); component.yScale = p.parseF2Dot14(); } glyph.components.push(component); moreComponents = !!(flags & 32); } } } // Transform an array of points and return a new array. function transformPoints(points, transform) { var newPoints = []; for (var i = 0; i < points.length; i += 1) { var pt = points[i]; var newPt = { x: transform.xScale * pt.x + transform.scale01 * pt.y + transform.dx, y: transform.scale10 * pt.x + transform.yScale * pt.y + transform.dy, onCurve: pt.onCurve, lastPointOfContour: pt.lastPointOfContour }; newPoints.push(newPt); } return newPoints; } function getContours(points) { var contours = []; var currentContour = []; for (var i = 0; i < points.length; i += 1) { var pt = points[i]; currentContour.push(pt); if (pt.lastPointOfContour) { contours.push(currentContour); currentContour = []; } } check.argument(currentContour.length === 0, 'There are still points left in the current contour.'); return contours; } // Convert the TrueType glyph outline to a Path. function getPath(points) { var p = new path.Path(); if (!points) { return p; } var contours = getContours(points); for (var i = 0; i < contours.length; i += 1) { var contour = contours[i]; var firstPt = contour[0]; var lastPt = contour[contour.length - 1]; var curvePt; var realFirstPoint; if (firstPt.onCurve) { curvePt = null; // The first point will be consumed by the moveTo command, // so skip it in the loop. realFirstPoint = true; } else { if (lastPt.onCurve) { // If the first point is off-curve and the last point is on-curve, // start at the last point. firstPt = lastPt; } else { // If both first and last points are off-curve, start at their middle. firstPt = { x: (firstPt.x + lastPt.x) / 2, y: (firstPt.y + lastPt.y) / 2 }; } curvePt = firstPt; // The first point is synthesized, so don't skip the real first point. realFirstPoint = false; } p.moveTo(firstPt.x, firstPt.y); for (var j = realFirstPoint ? 1 : 0; j < contour.length; j += 1) { var pt = contour[j]; var prevPt = j === 0 ? firstPt : contour[j - 1]; if (prevPt.onCurve && pt.onCurve) { // This is a straight line. p.lineTo(pt.x, pt.y); } else if (prevPt.onCurve && !pt.onCurve) { curvePt = pt; } else if (!prevPt.onCurve && !pt.onCurve) { var midPt = { x: (prevPt.x + pt.x) / 2, y: (prevPt.y + pt.y) / 2 }; p.quadraticCurveTo(prevPt.x, prevPt.y, midPt.x, midPt.y); curvePt = pt; } else if (!prevPt.onCurve && pt.onCurve) { // Previous point off-curve, this point on-curve. p.quadraticCurveTo(curvePt.x, curvePt.y, pt.x, pt.y); curvePt = null; } else { throw new Error('Invalid state.'); } } if (firstPt !== lastPt) { // Connect the last and first points if (curvePt) { p.quadraticCurveTo(curvePt.x, curvePt.y, firstPt.x, firstPt.y); } else { p.lineTo(firstPt.x, firstPt.y); } } } p.closePath(); return p; } function buildPath(glyphs, glyph) { if (glyph.isComposite) { for (var j = 0; j < glyph.components.length; j += 1) { var component = glyph.components[j]; var componentGlyph = glyphs.get(component.glyphIndex); // Force the ttfGlyphLoader to parse the glyph. componentGlyph.getPath(); if (componentGlyph.points) { var transformedPoints = transformPoints(componentGlyph.points, component); glyph.points = glyph.points.concat(transformedPoints); } } } return getPath(glyph.points); } // Parse all the glyphs according to the offsets from the `loca` table. function parseGlyfTable(data, start, loca, font) { var glyphs = new glyphset.GlyphSet(font); var i; // The last element of the loca table is invalid. for (i = 0; i < loca.length - 1; i += 1) { var offset = loca[i]; var nextOffset = loca[i + 1]; if (offset !== nextOffset) { glyphs.push(i, glyphset.ttfGlyphLoader(font, i, parseGlyph, data, start + offset, buildPath)); } else { glyphs.push(i, glyphset.glyphLoader(font, i)); } } return glyphs; } exports.parse = parseGlyfTable; },{"../check":2,"../glyphset":7,"../parse":9,"../path":10}],16:[function(require,module,exports){ // The `GPOS` table contains kerning pairs, among other things. // https://www.microsoft.com/typography/OTSPEC/gpos.htm 'use strict'; var check = require('../check'); var parse = require('../parse'); // Parse ScriptList and FeatureList tables of GPOS, GSUB, GDEF, BASE, JSTF tables. // These lists are unused by now, this function is just the basis for a real parsing. function parseTaggedListTable(data, start) { var p = new parse.Parser(data, start); var n = p.parseUShort(); var list = []; for (var i = 0; i < n; i++) { list[p.parseTag()] = { offset: p.parseUShort() }; } return list; } // Parse a coverage table in a GSUB, GPOS or GDEF table. // Format 1 is a simple list of glyph ids, // Format 2 is a list of ranges. It is expanded in a list of glyphs, maybe not the best idea. function parseCoverageTable(data, start) { var p = new parse.Parser(data, start); var format = p.parseUShort(); var count = p.parseUShort(); if (format === 1) { return p.parseUShortList(count); } else if (format === 2) { var coverage = []; for (; count--;) { var begin = p.parseUShort(); var end = p.parseUShort(); var index = p.parseUShort(); for (var i = begin; i <= end; i++) { coverage[index++] = i; } } return coverage; } } // Parse a Class Definition Table in a GSUB, GPOS or GDEF table. // Returns a function that gets a class value from a glyph ID. function parseClassDefTable(data, start) { var p = new parse.Parser(data, start); var format = p.parseUShort(); if (format === 1) { // Format 1 specifies a range of consecutive glyph indices, one class per glyph ID. var startGlyph = p.parseUShort(); var glyphCount = p.parseUShort(); var classes = p.parseUShortList(glyphCount); return function(glyphID) { return classes[glyphID - startGlyph] || 0; }; } else if (format === 2) { // Format 2 defines multiple groups of glyph indices that belong to the same class. var rangeCount = p.parseUShort(); var startGlyphs = []; var endGlyphs = []; var classValues = []; for (var i = 0; i < rangeCount; i++) { startGlyphs[i] = p.parseUShort(); endGlyphs[i] = p.parseUShort(); classValues[i] = p.parseUShort(); } return function(glyphID) { var l = 0; var r = startGlyphs.length - 1; while (l < r) { var c = (l + r + 1) >> 1; if (glyphID < startGlyphs[c]) { r = c - 1; } else { l = c; } } if (startGlyphs[l] <= glyphID && glyphID <= endGlyphs[l]) { return classValues[l] || 0; } return 0; }; } } // Parse a pair adjustment positioning subtable, format 1 or format 2 // The subtable is returned in the form of a lookup function. function parsePairPosSubTable(data, start) { var p = new parse.Parser(data, start); // This part is common to format 1 and format 2 subtables var format = p.parseUShort(); var coverageOffset = p.parseUShort(); var coverage = parseCoverageTable(data, start + coverageOffset); // valueFormat 4: XAdvance only, 1: XPlacement only, 0: no ValueRecord for second glyph // Only valueFormat1=4 and valueFormat2=0 is supported. var valueFormat1 = p.parseUShort(); var valueFormat2 = p.parseUShort(); var value1; var value2; if (valueFormat1 !== 4 || valueFormat2 !== 0) return; var sharedPairSets = {}; if (format === 1) { // Pair Positioning Adjustment: Format 1 var pairSetCount = p.parseUShort(); var pairSet = []; // Array of offsets to PairSet tables-from beginning of PairPos subtable-ordered by Coverage Index var pairSetOffsets = p.parseOffset16List(pairSetCount); for (var firstGlyph = 0; firstGlyph < pairSetCount; firstGlyph++) { var pairSetOffset = pairSetOffsets[firstGlyph]; var sharedPairSet = sharedPairSets[pairSetOffset]; if (!sharedPairSet) { // Parse a pairset table in a pair adjustment subtable format 1 sharedPairSet = {}; p.relativeOffset = pairSetOffset; var pairValueCount = p.parseUShort(); for (; pairValueCount--;) { var secondGlyph = p.parseUShort(); if (valueFormat1) value1 = p.parseShort(); if (valueFormat2) value2 = p.parseShort(); // We only support valueFormat1 = 4 and valueFormat2 = 0, // so value1 is the XAdvance and value2 is empty. sharedPairSet[secondGlyph] = value1; } } pairSet[coverage[firstGlyph]] = sharedPairSet; } return function(leftGlyph, rightGlyph) { var pairs = pairSet[leftGlyph]; if (pairs) return pairs[rightGlyph]; }; } else if (format === 2) { // Pair Positioning Adjustment: Format 2 var classDef1Offset = p.parseUShort(); var classDef2Offset = p.parseUShort(); var class1Count = p.parseUShort(); var class2Count = p.parseUShort(); var getClass1 = parseClassDefTable(data, start + classDef1Offset); var getClass2 = parseClassDefTable(data, start + classDef2Offset); // Parse kerning values by class pair. var kerningMatrix = []; for (var i = 0; i < class1Count; i++) { var kerningRow = kerningMatrix[i] = []; for (var j = 0; j < class2Count; j++) { if (valueFormat1) value1 = p.parseShort(); if (valueFormat2) value2 = p.parseShort(); // We only support valueFormat1 = 4 and valueFormat2 = 0, // so value1 is the XAdvance and value2 is empty. kerningRow[j] = value1; } } // Convert coverage list to a hash var covered = {}; for (i = 0; i < coverage.length; i++) covered[coverage[i]] = 1; // Get the kerning value for a specific glyph pair. return function(leftGlyph, rightGlyph) { if (!covered[leftGlyph]) return; var class1 = getClass1(leftGlyph); var class2 = getClass2(rightGlyph); var kerningRow = kerningMatrix[class1]; if (kerningRow) { return kerningRow[class2]; } }; } } // Parse a LookupTable (present in of GPOS, GSUB, GDEF, BASE, JSTF tables). function parseLookupTable(data, start) { var p = new parse.Parser(data, start); var lookupType = p.parseUShort(); var lookupFlag = p.parseUShort(); var useMarkFilteringSet = lookupFlag & 0x10; var subTableCount = p.parseUShort(); var subTableOffsets = p.parseOffset16List(subTableCount); var table = { lookupType: lookupType, lookupFlag: lookupFlag, markFilteringSet: useMarkFilteringSet ? p.parseUShort() : -1 }; // LookupType 2, Pair adjustment if (lookupType === 2) { var subtables = []; for (var i = 0; i < subTableCount; i++) { subtables.push(parsePairPosSubTable(data, start + subTableOffsets[i])); } // Return a function which finds the kerning values in the subtables. table.getKerningValue = function(leftGlyph, rightGlyph) { for (var i = subtables.length; i--;) { var value = subtables[i](leftGlyph, rightGlyph); if (value !== undefined) return value; } return 0; }; } return table; } // Parse the `GPOS` table which contains, among other things, kerning pairs. // https://www.microsoft.com/typography/OTSPEC/gpos.htm function parseGposTable(data, start, font) { var p = new parse.Parser(data, start); var tableVersion = p.parseFixed(); check.argument(tableVersion === 1, 'Unsupported GPOS table version.'); // ScriptList and FeatureList - ignored for now parseTaggedListTable(data, start + p.parseUShort()); // 'kern' is the feature we are looking for. parseTaggedListTable(data, start + p.parseUShort()); // LookupList var lookupListOffset = p.parseUShort(); p.relativeOffset = lookupListOffset; var lookupCount = p.parseUShort(); var lookupTableOffsets = p.parseOffset16List(lookupCount); var lookupListAbsoluteOffset = start + lookupListOffset; for (var i = 0; i < lookupCount; i++) { var table = parseLookupTable(data, lookupListAbsoluteOffset + lookupTableOffsets[i]); if (table.lookupType === 2 && !font.getGposKerningValue) font.getGposKerningValue = table.getKerningValue; } } exports.parse = parseGposTable; },{"../check":2,"../parse":9}],17:[function(require,module,exports){ // The `head` table contains global information about the font. // https://www.microsoft.com/typography/OTSPEC/head.htm 'use strict'; var check = require('../check'); var parse = require('../parse'); var table = require('../table'); // Parse the header `head` table function parseHeadTable(data, start) { var head = {}; var p = new parse.Parser(data, start); head.version = p.parseVersion(); head.fontRevision = Math.round(p.parseFixed() * 1000) / 1000; head.checkSumAdjustment = p.parseULong(); head.magicNumber = p.parseULong(); check.argument(head.magicNumber === 0x5F0F3CF5, 'Font header has wrong magic number.'); head.flags = p.parseUShort(); head.unitsPerEm = p.parseUShort(); head.created = p.parseLongDateTime(); head.modified = p.parseLongDateTime(); head.xMin = p.parseShort(); head.yMin = p.parseShort(); head.xMax = p.parseShort(); head.yMax = p.parseShort(); head.macStyle = p.parseUShort(); head.lowestRecPPEM = p.parseUShort(); head.fontDirectionHint = p.parseShort(); head.indexToLocFormat = p.parseShort(); head.glyphDataFormat = p.parseShort(); return head; } function makeHeadTable(options) { return new table.Table('head', [ {name: 'version', type: 'FIXED', value: 0x00010000}, {name: 'fontRevision', type: 'FIXED', value: 0x00010000}, {name: 'checkSumAdjustment', type: 'ULONG', value: 0}, {name: 'magicNumber', type: 'ULONG', value: 0x5F0F3CF5}, {name: 'flags', type: 'USHORT', value: 0}, {name: 'unitsPerEm', type: 'USHORT', value: 1000}, {name: 'created', type: 'LONGDATETIME', value: 0}, {name: 'modified', type: 'LONGDATETIME', value: 0}, {name: 'xMin', type: 'SHORT', value: 0}, {name: 'yMin', type: 'SHORT', value: 0}, {name: 'xMax', type: 'SHORT', value: 0}, {name: 'yMax', type: 'SHORT', value: 0}, {name: 'macStyle', type: 'USHORT', value: 0}, {name: 'lowestRecPPEM', type: 'USHORT', value: 0}, {name: 'fontDirectionHint', type: 'SHORT', value: 2}, {name: 'indexToLocFormat', type: 'SHORT', value: 0}, {name: 'glyphDataFormat', type: 'SHORT', value: 0} ], options); } exports.parse = parseHeadTable; exports.make = makeHeadTable; },{"../check":2,"../parse":9,"../table":11}],18:[function(require,module,exports){ // The `hhea` table contains information for horizontal layout. // https://www.microsoft.com/typography/OTSPEC/hhea.htm 'use strict'; var parse = require('../parse'); var table = require('../table'); // Parse the horizontal header `hhea` table function parseHheaTable(data, start) { var hhea = {}; var p = new parse.Parser(data, start); hhea.version = p.parseVersion(); hhea.ascender = p.parseShort(); hhea.descender = p.parseShort(); hhea.lineGap = p.parseShort(); hhea.advanceWidthMax = p.parseUShort(); hhea.minLeftSideBearing = p.parseShort(); hhea.minRightSideBearing = p.parseShort(); hhea.xMaxExtent = p.parseShort(); hhea.caretSlopeRise = p.parseShort(); hhea.caretSlopeRun = p.parseShort(); hhea.caretOffset = p.parseShort(); p.relativeOffset += 8; hhea.metricDataFormat = p.parseShort(); hhea.numberOfHMetrics = p.parseUShort(); return hhea; } function makeHheaTable(options) { return new table.Table('hhea', [ {name: 'version', type: 'FIXED', value: 0x00010000}, {name: 'ascender', type: 'FWORD', value: 0}, {name: 'descender', type: 'FWORD', value: 0}, {name: 'lineGap', type: 'FWORD', value: 0}, {name: 'advanceWidthMax', type: 'UFWORD', value: 0}, {name: 'minLeftSideBearing', type: 'FWORD', value: 0}, {name: 'minRightSideBearing', type: 'FWORD', value: 0}, {name: 'xMaxExtent', type: 'FWORD', value: 0}, {name: 'caretSlopeRise', type: 'SHORT', value: 1}, {name: 'caretSlopeRun', type: 'SHORT', value: 0}, {name: 'caretOffset', type: 'SHORT', value: 0}, {name: 'reserved1', type: 'SHORT', value: 0}, {name: 'reserved2', type: 'SHORT', value: 0}, {name: 'reserved3', type: 'SHORT', value: 0}, {name: 'reserved4', type: 'SHORT', value: 0}, {name: 'metricDataFormat', type: 'SHORT', value: 0}, {name: 'numberOfHMetrics', type: 'USHORT', value: 0} ], options); } exports.parse = parseHheaTable; exports.make = makeHheaTable; },{"../parse":9,"../table":11}],19:[function(require,module,exports){ // The `hmtx` table contains the horizontal metrics for all glyphs. // https://www.microsoft.com/typography/OTSPEC/hmtx.htm 'use strict'; var parse = require('../parse'); var table = require('../table'); // Parse the `hmtx` table, which contains the horizontal metrics for all glyphs. // This function augments the glyph array, adding the advanceWidth and leftSideBearing to each glyph. function parseHmtxTable(data, start, numMetrics, numGlyphs, glyphs) { var advanceWidth; var leftSideBearing; var p = new parse.Parser(data, start); for (var i = 0; i < numGlyphs; i += 1) { // If the font is monospaced, only one entry is needed. This last entry applies to all subsequent glyphs. if (i < numMetrics) { advanceWidth = p.parseUShort(); leftSideBearing = p.parseShort(); } var glyph = glyphs.get(i); glyph.advanceWidth = advanceWidth; glyph.leftSideBearing = leftSideBearing; } } function makeHmtxTable(glyphs) { var t = new table.Table('hmtx', []); for (var i = 0; i < glyphs.length; i += 1) { var glyph = glyphs.get(i); var advanceWidth = glyph.advanceWidth || 0; var leftSideBearing = glyph.leftSideBearing || 0; t.fields.push({name: 'advanceWidth_' + i, type: 'USHORT', value: advanceWidth}); t.fields.push({name: 'leftSideBearing_' + i, type: 'SHORT', value: leftSideBearing}); } return t; } exports.parse = parseHmtxTable; exports.make = makeHmtxTable; },{"../parse":9,"../table":11}],20:[function(require,module,exports){ // The `kern` table contains kerning pairs. // Note that some fonts use the GPOS OpenType layout table to specify kerning. // https://www.microsoft.com/typography/OTSPEC/kern.htm 'use strict'; var check = require('../check'); var parse = require('../parse'); // Parse the `kern` table which contains kerning pairs. function parseKernTable(data, start) { var pairs = {}; var p = new parse.Parser(data, start); var tableVersion = p.parseUShort(); check.argument(tableVersion === 0, 'Unsupported kern table version.'); // Skip nTables. p.skip('uShort', 1); var subTableVersion = p.parseUShort(); check.argument(subTableVersion === 0, 'Unsupported kern sub-table version.'); // Skip subTableLength, subTableCoverage p.skip('uShort', 2); var nPairs = p.parseUShort(); // Skip searchRange, entrySelector, rangeShift. p.skip('uShort', 3); for (var i = 0; i < nPairs; i += 1) { var leftIndex = p.parseUShort(); var rightIndex = p.parseUShort(); var value = p.parseShort(); pairs[leftIndex + ',' + rightIndex] = value; } return pairs; } exports.parse = parseKernTable; },{"../check":2,"../parse":9}],21:[function(require,module,exports){ // The `loca` table stores the offsets to the locations of the glyphs in the font. // https://www.microsoft.com/typography/OTSPEC/loca.htm 'use strict'; var parse = require('../parse'); // Parse the `loca` table. This table stores the offsets to the locations of the glyphs in the font, // relative to the beginning of the glyphData table. // The number of glyphs stored in the `loca` table is specified in the `maxp` table (under numGlyphs) // The loca table has two versions: a short version where offsets are stored as uShorts, and a long // version where offsets are stored as uLongs. The `head` table specifies which version to use // (under indexToLocFormat). function parseLocaTable(data, start, numGlyphs, shortVersion) { var p = new parse.Parser(data, start); var parseFn = shortVersion ? p.parseUShort : p.parseULong; // There is an extra entry after the last index element to compute the length of the last glyph. // That's why we use numGlyphs + 1. var glyphOffsets = []; for (var i = 0; i < numGlyphs + 1; i += 1) { var glyphOffset = parseFn.call(p); if (shortVersion) { // The short table version stores the actual offset divided by 2. glyphOffset *= 2; } glyphOffsets.push(glyphOffset); } return glyphOffsets; } exports.parse = parseLocaTable; },{"../parse":9}],22:[function(require,module,exports){ // The `ltag` table stores IETF BCP-47 language tags. It allows supporting // languages for which TrueType does not assign a numeric code. // https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6ltag.html // http://www.w3.org/International/articles/language-tags/ // http://www.iana.org/assignments/language-subtag-registry/language-subtag-registry 'use strict'; var check = require('../check'); var parse = require('../parse'); var table = require('../table'); function makeLtagTable(tags) { var r###lt = new table.Table('ltag', [ {name: 'version', type: 'ULONG', value: 1}, {name: 'flags', type: 'ULONG', value: 0}, {name: 'numTags', type: 'ULONG', value: tags.length} ]); var stringPool = ''; var stringPoolOffset = 12 + tags.length * 4; for (var i = 0; i < tags.length; ++i) { var pos = stringPool.indexOf(tags[i]); if (pos < 0) { pos = stringPool.length; stringPool += tags[i]; } r###lt.fields.push({name: 'offset ' + i, type: 'USHORT', value: stringPoolOffset + pos}); r###lt.fields.push({name: 'length ' + i, type: 'USHORT', value: tags[i].length}); } r###lt.fields.push({name: 'stringPool', type: 'CHARARRAY', value: stringPool}); return r###lt; } function parseLtagTable(data, start) { var p = new parse.Parser(data, start); var tableVersion = p.parseULong(); check.argument(tableVersion === 1, 'Unsupported ltag table version.'); // The 'ltag' specification does not define any flags; skip the field. p.skip('uLong', 1); var numTags = p.parseULong(); var tags = []; for (var i = 0; i < numTags; i++) { var tag = ''; var offset = start + p.parseUShort(); var length = p.parseUShort(); for (var j = offset; j < offset + length; ++j) { tag += String.fromCharCode(data.getInt8(j)); } tags.push(tag); } return tags; } exports.make = makeLtagTable; exports.parse = parseLtagTable; },{"../check":2,"../parse":9,"../table":11}],23:[function(require,module,exports){ // The `maxp` table establishes the memory requirements for the font. // We need it just to get the number of glyphs in the font. // https://www.microsoft.com/typography/OTSPEC/maxp.htm 'use strict'; var parse = require('../parse'); var table = require('../table'); // Parse the maximum profile `maxp` table. function parseMaxpTable(data, start) { var maxp = {}; var p = new parse.Parser(data, start); maxp.version = p.parseVersion(); maxp.numGlyphs = p.parseUShort(); if (maxp.version === 1.0) { maxp.maxPoints = p.parseUShort(); maxp.maxContours = p.parseUShort(); maxp.maxCompositePoints = p.parseUShort(); maxp.maxCompositeContours = p.parseUShort(); maxp.maxZones = p.parseUShort(); maxp.maxTwilightPoints = p.parseUShort(); maxp.maxStorage = p.parseUShort(); maxp.maxFunctionDefs = p.parseUShort(); maxp.maxInstructionDefs = p.parseUShort(); maxp.maxStackElements = p.parseUShort(); maxp.maxSizeOfInstructions = p.parseUShort(); maxp.maxComponentElements = p.parseUShort(); maxp.maxComponentDepth = p.parseUShort(); } return maxp; } function makeMaxpTable(numGlyphs) { return new table.Table('maxp', [ {name: 'version', type: 'FIXED', value: 0x00005000}, {name: 'numGlyphs', type: 'USHORT', value: numGlyphs} ]); } exports.parse = parseMaxpTable; exports.make = makeMaxpTable; },{"../parse":9,"../table":11}],24:[function(require,module,exports){ // The `name` naming table. // https://www.microsoft.com/typography/OTSPEC/name.htm 'use strict'; var types = require('../types'); var decode = types.decode; var encode = types.encode; var parse = require('../parse'); var table = require('../table'); // NameIDs for the name table. var nameTableNames = [ 'copyright', // 0 'fontFamily', // 1 'fontSubfamily', // 2 'uniqueID', // 3 'fullName', // 4 'version', // 5 'postScriptName', // 6 'trademark', // 7 'manufacturer', // 8 'designer', // 9 'description', // 10 'manufacturerURL', // 11 'designerURL', // 12 'license', // 13 'licenseURL', // 14 'reserved', // 15 'preferredFamily', // 16 'preferredSubfamily', // 17 'compatibleFullName', // 18 'sampleText', // 19 'postScriptFindFontName', // 20 'wwsFamily', // 21 'wwsSubfamily' // 22 ]; var macLanguages = { 0: 'en', 1: 'fr', 2: 'de', 3: 'it', 4: 'nl', 5: 'sv', 6: 'es', 7: 'da', 8: 'pt', 9: 'no', 10: 'he', 11: 'ja', 12: 'ar', 13: 'fi', 14: 'el', 15: 'is', 16: 'mt', 17: 'tr', 18: 'hr', 19: 'zh-Hant', 20: 'ur', 21: 'hi', 22: 'th', 23: 'ko', 24: 'lt', 25: 'pl', 26: 'hu', 27: 'es', 28: 'lv', 29: 'se', 30: 'fo', 31: 'fa', 32: 'ru', 33: 'zh', 34: 'nl-BE', 35: 'ga', 36: 'sq', 37: 'ro', 38: 'cz', 39: 'sk', 40: 'si', 41: 'yi', 42: 'sr', 43: 'mk', 44: 'bg', 45: 'uk', 46: 'be', 47: 'uz', 48: 'kk', 49: 'az-Cyrl', 50: 'az-Arab', 51: 'hy', 52: 'ka', 53: 'mo', 54: 'ky', 55: 'tg', 56: 'tk', 57: 'mn-CN', 58: 'mn', 59: 'ps', 60: 'ks', 61: 'ku', 62: 'sd', 63: 'bo', 64: 'ne', 65: 'sa', 66: 'mr', 67: 'bn', 68: 'as', 69: 'gu', 70: 'pa', 71: 'or', 72: 'ml', 73: 'kn', 74: 'ta', 75: 'te', 76: 'si', 77: 'my', 78: 'km', 79: 'lo', 80: 'vi', 81: 'id', 82: 'tl', 83: 'ms', 84: 'ms-Arab', 85: 'am', 86: 'ti', 87: 'om', 88: 'so', 89: 'sw', 90: 'rw', 91: 'rn', 92: 'ny', 93: 'mg', 94: 'eo', 128: 'cy', 129: 'eu', 130: 'ca', 131: 'la', 132: 'qu', 133: 'gn', 134: 'ay', 135: 'tt', 136: 'ug', 137: 'dz', 138: 'jv', 139: 'su', 140: 'gl', 141: 'af', 142: 'br', 143: 'iu', 144: 'gd', 145: 'gv', 146: 'ga', 147: 'to', 148: 'el-polyton', 149: 'kl', 150: 'az', 151: 'nn' }; // MacOS language ID → MacOS script ID // // Note that the script ID is not sufficient to determine what encoding // to use in TrueType files. For some languages, MacOS used a modification // of a mainstream script. For example, an Icelandic name would be stored // with smRoman in the TrueType naming table, but the actual encoding // is a special Icelandic version of the normal Macintosh Roman encoding. // As another example, Inuktitut uses an 8-bit encoding for Canadian Aboriginal // Syllables but MacOS had run out of available script codes, so this was // done as a (pretty radical) "modification" of Ethiopic. // // http://unicode.org/Public/MAPPINGS/VENDORS/APPLE/Readme.txt var macLanguageToScript = { 0: 0, // langEnglish → smRoman 1: 0, // langFrench → smRoman 2: 0, // langGerman → smRoman 3: 0, // langItalian → smRoman 4: 0, // langDutch → smRoman 5: 0, // langSwedish → smRoman 6: 0, // langSpanish → smRoman 7: 0, // langDanish → smRoman 8: 0, // langPortuguese → smRoman 9: 0, // langNorwegian → smRoman 10: 5, // langHebrew → smHebrew 11: 1, // langJapanese → smJapanese 12: 4, // langArabic → smArabic 13: 0, // langFinnish → smRoman 14: 6, // langGreek → smGreek 15: 0, // langIcelandic → smRoman (modified) 16: 0, // langMaltese → smRoman 17: 0, // langTurkish → smRoman (modified) 18: 0, // langCroatian → smRoman (modified) 19: 2, // langTradChinese → smTradChinese 20: 4, // langUrdu → smArabic 21: 9, // langHindi → smDevanagari 22: 21, // langThai → smThai 23: 3, // langKorean → smKorean 24: 29, // langLithuanian → smCentralEuroRoman 25: 29, // langPolish → smCentralEuroRoman 26: 29, // langHungarian → smCentralEuroRoman 27: 29, // langEstonian → smCentralEuroRoman 28: 29, // langLatvian → smCentralEuroRoman 29: 0, // langSami → smRoman 30: 0, // langFaroese → smRoman (modified) 31: 4, // langFarsi → smArabic (modified) 32: 7, // langRussian → smCyrillic 33: 25, // langSimpChinese → smSimpChinese 34: 0, // langFlemish → smRoman 35: 0, // langIrishGaelic → smRoman (modified) 36: 0, // langAlbanian → smRoman 37: 0, // langRomanian → smRoman (modified) 38: 29, // langCzech → smCentralEuroRoman 39: 29, // langSlovak → smCentralEuroRoman 40: 0, // langSlovenian → smRoman (modified) 41: 5, // langYiddish → smHebrew 42: 7, // langSerbian → smCyrillic 43: 7, // langMacedonian → smCyrillic 44: 7, // langBulgarian → smCyrillic 45: 7, // langUkrainian → smCyrillic (modified) 46: 7, // langByelorussian → smCyrillic 47: 7, // langUzbek → smCyrillic 48: 7, // langKazakh → smCyrillic 49: 7, // langAzerbaijani → smCyrillic 50: 4, // langAzerbaijanAr → smArabic 51: 24, // langArmenian → smArmenian 52: 23, // langGeorgian → smGeorgian 53: 7, // langMoldavian → smCyrillic 54: 7, // langKirghiz → smCyrillic 55: 7, // langTajiki → smCyrillic 56: 7, // langTurkmen → smCyrillic 57: 27, // langMongolian → smMongolian 58: 7, // langMongolianCyr → smCyrillic 59: 4, // langPashto → smArabic 60: 4, // langKurdish → smArabic 61: 4, // langKashmiri → smArabic 62: 4, // langSindhi → smArabic 63: 26, // langTibetan → smTibetan 64: 9, // langNepali → smDevanagari 65: 9, // langSanskrit → smDevanagari 66: 9, // langMarathi → smDevanagari 67: 13, // langBengali → smBengali 68: 13, // langAssamese → smBengali 69: 11, // langGujarati → smGujarati 70: 10, // langPunjabi → smGurmukhi 71: 12, // langOriya → smOriya 72: 17, // langMalayalam → smMalayalam 73: 16, // langKannada → smKannada 74: 14, // langTamil → smTamil 75: 15, // langTelugu → smTelugu 76: 18, // langSinhalese → smSinhalese 77: 19, // langBurmese → smBurmese 78: 20, // langKhmer → smKhmer 79: 22, // langLao → smLao 80: 30, // langVietnamese → smVietnamese 81: 0, // langIndonesian → smRoman 82: 0, // langTagalog → smRoman 83: 0, // langMalayRoman → smRoman 84: 4, // langMalayArabic → smArabic 85: 28, // langAmharic → smEthiopic 86: 28, // langTigrinya → smEthiopic 87: 28, // langOromo → smEthiopic 88: 0, // langSomali → smRoman 89: 0, // langSwahili → smRoman 90: 0, // langKinyarwanda → smRoman 91: 0, // langRundi → smRoman 92: 0, // langNyanja → smRoman 93: 0, // langMalagasy → smRoman 94: 0, // langEsperanto → smRoman 128: 0, // langWelsh → smRoman (modified) 129: 0, // langBasque → smRoman 130: 0, // langCatalan → smRoman 131: 0, // langLatin → smRoman 132: 0, // langQuechua → smRoman 133: 0, // langGuarani → smRoman 134: 0, // langAymara → smRoman 135: 7, // langTatar → smCyrillic 136: 4, // langUighur → smArabic 137: 26, // langDzongkha → smTibetan 138: 0, // langJavaneseRom → smRoman 139: 0, // langSundaneseRom → smRoman 140: 0, // langGalician → smRoman 141: 0, // langAfrikaans → smRoman 142: 0, // langBreton → smRoman (modified) 143: 28, // langInuktitut → smEthiopic (modified) 144: 0, // langScottishGaelic → smRoman (modified) 145: 0, // langManxGaelic → smRoman (modified) 146: 0, // langIrishGaelicScript → smRoman (modified) 147: 0, // langTongan → smRoman 148: 6, // langGreekAncient → smRoman 149: 0, // langGreenlandic → smRoman 150: 0, // langAzerbaijanRoman → smRoman 151: 0 // langNynorsk → smRoman }; // While Microsoft indicates a region/country for all its language // IDs, we omit the region code if it's equal to the "most likely // region subtag" according to Unicode CLDR. For scripts, we omit // the subtag if it is equal to the Suppress-Script entry in the // IANA language subtag registry for IETF BCP 47. // // For example, Microsoft states that its language code 0x041A is // Croatian in Croatia. We transform this to the BCP 47 language code 'hr' // and not 'hr-HR' because Croatia is the default country for Croatian, // according to Unicode CLDR. As another example, Microsoft states // that 0x101A is Croatian (Latin) in Bosnia-Herzegovina. We transform // this to 'hr-BA' and not 'hr-Latn-BA' because Latin is the default script // for the Croatian language, according to IANA. // // http://www.unicode.org/cldr/charts/latest/supplemental/likely_subtags.html // http://www.iana.org/assignments/language-subtag-registry/language-subtag-registry var windowsLanguages = { 0x0436: 'af', 0x041C: 'sq', 0x0484: 'gsw', 0x045E: 'am', 0x1401: 'ar-DZ', 0x3C01: 'ar-BH', 0x0C01: 'ar', 0x0801: 'ar-IQ', 0x2C01: 'ar-JO', 0x3401: 'ar-KW', 0x3001: 'ar-LB', 0x1001: 'ar-LY', 0x1801: 'ary', 0x2001: 'ar-OM', 0x4001: 'ar-QA', 0x0401: 'ar-SA', 0x2801: 'ar-SY', 0x1C01: 'aeb', 0x3801: 'ar-AE', 0x2401: 'ar-YE', 0x042B: 'hy', 0x044D: 'as', 0x082C: 'az-Cyrl', 0x042C: 'az', 0x046D: 'ba', 0x042D: 'eu', 0x0423: 'be', 0x0845: 'bn', 0x0445: 'bn-IN', 0x201A: 'bs-Cyrl', 0x141A: 'bs', 0x047E: 'br', 0x0402: 'bg', 0x0403: 'ca', 0x0C04: 'zh-HK', 0x1404: 'zh-MO', 0x0804: 'zh', 0x1004: 'zh-SG', 0x0404: 'zh-TW', 0x0483: 'co', 0x041A: 'hr', 0x101A: 'hr-BA', 0x0405: 'cs', 0x0406: 'da', 0x048C: 'prs', 0x0465: 'dv', 0x0813: 'nl-BE', 0x0413: 'nl', 0x0C09: 'en-AU', 0x2809: 'en-BZ', 0x1009: 'en-CA', 0x2409: 'en-029', 0x4009: 'en-IN', 0x1809: 'en-IE', 0x2009: 'en-JM', 0x4409: 'en-MY', 0x1409: 'en-NZ', 0x3409: 'en-PH', 0x4809: 'en-SG', 0x1C09: 'en-ZA', 0x2C09: 'en-TT', 0x0809: 'en-GB', 0x0409: 'en', 0x3009: 'en-ZW', 0x0425: 'et', 0x0438: 'fo', 0x0464: 'fil', 0x040B: 'fi', 0x080C: 'fr-BE', 0x0C0C: 'fr-CA', 0x040C: 'fr', 0x140C: 'fr-LU', 0x180C: 'fr-MC', 0x100C: 'fr-CH', 0x0462: 'fy', 0x0456: 'gl', 0x0437: 'ka', 0x0C07: 'de-AT', 0x0407: 'de', 0x1407: 'de-LI', 0x1007: 'de-LU', 0x0807: 'de-CH', 0x0408: 'el', 0x046F: 'kl', 0x0447: 'gu', 0x0468: 'ha', 0x040D: 'he', 0x0439: 'hi', 0x040E: 'hu', 0x040F: 'is', 0x0470: 'ig', 0x0421: 'id', 0x045D: 'iu', 0x085D: 'iu-Latn', 0x083C: 'ga', 0x0434: 'xh', 0x0435: 'zu', 0x0410: 'it', 0x0810: 'it-CH', 0x0411: 'ja', 0x044B: 'kn', 0x043F: 'kk', 0x0453: 'km', 0x0486: 'quc', 0x0487: 'rw', 0x0441: 'sw', 0x0457: 'kok', 0x0412: 'ko', 0x0440: 'ky', 0x0454: 'lo', 0x0426: 'lv', 0x0427: 'lt', 0x082E: 'dsb', 0x046E: 'lb', 0x042F: 'mk', 0x083E: 'ms-BN', 0x043E: 'ms', 0x044C: 'ml', 0x043A: 'mt', 0x0481: 'mi', 0x047A: 'arn', 0x044E: 'mr', 0x047C: 'moh', 0x0450: 'mn', 0x0850: 'mn-CN', 0x0461: 'ne', 0x0414: 'nb', 0x0814: 'nn', 0x0482: 'oc', 0x0448: 'or', 0x0463: 'ps', 0x0415: 'pl', 0x0416: 'pt', 0x0816: 'pt-PT', 0x0446: 'pa', 0x046B: 'qu-BO', 0x086B: 'qu-EC', 0x0C6B: 'qu', 0x0418: 'ro', 0x0417: 'rm', 0x0419: 'ru', 0x243B: 'smn', 0x103B: 'smj-NO', 0x143B: 'smj', 0x0C3B: 'se-FI', 0x043B: 'se', 0x083B: 'se-SE', 0x203B: 'sms', 0x183B: 'sma-NO', 0x1C3B: 'sms', 0x044F: 'sa', 0x1C1A: 'sr-Cyrl-BA', 0x0C1A: 'sr', 0x181A: 'sr-Latn-BA', 0x081A: 'sr-Latn', 0x046C: 'nso', 0x0432: 'tn', 0x045B: 'si', 0x041B: 'sk', 0x0424: 'sl', 0x2C0A: 'es-AR', 0x400A: 'es-BO', 0x340A: 'es-CL', 0x240A: 'es-CO', 0x140A: 'es-CR', 0x1C0A: 'es-DO', 0x300A: 'es-EC', 0x440A: 'es-SV', 0x100A: 'es-GT', 0x480A: 'es-HN', 0x080A: 'es-MX', 0x4C0A: 'es-NI', 0x180A: 'es-PA', 0x3C0A: 'es-PY', 0x280A: 'es-PE', 0x500A: 'es-PR', // Microsoft has defined two different language codes for // “Spanish with modern sorting” and “Spanish with traditional // sorting”. This makes sense for collation APIs, and it would be // possible to express this in BCP 47 language tags via Unicode // extensions (eg., es-u-co-trad is Spanish with traditional // sorting). However, for storing names in fonts, the distinction // does not make sense, so we give “es” in both cases. 0x0C0A: 'es', 0x040A: 'es', 0x540A: 'es-US', 0x380A: 'es-UY', 0x200A: 'es-VE', 0x081D: 'sv-FI', 0x041D: 'sv', 0x045A: 'syr', 0x0428: 'tg', 0x085F: 'tzm', 0x0449: 'ta', 0x0444: 'tt', 0x044A: 'te', 0x041E: 'th', 0x0451: 'bo', 0x041F: 'tr', 0x0442: 'tk', 0x0480: 'ug', 0x0422: 'uk', 0x042E: 'hsb', 0x0420: 'ur', 0x0843: 'uz-Cyrl', 0x0443: 'uz', 0x042A: 'vi', 0x0452: 'cy', 0x0488: 'wo', 0x0485: 'sah', 0x0478: 'ii', 0x046A: 'yo' }; // Returns a IETF BCP 47 language code, for example 'zh-Hant' // for 'Chinese in the traditional script'. function getLanguageCode(platformID, languageID, ltag) { switch (platformID) { case 0: // Unicode if (languageID === 0xFFFF) { return 'und'; } else if (ltag) { return ltag[languageID]; } break; case 1: // Macintosh return macLanguages[languageID]; case 3: // Windows return windowsLanguages[languageID]; } return undefined; } var utf16 = 'utf-16'; // MacOS script ID → encoding. This table stores the default case, // which can be overridden by macLanguageEncodings. var macScriptEncodings = { 0: 'macintosh', // smRoman 1: 'x-mac-japanese', // smJapanese 2: 'x-mac-chinesetrad', // smTradChinese 3: 'x-mac-korean', // smKorean 6: 'x-mac-greek', // smGreek 7: 'x-mac-cyrillic', // smCyrillic 9: 'x-mac-devanagai', // smDevanagari 10: 'x-mac-gurmukhi', // smGurmukhi 11: 'x-mac-gujarati', // smGujarati 12: 'x-mac-oriya', // smOriya 13: 'x-mac-bengali', // smBengali 14: 'x-mac-tamil', // smTamil 15: 'x-mac-telugu', // smTelugu 16: 'x-mac-kannada', // smKannada 17: 'x-mac-malayalam', // smMalayalam 18: 'x-mac-sinhalese', // smSinhalese 19: 'x-mac-burmese', // smBurmese 20: 'x-mac-khmer', // smKhmer 21: 'x-mac-thai', // smThai 22: 'x-mac-lao', // smLao 23: 'x-mac-georgian', // smGeorgian 24: 'x-mac-armenian', // smArmenian 25: 'x-mac-chinesesimp', // smSimpChinese 26: 'x-mac-tibetan', // smTibetan 27: 'x-mac-mongolian', // smMongolian 28: 'x-mac-ethiopic', // smEthiopic 29: 'x-mac-ce', // smCentralEuroRoman 30: 'x-mac-vietnamese', // smVietnamese 31: 'x-mac-extarabic' // smExtArabic }; // MacOS language ID → encoding. This table stores the exceptional // cases, which override macScriptEncodings. For writing MacOS naming // tables, we need to emit a MacOS script ID. Therefore, we cannot // merge macScriptEncodings into macLanguageEncodings. // // http://unicode.org/Public/MAPPINGS/VENDORS/APPLE/Readme.txt var macLanguageEncodings = { 15: 'x-mac-icelandic', // langIcelandic 17: 'x-mac-turkish', // langTurkish 18: 'x-mac-croatian', // langCroatian 24: 'x-mac-ce', // langLithuanian 25: 'x-mac-ce', // langPolish 26: 'x-mac-ce', // langHungarian 27: 'x-mac-ce', // langEstonian 28: 'x-mac-ce', // langLatvian 30: 'x-mac-icelandic', // langFaroese 37: 'x-mac-romanian', // langRomanian 38: 'x-mac-ce', // langCzech 39: 'x-mac-ce', // langSlovak 40: 'x-mac-ce', // langSlovenian 143: 'x-mac-inuit', // langInuktitut 146: 'x-mac-gaelic' // langIrishGaelicScript }; function getEncoding(platformID, encodingID, languageID) { switch (platformID) { case 0: // Unicode return utf16; case 1: // Apple Macintosh return macLanguageEncodings[languageID] || macScriptEncodings[encodingID]; case 3: // Microsoft Windows if (encodingID === 1 || encodingID === 10) { return utf16; } break; } return undefined; } // Parse the naming `name` table. // FIXME: Format 1 additional fields are not supported yet. // ltag is the content of the `ltag' table, such as ['en', 'zh-Hans', 'de-CH-1904']. function parseNameTable(data, start, ltag) { var name = {}; var p = new parse.Parser(data, start); var format = p.parseUShort(); var count = p.parseUShort(); var stringOffset = p.offset + p.parseUShort(); for (var i = 0; i < count; i++) { var platformID = p.parseUShort(); var encodingID = p.parseUShort(); var languageID = p.parseUShort(); var nameID = p.parseUShort(); var property = nameTableNames[nameID] || nameID; var byteLength = p.parseUShort(); var offset = p.parseUShort(); var language = getLanguageCode(platformID, languageID, ltag); var encoding = getEncoding(platformID, encodingID, languageID); if (encoding !== undefined && language !== undefined) { var text; if (encoding === utf16) { text = decode.UTF16(data, stringOffset + offset, byteLength); } else { text = decode.MACSTRING(data, stringOffset + offset, byteLength, encoding); } if (text) { var translations = name[property]; if (translations === undefined) { translations = name[property] = {}; } translations[language] = text; } } } var langTagCount = 0; if (format === 1) { // FIXME: Also handle Microsoft's 'name' table 1. langTagCount = p.parseUShort(); } return name; } // {23: 'foo'} → {'foo': 23} // ['bar', 'baz'] → {'bar': 0, 'baz': 1} function reverseDict(dict) { var r###lt = {}; for (var key in dict) { r###lt[dict[key]] = parseInt(key); } return r###lt; } function makeNameRecord(platformID, encodingID, languageID, nameID, length, offset) { return new table.Table('NameRecord', [ {name: 'platformID', type: 'USHORT', value: platformID}, {name: 'encodingID', type: 'USHORT', value: encodingID}, {name: 'languageID', type: 'USHORT', value: languageID}, {name: 'nameID', type: 'USHORT', value: nameID}, {name: 'length', type: 'USHORT', value: length}, {name: 'offset', type: 'USHORT', value: offset} ]); } // Finds the position of needle in haystack, or -1 if not there. // Like String.indexOf(), but for arrays. function findSubArray(needle, haystack) { var needleLength = needle.length; var limit = haystack.length - needleLength + 1; loop: for (var pos = 0; pos < limit; pos++) { for (; pos < limit; pos++) { for (var k = 0; k < needleLength; k++) { if (haystack[pos + k] !== needle[k]) { continue loop; } } return pos; } } return -1; } function addStringToPool(s, pool) { var offset = findSubArray(s, pool); if (offset < 0) { offset = pool.length; for (var i = 0, len = s.length; i < len; ++i) { pool.push(s[i]); } } return offset; } function makeNameTable(names, ltag) { var nameID; var nameIDs = []; var namesWithNumericKeys = {}; var nameTableIds = reverseDict(nameTableNames); for (var key in names) { var id = nameTableIds[key]; if (id === undefined) { id = key; } nameID = parseInt(id); namesWithNumericKeys[nameID] = names[key]; nameIDs.push(nameID); } var macLanguageIds = reverseDict(macLanguages); var windowsLanguageIds = reverseDict(windowsLanguages); var nameRecords = []; var stringPool = []; for (var i = 0; i < nameIDs.length; i++) { nameID = nameIDs[i]; var translations = namesWithNumericKeys[nameID]; for (var lang in translations) { var text = translations[lang]; // For MacOS, we try to emit the name in the form that was introduced // in the initial version of the TrueType spec (in the late 1980s). // However, this can fail for various reasons: the requested BCP 47 // language code might not have an old-style Mac equivalent; // we might not have a codec for the needed character encoding; // or the name might contain characters that cannot be expressed // in the old-style Macintosh encoding. In case of failure, we emit // the name in a more modern fashion (Unicode encoding with BCP 47 // language tags) that is recognized by MacOS 10.5, released in 2009. // If fonts were only read by operating systems, we could simply // emit all names in the modern form; this would be much easier. // However, there are many applications and libraries that read // 'name' tables directly, and these will usually only recognize // the ancient form (silently skipping the unrecognized names). var macPlatform = 1; // Macintosh var macLanguage = macLanguageIds[lang]; var macScript = macLanguageToScript[macLanguage]; var macEncoding = getEncoding(macPlatform, macScript, macLanguage); var macName = encode.MACSTRING(text, macEncoding); if (macName === undefined) { macPlatform = 0; // Unicode macLanguage = ltag.indexOf(lang); if (macLanguage < 0) { macLanguage = ltag.length; ltag.push(lang); } macScript = 4; // Unicode 2.0 and later macName = encode.UTF16(text); } var macNameOffset = addStringToPool(macName, stringPool); nameRecords.push(makeNameRecord(macPlatform, macScript, macLanguage, nameID, macName.length, macNameOffset)); var winLanguage = windowsLanguageIds[lang]; if (winLanguage !== undefined) { var winName = encode.UTF16(text); var winNameOffset = addStringToPool(winName, stringPool); nameRecords.push(makeNameRecord(3, 1, winLanguage, nameID, winName.length, winNameOffset)); } } } nameRecords.sort(function(a, b) { return ((a.platformID - b.platformID) || (a.encodingID - b.encodingID) || (a.languageID - b.languageID) || (a.nameID - b.nameID)); }); var t = new table.Table('name', [ {name: 'format', type: 'USHORT', value: 0}, {name: 'count', type: 'USHORT', value: nameRecords.length}, {name: 'stringOffset', type: 'USHORT', value: 6 + nameRecords.length * 12} ]); for (var r = 0; r < nameRecords.length; r++) { t.fields.push({name: 'record_' + r, type: 'TABLE', value: nameRecords[r]}); } t.fields.push({name: 'strings', type: 'LITERAL', value: stringPool}); return t; } exports.parse = parseNameTable; exports.make = makeNameTable; },{"../parse":9,"../table":11,"../types":28}],25:[function(require,module,exports){ // The `OS/2` table contains metrics required in OpenType fonts. // https://www.microsoft.com/typography/OTSPEC/os2.htm 'use strict'; var parse = require('../parse'); var table = require('../table'); var unicodeRanges = [ {begin: 0x0000, end: 0x007F}, // Basic Latin {begin: 0x0080, end: 0x00FF}, // Latin-1 Supplement {begin: 0x0100, end: 0x017F}, // Latin Extended-A {begin: 0x0180, end: 0x024F}, // Latin Extended-B {begin: 0x0250, end: 0x02AF}, // IPA Extensions {begin: 0x02B0, end: 0x02FF}, // Spacing Modifier Letters {begin: 0x0300, end: 0x036F}, // Combining Diacritical Marks {begin: 0x0370, end: 0x03FF}, // Greek and Coptic {begin: 0x2C80, end: 0x2CFF}, // Coptic {begin: 0x0400, end: 0x04FF}, // Cyrillic {begin: 0x0530, end: 0x058F}, // Armenian {begin: 0x0590, end: 0x05FF}, // Hebrew {begin: 0xA500, end: 0xA63F}, // Vai {begin: 0x0600, end: 0x06FF}, // Arabic {begin: 0x07C0, end: 0x07FF}, // NKo {begin: 0x0900, end: 0x097F}, // Devanagari {begin: 0x0980, end: 0x09FF}, // Bengali {begin: 0x0A00, end: 0x0A7F}, // Gurmukhi {begin: 0x0A80, end: 0x0AFF}, // Gujarati {begin: 0x0B00, end: 0x0B7F}, // Oriya {begin: 0x0B80, end: 0x0BFF}, // Tamil {begin: 0x0C00, end: 0x0C7F}, // Telugu {begin: 0x0C80, end: 0x0CFF}, // Kannada {begin: 0x0D00, end: 0x0D7F}, // Malayalam {begin: 0x0E00, end: 0x0E7F}, // Thai {begin: 0x0E80, end: 0x0EFF}, // Lao {begin: 0x10A0, end: 0x10FF}, // Georgian {begin: 0x1B00, end: 0x1B7F}, // Balinese {begin: 0x1100, end: 0x11FF}, // Hangul Jamo {begin: 0x1E00, end: 0x1EFF}, // Latin Extended Additional {begin: 0x1F00, end: 0x1FFF}, // Greek Extended {begin: 0x2000, end: 0x206F}, // General Punctuation {begin: 0x2070, end: 0x209F}, // Superscripts And Subscripts {begin: 0x20A0, end: 0x20CF}, // Currency Symbol {begin: 0x20D0, end: 0x20FF}, // Combining Diacritical Marks For Symbols {begin: 0x2100, end: 0x214F}, // Letterlike Symbols {begin: 0x2150, end: 0x218F}, // Number Forms {begin: 0x2190, end: 0x21FF}, // Arrows {begin: 0x2200, end: 0x22FF}, // Mathematical Operators {begin: 0x2300, end: 0x23FF}, // Miscellaneous Technical {begin: 0x2400, end: 0x243F}, // Control Pictures {begin: 0x2440, end: 0x245F}, // Optical Character Recognition {begin: 0x2460, end: 0x24FF}, // Enclosed Alphanumerics {begin: 0x2500, end: 0x257F}, // Box Drawing {begin: 0x2580, end: 0x259F}, // Block Elements {begin: 0x25A0, end: 0x25FF}, // Geometric Shapes {begin: 0x2600, end: 0x26FF}, // Miscellaneous Symbols {begin: 0x2700, end: 0x27BF}, // Dingbats {begin: 0x3000, end: 0x303F}, // CJK Symbols And Punctuation {begin: 0x3040, end: 0x309F}, // Hiragana {begin: 0x30A0, end: 0x30FF}, // Katakana {begin: 0x3100, end: 0x312F}, // Bopomofo {begin: 0x3130, end: 0x318F}, // Hangul Compatibility Jamo {begin: 0xA840, end: 0xA87F}, // Phags-pa {begin: 0x3200, end: 0x32FF}, // Enclosed CJK Letters And Months {begin: 0x3300, end: 0x33FF}, // CJK Compatibility {begin: 0xAC00, end: 0xD7AF}, // Hangul Syllables {begin: 0xD800, end: 0xDFFF}, // Non-Plane 0 * {begin: 0x10900, end: 0x1091F}, // Phoenicia {begin: 0x4E00, end: 0x9FFF}, // CJK Unified Ideographs {begin: 0xE000, end: 0xF8FF}, // Private Use Area (plane 0) {begin: 0x31C0, end: 0x31EF}, // CJK Strokes {begin: 0xFB00, end: 0xFB4F}, // Alphabetic Presentation Forms {begin: 0xFB50, end: 0xFDFF}, // Arabic Presentation Forms-A {begin: 0xFE20, end: 0xFE2F}, // Combining Half Marks {begin: 0xFE10, end: 0xFE1F}, // Vertical Forms {begin: 0xFE50, end: 0xFE6F}, // Small Form Variants {begin: 0xFE70, end: 0xFEFF}, // Arabic Presentation Forms-B {begin: 0xFF00, end: 0xFFEF}, // Halfwidth And Fullwidth Forms {begin: 0xFFF0, end: 0xFFFF}, // Specials {begin: 0x0F00, end: 0x0FFF}, // Tibetan {begin: 0x0700, end: 0x074F}, // Syriac {begin: 0x0780, end: 0x07BF}, // Thaana {begin: 0x0D80, end: 0x0DFF}, // Sinhala {begin: 0x1000, end: 0x109F}, // Myanmar {begin: 0x1200, end: 0x137F}, // Ethiopic {begin: 0x13A0, end: 0x13FF}, // Cherokee {begin: 0x1400, end: 0x167F}, // Unified Canadian Aboriginal Syllabics {begin: 0x1680, end: 0x169F}, // Ogham {begin: 0x16A0, end: 0x16FF}, // Runic {begin: 0x1780, end: 0x17FF}, // Khmer {begin: 0x1800, end: 0x18AF}, // Mongolian {begin: 0x2800, end: 0x28FF}, // Braille Patterns {begin: 0xA000, end: 0xA48F}, // Yi Syllables {begin: 0x1700, end: 0x171F}, // Tagalog {begin: 0x10300, end: 0x1032F}, // Old Italic {begin: 0x10330, end: 0x1034F}, // Gothic {begin: 0x10400, end: 0x1044F}, // Deseret {begin: 0x1D000, end: 0x1D0FF}, // Byzantine Musical Symbols {begin: 0x1D400, end: 0x1D7FF}, // Mathematical Alphanumeric Symbols {begin: 0xFF000, end: 0xFFFFD}, // Private Use (plane 15) {begin: 0xFE00, end: 0xFE0F}, // Variation Selectors {begin: 0xE0000, end: 0xE007F}, // Tags {begin: 0x1900, end: 0x194F}, // Limbu {begin: 0x1950, end: 0x197F}, // Tai Le {begin: 0x1980, end: 0x19DF}, // New Tai Lue {begin: 0x1A00, end: 0x1A1F}, // Buginese {begin: 0x2C00, end: 0x2C5F}, // Glagolitic {begin: 0x2D30, end: 0x2D7F}, // Tifinagh {begin: 0x4DC0, end: 0x4DFF}, // Yijing Hexagram Symbols {begin: 0xA800, end: 0xA82F}, // Syloti Nagri {begin: 0x10000, end: 0x1007F}, // Linear B Syllabary {begin: 0x10140, end: 0x1018F}, // Ancient Greek Numbers {begin: 0x10380, end: 0x1039F}, // Ugaritic {begin: 0x103A0, end: 0x103DF}, // Old Persian {begin: 0x10450, end: 0x1047F}, // Shavian {begin: 0x10480, end: 0x104AF}, // Osmanya {begin: 0x10800, end: 0x1083F}, // Cypriot Syllabary {begin: 0x10A00, end: 0x10A5F}, // Kharoshthi {begin: 0x1D300, end: 0x1D35F}, // Tai Xuan Jing Symbols {begin: 0x12000, end: 0x123FF}, // Cuneiform {begin: 0x1D360, end: 0x1D37F}, // Counting Rod Numerals {begin: 0x1B80, end: 0x1BBF}, // Sundanese {begin: 0x1C00, end: 0x1C4F}, // Lepcha {begin: 0x1C50, end: 0x1C7F}, // Ol Chiki {begin: 0xA880, end: 0xA8DF}, // Saurashtra {begin: 0xA900, end: 0xA92F}, // Kayah Li {begin: 0xA930, end: 0xA95F}, // Rejang {begin: 0xAA00, end: 0xAA5F}, // Cham {begin: 0x10190, end: 0x101CF}, // Ancient Symbols {begin: 0x101D0, end: 0x101FF}, // Phaistos Disc {begin: 0x102A0, end: 0x102DF}, // Carian {begin: 0x1F030, end: 0x1F09F} // Domino Tiles ]; function getUnicodeRange(unicode) { for (var i = 0; i < unicodeRanges.length; i += 1) { var range = unicodeRanges[i]; if (unicode >= range.begin && unicode < range.end) { return i; } } return -1; } // Parse the OS/2 and Windows metrics `OS/2` table function parseOS2Table(data, start) { var os2 = {}; var p = new parse.Parser(data, start); os2.version = p.parseUShort(); os2.xAvgCharWidth = p.parseShort(); os2.usWeightClass = p.parseUShort(); os2.usWidthClass = p.parseUShort(); os2.fsType = p.parseUShort(); os2.ySubscriptXSize = p.parseShort(); os2.ySubscriptYSize = p.parseShort(); os2.ySubscriptXOffset = p.parseShort(); os2.ySubscriptYOffset = p.parseShort(); os2.ySuperscriptXSize = p.parseShort(); os2.ySuperscriptYSize = p.parseShort(); os2.ySuperscriptXOffset = p.parseShort(); os2.ySuperscriptYOffset = p.parseShort(); os2.yStrikeoutSize = p.parseShort(); os2.yStrikeoutPosition = p.parseShort(); os2.sFamilyClass = p.parseShort(); os2.panose = []; for (var i = 0; i < 10; i++) { os2.panose[i] = p.parseByte(); } os2.ulUnicodeRange1 = p.parseULong(); os2.ulUnicodeRange2 = p.parseULong(); os2.ulUnicodeRange3 = p.parseULong(); os2.ulUnicodeRange4 = p.parseULong(); os2.achVendID = String.fromCharCode(p.parseByte(), p.parseByte(), p.parseByte(), p.parseByte()); os2.fsSelection = p.parseUShort(); os2.usFirstCharIndex = p.parseUShort(); os2.usLastCharIndex = p.parseUShort(); os2.sTypoAscender = p.parseShort(); os2.sTypoDescender = p.parseShort(); os2.sTypoLineGap = p.parseShort(); os2.usWinAscent = p.parseUShort(); os2.usWinDescent = p.parseUShort(); if (os2.version >= 1) { os2.ulCodePageRange1 = p.parseULong(); os2.ulCodePageRange2 = p.parseULong(); } if (os2.version >= 2) { os2.sxHeight = p.parseShort(); os2.sCapHeight = p.parseShort(); os2.usDefaultChar = p.parseUShort(); os2.usBreakChar = p.parseUShort(); os2.usMaxContent = p.parseUShort(); } return os2; } function makeOS2Table(options) { return new table.Table('OS/2', [ {name: 'version', type: 'USHORT', value: 0x0003}, {name: 'xAvgCharWidth', type: 'SHORT', value: 0}, {name: 'usWeightClass', type: 'USHORT', value: 0}, {name: 'usWidthClass', type: 'USHORT', value: 0}, {name: 'fsType', type: 'USHORT', value: 0}, {name: 'ySubscriptXSize', type: 'SHORT', value: 650}, {name: 'ySubscriptYSize', type: 'SHORT', value: 699}, {name: 'ySubscriptXOffset', type: 'SHORT', value: 0}, {name: 'ySubscriptYOffset', type: 'SHORT', value: 140}, {name: 'ySuperscriptXSize', type: 'SHORT', value: 650}, {name: 'ySuperscriptYSize', type: 'SHORT', value: 699}, {name: 'ySuperscriptXOffset', type: 'SHORT', value: 0}, {name: 'ySuperscriptYOffset', type: 'SHORT', value: 479}, {name: 'yStrikeoutSize', type: 'SHORT', value: 49}, {name: 'yStrikeoutPosition', type: 'SHORT', value: 258}, {name: 'sFamilyClass', type: 'SHORT', value: 0}, {name: 'bFamilyType', type: 'BYTE', value: 0}, {name: 'bSerifStyle', type: 'BYTE', value: 0}, {name: 'bWeight', type: 'BYTE', value: 0}, {name: 'bProportion', type: 'BYTE', value: 0}, {name: 'bContrast', type: 'BYTE', value: 0}, {name: 'bStrokeVariation', type: 'BYTE', value: 0}, {name: 'bArmStyle', type: 'BYTE', value: 0}, {name: 'bLetterform', type: 'BYTE', value: 0}, {name: 'bMidline', type: 'BYTE', value: 0}, {name: 'bXHeight', type: 'BYTE', value: 0}, {name: 'ulUnicodeRange1', type: 'ULONG', value: 0}, {name: 'ulUnicodeRange2', type: 'ULONG', value: 0}, {name: 'ulUnicodeRange3', type: 'ULONG', value: 0}, {name: 'ulUnicodeRange4', type: 'ULONG', value: 0}, {name: 'achVendID', type: 'CHARARRAY', value: 'XXXX'}, {name: 'fsSelection', type: 'USHORT', value: 0}, {name: 'usFirstCharIndex', type: 'USHORT', value: 0}, {name: 'usLastCharIndex', type: 'USHORT', value: 0}, {name: 'sTypoAscender', type: 'SHORT', value: 0}, {name: 'sTypoDescender', type: 'SHORT', value: 0}, {name: 'sTypoLineGap', type: 'SHORT', value: 0}, {name: 'usWinAscent', type: 'USHORT', value: 0}, {name: 'usWinDescent', type: 'USHORT', value: 0}, {name: 'ulCodePageRange1', type: 'ULONG', value: 0}, {name: 'ulCodePageRange2', type: 'ULONG', value: 0}, {name: 'sxHeight', type: 'SHORT', value: 0}, {name: 'sCapHeight', type: 'SHORT', value: 0}, {name: 'usDefaultChar', type: 'USHORT', value: 0}, {name: 'usBreakChar', type: 'USHORT', value: 0}, {name: 'usMaxContext', type: 'USHORT', value: 0} ], options); } exports.unicodeRanges = unicodeRanges; exports.getUnicodeRange = getUnicodeRange; exports.parse = parseOS2Table; exports.make = makeOS2Table; },{"../parse":9,"../table":11}],26:[function(require,module,exports){ // The `post` table stores additional PostScript information, such as glyph names. // https://www.microsoft.com/typography/OTSPEC/post.htm 'use strict'; var encoding = require('../encoding'); var parse = require('../parse'); var table = require('../table'); // Parse the PostScript `post` table function parsePostTable(data, start) { var post = {}; var p = new parse.Parser(data, start); var i; post.version = p.parseVersion(); post.italicAngle = p.parseFixed(); post.underlinePosition = p.parseShort(); post.underlineThickness = p.parseShort(); post.isFixedPitch = p.parseULong(); post.minMemType42 = p.parseULong(); post.maxMemType42 = p.parseULong(); post.minMemType1 = p.parseULong(); post.maxMemType1 = p.parseULong(); switch (post.version) { case 1: post.names = encoding.standardNames.slice(); break; case 2: post.numberOfGlyphs = p.parseUShort(); post.glyphNameIndex = new Array(post.numberOfGlyphs); for (i = 0; i < post.numberOfGlyphs; i++) { post.glyphNameIndex[i] = p.parseUShort(); } post.names = []; for (i = 0; i < post.numberOfGlyphs; i++) { if (post.glyphNameIndex[i] >= encoding.standardNames.length) { var nameLength = p.parseChar(); post.names.push(p.parseString(nameLength)); } } break; case 2.5: post.numberOfGlyphs = p.parseUShort(); post.offset = new Array(post.numberOfGlyphs); for (i = 0; i < post.numberOfGlyphs; i++) { post.offset[i] = p.parseChar(); } break; } return post; } function makePostTable() { return new table.Table('post', [ {name: 'version', type: 'FIXED', value: 0x00030000}, {name: 'italicAngle', type: 'FIXED', value: 0}, {name: 'underlinePosition', type: 'FWORD', value: 0}, {name: 'underlineThickness', type: 'FWORD', value: 0}, {name: 'isFixedPitch', type: 'ULONG', value: 0}, {name: 'minMemType42', type: 'ULONG', value: 0}, {name: 'maxMemType42', type: 'ULONG', value: 0}, {name: 'minMemType1', type: 'ULONG', value: 0}, {name: 'maxMemType1', type: 'ULONG', value: 0} ]); } exports.parse = parsePostTable; exports.make = makePostTable; },{"../encoding":4,"../parse":9,"../table":11}],27:[function(require,module,exports){ // The `sfnt` wrapper provides organization for the tables in the font. // It is the top-level data structure in a font. // https://www.microsoft.com/typography/OTSPEC/otff.htm // Recommendations for creating OpenType Fonts: // http://www.microsoft.com/typography/otspec140/recom.htm 'use strict'; var check = require('../check'); var table = require('../table'); var cmap = require('./cmap'); var cff = require('./cff'); var head = require('./head'); var hhea = require('./hhea'); var hmtx = require('./hmtx'); var ltag = require('./ltag'); var maxp = require('./maxp'); var _name = require('./name'); var os2 = require('./os2'); var post = require('./post'); function log2(v) { return Math.log(v) / Math.log(2) | 0; } function computeCheckSum(bytes) { while (bytes.length % 4 !== 0) { bytes.push(0); } var sum = 0; for (var i = 0; i < bytes.length; i += 4) { sum += (bytes[i] << 24) + (bytes[i + 1] << 16) + (bytes[i + 2] << 8) + (bytes[i + 3]); } sum %= Math.pow(2, 32); return sum; } function makeTableRecord(tag, checkSum, offset, length) { return new table.Table('Table Record', [ {name: 'tag', type: 'TAG', value: tag !== undefined ? tag : ''}, {name: 'checkSum', type: 'ULONG', value: checkSum !== undefined ? checkSum : 0}, {name: 'offset', type: 'ULONG', value: offset !== undefined ? offset : 0}, {name: 'length', type: 'ULONG', value: length !== undefined ? length : 0} ]); } function makeSfntTable(tables) { var sfnt = new table.Table('sfnt', [ {name: 'version', type: 'TAG', value: 'OTTO'}, {name: 'numTables', type: 'USHORT', value: 0}, {name: 'searchRange', type: 'USHORT', value: 0}, {name: 'entrySelector', type: 'USHORT', value: 0}, {name: 'rangeShift', type: 'USHORT', value: 0} ]); sfnt.tables = tables; sfnt.numTables = tables.length; var highestPowerOf2 = Math.pow(2, log2(sfnt.numTables)); sfnt.searchRange = 16 * highestPowerOf2; sfnt.entrySelector = log2(highestPowerOf2); sfnt.rangeShift = sfnt.numTables * 16 - sfnt.searchRange; var recordFields = []; var tableFields = []; var offset = sfnt.sizeOf() + (makeTableRecord().sizeOf() * sfnt.numTables); while (offset % 4 !== 0) { offset += 1; tableFields.push({name: 'padding', type: 'BYTE', value: 0}); } for (var i = 0; i < tables.length; i += 1) { var t = tables[i]; check.argument(t.tableName.length === 4, 'Table name' + t.tableName + ' is invalid.'); var tableLength = t.sizeOf(); var tableRecord = makeTableRecord(t.tableName, computeCheckSum(t.encode()), offset, tableLength); recordFields.push({name: tableRecord.tag + ' Table Record', type: 'TABLE', value: tableRecord}); tableFields.push({name: t.tableName + ' table', type: 'TABLE', value: t}); offset += tableLength; check.argument(!isNaN(offset), 'Something went wrong calculating the offset.'); while (offset % 4 !== 0) { offset += 1; tableFields.push({name: 'padding', type: 'BYTE', value: 0}); } } // Table records need to be sorted alphabetically. recordFields.sort(function(r1, r2) { if (r1.value.tag > r2.value.tag) { return 1; } else { return -1; } }); sfnt.fields = sfnt.fields.concat(recordFields); sfnt.fields = sfnt.fields.concat(tableFields); return sfnt; } // Get the metrics for a character. If the string has more than one character // this function returns metrics for the first available character. // You can provide optional fallback metrics if no characters are available. function metricsForChar(font, chars, notFoundMetrics) { for (var i = 0; i < chars.length; i += 1) { var glyphIndex = font.charToGlyphIndex(chars[i]); if (glyphIndex > 0) { var glyph = font.glyphs.get(glyphIndex); return glyph.getMetrics(); } } return notFoundMetrics; } function average(vs) { var sum = 0; for (var i = 0; i < vs.length; i += 1) { sum += vs[i]; } return sum / vs.length; } // Convert the font object to a SFNT data structure. // This structure contains all the necessary tables and metadata to create a binary OTF file. function fontToSfntTable(font) { var xMins = []; var yMins = []; var xMaxs = []; var yMaxs = []; var advanceWidths = []; var leftSideBearings = []; var rightSideBearings = []; var firstCharIndex; var lastCharIndex = 0; var ulUnicodeRange1 = 0; var ulUnicodeRange2 = 0; var ulUnicodeRange3 = 0; var ulUnicodeRange4 = 0; for (var i = 0; i < font.glyphs.length; i += 1) { var glyph = font.glyphs.get(i); var unicode = glyph.unicode | 0; if (firstCharIndex > unicode || firstCharIndex === null) { firstCharIndex = unicode; } if (lastCharIndex < unicode) { lastCharIndex = unicode; } var position = os2.getUnicodeRange(unicode); if (position < 32) { ulUnicodeRange1 |= 1 << position; } else if (position < 64) { ulUnicodeRange2 |= 1 << position - 32; } else if (position < 96) { ulUnicodeRange3 |= 1 << position - 64; } else if (position < 123) { ulUnicodeRange4 |= 1 << position - 96; } else { throw new Error('Unicode ranges bits > 123 are reserved for internal usage'); } // Skip non-important characters. if (glyph.name === '.notdef') continue; var metrics = glyph.getMetrics(); xMins.push(metrics.xMin); yMins.push(metrics.yMin); xMaxs.push(metrics.xMax); yMaxs.push(metrics.yMax); leftSideBearings.push(metrics.leftSideBearing); rightSideBearings.push(metrics.rightSideBearing); advanceWidths.push(glyph.advanceWidth); } var globals = { xMin: Math.min.apply(null, xMins), yMin: Math.min.apply(null, yMins), xMax: Math.max.apply(null, xMaxs), yMax: Math.max.apply(null, yMaxs), advanceWidthMax: Math.max.apply(null, advanceWidths), advanceWidthAvg: average(advanceWidths), minLeftSideBearing: Math.min.apply(null, leftSideBearings), maxLeftSideBearing: Math.max.apply(null, leftSideBearings), minRightSideBearing: Math.min.apply(null, rightSideBearings) }; globals.ascender = font.ascender !== undefined ? font.ascender : globals.yMax; globals.descender = font.descender !== undefined ? font.descender : globals.yMin; var headTable = head.make({ flags: 3, // 00000011 (baseline for font at y=0; left sidebearing point at x=0) unitsPerEm: font.unitsPerEm, xMin: globals.xMin, yMin: globals.yMin, xMax: globals.xMax, yMax: globals.yMax, lowestRecPPEM: 3 }); var hheaTable = hhea.make({ ascender: globals.ascender, descender: globals.descender, advanceWidthMax: globals.advanceWidthMax, minLeftSideBearing: globals.minLeftSideBearing, minRightSideBearing: globals.minRightSideBearing, xMaxExtent: globals.maxLeftSideBearing + (globals.xMax - globals.xMin), numberOfHMetrics: font.glyphs.length }); var maxpTable = maxp.make(font.glyphs.length); var os2Table = os2.make({ xAvgCharWidth: Math.round(globals.advanceWidthAvg), usWeightClass: 500, // Medium FIXME Make this configurable usWidthClass: 5, // Medium (normal) FIXME Make this configurable usFirstCharIndex: firstCharIndex, usLastCharIndex: lastCharIndex, ulUnicodeRange1: ulUnicodeRange1, ulUnicodeRange2: ulUnicodeRange2, ulUnicodeRange3: ulUnicodeRange3, ulUnicodeRange4: ulUnicodeRange4, fsSelection: 64, // REGULAR // See http://typophile.com/node/13081 for more info on vertical metrics. // We get metrics for typical characters (such as "x" for xHeight). // We provide some fallback characters if characters are unavailable: their // ordering was chosen experimentally. sTypoAscender: globals.ascender, sTypoDescender: globals.descender, sTypoLineGap: 0, usWinAscent: globals.yMax, usWinDescent: Math.abs(globals.yMin), ulCodePageRange1: 1, // FIXME: hard-code Latin 1 support for now sxHeight: metricsForChar(font, 'xyvw', {yMax: Math.round(globals.ascender / 2)}).yMax, sCapHeight: metricsForChar(font, 'HIKLEFJMNTZBDPRAGOQSUVWXY', globals).yMax, usDefaultChar: font.hasChar(' ') ? 32 : 0, // Use space as the default character, if available. usBreakChar: font.hasChar(' ') ? 32 : 0 // Use space as the break character, if available. }); var hmtxTable = hmtx.make(font.glyphs); var cmapTable = cmap.make(font.glyphs); var englishFamilyName = font.getEnglishName('fontFamily'); var englishStyleName = font.getEnglishName('fontSubfamily'); var englishFullName = englishFamilyName + ' ' + englishStyleName; var postScriptName = font.getEnglishName('postScriptName'); if (!postScriptName) { postScriptName = englishFamilyName.replace(/\s/g, '') + '-' + englishStyleName; } var names = {}; for (var n in font.names) { names[n] = font.names[n]; } if (!names.uniqueID) { names.uniqueID = {en: font.getEnglishName('manufacturer') + ':' + englishFullName}; } if (!names.postScriptName) { names.postScriptName = {en: postScriptName}; } if (!names.preferredFamily) { names.preferredFamily = font.names.fontFamily; } if (!names.preferredSubfamily) { names.preferredSubfamily = font.names.fontSubfamily; } var languageTags = []; var nameTable = _name.make(names, languageTags); var ltagTable = (languageTags.length > 0 ? ltag.make(languageTags) : undefined); var postTable = post.make(); var cffTable = cff.make(font.glyphs, { version: font.getEnglishName('version'), fullName: englishFullName, familyName: englishFamilyName, weightName: englishStyleName, postScriptName: postScriptName, unitsPerEm: font.unitsPerEm, fontBBox: [0, globals.yMin, globals.ascender, globals.advanceWidthMax] }); // The order does not matter because makeSfntTable() will sort them. var tables = [headTable, hheaTable, maxpTable, os2Table, nameTable, cmapTable, postTable, cffTable, hmtxTable]; if (ltagTable) { tables.push(ltagTable); } var sfntTable = makeSfntTable(tables); // Compute the font's checkSum and store it in head.checkSumAdjustment. var bytes = sfntTable.encode(); var checkSum = computeCheckSum(bytes); var tableFields = sfntTable.fields; var checkSumAdjusted = false; for (i = 0; i < tableFields.length; i += 1) { if (tableFields[i].name === 'head table') { tableFields[i].value.checkSumAdjustment = 0xB1B0AFBA - checkSum; checkSumAdjusted = true; break; } } if (!checkSumAdjusted) { throw new Error('Could not find head table with checkSum to adjust.'); } return sfntTable; } exports.computeCheckSum = computeCheckSum; exports.make = makeSfntTable; exports.fontToTable = fontToSfntTable; },{"../check":2,"../table":11,"./cff":12,"./cmap":13,"./head":17,"./hhea":18,"./hmtx":19,"./ltag":22,"./maxp":23,"./name":24,"./os2":25,"./post":26}],28:[function(require,module,exports){ // Data types used in the OpenType font file. // All OpenType fonts use Motorola-style byte ordering (Big Endian) /* global WeakMap */ 'use strict'; var check = require('./check'); var LIMIT16 = 32768; // The limit at which a 16-bit number switches signs == 2^15 var LIMIT32 = 2147483648; // The limit at which a 32-bit number switches signs == 2 ^ 31 var decode = {}; var encode = {}; var sizeOf = {}; // Return a function that always returns the same value. function constant(v) { return function() { return v; }; } // OpenType data types ////////////////////////////////////////////////////// // Convert an 8-bit unsigned integer to a list of 1 byte. encode.BYTE = function(v) { check.argument(v >= 0 && v <= 255, 'Byte value should be between 0 and 255.'); return [v]; }; sizeOf.BYTE = constant(1); // Convert a 8-bit signed integer to a list of 1 byte. encode.CHAR = function(v) { return [v.charCodeAt(0)]; }; sizeOf.CHAR = constant(1); // Convert an ASCII string to a list of bytes. encode.CHARARRAY = function(v) { var b = []; for (var i = 0; i < v.length; i += 1) { b.push(v.charCodeAt(i)); } return b; }; sizeOf.CHARARRAY = function(v) { return v.length; }; // Convert a 16-bit unsigned integer to a list of 2 bytes. encode.USHORT = function(v) { return [(v >> 8) & 0xFF, v & 0xFF]; }; sizeOf.USHORT = constant(2); // Convert a 16-bit signed integer to a list of 2 bytes. encode.SHORT = function(v) { // Two's complement if (v >= LIMIT16) { v = -(2 * LIMIT16 - v); } return [(v >> 8) & 0xFF, v & 0xFF]; }; sizeOf.SHORT = constant(2); // Convert a 24-bit unsigned integer to a list of 3 bytes. encode.UINT24 = function(v) { return [(v >> 16) & 0xFF, (v >> 8) & 0xFF, v & 0xFF]; }; sizeOf.UINT24 = constant(3); // Convert a 32-bit unsigned integer to a list of 4 bytes. encode.ULONG = function(v) { return [(v >> 24) & 0xFF, (v >> 16) & 0xFF, (v >> 8) & 0xFF, v & 0xFF]; }; sizeOf.ULONG = constant(4); // Convert a 32-bit unsigned integer to a list of 4 bytes. encode.LONG = function(v) { // Two's complement if (v >= LIMIT32) { v = -(2 * LIMIT32 - v); } return [(v >> 24) & 0xFF, (v >> 16) & 0xFF, (v >> 8) & 0xFF, v & 0xFF]; }; sizeOf.LONG = constant(4); encode.FIXED = encode.ULONG; sizeOf.FIXED = sizeOf.ULONG; encode.FWORD = encode.SHORT; sizeOf.FWORD = sizeOf.SHORT; encode.UFWORD = encode.USHORT; sizeOf.UFWORD = sizeOf.USHORT; // FIXME Implement LONGDATETIME encode.LONGDATETIME = function() { return [0, 0, 0, 0, 0, 0, 0, 0]; }; sizeOf.LONGDATETIME = constant(8); // Convert a 4-char tag to a list of 4 bytes. encode.TAG = function(v) { check.argument(v.length === 4, 'Tag should be exactly 4 ASCII characters.'); return [v.charCodeAt(0), v.charCodeAt(1), v.charCodeAt(2), v.charCodeAt(3)]; }; sizeOf.TAG = constant(4); // CFF data types /////////////////////////////////////////////////////////// encode.Card8 = encode.BYTE; sizeOf.Card8 = sizeOf.BYTE; encode.Card16 = encode.USHORT; sizeOf.Card16 = sizeOf.USHORT; encode.OffSize = encode.BYTE; sizeOf.OffSize = sizeOf.BYTE; encode.SID = encode.USHORT; sizeOf.SID = sizeOf.USHORT; // Convert a numeric operand or charstring number to a variable-size list of bytes. encode.NUMBER = function(v) { if (v >= -107 && v <= 107) { return [v + 139]; } else if (v >= 108 && v <= 1131) { v = v - 108; return [(v >> 8) + 247, v & 0xFF]; } else if (v >= -1131 && v <= -108) { v = -v - 108; return [(v >> 8) + 251, v & 0xFF]; } else if (v >= -32768 && v <= 32767) { return encode.NUMBER16(v); } else { return encode.NUMBER32(v); } }; sizeOf.NUMBER = function(v) { return encode.NUMBER(v).length; }; // Convert a signed number between -32768 and +32767 to a three-byte value. // This ensures we always use three bytes, but is not the most compact format. encode.NUMBER16 = function(v) { return [28, (v >> 8) & 0xFF, v & 0xFF]; }; sizeOf.NUMBER16 = constant(3); // Convert a signed number between -(2^31) and +(2^31-1) to a five-byte value. // This is useful if you want to be sure you always use four bytes, // at the expense of wasting a few bytes for smaller numbers. encode.NUMBER32 = function(v) { return [29, (v >> 24) & 0xFF, (v >> 16) & 0xFF, (v >> 8) & 0xFF, v & 0xFF]; }; sizeOf.NUMBER32 = constant(5); encode.REAL = function(v) { var value = v.toString(); // Some numbers use an epsilon to encode the value. (e.g. JavaScript will store 0.0000001 as 1e-7) // This code converts it back to a number without the epsilon. var m = /\.(\d*?)(?:9{5,20}|0{5,20})\d{0,2}(?:e(.+)|$)/.exec(value); if (m) { var epsilon = parseFloat('1e' + ((m[2] ? +m[2] : 0) + m[1].length)); value = (Math.round(v * epsilon) / epsilon).toString(); } var nibbles = ''; var i; var ii; for (i = 0, ii = value.length; i < ii; i += 1) { var c = value[i]; if (c === 'e') { nibbles += value[++i] === '-' ? 'c' : 'b'; } else if (c === '.') { nibbles += 'a'; } else if (c === '-') { nibbles += 'e'; } else { nibbles += c; } } nibbles += (nibbles.length & 1) ? 'f' : 'ff'; var out = [30]; for (i = 0, ii = nibbles.length; i < ii; i += 2) { out.push(parseInt(nibbles.substr(i, 2), 16)); } return out; }; sizeOf.REAL = function(v) { return encode.REAL(v).length; }; encode.NAME = encode.CHARARRAY; sizeOf.NAME = sizeOf.CHARARRAY; encode.STRING = encode.CHARARRAY; sizeOf.STRING = sizeOf.CHARARRAY; decode.UTF16 = function(data, offset, numBytes) { var codePoints = []; var numChars = numBytes / 2; for (var j = 0; j < numChars; j++, offset += 2) { codePoints[j] = data.getUint16(offset); } return String.fromCharCode.apply(null, codePoints); }; // Convert a JavaScript string to UTF16-BE. encode.UTF16 = function(v) { var b = []; for (var i = 0; i < v.length; i += 1) { var codepoint = v.charCodeAt(i); b.push((codepoint >> 8) & 0xFF); b.push(codepoint & 0xFF); } return b; }; sizeOf.UTF16 = function(v) { return v.length * 2; }; // Data for converting old eight-bit Macintosh encodings to Unicode. // This representation is optimized for decoding; encoding is slower // and needs more memory. The assumption is that all opentype.js users // want to open fonts, but saving a font will be comperatively rare // so it can be more expensive. Keyed by IANA character set name. // // Python script for generating these strings: // // s = u''.join([chr(c).decode('mac_greek') for c in range(128, 256)]) // print(s.encode('utf-8')) var eightBitMacEncodings = { 'x-mac-croatian': // Python: 'mac_croatian' 'ÄÅÇÉÑÖÜáàâäãåçéèêëíìîïñóòôöõúùûü†°¢£§•¶ß®Š™´¨≠ŽØ∞±≤≥∆µ∂∑∏š∫ªºΩžø' + '¿¡¬√ƒ≈Ć«Č… ÀÃÕŒœĐ—“”‘’÷◊©⁄€‹›Æ»–·‚„‰ÂćÁčÈÍÎÏÌÓÔđÒÚÛÙıˆ˜¯πË˚¸Êæˇ', 'x-mac-cyrillic': // Python: 'mac_cyrillic' 'АБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ†°Ґ£§•¶І®©™Ђђ≠Ѓѓ∞±≤≥іµґЈЄєЇїЉљЊњ' + 'јЅ¬√ƒ≈∆«»… ЋћЌќѕ–—“”‘’÷„ЎўЏџ№Ёёяабвгдежзийклмнопрстуфхцчшщъыьэю', 'x-mac-gaelic': // http://unicode.org/Public/MAPPINGS/VENDORS/APPLE/GAELIC.TXT 'ÄÅÇÉÑÖÜáàâäãåçéèêëíìîïñóòôöõúùûü†°¢£§•¶ß®©™´¨≠ÆØḂ±≤≥ḃĊċḊḋḞḟĠġṀæø' + 'ṁṖṗɼƒſṠ«»… ÀÃÕŒœ–—“”‘’ṡẛÿŸṪ€‹›Ŷŷṫ·Ỳỳ⁊ÂÊÁËÈÍÎÏÌÓÔ♣ÒÚÛÙıÝýŴŵẄẅẀẁẂẃ', 'x-mac-greek': // Python: 'mac_greek' 'Ĺ²É³ÖÜ΅àâä΄¨çéèê룙î‰ôö¦€ùûü†ΓΔΘΛΞΠß®©ΣΪ§≠°·Α±≤≥¥ΒΕΖΗΙΚΜΦΫΨΩ' + 'άΝ¬ΟΡ≈Τ«»… ΥΧΆΈœ–―“”‘’÷ΉΊΌΎέήίόΏύαβψδεφγηιξκλμνοπώρστθωςχυζϊϋΐΰ\u00AD', 'x-mac-icelandic': // Python: 'mac_iceland' 'ÄÅÇÉÑÖÜáàâäãåçéèêëíìîïñóòôöõúùûüÝ°¢£§•¶ß®©™´¨≠ÆØ∞±≤≥¥µ∂∑∏π∫ªºΩæø' + '¿¡¬√ƒ≈∆«»… ÀÃÕŒœ–—“”‘’÷◊ÿŸ⁄€ÐðÞþý·‚„‰ÂÊÁËÈÍÎÏÌÓÔÒÚÛÙıˆ˜¯˘˙˚¸˝˛ˇ', 'x-mac-inuit': // http://unicode.org/Public/MAPPINGS/VENDORS/APPLE/INUIT.TXT 'ᐃᐄᐅᐆᐊᐋᐱᐲᐳᐴᐸᐹᑉᑎᑏᑐᑑᑕᑖᑦᑭᑮᑯᑰᑲᑳᒃᒋᒌᒍᒎᒐᒑ°ᒡᒥᒦ•¶ᒧ®©™ᒨᒪᒫᒻᓂᓃᓄᓅᓇᓈᓐᓯᓰᓱᓲᓴᓵᔅᓕᓖᓗ' + 'ᓘᓚᓛᓪᔨᔩᔪᔫᔭ… ᔮᔾᕕᕖᕗ–—“”‘’ᕘᕙᕚᕝᕆᕇᕈᕉᕋᕌᕐᕿᖀᖁᖂᖃᖄᖅᖏᖐᖑᖒᖓᖔᖕᙱᙲᙳᙴᙵᙶᖖᖠᖡᖢᖣᖤᖥᖦᕼŁł', 'x-mac-ce': // Python: 'mac_latin2' 'ÄĀāÉĄÖÜáąČäčĆć鏟ĎíďĒēĖóėôöõúĚěü†°Ę£§•¶ß®©™ę¨≠ģĮįĪ≤≥īĶ∂∑łĻļĽľĹĺŅ' + 'ņѬ√ńŇ∆«»… ňŐÕőŌ–—“”‘’÷◊ōŔŕŘ‹›řŖŗŠ‚„šŚśÁŤťÍŽžŪÓÔūŮÚůŰűŲųÝýķŻŁżĢˇ', macintosh: // Python: 'mac_roman' 'ÄÅÇÉÑÖÜáàâäãåçéèêëíìîïñóòôöõúùûü†°¢£§•¶ß®©™´¨≠ÆØ∞±≤≥¥µ∂∑∏π∫ªºΩæø' + '¿¡¬√ƒ≈∆«»… ÀÃÕŒœ–—“”‘’÷◊ÿŸ⁄€‹›fifl‡·‚„‰ÂÊÁËÈÍÎÏÌÓÔÒÚÛÙıˆ˜¯˘˙˚¸˝˛ˇ', 'x-mac-romanian': // Python: 'mac_romanian' 'ÄÅÇÉÑÖÜáàâäãåçéèêëíìîïñóòôöõúùûü†°¢£§•¶ß®©™´¨≠ĂȘ∞±≤≥¥µ∂∑∏π∫ªºΩăș' + '¿¡¬√ƒ≈∆«»… ÀÃÕŒœ–—“”‘’÷◊ÿŸ⁄€‹›Țț‡·‚„‰ÂÊÁËÈÍÎÏÌÓÔÒÚÛÙıˆ˜¯˘˙˚¸˝˛ˇ', 'x-mac-turkish': // Python: 'mac_turkish' 'ÄÅÇÉÑÖÜáàâäãåçéèêëíìîïñóòôöõúùûü†°¢£§•¶ß®©™´¨≠ÆØ∞±≤≥¥µ∂∑∏π∫ªºΩæø' + '¿¡¬√ƒ≈∆«»… ÀÃÕŒœ–—“”‘’÷◊ÿŸĞğİıŞş‡·‚„‰ÂÊÁËÈÍÎÏÌÓÔÒÚÛÙˆ˜¯˘˙˚¸˝˛ˇ' }; // Decodes an old-style Macintosh string. Returns either a Unicode JavaScript // string, or 'undefined' if the encoding is unsupported. For example, we do // not support Chinese, Japanese or Korean because these would need large // mapping tables. decode.MACSTRING = function(dataView, offset, dataLength, encoding) { var table = eightBitMacEncodings[encoding]; if (table === undefined) { return undefined; } var r###lt = ''; for (var i = 0; i < dataLength; i++) { var c = dataView.getUint8(offset + i); // In all eight-bit Mac encodings, the characters 0x00..0x7F are // mapped to U+0000..U+007F; we only need to look up the others. if (c <= 0x7F) { r###lt += String.fromCharCode(c); } else { r###lt += table[c & 0x7F]; } } return r###lt; }; // Helper function for encode.MACSTRING. Returns a dictionary for mapping // Unicode character codes to their 8-bit MacOS equivalent. This table // is not exactly a super cheap data structure, but we do not care because // encoding Macintosh strings is only rarely needed in typical applications. var macEncodingTableCache = typeof WeakMap === 'function' && new WeakMap(); var macEncodingCacheKeys; var getMacEncodingTable = function(encoding) { // Since we use encoding as a cache key for WeakMap, it has to be // a String object and not a literal. And at least on NodeJS 2.10.1, // WeakMap requires that the same String instance is passed for cache hits. if (!macEncodingCacheKeys) { macEncodingCacheKeys = {}; for (var e in eightBitMacEncodings) { /*jshint -W053 */ // Suppress "Do not use String as a constructor." macEncodingCacheKeys[e] = new String(e); } } var cacheKey = macEncodingCacheKeys[encoding]; if (cacheKey === undefined) { return undefined; } // We can't do "if (cache.has(key)) {return cache.get(key)}" here: // since garbage collection may run at any time, it could also kick in // between the calls to cache.has() and cache.get(). In that case, // we would return 'undefined' even though we do support the encoding. if (macEncodingTableCache) { var cachedTable = macEncodingTableCache.get(cacheKey); if (cachedTable !== undefined) { return cachedTable; } } var decodingTable = eightBitMacEncodings[encoding]; if (decodingTable === undefined) { return undefined; } var encodingTable = {}; for (var i = 0; i < decodingTable.length; i++) { encodingTable[decodingTable.charCodeAt(i)] = i + 0x80; } if (macEncodingTableCache) { macEncodingTableCache.set(cacheKey, encodingTable); } return encodingTable; }; // Encodes an old-style Macintosh string. Returns a byte array upon success. // If the requested encoding is unsupported, or if the input string contains // a character that cannot be expressed in the encoding, the function returns // 'undefined'. encode.MACSTRING = function(str, encoding) { var table = getMacEncodingTable(encoding); if (table === undefined) { return undefined; } var r###lt = []; for (var i = 0; i < str.length; i++) { var c = str.charCodeAt(i); // In all eight-bit Mac encodings, the characters 0x00..0x7F are // mapped to U+0000..U+007F; we only need to look up the others. if (c >= 0x80) { c = table[c]; if (c === undefined) { // str contains a Unicode character that cannot be encoded // in the requested encoding. return undefined; } } r###lt.push(c); } return r###lt; }; sizeOf.MACSTRING = function(str, encoding) { var b = encode.MACSTRING(str, encoding); if (b !== undefined) { return b.length; } else { return 0; } }; // Convert a list of values to a CFF INDEX structure. // The values should be objects containing name / type / value. encode.INDEX = function(l) { var i; //var offset, offsets, offsetEncoder, encodedOffsets, encodedOffset, data, // dataSize, i, v; // Because we have to know which data type to use to encode the offsets, // we have to go through the values twice: once to encode the data and // calculate the offets, then again to encode the offsets using the fitting data type. var offset = 1; // First offset is always 1. var offsets = [offset]; var data = []; var dataSize = 0; for (i = 0; i < l.length; i += 1) { var v = encode.OBJECT(l[i]); Array.prototype.push.apply(data, v); dataSize += v.length; offset += v.length; offsets.push(offset); } if (data.length === 0) { return [0, 0]; } var encodedOffsets = []; var offSize = (1 + Math.floor(Math.log(dataSize) / Math.log(2)) / 8) | 0; var offsetEncoder = [undefined, encode.BYTE, encode.USHORT, encode.UINT24, encode.ULONG][offSize]; for (i = 0; i < offsets.length; i += 1) { var encodedOffset = offsetEncoder(offsets[i]); Array.prototype.push.apply(encodedOffsets, encodedOffset); } return Array.prototype.concat(encode.Card16(l.length), encode.OffSize(offSize), encodedOffsets, data); }; sizeOf.INDEX = function(v) { return encode.INDEX(v).length; }; // Convert an object to a CFF DICT structure. // The keys should be numeric. // The values should be objects containing name / type / value. encode.DICT = function(m) { var d = []; var keys = Object.keys(m); var length = keys.length; for (var i = 0; i < length; i += 1) { // Object.keys() return string keys, but our keys are always numeric. var k = parseInt(keys[i], 0); var v = m[k]; // Value comes before the key. d = d.concat(encode.OPERAND(v.value, v.type)); d = d.concat(encode.OPERATOR(k)); } return d; }; sizeOf.DICT = function(m) { return encode.DICT(m).length; }; encode.OPERATOR = function(v) { if (v < 1200) { return [v]; } else { return [12, v - 1200]; } }; encode.OPERAND = function(v, type) { var d = []; if (Array.isArray(type)) { for (var i = 0; i < type.length; i += 1) { check.argument(v.length === type.length, 'Not enough arguments given for type' + type); d = d.concat(encode.OPERAND(v[i], type[i])); } } else { if (type === 'SID') { d = d.concat(encode.NUMBER(v)); } else if (type === 'offset') { // We make it easy for ourselves and always encode offsets as // 4 bytes. This makes offset calculation for the top dict easier. d = d.concat(encode.NUMBER32(v)); } else if (type === 'number') { d = d.concat(encode.NUMBER(v)); } else if (type === 'real') { d = d.concat(encode.REAL(v)); } else { throw new Error('Unknown operand type ' + type); // FIXME Add support for booleans } } return d; }; encode.OP = encode.BYTE; sizeOf.OP = sizeOf.BYTE; // memoize charstring encoding using WeakMap if available var wmm = typeof WeakMap === 'function' && new WeakMap(); // Convert a list of CharString operations to bytes. encode.CHARSTRING = function(ops) { // See encode.MACSTRING for why we don't do "if (wmm && wmm.has(ops))". if (wmm) { var cachedValue = wmm.get(ops); if (cachedValue !== undefined) { return cachedValue; } } var d = []; var length = ops.length; for (var i = 0; i < length; i += 1) { var op = ops[i]; d = d.concat(encode[op.type](op.value)); } if (wmm) { wmm.set(ops, d); } return d; }; sizeOf.CHARSTRING = function(ops) { return encode.CHARSTRING(ops).length; }; // Utility functions //////////////////////////////////////////////////////// // Convert an object containing name / type / value to bytes. encode.OBJECT = function(v) { var encodingFunction = encode[v.type]; check.argument(encodingFunction !== undefined, 'No encoding function for type ' + v.type); return encodingFunction(v.value); }; sizeOf.OBJECT = function(v) { var sizeOfFunction = sizeOf[v.type]; check.argument(sizeOfFunction !== undefined, 'No sizeOf function for type ' + v.type); return sizeOfFunction(v.value); }; // Convert a table object to bytes. // A table contains a list of fields containing the metadata (name, type and default value). // The table itself has the field values set as attributes. encode.TABLE = function(table) { var d = []; var length = table.fields.length; for (var i = 0; i < length; i += 1) { var field = table.fields[i]; var encodingFunction = encode[field.type]; check.argument(encodingFunction !== undefined, 'No encoding function for field type ' + field.type); var value = table[field.name]; if (value === undefined) { value = field.value; } var bytes = encodingFunction(value); d = d.concat(bytes); } return d; }; sizeOf.TABLE = function(table) { var numBytes = 0; var length = table.fields.length; for (var i = 0; i < length; i += 1) { var field = table.fields[i]; var sizeOfFunction = sizeOf[field.type]; check.argument(sizeOfFunction !== undefined, 'No sizeOf function for field type ' + field.type); var value = table[field.name]; if (value === undefined) { value = field.value; } numBytes += sizeOfFunction(value); } return numBytes; }; // Merge in a list of bytes. encode.LITERAL = function(v) { return v; }; sizeOf.LITERAL = function(v) { return v.length; }; exports.decode = decode; exports.encode = encode; exports.sizeOf = sizeOf; },{"./check":2}],29:[function(require,module,exports){ 'use strict'; exports.isBrowser = function() { return typeof window !== 'undefined'; }; exports.isNode = function() { return typeof window === 'undefined'; }; exports.nodeBufferToArrayBuffer = function(buffer) { var ab = new ArrayBuffer(buffer.length); var view = new Uint8Array(ab); for (var i = 0; i < buffer.length; ++i) { view[i] = buffer[i]; } return ab; }; exports.arrayBufferToNodeBuffer = function(ab) { var buffer = new Buffer(ab.byteLength); var view = new Uint8Array(ab); for (var i = 0; i < buffer.length; ++i) { buffer[i] = view[i]; } return buffer; }; exports.checkArgument = function(expression, message) { if (!expression) { throw message; } }; },{}]},{},[8])(8) });