astro-ghostcms/.pnpm-store/v3/files/fb/f9bc36ab03b3709e1e1971aca73...

977 lines
28 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 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 };