opentype.js
Version:
OpenType font parser
1,775 lines (1,544 loc) • 477 kB
JavaScript
/**
* https://opentype.js.org v1.3.4 | (c) Frederik De Bleser and other contributors | MIT License | Uses tiny-inflate by Devon Govett and string.prototype.codepointat polyfill by Mathias Bynens
*/
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
typeof define === 'function' && define.amd ? define(['exports'], factory) :
(global = global || self, factory(global.opentype = {}));
}(this, (function (exports) { 'use strict';
/*! https://mths.be/codepointat v0.2.0 by @mathias */
if (!String.prototype.codePointAt) {
(function() {
var defineProperty = (function() {
// IE 8 only supports `Object.defineProperty` on DOM elements
try {
var object = {};
var $defineProperty = Object.defineProperty;
var result = $defineProperty(object, object, object) && $defineProperty;
} catch(error) {}
return result;
}());
var codePointAt = function(position) {
if (this == null) {
throw TypeError();
}
var string = String(this);
var size = string.length;
// `ToInteger`
var index = position ? Number(position) : 0;
if (index != index) { // better `isNaN`
index = 0;
}
// Account for out-of-bounds indices:
if (index < 0 || index >= size) {
return undefined;
}
// Get the first code unit
var first = string.charCodeAt(index);
var second;
if ( // check if it’s the start of a surrogate pair
first >= 0xD800 && first <= 0xDBFF && // high surrogate
size > index + 1 // there is a next code unit
) {
second = string.charCodeAt(index + 1);
if (second >= 0xDC00 && second <= 0xDFFF) { // low surrogate
// https://mathiasbynens.be/notes/javascript-encoding#surrogate-formulae
return (first - 0xD800) * 0x400 + second - 0xDC00 + 0x10000;
}
}
return first;
};
if (defineProperty) {
defineProperty(String.prototype, 'codePointAt', {
'value': codePointAt,
'configurable': true,
'writable': true
});
} else {
String.prototype.codePointAt = codePointAt;
}
}());
}
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, btype, 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;
var tinyInflate = tinf_uncompress;
// The Bounding Box object
function derive(v0, v1, v2, v3, t) {
return Math.pow(1 - t, 3) * v0 +
3 * Math.pow(1 - t, 2) * t * v1 +
3 * (1 - t) * Math.pow(t, 2) * v2 +
Math.pow(t, 3) * v3;
}
/**
* A bounding box is an enclosing box that describes the smallest measure within which all the points lie.
* It is used to calculate the bounding box of a glyph or text path.
*
* On initialization, x1/y1/x2/y2 will be NaN. Check if the bounding box is empty using `isEmpty()`.
*
* @exports opentype.BoundingBox
* @class
* @constructor
*/
function BoundingBox() {
this.x1 = Number.NaN;
this.y1 = Number.NaN;
this.x2 = Number.NaN;
this.y2 = Number.NaN;
}
/**
* Returns true if the bounding box is empty, that is, no points have been added to the box yet.
*/
BoundingBox.prototype.isEmpty = function() {
return isNaN(this.x1) || isNaN(this.y1) || isNaN(this.x2) || isNaN(this.y2);
};
/**
* Add the point to the bounding box.
* The x1/y1/x2/y2 coordinates of the bounding box will now encompass the given point.
* @param {number} x - The X coordinate of the point.
* @param {number} y - The Y coordinate of the point.
*/
BoundingBox.prototype.addPoint = function(x, y) {
if (typeof x === 'number') {
if (isNaN(this.x1) || isNaN(this.x2)) {
this.x1 = x;
this.x2 = x;
}
if (x < this.x1) {
this.x1 = x;
}
if (x > this.x2) {
this.x2 = x;
}
}
if (typeof y === 'number') {
if (isNaN(this.y1) || isNaN(this.y2)) {
this.y1 = y;
this.y2 = y;
}
if (y < this.y1) {
this.y1 = y;
}
if (y > this.y2) {
this.y2 = y;
}
}
};
/**
* Add a X coordinate to the bounding box.
* This extends the bounding box to include the X coordinate.
* This function is used internally inside of addBezier.
* @param {number} x - The X coordinate of the point.
*/
BoundingBox.prototype.addX = function(x) {
this.addPoint(x, null);
};
/**
* Add a Y coordinate to the bounding box.
* This extends the bounding box to include the Y coordinate.
* This function is used internally inside of addBezier.
* @param {number} y - The Y coordinate of the point.
*/
BoundingBox.prototype.addY = function(y) {
this.addPoint(null, y);
};
/**
* Add a Bézier curve to the bounding box.
* This extends the bounding box to include the entire Bézier.
* @param {number} x0 - The starting X coordinate.
* @param {number} y0 - The starting Y coordinate.
* @param {number} x1 - The X coordinate of the first control point.
* @param {number} y1 - The Y coordinate of the first control point.
* @param {number} x2 - The X coordinate of the second control point.
* @param {number} y2 - The Y coordinate of the second control point.
* @param {number} x - The ending X coordinate.
* @param {number} y - The ending Y coordinate.
*/
BoundingBox.prototype.addBezier = function(x0, y0, x1, y1, x2, y2, x, y) {
// This code is based on http://nishiohirokazu.blogspot.com/2009/06/how-to-calculate-bezier-curves-bounding.html
// and https://github.com/icons8/svg-path-bounding-box
var p0 = [x0, y0];
var p1 = [x1, y1];
var p2 = [x2, y2];
var p3 = [x, y];
this.addPoint(x0, y0);
this.addPoint(x, y);
for (var i = 0; i <= 1; i++) {
var b = 6 * p0[i] - 12 * p1[i] + 6 * p2[i];
var a = -3 * p0[i] + 9 * p1[i] - 9 * p2[i] + 3 * p3[i];
var c = 3 * p1[i] - 3 * p0[i];
if (a === 0) {
if (b === 0) { continue; }
var t = -c / b;
if (0 < t && t < 1) {
if (i === 0) { this.addX(derive(p0[i], p1[i], p2[i], p3[i], t)); }
if (i === 1) { this.addY(derive(p0[i], p1[i], p2[i], p3[i], t)); }
}
continue;
}
var b2ac = Math.pow(b, 2) - 4 * c * a;
if (b2ac < 0) { continue; }
var t1 = (-b + Math.sqrt(b2ac)) / (2 * a);
if (0 < t1 && t1 < 1) {
if (i === 0) { this.addX(derive(p0[i], p1[i], p2[i], p3[i], t1)); }
if (i === 1) { this.addY(derive(p0[i], p1[i], p2[i], p3[i], t1)); }
}
var t2 = (-b - Math.sqrt(b2ac)) / (2 * a);
if (0 < t2 && t2 < 1) {
if (i === 0) { this.addX(derive(p0[i], p1[i], p2[i], p3[i], t2)); }
if (i === 1) { this.addY(derive(p0[i], p1[i], p2[i], p3[i], t2)); }
}
}
};
/**
* Add a quadratic curve to the bounding box.
* This extends the bounding box to include the entire quadratic curve.
* @param {number} x0 - The starting X coordinate.
* @param {number} y0 - The starting Y coordinate.
* @param {number} x1 - The X coordinate of the control point.
* @param {number} y1 - The Y coordinate of the control point.
* @param {number} x - The ending X coordinate.
* @param {number} y - The ending Y coordinate.
*/
BoundingBox.prototype.addQuad = function(x0, y0, x1, y1, x, y) {
var cp1x = x0 + 2 / 3 * (x1 - x0);
var cp1y = y0 + 2 / 3 * (y1 - y0);
var cp2x = cp1x + 1 / 3 * (x - x0);
var cp2y = cp1y + 1 / 3 * (y - y0);
this.addBezier(x0, y0, cp1x, cp1y, cp2x, cp2y, x, y);
};
// Geometric objects
/**
* A bézier path containing a set of path commands similar to a SVG path.
* Paths can be drawn on a context using `draw`.
* @exports opentype.Path
* @class
* @constructor
*/
function Path() {
this.commands = [];
this.fill = 'black';
this.stroke = null;
this.strokeWidth = 1;
}
/**
* @param {number} x
* @param {number} y
*/
Path.prototype.moveTo = function(x, y) {
this.commands.push({
type: 'M',
x: x,
y: y
});
};
/**
* @param {number} x
* @param {number} y
*/
Path.prototype.lineTo = function(x, y) {
this.commands.push({
type: 'L',
x: x,
y: y
});
};
/**
* Draws cubic curve
* @function
* curveTo
* @memberof opentype.Path.prototype
* @param {number} x1 - x of control 1
* @param {number} y1 - y of control 1
* @param {number} x2 - x of control 2
* @param {number} y2 - y of control 2
* @param {number} x - x of path point
* @param {number} y - y of path point
*/
/**
* Draws cubic curve
* @function
* bezierCurveTo
* @memberof opentype.Path.prototype
* @param {number} x1 - x of control 1
* @param {number} y1 - y of control 1
* @param {number} x2 - x of control 2
* @param {number} y2 - y of control 2
* @param {number} x - x of path point
* @param {number} y - y of path point
* @see curveTo
*/
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
});
};
/**
* Draws quadratic curve
* @function
* quadraticCurveTo
* @memberof opentype.Path.prototype
* @param {number} x1 - x of control
* @param {number} y1 - y of control
* @param {number} x - x of path point
* @param {number} y - y of path point
*/
/**
* Draws quadratic curve
* @function
* quadTo
* @memberof opentype.Path.prototype
* @param {number} x1 - x of control
* @param {number} y1 - y of control
* @param {number} x - x of path point
* @param {number} y - y of path point
*/
Path.prototype.quadTo = Path.prototype.quadraticCurveTo = function(x1, y1, x, y) {
this.commands.push({
type: 'Q',
x1: x1,
y1: y1,
x: x,
y: y
});
};
/**
* Closes the path
* @function closePath
* @memberof opentype.Path.prototype
*/
/**
* Close the path
* @function close
* @memberof opentype.Path.prototype
*/
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.
* @param {Array} pathOrCommands - another opentype.Path, an opentype.BoundingBox, or an array of commands.
*/
Path.prototype.extend = function(pathOrCommands) {
if (pathOrCommands.commands) {
pathOrCommands = pathOrCommands.commands;
} else if (pathOrCommands instanceof BoundingBox) {
var box = pathOrCommands;
this.moveTo(box.x1, box.y1);
this.lineTo(box.x2, box.y1);
this.lineTo(box.x2, box.y2);
this.lineTo(box.x1, box.y2);
this.close();
return;
}
Array.prototype.push.apply(this.commands, pathOrCommands);
};
/**
* Calculate the bounding box of the path.
* @returns {opentype.BoundingBox}
*/
Path.prototype.getBoundingBox = function() {
var box = new BoundingBox();
var startX = 0;
var startY = 0;
var prevX = 0;
var prevY = 0;
for (var i = 0; i < this.commands.length; i++) {
var cmd = this.commands[i];
switch (cmd.type) {
case 'M':
box.addPoint(cmd.x, cmd.y);
startX = prevX = cmd.x;
startY = prevY = cmd.y;
break;
case 'L':
box.addPoint(cmd.x, cmd.y);
prevX = cmd.x;
prevY = cmd.y;
break;
case 'Q':
box.addQuad(prevX, prevY, cmd.x1, cmd.y1, cmd.x, cmd.y);
prevX = cmd.x;
prevY = cmd.y;
break;
case 'C':
box.addBezier(prevX, prevY, cmd.x1, cmd.y1, cmd.x2, cmd.y2, cmd.x, cmd.y);
prevX = cmd.x;
prevY = cmd.y;
break;
case 'Z':
prevX = startX;
prevY = startY;
break;
default:
throw new Error('Unexpected path command ' + cmd.type);
}
}
if (box.isEmpty()) {
box.addPoint(0, 0);
}
return box;
};
/**
* Draw the path to a 2D context.
* @param {CanvasRenderingContext2D} ctx - A 2D drawing 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
* @param {number} [decimalPlaces=2] - The amount of decimal places for floating-point values
* @return {string}
*/
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 arguments$1 = arguments;
var s = '';
for (var i = 0; i < arguments.length; i += 1) {
var v = arguments$1[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 an SVG <path> element, as a string.
* @param {number} [decimalPlaces=2] - The amount of decimal places for floating-point values
* @return {string}
*/
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;
};
/**
* Convert the path to a DOM element.
* @param {number} [decimalPlaces=2] - The amount of decimal places for floating-point values
* @return {SVGPathElement}
*/
Path.prototype.toDOMElement = function(decimalPlaces) {
var temporaryPath = this.toPathData(decimalPlaces);
var newPath = document.createElementNS('http://www.w3.org/2000/svg', 'path');
newPath.setAttribute('d', temporaryPath);
return newPath;
};
// Run-time checking of preconditions.
function fail(message) {
throw new Error(message);
}
// Precondition function that checks if the given predicate is true.
// If not, it will throw an error.
function argument(predicate, message) {
if (!predicate) {
fail(message);
}
}
var check = { fail: fail, argument: argument, assert: argument };
// Data types used in the OpenType font file.
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
/**
* @exports opentype.decode
* @class
*/
var decode = {};
/**
* @exports opentype.encode
* @class
*/
var encode = {};
/**
* @exports opentype.sizeOf
* @class
*/
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.
* @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.');
}
var b = [];
for (var 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) {
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 = '';
for (var 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 (var i$1 = 0, ii$1 = nibbles.length; i$1 < ii$1; i$1 += 2) {
out.push(parseInt(nibbles.substr(i$1, 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) {
var codePoints = [];
var numChars = numBytes;
for (var 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) {
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.
* @param {string}
* @returns {Array}
*/
encode.UTF16 = function(v) {
var b = [];
for (var i = 0; i < v.length; i += 1) {
var 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
*/
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.
* @param {DataView} dataView
* @param {number} offset
* @param {number} dataLength
* @param {string} encoding
* @returns {string}
*/
decode.MACSTRING = function(dataView, offset, dataLength, encoding) {
var table = eightBitMacEncodings[encoding];
if (table === undefined) {
return undefined;
}
var result = '';
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) {
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.
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'.
* @param {string} str
* @param {string} encoding
* @returns {Array}
*/
encode.MACSTRING = function(str, encoding) {
var table = getMacEncodingTable(encoding);
if (table === undefined) {
return undefined;
}
var result = [];
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;
}
}
result[i] = c;
// result.push(c);
}
return result;
};
/**
* @param {string} str
* @param {string} encoding
* @returns {number}
*/
sizeOf.MACSTRING = function(str, encoding) {
var 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) {
var runLength = 0;
var 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) {
var runLength = 0;
var numDeltas = deltas.length;
var pos = offset;
while (pos < numDeltas && runLength < 64) {
var 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 (var i = offset; i < pos; ++i) {
result.push((deltas[i] + 256) & 0xff);
}
return pos;
}
// Helper for encode.VARDELTAS
function encodeVarDeltaRunAsWords(deltas, offset, result) {
var runLength = 0;
var numDeltas = deltas.length;
var pos = offset;
while (pos < numDeltas && runLength < 64) {
var 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 (var i = offset; i < pos; ++i) {
var 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) {
var pos = 0;
var result = [];
while (pos < deltas.length) {
var 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.
var offset = 1; // First offset is always 1.
var offsets = [offset];
var data = [];
for (var i = 0; i < l.length; i += 1) {
var v = encode.OBJECT(l[i]);
Array.prototype.push.apply(data, v);
offset += v.length;
offsets.push(offset);
}
if (data.length === 0) {
return [0, 0];
}
var encodedOffsets = [];
var offSize = (1 + Math.floor(Math.log(offset) / Math.log(2)) / 8) | 0;
var offsetEncoder = [undefined, encode.BYTE, encode.USHORT, encode.UINT24, encode.ULONG][offSize];
for (var i$1 = 0; i$1 < offsets.length; i$1 += 1) {
var encodedOffset = offsetEncoder(offsets[i$1]);
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) {
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;
};
/**
* @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) {
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.
* @param {Array}
* @returns {Array}
*/
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) {