977 lines
28 KiB
Plaintext
977 lines
28 KiB
Plaintext
// Data types used in the OpenType font file.
|
||
// All OpenType fonts use Motorola-style byte ordering (Big Endian)
|
||
|
||
import check from './check';
|
||
|
||
const LIMIT16 = 32768; // The limit at which a 16-bit number switches signs == 2^15
|
||
const LIMIT32 = 2147483648; // The limit at which a 32-bit number switches signs == 2 ^ 31
|
||
|
||
/**
|
||
* @exports opentype.decode
|
||
* @class
|
||
*/
|
||
const decode = {};
|
||
/**
|
||
* @exports opentype.encode
|
||
* @class
|
||
*/
|
||
const encode = {};
|
||
/**
|
||
* @exports opentype.sizeOf
|
||
* @class
|
||
*/
|
||
const 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.
|
||
* @param {number}
|
||
* @returns {Array}
|
||
*/
|
||
encode.BYTE = function(v) {
|
||
check.argument(v >= 0 && v <= 255, 'Byte value should be between 0 and 255.');
|
||
return [v];
|
||
};
|
||
/**
|
||
* @constant
|
||
* @type {number}
|
||
*/
|
||
sizeOf.BYTE = constant(1);
|
||
|
||
/**
|
||
* Convert a 8-bit signed integer to a list of 1 byte.
|
||
* @param {string}
|
||
* @returns {Array}
|
||
*/
|
||
encode.CHAR = function(v) {
|
||
return [v.charCodeAt(0)];
|
||
};
|
||
|
||
/**
|
||
* @constant
|
||
* @type {number}
|
||
*/
|
||
sizeOf.CHAR = constant(1);
|
||
|
||
/**
|
||
* Convert an ASCII string to a list of bytes.
|
||
* @param {string}
|
||
* @returns {Array}
|
||
*/
|
||
encode.CHARARRAY = function(v) {
|
||
if (typeof v === 'undefined') {
|
||
v = '';
|
||
console.warn('Undefined CHARARRAY encountered and treated as an empty string. This is probably caused by a missing glyph name.');
|
||
}
|
||
const b = [];
|
||
for (let i = 0; i < v.length; i += 1) {
|
||
b[i] = v.charCodeAt(i);
|
||
}
|
||
|
||
return b;
|
||
};
|
||
|
||
/**
|
||
* @param {Array}
|
||
* @returns {number}
|
||
*/
|
||
sizeOf.CHARARRAY = function(v) {
|
||
if (typeof v === 'undefined') {
|
||
return 0;
|
||
}
|
||
return v.length;
|
||
};
|
||
|
||
/**
|
||
* Convert a 16-bit unsigned integer to a list of 2 bytes.
|
||
* @param {number}
|
||
* @returns {Array}
|
||
*/
|
||
encode.USHORT = function(v) {
|
||
return [(v >> 8) & 0xFF, v & 0xFF];
|
||
};
|
||
|
||
/**
|
||
* @constant
|
||
* @type {number}
|
||
*/
|
||
sizeOf.USHORT = constant(2);
|
||
|
||
/**
|
||
* Convert a 16-bit signed integer to a list of 2 bytes.
|
||
* @param {number}
|
||
* @returns {Array}
|
||
*/
|
||
encode.SHORT = function(v) {
|
||
// Two's complement
|
||
if (v >= LIMIT16) {
|
||
v = -(2 * LIMIT16 - v);
|
||
}
|
||
|
||
return [(v >> 8) & 0xFF, v & 0xFF];
|
||
};
|
||
|
||
/**
|
||
* @constant
|
||
* @type {number}
|
||
*/
|
||
sizeOf.SHORT = constant(2);
|
||
|
||
/**
|
||
* Convert a 24-bit unsigned integer to a list of 3 bytes.
|
||
* @param {number}
|
||
* @returns {Array}
|
||
*/
|
||
encode.UINT24 = function(v) {
|
||
return [(v >> 16) & 0xFF, (v >> 8) & 0xFF, v & 0xFF];
|
||
};
|
||
|
||
/**
|
||
* @constant
|
||
* @type {number}
|
||
*/
|
||
sizeOf.UINT24 = constant(3);
|
||
|
||
/**
|
||
* Convert a 32-bit unsigned integer to a list of 4 bytes.
|
||
* @param {number}
|
||
* @returns {Array}
|
||
*/
|
||
encode.ULONG = function(v) {
|
||
return [(v >> 24) & 0xFF, (v >> 16) & 0xFF, (v >> 8) & 0xFF, v & 0xFF];
|
||
};
|
||
|
||
/**
|
||
* @constant
|
||
* @type {number}
|
||
*/
|
||
sizeOf.ULONG = constant(4);
|
||
|
||
/**
|
||
* Convert a 32-bit unsigned integer to a list of 4 bytes.
|
||
* @param {number}
|
||
* @returns {Array}
|
||
*/
|
||
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];
|
||
};
|
||
|
||
/**
|
||
* @constant
|
||
* @type {number}
|
||
*/
|
||
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;
|
||
|
||
/**
|
||
* Convert a 32-bit Apple Mac timestamp integer to a list of 8 bytes, 64-bit timestamp.
|
||
* @param {number}
|
||
* @returns {Array}
|
||
*/
|
||
encode.LONGDATETIME = function(v) {
|
||
return [0, 0, 0, 0, (v >> 24) & 0xFF, (v >> 16) & 0xFF, (v >> 8) & 0xFF, v & 0xFF];
|
||
};
|
||
|
||
/**
|
||
* @constant
|
||
* @type {number}
|
||
*/
|
||
sizeOf.LONGDATETIME = constant(8);
|
||
|
||
/**
|
||
* Convert a 4-char tag to a list of 4 bytes.
|
||
* @param {string}
|
||
* @returns {Array}
|
||
*/
|
||
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)];
|
||
};
|
||
|
||
/**
|
||
* @constant
|
||
* @type {number}
|
||
*/
|
||
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.
|
||
/**
|
||
* Convert a numeric operand or charstring number to a variable-size list of bytes.
|
||
* @param {number}
|
||
* @returns {Array}
|
||
*/
|
||
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);
|
||
}
|
||
};
|
||
|
||
/**
|
||
* @param {number}
|
||
* @returns {number}
|
||
*/
|
||
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.
|
||
* @param {number}
|
||
* @returns {Array}
|
||
*/
|
||
encode.NUMBER16 = function(v) {
|
||
return [28, (v >> 8) & 0xFF, v & 0xFF];
|
||
};
|
||
|
||
/**
|
||
* @constant
|
||
* @type {number}
|
||
*/
|
||
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.
|
||
* @param {number}
|
||
* @returns {Array}
|
||
*/
|
||
encode.NUMBER32 = function(v) {
|
||
return [29, (v >> 24) & 0xFF, (v >> 16) & 0xFF, (v >> 8) & 0xFF, v & 0xFF];
|
||
};
|
||
|
||
/**
|
||
* @constant
|
||
* @type {number}
|
||
*/
|
||
sizeOf.NUMBER32 = constant(5);
|
||
|
||
/**
|
||
* @param {number}
|
||
* @returns {Array}
|
||
*/
|
||
encode.REAL = function(v) {
|
||
let 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.
|
||
const m = /\.(\d*?)(?:9{5,20}|0{5,20})\d{0,2}(?:e(.+)|$)/.exec(value);
|
||
if (m) {
|
||
const epsilon = parseFloat('1e' + ((m[2] ? +m[2] : 0) + m[1].length));
|
||
value = (Math.round(v * epsilon) / epsilon).toString();
|
||
}
|
||
|
||
let nibbles = '';
|
||
for (let i = 0, ii = value.length; i < ii; i += 1) {
|
||
const 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';
|
||
const out = [30];
|
||
for (let i = 0, ii = nibbles.length; i < ii; i += 2) {
|
||
out.push(parseInt(nibbles.substr(i, 2), 16));
|
||
}
|
||
|
||
return out;
|
||
};
|
||
|
||
/**
|
||
* @param {number}
|
||
* @returns {number}
|
||
*/
|
||
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;
|
||
|
||
/**
|
||
* @param {DataView} data
|
||
* @param {number} offset
|
||
* @param {number} numBytes
|
||
* @returns {string}
|
||
*/
|
||
decode.UTF8 = function(data, offset, numBytes) {
|
||
const codePoints = [];
|
||
const numChars = numBytes;
|
||
for (let j = 0; j < numChars; j++, offset += 1) {
|
||
codePoints[j] = data.getUint8(offset);
|
||
}
|
||
|
||
return String.fromCharCode.apply(null, codePoints);
|
||
};
|
||
|
||
/**
|
||
* @param {DataView} data
|
||
* @param {number} offset
|
||
* @param {number} numBytes
|
||
* @returns {string}
|
||
*/
|
||
decode.UTF16 = function(data, offset, numBytes) {
|
||
const codePoints = [];
|
||
const numChars = numBytes / 2;
|
||
for (let 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.
|
||
* @param {string}
|
||
* @returns {Array}
|
||
*/
|
||
encode.UTF16 = function(v) {
|
||
const b = [];
|
||
for (let i = 0; i < v.length; i += 1) {
|
||
const codepoint = v.charCodeAt(i);
|
||
b[b.length] = (codepoint >> 8) & 0xFF;
|
||
b[b.length] = codepoint & 0xFF;
|
||
}
|
||
|
||
return b;
|
||
};
|
||
|
||
/**
|
||
* @param {string}
|
||
* @returns {number}
|
||
*/
|
||
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 comparatively 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'))
|
||
/**
|
||
* @private
|
||
*/
|
||
const 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.
|
||
* @param {DataView} dataView
|
||
* @param {number} offset
|
||
* @param {number} dataLength
|
||
* @param {string} encoding
|
||
* @returns {string}
|
||
*/
|
||
decode.MACSTRING = function(dataView, offset, dataLength, encoding) {
|
||
const table = eightBitMacEncodings[encoding];
|
||
if (table === undefined) {
|
||
return undefined;
|
||
}
|
||
|
||
let result = '';
|
||
for (let i = 0; i < dataLength; i++) {
|
||
const 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) {
|
||
result += String.fromCharCode(c);
|
||
} else {
|
||
result += table[c & 0x7F];
|
||
}
|
||
}
|
||
|
||
return result;
|
||
};
|
||
|
||
// 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.
|
||
const macEncodingTableCache = typeof WeakMap === 'function' && new WeakMap();
|
||
let macEncodingCacheKeys;
|
||
const 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 (let e in eightBitMacEncodings) {
|
||
/*jshint -W053 */ // Suppress "Do not use String as a constructor."
|
||
macEncodingCacheKeys[e] = new String(e);
|
||
}
|
||
}
|
||
|
||
const 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) {
|
||
const cachedTable = macEncodingTableCache.get(cacheKey);
|
||
if (cachedTable !== undefined) {
|
||
return cachedTable;
|
||
}
|
||
}
|
||
|
||
const decodingTable = eightBitMacEncodings[encoding];
|
||
if (decodingTable === undefined) {
|
||
return undefined;
|
||
}
|
||
|
||
const encodingTable = {};
|
||
for (let 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'.
|
||
* @param {string} str
|
||
* @param {string} encoding
|
||
* @returns {Array}
|
||
*/
|
||
encode.MACSTRING = function(str, encoding) {
|
||
const table = getMacEncodingTable(encoding);
|
||
if (table === undefined) {
|
||
return undefined;
|
||
}
|
||
|
||
const result = [];
|
||
for (let i = 0; i < str.length; i++) {
|
||
let 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;
|
||
}
|
||
}
|
||
result[i] = c;
|
||
// result.push(c);
|
||
}
|
||
|
||
return result;
|
||
};
|
||
|
||
/**
|
||
* @param {string} str
|
||
* @param {string} encoding
|
||
* @returns {number}
|
||
*/
|
||
sizeOf.MACSTRING = function(str, encoding) {
|
||
const b = encode.MACSTRING(str, encoding);
|
||
if (b !== undefined) {
|
||
return b.length;
|
||
} else {
|
||
return 0;
|
||
}
|
||
};
|
||
|
||
// Helper for encode.VARDELTAS
|
||
function isByteEncodable(value) {
|
||
return value >= -128 && value <= 127;
|
||
}
|
||
|
||
// Helper for encode.VARDELTAS
|
||
function encodeVarDeltaRunAsZeroes(deltas, pos, result) {
|
||
let runLength = 0;
|
||
const numDeltas = deltas.length;
|
||
while (pos < numDeltas && runLength < 64 && deltas[pos] === 0) {
|
||
++pos;
|
||
++runLength;
|
||
}
|
||
result.push(0x80 | (runLength - 1));
|
||
return pos;
|
||
}
|
||
|
||
// Helper for encode.VARDELTAS
|
||
function encodeVarDeltaRunAsBytes(deltas, offset, result) {
|
||
let runLength = 0;
|
||
const numDeltas = deltas.length;
|
||
let pos = offset;
|
||
while (pos < numDeltas && runLength < 64) {
|
||
const value = deltas[pos];
|
||
if (!isByteEncodable(value)) {
|
||
break;
|
||
}
|
||
|
||
// Within a byte-encoded run of deltas, a single zero is best
|
||
// stored literally as 0x00 value. However, if we have two or
|
||
// more zeroes in a sequence, it is better to start a new run.
|
||
// Fore example, the sequence of deltas [15, 15, 0, 15, 15]
|
||
// becomes 6 bytes (04 0F 0F 00 0F 0F) when storing the zero
|
||
// within the current run, but 7 bytes (01 0F 0F 80 01 0F 0F)
|
||
// when starting a new run.
|
||
if (value === 0 && pos + 1 < numDeltas && deltas[pos + 1] === 0) {
|
||
break;
|
||
}
|
||
|
||
++pos;
|
||
++runLength;
|
||
}
|
||
result.push(runLength - 1);
|
||
for (let i = offset; i < pos; ++i) {
|
||
result.push((deltas[i] + 256) & 0xff);
|
||
}
|
||
return pos;
|
||
}
|
||
|
||
// Helper for encode.VARDELTAS
|
||
function encodeVarDeltaRunAsWords(deltas, offset, result) {
|
||
let runLength = 0;
|
||
const numDeltas = deltas.length;
|
||
let pos = offset;
|
||
while (pos < numDeltas && runLength < 64) {
|
||
const value = deltas[pos];
|
||
|
||
// Within a word-encoded run of deltas, it is easiest to start
|
||
// a new run (with a different encoding) whenever we encounter
|
||
// a zero value. For example, the sequence [0x6666, 0, 0x7777]
|
||
// needs 7 bytes when storing the zero inside the current run
|
||
// (42 66 66 00 00 77 77), and equally 7 bytes when starting a
|
||
// new run (40 66 66 80 40 77 77).
|
||
if (value === 0) {
|
||
break;
|
||
}
|
||
|
||
// Within a word-encoded run of deltas, a single value in the
|
||
// range (-128..127) should be encoded within the current run
|
||
// because it is more compact. For example, the sequence
|
||
// [0x6666, 2, 0x7777] becomes 7 bytes when storing the value
|
||
// literally (42 66 66 00 02 77 77), but 8 bytes when starting
|
||
// a new run (40 66 66 00 02 40 77 77).
|
||
if (isByteEncodable(value) && pos + 1 < numDeltas && isByteEncodable(deltas[pos + 1])) {
|
||
break;
|
||
}
|
||
|
||
++pos;
|
||
++runLength;
|
||
}
|
||
result.push(0x40 | (runLength - 1));
|
||
for (let i = offset; i < pos; ++i) {
|
||
const val = deltas[i];
|
||
result.push(((val + 0x10000) >> 8) & 0xff, (val + 0x100) & 0xff);
|
||
}
|
||
return pos;
|
||
}
|
||
|
||
/**
|
||
* Encode a list of variation adjustment deltas.
|
||
*
|
||
* Variation adjustment deltas are used in ‘gvar’ and ‘cvar’ tables.
|
||
* They indicate how points (in ‘gvar’) or values (in ‘cvar’) get adjusted
|
||
* when generating instances of variation fonts.
|
||
*
|
||
* @see https://www.microsoft.com/typography/otspec/gvar.htm
|
||
* @see https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6gvar.html
|
||
* @param {Array}
|
||
* @return {Array}
|
||
*/
|
||
encode.VARDELTAS = function(deltas) {
|
||
let pos = 0;
|
||
const result = [];
|
||
while (pos < deltas.length) {
|
||
const value = deltas[pos];
|
||
if (value === 0) {
|
||
pos = encodeVarDeltaRunAsZeroes(deltas, pos, result);
|
||
} else if (value >= -128 && value <= 127) {
|
||
pos = encodeVarDeltaRunAsBytes(deltas, pos, result);
|
||
} else {
|
||
pos = encodeVarDeltaRunAsWords(deltas, pos, result);
|
||
}
|
||
}
|
||
return result;
|
||
};
|
||
|
||
// Convert a list of values to a CFF INDEX structure.
|
||
// The values should be objects containing name / type / value.
|
||
/**
|
||
* @param {Array} l
|
||
* @returns {Array}
|
||
*/
|
||
encode.INDEX = function(l) {
|
||
//var offset, offsets, offsetEncoder, encodedOffsets, encodedOffset, data,
|
||
// 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 offsets, then again to encode the offsets using the fitting data type.
|
||
let offset = 1; // First offset is always 1.
|
||
const offsets = [offset];
|
||
const data = [];
|
||
for (let i = 0; i < l.length; i += 1) {
|
||
const v = encode.OBJECT(l[i]);
|
||
Array.prototype.push.apply(data, v);
|
||
offset += v.length;
|
||
offsets.push(offset);
|
||
}
|
||
|
||
if (data.length === 0) {
|
||
return [0, 0];
|
||
}
|
||
|
||
const encodedOffsets = [];
|
||
const offSize = (1 + Math.floor(Math.log(offset) / Math.log(2)) / 8) | 0;
|
||
const offsetEncoder = [undefined, encode.BYTE, encode.USHORT, encode.UINT24, encode.ULONG][offSize];
|
||
for (let i = 0; i < offsets.length; i += 1) {
|
||
const encodedOffset = offsetEncoder(offsets[i]);
|
||
Array.prototype.push.apply(encodedOffsets, encodedOffset);
|
||
}
|
||
|
||
return Array.prototype.concat(encode.Card16(l.length),
|
||
encode.OffSize(offSize),
|
||
encodedOffsets,
|
||
data);
|
||
};
|
||
|
||
/**
|
||
* @param {Array}
|
||
* @returns {number}
|
||
*/
|
||
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.
|
||
* @param {Object} m
|
||
* @returns {Array}
|
||
*/
|
||
encode.DICT = function(m) {
|
||
let d = [];
|
||
const keys = Object.keys(m);
|
||
const length = keys.length;
|
||
|
||
for (let i = 0; i < length; i += 1) {
|
||
// Object.keys() return string keys, but our keys are always numeric.
|
||
const k = parseInt(keys[i], 0);
|
||
const 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;
|
||
};
|
||
|
||
/**
|
||
* @param {Object}
|
||
* @returns {number}
|
||
*/
|
||
sizeOf.DICT = function(m) {
|
||
return encode.DICT(m).length;
|
||
};
|
||
|
||
/**
|
||
* @param {number}
|
||
* @returns {Array}
|
||
*/
|
||
encode.OPERATOR = function(v) {
|
||
if (v < 1200) {
|
||
return [v];
|
||
} else {
|
||
return [12, v - 1200];
|
||
}
|
||
};
|
||
|
||
/**
|
||
* @param {Array} v
|
||
* @param {string}
|
||
* @returns {Array}
|
||
*/
|
||
encode.OPERAND = function(v, type) {
|
||
let d = [];
|
||
if (Array.isArray(type)) {
|
||
for (let 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
|
||
const wmm = typeof WeakMap === 'function' && new WeakMap();
|
||
|
||
/**
|
||
* Convert a list of CharString operations to bytes.
|
||
* @param {Array}
|
||
* @returns {Array}
|
||
*/
|
||
encode.CHARSTRING = function(ops) {
|
||
// See encode.MACSTRING for why we don't do "if (wmm && wmm.has(ops))".
|
||
if (wmm) {
|
||
const cachedValue = wmm.get(ops);
|
||
if (cachedValue !== undefined) {
|
||
return cachedValue;
|
||
}
|
||
}
|
||
|
||
let d = [];
|
||
const length = ops.length;
|
||
|
||
for (let i = 0; i < length; i += 1) {
|
||
const op = ops[i];
|
||
d = d.concat(encode[op.type](op.value));
|
||
}
|
||
|
||
if (wmm) {
|
||
wmm.set(ops, d);
|
||
}
|
||
|
||
return d;
|
||
};
|
||
|
||
/**
|
||
* @param {Array}
|
||
* @returns {number}
|
||
*/
|
||
sizeOf.CHARSTRING = function(ops) {
|
||
return encode.CHARSTRING(ops).length;
|
||
};
|
||
|
||
// Utility functions ////////////////////////////////////////////////////////
|
||
|
||
/**
|
||
* Convert an object containing name / type / value to bytes.
|
||
* @param {Object}
|
||
* @returns {Array}
|
||
*/
|
||
encode.OBJECT = function(v) {
|
||
const encodingFunction = encode[v.type];
|
||
check.argument(encodingFunction !== undefined, 'No encoding function for type ' + v.type);
|
||
return encodingFunction(v.value);
|
||
};
|
||
|
||
/**
|
||
* @param {Object}
|
||
* @returns {number}
|
||
*/
|
||
sizeOf.OBJECT = function(v) {
|
||
const 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.
|
||
* @param {opentype.Table}
|
||
* @returns {Array}
|
||
*/
|
||
encode.TABLE = function(table) {
|
||
let d = [];
|
||
const length = table.fields.length;
|
||
const subtables = [];
|
||
const subtableOffsets = [];
|
||
|
||
for (let i = 0; i < length; i += 1) {
|
||
const field = table.fields[i];
|
||
const encodingFunction = encode[field.type];
|
||
check.argument(encodingFunction !== undefined, 'No encoding function for field type ' + field.type + ' (' + field.name + ')');
|
||
let value = table[field.name];
|
||
if (value === undefined) {
|
||
value = field.value;
|
||
}
|
||
|
||
const bytes = encodingFunction(value);
|
||
|
||
if (field.type === 'TABLE') {
|
||
subtableOffsets.push(d.length);
|
||
d = d.concat([0, 0]);
|
||
subtables.push(bytes);
|
||
} else {
|
||
d = d.concat(bytes);
|
||
}
|
||
}
|
||
|
||
for (let i = 0; i < subtables.length; i += 1) {
|
||
const o = subtableOffsets[i];
|
||
const offset = d.length;
|
||
check.argument(offset < 65536, 'Table ' + table.tableName + ' too big.');
|
||
d[o] = offset >> 8;
|
||
d[o + 1] = offset & 0xff;
|
||
d = d.concat(subtables[i]);
|
||
}
|
||
|
||
return d;
|
||
};
|
||
|
||
/**
|
||
* @param {opentype.Table}
|
||
* @returns {number}
|
||
*/
|
||
sizeOf.TABLE = function(table) {
|
||
let numBytes = 0;
|
||
const length = table.fields.length;
|
||
|
||
for (let i = 0; i < length; i += 1) {
|
||
const field = table.fields[i];
|
||
const sizeOfFunction = sizeOf[field.type];
|
||
check.argument(sizeOfFunction !== undefined, 'No sizeOf function for field type ' + field.type + ' (' + field.name + ')');
|
||
let value = table[field.name];
|
||
if (value === undefined) {
|
||
value = field.value;
|
||
}
|
||
|
||
numBytes += sizeOfFunction(value);
|
||
|
||
// Subtables take 2 more bytes for offsets.
|
||
if (field.type === 'TABLE') {
|
||
numBytes += 2;
|
||
}
|
||
}
|
||
|
||
return numBytes;
|
||
};
|
||
|
||
encode.RECORD = encode.TABLE;
|
||
sizeOf.RECORD = sizeOf.TABLE;
|
||
|
||
// Merge in a list of bytes.
|
||
encode.LITERAL = function(v) {
|
||
return v;
|
||
};
|
||
|
||
sizeOf.LITERAL = function(v) {
|
||
return v.length;
|
||
};
|
||
|
||
export { decode, encode, sizeOf };
|