PageSourceSearch

https://orby-gamma.vercel.app/scripts/vendor/opentype.module.js

js orby-gamma.vercel.app collected 2026-10-03 07:26:56 UTC 464,140 bytes, 14,460 lines download raw bytes

1/**
2 * 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
3 */
4
5/*! https://mths.be/codepointat v0.2.0 by @mathias */
6if (!String.prototype.codePointAt) {
7	(function() {
8		var defineProperty = (function() {
9			// IE 8 only supports `Object.defineProperty` on DOM elements
10			try {
11				var object = {};
12				var $defineProperty = Object.defineProperty;
13				var result = $defineProperty(object, object, object) && $defineProperty;
14			} catch(error) {}
15			return result;
16		}());
17		var codePointAt = function(position) {
18			if (this == null) {
19				throw TypeError();
20			}
21			var string = String(this);
22			var size = string.length;
23			// `ToInteger`
24			var index = position ? Number(position) : 0;
25			if (index != index) { // better `isNaN`
26				index = 0;
27			}
28			// Account for out-of-bounds indices:
29			if (index < 0 || index >= size) {
30				return undefined;
31			}
32			// Get the first code unit
33			var first = string.charCodeAt(index);
34			var second;
35			if ( // check if it’s the start of a surrogate pair
36				first >= 0xD800 && first <= 0xDBFF && // high surrogate
37				size > index + 1 // there is a next code unit
38			) {
39				second = string.charCodeAt(index + 1);
40				if (second >= 0xDC00 && second <= 0xDFFF) { // low surrogate
41					// https://mathiasbynens.be/notes/javascript-encoding#surrogate-formulae
42					return (first - 0xD800) * 0x400 + second - 0xDC00 + 0x10000;
43				}
44			}
45			return first;
46		};
47		if (defineProperty) {
48			defineProperty(String.prototype, 'codePointAt', {
49				'value': codePointAt,
50				'configurable': true,
51				'writable': true
52			});
53		} else {
54			String.prototype.codePointAt = codePointAt;
55		}
56	}());
57}
58
59var TINF_OK = 0;
60var TINF_DATA_ERROR = -3;
61
62function Tree() {
63  this.table = new Uint16Array(16);   /* table of code length counts */
64  this.trans = new Uint16Array(288);  /* code -> symbol translation table */
65}
66
67function Data(source, dest) {
68  this.source = source;
69  this.sourceIndex = 0;
70  this.tag = 0;
71  this.bitcount = 0;
72  
73  this.dest = dest;
74  this.destLen = 0;
75  
76  this.ltree = new Tree();  /* dynamic length/symbol tree */
77  this.dtree = new Tree();  /* dynamic distance tree */
78}
79
80/* --------------------------------------------------- *
vendor: 7,409 bytes, lines 81-381
81 * -- uninitialized global data (static structures) -- *
82 * --------------------------------------------------- */
83
84var sltree = new Tree();
85var sdtree = new Tree();
86
87/* extra bits and base tables for length codes */
88var length_bits = new Uint8Array(30);
89var length_base = new Uint16Array(30);
90
91/* extra bits and base tables for distance codes */
92var dist_bits = new Uint8Array(30);
93var dist_base = new Uint16Array(30);
94
95/* special ordering of code length codes */
96var clcidx = new Uint8Array([
97  16, 17, 18, 0, 8, 7, 9, 6,
98  10, 5, 11, 4, 12, 3, 13, 2,
99  14, 1, 15
100]);
101
102/* used by tinf_decode_trees, avoids allocations every call */
103var code_tree = new Tree();
104var lengths = new Uint8Array(288 + 32);
105
106/* ----------------------- *
107 * -- utility functions -- *
108 * ----------------------- */
109
110/* build extra bits and base tables */
111function tinf_build_bits_base(bits, base, delta, first) {
112  var i, sum;
113
114  /* build bits table */
115  for (i = 0; i < delta; ++i) { bits[i] = 0; }
116  for (i = 0; i < 30 - delta; ++i) { bits[i + delta] = i / delta | 0; }
117
118  /* build base table */
119  for (sum = first, i = 0; i < 30; ++i) {
120    base[i] = sum;
121    sum += 1 << bits[i];
122  }
123}
124
125/* build the fixed huffman trees */
126function tinf_build_fixed_trees(lt, dt) {
127  var i;
128
129  /* build fixed length tree */
130  for (i = 0; i < 7; ++i) { lt.table[i] = 0; }
131
132  lt.table[7] = 24;
133  lt.table[8] = 152;
134  lt.table[9] = 112;
135
136  for (i = 0; i < 24; ++i) { lt.trans[i] = 256 + i; }
137  for (i = 0; i < 144; ++i) { lt.trans[24 + i] = i; }
138  for (i = 0; i < 8; ++i) { lt.trans[24 + 144 + i] = 280 + i; }
139  for (i = 0; i < 112; ++i) { lt.trans[24 + 144 + 8 + i] = 144 + i; }
140
141  /* build fixed distance tree */
142  for (i = 0; i < 5; ++i) { dt.table[i] = 0; }
143
144  dt.table[5] = 32;
145
146  for (i = 0; i < 32; ++i) { dt.trans[i] = i; }
147}
148
149/* given an array of code lengths, build a tree */
150var offs = new Uint16Array(16);
151
152function tinf_build_tree(t, lengths, off, num) {
153  var i, sum;
154
155  /* clear code length count table */
156  for (i = 0; i < 16; ++i) { t.table[i] = 0; }
157
158  /* scan symbol lengths, and sum code length counts */
159  for (i = 0; i < num; ++i) { t.table[lengths[off + i]]++; }
160
161  t.table[0] = 0;
162
163  /* compute offset table for distribution sort */
164  for (sum = 0, i = 0; i < 16; ++i) {
165    offs[i] = sum;
166    sum += t.table[i];
167  }
168
169  /* create code->symbol translation table (symbols sorted by code) */
170  for (i = 0; i < num; ++i) {
171    if (lengths[off + i]) { t.trans[offs[lengths[off + i]]++] = i; }
172  }
173}
174
175/* ---------------------- *
176 * -- decode functions -- *
177 * ---------------------- */
178
179/* get one bit from source stream */
180function tinf_getbit(d) {
181  /* check if tag is empty */
182  if (!d.bitcount--) {
183    /* load next tag */
184    d.tag = d.source[d.sourceIndex++];
185    d.bitcount = 7;
186  }
187
188  /* shift bit out of tag */
189  var bit = d.tag & 1;
190  d.tag >>>= 1;
191
192  return bit;
193}
194
195/* read a num bit value from a stream and add base */
196function tinf_read_bits(d, num, base) {
197  if (!num)
198    { return base; }
199
200  while (d.bitcount < 24) {
201    d.tag |= d.source[d.sourceIndex++] << d.bitcount;
202    d.bitcount += 8;
203  }
204
205  var val = d.tag & (0xffff >>> (16 - num));
206  d.tag >>>= num;
207  d.bitcount -= num;
208  return val + base;
209}
210
211/* given a data stream and a tree, decode a symbol */
212function tinf_decode_symbol(d, t) {
213  while (d.bitcount < 24) {
214    d.tag |= d.source[d.sourceIndex++] << d.bitcount;
215    d.bitcount += 8;
216  }
217  
218  var sum = 0, cur = 0, len = 0;
219  var tag = d.tag;
220
221  /* get more bits while code value is above sum */
222  do {
223    cur = 2 * cur + (tag & 1);
224    tag >>>= 1;
225    ++len;
226
227    sum += t.table[len];
228    cur -= t.table[len];
229  } while (cur >= 0);
230  
231  d.tag = tag;
232  d.bitcount -= len;
233
234  return t.trans[sum + cur];
235}
236
237/* given a data stream, decode dynamic trees from it */
238function tinf_decode_trees(d, lt, dt) {
239  var hlit, hdist, hclen;
240  var i, num, length;
241
242  /* get 5 bits HLIT (257-286) */
243  hlit = tinf_read_bits(d, 5, 257);
244
245  /* get 5 bits HDIST (1-32) */
246  hdist = tinf_read_bits(d, 5, 1);
247
248  /* get 4 bits HCLEN (4-19) */
249  hclen = tinf_read_bits(d, 4, 4);
250
251  for (i = 0; i < 19; ++i) { lengths[i] = 0; }
252
253  /* read code lengths for code length alphabet */
254  for (i = 0; i < hclen; ++i) {
255    /* get 3 bits code length (0-7) */
256    var clen = tinf_read_bits(d, 3, 0);
257    lengths[clcidx[i]] = clen;
258  }
259
260  /* build code length tree */
261  tinf_build_tree(code_tree, lengths, 0, 19);
262
263  /* decode code lengths for the dynamic trees */
264  for (num = 0; num < hlit + hdist;) {
265    var sym = tinf_decode_symbol(d, code_tree);
266
267    switch (sym) {
268      case 16:
269        /* copy previous code length 3-6 times (read 2 bits) */
270        var prev = lengths[num - 1];
271        for (length = tinf_read_bits(d, 2, 3); length; --length) {
272          lengths[num++] = prev;
273        }
274        break;
275      case 17:
276        /* repeat code length 0 for 3-10 times (read 3 bits) */
277        for (length = tinf_read_bits(d, 3, 3); length; --length) {
278          lengths[num++] = 0;
279        }
280        break;
281      case 18:
282        /* repeat code length 0 for 11-138 times (read 7 bits) */
283        for (length = tinf_read_bits(d, 7, 11); length; --length) {
284          lengths[num++] = 0;
285        }
286        break;
287      default:
288        /* values 0-15 represent the actual code lengths */
289        lengths[num++] = sym;
290        break;
291    }
292  }
293
294  /* build dynamic trees */
295  tinf_build_tree(lt, lengths, 0, hlit);
296  tinf_build_tree(dt, lengths, hlit, hdist);
297}
298
299/* ----------------------------- *
300 * -- block inflate functions -- *
301 * ----------------------------- */
302
303/* given a stream and two trees, inflate a block of data */
304function tinf_inflate_block_data(d, lt, dt) {
305  while (1) {
306    var sym = tinf_decode_symbol(d, lt);
307
308    /* check for end of block */
309    if (sym === 256) {
310      return TINF_OK;
311    }
312
313    if (sym < 256) {
314      d.dest[d.destLen++] = sym;
315    } else {
316      var length, dist, offs;
317      var i;
318
319      sym -= 257;
320
321      /* possibly get more bits from length code */
322      length = tinf_read_bits(d, length_bits[sym], length_base[sym]);
323
324      dist = tinf_decode_symbol(d, dt);
325
326      /* possibly get more bits from distance code */
327      offs = d.destLen - tinf_read_bits(d, dist_bits[dist], dist_base[dist]);
328
329      /* copy match */
330      for (i = offs; i < offs + length; ++i) {
331        d.dest[d.destLen++] = d.dest[i];
332      }
333    }
334  }
335}
336
337/* inflate an uncompressed block of data */
338function tinf_inflate_uncompressed_block(d) {
339  var length, invlength;
340  var i;
341  
342  /* unread from bitbuffer */
343  while (d.bitcount > 8) {
344    d.sourceIndex--;
345    d.bitcount -= 8;
346  }
347
348  /* get length */
349  length = d.source[d.sourceIndex + 1];
350  length = 256 * length + d.source[d.sourceIndex];
351
352  /* get one's complement of length */
353  invlength = d.source[d.sourceIndex + 3];
354  invlength = 256 * invlength + d.source[d.sourceIndex + 2];
355
356  /* check length */
357  if (length !== (~invlength & 0x0000ffff))
358    { return TINF_DATA_ERROR; }
359
360  d.sourceIndex += 4;
361
362  /* copy block */
363  for (i = length; i; --i)
364    { d.dest[d.destLen++] = d.source[d.sourceIndex++]; }
365
366  /* make sure we start next block on a byte boundary */
367  d.bitcount = 0;
368
369  return TINF_OK;
370}
371
372/* inflate stream from source to dest */
373function tinf_uncompress(source, dest) {
374  var d = new Data(source, dest);
375  var bfinal, btype, res;
376
377  do {
378    /* read final block flag */
379    bfinal = tinf_getbit(d);
380
381    /* read block type (2 bits) */
382    btype = tinf_read_bits(d, 2, 0);
383
384    /* decompress block */
385    switch (btype) {
386      case 0:
387        /* decompress uncompressed block */
388        res = tinf_inflate_uncompressed_block(d);
389        break;
390      case 1:
391        /* decompress block with fixed huffman trees */
392        res = tinf_inflate_block_data(d, sltree, sdtree);
393        break;
394      case 2:
395        /* decompress block with dynamic huffman trees */
396        tinf_decode_trees(d, d.ltree, d.dtree);
397        res = tinf_inflate_block_data(d, d.ltree, d.dtree);
398        break;
399      default:
400        res = TINF_DATA_ERROR;
401    }
402
403    if (res !== TINF_OK)
404      { throw new Error('Data error'); }
405
406  } while (!bfinal);
407
408  if (d.destLen < d.dest.length) {
409    if (typeof d.dest.slice === 'function')
410      { return d.dest.slice(0, d.destLen); }
411    else
412      { return d.dest.subarray(0, d.destLen); }
413  }
414  
415  return d.dest;
416}
417
418/* -------------------- *
419 * -- initialization -- *
420 * -------------------- */
421
422/* build fixed huffman trees */
423tinf_build_fixed_trees(sltree, sdtree);
424
425/* build extra bits and base tables */
426tinf_build_bits_base(length_bits, length_base, 4, 3);
427tinf_build_bits_base(dist_bits, dist_base, 2, 1);
428
429/* fix a special case */
430length_bits[28] = 0;
431length_base[28] = 258;
432
433var tinyInflate = tinf_uncompress;
434
435// The Bounding Box object
436
437function derive(v0, v1, v2, v3, t) {
438    return Math.pow(1 - t, 3) * v0 +
439        3 * Math.pow(1 - t, 2) * t * v1 +
440        3 * (1 - t) * Math.pow(t, 2) * v2 +
441        Math.pow(t, 3) * v3;
442}
443/**
444 * A bounding box is an enclosing box that describes the smallest measure within which all the points lie.
445 * It is used to calculate the bounding box of a glyph or text path.
446 *
447 * On initialization, x1/y1/x2/y2 will be NaN. Check if the bounding box is empty using `isEmpty()`.
448 *
449 * @exports opentype.BoundingBox
450 * @class
451 * @constructor
452 */
453function BoundingBox() {
454    this.x1 = Number.NaN;
455    this.y1 = Number.NaN;
456    this.x2 = Number.NaN;
457    this.y2 = Number.NaN;
458}
459
460/**
461 * Returns true if the bounding box is empty, that is, no points have been added to the box yet.
462 */
463BoundingBox.prototype.isEmpty = function() {
464    return isNaN(this.x1) || isNaN(this.y1) || isNaN(this.x2) || isNaN(this.y2);
465};
466
467/**
468 * Add the point to the bounding box.
469 * The x1/y1/x2/y2 coordinates of the bounding box will now encompass the given point.
470 * @param {number} x - The X coordinate of the point.
471 * @param {number} y - The Y coordinate of the point.
472 */
473BoundingBox.prototype.addPoint = function(x, y) {
474    if (typeof x === 'number') {
475        if (isNaN(this.x1) || isNaN(this.x2)) {
476            this.x1 = x;
477            this.x2 = x;
478        }
479        if (x < this.x1) {
480            this.x1 = x;
481        }
482        if (x > this.x2) {
483            this.x2 = x;
484        }
485    }
486    if (typeof y === 'number') {
487        if (isNaN(this.y1) || isNaN(this.y2)) {
488            this.y1 = y;
489            this.y2 = y;
490        }
491        if (y < this.y1) {
492            this.y1 = y;
493        }
494        if (y > this.y2) {
495            this.y2 = y;
496        }
497    }
498};
499
500/**
501 * Add a X coordinate to the bounding box.
502 * This extends the bounding box to include the X coordinate.
503 * This function is used internally inside of addBezier.
504 * @param {number} x - The X coordinate of the point.
505 */
506BoundingBox.prototype.addX = function(x) {
507    this.addPoint(x, null);
508};
509
510/**
511 * Add a Y coordinate to the bounding box.
512 * This extends the bounding box to include the Y coordinate.
513 * This function is used internally inside of addBezier.
514 * @param {number} y - The Y coordinate of the point.
515 */
516BoundingBox.prototype.addY = function(y) {
517    this.addPoint(null, y);
518};
519
520/**
521 * Add a Bézier curve to the bounding box.
522 * This extends the bounding box to include the entire Bézier.
523 * @param {number} x0 - The starting X coordinate.
524 * @param {number} y0 - The starting Y coordinate.
525 * @param {number} x1 - The X coordinate of the first control point.
526 * @param {number} y1 - The Y coordinate of the first control point.
527 * @param {number} x2 - The X coordinate of the second control point.
528 * @param {number} y2 - The Y coordinate of the second control point.
529 * @param {number} x - The ending X coordinate.
530 * @param {number} y - The ending Y coordinate.
531 */
532BoundingBox.prototype.addBezier = function(x0, y0, x1, y1, x2, y2, x, y) {
533    // This code is based on http://nishiohirokazu.blogspot.com/2009/06/how-to-calculate-bezier-curves-bounding.html
534    // and https://github.com/icons8/svg-path-bounding-box
535
536    var p0 = [x0, y0];
537    var p1 = [x1, y1];
538    var p2 = [x2, y2];
539    var p3 = [x, y];
540
541    this.addPoint(x0, y0);
542    this.addPoint(x, y);
543
544    for (var i = 0; i <= 1; i++) {
545        var b = 6 * p0[i] - 12 * p1[i] + 6 * p2[i];
546        var a = -3 * p0[i] + 9 * p1[i] - 9 * p2[i] + 3 * p3[i];
547        var c = 3 * p1[i] - 3 * p0[i];
548
549        if (a === 0) {
550            if (b === 0) { continue; }
551            var t = -c / b;
552            if (0 < t && t < 1) {
553                if (i === 0) { this.addX(derive(p0[i], p1[i], p2[i], p3[
553i], t)); }
554                if (i === 1) { this.addY(derive(p0[i], p1[i], p2[i], p3[i], t)); }
555            }
556            continue;
557        }
558
559        var b2ac = Math.pow(b, 2) - 4 * c * a;
560        if (b2ac < 0) { continue; }
561        var t1 = (-b + Math.sqrt(b2ac)) / (2 * a);
562        if (0 < t1 && t1 < 1) {
563            if (i === 0) { this.addX(derive(p0[i], p1[i], p2[i], p3[i], t1)); }
564            if (i === 1) { this.addY(derive(p0[i], p1[i], p2[i], p3[i], t1)); }
565        }
566        var t2 = (-b - Math.sqrt(b2ac)) / (2 * a);
567        if (0 < t2 && t2 < 1) {
568            if (i === 0) { this.addX(derive(p0[i], p1[i], p2[i], p3[i], t2)); }
569            if (i === 1) { this.addY(derive(p0[i], p1[i], p2[i], p3[i], t2)); }
570        }
571    }
572};
573
574/**
575 * Add a quadratic curve to the bounding box.
576 * This extends the bounding box to include the entire quadratic curve.
577 * @param {number} x0 - The starting X coordinate.
578 * @param {number} y0 - The starting Y coordinate.
579 * @param {number} x1 - The X coordinate of the control point.
580 * @param {number} y1 - The Y coordinate of the control point.
581 * @param {number} x - The ending X coordinate.
582 * @param {number} y - The ending Y coordinate.
583 */
584BoundingBox.prototype.addQuad = function(x0, y0, x1, y1, x, y) {
585    var cp1x = x0 + 2 / 3 * (x1 - x0);
586    var cp1y = y0 + 2 / 3 * (y1 - y0);
587    var cp2x = cp1x + 1 / 3 * (x - x0);
588    var cp2y = cp1y + 1 / 3 * (y - y0);
589    this.addBezier(x0, y0, cp1x, cp1y, cp2x, cp2y, x, y);
590};
591
592// Geometric objects
593
594/**
595 * A bézier path containing a set of path commands similar to a SVG path.
596 * Paths can be drawn on a context using `draw`.
597 * @exports opentype.Path
598 * @class
599 * @constructor
600 */
601function Path() {
602    this.commands = [];
603    this.fill = 'black';
604    this.stroke = null;
605    this.strokeWidth = 1;
606}
607
608/**
609 * @param  {number} x
610 * @param  {number} y
611 */
612Path.prototype.moveTo = function(x, y) {
613    this.commands.push({
614        type: 'M',
615        x: x,
616        y: y
617    });
618};
619
620/**
621 * @param  {number} x
622 * @param  {number} y
623 */
624Path.prototype.lineTo = function(x, y) {
625    this.commands.push({
626        type: 'L',
627        x: x,
628        y: y
629    });
630};
631
632/**
633 * Draws cubic curve
634 * @function
635 * curveTo
636 * @memberof opentype.Path.prototype
637 * @param  {number} x1 - x of control 1
638 * @param  {number} y1 - y of control 1
639 * @param  {number} x2 - x of control 2
640 * @param  {number} y2 - y of control 2
641 * @param  {number} x - x of path point
642 * @param  {number} y - y of path point
643 */
644
645/**
646 * Draws cubic curve
647 * @function
648 * bezierCurveTo
649 * @memberof opentype.Path.prototype
650 * @param  {number} x1 - x of control 1
651 * @param  {number} y1 - y of control 1
652 * @param  {number} x2 - x of control 2
653 * @param  {number} y2 - y of control 2
654 * @param  {number} x - x of path point
655 * @param  {number} y - y of path point
656 * @see curveTo
657 */
658Path.prototype.curveTo = Path.prototype.bezierCurveTo = function(x1, y1, x2, y2, x, y) {
659    this.commands.push({
660        type: 'C',
661        x1: x1,
662        y1: y1,
663        x2: x2,
664        y2: y2,
665        x: x,
666        y: y
667    });
668};
669
670/**
671 * Draws quadratic curve
672 * @function
673 * quadraticCurveTo
674 * @memberof opentype.Path.prototype
675 * @param  {number} x1 - x of control
676 * @param  {number} y1 - y of control
677 * @param  {number} x - x of path point
678 * @param  {number} y - y of path point
679 */
680
681/**
682 * Draws quadratic curve
683 * @function
684 * quadTo
685 * @memberof opentype.Path.prototype
686 * @param  {number} x1 - x of control
687 * @param  {number} y1 - y of control
688 * @param  {number} x - x of path point
689 * @param  {number} y - y of path point
690 */
691Path.prototype.quadTo = Path.prototype.quadraticCurveTo = function(x1, y1, x, y) {
692    this.commands.push({
693        type: 'Q',
694        x1: x1,
695        y1: y1,
696        x: x,
697        y: y
698    });
699};
700
701/**
702 * Closes the path
703 * @function closePath
704 * @memberof opentype.Path.prototype
705 */
706
707/**
708 * Close the path
709 * @function close
710 * @memberof opentype.Path.prototype
711 */
712Path.prototype.close = Path.prototype.closePath = function() {
713    this.commands.push({
714        type: 'Z'
715    });
716};
717
718/**
719 * Add the given path or list of commands to the commands of this path.
720 * @param  {Array} pathOrCommands - another opentype.Path, an opentype.BoundingBox, or an array of commands.
721 */
722Path.prototype.extend = function(pathOrCommands) {
723    if (pathOrCommands.commands) {
724        pathOrCommands = pathOrCommands.commands;
725    } else if (pathOrCommands instanceof BoundingBox) {
726        var box = pathOrCommands;
727        this.moveTo(box.x1, box.y1);
728        this.lineTo(box.x2, box.y1);
729        this.lineTo(box.x2, box.y2);
730        this.lineTo(box.x1, box.y2);
731        this.close();
732        return;
733    }
734
735    Array.prototype.push.apply(this.commands, pathOrCommands);
736};
737
738/**
739 * Calculate the bounding box of the path.
740 * @returns {opentype.BoundingBox}
741 */
742Path.prototype.getBoundingBox = function() {
743    var box = new BoundingBox();
744
745    var startX = 0;
746    var startY = 0;
747    var prevX = 0;
748    var prevY = 0;
749    for (var i = 0; i < this.commands.length; i++) {
750        var cmd = this.commands[i];
751        switch (cmd.type) {
752            case 'M':
753                box.addPoint(cmd.x, cmd.y);
754                startX = prevX = cmd.x;
755                startY = prevY = cmd.y;
756                break;
757            case 'L':
758                box.addPoint(cmd.x, cmd.y);
759                prevX = cmd.x;
760                prevY = cmd.y;
761                break;
762            case 'Q':
763                box.addQuad(prevX, prevY, cmd.x1, cmd.y1, cmd.x, cmd.y);
764                prevX = cmd.x;
765                prevY = cmd.y;
766                break;
767            case 'C':
768                box.addBezier(prevX, prevY, cmd.x1, cmd.y1, cmd.x2, cmd.y2, cmd.x, cmd.y);
769                prevX = cmd.x;
770                prevY = cmd.y;
771                break;
772            case 'Z':
773                prevX = startX;
774                prevY = startY;
775                break;
776            default:
777                throw new Error('Unexpected path command ' + cmd.type);
778        }
779    }
780    if (box.isEmpty()) {
781        box.addPoint(0, 0);
782    }
783    return box;
784};
785
786/**
787 * Draw the path to a 2D context.
788 * @param {CanvasRenderingContext2D} ctx - A 2D drawing context.
789 */
790Path.prototype.draw = function(ctx) {
791    ctx.beginPath();
792    for (var i = 0; i < this.commands.length; i += 1) {
793        var cmd = this.commands[i];
794        if (cmd.type === 'M') {
795            ctx.moveTo(cmd.x, cmd.y);
796        } else if (cmd.type === 'L') {
797            ctx.lineTo(cmd.x, cmd.y);
798        } else if (cmd.type === 'C') {
799            ctx.bezierCurveTo(cmd.x1, cmd.y1, cmd.x2, cmd.y2, cmd.x, cmd.y);
800        } else if (cmd.type === 'Q') {
801            ctx.quadraticCurveTo(cmd.x1, cmd.y1, cmd.x, cmd.y);
802        } else if (cmd.type === 'Z') {
803            ctx.closePath();
804        }
805    }
806
807    if (this.fill) {
808        ctx.fillStyle = this.fill;
809        ctx.fill();
810    }
811
812    if (this.stroke) {
813        ctx.strokeStyle = this.stroke;
814        ctx.lineWidth = this.strokeWidth;
815        ctx.stroke();
816    }
817};
818
819/**
820 * Convert the Path to a string of path data instructions
821 * See http://www.w3.org/TR/SVG/paths.html#PathData
822 * @param  {number} [decimalPlaces=2] - The amount of decimal places for floating-point values
823 * @return {string}
824 */
825Path.prototype.toPathData = function(decimalPlaces) {
826    decimalPlaces = decimalPlaces !== undefined ? decimalPlaces : 2;
827
828    function floatToString(v) {
829        if (Math.round(v) === v) {
830            return '' + Math.round(v);
831        } else {
832            return v.toFixed(decimalPlaces);
833        }
834    }
835
836    function packValues() {
837        var arguments$1 = arguments;
838
839        var s = '';
840        for (var i = 0; i < arguments.length; i += 1) {
841            var v = arguments$1[i];
842            if (v >= 0 && i > 0) {
843                s += ' ';
844            }
845
846            s += floatToString(v);
847        }
848
849        return s;
850    }
851
852    var d = '';
853    for (var i = 0; i < this.commands.length; i += 1) {
854        var cmd = this.commands[i];
855        if (cmd.type === 'M') {
856            d += 'M' + packValues(cmd.x, cmd.y);
857        } else if (cmd.type === 'L') {
858            d += 'L' + packValues(cmd.x, cmd.y);
859        } else if (cmd.type === 'C') {
860            d += 'C' + packValues(cmd.x1, cmd.y1, cmd.x2, cmd.y2, cmd.x, cmd.y);
861        } else if (cmd.type === 'Q') {
862            d += 'Q' + packValues(cmd.x1, cmd.y1, cmd.x, cmd.y);
863        } else if (cmd.type === 'Z') {
864            d += 'Z';
865        }
866    }
867
868    return d;
869};
870
871/**
872 * Convert the path to an SVG <path> element, as a string.
873 * @param  {number} [decimalPlaces=2] - The amount of decimal places for floating-point values
874 * @return {string}
875 */
876Path.prototype.toSVG = function(decimalPlaces) {
877    var svg = '<path d="';
878    svg += this.toPathData(decimalPlaces);
879    svg += '"';
880    if (this.fill && this.fill !== 'black') {
881        if (this.fill === null) {
882            svg += ' fill="none"';
883        } else {
884            svg += ' fill="' + this.fill + '"';
885        }
886    }
887
888    if (this.stroke) {
889        svg += ' stroke="' + this.stroke + '" stroke-width="' + this.strokeWidth + '"';
890    }
891
892    svg += '/>';
893    return svg;
894};
895
896/**
897 * Convert the path to a DOM element.
898 * @param  {number} [decimalPlaces=2] - The amount of decimal places for floating-point values
899 * @return {SVGPathElement}
900 */
901Path.prototype.toDOMElement = function(decimalPlaces) {
902    var temporaryPath = this.toPathData(decimalPlaces);
903    var newPath = document.createElementNS('http://www.w3.org/2000/svg', 'path');
904
905    newPath.setAttribute('d', temporaryPath);
906
907    return newPath;
908};
909
910// Run-time checking of preconditions.
911
912function fail(message) {
913    throw new Error(message);
914}
915
916// Precondition function that checks if the given predicate is true.
917// If not, it will throw an error.
918function argument(predicate, message) {
919    if (!predicate) {
920        fail(message);
921    }
922}
923var check = { fail: fail, argument: argument, assert: argument };
924
925// Data types used in the OpenType font file.
926
927var LIMIT16 = 32768; // The limit at which a 16-bit number switches signs == 2^15
928var LIMIT32 = 2147483648; // The limit at which a 32-bit number switches signs == 2 ^ 31
929
930/**
931 * @exports opentype.decode
932 * @class
933 */
934var decode = {};
935/**
936 * @exports opentype.encode
937 * @class
938 */
939var encode = {};
940/**
941 * @exports opentype.sizeOf
942 * @class
943 */
944var sizeOf = {};
945
946// Return a function that always returns the same value.
947function constant(v) {
948    return function() {
949        return v;
950    };
951}
952
953// OpenType data types //////////////////////////////////////////////////////
954
955/**
956 * Convert an 8-bit unsigned integer to a list of 1 byte.
957 * @param {number}
958 * @returns {Array}
959 */
960encode.BYTE = function(v) {
961    check.argument(v >= 0 && v <= 255, 'Byte value should be between 0 and 255.');
962    return [v];
963};
964/**
965 * @constant
966 * @type {number}
967 */
968sizeOf.BYTE = constant(1);
969
970/**
971 * Convert a 8-bit signed integer to a list of 1 byte.
972 * @param {string}
973 * @returns {Array}
974 */
975encode.CHAR = function(v) {
976    return [v.charCodeAt(0)];
977};
978
979/**
980 * @constant
981 * @type {number}
982 */
983sizeOf.CHAR = constant(1);
984
985/**
986 * Convert an ASCII string to a list of bytes.
987 * @param {string}
988 * @returns {Array}
989 */
990encode.CHARARRAY = function(v) {
991    if (typeof v === 'undefined') {
992        v = '';
993        console.warn('Undefined CHARARRAY encountered and treated as an empty string. This is probably caused by a missing glyph name.');
994    }
995    var b = [];
996    for (var i = 0; i < v.length; i += 1) {
997        b[i] = v.charCodeAt(i);
998    }
999
1000    return b;
1001};
1002
1003/**
1004 * @param {Array}
1005 * @returns {number}
1006 */
1007sizeOf.CHARARRAY = function(v) {
1008    if (typeof v === 'undefined') {
1009        return 0;
1010    }
1011    return v.length;
1012};
1013
1014/**
1015 * Convert a 16-bit unsigned integer to a list of 2 bytes.
1016 * @param {number}
1017 * @returns {Array}
1018 */
1019encode.USHORT = function(v) {
1020    return [(v >> 8) & 0xFF, v & 0xFF];
1021};
1022
1023/**
1024 * @constant
1025 * @type {number}
1026 */
1027sizeOf.USHORT = constant(2);
1028
1029/**
1030 * Convert a 16-bit signed integer to a list of 2 bytes.
1031 * @param {number}
1032 * @returns {Array}
1033 */
1034encode.SHORT = function(v) {
1035    // Two's complement
1036    if (v >= LIMIT16) {
1037        v = -(2 * LIMIT16 - v);
1038    }
1039
1040    return [(v >> 8) & 0xFF, v & 0xFF];
1041};
1042
1043/**
1044 * @constant
1045 * @type {number}
1046 */
1047sizeOf.SHORT = constant(2);
1048
1049/**
1050 * Convert a 24-bit unsigned integer to a list of 3 bytes.
1051 * @param {number}
1052 * @returns {Array}
1053 */
1054encode.UINT24 = function(v) {
1055    return [(v >> 16) & 0xFF, (v >> 8) & 0xFF, v & 0xFF];
1056};
1057
1058/**
1059 * @constant
1060 * @type {number}
1061 */
1062sizeOf.UINT24 = constant(3);
1063
1064/**
1065 * Convert a 32-bit unsigned integer to a list of 4 bytes.
1066 * @param {number}
1067 * @returns {Array}
1068 */
1069encode.ULONG = function(v) {
1070    return [(v >> 24) & 0xFF, (v >> 16) & 0xFF, (v >> 8) & 0xFF, v & 0xFF];
1071};
1072
1073/**
1074 * @constant
1075 * @type {number}
1076 */
1077sizeOf.ULONG = constant(4);
1078
1079/**
1080 * Convert a 32-bit unsigned integer to a list of 4 bytes.
1081 * @param {number}
1082 * @returns {Array}
1083 */
1084encode.LONG = function(v) {
1085    // Two's complement
1086    if (v >= LIMIT32) {
1087        v = -(2 * LIMIT32 - v);
1088    }
1089
1090    return [(v >> 24) & 0xFF, (v >> 16) & 0xFF, (v >> 8) & 0xFF, v & 0xFF];
1091};
1092
1093/**
1094 * @constant
1095 * @type {number}
1096 */
1097sizeOf.LONG = constant(4);
1098
1099encode.FIXED = encode.ULONG;
1100sizeOf.FIXED = sizeOf.ULONG;
1101
1102encode.FWORD = encode.SHORT;
1103sizeOf.FWORD = sizeOf.SHORT;
1104
1105encode.UFWORD = encode.USHORT;
1106sizeOf.UFWORD = sizeOf.USHORT;
1107
1108/**
1109 * Convert a 32-bit Apple Mac timestamp integer to a list of 8 bytes, 64-bit timestamp.
1110 * @param {number}
1111 * @returns {Array}
1112 */
1113encode.LONGDATETIME = function(v) {
1114    return [0, 0, 0, 0, (v >> 24) & 0xFF, (v >> 16) & 0xFF, (v >> 8) & 0xFF, v & 0xFF];
1115};
1116
1117/**
1118 * @constant
1119 * @type {number}
1120 */
1121sizeOf.LONGDATETIME = constant(8);
1122
1123/**
1124 * Convert a 4-char tag to a list of 4 bytes.
1125 * @param {string}
1126 * @returns {Array}
1127 */
1128encode.TAG = function(v) {
1129    check.argument(v.length === 4, 'Tag should be exactly 4 ASCII characters.');
1130    return [v.charCodeAt(0),
1131            v.charCodeAt(1),
1132            v.charCodeAt(2),
1133            v.charCodeAt(3)];
1134};
1135
1136/**
1137 * @constant
1138 * @type {number}
1139 */
1140sizeOf.TAG = constant(4);
1141
1142// CFF data types ///////////////////////////////////////////////////////////
1143
1144encode.Card8 = encode.BYTE;
1145sizeOf.Card8 = sizeOf.BYTE;
1146
1147encode.Card16 = encode.USHORT;
1148sizeOf.Card16 = sizeOf.USHORT;
1149
1150encode.OffSize = encode.BYTE;
1151sizeOf.OffSize = sizeOf.BYTE;
1152
1153encode.SID = encode.USHORT;
1154sizeOf.SID = sizeOf.USHORT;
1155
1156// Convert a numeric operand or charstring number to a variable-size list of bytes.
1157/**
1158 * Convert a numeric operand or charstring number to a variable-size list of bytes.
1159 * @param {number}
1160 * @returns {Array}
1161 */
1162encode.NUMBER = function(v) {
1163    if (v >= -107 && v <= 107) {
1164        return [v + 139];
1165    } else if (v >= 108 && v <= 1131) {
1166        v = v - 108;
1167        return [(v >> 8) + 247, v & 0xFF];
1168    } else if (v >= -1131 && v <= -108) {
1169        v = -v - 108;
1170        return [(v >> 8) + 251, v & 0xFF];
1171    } else if (v >= -32768 && v <= 32767) {
1172        return encode.NUMBER16(v);
1173    } else {
1174        return encode.NUMBER32(v);
1175    }
1176};
1177
1178/**
1179 * @param {number}
1180 * @returns {number}
1181 */
1182sizeOf.NUMBER = function(v) {
1183    return encode.NUMBER(v).length;
1184};
1185
1186/**
1187 * Convert a signed number between -32768 and +32767 to a three-byte value.
1188 * This ensures we always use three bytes, but is not the most compact format.
1189 * @param {number}
1190 * @returns {Array}
1191 */
1192encode.NUMBER16 = function(v) {
1193    return [28, (v >> 8) & 0xFF, v & 0xFF];
1194};
1195
1196/**
1197 * @constant
1198 * @type {number}
1199 */
1200sizeOf.NUMBER16 = constant(3);
1201
1202/**
1203 * Convert a signed number between -(2^31) and +(2^31-1) to a five-byte value.
1204 * This is useful if you want to be sure you always use four bytes,
1205 * at the expense of wasting a few bytes for smaller numbers.
1206 * @param {number}
1207 * @returns {Array}
1208 */
1209encode.NUMBER32 = function(v) {
1210    return [29, (v >> 24) & 0xFF, (v >> 16) & 0xFF, (v >> 8) & 0xFF, v & 0xFF];
1211};
1212
1213/**
1214 * @constant
1215 * @type {number}
1216 */
1217sizeOf.NUMBER32 = constant(5);
1218
1219/**
1220 * @param {number}
1221 * @returns {Array}
1222 */
1223encode.REAL = function(v) {
1224    var value = v.toString();
1225
1226    // Some numbers use an epsilon to encode the value. (e.g. JavaScript will store 0.0000001 as 1e-7)
1227    // This code converts it back to a number without the epsilon.
1228    var m = /\.(\d*?)(?:9{5,20}|0{5,20})\d{0,2}(?:e(.+)|$)/.exec(value);
1229    if (m) {
1230        var epsilon = parseFloat('1e' + ((m[2] ? +m[2] : 0) + m[1].length));
1231        value = (Math.round(v * epsilon) / epsilon).toString();
1232    }
1233
1234    var nibbles = '';
1235    for (var i = 0, ii = value.length; i < ii; i += 1) {
1236        var c = value[i];
1237        if (c === 'e') {
1238            nibbles += value[++i] === '-' ? 'c' : 'b';
1239        } else if (c === '.') {
1240            nibbles += 'a';
1241        } else if (c === '-') {
1242            nibbles += 'e';
1243        } else {
1244            nibbles += c;
1245        }
1246    }
1247
1248    nibbles += (nibbles.length & 1) ? 'f' : 'ff';
1249    var out = [30];
1250    for (var i$1 = 0, ii$1 = nibbles.length; i$1 < ii$1; i$1 += 2) {
1251        out.push(parseInt(nibbles.substr(i$1, 2), 16));
1252    }
1253
1254    return out;
1255};
1256
1257/**
1258 * @param {number}
1259 * @returns {number}
1260 */
1261sizeOf.REAL = function(v) {
1262    return encode.REAL(v).length;
1263};
1264
1265encode.NAME = encode.CHARARRAY;
1266sizeOf.NAME = sizeOf.CHARARRAY;
1267
1268encode.STRING = encode.CHARARRAY;
1269sizeOf.STRING = sizeOf.CHARARRAY;
1270
1271/**
1272 * @param {DataView} data
1273 * @param {number} offset
1274 * @param {number} numBytes
1275 * @returns {string}
1276 */
1277decode.UTF8 = function(data, offset, numBytes) {
1278    var codePoints = [];
1279    var numChars = numBytes;
1280    for (var j = 0; j < numChars; j++, offset += 1) {
1281        codePoints[j] = data.getUint8(offset);
1282    }
1283
1284    return String.fromCharCode.apply(null, codePoints);
1285};
1286
1287/**
1288 * @param {DataView} data
1289 * @param {number} offset
1290 * @param {number} numBytes
1291 * @returns {string}
1292 */
1293decode.UTF16 = function(data, offset, numBytes) {
1294    var codePoints = [];
1295    var numChars = numBytes / 2;
1296    for (var j = 0; j < numChars; j++, offset += 2) {
1297        codePoints[j] = data.getUint16(offset);
1298    }
1299
1300    return String.fromCharCode.apply(null, codePoints);
1301};
1302
1303/**
1304 * Convert a JavaScript string to UTF16-BE.
1305 * @param {string}
1306 * @returns {Array}
1307 */
1308encode.UTF16 = function(v) {
1309    var b = [];
1310    for (var i = 0; i < v.length; i += 1) {
1311        var codepoint = v.charCodeAt(i);
1312        b[b.length] = (codepoint >> 8) & 0xFF;
1313        b[b.length] = codepoint & 0xFF;
1314    }
1315
1316    return b;
1317};
1318
1319/**
1320 * @param {string}
1321 * @returns {number}
1322 */
1323sizeOf.UTF16 = function(v) {
1324    return v.length * 2;
1325};
1326
1327// Data for converting old eight-bit Macintosh encodings to Unicode.
1328// This representation is optimized for decoding; encoding is slower
1329// and needs more memory. The assumption is that all opentype.js users
1330// want to open fonts, but saving a font will be comparatively rare
1331// so it can be more expensive. Keyed by IANA character set name.
1332//
1333// Python script for generating these strings:
1334//
1335//     s = u''.join([chr(c).decode('mac_greek') for c in range(128, 256)])
1336//     print(s.encode('utf-8'))
1337/**
1338 * @private
1339 */
1340var eightBitMacEncodings = {
1341    'x-mac-croatian':  // Python: 'mac_croatian'
1342    'ÄÅÇÉÑÖÜáàâäãåçéèêëíìîïñóòôöõúùûü†°¢£§•¶ß®Š™´¨≠ŽØ∞±≤≥∆µ∂∑∏š∫ªºΩžø' +
1343    '¿¡¬√ƒ≈ƫȅ ÀÃÕŒœĐ—“”‘’÷◊©⁄€‹›Æ»–·‚„‰ÂćÁčÈÍÎÏÌÓÔđÒÚÛÙıˆ˜¯πË˚¸Êæˇ',
1344    'x-mac-cyrillic':  // Python: 'mac_cyrillic'
1345    'АБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ†°Ґ£§•¶І®©™Ђђ≠Ѓѓ∞±≤≥іµґЈЄєЇїЉљЊњ' +
1346    'јЅ¬√ƒ≈∆«»… ЋћЌќѕ–—“”‘’÷„ЎўЏџ№Ёёяабвгдежзийклмнопрстуфхцчшщъыьэю',
1347    'x-mac-gaelic': // http://unicode.org/Public/MAPPINGS/VENDORS/APPLE/GAELIC.TXT
1348    'ÄÅÇÉÑÖÜáàâäãåçéèêëíìîïñóòôöõúùûü†°¢£§•¶ß®©™´¨≠ÆØḂ±≤≥ḃĊċḊḋḞḟĠġṀæø' +
1349    'ṁṖṗɼƒſṠ«»… ÀÃÕŒœ–—“”‘’ṡẛÿŸṪ€‹›Ŷŷṫ·Ỳỳ⁊ÂÊÁËÈÍÎÏÌÓÔ♣ÒÚÛÙıÝýŴŵẄẅẀẁẂẃ',
1350    'x-mac-greek':  // Python: 'mac_greek'
1351    'Ĺ²É³ÖÜ΅àâä΄¨çéèê룙î‰ôö¦€ùûü†ΓΔΘΛΞΠß®©ΣΪ§≠°·Α±≤≥¥ΒΕΖΗΙΚΜΦΫΨΩ' +
1352    'άΝ¬ΟΡ≈Τ«»… ΥΧΆΈœ–―“”‘’÷ΉΊΌΎέήίόΏύαβψδεφγηιξκλμνοπώρστθωςχυζϊϋΐΰ\u00AD',
1353    'x-mac-icelandic':  // Python: 'mac_iceland'
1354    'ÄÅÇÉÑÖÜáàâäãåçéèêëíìîïñóòôöõúùûüݰ¢£§•¶ß®©™´¨≠ÆØ∞±≤≥¥µ∂∑∏π∫ªºΩæø' +
1355    '¿¡¬√ƒ≈∆«»… ÀÃÕŒœ–—“”‘’÷◊ÿŸ⁄€ÐðÞþý·‚„‰ÂÊÁËÈÍÎÏÌÓÔÒÚÛÙıˆ˜¯˘˙˚¸˝˛ˇ',
1356    'x-mac-inuit': // http://unicode.org/Public/MAPPINGS/VENDORS/APPLE/INUIT.TXT
1357    'ᐃᐄᐅᐆᐊᐋᐱᐲᐳᐴᐸᐹᑉᑎᑏᑐᑑᑕᑖᑦᑭᑮᑯᑰᑲᑳᒃᒋᒌᒍᒎᒐᒑ°ᒡᒥᒦ•¶ᒧ®©™ᒨᒪᒫᒻᓂᓃᓄᓅᓇᓈᓐᓯᓰᓱᓲᓴᓵᔅᓕᓖᓗ' +
1358    'ᓘᓚᓛᓪᔨᔩᔪᔫᔭ… ᔮᔾᕕᕖᕗ–—“”‘’ᕘᕙᕚᕝᕆᕇᕈᕉᕋᕌᕐᕿᖀᖁᖂᖃᖄᖅᖏᖐᖑᖒᖓᖔᖕᙱᙲᙳᙴᙵᙶᖖᖠᖡᖢᖣᖤᖥᖦᕼŁł',
1359    'x-mac-ce':  // Python: 'mac_latin2'
1360    'ÄĀāÉĄÖÜáąČäčĆć鏟ĎíďĒēĖóėôöõúĚěü†°Ę£§•¶ß®©™ę¨≠ģĮįĪ≤≥īĶ∂∑łĻļĽľĹĺŅ' +
1361    'ņѬ√ńŇ∆«»… ňŐÕőŌ–—“”‘’÷◊ōŔŕŘ‹›řŖŗŠ‚„šŚśÁŤťÍŽžŪÓÔūŮÚůŰűŲųÝýķŻŁżĢˇ',
1362    macintosh:  // Python: 'mac_roman'
1363    'ÄÅÇÉÑÖÜáàâäãåçéèêëíìîïñóòôöõúùûü†°¢£§•¶ß®©™´¨≠ÆØ∞±≤≥¥µ∂∑∏π∫ªºΩæø' +
1364    '¿¡¬√ƒ≈∆«»… ÀÃÕŒœ–—“”‘’÷◊ÿŸ⁄€‹›fifl‡·‚„‰ÂÊÁËÈÍÎÏÌÓÔÒÚÛÙıˆ˜¯˘˙˚¸˝˛ˇ',
1365    'x-mac-romanian':  // Python: 'mac_romanian'
1366    'ÄÅÇÉÑÖÜáàâäãåçéèêëíìîïñóòôöõúùûü†°¢£§•¶ß®©™´¨≠ĂȘ∞±≤≥¥µ∂∑∏π∫ªºΩăș' +
1367    '¿¡¬√ƒ≈∆«»… ÀÃÕŒœ–—“”‘’÷◊ÿŸ⁄€‹›Țț‡·‚„‰ÂÊÁËÈÍÎÏÌÓÔÒÚÛÙıˆ˜¯˘˙˚¸˝˛ˇ',
1368    'x-mac-turkish':  // Python: 'mac_turkish'
1369    'ÄÅÇÉÑÖÜáàâäãåçéèêëíìîïñóòôöõúùûü†°¢£§•¶ß®©™´¨≠ÆØ∞±≤≥¥µ∂∑∏π∫ªºΩæø' +
1370    '¿¡¬√ƒ≈∆«»… ÀÃÕŒœ–—“”‘’÷◊ÿŸĞğİıŞş‡·‚„‰ÂÊÁËÈÍÎÏÌÓÔÒÚÛÙˆ˜¯˘˙˚¸˝˛ˇ'
1371};
1372
1373/**
1374 * Decodes an old-style Macintosh string. Returns either a Unicode JavaScript
1375 * string, or 'undefined' if the encoding is unsupported. For example, we do
1376 * not support Chinese, Japanese or Korean because these would need large
1377 * mapping tables.
1378 * @param {DataView} dataView
1379 * @param {number} offset
1380 * @param {number} dataLength
1381 * @param {string} encoding
1382 * @returns {string}
1383 */
1384decode.MACSTRING = function(dataView, offset, dataLength, encoding) {
1385    var table = eightBitMacEncodings[encoding];
1386    if (table === undefined) {
1387        return undefined;
1388    }
1389
1390    var result = '';
1391    for (var i = 0; i < dataLength; i++) {
1392        var c = dataView.getUint8(offset + i);
1393        // In all eight-bit Mac encodings, the characters 0x00..0x7F are
1394        // mapped to U+0000..U+007F; we only need to look up the others.
1395        if (c <= 0x7F) {
1396            result += String.fromCharCode(c);
1397        } else {
1398            result += table[c & 0x7F];
1399        }
1400    }
1401
1402    return result;
1403};
1404
1405// Helper function for encode.MACSTRING. Returns a dictionary for mapping
1406// Unicode character codes to their 8-bit MacOS equivalent. This table
1407// is not exactly a super cheap data structure, but we do not care because
1408// encoding Macintosh strings is only rarely needed in typical applications.
1409var macEncodingTableCache = typeof WeakMap === 'function' && new WeakMap();
1410var macEncodingCacheKeys;
1411var getMacEncodingTable = function (encoding) {
1412    // Since we use encoding as a cache key for WeakMap, it has to be
1413    // a String object and not a literal. And at least on NodeJS 2.10.1,
1414    // WeakMap requires that the same String instance is passed for cache hits.
1415    if (!macEncodingCacheKeys) {
1416        macEncodingCacheKeys = {};
1417        for (var e in eightBitMacEncodings) {
1418            /*jshint -W053 */  // Suppress "Do not use String as a constructor."
1419            macEncodingCacheKeys[e] = new String(e);
1420        }
1421    }
1422
1423    var cacheKey = macEncodingCacheKeys[encoding];
1424    if (cacheKey === undefined) {
1425        return undefined;
1426    }
1427
1428    // We can't do "if (cache.has(key)) {return cache.get(key)}" here:
1429    // since garbage collection may run at any time, it could also kick in
1430    // between the calls to cache.has() and cache.get(). In that case,
1431    // we would return 'undefined' even though we do support the encoding.
1432    if (macEncodingTableCache) {
1433        var cachedTable = macEncodingTableCache.get(cacheKey);
1434        if (cachedTable !== undefined) {
1435            return cachedTable;
1436        }
1437    }
1438
1439    var decodingTable = eightBitMacEncodings[encoding];
1440    if (decodingTable === undefined) {
1441        return undefined;
1442    }
1443
1444    var encodingTable = {};
1445    for (var i = 0; i < decodingTable.length; i++) {
1446        encodingTable[decodingTable.charCodeAt(i)] = i + 0x80;
1447    }
1448
1449    if (macEncodingTableCache) {
1450        macEncodingTableCache.set(cacheKey, encodingTable);
1451    }
1452
1453    return encodingTable;
1454};
1455
1456/**
1457 * Encodes an old-style Macintosh string. Returns a byte array upon success.
1458 * If the requested encoding is unsupported, or if the input string contains
1459 * a character that cannot be expressed in the encoding, the function returns
1460 * 'undefined'.
1461 * @param {string} str
1462 * @param {string} encoding
1463 * @returns {Array}
1464 */
1465encode.MACSTRING = function(str, encoding) {
1466    var table = getMacEncodingTable(encoding);
1467    if (table === undefined) {
1468        return undefined;
1469    }
1470
1471    var result = [];
1472    for (var i = 0; i < str.length; i++) {
1473        var c = str.charCodeAt(i);
1474
1475        // In all eight-bit Mac encodings, the characters 0x00..0x7F are
1476        // mapped to U+0000..U+007F; we only need to look up the others.
1477        if (c >= 0x80) {
1478            c = table[c];
1479            if (c === undefined) {
1480                // str contains a Unicode character that cannot be encoded
1481                // in the requested encoding.
1482                return undefined;
1483            }
1484        }
1485        result[i] = c;
1486        // result.push(c);
1487    }
1488
1489    return result;
1490};
1491
1492/**
1493 * @param {string} str
1494 * @param {string} encoding
1495 * @returns {number}
1496 */
1497sizeOf.MACSTRING = function(str, encoding) {
1498    var b = encode.MACSTRING(str, encoding);
1499    if (b !== undefined) {
1500        return b.length;
1501    } else {
1502        return 0;
1503    }
1504};
1505
1506// Helper for encode.VARDELTAS
1507function isByteEncodable(value) {
1508    return value >= -128 && value <= 127;
1509}
1510
1511// Helper for encode.VARDELTAS
1512function encodeVarDeltaRunAsZeroes(deltas, pos, result) {
1513    var runLength = 0;
1514    var numDeltas = deltas.length;
1515    while (pos < numDeltas && runLength < 64 && deltas[pos] === 0) {
1516        ++pos;
1517        ++runLength;
1518    }
1519    result.push(0x80 | (runLength - 1));
1520    return pos;
1521}
1522
1523// Helper for encode.VARDELTAS
1524function encodeVarDeltaRun
1524AsBytes(deltas, offset, result) {
1525    var runLength = 0;
1526    var numDeltas = deltas.length;
1527    var pos = offset;
1528    while (pos < numDeltas && runLength < 64) {
1529        var value = deltas[pos];
1530        if (!isByteEncodable(value)) {
1531            break;
1532        }
1533
1534        // Within a byte-encoded run of deltas, a single zero is best
1535        // stored literally as 0x00 value. However, if we have two or
1536        // more zeroes in a sequence, it is better to start a new run.
1537        // Fore example, the sequence of deltas [15, 15, 0, 15, 15]
1538        // becomes 6 bytes (04 0F 0F 00 0F 0F) when storing the zero
1539        // within the current run, but 7 bytes (01 0F 0F 80 01 0F 0F)
1540        // when starting a new run.
1541        if (value === 0 && pos + 1 < numDeltas && deltas[pos + 1] === 0) {
1542            break;
1543        }
1544
1545        ++pos;
1546        ++runLength;
1547    }
1548    result.push(runLength - 1);
1549    for (var i = offset; i < pos; ++i) {
1550        result.push((deltas[i] + 256) & 0xff);
1551    }
1552    return pos;
1553}
1554
1555// Helper for encode.VARDELTAS
1556function encodeVarDeltaRunAsWords(deltas, offset, result) {
1557    var runLength = 0;
1558    var numDeltas = deltas.length;
1559    var pos = offset;
1560    while (pos < numDeltas && runLength < 64) {
1561        var value = deltas[pos];
1562
1563        // Within a word-encoded run of deltas, it is easiest to start
1564        // a new run (with a different encoding) whenever we encounter
1565        // a zero value. For example, the sequence [0x6666, 0, 0x7777]
1566        // needs 7 bytes when storing the zero inside the current run
1567        // (42 66 66 00 00 77 77), and equally 7 bytes when starting a
1568        // new run (40 66 66 80 40 77 77).
1569        if (value === 0) {
1570            break;
1571        }
1572
1573        // Within a word-encoded run of deltas, a single value in the
1574        // range (-128..127) should be encoded within the current run
1575        // because it is more compact. For example, the sequence
1576        // [0x6666, 2, 0x7777] becomes 7 bytes when storing the value
1577        // literally (42 66 66 00 02 77 77), but 8 bytes when starting
1578        // a new run (40 66 66 00 02 40 77 77).
1579        if (isByteEncodable(value) && pos + 1 < numDeltas && isByteEncodable(deltas[pos + 1])) {
1580            break;
1581        }
1582
1583        ++pos;
1584        ++runLength;
1585    }
1586    result.push(0x40 | (runLength - 1));
1587    for (var i = offset; i < pos; ++i) {
1588        var val = deltas[i];
1589        result.push(((val + 0x10000) >> 8) & 0xff, (val + 0x100) & 0xff);
1590    }
1591    return pos;
1592}
1593
1594/**
1595 * Encode a list of variation adjustment deltas.
1596 *
1597 * Variation adjustment deltas are used in ‘gvar’ and ‘cvar’ tables.
1598 * They indicate how points (in ‘gvar’) or values (in ‘cvar’) get adjusted
1599 * when generating instances of variation fonts.
1600 *
1601 * @see https://www.microsoft.com/typography/otspec/gvar.htm
1602 * @see https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6gvar.html
1603 * @param {Array}
1604 * @return {Array}
1605 */
1606encode.VARDELTAS = function(deltas) {
1607    var pos = 0;
1608    var result = [];
1609    while (pos < deltas.length) {
1610        var value = deltas[pos];
1611        if (value === 0) {
1612            pos = encodeVarDeltaRunAsZeroes(deltas, pos, result);
1613        } else if (value >= -128 && value <= 127) {
1614            pos = encodeVarDeltaRunAsBytes(deltas, pos, result);
1615        } else {
1616            pos = encodeVarDeltaRunAsWords(deltas, pos, result);
1617        }
1618    }
1619    return result;
1620};
1621
1622// Convert a list of values to a CFF INDEX structure.
1623// The values should be objects containing name / type / value.
1624/**
1625 * @param {Array} l
1626 * @returns {Array}
1627 */
1628encode.INDEX = function(l) {
1629    //var offset, offsets, offsetEncoder, encodedOffsets, encodedOffset, data,
1630    //    i, v;
1631    // Because we have to know which data type to use to encode the offsets,
1632    // we have to go through the values twice: once to encode the data and
1633    // calculate the offsets, then again to encode the offsets using the fitting data type.
1634    var offset = 1; // First offset is always 1.
1635    var offsets = [offset];
1636    var data = [];
1637    for (var i = 0; i < l.length; i += 1) {
1638        var v = encode.OBJECT(l[i]);
1639        Array.prototype.push.apply(data, v);
1640        offset += v.length;
1641        offsets.push(offset);
1642    }
1643
1644    if (data.length === 0) {
1645        return [0, 0];
1646    }
1647
1648    var encodedOffsets = [];
1649    var offSize = (1 + Math.floor(Math.log(offset) / Math.log(2)) / 8) | 0;
1650    var offsetEncoder = [undefined, encode.BYTE, encode.USHORT, encode.UINT24, encode.ULONG][offSize];
1651    for (var i$1 = 0; i$1 < offsets.length; i$1 += 1) {
1652        var encodedOffset = offsetEncoder(offsets[i$1]);
1653        Array.prototype.push.apply(encodedOffsets, encodedOffset);
1654    }
1655
1656    return Array.prototype.concat(encode.Card16(l.length),
1657                           encode.OffSize(offSize),
1658                           encodedOffsets,
1659                           data);
1660};
1661
1662/**
1663 * @param {Array}
1664 * @returns {number}
1665 */
1666sizeOf.INDEX = function(v) {
1667    return encode.INDEX(v).length;
1668};
1669
1670/**
1671 * Convert an object to a CFF DICT structure.
1672 * The keys should be numeric.
1673 * The values should be objects containing name / type / value.
1674 * @param {Object} m
1675 * @returns {Array}
1676 */
1677encode.DICT = function(m) {
1678    var d = [];
1679    var keys = Object.keys(m);
1680    var length = keys.length;
1681
1682    for (var i = 0; i < length; i += 1) {
1683        // Object.keys() return string keys, but our keys are always numeric.
1684        var k = parseInt(keys[i], 0);
1685        var v = m[k];
1686        // Value comes before the key.
1687        d = d.concat(encode.OPERAND(v.value, v.type));
1688        d = d.concat(encode.OPERATOR(k));
1689    }
1690
1691    return d;
1692};
1693
1694/**
1695 * @param {Object}
1696 * @returns {number}
1697 */
1698sizeOf.DICT = function(m) {
1699    return encode.DICT(m).length;
1700};
1701
1702/**
1703 * @param {number}
1704 * @returns {Array}
1705 */
1706encode.OPERATOR = function(v) {
1707    if (v < 1200) {
1708        return [v];
1709    } else {
1710        return [12, v - 1200];
1711    }
1712};
1713
1714/**
1715 * @param {Array} v
1716 * @param {string}
1717 * @returns {Array}
1718 */
1719encode.OPERAND = function(v, type) {
1720    var d = [];
1721    if (Array.isArray(type)) {
1722        for (var i = 0; i < type.length; i += 1) {
1723            check.argument(v.length === type.length, 'Not enough arguments given for type' + type);
1724            d = d.concat(encode.OPERAND(v[i], type[i]));
1725        }
1726    } else {
1727        if (type === 'SID') {
1728            d = d.concat(encode.NUMBER(v));
1729        } else if (type === 'offset') {
1730            // We make it easy for ourselves and always encode offsets as
1731            // 4 bytes. This makes offset calculation for the top dict easier.
1732            d = d.concat(encode.NUMBER32(v));
1733        } else if (type === 'number') {
1734            d = d.concat(encode.NUMBER(v));
1735        } else if (type === 'real') {
1736            d = d.concat(encode.REAL(v));
1737        } else {
1738            throw new Error('Unknown operand type ' + type);
1739            // FIXME Add support for booleans
1740        }
1741    }
1742
1743    return d;
1744};
1745
1746encode.OP = encode.BYTE;
1747sizeOf.OP = sizeOf.BYTE;
1748
1749// memoize charstring encoding using WeakMap if available
1750var wmm = typeof WeakMap === 'function' && new WeakMap();
1751
1752/**
1753 * Convert a list of CharString operations to bytes.
1754 * @param {Array}
1755 * @returns {Array}
1756 */
1757encode.CHARSTRING = function(ops) {
1758    // See encode.MACSTRING for why we don't do "if (wmm && wmm.has(ops))".
1759    if (wmm) {
1760        var cachedValue = wmm.get(ops);
1761        if (cachedValue !== undefined) {
1762            return cachedValue;
1763        }
1764    }
1765
1766    var d = [];
1767    var length = ops.length;
1768
1769    for (var i = 0; i < length; i += 1) {
1770        var op = ops[i];
1771        d = d.concat(encode[op.type](op.value));
1772    }
1773
1774    if (wmm) {
1775        wmm.set(ops, d);
1776    }
1777
1778    return d;
1779};
1780
1781/**
1782 * @param {Array}
1783 * @returns {number}
1784 */
1785sizeOf.CHARSTRING = function(ops) {
1786    return encode.CHARSTRING(ops).length;
1787};
1788
1789// Utility functions ////////////////////////////////////////////////////////
1790
1791/**
1792 * Convert an object containing name / type / value to bytes.
1793 * @param {Object}
1794 * @returns {Array}
1795 */
1796encode.OBJECT = function(v) {
1797    var encodingFunction = encode[v.type];
1798    check.argument(encodingFunction !== undefined, 'No encoding function for type ' + v.type);
1799    return encodingFunction(v.value);
1800};
1801
1802/**
1803 * @param {Object}
1804 * @returns {number}
1805 */
1806sizeOf.OBJECT = function(v) {
1807    var sizeOfFunction = sizeOf[v.type];
1808    check.argument(sizeOfFunction !== undefined, 'No sizeOf function for type ' + v.type);
1809    return sizeOfFunction(v.value);
1810};
1811
1812/**
1813 * Convert a table object to bytes.
1814 * A table contains a list of fields containing the metadata (name, type and default value).
1815 * The table itself has the field values set as attributes.
1816 * @param {opentype.Table}
1817 * @returns {Array}
1818 */
1819encode.TABLE = function(table) {
1820    var d = [];
1821    var length = table.fields.length;
1822    var subtables = [];
1823    var subtableOffsets = [];
1824
1825    for (var i = 0; i < length; i += 1) {
1826        var field = table.fields[i];
1827        var encodingFunction = encode[field.type];
1828        check.argument(encodingFunction !== undefined, 'No encoding function for field type ' + field.type + ' (' + field.name + ')');
1829        var value = table[field.name];
1830        if (value === undefined) {
1831            value = field.value;
1832        }
1833
1834        var bytes = encodingFunction(value);
1835
1836        if (field.type === 'TABLE') {
1837            subtableOffsets.push(d.length);
1838            d = d.concat([0, 0]);
1839            subtables.push(bytes);
1840        } else {
1841            d = d.concat(bytes);
1842        }
1843    }
1844
1845    for (var i$1 = 0; i$1 < subtables.length; i$1 += 1) {
1846        var o = subtableOffsets[i$1];
1847        var offset = d.length;
1848        check.argument(offset < 65536, 'Table ' + table.tableName + ' too big.');
1849        d[o] = offset >> 8;
1850        d[o + 1] = offset & 0xff;
1851        d = d.concat(subtables[i$1]);
1852    }
1853
1854    return d;
1855};
1856
1857/**
1858 * @param {opentype.Table}
1859 * @returns {number}
1860 */
1861sizeOf.TABLE = function(table) {
1862    var numBytes = 0;
1863    var length = table.fields.length;
1864
1865    for (var i = 0; i < length; i += 1) {
1866        var field = table.fields[i];
1867        var sizeOfFunction = sizeOf[field.type];
1868        check.argument(sizeOfFunction !== undefined, 'No sizeOf function for field type ' + field.type + ' (' + field.name + ')');
1869        var value = table[field.name];
1870        if (value === undefined) {
1871            value = field.value;
1872        }
1873
1874        numBytes += sizeOfFunction(value);
1875
1876        // Subtables take 2 more bytes for offsets.
1877        if (field.type === 'TABLE') {
1878            numBytes += 2;
1879        }
1880    }
1881
1882    return numBytes;
1883};
1884
1885encode.RECORD = encode.TABLE;
1886sizeOf.RECORD = sizeOf.TABLE;
1887
1888// Merge in a list of bytes.
1889encode.LITERAL = function(v) {
1890    return v;
1891};
1892
1893sizeOf.LITERAL = function(v) {
1894    return v.length;
1895};
1896
1897// Table metadata
1898
1899/**
1900 * @exports opentype.Table
1901 * @class
1902 * @param {string} tableName
1903 * @param {Array} fields
1904 * @param {Object} options
1905 * @constructor
1906 */
1907function Table(tableName, fields, options) {
1908    // For coverage tables with coverage format 2, we do not want to add the coverage data directly to the table object,
1909    // as this will result in wrong encoding order of the coverage data on serialization to bytes.
1910    // The fallback of using the field values directly when not present on the table is handled in types.encode.TABLE() already.
1911    if (fields.length && (fields[0].name !== 'coverageFormat' || fields[0].value === 1)) {
1912        for (var i = 0; i < fields.length; i += 1) {
1913            var field = fields[i];
1914            this[field.name] = field.value;
1915        }
1916    }
1917
1918    this.tableName = tableName;
1919    this.fields = fields;
1920    if (options) {
1921        var optionKeys = Object.keys(options);
1922        for (var i$1 = 0; i$1 < optionKeys.length; i$1 += 1) {
1923            var k = optionKeys[i$1];
1924            var v = options[k];
1925            if (this[k] !== undefined) {
1926                this[k] = v;
1927            }
1928        }
1929    }
1930}
1931
1932/**
1933 * Encodes the table and returns an array of bytes
1934 * @return {Array}
1935 */
1936Table.prototype.encode = function() {
1937    return encode.TABLE(this);
1938};
1939
1940/**
1941 * Get the size of the table.
1942 * @return {number}
1943 */
1944Table.prototype.sizeOf = function() {
1945    return sizeOf.TABLE(this);
1946};
1947
1948/**
1949 * @private
1950 */
1951function ushortList(itemName, list, count) {
1952    if (count === undefined) {
1953        count = list.length;
1954    }
1955    var fields = new Array(list.length + 1);
1956    fields[0] = {name: itemName + 'Count', type: 'USHORT', value: count};
1957    for (var i = 0; i < list.length; i++) {
1958        fields[i + 1] = {name: itemName + i, type: 'USHORT', value: list[i]};
1959    }
1960    return fields;
1961}
1962
1963/**
1964 * @private
1965 */
1966function tableList(itemName, records, itemCallback) {
1967    var count = records.length;
1968    var fields = new Array(count + 1);
1969    fields[0] = {name: itemName + 'Count', type: 'USHORT', value: count};
1970    for (var i = 0; i < count; i++) {
1971        fields[i + 1] = {name: itemName + i, type: 'TABLE', value: itemCallback(records[i], i)};
1972    }
1973    return fields;
1974}
1975
1976/**
1977 * @private
1978 */
1979function recordList(itemName, records, itemCallback) {
1980    var count = records.length;
1981    var fields = [];
1982    fields[0] = {name: itemName + 'Count', type: 'USHORT', value: count};
1983    for (var i = 0; i < count; i++) {
1984        fields = fields.concat(itemCallback(records[i], i));
1985    }
1986    return fields;
1987}
1988
1989// Common Layout Tables
1990
1991/**
1992 * @exports opentype.Coverage
1993 * @class
1994 * @param {opentype.Table}
1995 * @constructor
1996 * @extends opentype.Table
1997 */
1998function Coverage(coverageTable) {
1999    if (coverageTable.format === 1) {
2000        Table.call(this, 'coverageTable',
2001            [{name: 'coverageFormat', type: 'USHORT', value: 1}]
2002            .concat(ushortList('glyph', coverageTable.glyphs))
2003        );
2004    } else if (coverageTable.format === 2) {
2005        Table.call(this, 'coverageTable',
2006            [{name: 'coverageFormat', type: 'USHORT', value: 2}]
2007            .concat(recordList('rangeRecord', coverageTable.ranges, function(RangeRecord) {
2008                return [
2009                    {name: 'startGlyphID', type: 'USHORT', value: RangeRecord.start},
2010                    {name: 'endGlyphID', type: 'USHORT', value: RangeRecord.end},
2011                    {name: 'startCoverageIndex', type: 'USHORT', value: RangeRecord.index} ];
2012            }))
2013        );
2014    } else {
2015        check.assert(false, 'Coverage format must be 1 or 2.');
2016    }
2017}
2018Coverage.prototype = Object.create(Table.prototype);
2019Coverage.prototype.constructor = Coverage;
2020
2021function ScriptList(scriptListTable) {
2022    Table.call(this, 'scriptListTable',
2023        recordList('scriptRecord', scriptListTable, function(scriptRecord, i) {
2024            var script = scriptRecord.script;
2025            var defaultLangSys = script.defaultLangSys;
2026            check.assert(!!defaultLangSys, 'Unable to write GSUB: script ' + scriptRecord.tag + ' has no default language system.');
2027            return [
2028                {name: 'scriptTag' + i, type: 'TAG', value: scriptRecord.tag},
2029                {name: 'script' + i, type: 'TABLE', value: new Table('scriptTable', [
2030                    {name: 'defaultLangSys', type: 'TABLE', value: new Table('defaultLangSys', [
2031                        {name: 'lookupOrder', type: 'USHORT', value: 0},
2032                        {name: 'reqFeatureIndex', type: 'USHORT', value: defaultLangSys.reqFeatureIndex}]
2033                        .concat(ushortList('featureIndex', defaultLangSys.featureIndexes)))}
2034                    ].concat(recordList('langSys', script.langSysRecords, function(langSysRecord, i) {
2035                        var langSys = langSysRecord.langSys;
2036                        return [
2037                            {name: 'langSysTag' + i, type: 'TAG', value: langSysRecord.tag},
2038                            {name: 'langSys' + i, type: 'TABLE', value: new Table('langSys', [
2039                                {name: 'lookupOrder', type: 'USHORT', value: 0},
2040                                {name: 'reqFeatureIndex', type: 'USHORT', value: langSys.reqFeatureIndex}
2041                                ].concat(ushortList('featureIndex', langSys.featureIndexes)))}
2042                        ];
2043                    })))}
2044            ];
2045        })
2046    );
2047}
2048ScriptList.prototype = Object.create(Table.prototype);
2049ScriptList.prototype.constructor = ScriptList;
2050
2051/**
2052 * @exports opentype.FeatureList
2053 * @class
2054 * @param {opentype.Table}
2055 * @constructor
2056 * @extends opentype.Table
2057 */
2058function FeatureList(featureListTable) {
2059    Table.call(this, 'featureListTable',
2060        recordList('featureRecord', featureListTable, function(featureRecord, i) {
2061            var feature = featureRecord.feature;
2062            return [
2063                {name: 'featureTag' + i, type: 'TAG', value: featureRecord.tag},
2064                {name: 'feature' + i, type: 'TABLE', value: new Table('featureTable', [
2065                    {name: 'featureParams', type: 'USHORT', value: feature.featureParams} ].concat(ushortList('lookupListIndex', feature.lookupListIndexes)))}
2066            ];
2067        })
2068    );
2069}
2070FeatureList.prototype = Object.create(Table.prototype);
2071FeatureList.prototype.constructor = FeatureList;
2072
2073/**
2074 * @exports opentype.LookupList
2075 * @class
2076 * @param {opentype.Table}
2077 * @param {Object}
2078 * @constructor
2079 * @extends opentype.Table
2080 */
2081function LookupList(lookupListTable, subtableMakers) {
2082    Table.call(this, 'lookupListTable', tableList('lookup', lookupListTable, function(lookupTable) {
2083        var subtableCallback = subtableMakers[lookupTable.lookupType];
2084        check.assert(!!subtableCallback, 'Unable to write GSUB lookup type ' + lookupTable.lookupType + ' tables.');
2085        return new Table('lookupTable', [
2086            {name: 'lookupType', type: 'USHORT', value: lookupTable.lookupType},
2087            {name: 'lookupFlag', type: 'USHORT', value: lookupTable.lookupFlag}
2088        ].concat(tableList('subtable', lookupTable.subtables, subtableCallback)));
2089    }));
2090}
2091LookupList.prototype = Object.create(Table.prototype);
2092LookupList.prototype.constructor = LookupList;
2093
2094// Record = same as Table, but inlined (a Table has an offset and its data is further in the stream)
2095// Don't use offsets inside Records (probable bug), only in Tables.
2096var table = {
2097    Table: Table,
2098    Record: Table,
2099    Coverage: Coverage,
2100    ScriptList: ScriptList,
2101    FeatureList: FeatureList,
2102    LookupList: LookupList,
2103    ushortList: ushortList,
2104    tableList: tableList,
2105    recordList: recordList,
2106};
2107
2108// Parsing utility functions
2109
2110// Retrieve an unsigned byte from the DataView.
2111function getByte(dataView, offset) {
2112    return dataView.getUint8(offset);
2113}
2114
2115// Retrieve an unsigned 16-bit short from the DataView.
2116// The value is stored in big endian.
2117function getUShort(dataView, offset) {
2118    return dataView.getUint16(offset, false);
2119}
2120
2121// Retrieve a signed 16-bit short from the DataView.
2122// The value is stored in big endian.
2123function getShort(dataView, offset) {
2124    return dataView.getInt16(offset, false);
2125}
2126
2127// Retrieve an unsigned 32-bit long from the DataView.
2128// The value is stored in big endian.
2129function getULong(dataView, offset) {
2130    return dataView.getUint32(offset, false);
2131}
2132
2133// Retrieve a 32-bit signed fixed-point number (16.16) from the DataView.
2134// The value is stored in big endian.
2135function getFixed(dataView, offset) {
2136    var decimal = dataView.getInt16(offset, false);
2137    var fraction = dataView.getUint16(offset + 2, false);
2138    return decimal + fraction / 65535;
2139}
2140
2141// Retrieve a 4-character tag from the DataView.
2142// Tags are used to identify tables.
2143function getTag(dataView, offset) {
2144    var tag = '';
2145    for (var i = offset; i < offset + 4; i += 1) {
2146        tag += String.fromCharCode(dataView.getInt8(i));
2147    }
2148
2149    return tag;
2150}
2151
2152// Retrieve an offset from the DataView.
2153// Offsets are 1 to 4 bytes in length, depending on the offSize argument.
2154function getOffset(dataView, offset, offSize) {
2155    var v = 0;
2156    for (var i = 0; i < offSize; i += 1) {
2157        v <<= 8;
2158        v += dataView.getUint8(offset + i);
2159    }
2160
2161    return v;
2162}
2163
2164// Retrieve a number of bytes from start offset to the end offset from the DataView.
2165function getBytes(dataView, startOffset, endOffset) {
2166    var bytes = [];
2167    for (var i = startOffset; i < endOffset; i += 1) {
2168        bytes.push(dataView.getUint8(i));
2169    }
2170
2171    return bytes;
2172}
2173
2174// Convert the list of bytes to a string.
2175function bytesToString(bytes) {
2176    var s = '';
2177    for (var i = 0; i < bytes.length; i += 1) {
2178        s += String.fromCharCode(bytes[i]);
2179    }
2180
2181    return s;
2182}
2183
2184var typeOffsets = {
2185    byte: 1,
2186    uShort: 2,
2187    short: 2,
2188    uLong: 4,
2189    fixed: 4,
2190    longDateTime: 8,
2191    tag: 4
2192};
2193
2194// A stateful parser that changes the offset whenever a value is retrieved.
2195// The data is a DataView.
2196function Parser(data, offset) {
2197    this.data = data;
2198    this.offset = offset;
2199    this.relativeOffset = 0;
2200}
2201
2202Parser.prototype.parseByte = function() {
2203    var v = this.data.getUint8(this.offset + this.relativeOffset);
2204    this.relativeOffset += 1;
2205    return v;
2206};
2207
2208Parser.prototype.parseChar = function() {
2209    var v = this.data.getInt8(this.offset + this.relativeOffset);
2210    this.relativeOffset += 1;
2211    return v;
2212};
2213
2214Parser.prototype.parseCard8 = Parser.prototype.parseByte;
2215
2216Parser.prototype.parseUShort = function() {
2217    var v = this.data.getUint16(this.offset + this.relativeOffset);
2218    this.relativeOffset += 2;
2219    return v;
2220};
2221
2222Parser.prototype.parseCard16 = Parser.prototype.parseUShort;
2223Parser.prototype.parseSID = Parser.prototype.parseUShort;
2224Parser.prototype.parseOffset16 = Parser.prototype.parseUShort;
2225
2226Parser.prototype.parseShort = function() {
2227    var v = this.data.getInt16(this.offset + this.relativeOffset);
2228    this.relativeOffset += 2;
2229    return v;
2230};
2231
2232Parser.prototype.parseF2Dot14 = function() {
2233    var v = this.data.getInt16(this.offset + this.relativeOffset) / 16384;
2234    this.relativeOffset += 2;
2235    return v;
2236};
2237
2238Parser.prototype.parseULong = function() {
2239    var v = getULong(this.data, this.offset + this.relativeOffset);
2240    this.relativeOffset += 4;
2241    return v;
2242};
2243
2244Parser.prototype.parseOffset32 = Parser.prototype.parseULong;
2245
2246Parser.prototype.parseFixed = function() {
2247    var v = getFixed(this.data, this.offset + this.relativeOffset);
2248    this.relativeOffset += 4;
2249    return v;
2250};
2251
2252Parser.prototype.parseString = function(length) {
2253    var dataView = this.data;
2254    var offset = this.offset + this.relativeOffset;
2255    var string = '';
2256    this.relativeOffset += length;
2257    for (var i = 0; i < length; i++) {
vendor: 14,037 bytes, lines 2258-2683
2258        string += String.fromCharCode(dataView.getUint8(offset + i));
2259    }
2260
2261    return string;
2262};
2263
2264Parser.prototype.parseTag = function() {
2265    return this.parseString(4);
2266};
2267
2268// LONGDATETIME is a 64-bit integer.
2269// JavaScript and unix timestamps traditionally use 32 bits, so we
2270// only take the last 32 bits.
2271// + Since until 2038 those bits will be filled by zeros we can ignore them.
2272Parser.prototype.parseLongDateTime = function() {
2273    var v = getULong(this.data, this.offset + this.relativeOffset + 4);
2274    // Subtract seconds between 01/01/1904 and 01/01/1970
2275    // to convert Apple Mac timestamp to Standard Unix timestamp
2276    v -= 2082844800;
2277    this.relativeOffset += 8;
2278    return v;
2279};
2280
2281Parser.prototype.parseVersion = function(minorBase) {
2282    var major = getUShort(this.data, this.offset + this.relativeOffset);
2283
2284    // How to interpret the minor version is very vague in the spec. 0x5000 is 5, 0x1000 is 1
2285    // Default returns the correct number if minor = 0xN000 where N is 0-9
2286    // Set minorBase to 1 for tables that use minor = N where N is 0-9
2287    var minor = getUShort(this.data, this.offset + this.relativeOffset + 2);
2288    this.relativeOffset += 4;
2289    if (minorBase === undefined) { minorBase = 0x1000; }
2290    return major + minor / minorBase / 10;
2291};
2292
2293Parser.prototype.skip = function(type, amount) {
2294    if (amount === undefined) {
2295        amount = 1;
2296    }
2297
2298    this.relativeOffset += typeOffsets[type] * amount;
2299};
2300
2301///// Parsing lists and records ///////////////////////////////
2302
2303// Parse a list of 32 bit unsigned integers.
2304Parser.prototype.parseULongList = function(count) {
2305    if (count === undefined) { count = this.parseULong(); }
2306    var offsets = new Array(count);
2307    var dataView = this.data;
2308    var offset = this.offset + this.relativeOffset;
2309    for (var i = 0; i < count; i++) {
2310        offsets[i] = dataView.getUint32(offset);
2311        offset += 4;
2312    }
2313
2314    this.relativeOffset += count * 4;
2315    return offsets;
2316};
2317
2318// Parse a list of 16 bit unsigned integers. The length of the list can be read on the stream
2319// or provided as an argument.
2320Parser.prototype.parseOffset16List =
2321Parser.prototype.parseUShortList = function(count) {
2322    if (count === undefined) { count = this.parseUShort(); }
2323    var offsets = new Array(count);
2324    var dataView = this.data;
2325    var offset = this.offset + this.relativeOffset;
2326    for (var i = 0; i < count; i++) {
2327        offsets[i] = dataView.getUint16(offset);
2328        offset += 2;
2329    }
2330
2331    this.relativeOffset += count * 2;
2332    return offsets;
2333};
2334
2335// Parses a list of 16 bit signed integers.
2336Parser.prototype.parseShortList = function(count) {
2337    var list = new Array(count);
2338    var dataView = this.data;
2339    var offset = this.offset + this.relativeOffset;
2340    for (var i = 0; i < count; i++) {
2341        list[i] = dataView.getInt16(offset);
2342        offset += 2;
2343    }
2344
2345    this.relativeOffset += count * 2;
2346    return list;
2347};
2348
2349// Parses a list of bytes.
2350Parser.prototype.parseByteList = function(count) {
2351    var list = new Array(count);
2352    var dataView = this.data;
2353    var offset = this.offset + this.relativeOffset;
2354    for (var i = 0; i < count; i++) {
2355        list[i] = dataView.getUint8(offset++);
2356    }
2357
2358    this.relativeOffset += count;
2359    return list;
2360};
2361
2362/**
2363 * Parse a list of items.
2364 * Record count is optional, if omitted it is read from the stream.
2365 * itemCallback is one of the Parser methods.
2366 */
2367Parser.prototype.parseList = function(count, itemCallback) {
2368    if (!itemCallback) {
2369        itemCallback = count;
2370        count = this.parseUShort();
2371    }
2372    var list = new Array(count);
2373    for (var i = 0; i < count; i++) {
2374        list[i] = itemCallback.call(this);
2375    }
2376    return list;
2377};
2378
2379Parser.prototype.parseList32 = function(count, itemCallback) {
2380    if (!itemCallback) {
2381        itemCallback = count;
2382        count = this.parseULong();
2383    }
2384    var list = new Array(count);
2385    for (var i = 0; i < count; i++) {
2386        list[i] = itemCallback.call(this);
2387    }
2388    return list;
2389};
2390
2391/**
2392 * Parse a list of records.
2393 * Record count is optional, if omitted it is read from the stream.
2394 * Example of recordDescription: { sequenceIndex: Parser.uShort, lookupListIndex: Parser.uShort }
2395 */
2396Parser.prototype.parseRecordList = function(count, recordDescription) {
2397    // If the count argument is absent, read it in the stream.
2398    if (!recordDescription) {
2399        recordDescription = count;
2400        count = this.parseUShort();
2401    }
2402    var records = new Array(count);
2403    var fields = Object.keys(recordDescription);
2404    for (var i = 0; i < count; i++) {
2405        var rec = {};
2406        for (var j = 0; j < fields.length; j++) {
2407            var fieldName = fields[j];
2408            var fieldType = recordDescription[fieldName];
2409            rec[fieldName] = fieldType.call(this);
2410        }
2411        records[i] = rec;
2412    }
2413    return records;
2414};
2415
2416Parser.prototype.parseRecordList32 = function(count, recordDescription) {
2417    // If the count argument is absent, read it in the stream.
2418    if (!recordDescription) {
2419        recordDescription = count;
2420        count = this.parseULong();
2421    }
2422    var records = new Array(count);
2423    var fields = Object.keys(recordDescription);
2424    for (var i = 0; i < count; i++) {
2425        var rec = {};
2426        for (var j = 0; j < fields.length; j++) {
2427            var fieldName = fields[j];
2428            var fieldType = recordDescription[fieldName];
2429            rec[fieldName] = fieldType.call(this);
2430        }
2431        records[i] = rec;
2432    }
2433    return records;
2434};
2435
2436// Parse a data structure into an object
2437// Example of description: { sequenceIndex: Parser.uShort, lookupListIndex: Parser.uShort }
2438Parser.prototype.parseStruct = function(description) {
2439    if (typeof description === 'function') {
2440        return description.call(this);
2441    } else {
2442        var fields = Object.keys(description);
2443        var struct = {};
2444        for (var j = 0; j < fields.length; j++) {
2445            var fieldName = fields[j];
2446            var fieldType = description[fieldName];
2447            struct[fieldName] = fieldType.call(this);
2448        }
2449        return struct;
2450    }
2451};
2452
2453/**
2454 * Parse a GPOS valueRecord
2455 * https://docs.microsoft.com/en-us/typography/opentype/spec/gpos#value-record
2456 * valueFormat is optional, if omitted it is read from the stream.
2457 */
2458Parser.prototype.parseValueRecord = function(valueFormat) {
2459    if (valueFormat === undefined) {
2460        valueFormat = this.parseUShort();
2461    }
2462    if (valueFormat === 0) {
2463        // valueFormat2 in kerning pairs is most often 0
2464        // in this case return undefined instead of an empty object, to save space
2465        return;
2466    }
2467    var valueRecord = {};
2468
2469    if (valueFormat & 0x0001) { valueRecord.xPlacement = this.parseShort(); }
2470    if (valueFormat & 0x0002) { valueRecord.yPlacement = this.parseShort(); }
2471    if (valueFormat & 0x0004) { valueRecord.xAdvance = this.parseShort(); }
2472    if (valueFormat & 0x0008) { valueRecord.yAdvance = this.parseShort(); }
2473
2474    // Device table (non-variable font) / VariationIndex table (variable font) not supported
2475    // https://docs.microsoft.com/fr-fr/typography/opentype/spec/chapter2#devVarIdxTbls
2476    if (valueFormat & 0x0010) { valueRecord.xPlaDevice = undefined; this.parseShort(); }
2477    if (valueFormat & 0x0020) { valueRecord.yPlaDevice = undefined; this.parseShort(); }
2478    if (valueFormat & 0x0040) { valueRecord.xAdvDevice = undefined; this.parseShort(); }
2479    if (valueFormat & 0x0080) { valueRecord.yAdvDevice = undefined; this.parseShort(); }
2480
2481    return valueRecord;
2482};
2483
2484/**
2485 * Parse a list of GPOS valueRecords
2486 * https://docs.microsoft.com/en-us/typography/opentype/spec/gpos#value-record
2487 * valueFormat and valueCount are read from the stream.
2488 */
2489Parser.prototype.parseValueRecordList = function() {
2490    var valueFormat = this.parseUShort();
2491    var valueCount = this.parseUShort();
2492    var values = new Array(valueCount);
2493    for (var i = 0; i < valueCount; i++) {
2494        values[i] = this.parseValueRecord(valueFormat);
2495    }
2496    return values;
2497};
2498
2499Parser.prototype.parsePointer = function(description) {
2500    var structOffset = this.parseOffset16();
2501    if (structOffset > 0) {
2502        // NULL offset => return undefined
2503        return new Parser(this.data, this.offset + structOffset).parseStruct(description);
2504    }
2505    return undefined;
2506};
2507
2508Parser.prototype.parsePointer32 = function(description) {
2509    var structOffset = this.parseOffset32();
2510    if (structOffset > 0) {
2511        // NULL offset => return undefined
2512        return new Parser(this.data, this.offset + structOffset).parseStruct(description);
2513    }
2514    return undefined;
2515};
2516
2517/**
2518 * Parse a list of offsets to lists of 16-bit integers,
2519 * or a list of offsets to lists of offsets to any kind of items.
2520 * If itemCallback is not provided, a list of list of UShort is assumed.
2521 * If provided, itemCallback is called on each item and must parse the item.
2522 * See examples in tables/gsub.js
2523 */
2524Parser.prototype.parseListOfLists = function(itemCallback) {
2525    var offsets = this.parseOffset16List();
2526    var count = offsets.length;
2527    var relativeOffset = this.relativeOffset;
2528    var list = new Array(count);
2529    for (var i = 0; i < count; i++) {
2530        var start = offsets[i];
2531        if (start === 0) {
2532            // NULL offset
2533            // Add i as owned property to list. Convenient with assert.
2534            list[i] = undefined;
2535            continue;
2536        }
2537        this.relativeOffset = start;
2538        if (itemCallback) {
2539            var subOffsets = this.parseOffset16List();
2540            var subList = new Array(subOffsets.length);
2541            for (var j = 0; j < subOffsets.length; j++) {
2542                this.relativeOffset = start + subOffsets[j];
2543                subList[j] = itemCallback.call(this);
2544            }
2545            list[i] = subList;
2546        } else {
2547            list[i] = this.parseUShortList();
2548        }
2549    }
2550    this.relativeOffset = relativeOffset;
2551    return list;
2552};
2553
2554///// Complex tables parsing //////////////////////////////////
2555
2556// Parse a coverage table in a GSUB, GPOS or GDEF table.
2557// https://www.microsoft.com/typography/OTSPEC/chapter2.htm
2558// parser.offset must point to the start of the table containing the coverage.
2559Parser.prototype.parseCoverage = function() {
2560    var startOffset = this.offset + this.relativeOffset;
2561    var format = this.parseUShort();
2562    var count = this.parseUShort();
2563    if (format === 1) {
2564        return {
2565            format: 1,
2566            glyphs: this.parseUShortList(count)
2567        };
2568    } else if (format === 2) {
2569        var ranges = new Array(count);
2570        for (var i = 0; i < count; i++) {
2571            ranges[i] = {
2572                start: this.parseUShort(),
2573                end: this.parseUShort(),
2574                index: this.parseUShort()
2575            };
2576        }
2577        return {
2578            format: 2,
2579            ranges: ranges
2580        };
2581    }
2582    throw new Error('0x' + startOffset.toString(16) + ': Coverage format must be 1 or 2.');
2583};
2584
2585// Parse a Class Definition Table in a GSUB, GPOS or GDEF table.
2586// https://www.microsoft.com/typography/OTSPEC/chapter2.htm
2587Parser.prototype.parseClassDef = function() {
2588    var startOffset = this.offset + this.relativeOffset;
2589    var format = this.parseUShort();
2590    if (format === 1) {
2591        return {
2592            format: 1,
2593            startGlyph: this.parseUShort(),
2594            classes: this.parseUShortList()
2595        };
2596    } else if (format === 2) {
2597        return {
2598            format: 2,
2599            ranges: this.parseRecordList({
2600                start: Parser.uShort,
2601                end: Parser.uShort,
2602                classId: Parser.uShort
2603            })
2604        };
2605    }
2606    throw new Error('0x' + startOffset.toString(16) + ': ClassDef format must be 1 or 2.');
2607};
2608
2609///// Static methods ///////////////////////////////////
2610// These convenience methods can be used as callbacks and should be called with "this" context set to a Parser instance.
2611
2612Parser.list = function(count, itemCallback) {
2613    return function() {
2614        return this.parseList(count, itemCallback);
2615    };
2616};
2617
2618Parser.list32 = function(count, itemCallback) {
2619    return function() {
2620        return this.parseList32(count, itemCallback);
2621    };
2622};
2623
2624Parser.recordList = function(count, recordDescription) {
2625    return function() {
2626        return this.parseRecordList(count, recordDescription);
2627    };
2628};
2629
2630Parser.recordList32 = function(count, recordDescription) {
2631    return function() {
2632        return this.parseRecordList32(count, recordDescription);
2633    };
2634};
2635
2636Parser.pointer = function(description) {
2637    return function() {
2638        return this.parsePointer(description);
2639    };
2640};
2641
2642Parser.pointer32 = function(description) {
2643    return function() {
2644        return this.parsePointer32(description);
2645    };
2646};
2647
2648Parser.tag = Parser.prototype.parseTag;
2649Parser.byte = Parser.prototype.parseByte;
2650Parser.uShort = Parser.offset16 = Parser.prototype.parseUShort;
2651Parser.uShortList = Parser.prototype.parseUShortList;
2652Parser.uLong = Parser.offset32 = Parser.prototype.parseULong;
2653Parser.uLongList = Parser.prototype.parseULongList;
2654Parser.struct = Parser.prototype.parseStruct;
2655Parser.coverage = Parser.prototype.parseCoverage;
2656Parser.classDef = Parser.prototype.parseClassDef;
2657
2658///// Script, Feature, Lookup lists ///////////////////////////////////////////////
2659// https://www.microsoft.com/typography/OTSPEC/chapter2.htm
2660
2661var langSysTable = {
2662    reserved: Parser.uShort,
2663    reqFeatureIndex: Parser.uShort,
2664    featureIndexes: Parser.uShortList
2665};
2666
2667Parser.prototype.parseScriptList = function() {
2668    return this.parsePointer(Parser.recordList({
2669        tag: Parser.tag,
2670        script: Parser.pointer({
2671            defaultLangSys: Parser.pointer(langSysTable),
2672            langSysRecords: Parser.recordList({
2673                tag: Parser.tag,
2674                langSys: Parser.pointer(langSysTable)
2675            })
2676        })
2677    })) || [];
2678};
2679
2680Parser.prototype.parseFeatureList = function() {
2681    return this.parsePointer(Parser.recordList({
2682        tag: Parser.tag,
2683        feature: Parser.pointer({
2684            featureParams: Parser.offset16,
2685            lookupListIndexes: Parser.uShortList
2686        })
2687    })) || [];
2688};
2689
2690Parser.prototype.parseLookupList = function(lookupTableParsers) {
2691    return this.parsePointer(Parser.list(Parser.pointer(function() {
2692        var lookupType = this.parseUShort();
2693        check.argument(1 <= lookupType && lookupType <= 9, 'GPOS/GSUB lookup type ' + lookupType + ' unknown.');
2694        var lookupFlag = this.parseUShort();
2695        var useMarkFilteringSet = lookupFlag & 0x10;
2696        return {
2697            lookupType: lookupType,
2698            lookupFlag: lookupFlag,
2699            subtables: this.parseList(Parser.pointer(lookupTableParsers[lookupType])),
2700            markFilteringSet: useMarkFilteringSet ? this.parseUShort() : undefined
2701        };
2702    }))) || [];
2703};
2704
2705Parser.prototype.parseFeatureVariationsList = function() {
2706    return this.parsePointer32(function() {
2707        var majorVersion = this.parseUShort();
2708        var minorVersion = this.parseUShort();
2709        check.argument(majorVersion === 1 && minorVersion < 1, 'GPOS/GSUB feature variations table unknown.');
2710        var featureVariations = this.parseRecordList32({
2711            conditionSetOffset: Parser.offset32,
2712            featureTableSubstitutionOffset: Parser.offset32
2713        });
2714        return featureVariations;
2715    }) || [];
2716};
2717
2718var parse = {
2719    getByte: getByte,
2720    getCard8: getByte,
2721    getUShort: getUShort,
2722    getCard16: getUShort,
2723    getShort: getShort,
2724    getULong: getULong,
2725    getFixed: getFixed,
2726    getTag: getTag,
2727    getOffset: getOffset,
2728    getBytes: getBytes,
2729    bytesToString: bytesToString,
2730    Parser: Parser,
2731};
2732
2733// The `cmap` table stores the mappings from characters to glyphs.
2734
2735function parseCmapTableFormat12(cmap, p) {
2736    //Skip reserved.
2737    p.parseUShort();
2738
2739    // Length in bytes of the sub-tables.
2740    cmap.length = p.parseULong();
2741    cmap.language = p.parseULong();
2742
2743    var groupCount;
2744    cmap.groupCount = groupCount = p.parseULong();
2745    cmap.glyphIndexMap = {};
2746
2747    for (var i = 0; i < groupCount; i += 1) {
2748        var startCharCode = p.parseULong();
2749        var endCharCode = p.parseULong();
2750        var startGlyphId = p.parseULong();
2751
2752        for (var c = startCharCode; c <= endCharCode; c += 1) {
2753            cmap.glyphIndexMap[c] = startGlyphId;
2754            startGlyphId++;
2755        }
2756    }
2757}
2758
2759function parseCmapTableFormat4(cmap, p, data, start, offset) {
2760    // Length in bytes of the sub-tables.
2761    cmap.length = p.parseUShort();
2762    cmap.language = p.parseUShort();
2763
2764    // segCount is stored x 2.
2765    var segCount;
2766    cmap.segCount = segCount = p.parseUShort() >> 1;
2767
2768    // Skip searchRange, entrySelector, rangeShift.
2769    p.skip('uShort', 3);
2770
2771    // The "unrolled" mapping from character codes to glyph indices.
2772    cmap.glyphIndexMap = {};
2773    var endCountParser = new parse.Parser(data, start + offset + 14);
2774    var startCountParser = new parse.Parser(data, start + offset + 16 + segCount * 2);
2775    var idDeltaParser = new parse.Parser(data, start + offset + 16 + segCount * 4);
2776    var idRangeOffsetParser = new parse.Parser(data, start + offset + 16 + segCount * 6);
2777    var glyphIndexOffset = start + offset + 16 + segCount * 8;
2778    for (var i = 0; i < segCount - 1; i += 1) {
2779        var glyphIndex = (void 0);
2780        var endCount = endCountParser.parseUShort();
2781        var startCount = startCountParser.parseUShort();
2782        var idDelta = idDeltaParser.parseShort();
2783        var idRangeOffset = idRangeOffsetParser.parseUShort();
2784        for (var c = startCount; c <= endCount; c += 1) {
2785            if (idRangeOffset !== 0) {
2786                // The idRangeOffset is relative to the current position in the idRangeOffset array.
2787                // Take the current offset in the idRangeOffset array.
2788                glyphIndexOffset = (idRangeOffsetParser.offset + idRangeOffsetParser.relativeOffset - 2);
2789
2790                // Add the value of the idRangeOffset, which will move us into the glyphIndex array.
2791                glyphIndexOffset += idRangeOffset;
2792
2793                // Then add the character index of the current segment, multiplied by 2 for USHORTs.
2794                glyphIndexOffset += (c - startCount) * 2;
2795                glyphIndex = parse.getUShort(data, glyphIndexOffset);
2796                if (glyphIndex !== 0) {
2797                    glyphIndex = (glyphIndex + idDelta) & 0xFFFF;
2798                }
2799            } else {
2800                glyphIndex = (c + idDelta) & 0xFFFF;
2801            }
2802
2803            cmap.glyphIndexMap[c] = glyphIndex;
2804        }
2805    }
2806}
2807
2808// Parse the `cmap` table. This table stores the mappings from characters to glyphs.
2809// There are many available formats, but we only support the Windows format 4 and 12.
2810// This function returns a `CmapEncoding` object or null if no supported format could be found.
2811function parseCmapTable(data, start) {
2812    var cmap = {};
2813    cmap.version = parse.getUShort(data, start);
2814    check.argument(cmap.version === 0, 'cmap table version should be 0.');
2815
2816    // The cmap table can contain many sub-tables, each with their own format.
2817    // We're only interested in a "platform 0" (Unicode format) and "platform 3" (Windows format) table.
2818    cmap.numTables = parse.getUShort(data, start + 2);
2819    var offset = -1;
2820    for (var i = cmap.numTables - 1; i >= 0; i -= 1) {
2821        var platformId = parse.getUShort(data, start + 4 + (i * 8));
2822        var encodingId = parse.getUShort(data, start + 4 + (i * 8) + 2);
2823        if ((platformId === 3 && (encodingId === 0 || encodingId === 1 || encodingId === 10)) ||
2824            (platformId === 0 && (encodingId === 0 || encodingId === 1 || encodingId === 2 || encodingId === 3 || encodingId === 4))) {
2825            offset = parse.getULong(data, start + 4 + (i * 8) + 4);
2826            break;
2827        }
2828    }
2829
2830    if (offset === -1) {
2831        // There is no cmap table in the font that we support.
2832        throw new Error('No valid cmap sub-tables found.');
2833    }
2834
2835    var p = new parse.Parser(data, start + offset);
2836    cmap.format = p.parseUShort();
2837
2838    if (cmap.format === 12) {
2839        parseCmapTableFormat12(cmap, p);
2840    } else if (cmap.format === 4) {
2841        parseCmapTableFormat4(cmap, p, data, start, offset);
2842    } else {
2843        throw new Error('Only format 4 and 12 cmap tables are supported (found format ' + cmap.format + ').');
2844    }
2845
2846    return cmap;
2847}
2848
2849function addSegment(t, code, glyphIndex) {
2850    t.segments.push({
2851        end: code,
2852        start: code,
2853        delta: -(code - glyphIndex),
2854        offset: 0,
2855        glyphIndex: glyphIndex
2856    });
2857}
2858
2859function addTerminatorSegment(t) {
2860    t.segments.push({
2861        end: 0xFFFF,
2862        start: 0xFFFF,
2863        delta: 1,
2864        offset: 0
2865    });
2866}
2867
2868// Make cmap table, format 4 by default, 12 if needed only
2869function makeCmapTable(glyphs) {
2870    // Plan 0 is the base Unicode Plan but emojis, for example are on another plan, and needs cmap 12 format (with 32bit)
2871    var isPlan0Only = true;
2872    var i;
2873
2874    // Check if we need to add cmap format 12 or if format 4 only is fine
2875    for (i = glyphs.length - 1; i > 0; i -= 1) {
2876        var g = glyphs.get(i);
2877        if (g.unicode > 65535) {
2878            console.log('Adding CMAP format 12 (needed!)');
2879            isPlan0Only = false;
2880            break;
2881        }
2882    }
2883
2884    var cmapTable = [
2885        {name: 'version', type: 'USHORT', value: 0},
2886        {name: 'numTables', type: 'USHORT', value: isPlan0Only ? 1 : 2},
2887
2888        // CMAP 4 header
2889        {name: 'platformID', type: 'USHORT', value: 3},
2890        {name: 'encodingID', type: 'USHORT', value: 1},
2891        {name: 'offset', type: 'ULONG', value: isPlan0Only ? 12 : (12 + 8)}
2892    ];
2893
2894    if (!isPlan0Only)
2895        { cmapTable = cmapTable.concat([
2896            // CMAP 12 header
2897            {name: 'cmap12PlatformID', type: 'USHORT', value: 3}, // We encode only for PlatformID = 3 (Windows) because it is supported everywhere
2898            {name: 'cmap12EncodingID', type: 'USHORT', value: 10},
2899            {name: 'cmap12Offset', type: 'ULONG', value: 0}
2900        ]); }
2901
2902    cmapTable = cmapTable.concat([
2903        // CMAP 4 Subtable
2904        {name: 'format', type: 'USHORT', value: 4},
2905        {name: 'cmap4Length', type: 'USHORT', value: 0},
2906        {name: 'language', type: 'USHORT', value: 0},
2907        {name: 'segCountX2', type: 'USHORT', value: 0},
2908        {name: 'searchRange', type: 'USHORT', value: 0},
2909        {name: 'entrySelector', type: 'USHORT', value: 0},
2910        {name: 'rangeShift', type: 'USHORT', value: 0}
2911    ]);
2912
2913    var t = new table.Table('cmap', cmapTable);
2914
2915    t.segments = [];
2916    for (i = 0; i < glyphs.length; i += 1) {
2917        var glyph = glyphs.get(i);
2918        for (var j = 0; j < glyph.unicodes.length; j += 1) {
2919            addSegment(t, glyph.unicodes[j], i);
2920        }
2921
2922        t.segments = t.segments.sort(function (a, b) {
2923            return a.start - b.start;
2924        });
2925    }
2926
2927    addTerminatorSegment(t);
2928
2929    var segCount = t.segments.length;
2930    var segCountToRemove = 0;
2931
2932    // CMAP 4
2933    // Set up parallel segment arrays.
2934    var endCounts = [];
2935    var startCounts = [];
2936    var idDeltas = [];
2937    var idRangeOffsets = [];
2938    var glyphIds = [];
2939
2940    // CMAP 12
2941    var cmap12Groups = [];
2942
2943    // Reminder this loop is not following the specification at 100%
2944    // The specification -> find suites of characters and make a group
2945    // Here we're doing one group for each letter
2946    // Doing as the spec can save 8 times (or more) space
2947    for (i = 0; i < segCount; i += 1) {
2948        var segment = t.segments[i];
2949
2950        // CMAP 4
2951        if (segment.end <= 65535 && segment.start <= 65535) {
2952            endCounts = endCounts.concat({name: 'end_' + i, type: 'USHORT', value: segment.end});
2953            startCounts = startCounts.concat({name: 'start_' + i, type: 'USHORT', value: segment.start});
2954            idDeltas = idDeltas.concat({name: 'idDelta_' + i, type: 'SHORT', value: segment.delta});
2955            idRangeOffsets = idRangeOffsets.concat({name: 'idRangeOffset_' + i, type: 'USHORT', value: segment.offset});
2956            if (segment.glyphId !== undefined) {
2957                glyphIds = glyphIds.concat({name: 'glyph_' + i, type: 'USHORT', value: segment.glyphId});
2958            }
2959        } else {
2960            // Skip Unicode > 65535 (16bit unsigned max) for CMAP 4, will be added in CMAP 12
2961            segCountToRemove += 1;
2962        }
2963
2964        // CMAP 12
2965        // Skip Terminator Segment
2966        if (!isPlan0Only && segment.glyphIndex !== undefined) {
2967            cmap12Groups = cmap12Groups.concat({name: 'cmap12Start_' + i, type: 'ULONG', value: segment.start});
2968            cmap12Groups = cmap12Groups.concat({name: 'cmap12End_' + i, type: 'ULONG', value: segment.end});
2969            cmap12Groups = cmap12Groups.concat({name: 'cmap12Glyph_' + i, type: 'ULONG', value: segment.glyphIndex});
2970        }
2971    }
2972
2973    // CMAP 4 Subtable
2974    t.segCountX2 = (segCount - segCountToRemove) * 2;
2975    t.searchRange = Math.pow(2, Math.floor(Math.log((segCount - segCountToRemove)) / Math.log(2))) * 2;
2976    t.entrySelector = Math.log(t.searchRange / 2) / Math.log(2);
2977    t.rangeShift = t.segCountX2 - t.searchRange;
2978
2979    t.fields = t.fields.concat(endCounts);
2980    t.fields.push({name: 'reservedPad', type: 'USHORT', value: 0});
2981    t.fields = t.fields.concat(startCounts);
2982    t.fields = t.fields.concat(idDeltas);
2983    t.fields = t.fields.concat(idRangeOffsets);
2984    t.fields = t.fields.concat(glyphIds);
2985
2986    t.cmap4Length = 14 + // Subtable header
2987        endCounts.length * 2 +
2988        2 + // reservedPad
2989        startCounts.length * 2 +
2990        idDeltas.length * 2 +
2991        idRangeOffsets.length * 2 +
2992        glyphIds.length * 2;
2993
2994    if (!isPlan0Only) {
2995        // CMAP 12 Subtable
2996        var cmap12Length = 16 + // Subtable header
2997            cmap12Groups.length * 4;
2998
2999        t.cmap12Offset = 12 + (2 * 2) + 4 + t.cmap4Length;
3000        t.fields = t.fields.concat([
3001            {name: 'cmap12Format', type: 'USHORT', value: 12},
3002            {name: 'cmap12Reserved', type: 'USHORT', value: 0},
3003            {name: 'cmap12Length', type: 'ULONG', value: cmap12Length},
3004            {name: 'cmap12Language', type: 'ULONG', value: 0},
3005            {name: 'cmap12nGroups', type: 'ULONG', value: cmap12Groups.length / 3}
3006        ]);
3007
3008        t.fields = t.fields.concat(cmap12Groups);
3009    }
3010
3011    return t;
3012}
3013
3014var cmap = { parse: parseCmapTable, make: makeCmapTable };
3015
3016// Glyph encoding
3017
3018var cffStandardStrings = [
3019    '.notdef', 'space', 'exclam', 'quotedbl', 'numbersign', 'dollar', 'percent', 'ampersand', 'quoteright',
3020    'parenleft', 'parenright', 'asterisk', 'plus', 'comma', 'hyphen', 'period', 'slash', 'zero', 'one', 'two',
3021    'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'colon', 'semicolon', 'less', 'equal', 'greater',
3022    'question', 'at', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S',
3023    'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'bracketleft', 'backslash', 'bracketright', 'asciicircum', 'underscore',
3024    'quoteleft', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't',
3025    'u', 'v', 'w', 'x', 'y', 'z', 'braceleft', 'bar', 'braceright', 'asciitilde', 'exclamdown', 'cent', 'sterling',
3026    'fraction', 'yen', 'florin', 'section', 'currency', 'quotesingle', 'quotedblleft', 'guillemotleft',
3027    'guilsinglleft', 'guilsinglright', 'fi', 'fl', 'endash', 'dagger', 'daggerdbl', 'periodce
3027ntered', 'paragraph',
3028    'bullet', 'quotesinglbase', 'quotedblbase', 'quotedblright', 'guillemotright', 'ellipsis', 'perthousand',
3029    'questiondown', 'grave', 'acute', 'circumflex', 'tilde', 'macron', 'breve', 'dotaccent', 'dieresis', 'ring',
3030    'cedilla', 'hungarumlaut', 'ogonek', 'caron', 'emdash', 'AE', 'ordfeminine', 'Lslash', 'Oslash', 'OE',
3031    'ordmasculine', 'ae', 'dotlessi', 'lslash', 'oslash', 'oe', 'germandbls', 'onesuperior', 'logicalnot', 'mu',
3032    'trademark', 'Eth', 'onehalf', 'plusminus', 'Thorn', 'onequarter', 'divide', 'brokenbar', 'degree', 'thorn',
3033    'threequarters', 'twosuperior', 'registered', 'minus', 'eth', 'multiply', 'threesuperior', 'copyright',
3034    'Aacute', 'Acircumflex', 'Adieresis', 'Agrave', 'Aring', 'Atilde', 'Ccedilla', 'Eacute', 'Ecircumflex',
3035    'Edieresis', 'Egrave', 'Iacute', 'Icircumflex', 'Idieresis', 'Igrave', 'Ntilde', 'Oacute', 'Ocircumflex',
3036    'Odieresis', 'Ograve', 'Otilde', 'Scaron', 'Uacute', 'Ucircumflex', 'Udieresis', 'Ugrave', 'Yacute',
3037    'Ydieresis', 'Zcaron', 'aacute', 'acircumflex', 'adieresis', 'agrave', 'aring', 'atilde', 'ccedilla', 'eacute',
3038    'ecircumflex', 'edieresis', 'egrave', 'iacute', 'icircumflex', 'idieresis', 'igrave', 'ntilde', 'oacute',
3039    'ocircumflex', 'odieresis', 'ograve', 'otilde', 'scaron', 'uacute', 'ucircumflex', 'udieresis', 'ugrave',
3040    'yacute', 'ydieresis', 'zcaron', 'exclamsmall', 'Hungarumlautsmall', 'dollaroldstyle', 'dollarsuperior',
3041    'ampersandsmall', 'Acutesmall', 'parenleftsuperior', 'parenrightsuperior', '266 ff', 'onedotenleader',
3042    'zerooldstyle', 'oneoldstyle', 'twooldstyle', 'threeoldstyle', 'fouroldstyle', 'fiveoldstyle', 'sixoldstyle',
3043    'sevenoldstyle', 'eightoldstyle', 'nineoldstyle', 'commasuperior', 'threequartersemdash', 'periodsuperior',
3044    'questionsmall', 'asuperior', 'bsuperior', 'centsuperior', 'dsuperior', 'esuperior', 'isuperior', 'lsuperior',
3045    'msuperior', 'nsuperior', 'osuperior', 'rsuperior', 'ssuperior', 'tsuperior', 'ff', 'ffi', 'ffl',
3046    'parenleftinferior', 'parenrightinferior', 'Circumflexsmall', 'hyphensuperior', 'Gravesmall', 'Asmall',
3047    'Bsmall', 'Csmall', 'Dsmall', 'Esmall', 'Fsmall', 'Gsmall', 'Hsmall', 'Ismall', 'Jsmall', 'Ksmall', 'Lsmall',
3048    'Msmall', 'Nsmall', 'Osmall', 'Psmall', 'Qsmall', 'Rsmall', 'Ssmall', 'Tsmall', 'Usmall', 'Vsmall', 'Wsmall',
3049    'Xsmall', 'Ysmall', 'Zsmall', 'colonmonetary', 'onefitted', 'rupiah', 'Tildesmall', 'exclamdownsmall',
3050    'centoldstyle', 'Lslashsmall', 'Scaronsmall', 'Zcaronsmall', 'Dieresissmall', 'Brevesmall', 'Caronsmall',
3051    'Dotaccentsmall', 'Macronsmall', 'figuredash', 'hypheninferior', 'Ogoneksmall', 'Ringsmall', 'Cedillasmall',
3052    'questiondownsmall', 'oneeighth', 'threeeighths', 'fiveeighths', 'seveneighths', 'onethird', 'twothirds',
3053    'zerosuperior', 'foursuperior', 'fivesuperior', 'sixsuperior', 'sevensuperior', 'eightsuperior', 'ninesuperior',
3054    'zeroinferior', 'oneinferior', 'twoinferior', 'threeinferior', 'fourinferior', 'fiveinferior', 'sixinferior',
3055    'seveninferior', 'eightinferior', 'nineinferior', 'centinferior', 'dollarinferior', 'periodinferior',
3056    'commainferior', 'Agravesmall', 'Aacutesmall', 'Acircumflexsmall', 'Atildesmall', 'Adieresissmall',
3057    'Aringsmall', 'AEsmall', 'Ccedillasmall', 'Egravesmall', 'Eacutesmall', 'Ecircumflexsmall', 'Edieresissmall',
3058    'Igravesmall', 'Iacutesmall', 'Icircumflexsmall', 'Idieresissmall', 'Ethsmall', 'Ntildesmall', 'Ogravesmall',
3059    'Oacutesmall', 'Ocircumflexsmall', 'Otildesmall', 'Odieresissmall', 'OEsmall', 'Oslashsmall', 'Ugravesmall',
3060    'Uacutesmall', 'Ucircumflexsmall', 'Udieresissmall', 'Yacutesmall', 'Thornsmall', 'Ydieresissmall', '001.000',
3061    '001.001', '001.002', '001.003', 'Black', 'Bold', 'Book', 'Light', 'Medium', 'Regular', 'Roman', 'Semibold'];
3062
3063var cffStandardEncoding = [
3064    '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '',
3065    '', '', '', '', 'space', 'exclam', 'quotedbl', 'numbersign', 'dollar', 'percent', 'ampersand', 'quoteright',
3066    'parenleft', 'parenright', 'asterisk', 'plus', 'comma', 'hyphen', 'period', 'slash', 'zero', 'one', 'two',
3067    'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'colon', 'semicolon', 'less', 'equal', 'greater',
3068    'question', 'at', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S',
3069    'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'bracketleft', 'backslash', 'bracketright', 'asciicircum', 'underscore',
3070    'quoteleft', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't',
3071    'u', 'v', 'w', 'x', 'y', 'z', 'braceleft', 'bar', 'braceright', 'asciitilde', '', '', '', '', '', '', '', '',
3072    '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '',
3073    'exclamdown', 'cent', 'sterling', 'fraction', 'yen', 'florin', 'section', 'currency', 'quotesingle',
3074    'quotedblleft', 'guillemotleft', 'guilsinglleft', 'guilsinglright', 'fi', 'fl', '', 'endash', 'dagger',
3075    'daggerdbl', 'periodcentered', '', 'paragraph', 'bullet', 'quotesinglbase', 'quotedblbase', 'quotedblright',
3076    'guillemotright', 'ellipsis', 'perthousand', '', 'questiondown', '', 'grave', 'acute', 'circumflex', 'tilde',
3077    'macron', 'breve', 'dotaccent', 'dieresis', '', 'ring', 'cedilla', '', 'hungarumlaut', 'ogonek', 'caron',
3078    'emdash', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', 'AE', '', 'ordfeminine', '', '', '',
3079    '', 'Lslash', 'Oslash', 'OE', 'ordmasculine', '', '', '', '', '', 'ae', '', '', '', 'dotlessi', '', '',
3080    'lslash', 'oslash', 'oe', 'germandbls'];
3081
3082var cffExpertEncoding = [
3083    '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '',
3084    '', '', '', '', 'space', 'exclamsmall', 'Hungarumlautsmall', '', 'dollaroldstyle', 'dollarsuperior',
3085    'ampersandsmall', 'Acutesmall', 'parenleftsuperior', 'parenrightsuperior', 'twodotenleader', 'onedotenleader',
3086    'comma', 'hyphen', 'period', 'fraction', 'zerooldstyle', 'oneoldstyle', 'twooldstyle', 'threeoldstyle',
3087    'fouroldstyle', 'fiveoldstyle', 'sixoldstyle', 'sevenoldstyle', 'eightoldstyle', 'nineoldstyle', 'colon',
3088    'semicolon', 'commasuperior', 'threequartersemdash', 'periodsuperior', 'questionsmall', '', 'asuperior',
3089    'bsuperior', 'centsuperior', 'dsuperior', 'esuperior', '', '', 'isuperior', '', '', 'lsuperior', 'msuperior',
3090    'nsuperior', 'osuperior', '', '', 'rsuperior', 'ssuperior', 'tsuperior', '', 'ff', 'fi', 'fl', 'ffi', 'ffl',
3091    'parenleftinferior', '', 'parenrightinferior', 'Circumflexsmall', 'hyphensuperior', 'Gravesmall', 'Asmall',
3092    'Bsmall', 'Csmall', 'Dsmall', 'Esmall', 'Fsmall', 'Gsmall', 'Hsmall', 'Ismall', 'Jsmall', 'Ksmall', 'Lsmall',
3093    'Msmall', 'Nsmall', 'Osmall', 'Psmall', 'Qsmall', 'Rsmall', 'Ssmall', 'Tsmall', 'Usmall', 'Vsmall', 'Wsmall',
3094    'Xsmall', 'Ysmall', 'Zsmall', 'colonmonetary', 'onefitted', 'rupiah', 'Tildesmall', '', '', '', '', '', '', '',
3095    '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '',
3096    'exclamdownsmall', 'centoldstyle', 'Lslashsmall', '', '', 'Scaronsmall', 'Zcaronsmall', 'Dieresissmall',
3097    'Brevesmall', 'Caronsmall', '', 'Dotaccentsmall', '', '', 'Macronsmall', '', '', 'figuredash', 'hypheninferior',
3098    '', '', 'Ogoneksmall', 'Ringsmall', 'Cedillasmall', '', '', '', 'onequarter', 'onehalf', 'threequarters',
3099    'questiondownsmall', 'oneeighth', 'threeeighths', 'fiveeighths', 'seveneighths', 'onethird', 'twothirds', '',
3100    '', 'zerosuperior', 'onesuperior', 'twosuperior', 'threesuperior', 'foursuperior', 'fivesuperior',
3101    'sixsuperior', 'sevensuperior', 'eightsuperior', 'ninesuperior', 'zeroinferior', 'oneinferior', 'twoinferior',
3102    'threeinferior', 'fourinferior', 'fiveinferior', 'sixinferior', 'seveninferior', 'eightinferior',
3103    'nineinferior', 'centinferior', 'dollarinferior', 'periodinferior', 'commainferior', 'Agravesmall',
3104    'Aacutesmall', 'Acircumflexsmall', 'Atildesmall', 'Adieresissmall', 'Aringsmall', 'AEsmall', 'Ccedillasmall',
3105    'Egravesmall', 'Eacutesmall', 'Ecircumflexsmall', 'Edieresissmall', 'Igravesmall', 'Iacutesmall',
3106    'Icircumflexsmall', 'Idieresissmall', 'Ethsmall', 'Ntildesmall', 'Ogravesmall', 'Oacutesmall',
3107    'Ocircumflexsmall', 'Otildesmall', 'Odieresissmall', 'OEsmall', 'Oslashsmall', 'Ugravesmall', 'Uacutesmall',
3108    'Ucircumflexsmall', 'Udieresissmall', 'Yacutesmall', 'Thornsmall', 'Ydieresissmall'];
3109
3110var standardNames = [
3111    '.notdef', '.null', 'nonmarkingreturn', 'space', 'exclam', 'quotedbl', 'numbersign', 'dollar', 'percent',
3112    'ampersand', 'quotesingle', 'parenleft', 'parenright', 'asterisk', 'plus', 'comma', 'hyphen', 'period', 'slash',
3113    'zero', 'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'colon', 'semicolon', 'less',
3114    'equal', 'greater', 'question', 'at', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
3115    'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'bracketleft', 'backslash', 'bracketright',
3116    'asciicircum', 'underscore', 'grave', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
3117    'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'braceleft', 'bar', 'braceright', 'asciitilde',
3118    'Adieresis', 'Aring', 'Ccedilla', 'Eacute', 'Ntilde', 'Odieresis', 'Udieresis', 'aacute', 'agrave',
3119    'acircumflex', 'adieresis', 'atilde', 'aring', 'ccedilla', 'eacute', 'egrave', 'ecircumflex', 'edieresis',
3120    'iacute', 'igrave', 'icircumflex', 'idieresis', 'ntilde', 'oacute', 'ograve', 'ocircumflex', 'odieresis',
3121    'otilde', 'uacute', 'ugrave', 'ucircumflex', 'udieresis', 'dagger', 'degree', 'cent', 'sterling', 'section',
3122    'bullet', 'paragraph', 'germandbls', 'registered', 'copyright', 'trademark', 'acute', 'dieresis', 'notequal',
3123    'AE', 'Oslash', 'infinity', 'plusminus', 'lessequal', 'greaterequal', 'yen', 'mu', 'partialdiff', 'summation',
3124    'product', 'pi', 'integral', 'ordfeminine', 'ordmasculine', 'Omega', 'ae', 'oslash', 'questiondown',
3125    'exclamdown', 'logicalnot', 'radical', 'florin', 'approxequal', 'Delta', 'guillemotleft', 'guillemotright',
3126    'ellipsis', 'nonbreakingspace', 'Agrave', 'Atilde', 'Otilde', 'OE', 'oe', 'endash', 'emdash', 'quotedblleft',
3127    'quotedblright', 'quoteleft', 'quoteright', 'divide', 'lozenge', 'ydieresis', 'Ydieresis', 'fraction',
3128    'currency', 'guilsinglleft', 'guilsinglright', 'fi', 'fl', 'daggerdbl', 'periodce
vendor: 4,306 bytes, lines 3128-3273
3128ntered', 'quotesinglbase',
3129    'quotedblbase', 'perthousand', 'Acircumflex', 'Ecircumflex', 'Aacute', 'Edieresis', 'Egrave', 'Iacute',
3130    'Icircumflex', 'Idieresis', 'Igrave', 'Oacute', 'Ocircumflex', 'apple', 'Ograve', 'Uacute', 'Ucircumflex',
3131    'Ugrave', 'dotlessi', 'circumflex', 'tilde', 'macron', 'breve', 'dotaccent', 'ring', 'cedilla', 'hungarumlaut',
3132    'ogonek', 'caron', 'Lslash', 'lslash', 'Scaron', 'scaron', 'Zcaron', 'zcaron', 'brokenbar', 'Eth', 'eth',
3133    'Yacute', 'yacute', 'Thorn', 'thorn', 'minus', 'multiply', 'onesuperior', 'twosuperior', 'threesuperior',
3134    'onehalf', 'onequarter', 'threequarters', 'franc', 'Gbreve', 'gbreve', 'Idotaccent', 'Scedilla', 'scedilla',
3135    'Cacute', 'cacute', 'Ccaron', 'ccaron', 'dcroat'];
3136
3137/**
3138 * This is the encoding used for fonts created from scratch.
3139 * It loops through all glyphs and finds the appropriate unicode value.
3140 * Since it's linear time, other encodings will be faster.
3141 * @exports opentype.DefaultEncoding
3142 * @class
3143 * @constructor
3144 * @param {opentype.Font}
3145 */
3146function DefaultEncoding(font) {
3147    this.font = font;
3148}
3149
3150DefaultEncoding.prototype.charToGlyphIndex = function(c) {
3151    var code = c.codePointAt(0);
3152    var glyphs = this.font.glyphs;
3153    if (glyphs) {
3154        for (var i = 0; i < glyphs.length; i += 1) {
3155            var glyph = glyphs.get(i);
3156            for (var j = 0; j < glyph.unicodes.length; j += 1) {
3157                if (glyph.unicodes[j] === code) {
3158                    return i;
3159                }
3160            }
3161        }
3162    }
3163    return null;
3164};
3165
3166/**
3167 * @exports opentype.CmapEncoding
3168 * @class
3169 * @constructor
3170 * @param {Object} cmap - a object with the cmap encoded data
3171 */
3172function CmapEncoding(cmap) {
3173    this.cmap = cmap;
3174}
3175
3176/**
3177 * @param  {string} c - the character
3178 * @return {number} The glyph index.
3179 */
3180CmapEncoding.prototype.charToGlyphIndex = function(c) {
3181    return this.cmap.glyphIndexMap[c.codePointAt(0)] || 0;
3182};
3183
3184/**
3185 * @exports opentype.CffEncoding
3186 * @class
3187 * @constructor
3188 * @param {string} encoding - The encoding
3189 * @param {Array} charset - The character set.
3190 */
3191function CffEncoding(encoding, charset) {
3192    this.encoding = encoding;
3193    this.charset = charset;
3194}
3195
3196/**
3197 * @param  {string} s - The character
3198 * @return {number} The index.
3199 */
3200CffEncoding.prototype.charToGlyphIndex = function(s) {
3201    var code = s.codePointAt(0);
3202    var charName = this.encoding[code];
3203    return this.charset.indexOf(charName);
3204};
3205
3206/**
3207 * @exports opentype.GlyphNames
3208 * @class
3209 * @constructor
3210 * @param {Object} post
3211 */
3212function GlyphNames(post) {
3213    switch (post.version) {
3214        case 1:
3215            this.names = standardNames.slice();
3216            break;
3217        case 2:
3218            this.names = new Array(post.numberOfGlyphs);
3219            for (var i = 0; i < post.numberOfGlyphs; i++) {
3220                if (post.glyphNameIndex[i] < standardNames.length) {
3221                    this.names[i] = standardNames[post.glyphNameIndex[i]];
3222                } else {
3223                    this.names[i] = post.names[post.glyphNameIndex[i] - standardNames.length];
3224                }
3225            }
3226
3227            break;
3228        case 2.5:
3229            this.names = new Array(post.numberOfGlyphs);
3230            for (var i$1 = 0; i$1 < post.numberOfGlyphs; i$1++) {
3231                this.names[i$1] = standardNames[i$1 + post.glyphNameIndex[i$1]];
3232            }
3233
3234            break;
3235        case 3:
3236            this.names = [];
3237            break;
3238        default:
3239            this.names = [];
3240            break;
3241    }
3242}
3243
3244/**
3245 * Gets the index of a glyph by name.
3246 * @param  {string} name - The glyph name
3247 * @return {number} The index
3248 */
3249GlyphNames.prototype.nameToGlyphIndex = function(name) {
3250    return this.names.indexOf(name);
3251};
3252
3253/**
3254 * @param  {number} gid
3255 * @return {string}
3256 */
3257GlyphNames.prototype.glyphIndexToName = function(gid) {
3258    return this.names[gid];
3259};
3260
3261function addGlyphNamesAll(font) {
3262    var glyph;
3263    var glyphIndexMap = font.tables.cmap.glyphIndexMap;
3264    var charCodes = Object.keys(glyphIndexMap);
3265
3266    for (var i = 0; i < charCodes.length; i += 1) {
3267        var c = charCodes[i];
3268        var glyphIndex = glyphIndexMap[c];
3269        glyph = font.glyphs.get(glyphIndex);
3270        glyph.addUnicode(parseInt(c));
3271    }
3272
3273    for (var i$1 = 0; i$1 < font.glyphs.length; i$1 += 1) {
3274        glyph = font.glyphs.get(i$1);
3275        if (font.cffEncoding) {
3276            if (font.isCIDFont) {
3277                glyph.name = 'gid' + i$1;
3278            } else {
3279                glyph.name = font.cffEncoding.charset[i$1];
3280            }
3281        } else if (font.glyphNames.names) {
3282            glyph.name = font.glyphNames.glyphIndexToName(i$1);
3283        }
3284    }
3285}
3286
3287function addGlyphNamesToUnicodeMap(font) {
3288    font._IndexToUnicodeMap = {};
3289
3290    var glyphIndexMap = font.tables.cmap.glyphIndexMap;
3291    var charCodes = Object.keys(glyphIndexMap);
3292
3293    for (var i = 0; i < charCodes.length; i += 1) {
3294        var c = charCodes[i];
3295        var glyphIndex = glyphIndexMap[c];
3296        if (font._IndexToUnicodeMap[glyphIndex] === undefined) {
3297            font._IndexToUnicodeMap[glyphIndex] = {
3298                unicodes: [parseInt(c)]
3299            };
3300        } else {
3301            font._IndexToUnicodeMap[glyphIndex].unicodes.push(parseInt(c));
3302        }
3303    }
3304}
3305
3306/**
3307 * @alias opentype.addGlyphNames
3308 * @param {opentype.Font}
3309 * @param {Object}
3310 */
3311function addGlyphNames(font, opt) {
3312    if (opt.lowMemory) {
3313        addGlyphNamesToUnicodeMap(font);
3314    } else {
3315        addGlyphNamesAll(font);
3316    }
3317}
3318
3319// Drawing utility functions.
3320
3321// Draw a line on the given context from point `x1,y1` to point `x2,y2`.
3322function line(ctx, x1, y1, x2, y2) {
3323    ctx.beginPath();
3324    ctx.moveTo(x1, y1);
3325    ctx.lineTo(x2, y2);
3326    ctx.stroke();
3327}
3328
3329var draw = { line: line };
3330
3331// The Glyph object
3332// import glyf from './tables/glyf' Can't be imported here, because it's a circular dependency
3333
3334function getPathDefinition(glyph, path) {
3335    var _path = path || new Path();
3336    return {
3337        configurable: true,
3338
3339        get: function() {
3340            if (typeof _path === 'function') {
3341                _path = _path();
3342            }
3343
3344            return _path;
3345        },
3346
3347        set: function(p) {
3348            _path = p;
3349        }
3350    };
3351}
3352/**
3353 * @typedef GlyphOptions
3354 * @type Object
3355 * @property {string} [name] - The glyph name
3356 * @property {number} [unicode]
3357 * @property {Array} [unicodes]
3358 * @property {number} [xMin]
3359 * @property {number} [yMin]
3360 * @property {number} [xMax]
3361 * @property {number} [yMax]
3362 * @property {number} [advanceWidth]
3363 */
3364
3365// A Glyph is an individual mark that often corresponds to a character.
3366// Some glyphs, such as ligatures, are a combination of many characters.
3367// Glyphs are the basic building blocks of a font.
3368//
3369// The `Glyph` class contains utility methods for drawing the path and its points.
3370/**
3371 * @exports opentype.Glyph
3372 * @class
3373 * @param {GlyphOptions}
3374 * @constructor
3375 */
3376function Glyph(options) {
3377    // By putting all the code on a prototype function (which is only declared once)
3378    // we reduce the memory requirements for larger fonts by some 2%
3379    this.bindConstructorValues(options);
3380}
3381
3382/**
3383 * @param  {GlyphOptions}
3384 */
3385Glyph.prototype.bindConstructorValues = function(options) {
3386    this.index = options.index || 0;
3387
3388    // These three values cannot be deferred for memory optimization:
3389    this.name = options.name || null;
3390    this.unicode = options.unicode || undefined;
3391    this.unicodes = options.unicodes || options.unicode !== undefined ? [options.unicode] : [];
3392
3393    // But by binding these values only when necessary, we reduce can
3394    // the memory requirements by almost 3% for larger fonts.
3395    if ('xMin' in options) {
3396        this.xMin = options.xMin;
3397    }
3398
3399    if ('yMin' in options) {
3400        this.yMin = options.yMin;
3401    }
3402
3403    if ('xMax' in options) {
3404        this.xMax = options.xMax;
3405    }
3406
3407    if ('yMax' in options) {
3408        this.yMax = options.yMax;
3409    }
3410
3411    if ('advanceWidth' in options) {
3412        this.advanceWidth = options.advanceWidth;
3413    }
3414
3415    // The path for a glyph is the most memory intensive, and is bound as a value
3416    // with a getter/setter to ensure we actually do path parsing only once the
3417    // path is actually needed by anything.
3418    Object.defineProperty(this, 'path', getPathDefinition(this, options.path));
3419};
3420
3421/**
3422 * @param {number}
3423 */
3424Glyph.prototype.addUnicode = function(unicode) {
3425    if (this.unicodes.length === 0) {
3426        this.unicode = unicode;
3427    }
3428
3429    this.unicodes.push(unicode);
3430};
3431
3432/**
3433 * Calculate the minimum bounding box for this glyph.
3434 * @return {opentype.BoundingBox}
3435 */
3436Glyph.prototype.getBoundingBox = function() {
3437    return this.path.getBoundingBox();
3438};
3439
3440/**
3441 * Convert the glyph to a Path we can draw on a drawing context.
3442 * @param  {number} [x=0] - Horizontal position of the beginning of the text.
3443 * @param  {number} [y=0] - Vertical position of the *baseline* of the text.
3444 * @param  {number} [fontSize=72] - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`.
3445 * @param  {Object=} options - xScale, yScale to stretch the glyph.
3446 * @param  {opentype.Font} if hinting is to be used, the font
3447 * @return {opentype.Path}
3448 */
3449Glyph.prototype.getPath = function(x, y, fontSize, options, font) {
3450    x = x !== undefined ? x : 0;
3451    y = y !== undefined ? y : 0;
3452    fontSize = fontSize !== undefined ? fontSize : 72;
3453    var commands;
3454    var hPoints;
3455    if (!options) { options = { }; }
3456    var xScale = options.xScale;
3457    var yScale = options.yScale;
3458
3459    if (options.hinting && font && font.hinting) {
3460        // in case of hinting, the hinting engine takes care
3461        // of scaling the points (not the path) before hinting.
3462        hPoints = this.path && font.hinting.exec(this, fontSize);
3463        // in case the hinting engine failed hPoints is undefined
3464        // and thus reverts to plain rending
3465    }
3466
3467    if (hPoints) {
3468        // Call font.hinting.getCommands instead of `glyf.getPath(hPoints).commands` to avoid a circular dependency
3469        commands = font.hinting.getCommands(hPoints);
3470        x = Math.round(x);
3471        y = Math.round(y);
3472        // TODO in case of hinting xyScaling is not yet supported
3473        xScale = yScale = 1;
3474    } else {
3475        commands = this.path.commands;
3476        var scale = 1 / (this.path.unitsPerEm || 1000) * fontSize;
3477        if (xScale === undefined) { xScale = scale; }
3478        if (yScale === undefined) { yScale = scale; }
3479    }
3480
3481    var p = new Path();
3482    for (var i = 0; i < commands.length; i += 1) {
3483        var cmd = commands[i];
3484        if (cmd.type === 'M') {
3485            p.moveTo(x + (cmd.x * xScale), y + (-cmd.y * yScale));
3486        } else if (cmd.type === 'L') {
3487            p.lineTo(x + (cmd.x * xScale), y + (-cmd.y * yScale));
3488        } else if (cmd.type === 'Q') {
3489            p.quadraticCurveTo(x + (cmd.x1 * xScale), y + (-cmd.y1 * yScale),
3490                               x + (cmd.x * xScale), y + (-cmd.y * yScale));
3491        } else if (cmd.type === 'C') {
3492            p.curveTo(x + (cmd.x1 * xScale), y + (-cmd.y1 * yScale),
3493                      x + (cmd.x2 * xScale), y + (-cmd.y2 * yScale),
3494                      x + (cmd.x * xScale), y + (-cmd.y * yScale));
3495        } else if (cmd.type === 'Z') {
3496            p.closePath();
3497        }
3498    }
3499
3500    return p;
3501};
3502
3503/**
3504 * Split the glyph into contours.
3505 * This function is here for backwards compatibility, and to
3506 * provide raw access to the TrueType glyph outlines.
3507 * @return {Array}
3508 */
3509Glyph.prototype.getContours = function() {
3510    if (this.points === undefined) {
3511        return [];
3512    }
3513
3514    var contours = [];
3515    var currentContour = [];
3516    for (var i = 0; i < this.points.length; i += 1) {
3517        var pt = this.points[i];
3518        currentContour.push(pt);
3519        if (pt.lastPointOfContour) {
3520            contours.push(currentContour);
3521            currentContour = [];
3522        }
3523    }
3524
3525    check.argument(currentContour.length === 0, 'There are still points left in the current contour.');
3526    return contours;
3527};
3528
3529/**
3530 * Calculate the xMin/yMin/xMax/yMax/lsb/rsb for a Glyph.
3531 * @return {Object}
3532 */
3533Glyph.prototype.getMetrics = function() {
3534    var commands = this.path.commands;
3535    var xCoords = [];
3536    var yCoords = [];
3537    for (var i = 0; i < commands.length; i += 1) {
3538        var cmd = commands[i];
3539        if (cmd.type !== 'Z') {
3540            xCoords.push(cmd.x);
3541            yCoords.push(cmd.y);
3542        }
3543
3544        if (cmd.type === 'Q' || cmd.type === 'C') {
3545            xCoords.push(cmd.x1);
3546            yCoords.push(cmd.y1);
3547        }
3548
3549        if (cmd.type === 'C') {
3550            xCoords.push(cmd.x2);
3551            yCoords.push(cmd.y2);
3552        }
3553    }
3554
3555    var metrics = {
3556        xMin: Math.min.apply(null, xCoords),
3557        yMin: Math.min.apply(null, yCoords),
3558        xMax: Math.max.apply(null, xCoords),
3559        yMax: Math.max.apply(null, yCoords),
3560        leftSideBearing: this.leftSideBearing
3561    };
3562
3563    if (!isFinite(metrics.xMin)) {
3564        metrics.xMin = 0;
3565    }
3566
3567    if (!isFinite(metrics.xMax)) {
3568        metrics.xMax = this.advanceWidth;
3569    }
3570
3571    if (!isFinite(metrics.yMin)) {
3572        metrics.yMin = 0;
3573    }
3574
3575    if (!isFinite(metrics.yMax)) {
3576        metrics.yMax = 0;
3577    }
3578
3579    metrics.rightSideBearing = this.advanceWidth - metrics.leftSideBearing - (metrics.xMax - metrics.xMin);
3580    return metrics;
3581};
3582
3583/**
3584 * Draw the glyph on the given context.
3585 * @param  {CanvasRenderingContext2D} ctx - A 2D drawing context, like Canvas.
3586 * @param  {number} [x=0] - Horizontal position of the beginning of the text.
3587 * @param  {number} [y=0] - Vertical position of the *baseline* of the text.
3588 * @param  {number} [fontSize=72] - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`.
3589 * @param  {Object=} options - xScale, yScale to stretch the glyph.
3590 */
3591Glyph.prototype.draw = function(ctx, x, y, fontSize, options) {
3592    this.getPath(x, y, fontSize, options).draw(ctx);
3593};
3594
3595/**
3596 * Draw the points of the glyph.
3597 * On-curve points will be drawn in blue, off-curve points will be drawn in red.
3598 * @param  {CanvasRenderingContext2D} ctx - A 2D drawing context, like Canvas.
3599 * @param  {number} [x=0] - Horizontal position of the beginning of the text.
3600 * @param  {number} [y=0] - Vertical position of the *baseline* of the text.
3601 * @param  {number} [fontSize=72] - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`.
3602 */
3603Glyph.prototype.drawPoints = function(ctx, x, y, fontSize) {
3604    function drawCircles(l, x, y, scale) {
3605        ctx.beginPath();
3606        for (var j = 0; j < l.length; j += 1) {
3607            ctx.moveTo(x + (l[j].x * scale), y + (l[j].y * scale));
3608            ctx.arc(x + (l[j].x * scale), y + (l[j].y * scale), 2, 0, Math.PI * 2, false);
3609        }
3610
3611        ctx.closePath();
3612        ctx.fill();
3613    }
3614
3615    x = x !== undefined ? x : 0;
3616    y = y !== undefined ? y : 0;
3617    fontSize = fontSize !== undefined ? fontSize : 24;
3618    var scale = 1 / this.path.unitsPerEm * fontSize;
3619
3620    var blueCircles = [];
3621    var redCircles = [];
3622    var path = this.path;
3623    for (var i = 0; i < path.commands.length; i += 1) {
3624        var cmd = path.commands[i];
3625        if (cmd.x !== undefined) {
3626            blueCircles.push({x: cmd.x, y: -cmd.y});
3627        }
3628
3629        if (cmd.x1 !== undefined) {
3630            redCircles.push({x: cmd.x1, y: -cmd.y1});
3631        }
3632
3633        if (cmd.x2 !== undefined) {
3634            redCircles.push({x: cmd.x2, y: -cmd.y2});
3635        }
3636    }
3637
3638    ctx.fillStyle = 'blue';
3639    drawCircles(blueCircles, x, y, scale);
3640    ctx.fillStyle = 'red';
3641    drawCircles(redCircles, x, y, scale);
3642};
3643
3644/**
3645 * Draw lines indicating important font measurements.
3646 * Black lines indicate the origin of the coordinate system (point 0,0).
3647 * Blue lines indicate the glyph bounding box.
3648 * Green line indicates the advance width of the glyph.
3649 * @param  {CanvasRenderingContext2D} ctx - A 2D drawing context, like Canvas.
3650 * @param  {number} [x=0] - Horizontal position of the beginning of the text.
3651 * @param  {number} [y=0] - Vertical position of the *baseline* of the text.
3652 * @param  {number} [fontSize=72] - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`.
3653 */
3654Glyph.prototype.drawMetrics = function(ctx, x, y, fontSize) {
3655    var scale;
3656    x = x !== undefined ? x : 0;
3657    y = y !== undefined ? y : 0;
3658    fontSize = fontSize !== undefined ? fontSize : 24;
3659    scale = 1 / this.path.unitsPerEm * fontSize;
3660    ctx.lineWidth = 1;
3661
3662    // Draw the origin
3663    ctx.strokeStyle = 'black';
3664    draw.line(ctx, x, -10000, x, 10000);
3665    draw.line(ctx, -10000, y, 10000, y);
3666
3667    // This code is here due to memory optimization: by not using
3668    // defaults in the constructor, we save a notable amount of memory.
3669    var xMin = this.xMin || 0;
3670    var yMin = this.yMin || 0;
3671    var xMax = this.xMax || 0;
3672    var yMax = this.yMax || 0;
3673    var advanceWidth = this.advanceWidth || 0;
3674
3675    // Draw the glyph box
3676    ctx.strokeStyle = 'blue';
3677    draw.line(ctx, x + (xMin * scale), -10000, x + (xMin * scale), 10000);
3678    draw.line(ctx, x + (xMax * scale), -10000, x + (xMax * scale), 10000);
3679    draw.line(ctx, -10000, y + (-yMin * scale), 10000, y + (-yMin * scale));
3680    draw.line(ctx, -10000, y + (-yMax * scale), 10000, y + (-yMax * scale));
3681
3682    // Draw the advance width
3683    ctx.strokeStyle = 'green';
3684    draw.line(ctx, x + (advanceWidth * scale), -10000, x + (advanceWidth * scale), 10000);
3685};
3686
3687// The GlyphSet object
3688
3689// Define a property on the glyph that depends on the path being loaded.
3690function defineDependentProperty(glyph, externalName, internalName) {
3691    Object.defineProperty(glyph, externalName, {
3692        get: function() {
3693            // Request the path property to make sure the path is loaded.
3694            glyph.path; // jshint ignore:line
3695            return glyph[internalName];
3696        },
3697        set: function(newValue) {
3698            glyph[internalName] = newValue;
3699        },
3700        enumerable: true,
3701        configurable: true
3702    });
3703}
3704
3705/**
3706 * A GlyphSet represents all glyphs available in the font, but modelled using
3707 * a deferred glyph loader, for retrieving glyphs only once they are absolutely
3708 * necessary, to keep the memory footprint down.
3709 * @exports opentype.GlyphSet
3710 * @class
3711 * @param {opentype.Font}
3712 * @param {Array}
3713 */
3714function GlyphSet(font, glyphs) {
3715    this.font = font;
3716    this.glyphs = {};
3717    if (Array.isArray(glyphs)) {
3718        for (var i = 0; i < glyphs.length; i++) {
3719            var glyph = glyphs[i];
3720            glyph.path.unitsPerEm = font.unitsPerEm;
3721            this.glyphs[i] = glyph;
3722        }
3723    }
3724
3725    this.length = (glyphs && glyphs.length) || 0;
3726}
3727
3728/**
3729 * @param  {number} index
3730 * @return {opentype.Glyph}
3731 */
3732GlyphSet.prototype.get = function(index) {
3733    // this.glyphs[index] is 'undefined' when low memory mode is on. glyph is pushed on request only.
3734    if (this.glyphs[index] === undefined) {
3735        this.font._push(index);
3736        if (typeof this.glyphs[index] === 'function') {
3737            this.glyphs[index] = this.glyphs[index]();
3738        }
3739
3740        var glyph = this.glyphs[index];
3741        var unicodeObj = this.font._IndexToUnicodeMap[index];
3742
3743        if (unicodeObj) {
3744            for (var j = 0; j < unicodeObj.unicodes.length; j++)
3745                { glyph.addUnicode(unicodeObj.unicodes[j]); }
3746        }
3747
3748        if (this.font.cffEncoding) {
3749            if (this.font.isCIDFont) {
3750                glyph.name = 'gid' + index;
3751            } else {
3752                glyph.name = this.font.cffEncoding.charset[index];
3753            }
3754        } else if (this.font.glyphNames.names) {
3755            glyph.name = this.font.glyphNames.glyphIndexToName(index);
3756        }
3757
3758        this.glyphs[index].advanceWidth = this.font._hmtxTableData[index].advanceWidth;
3759        this.glyphs[index].leftSideBearing = this.font._hmtxTableData[index].leftSideBearing;
3760    } else {
3761        if (typeof this.glyphs[index] === 'function') {
3762            this.glyphs[index] = this.glyphs[index]();
3763        }
3764    }
3765
3766    return this.glyphs[index];
3767};
3768
3769/**
3770 * @param  {number} index
3771 * @param  {Object}
3772 */
3773GlyphSet.prototype.push = function(index, loader) {
3774    this.glyphs[index] = loader;
3775    this.length++;
3776};
3777
3778/**
3779 * @alias opentype.glyphLoader
3780 * @param  {opentype.Font} font
3781 * @param  {number} index
3782 * @return {opentype.Glyph}
3783 */
3784function glyphLoader(font, index) {
3785    return new Glyph({index: index, font: font});
3786}
3787
3788/**
3789 * Generate a stub glyph that can be filled with all metadata *except*
3790 * the "points" and "path" properties, which must be loaded only once
3791 * the glyph's path is actually requested for text shaping.
3792 * @alias opentype.ttfGlyphLoader
3793 * @param  {opentype.Font} font
3794 * @param  {number} index
3795 * @param  {Function} parseGlyph
3796 * @param  {Object} data
3797 * @param  {number} position
3798 * @param  {Function} buildPath
3799 * @return {opentype.Glyph}
3800 */
3801function ttfGlyphLoader(font, index, parseGlyph, data, position, buildPath) {
3802    return function() {
3803        var glyph = new Glyph({index: index, font: font});
3804
3805        glyph.path = function() {
3806            parseGlyph(glyph, data, position);
3807            var path = buildPath(font.glyphs, glyph);
3808            path.unitsPerEm = font.unitsPerEm;
3809            return path;
3810        };
3811
3812        defineDependentProperty(glyph, 'xMin', '_xMin');
3813        defineDependentProperty(glyph, 'xMax', '_xMax');
3814        defineDependentProperty(glyph, 'yMin', '_yMin');
3815        defineDependentProperty(glyph, 'yMax', '_yMax');
3816
3817        return glyph;
3818    };
3819}
3820/**
3821 * @alias opentype.cffGlyphLoader
3822 * @param  {opentype.Font} font
3823 * @param  {number} index
3824 * @param  {Function} parseCFFCharstring
3825 * @param  {string} charstring
3826 * @return {opentype.Glyph}
3827 */
3828function cffGlyphLoader(font, index, parseCFFCharstring, charstring) {
3829    return function() {
3830        var glyph = new Glyph({index: index, font: font});
3831
3832        glyph.path = function() {
3833            var path = parseCFFCharstring(font, glyph, charstring);
3834            path.unitsPerEm = font.unitsPerEm;
3835            return path;
3836        };
3837
3838        return glyph;
3839    };
3840}
3841
3842var glyphset = { GlyphSet: GlyphSet, glyphLoader: glyphLoader, ttfGlyphLoader: ttfGlyphLoader, cffGlyphLoader: cffGlyphLoader };
3843
3844// The `CFF` table contains the glyph outlines in PostScript format.
3845
3846// Custom equals function that can also check lists.
3847function equals(a, b) {
3848    if (a === b) {
3849        return true;
3850    } else if (Array.isArray(a) && Array.isArray(b)) {
3851        if (a.length !== b.length) {
3852            return false;
vendor: 34,444 bytes, lines 3853-4795
3853        }
3854
3855        for (var i = 0; i < a.length; i += 1) {
3856            if (!equals(a[i], b[i])) {
3857                return false;
3858            }
3859        }
3860
3861        return true;
3862    } else {
3863        return false;
3864    }
3865}
3866
3867// Subroutines are encoded using the negative half of the number space.
3868// See type 2 chapter 4.7 "Subroutine operators".
3869function calcCFFSubroutineBias(subrs) {
3870    var bias;
3871    if (subrs.length < 1240) {
3872        bias = 107;
3873    } else if (subrs.length < 33900) {
3874        bias = 1131;
3875    } else {
3876        bias = 32768;
3877    }
3878
3879    return bias;
3880}
3881
3882// Parse a `CFF` INDEX array.
3883// An index array consists of a list of offsets, then a list of objects at those offsets.
3884function parseCFFIndex(data, start, conversionFn) {
3885    var offsets = [];
3886    var objects = [];
3887    var count = parse.getCard16(data, start);
3888    var objectOffset;
3889    var endOffset;
3890    if (count !== 0) {
3891        var offsetSize = parse.getByte(data, start + 2);
3892        objectOffset = start + ((count + 1) * offsetSize) + 2;
3893        var pos = start + 3;
3894        for (var i = 0; i < count + 1; i += 1) {
3895            offsets.push(parse.getOffset(data, pos, offsetSize));
3896            pos += offsetSize;
3897        }
3898
3899        // The total size of the index array is 4 header bytes + the value of the last offset.
3900        endOffset = objectOffset + offsets[count];
3901    } else {
3902        endOffset = start + 2;
3903    }
3904
3905    for (var i$1 = 0; i$1 < offsets.length - 1; i$1 += 1) {
3906        var value = parse.getBytes(data, objectOffset + offsets[i$1], objectOffset + offsets[i$1 + 1]);
3907        if (conversionFn) {
3908            value = conversionFn(value);
3909        }
3910
3911        objects.push(value);
3912    }
3913
3914    return {objects: objects, startOffset: start, endOffset: endOffset};
3915}
3916
3917function parseCFFIndexLowMemory(data, start) {
3918    var offsets = [];
3919    var count = parse.getCard16(data, start);
3920    var objectOffset;
3921    var endOffset;
3922    if (count !== 0) {
3923        var offsetSize = parse.getByte(data, start + 2);
3924        objectOffset = start + ((count + 1) * offsetSize) + 2;
3925        var pos = start + 3;
3926        for (var i = 0; i < count + 1; i += 1) {
3927            offsets.push(parse.getOffset(data, pos, offsetSize));
3928            pos += offsetSize;
3929        }
3930
3931        // The total size of the index array is 4 header bytes + the value of the last offset.
3932        endOffset = objectOffset + offsets[count];
3933    } else {
3934        endOffset = start + 2;
3935    }
3936
3937    return {offsets: offsets, startOffset: start, endOffset: endOffset};
3938}
3939function getCffIndexObject(i, offsets, data, start, conversionFn) {
3940    var count = parse.getCard16(data, start);
3941    var objectOffset = 0;
3942    if (count !== 0) {
3943        var offsetSize = parse.getByte(data, start + 2);
3944        objectOffset = start + ((count + 1) * offsetSize) + 2;
3945    }
3946
3947    var value = parse.getBytes(data, objectOffset + offsets[i], objectOffset + offsets[i + 1]);
3948    if (conversionFn) {
3949        value = conversionFn(value);
3950    }
3951    return value;
3952}
3953
3954// Parse a `CFF` DICT real value.
3955function parseFloatOperand(parser) {
3956    var s = '';
3957    var eof = 15;
3958    var lookup = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '.', 'E', 'E-', null, '-'];
3959    while (true) {
3960        var b = parser.parseByte();
3961        var n1 = b >> 4;
3962        var n2 = b & 15;
3963
3964        if (n1 === eof) {
3965            break;
3966        }
3967
3968        s += lookup[n1];
3969
3970        if (n2 === eof) {
3971            break;
3972        }
3973
3974        s += lookup[n2];
3975    }
3976
3977    return parseFloat(s);
3978}
3979
3980// Parse a `CFF` DICT operand.
3981function parseOperand(parser, b0) {
3982    var b1;
3983    var b2;
3984    var b3;
3985    var b4;
3986    if (b0 === 28) {
3987        b1 = parser.parseByte();
3988        b2 = parser.parseByte();
3989        return b1 << 8 | b2;
3990    }
3991
3992    if (b0 === 29) {
3993        b1 = parser.parseByte();
3994        b2 = parser.parseByte();
3995        b3 = parser.parseByte();
3996        b4 = parser.parseByte();
3997        return b1 << 24 | b2 << 16 | b3 << 8 | b4;
3998    }
3999
4000    if (b0 === 30) {
4001        return parseFloatOperand(parser);
4002    }
4003
4004    if (b0 >= 32 && b0 <= 246) {
4005        return b0 - 139;
4006    }
4007
4008    if (b0 >= 247 && b0 <= 250) {
4009        b1 = parser.parseByte();
4010        return (b0 - 247) * 256 + b1 + 108;
4011    }
4012
4013    if (b0 >= 251 && b0 <= 254) {
4014        b1 = parser.parseByte();
4015        return -(b0 - 251) * 256 - b1 - 108;
4016    }
4017
4018    throw new Error('Invalid b0 ' + b0);
4019}
4020
4021// Convert the entries returned by `parseDict` to a proper dictionary.
4022// If a value is a list of one, it is unpacked.
4023function entriesToObject(entries) {
4024    var o = {};
4025    for (var i = 0; i < entries.length; i += 1) {
4026        var key = entries[i][0];
4027        var values = entries[i][1];
4028        var value = (void 0);
4029        if (values.length === 1) {
4030            value = values[0];
4031        } else {
4032            value = values;
4033        }
4034
4035        if (o.hasOwnProperty(key) && !isNaN(o[key])) {
4036            throw new Error('Object ' + o + ' already has key ' + key);
4037        }
4038
4039        o[key] = value;
4040    }
4041
4042    return o;
4043}
4044
4045// Parse a `CFF` DICT object.
4046// A dictionary contains key-value pairs in a compact tokenized format.
4047function parseCFFDict(data, start, size) {
4048    start = start !== undefined ? start : 0;
4049    var parser = new parse.Parser(data, start);
4050    var entries = [];
4051    var operands = [];
4052    size = size !== undefined ? size : data.length;
4053
4054    while (parser.relativeOffset < size) {
4055        var op = parser.parseByte();
4056
4057        // The first byte for each dict item distinguishes between operator (key) and operand (value).
4058        // Values <= 21 are operators.
4059        if (op <= 21) {
4060            // Two-byte operators have an initial escape byte of 12.
4061            if (op === 12) {
4062                op = 1200 + parser.parseByte();
4063            }
4064
4065            entries.push([op, operands]);
4066            operands = [];
4067        } else {
4068            // Since the operands (values) come before the operators (keys), we store all operands in a list
4069            // until we encounter an operator.
4070            operands.push(parseOperand(parser, op));
4071        }
4072    }
4073
4074    return entriesToObject(entries);
4075}
4076
4077// Given a String Index (SID), return the value of the string.
4078// Strings below index 392 are standard CFF strings and are not encoded in the font.
4079function getCFFString(strings, index) {
4080    if (index <= 390) {
4081        index = cffStandardStrings[index];
4082    } else {
4083        index = strings[index - 391];
4084    }
4085
4086    return index;
4087}
4088
4089// Interpret a dictionary and return a new dictionary with readable keys and values for missing entries.
4090// This function takes `meta` which is a list of objects containing `operand`, `name` and `default`.
4091function interpretDict(dict, meta, strings) {
4092    var newDict = {};
4093    var value;
4094
4095    // Because we also want to include missing values, we start out from the meta list
4096    // and lookup values in the dict.
4097    for (var i = 0; i < meta.length; i += 1) {
4098        var m = meta[i];
4099
4100        if (Array.isArray(m.type)) {
4101            var values = [];
4102            values.length = m.type.length;
4103            for (var j = 0; j < m.type.length; j++) {
4104                value = dict[m.op] !== undefined ? dict[m.op][j] : undefined;
4105                if (value === undefined) {
4106                    value = m.value !== undefined && m.value[j] !== undefined ? m.value[j] : null;
4107                }
4108                if (m.type[j] === 'SID') {
4109                    value = getCFFString(strings, value);
4110                }
4111                values[j] = value;
4112            }
4113            newDict[m.name] = values;
4114        } else {
4115            value = dict[m.op];
4116            if (value === undefined) {
4117                value = m.value !== undefined ? m.value : null;
4118            }
4119
4120            if (m.type === 'SID') {
4121                value = getCFFString(strings, value);
4122            }
4123            newDict[m.name] = value;
4124        }
4125    }
4126
4127    return newDict;
4128}
4129
4130// Parse the CFF header.
4131function parseCFFHeader(data, start) {
4132    var header = {};
4133    header.formatMajor = parse.getCard8(data, start);
4134    header.formatMinor = parse.getCard8(data, start + 1);
4135    header.size = parse.getCard8(data, start + 2);
4136    header.offsetSize = parse.getCard8(data, start + 3);
4137    header.startOffset = start;
4138    header.endOffset = start + 4;
4139    return header;
4140}
4141
4142var TOP_DICT_META = [
4143    {name: 'version', op: 0, type: 'SID'},
4144    {name: 'notice', op: 1, type: 'SID'},
4145    {name: 'copyright', op: 1200, type: 'SID'},
4146    {name: 'fullName', op: 2, type: 'SID'},
4147    {name: 'familyName', op: 3, type: 'SID'},
4148    {name: 'weight', op: 4, type: 'SID'},
4149    {name: 'isFixedPitch', op: 1201, type: 'number', value: 0},
4150    {name: 'italicAngle', op: 1202, type: 'number', value: 0},
4151    {name: 'underlinePosition', op: 1203, type: 'number', value: -100},
4152    {name: 'underlineThickness', op: 1204, type: 'number', value: 50},
4153    {name: 'paintType', op: 1205, type: 'number', value: 0},
4154    {name: 'charstringType', op: 1206, type: 'number', value: 2},
4155    {
4156        name: 'fontMatrix',
4157        op: 1207,
4158        type: ['real', 'real', 'real', 'real', 'real', 'real'],
4159        value: [0.001, 0, 0, 0.001, 0, 0]
4160    },
4161    {name: 'uniqueId', op: 13, type: 'number'},
4162    {name: 'fontBBox', op: 5, type: ['number', 'number', 'number', 'number'], value: [0, 0, 0, 0]},
4163    {name: 'strokeWidth', op: 1208, type: 'number', value: 0},
4164    {name: 'xuid', op: 14, type: [], value: null},
4165    {name: 'charset', op: 15, type: 'offset', value: 0},
4166    {name: 'encoding', op: 16, type: 'offset', value: 0},
4167    {name: 'charStrings', op: 17, type: 'offset', value: 0},
4168    {name: 'private', op: 18, type: ['number', 'offset'], value: [0, 0]},
4169    {name: 'ros', op: 1230, type: ['SID', 'SID', 'number']},
4170    {name: 'cidFontVersion', op: 1231, type: 'number', value: 0},
4171    {name: 'cidFontRevision', op: 1232, type: 'number', value: 0},
4172    {name: 'cidFontType', op: 1233, type: 'number', value: 0},
4173    {name: 'cidCount', op: 1234, type: 'number', value: 8720},
4174    {name: 'uidBase', op: 1235, type: 'number'},
4175    {name: 'fdArray', op: 1236, type: 'offset'},
4176    {name: 'fdSelect', op: 1237, type: 'offset'},
4177    {name: 'fontName', op: 1238, type: 'SID'}
4178];
4179
4180var PRIVATE_DICT_META = [
4181    {name: 'subrs', op: 19, type: 'offset', value: 0},
4182    {name: 'defaultWidthX', op: 20, type: 'number', value: 0},
4183    {name: 'nominalWidthX', op: 21, type: 'number', value: 0}
4184];
4185
4186// Parse the CFF top dictionary. A CFF table can contain multiple fonts, each with their own top dictionary.
4187// The top dictionary contains the essential metadata for the font, together with the private dictionary.
4188function parseCFFTopDict(data, strings) {
4189    var dict = parseCFFDict(data, 0, data.byteLength);
4190    return interpretDict(dict, TOP_DICT_META, strings);
4191}
4192
4193// Parse the CFF private dictionary. We don't fully parse out all the values, only the ones we need.
4194function parseCFFPrivateDict(data, start, size, strings) {
4195    var dict = parseCFFDict(data, start, size);
4196    return interpretDict(dict, PRIVATE_DICT_META, strings);
4197}
4198
4199// Returns a list of "Top DICT"s found using an INDEX list.
4200// Used to read both the usual high-level Top DICTs and also the FDArray
4201// discovered inside CID-keyed fonts.  When a Top DICT has a reference to
4202// a Private DICT that is read and saved into the Top DICT.
4203//
4204// In addition to the expected/optional values as outlined in TOP_DICT_META
4205// the following values might be saved into the Top DICT.
4206//
4207//    _subrs []        array of local CFF subroutines from Private DICT
4208//    _subrsBias       bias value computed from number of subroutines
4209//                      (see calcCFFSubroutineBias() and parseCFFCharstring())
4210//    _defaultWidthX   default widths for CFF characters
4211//    _nominalWidthX   bias added to width embedded within glyph description
4212//
4213//    _privateDict     saved copy of parsed Private DICT from Top DICT
4214function gatherCFFTopDicts(data, start, cffIndex, strings) {
4215    var topDictArray = [];
4216    for (var iTopDict = 0; iTopDict < cffIndex.length; iTopDict += 1) {
4217        var topDictData = new DataView(new Uint8Array(cffIndex[iTopDict]).buffer);
4218        var topDict = parseCFFTopDict(topDictData, strings);
4219        topDict._subrs = [];
4220        topDict._subrsBias = 0;
4221        topDict._defaultWidthX = 0;
4222        topDict._nominalWidthX = 0;
4223        var privateSize = topDict.private[0];
4224        var privateOffset = topDict.private[1];
4225        if (privateSize !== 0 && privateOffset !== 0) {
4226            var privateDict = parseCFFPrivateDict(data, privateOffset + start, privateSize, strings);
4227            topDict._defaultWidthX = privateDict.defaultWidthX;
4228            topDict._nominalWidthX = privateDict.nominalWidthX;
4229            if (privateDict.subrs !== 0) {
4230                var subrOffset = privateOffset + privateDict.subrs;
4231                var subrIndex = parseCFFIndex(data, subrOffset + start);
4232                topDict._subrs = subrIndex.objects;
4233                topDict._subrsBias = calcCFFSubroutineBias(topDict._subrs);
4234            }
4235            topDict._privateDict = privateDict;
4236        }
4237        topDictArray.push(topDict);
4238    }
4239    return topDictArray;
4240}
4241
4242// Parse the CFF charset table, which contains internal names for all the glyphs.
4243// This function will return a list of glyph names.
4244// See Adobe TN #5176 chapter 13, "Charsets".
4245function parseCFFCharset(data, start, nGlyphs, strings) {
4246    var sid;
4247    var count;
4248    var parser = new parse.Parser(data, start);
4249
4250    // The .notdef glyph is not included, so subtract 1.
4251    nGlyphs -= 1;
4252    var charset = ['.notdef'];
4253
4254    var format = parser.parseCard8();
4255    if (format === 0) {
4256        for (var i = 0; i < nGlyphs; i += 1) {
4257            sid = parser.parseSID();
4258            charset.push(getCFFString(strings, sid));
4259        }
4260    } else if (format === 1) {
4261        while (charset.length <= nGlyphs) {
4262            sid = parser.parseSID();
4263            count = parser.parseCard8();
4264            for (var i$1 = 0; i$1 <= count; i$1 += 1) {
4265                charset.push(getCFFString(strings, sid));
4266                sid += 1;
4267            }
4268        }
4269    } else if (format === 2) {
4270        while (charset.length <= nGlyphs) {
4271            sid = parser.parseSID();
4272            count = parser.parseCard16();
4273            for (var i$2 = 0; i$2 <= count; i$2 += 1) {
4274                charset.push(getCFFString(strings, sid));
4275                sid += 1;
4276            }
4277        }
4278    } else {
4279        throw new Error('Unknown charset format ' + format);
4280    }
4281
4282    return charset;
4283}
4284
4285// Parse the CFF encoding data. Only one encoding can be specified per font.
4286// See Adobe TN #5176 chapter 12, "Encodings".
4287function parseCFFEncoding(data, start, charset) {
4288    var code;
4289    var enc = {};
4290    var parser = new parse.Parser(data, start);
4291    var format = parser.parseCard8();
4292    if (format === 0) {
4293        var nCodes = parser.parseCard8();
4294        for (var i = 0; i < nCodes; i += 1) {
4295            code = parser.parseCard8();
4296            enc[code] = i;
4297        }
4298    } else if (format === 1) {
4299        var nRanges = parser.parseCard8();
4300        code = 1;
4301        for (var i$1 = 0; i$1 < nRanges; i$1 += 1) {
4302            var first = parser.parseCard8();
4303            var nLeft = parser.parseCard8();
4304            for (var j = first; j <= first + nLeft; j += 1) {
4305                enc[j] = code;
4306                code += 1;
4307            }
4308        }
4309    } else {
4310        throw new Error('Unknown encoding format ' + format);
4311    }
4312
4313    return new CffEncoding(enc, charset);
4314}
4315
4316// Take in charstring code and return a Glyph object.
4317// The encoding is described in the Type 2 Charstring Format
4318// https://www.microsoft.com/typography/OTSPEC/charstr2.htm
4319function parseCFFCharstring(font, glyph, code) {
4320    var c1x;
4321    var c1y;
4322    var c2x;
4323    var c2y;
4324    var p = new Path();
4325    var stack = [];
4326    var nStems = 0;
4327    var haveWidth = false;
4328    var open = false;
4329    var x = 0;
4330    var y = 0;
4331    var subrs;
4332    var subrsBias;
4333    var defaultWidthX;
4334    var nominalWidthX;
4335    if (font.isCIDFont) {
4336        var fdIndex = font.tables.cff.topDict._fdSelect[glyph.index];
4337        var fdDict = font.tables.cff.topDict._fdArray[fdIndex];
4338        subrs = fdDict._subrs;
4339        subrsBias = fdDict._subrsBias;
4340        defaultWidthX = fdDict._defaultWidthX;
4341        nominalWidthX = fdDict._nominalWidthX;
4342    } else {
4343        subrs = font.tables.cff.topDict._subrs;
4344        subrsBias = font.tables.cff.topDict._subrsBias;
4345        defaultWidthX = font.tables.cff.topDict._defaultWidthX;
4346        nominalWidthX = font.tables.cff.topDict._nominalWidthX;
4347    }
4348    var width = defaultWidthX;
4349
4350    function newContour(x, y) {
4351        if (open) {
4352            p.closePath();
4353        }
4354
4355        p.moveTo(x, y);
4356        open = true;
4357    }
4358
4359    function parseStems() {
4360        var hasWidthArg;
4361
4362        // The number of stem operators on the stack is always even.
4363        // If the value is uneven, that means a width is specified.
4364        hasWidthArg = stack.length % 2 !== 0;
4365        if (hasWidthArg && !haveWidth) {
4366            width = stack.shift() + nominalWidthX;
4367        }
4368
4369        nStems += stack.length >> 1;
4370        stack.length = 0;
4371        haveWidth = true;
4372    }
4373
4374    function parse(code) {
4375        var b1;
4376        var b2;
4377        var b3;
4378        var b4;
4379        var codeIndex;
4380        var subrCode;
4381        var jpx;
4382        var jpy;
4383        var c3x;
4384        var c3y;
4385        var c4x;
4386        var c4y;
4387
4388        var i = 0;
4389        while (i < code.length) {
4390            var v = code[i];
4391            i += 1;
4392            switch (v) {
4393                case 1: // hstem
4394                    parseStems();
4395                    break;
4396                case 3: // vstem
4397                    parseStems();
4398                    break;
4399                case 4: // vmoveto
4400                    if (stack.length > 1 && !haveWidth) {
4401                        width = stack.shift() + nominalWidthX;
4402                        haveWidth = true;
4403                    }
4404
4405                    y += stack.pop();
4406                    newContour(x, y);
4407                    break;
4408                case 5: // rlineto
4409                    while (stack.length > 0) {
4410                        x += stack.shift();
4411                        y += stack.shift();
4412                        p.lineTo(x, y);
4413                    }
4414
4415                    break;
4416                case 6: // hlineto
4417                    while (stack.length > 0) {
4418                        x += stack.shift();
4419                        p.lineTo(x, y);
4420                        if (stack.length === 0) {
4421                            break;
4422                        }
4423
4424                        y += stack.shift();
4425                        p.lineTo(x, y);
4426                    }
4427
4428                    break;
4429                case 7: // vlineto
4430                    while (stack.length > 0) {
4431                        y += stack.shift();
4432                        p.lineTo(x, y);
4433                        if (stack.length === 0) {
4434                            break;
4435                        }
4436
4437                        x += stack.shift();
4438                        p.lineTo(x, y);
4439                    }
4440
4441                    break;
4442                case 8: // rrcurveto
4443                    while (stack.length > 0) {
4444                        c1x = x + stack.shift();
4445                        c1y = y + stack.shift();
4446                        c2x = c1x + stack.shift();
4447                        c2y = c1y + stack.shift();
4448                        x = c2x + stack.shift();
4449                        y = c2y + stack.shift();
4450                        p.curveTo(c1x, c1y, c2x, c2y, x, y);
4451                    }
4452
4453                    break;
4454                case 10: // callsubr
4455                    codeIndex = stack.pop() + subrsBias;
4456                    subrCode = subrs[codeIndex];
4457                    if (subrCode) {
4458                        parse(subrCode);
4459                    }
4460
4461                    break;
4462                case 11: // return
4463                    return;
4464                case 12: // flex operators
4465                    v = code[i];
4466                    i += 1;
4467                    switch (v) {
4468                        case 35: // flex
4469                            // |- dx1 dy1 dx2 dy2 dx3 dy3 dx4 dy4 dx5 dy5 dx6 dy6 fd flex (12 35) |-
4470                            c1x = x   + stack.shift();    // dx1
4471                            c1y = y   + stack.shift();    // dy1
4472                            c2x = c1x + stack.shift();    // dx2
4473                            c2y = c1y + stack.shift();    // dy2
4474                            jpx = c2x + stack.shift();    // dx3
4475                            jpy = c2y + stack.shift();    // dy3
4476                            c3x = jpx + stack.shift();    // dx4
4477                            c3y = jpy + stack.shift();    // dy4
4478                            c4x = c3x + stack.shift();    // dx5
4479                            c4y = c3y + stack.shift();    // dy5
4480                            x = c4x   + stack.shift();    // dx6
4481                            y = c4y   + stack.shift();    // dy6
4482                            stack.shift();                // flex depth
4483                            p.curveTo(c1x, c1y, c2x, c2y, jpx, jpy);
4484                            p.curveTo(c3x, c3y, c4x, c4y, x, y);
4485                            break;
4486                        case 34: // hflex
4487                            // |- dx1 dx2 dy2 dx3 dx4 dx5 dx6 hflex (12 34) |-
4488                            c1x = x   + stack.shift();    // dx1
4489                            c1y = y;                      // dy1
4490                            c2x = c1x + stack.shift();    // dx2
4491                            c2y = c1y + stack.shift();    // dy2
4492                            jpx = c2x + stack.shift();    // dx3
4493                            jpy = c2y;                    // dy3
4494                            c3x = jpx + stack.shift();    // dx4
4495                            c3y = c2y;                    // dy4
4496                            c4x = c3x + stack.shift();    // dx5
4497                            c4y = y;                      // dy5
4498                            x = c4x + stack.shift();      // dx6
4499                            p.curveTo(c1x, c1y, c2x, c2y, jpx, jpy);
4500                            p.curveTo(c3x, c3y, c4x, c4y, x, y);
4501                            break;
4502                        case 36: // hflex1
4503                            // |- dx1 dy1 dx2 dy2 dx3 dx4 dx5 dy5 dx6 hflex1 (12 36) |-
4504                            c1x = x   + stack.shift();    // dx1
4505                            c1y = y   + stack.shift();    // dy1
4506                            c2x = c1x + stack.shift();    // dx2
4507                            c2y = c1y + stack.shift();    // dy2
4508                            jpx = c2x + stack.shift();    // dx3
4509                            jpy = c2y;                    // dy3
4510                            c3x = jpx + stack.shift();    // dx4
4511                            c3y = c2y;                    // dy4
4512                            c4x = c3x + stack.shift();    // dx5
4513                            c4y = c3y + stack.shift();    // dy5
4514                            x = c4x + stack.shift();      // dx6
4515                            p.curveTo(c1x, c1y, c2x, c2y, jpx, jpy);
4516                            p.curveTo(c3x, c3y, c4x, c4y, x, y);
4517                            break;
4518                        case 37: // flex1
4519                            // |- dx1 dy1 dx2 dy2 dx3 dy3 dx4 dy4 dx5 dy5 d6 flex1 (12 37) |-
4520                            c1x = x   + stack.shift();    // dx1
4521                            c1y = y   + stack.shift();    // dy1
4522                            c2x = c1x + stack.shift();    // dx2
4523                            c2y = c1y + stack.shift();    // dy2
4524                            jpx = c2x + stack.shift();    // dx3
4525                            jpy = c2y + stack.shift();    // dy3
4526                            c3x = jpx + stack.shift();    // dx4
4527                            c3y = jpy + stack.shift();    // dy4
4528                            c4x = c3x + stack.shift();    // dx5
4529                            c4y = c3y + stack.shift();    // dy5
4530                            if (Math.abs(c4x - x) > Math.abs(c4y - y)) {
4531                                x = c4x + stack.shift();
4532                            } else {
4533                                y = c4y + stack.shift();
4534                            }
4535
4536                            p.curveTo(c1x, c1y, c2x, c2y, jpx, jpy);
4537                            p.curveTo(c3x, c3y, c4x, c4y, x, y);
4538                            break;
4539                        default:
4540                            console.log('Glyph ' + glyph.index + ': unknown operator ' + 1200 + v);
4541                            stack.length = 0;
4542                    }
4543                    break;
4544                case 14: // endchar
4545                    if (stack.length > 0 && !haveWidth) {
4546                        width = stack.shift() + nominalWidthX;
4547                        haveWidth = true;
4548                    }
4549
4550                    if (open) {
4551                        p.closePath();
4552                        open = false;
4553                    }
4554
4555                    break;
4556                case 18: // hstemhm
4557                    parseStems();
4558                    break;
4559                case 19: // hintmask
4560                case 20: // cntrmask
4561                    parseStems();
4562                    i += (nStems + 7) >> 3;
4563                    break;
4564                case 21: // rmoveto
4565                    if (stack.length > 2 && !haveWidth) {
4566                        width = stack.shift() + nominalWidthX;
4567                        haveWidth = true;
4568                    }
4569
4570                    y += stack.pop();
4571                    x += stack.pop();
4572                    newContour(x, y);
4573                    break;
4574                case 22: // hmoveto
4575                    if (stack.length > 1 && !haveWidth) {
4576                        width = stack.shift() + nominalWidthX;
4577                        haveWidth = true;
4578                    }
4579
4580                    x += stack.pop();
4581                    newContour(x, y);
4582                    break;
4583                case 23: // vstemhm
4584                    parseStems();
4585                    break;
4586                case 24: // rcurveline
4587                    while (stack.length > 2) {
4588                        c1x = x + stack.shift();
4589                        c1y = y + stack.shift();
4590                        c2x = c1x + stack.shift();
4591                        c2y = c1y + stack.shift();
4592                        x = c2x + stack.shift();
4593                        y = c2y + stack.shift();
4594                        p.curveTo(c1x, c1y, c2x, c2y, x, y);
4595                    }
4596
4597                    x += stack.shift();
4598                    y += stack.shift();
4599                    p.lineTo(x, y);
4600                    break;
4601                case 25: // rlinecurve
4602                    while (stack.length > 6) {
4603                        x += stack.shift();
4604                        y += stack.shift();
4605                        p.lineTo(x, y);
4606                    }
4607
4608                    c1x = x + stack.shift();
4609                    c1y = y + stack.shift();
4610                    c2x = c1x + stack.shift();
4611                    c2y = c1y + stack.shift();
4612                    x = c2x + stack.shift();
4613                    y = c2y + stack.shift();
4614                    p.curveTo(c1x, c1y, c2x, c2y, x, y);
4615                    break;
4616                case 26: // vvcurveto
4617                    if (stack.length % 2) {
4618                        x += stack.shift();
4619                    }
4620
4621                    while (stack.length > 0) {
4622                        c1x = x;
4623                        c1y = y + stack.shift();
4624                        c2x = c1x + stack.shift();
4625                        c2y = c1y + stack.shift();
4626                        x = c2x;
4627                        y = c2y + stack.shift();
4628                        p.curveTo(c1x, c1y, c2x, c2y, x, y);
4629                    }
4630
4631                    break;
4632                case 27: // hhcurveto
4633                    if (stack.length % 2) {
4634                        y += stack.shift();
4635                    }
4636
4637                    while (stack.length > 0) {
4638                        c1x = x + stack.shift();
4639                        c1y = y;
4640                        c2x = c1x + stack.shift();
4641                        c2y = c1y + stack.shift();
4642                        x = c2x + stack.shift();
4643                        y = c2y;
4644                        p.curveTo(c1x, c1y, c2x, c2y, x, y);
4645                    }
4646
4647                    break;
4648                case 28: // shortint
4649                    b1 = code[i];
4650                    b2 = code[i + 1];
4651                    stack.push(((b1 << 24) | (b2 << 16)) >> 16);
4652                    i += 2;
4653                    break;
4654                case 29: // callgsubr
4655                    codeIndex = stack.pop() + font.gsubrsBias;
4656                    subrCode = font.gsubrs[codeIndex];
4657                    if (subrCode) {
4658                        parse(subrCode);
4659                    }
4660
4661                    break;
4662                case 30: // vhcurveto
4663                    while (stack.length > 0) {
4664                        c1x = x;
4665                        c1y = y + stack.shift();
4666                        c2x = c1x + stack.shift();
4667                        c2y = c1y + stack.shift();
4668                        x = c2x + stack.shift();
4669                        y = c2y + (stack.length === 1 ? stack.shift() : 0);
4670                        p.curveTo(c1x, c1y, c2x, c2y, x, y);
4671                        if (stack.length === 0) {
4672                            break;
4673                        }
4674
4675                        c1x = x + stack.shift();
4676                        c1y = y;
4677                        c2x = c1x + stack.shift();
4678                        c2y = c1y + stack.shift();
4679                        y = c2y + stack.shift();
4680                        x = c2x + (stack.length === 1 ? stack.shift() : 0);
4681                        p.curveTo(c1x, c1y, c2x, c2y, x, y);
4682                    }
4683
4684                    break;
4685                case 31: // hvcurveto
4686                    while (stack.length > 0) {
4687                        c1x = x + stack.shift();
4688                        c1y = y;
4689                        c2x = c1x + stack.shift();
4690                        c2y = c1y + stack.shift();
4691                        y = c2y + stack.shift();
4692                        x = c2x + (stack.length === 1 ? stack.shift() : 0);
4693                        p.curveTo(c1x, c1y, c2x, c2y, x, y);
4694                        if (stack.length === 0) {
4695                            break;
4696                        }
4697
4698                        c1x = x;
4699                        c1y = y + stack.shift();
4700                        c2x = c1x + stack.shift();
4701                        c2y = c1y + stack.shift();
4702                        x = c2x + stack.shift();
4703                        y = c2y + (stack.length === 1 ? stack.shift() : 0);
4704                        p.curveTo(c1x, c1y, c2x, c2y, x, y);
4705                    }
4706
4707                    break;
4708                default:
4709                    if (v < 32) {
4710                        console.log('Glyph ' + glyph.index + ': unknown operator ' + v);
4711                    } else if (v < 247) {
4712                        stack.push(v - 139);
4713                    } else if (v < 251) {
4714                        b1 = code[i];
4715                        i += 1;
4716                        stack.push((v - 247) * 256 + b1 + 108);
4717                    } else if (v < 255) {
4718                        b1 = code[i];
4719                        i += 1;
4720                        stack.push(-(v - 251) * 256 - b1 - 108);
4721                    } else {
4722                        b1 = code[i];
4723                        b2 = code[i + 1];
4724                        b3 = code[i + 2];
4725                        b4 = code[i + 3];
4726                        i += 4;
4727                        stack.push(((b1 << 24) | (b2 << 16) | (b3 << 8) | b4) / 65536);
4728                    }
4729            }
4730        }
4731    }
4732
4733    parse(code);
4734
4735    glyph.advanceWidth = width;
4736    return p;
4737}
4738
4739function parseCFFFDSelect(data, start, nGlyphs, fdArrayCount) {
4740    var fdSelect = [];
4741    var fdIndex;
4742    var parser = new parse.Parser(data, start);
4743    var format = parser.parseCard8();
4744    if (format === 0) {
4745        // Simple list of nGlyphs elements
4746        for (var iGid = 0; iGid < nGlyphs; iGid++) {
4747            fdIndex = parser.parseCard8();
4748            if (fdIndex >= fdArrayCount) {
4749                throw new Error('CFF table CID Font FDSelect has bad FD index value ' + fdIndex + ' (FD count ' + fdArrayCount + ')');
4750            }
4751            fdSelect.push(fdIndex);
4752        }
4753    } else if (format === 3) {
4754        // Ranges
4755        var nRanges = parser.parseCard16();
4756        var first = parser.parseCard16();
4757        if (first !== 0) {
4758            throw new Error('CFF Table CID Font FDSelect format 3 range has bad initial GID ' + first);
4759        }
4760        var next;
4761        for (var iRange = 0; iRange < nRanges; iRange++) {
4762            fdIndex = parser.parseCard8();
4763            next = parser.parseCard16();
4764            if (fdIndex >= fdArrayCount) {
4765                throw new Error('CFF table CID Font FDSelect has bad FD index value ' + fdIndex + ' (FD count ' + fdArrayCount + ')');
4766            }
4767            if (next > nGlyphs) {
4768                throw new Error('CFF Table CID Font FDSelect format 3 range has bad GID ' + next);
4769            }
4770            for (; first < next; first++) {
4771                fdSelect.push(fdIndex);
4772            }
4773            first = next;
4774        }
4775        if (next !== nGlyphs) {
4776            throw new Error('CFF Table CID Font FDSelect format 3 range has bad final GID ' + next);
4777        }
4778    } else {
4779        throw new Error('CFF Table CID Font FDSelect table has unsupported format ' + format);
4780    }
4781    return fdSelect;
4782}
4783
4784// Parse the `CFF` table, which contains the glyph outlines in PostScript format.
4785function parseCFFTable(data, start, font, opt) {
4786    font.tables.cff = {};
4787    var header = parseCFFHeader(data, start);
4788    var nameIndex = parseCFFIndex(data, header.endOffset, parse.bytesToString);
4789    var topDictIndex = parseCFFIndex(data, nameIndex.endOffset);
4790    var stringIndex = parseCFFIndex(data, topDictIndex.endOffset, parse.bytesToString);
4791    var globalSubrIndex = parseCFFIndex(data, stringIndex.endOffset);
4792    font.gsubrs = globalSubrIndex.objects;
4793    font.gsubrsBias = calcCFFSubroutineBias(font.gsubrs);
4794
4795    var topDictArray = gatherCFFTopDicts(data, start, topDictIndex.objects, str
4795ingIndex.objects);
4796    if (topDictArray.length !== 1) {
4797        throw new Error('CFF table has too many fonts in \'FontSet\' - count of fonts NameIndex.length = ' + topDictArray.length);
4798    }
4799
4800    var topDict = topDictArray[0];
4801    font.tables.cff.topDict = topDict;
4802
4803    if (topDict._privateDict) {
4804        font.defaultWidthX = topDict._privateDict.defaultWidthX;
4805        font.nominalWidthX = topDict._privateDict.nominalWidthX;
4806    }
4807
4808    if (topDict.ros[0] !== undefined && topDict.ros[1] !== undefined) {
4809        font.isCIDFont = true;
4810    }
4811
4812    if (font.isCIDFont) {
4813        var fdArrayOffset = topDict.fdArray;
4814        var fdSelectOffset = topDict.fdSelect;
4815        if (fdArrayOffset === 0 || fdSelectOffset === 0) {
4816            throw new Error('Font is marked as a CID font, but FDArray and/or FDSelect information is missing');
4817        }
4818        fdArrayOffset += start;
4819        var fdArrayIndex = parseCFFIndex(data, fdArrayOffset);
4820        var fdArray = gatherCFFTopDicts(data, start, fdArrayIndex.objects, stringIndex.objects);
4821        topDict._fdArray = fdArray;
4822        fdSelectOffset += start;
4823        topDict._fdSelect = parseCFFFDSelect(data, fdSelectOffset, font.numGlyphs, fdArray.length);
4824    }
4825
4826    var privateDictOffset = start + topDict.private[1];
4827    var privateDict = parseCFFPrivateDict(data, privateDictOffset, topDict.private[0], stringIndex.objects);
4828    font.defaultWidthX = privateDict.defaultWidthX;
4829    font.nominalWidthX = privateDict.nominalWidthX;
4830
4831    if (privateDict.subrs !== 0) {
4832        var subrOffset = privateDictOffset + privateDict.subrs;
4833        var subrIndex = parseCFFIndex(data, subrOffset);
4834        font.subrs = subrIndex.objects;
4835        font.subrsBias = calcCFFSubroutineBias(font.subrs);
4836    } else {
4837        font.subrs = [];
4838        font.subrsBias = 0;
4839    }
4840
4841    // Offsets in the top dict are relative to the beginning of the CFF data, so add the CFF start offset.
4842    var charStringsIndex;
4843    if (opt.lowMemory) {
4844        charStringsIndex = parseCFFIndexLowMemory(data, start + topDict.charStrings);
4845        font.nGlyphs = charStringsIndex.offsets.length;
4846    } else {
4847        charStringsIndex = parseCFFIndex(data, start + topDict.charStrings);
4848        font.nGlyphs = charStringsIndex.objects.length;
4849    }
4850
4851    var charset = parseCFFCharset(data, start + topDict.charset, font.nGlyphs, stringIndex.objects);
4852    if (topDict.encoding === 0) {
4853        // Standard encoding
4854        font.cffEncoding = new CffEncoding(cffStandardEncoding, charset);
4855    } else if (topDict.encoding === 1) {
4856        // Expert encoding
4857        font.cffEncoding = new CffEncoding(cffExpertEncoding, charset);
4858    } else {
4859        font.cffEncoding = parseCFFEncoding(data, start + topDict.encoding, charset);
4860    }
4861
4862    // Prefer the CMAP encoding to the CFF encoding.
4863    font.encoding = font.encoding || font.cffEncoding;
4864
4865    font.glyphs = new glyphset.GlyphSet(font);
4866    if (opt.lowMemory) {
4867        font._push = function(i) {
4868            var charString = getCffIndexObject(i, charStringsIndex.offsets, data, start + topDict.charStrings);
4869            font.glyphs.push(i, glyphset.cffGlyphLoader(font, i, parseCFFCharstring, charString));
4870        };
4871    } else {
4872        for (var i = 0; i < font.nGlyphs; i += 1) {
4873            var charString = charStringsIndex.objects[i];
4874            font.glyphs.push(i, glyphset.cffGlyphLoader(font, i, parseCFFCharstring, charString));
4875        }
4876    }
4877}
4878
4879// Convert a string to a String ID (SID).
4880// The list of strings is modified in place.
4881function encodeString(s, strings) {
4882    var sid;
4883
4884    // Is the string in the CFF standard strings?
4885    var i = cffStandardStrings.indexOf(s);
4886    if (i >= 0) {
4887        sid = i;
4888    }
4889
4890    // Is the string already in the string index?
4891    i = strings.indexOf(s);
4892    if (i >= 0) {
4893        sid = i + cffStandardStrings.length;
4894    } else {
4895        sid = cffStandardStrings.length + strings.length;
4896        strings.push(s);
4897    }
4898
4899    return sid;
4900}
4901
4902function makeHeader() {
4903    return new table.Record('Header', [
4904        {name: 'major', type: 'Card8', value: 1},
4905        {name: 'minor', type: 'Card8', value: 0},
4906        {name: 'hdrSize', type: 'Card8', value: 4},
4907        {name: 'major', type: 'Card8', value: 1}
4908    ]);
4909}
4910
4911function makeNameIndex(fontNames) {
4912    var t = new table.Record('Name INDEX', [
4913        {name: 'names', type: 'INDEX', value: []}
4914    ]);
4915    t.names = [];
4916    for (var i = 0; i < fontNames.length; i += 1) {
4917        t.names.push({name: 'name_' + i, type: 'NAME', value: fontNames[i]});
4918    }
4919
4920    return t;
4921}
4922
4923// Given a dictionary's metadata, create a DICT structure.
4924function makeDict(meta, attrs, strings) {
4925    var m = {};
4926    for (var i = 0; i < meta.length; i += 1) {
4927        var entry = meta[i];
4928        var value = attrs[entry.name];
4929        if (value !== undefined && !equals(value, entry.value)) {
4930            if (entry.type === 'SID') {
4931                value = encodeString(value, strings);
4932            }
4933
4934            m[entry.op] = {name: entry.name, type: entry.type, value: value};
4935        }
4936    }
4937
4938    return m;
4939}
4940
4941// The Top DICT houses the global font attributes.
4942function makeTopDict(attrs, strings) {
4943    var t = new table.Record('Top DICT', [
4944        {name: 'dict', type: 'DICT', value: {}}
4945    ]);
4946    t.dict = makeDict(TOP_DICT_META, attrs, strings);
4947    return t;
4948}
4949
4950function makeTopDictIndex(topDict) {
4951    var t = new table.Record('Top DICT INDEX', [
4952        {name: 'topDicts', type: 'INDEX', value: []}
4953    ]);
4954    t.topDicts = [{name: 'topDict_0', type: 'TABLE', value: topDict}];
4955    return t;
4956}
4957
4958function makeStringIndex(strings) {
4959    var t = new table.Record('String INDEX', [
4960        {name: 'strings', type: 'INDEX', value: []}
4961    ]);
4962    t.strings = [];
4963    for (var i = 0; i < strings.length; i += 1) {
4964        t.strings.push({name: 'string_' + i, type: 'STRING', value: strings[i]});
4965    }
4966
4967    return t;
4968}
4969
4970function makeGlobalSubrIndex() {
4971    // Currently we don't use subroutines.
4972    return new table.Record('Global Subr INDEX', [
4973        {name: 'subrs', type: 'INDEX', value: []}
4974    ]);
4975}
4976
4977function makeCharsets(glyphNames, strings) {
4978    var t = new table.Record('Charsets', [
4979        {name: 'format', type: 'Card8', value: 0}
4980    ]);
4981    for (var i = 0; i < glyphNames.length; i += 1) {
4982        var glyphName = glyphNames[i];
4983        var glyphSID = encodeString(glyphName, strings);
4984        t.fields.push({name: 'glyph_' + i, type: 'SID', value: glyphSID});
4985    }
4986
4987    return t;
4988}
4989
4990function glyphToOps(glyph) {
4991    var ops = [];
4992    var path = glyph.path;
4993    ops.push({name: 'width', type: 'NUMBER', value: glyph.advanceWidth});
4994    var x = 0;
4995    var y = 0;
4996    for (var i = 0; i < path.commands.length; i += 1) {
4997        var dx = (void 0);
4998        var dy = (void 0);
4999        var cmd = path.commands[i];
5000        if (cmd.type === 'Q') {
5001            // CFF only supports bézier curves, so convert the quad to a bézier.
5002            var _13 = 1 / 3;
5003            var _23 = 2 / 3;
5004
5005            // We're going to create a new command so we don't change the original path.
5006            // Since all coordinates are relative, we round() them ASAP to avoid propagating errors.
5007            cmd = {
5008                type: 'C',
5009                x: cmd.x,
5010                y: cmd.y,
5011                x1: Math.round(_13 * x + _23 * cmd.x1),
5012                y1: Math.round(_13 * y + _23 * cmd.y1),
5013                x2: Math.round(_13 * cmd.x + _23 * cmd.x1),
5014                y2: Math.round(_13 * cmd.y + _23 * cmd.y1)
5015            };
5016        }
5017
5018        if (cmd.type === 'M') {
5019            dx = Math.round(cmd.x - x);
5020            dy = Math.round(cmd.y - y);
5021            ops.push({name: 'dx', type: 'NUMBER', value: dx});
5022            ops.push({name: 'dy', type: 'NUMBER', value: dy});
5023            ops.push({name: 'rmoveto', type: 'OP', value: 21});
5024            x = Math.round(cmd.x);
5025            y = Math.round(cmd.y);
5026        } else if (cmd.type === 'L') {
5027            dx = Math.round(cmd.x - x);
5028            dy = Math.round(cmd.y - y);
5029            ops.push({name: 'dx', type: 'NUMBER', value: dx});
5030            ops.push({name: 'dy', type: 'NUMBER', value: dy});
5031            ops.push({name: 'rlineto', type: 'OP', value: 5});
5032            x = Math.round(cmd.x);
5033            y = Math.round(cmd.y);
5034        } else if (cmd.type === 'C') {
5035            var dx1 = Math.round(cmd.x1 - x);
5036            var dy1 = Math.round(cmd.y1 - y);
5037            var dx2 = Math.round(cmd.x2 - cmd.x1);
5038            var dy2 = Math.round(cmd.y2 - cmd.y1);
5039            dx = Math.round(cmd.x - cmd.x2);
5040            dy = Math.round(cmd.y - cmd.y2);
5041            ops.push({name: 'dx1', type: 'NUMBER', value: dx1});
5042            ops.push({name: 'dy1', type: 'NUMBER', value: dy1});
5043            ops.push({name: 'dx2', type: 'NUMBER', value: dx2});
5044            ops.push({name: 'dy2', type: 'NUMBER', value: dy2});
5045            ops.push({name: 'dx', type: 'NUMBER', value: dx});
5046            ops.push({name: 'dy', type: 'NUMBER', value: dy});
5047            ops.push({name: 'rrcurveto', type: 'OP', value: 8});
5048            x = Math.round(cmd.x);
5049            y = Math.round(cmd.y);
5050        }
5051
5052        // Contours are closed automatically.
5053    }
5054
5055    ops.push({name: 'endchar', type: 'OP', value: 14});
5056    return ops;
5057}
5058
5059function makeCharStringsIndex(glyphs) {
5060    var t = new table.Record('CharStrings INDEX', [
5061        {name: 'charStrings', type: 'INDEX', value: []}
5062    ]);
5063
5064    for (var i = 0; i < glyphs.length; i += 1) {
5065        var glyph = glyphs.get(i);
5066        var ops = glyphToOps(glyph);
5067        t.charStrings.push({name: glyph.name, type: 'CHARSTRING', value: ops});
5068    }
5069
5070    return t;
5071}
5072
5073function makePrivateDict(attrs, strings) {
5074    var t = new table.Record('Private DICT', [
5075        {name: 'dict', type: 'DICT', value: {}}
5076    ]);
5077    t.dict = makeDict(PRIVATE_DICT_META, attrs, strings);
5078    return t;
5079}
5080
5081function makeCFFTable(glyphs, options) {
5082    var t = new table.Table('CFF ', [
5083        {name: 'header', type: 'RECORD'},
5084        {name: 'nameIndex', type: 'RECORD'},
5085        {name: 'topDictIndex', type: 'RECORD'},
5086        {name: 'stringIndex', type: 'RECORD'},
5087        {name: 'globalSubrIndex', type: 'RECORD'},
5088        {name: 'charsets', type: 'RECORD'},
5089        {name: 'charStringsIndex', type: 'RECORD'},
5090        {name: 'privateDict', type: 'RECORD'}
5091    ]);
5092
5093    var fontScale = 1 / options.unitsPerEm;
5094    // We use non-zero values for the offsets so that the DICT encodes them.
5095    // This is important because the size of the Top DICT plays a role in offset calculation,
5096    // and the size shouldn't change after we've written correct offsets.
5097    var attrs = {
5098        version: options.version,
5099        fullName: options.fullName,
5100        familyName: options.familyName,
5101        weight: options.weightName,
5102        fontBBox: options.fontBBox || [0, 0, 0, 0],
5103        fontMatrix: [fontScale, 0, 0, fontScale, 0, 0],
5104        charset: 999,
5105        encoding: 0,
5106        charStrings: 999,
5107        private: [0, 999]
5108    };
5109
5110    var privateAttrs = {};
5111
5112    var glyphNames = [];
5113    var glyph;
5114
5115    // Skip first glyph (.notdef)
5116    for (var i = 1; i < glyphs.length; i += 1) {
5117        glyph = glyphs.get(i);
5118        glyphNames.push(glyph.name);
5119    }
5120
5121    var strings = [];
5122
5123    t.header = makeHeader();
5124    t.nameIndex = makeNameIndex([options.postScriptName]);
5125    var topDict = makeTopDict(attrs, strings);
5126    t.topDictIndex = makeTopDictIndex(topDict);
5127    t.globalSubrIndex = makeGlobalSubrIndex();
5128    t.charsets = makeCharsets(glyphNames, strings);
5129    t.charStringsIndex = makeCharStringsIndex(glyphs);
5130    t.privateDict = makePrivateDict(privateAttrs, strings);
5131
5132    // Needs to come at the end, to encode all custom strings used in the font.
5133    t.stringIndex = makeStringIndex(strings);
5134
5135    var startOffset = t.header.sizeOf() +
5136        t.nameIndex.sizeOf() +
5137        t.topDictIndex.sizeOf() +
5138        t.stringIndex.sizeOf() +
5139        t.globalSubrIndex.sizeOf();
5140    attrs.charset = startOffset;
5141
5142    // We use the CFF standard encoding; proper encoding will be handled in cmap.
5143    attrs.encoding = 0;
5144    attrs.charStrings = attrs.charset + t.charsets.sizeOf();
5145    attrs.private[1] = attrs.charStrings + t.charStringsIndex.sizeOf();
5146
5147    // Recreate the Top DICT INDEX with the correct offsets.
5148    topDict = makeTopDict(attrs, strings);
5149    t.topDictIndex = makeTopDictIndex(topDict);
5150
5151    return t;
5152}
5153
5154var cff = { parse: parseCFFTable, make: makeCFFTable };
5155
5156// The `head` table contains global information about the font.
5157
5158// Parse the header `head` table
5159function parseHeadTable(data, start) {
5160    var head = {};
5161    var p = new parse.Parser(data, start);
5162    head.version = p.parseVersion();
5163    head.fontRevision = Math.round(p.parseFixed() * 1000) / 1000;
5164    head.checkSumAdjustment = p.parseULong();
5165    head.magicNumber = p.parseULong();
5166    check.argument(head.magicNumber === 0x5F0F3CF5, 'Font header has wrong magic number.');
5167    head.flags = p.parseUShort();
5168    head.unitsPerEm = p.parseUShort();
5169    head.created = p.parseLongDateTime();
5170    head.modified = p.parseLongDateTime();
5171    head.xMin = p.parseShort();
5172    head.yMin = p.parseShort();
5173    head.xMax = p.parseShort();
5174    head.yMax = p.parseShort();
5175    head.macStyle = p.parseUShort();
5176    head.lowestRecPPEM = p.parseUShort();
5177    head.fontDirectionHint = p.parseShort();
5178    head.indexToLocFormat = p.parseShort();
5179    head.glyphDataFormat = p.parseShort();
5180    return head;
5181}
5182
5183function makeHeadTable(options) {
5184    // Apple Mac timestamp epoch is 01/01/1904 not 01/01/1970
5185    var timestamp = Math.round(new Date().getTime() / 1000) + 2082844800;
5186    var createdTimestamp = timestamp;
5187
5188    if (options.createdTimestamp) {
5189        createdTimestamp = options.createdTimestamp + 2082844800;
5190    }
5191
5192    return new table.Table('head', [
5193        {name: 'version', type: 'FIXED', value: 0x00010000},
5194        {name: 'fontRevision', type: 'FIXED', value: 0x00010000},
5195        {name: 'checkSumAdjustment', type: 'ULONG', value: 0},
5196        {name: 'magicNumber', type: 'ULONG', value: 0x5F0F3CF5},
5197        {name: 'flags', type: 'USHORT', value: 0},
5198        {name: 'unitsPerEm', type: 'USHORT', value: 1000},
5199        {name: 'created', type: 'LONGDATETIME', value: createdTimestamp},
5200        {name: 'modified', type: 'LONGDATETIME', value: timestamp},
5201        {name: 'xMin', type: 'SHORT', value: 0},
5202        {name: 'yMin', type: 'SHORT', value: 0},
5203        {name: 'xMax', type: 'SHORT', value: 0},
5204        {name: 'yMax', type: 'SHORT', value: 0},
5205        {name: 'macStyle', type: 'USHORT', value: 0},
5206        {name: 'lowestRecPPEM', type: 'USHORT', value: 0},
5207        {name: 'fontDirectionHint', type: 'SHORT', value: 2},
5208        {name: 'indexToLocFormat', type: 'SHORT', value: 0},
5209        {name: 'glyphDataFormat', type: 'SHORT', value: 0}
5210    ], options);
5211}
5212
5213var head = { parse: parseHeadTable, make: makeHeadTable };
5214
5215// The `hhea` table contains information for horizontal layout.
5216
5217// Parse the horizontal header `hhea` table
5218function parseHheaTable(data, start) {
5219    var hhea = {};
5220    var p = new parse.Parser(data, start);
5221    hhea.version = p.parseVersion();
5222    hhea.ascender = p.parseShort();
5223    hhea.descender = p.parseShort();
5224    hhea.lineGap = p.parseShort();
5225    hhea.advanceWidthMax = p.parseUShort();
5226    hhea.minLeftSideBearing = p.parseShort();
5227    hhea.minRightSideBearing = p.parseShort();
5228    hhea.xMaxExtent = p.parseShort();
5229    hhea.caretSlopeRise = p.parseShort();
5230    hhea.caretSlopeRun = p.parseShort();
5231    hhea.caretOffset = p.parseShort();
5232    p.relativeOffset += 8;
5233    hhea.metricDataFormat = p.parseShort();
5234    hhea.numberOfHMetrics = p.parseUShort();
5235    return hhea;
5236}
5237
5238function makeHheaTable(options) {
5239    return new table.Table('hhea', [
5240        {name: 'version', type: 'FIXED', value: 0x00010000},
5241        {name: 'ascender', type: 'FWORD', value: 0},
5242        {name: 'descender', type: 'FWORD', value: 0},
5243        {name: 'lineGap', type: 'FWORD', value: 0},
5244        {name: 'advanceWidthMax', type: 'UFWORD', value: 0},
5245        {name: 'minLeftSideBearing', type: 'FWORD', value: 0},
5246        {name: 'minRightSideBearing', type: 'FWORD', value: 0},
5247        {name: 'xMaxExtent', type: 'FWORD', value: 0},
5248        {name: 'caretSlopeRise', type: 'SHORT', value: 1},
5249        {name: 'caretSlopeRun', type: 'SHORT', value: 0},
5250        {name: 'caretOffset', type: 'SHORT', value: 0},
5251        {name: 'reserved1', type: 'SHORT', value: 0},
5252        {name: 'reserved2', type: 'SHORT', value: 0},
5253        {name: 'reserved3', type: 'SHORT', value: 0},
5254        {name: 'reserved4', type: 'SHORT', value: 0},
5255        {name: 'metricDataFormat', type: 'SHORT', value: 0},
5256        {name: 'numberOfHMetrics', type: 'USHORT', value: 0}
5257    ], options);
5258}
5259
5260var hhea = { parse: parseHheaTable, make: makeHheaTable };
5261
5262// The `hmtx` table contains the horizontal metrics for all glyphs.
5263
5264function parseHmtxTableAll(data, start, numMetrics, numGlyphs, glyphs) {
5265    var advanceWidth;
5266    var leftSideBearing;
5267    var p = new parse.Parser(data, start);
5268    for (var i = 0; i < numGlyphs; i += 1) {
5269        // If the font is monospaced, only one entry is needed. This last entry applies to all subsequent glyphs.
5270        if (i < numMetrics) {
5271            advanceWidth = p.parseUShort();
5272            leftSideBearing = p.parseShort();
5273        }
5274
5275        var glyph = glyphs.get(i);
5276        glyph.advanceWidth = advanceWidth;
5277        glyph.leftSideBearing = leftSideBearing;
5278    }
5279}
5280
5281function parseHmtxTableOnLowMemory(font, data, start, numMetrics, numGlyphs) {
5282    font._hmtxTableData = {};
5283
5284    var advanceWidth;
5285    var leftSideBearing;
5286    var p = new parse.Parser(data, start);
5287    for (var i = 0; i < numGlyphs; i += 1) {
5288        // If the font is monospaced, only one entry is needed. This last entry applies to all subsequent glyphs.
5289        if (i < numMetrics) {
5290            advanceWidth = p.parseUShort();
5291            leftSideBearing = p.parseShort();
5292        }
5293
5294        font._hmtxTableData[i] = {
5295            advanceWidth: advanceWidth,
5296            leftSideBearing: leftSideBearing
5297        };
5298    }
5299}
5300
5301// Parse the `hmtx` table, which contains the horizontal metrics for all glyphs.
5302// This function augments the glyph array, adding the advanceWidth and leftSideBearing to each glyph.
5303function parseHmtxTable(font, data, start, numMetrics, numGlyphs, glyphs, opt) {
5304    if (opt.lowMemory)
5305        { parseHmtxTableOnLowMemory(font, data, start, numMetrics, numGlyphs); }
5306    else
5307        { parseHmtxTableAll(data, start, numMetrics, numGlyphs, glyphs); }
5308}
5309
5310function makeHmtxTable(glyphs) {
5311    var t = new table.Table('hmtx', []);
5312    for (var i = 0; i < glyphs.length; i += 1) {
5313        var glyph = glyphs.get(i);
5314        var advanceWidth = glyph.advanceWidth || 0;
5315        var leftSideBearing = glyph.leftSideBearing || 0;
5316        t.fields.push({name: 'advanceWidth_' + i, type: 'USHORT', value: advanceWidth});
5317        t.fields.push({name: 'leftSideBearing_' + i, type: 'SHORT', value: leftSideBearing});
5318    }
5319
5320    return t;
5321}
5322
5323var hmtx = { parse: parseHmtxTable, make: makeHmtxTable };
5324
5325// The `ltag` table stores IETF BCP-47 language tags. It allows supporting
5326
5327function makeLtagTable(tags) {
5328    var result = new table.Table('ltag', [
5329        {name: 'version', type: 'ULONG', value: 1},
5330        {name: 'flags', type: 'ULONG', value: 0},
5331        {name: 'numTags', type: 'ULONG', value: tags.length}
5332    ]);
5333
5334    var stringPool = '';
5335    var stringPoolOffset = 12 + tags.length * 4;
5336    for (var i = 0; i < tags.length; ++i) {
5337        var pos = stringPool.indexOf(tags[i]);
5338        if (pos < 0) {
5339            pos = stringPool.length;
5340            stringPool += tags[i];
5341        }
5342
5343        result.fields.push({name: 'offset ' + i, type: 'USHORT', value: stringPoolOffset + pos});
5344        result.fields.push({name: 'length ' + i, type: 'USHORT', value: tags[i].length});
5345    }
5346
5347    result.fields.push({name: 'stringPool', type: 'CHARARRAY', value: stringPool});
5348    return result;
5349}
5350
5351function parseLtagTable(data, start) {
5352    var p = new parse.Parser(data, start);
5353    var tableVersion = p.parseULong();
5354    check.argument(tableVersion === 1, 'Unsupported ltag table version.');
5355    // The 'ltag' specification does not define any flags; skip the field.
5356    p.skip('uLong', 1);
5357    var numTags = p.parseULong();
5358
5359    var tags = [];
5360    for (var i = 0; i < numTags; i++) {
5361        var tag = '';
5362        var offset = start + p.parseUShort();
5363        var length = p.parseUShort();
5364        for (var j = offset; j < offset + length; ++j) {
5365            tag += String.fromCharCode(data.getInt8(j));
5366        }
5367
5368        tags.push(tag);
5369    }
5370
5371    return tags;
5372}
5373
5374var ltag = { make: makeLtagTable, parse: parseLtagTable };
5375
5376// The `maxp` table establishes the memory requirements for the font.
5377
5378// Parse the maximum profile `maxp` table.
5379function parseMaxpTable(data, start) {
5380    var maxp = {};
5381    var p = new parse.Parser(data, start);
5382    maxp.version = p.parseVersion();
5383    maxp.numGlyphs = p.parseUShort();
5384    if (maxp.version === 1.0) {
5385        maxp.maxPoints = p.parseUShort();
5386        maxp.maxContours = p.parseUShort();
5387        maxp.maxCompositePoints = p.parseUShort();
5388        maxp.maxCompositeContours = p.parseUShort();
5389        maxp.maxZones = p.parseUShort();
5390        maxp.maxTwilightPoints = p.parseUShort();
5391        maxp.maxStorage = p.parseUShort();
5392        maxp.maxFunctionDefs = p.parseUShort();
5393        maxp.maxInstructionDefs = p.parseUShort();
5394        maxp.maxStackElements = p.parseUShort();
5395        maxp.maxSizeOfInstructions = p.parseUShort();
5396        maxp.maxComponentElements = p.parseUShort();
5397        maxp.maxComponentDepth = p.parseUShort();
5398    }
5399
5400    return maxp;
5401}
5402
5403function makeMaxpTable(numGlyphs) {
5404    return new table.Table('maxp', [
5405        {name: 'version', type: 'FIXED', value: 0x00005000},
5406        {name: 'numGlyphs', type: 'USHORT', value: numGlyphs}
5407    ]);
5408}
5409
5410var maxp = { parse: parseMaxpTable, make: makeMaxpTable };
5411
5412// The `name` naming table.
5413
5414// NameIDs for the name table.
5415var nameTableNames = [
5416    'copyright',              // 0
5417    'fontFamily',             // 1
5418    'fontSubfamily',          // 2
5419    'uniqueID',               // 3
5420    'fullName',               // 4
5421    'version',                // 5
5422    'postScriptName',         // 6
5423    'trademark',              // 7
5424    'manufacturer',           // 8
5425    'designer',               // 9
5426    'description',            // 10
5427    'manufacturerURL',        // 11
5428    'designerURL',            // 12
5429    'license',                // 13
5430    'licenseURL',             // 14
5431    'reserved',               // 15
5432    'preferredFamily',        // 16
5433    'preferredSubfamily',     // 17
5434    'compatibleFullName',     // 18
5435    'sampleText',             // 19
5436    'postScriptFindFontName', // 20
5437    'wwsFamily',              // 21
5438    'wwsSubfamily'            // 22
5439];
5440
5441var macLanguages = {
5442    0: 'en',
5443    1: 'fr',
5444    2: 'de',
5445    3: 'it',
5446    4: 'nl',
5447    5: 'sv',
5448    6: 'es',
5449    7: 'da',
5450    8: 'pt',
5451    9: 'no',
5452    10: 'he',
5453    11: 'ja',
5454    12: 'ar',
5455    13: 'fi',
5456    14: 'el',
5457    15: 'is',
5458    16: 'mt',
5459    17: 'tr',
5460    18: 'hr',
5461    19: 'zh-Hant',
5462    20: 'ur',
5463    21: 'hi',
5464    22: 'th',
5465    23: 'ko',
5466    24: 'lt',
5467    25: 'pl',
5468    26: 'hu',
5469    27: 'es',
5470    28: 'lv',
5471    29: 'se',
5472    30: 'fo',
5473    31: 'fa',
5474    32: 'ru',
5475    33: 'zh',
5476    34: 'nl-BE',
5477    35: 'ga',
5478    36: 'sq',
5479    37: 'ro',
5480    38: 'cz',
5481    39: 'sk',
5482    40: 'si',
5483    41: 'yi',
5484    42: 'sr',
5485    43: 'mk',
5486    44: 'bg',
5487    45: 'uk',
5488    46: 'be',
5489    47: 'uz',
5490    48: 'kk',
5491    49: 'az-Cyrl',
5492    50: 'az-Arab',
5493    51: 'hy',
5494    52: 'ka',
5495    53: 'mo',
5496    54: 'ky',
5497    55: 'tg',
5498    56: 'tk',
5499    57: 'mn-CN',
5500    58: 'mn',
5501    59: 'ps',
5502    60: 'ks',
5503    61: 'ku',
5504    62: 'sd',
5505    63: 'bo',
5506    64: 'ne',
5507    65: 'sa',
5508    66: 'mr',
5509    67: 'bn',
5510    68: 'as',
5511    69: 'gu',
5512    70: 'pa',
5513    71: 'or',
5514    72: 'ml',
5515    73: 'kn',
5516    74: 'ta',
5517    75: 'te',
5518    76: 'si',
5519    77: 'my',
5520    78: 'km',
5521    79: 'lo',
5522    80: 'vi',
5523    81: 'id',
5524    82: 'tl',
5525    83: 'ms',
5526    84: 'ms-Arab',
5527    85: 'am',
5528    86: 'ti',
5529    87: 'om',
5530    88: 'so',
5531    89: 'sw',
5532    90: 'rw',
5533    91: 'rn',
5534    92: 'ny',
5535    93: 'mg',
5536    94: 'eo',
5537    128: 'cy',
5538    129: 'eu',
5539    130: 'ca',
5540    131: 'la',
5541    132: 'qu',
5542    133: 'gn',
5543    134: 'ay',
5544    135: 'tt',
5545    136: 'ug',
5546    137: 'dz',
5547    138: 'jv',
5548    139: 'su',
5549    140: 'gl',
5550    141: 'af',
5551    142: 'br',
5552    143: 'iu',
5553    144: 'gd',
5554    145: 'gv',
5555    146: 'ga',
5556    147: 'to',
5557    148: 'el-polyton',
5558    149: 'kl',
5559    150: 'az',
5560    151: 'nn'
5561};
5562
5563// MacOS language ID → MacOS script ID
5564//
5565// Note that the script ID is not sufficient to determine what encoding
5566// to use in TrueType files. For some languages, MacOS used a modification
5567// of a mainstream script. For example, an Icelandic name would be stored
5568// with smRoman in the TrueType naming table, but the actual encoding
5569// is a special Icelandic version of the normal Macintosh Roman encoding.
5570// As another example, Inuktitut uses an 8-bit encoding for Canadian Aboriginal
5571// Syllables but MacOS had run out of available script codes, so this was
5572// done as a (pretty radical) "modification" of Ethiopic.
5573//
5574// http://unicode.org/Public/MAPPINGS/VENDORS/APPLE/Readme.txt
5575var macLanguageToScript = {
5576    0: 0,  // langEnglish → smRoman
5577    1: 0,  // langFrench → smRoman
5578    2: 0,  // langGerman → smRoman
5579    3: 0,  // langItalian → smRoman
5580    4: 0,  // langDutch → smRoman
5581    5: 0,  // langSwedish → smRoman
5582    6: 0,  // langSpanish → smRoman
5583    7: 0,  // langDanish → smRoman
5584    8: 0,  // langPortuguese → smRoman
5585    9: 0,  // langNorwegian → smRoman
5586    10: 5,  // langHebrew → smHebrew
5587    11: 1,  // langJapanese → smJapanese
5588    12: 4,  // langArabic → smArabic
5589    13: 0,  // langFinnish → smRoman
5590    14: 6,  // langGreek → smGreek
5591    15: 0,  // langIcelandic → smRoman (modified)
5592    16: 0,  // langMaltese → smRoman
5593    17: 0,  // langTurkish → smRoman (modified)
5594    18: 0,  // langCroatian → smRoman (modified)
5595    19: 2,  // langTradChinese → smTradChinese
5596    20: 4,  // langUrdu → smArabic
5597    21: 9,  // langHindi → smDevanagari
5598    22: 21,  // langThai → smThai
5599    23: 3,  // langKorean → smKorean
5600    24: 29,  // langLithuanian → smCentralEuroRoman
5601    25: 29,  // langPolish → smCentralEuroRoman
5602    26: 29,  // langHungarian → smCentralEuroRoman
5603    27: 29,  // langEstonian → smCentralEuroRoman
5604    28: 29,  // langLatvian → smCentralEuroRoman
5605    29: 0,  // langSami → smRoman
5606    30: 0,  // langFaroese → smRoman (modified)
5607    31: 4,  // langFarsi → smArabic (modified)
5608    32: 7,  // langRussian → smCyrillic
5609    33: 25,  // langSimpChinese → smSimpChinese
5610    34: 0,  // langFlemish → smRoman
5611    35: 0,  // langIrishGaelic → smRoman (modified)
5612    36: 0,  // langAlbanian → smRoman
5613    37: 0,  // langRomanian → smRoman (modified)
5614    38: 29,  // langCzech → smCentralEuroRoman
5615    39: 29,  // langSlovak → smCentralEuroRoman
5616    40: 0,  // langSlovenian → smRoman (modified)
5617    41: 5,  // langYiddish → smHebrew
5618    42: 7,  // langSerbian → smCyrillic
5619    43: 7,  // langMacedonian → smCyrillic
5620    44: 7,  // langBulgarian → smCyrillic
5621    45: 7,  // langUkrainian → smCyrillic (modified)
5622    46: 7,  // langByelorussian → smCyrillic
5623    47: 7,  // langUzbek → smCyrillic
5624    48: 7,  // langKazakh → smCyrillic
5625    49: 7,  // langAzerbaijani → smCyrillic
5626    50: 4,  // langAzerbaijanAr → smArabic
5627    51: 24,  // langArmenian → smArmenian
5628    52: 23,  // langGeorgian → smGeorgian
5629    53: 7,  // langMoldavian → smCyrillic
5630    54: 7,  // langKirghiz → smCyrillic
5631    55: 7,  // langTajiki → smCyrillic
5632    56: 7,  // langTurkmen → smCyrillic
5633    57: 27,  // langMongolian → smMongolian
5634    58: 7,  // langMongolianCyr → smCyrillic
5635    59: 4,  // langPashto → smArabic
5636    60: 4,  // langKurdish → smArabic
5637    61: 4,  // langKashmiri → smArabic
5638    62: 4,  // langSindhi → smArabic
5639    63: 26,  // langTibetan → smTibetan
5640    64: 9,  // langNepali → smDevanagari
5641    65: 9,  // langSanskrit → smDevanagari
5642    66: 9,  // langMarathi → smDevanagari
5643    67: 13,  // langBengali → smBengali
5644    68: 13,  // langAssamese → smBengali
5645    69: 11,  // langGujarati → smGujarati
5646    70: 10,  // langPunjabi → smGurmukhi
5647    71: 12,  // langOriya → smOriya
5648    72: 17,  // langMalayalam → smMalayalam
5649    73: 16,  // langKannada → smKannada
5650    74: 14,  // langTamil → smTamil
5651    75: 15,  // langTelugu → smTelugu
5652    76: 18,  // langSinhalese → smSinhalese
5653    77: 19,  // langBurmese → smBurmese
5654    78: 20,  // langKhmer → smKhmer
5655    79: 22,  // langLao → smLao
5656    80: 30,  // langVietnamese → smVietnamese
5657    81: 0,  // langIndonesian → smRoman
5658    82: 0,  // langTagalog → smRoman
5659    83: 0,  // langMalayRoman → smRoman
5660    84: 4,  // langMalayArabic → smArabic
5661    85: 28,  // langAmharic → smEthiopic
5662    86: 28,  // langTigrinya → smEthiopic
5663    87: 28,  // langOromo → smEthiopic
5664    88: 0,  // langSomali → smRoman
5665    89: 0,  // langSwahili → smRoman
5666    90: 0,  // langKinyarwanda → smRoman
5667    91: 0,  // langRundi → smRoman
5668    92: 0,  // langNyanja → smRoman
5669    93: 0,  // langMalagasy → smRoman
5670    94: 0,  // langEsperanto → smRoman
5671    128: 0,  // langWelsh → smRoman (modified)
5672    129: 0,  // langBasque → smRoman
5673    130: 0,  // langCatalan → smRoman
5674    131: 0,  // langLatin → smRoman
5675    132: 0,  // langQuechua → smRoman
5676    133: 0,  // langGuarani → smRoman
5677    134: 0,  // langAymara → smRoman
5678    135: 7,  // langTatar → smCyrillic
5679    136: 4,  // langUighur → smArabic
5680    137: 26,  // langDzongkha → smTibetan
5681    138: 0,  // langJavaneseRom → smRoman
5682    139: 0,  // langSundaneseRom → smRoman
5683    140: 0,  // langGalician → smRoman
5684    141: 0,  // langAfrikaans → smRoman
5685    142: 0,  // langBreton → smRoman (modified)
5686    143: 28,  // langInuktitut → smEthiopic (modified)
5687    144: 0,  // langScottishGaelic → smRoman (modified)
5688    145: 0,  // langManxGaelic → smRoman (modified)
5689    146: 0,  // langIrishGaelicScript → smRoman (modified)
5690    147: 0,  // langTongan → smRoman
5691    148: 6,  // langGreekAncient → smRoman
5692    149: 0,  // langGreenlandic → smRoman
5693    150: 0,  // langAzerbaijanRoman → smRoman
5694    151: 0   // langNynorsk → smRoman
5695};
5696
5697// While Microsoft indicates a region/country for all its language
5698// IDs, we omit the region code if it's equal to the "most likely
5699// region subtag" according to Unicode CLDR. For scripts, we omit
5700// the subtag if it is equal to the Suppress-Script entry in the
5701// IANA language subtag registry for IETF BCP 47.
5702//
5703// For example, Microsoft states that its language code 0x041A is
5704// Croatian in Croatia. We transform this to the BCP 47 language code 'hr'
5705// and not 'hr-HR' because Croatia is the default country for Croatian,
5706// according to Unicode CLDR. As another example, Microsoft states
5707// that 0x101A is Croatian (Latin) in Bosnia-Herzegovina. We transform
5708// this to 'hr-BA' and not 'hr-Latn-BA' because Latin is the default script
5709// for the Croatian language, according to IANA.
5710//
5711// http://www.unicode.org/cldr/charts/latest/supplemental/likely_subtags.html
5712// http://www.iana.org/assignments/language-subtag-registry/language-subtag-registry
5713var windowsLanguages = {
5714    0x0436: 'af',
5715    0x041C: 'sq',
5716    0x0484: 'gsw',
5717    0x045E: 'am',
5718    0x1401: 'ar-DZ',
5719    0x3C01: 'ar-BH',
5720    0x0C01: 'ar',
5721    0x0801: 'ar-IQ',
5722    0x2C01: 'ar-JO',
5723    0x3401: 'ar-KW',
5724    0x3001: 'ar-LB',
5725    0x1001: 'ar-LY',
5726    0x1801: 'ary',
5727    0x2001: 'ar-OM',
5728    0x4001: 'ar-QA',
5729    0x0401: 'ar-SA',
5730    0x2801: 'ar-SY',
5731    0x1C01: 'aeb',
5732    0x3801: 'ar-AE',
5733    0x2401: 'ar-YE',
5734    0x042B: 'hy',
5735    0x044D: 'as',
5736    0x082C: 'az-Cyrl',
5737    0x042C: 'az',
5738    0x046D: 'ba',
5739    0x042D: 'eu',
5740    0x0423: 'be',
5741    0x0845: 'bn',
5742    0x0445: 'bn-IN',
5743    0x201A: 'bs-Cyrl',
5744    0x141A: 'bs',
5745    0x047E: 'br',
5746    0x0402: 'bg',
5747    0x0403: 'ca',
5748    0x0C04: 'zh-HK',
5749    0x1404: 'zh-MO',
5750    0x0804: 'zh',
5751    0x1004: 'zh-SG',
5752    0x0404: 'zh-TW',
5753    0x0483: 'co',
5754    0x041A: 'hr',
5755    0x101A: 'hr-BA',
5756    0x0405: 'cs',
5757    0x0406: 'da',
5758    0x048C: 'prs',
5759    0x0465: 'dv',
5760    0x0813: 'nl-BE',
5761    0x0413: 'nl',
5762    0x0C09: 'en-AU',
5763    0x2809: 'en-BZ',
5764    0x1009: 'en-CA',
5765    0x2409: 'en-029',
5766    0x4009: 'en-IN',
5767    0x1809: 'en-IE',
5768    0x2009: 'en-JM',
5769    0x4409: 'en-MY',
5770    0x1409: 'en-NZ',
5771    0x3409: 'en-PH',
5772    0x4809: 'en-SG',
5773    0x1C09: 'en-ZA',
5774    0x2C09: 'en-TT',
5775    0x0809: 'en-GB',
5776    0x0409: 'en',
5777    0x3009: 'en-ZW',
5778    0x0425: 'et',
5779    0x0438: 'fo',
5780    0x0464: 'fil',
5781    0x040B: 'fi',
5782    0x080C: 'fr-BE',
5783    0x0C0C: 'fr-CA',
5784    0x040C: 'fr',
5785    0x140C: 'fr-LU',
5786    0x180C: 'fr-MC',
5787    0x100C: 'fr-CH',
5788    0x0462: 'fy',
5789    0x0456: 'gl',
5790    0x0437: 'ka',
5791    0x0C07: 'de-AT',
5792    0x0407: 'de',
5793    0x1407: 'de-LI',
5794    0x1007: 'de-LU',
5795    0x0807: 'de-CH',
5796    0x0408: 'el',
5797    0x046F: 'kl',
5798    0x0447: 'gu',
5799    0x0468: 'ha',
5800    0x040D: 'he',
5801    0x0439: 'hi',
5802    0x040E: 'hu',
5803    0x040F: 'is',
5804    0x0470: 'ig',
5805    0x0421: 'id',
5806    0x045D: 'iu',
5807    0x085D: 'iu-Latn',
5808    0x083C: 'ga',
5809    0x0434: 'xh',
5810    0x0435: 'zu',
5811    0x0410: 'it',
5812    0x0810: 'it-CH',
5813    0x0411: 'ja',
5814    0x044B: 'kn',
5815    0x043F: 'kk',
5816    0x0453: 'km',
5817    0x0486: 'quc',
5818    0x0487: 'rw',
5819    0x0441: 'sw',
5820    0x0457: 'kok',
5821    0x0412: 'ko',
5822    0x0440: 'ky',
5823    0x0454: 'lo',
5824    0x0426: 'lv',
5825    0x0427: 'lt',
5826    0x082E: 'dsb',
5827    0x046E: 'lb',
5828    0x042F: 'mk',
5829    0x083E: 'ms-BN',
5830    0x043E: 'ms',
5831    0x044C: 'ml',
5832    0x043A: 'mt',
5833    0x0481: 'mi',
5834    0x047A: 'arn',
5835    0x044E: 'mr',
5836    0x047C: 'moh',
5837    0x0450: 'mn',
5838    0x0850: 'mn-CN',
5839    0x0461: 'ne',
5840    0x0414: 'nb',
5841    0x0814: 'nn',
5842    0x0482: 'oc',
5843    0x0448: 'or',
5844    0x0463: 'ps',
5845    0x0415: 'pl',
5846    0x0416: 'pt',
5847    0x0816: 'pt-PT',
5848    0x0446: 'pa',
5849    0x046B: 'qu-BO',
5850    0x086B: 'qu-EC',
5851    0x0C6B: 'qu',
5852    0x0418: 'ro',
5853    0x0417: 'rm',
5854    0x0419: 'ru',
5855    0x243B: 'smn',
5856    0x103B: 'smj-NO',
5857    0x143B: 'smj',
5858    0x0C3B: 'se-FI',
5859    0x043B: 'se',
5860    0x083B: 'se-SE',
5861    0x203B: 'sms',
5862    0x183B: 'sma-NO',
5863    0x1C3B: 'sms',
5864    0x044F: 'sa',
5865    0x1C1A: 'sr-Cyrl-BA',
5866    0x0C1A: 'sr',
5867    0x181A: 'sr-Latn-BA',
5868    0x081A: 'sr-Latn',
5869    0x046C: 'nso',
5870    0x0432: 'tn',
5871    0x045B: 'si',
5872    0x041B: 'sk',
5873    0x0424: 'sl',
5874    0x2C0A: 'es-AR',
5875    0x400A: 'es-BO',
5876    0x340A: 'es-CL',
5877    0x240A: 'es-CO',
5878    0x140A: 'es-CR',
5879    0x1C0A: 'es-DO',
5880    0x300A: 'es-EC',
5881    0x440A: 'es-SV',
5882    0x100A: 'es-GT',
5883    0x480A: 'es-HN',
5884    0x080A: 'es-MX',
5885    0x4C0A: 'es-NI',
5886    0x180A: 'es-PA',
5887    0x3C0A: 'es-PY',
5888    0x280A: 'es-PE',
5889    0x500A: 'es-PR',
5890
5891    // Microsoft has defined two different language codes for
5892    // “Spanish with modern sorting” and “Spanish with traditional
5893    // sorting”. This makes sense for collation APIs, and it would be
5894    // possible to express this in BCP 47 language tags via Unicode
5895    // extensions (eg., es-u-co-trad is Spanish with traditional
5896    // sorting). However, for storing names in fonts, the distinction
5897    // does not make sense, so we give “es” in both cases.
5898    0x0C0A: 'es',
5899    0x040A: 'es',
5900
5901    0x540A: 'es-US',
5902    0x380A: 'es-UY',
5903    0x200A: 'es-VE',
5904    0x081D: 'sv-FI',
5905    0x041D: 'sv',
5906    0x045A: 'syr',
5907    0x0428: 'tg',
5908    0x085F: 'tzm',
5909    0x0449: 'ta',
5910    0x0444: 'tt',
5911    0x044A: 'te',
5912    0x041E: 'th',
5913    0x0451: 'bo',
5914    0x041F: 'tr',
5915    0x0442: 'tk',
5916    0x0480: 'ug',
5917    0x0422: 'uk',
5918    0x042E: 'hsb',
5919    0x0420: 'ur',
5920    0x0843: 'uz-Cyrl',
5921    0x0443: 'uz',
5922    0x042A: 'vi',
5923    0x0452: 'cy',
5924    0x0488: 'wo',
5925    0x0485: 'sah',
5926    0x0478: 'ii',
5927    0x046A: 'yo'
5928};
5929
5930// Returns a IETF BCP 47 language code, for example 'zh-Hant'
5931// for 'Chinese in the traditional script'.
5932function getLanguageCode(platformID, languageID, ltag) {
5933    switch (platformID) {
5934        case 0:  // Unicode
5935            if (languageID === 0xFFFF) {
5936                return 'und';
5937            } else if (ltag) {
5938                return ltag[languageID];
5939            }
5940
5941            break;
5942
5943        case 1:  // Macintosh
5944            return macLanguages[languageID];
5945
5946        case 3:  // Windows
5947            return windowsLanguages[languageID];
5948    }
5949
5950    return undefined;
5951}
5952
5953var utf16 = 'utf-16';
5954
5955// MacOS script ID → encoding. This table stores the default case,
5956// which can be overridden by macLanguageEncodings.
5957var macScriptEncodings = {
5958    0: 'macintosh',           // smRoman
5959    1: 'x-mac-japanese',      // smJapanese
5960    2: 'x-mac-chinesetrad',   // smTradChinese
5961    3: 'x-mac-korean',        // smKorean
5962    6: 'x-mac-greek',         // smGreek
5963    7: 'x-mac-cyrillic',      // smCyrillic
5964    9: 'x-mac-devanagai',     // smDevanagari
5965    10: 'x-mac-gurmukhi',     // smGurmukhi
5966    11: 'x-mac-gujarati',     // smGujarati
5967    12: 'x-mac-oriya',        // smOriya
5968    13: 'x-mac-bengali',      // smBengali
5969    14: 'x-mac-tamil',        // smTamil
5970    15: 'x-mac-telugu',       // smTelugu
5971    16: 'x-mac-kannada',      // smKannada
5972    17: 'x-mac-malayalam',    // smMalayalam
5973    18: 'x-mac-sinhalese',    // smSinhalese
5974    19: 'x-mac-burmese',      // smBurmese
5975    20: 'x-mac-khmer',        // smKhmer
5976    21: 'x-mac-thai',         // smThai
5977    22: 'x-mac-lao',          // smLao
5978    23: 'x-mac-georgian',     // smGeorgian
5979    24: 'x-mac-armenian',     // smArmenian
5980    25: 'x-mac-chinesesimp',  // smSimpChinese
5981    26: 'x-mac-tibetan',      // smTibetan
5982    27: 'x-mac-mongolian',    // smMongolian
5983    28: 'x-mac-ethiopic',     // smEthiopic
5984    29: 'x-mac-ce',           // smCentralEuroRoman
5985    30: 'x-mac-vietnamese',   // smVietnamese
5986    31: 'x-mac-extarabic'     // smExtArabic
5987};
5988
5989// MacOS language ID → encoding. This table stores the exceptional
5990// cases, which override macScriptEncodings. For writing MacOS naming
5991// tables, we need to emit a MacOS script ID. Therefore, we cannot
5992// merge macScriptEncodings into macLanguageEncodings.
5993//
5994// http://unicode.org/Public/MAPPINGS/VENDORS/APPLE/Readme.txt
5995var macLanguageEncodings = {
5996    15: 'x-mac-icelandic',    // langIcelandic
5997    17: 'x-mac-turkish',      // langTurkish
5998    18: 'x-mac-croatian',     // langCroatian
5999    24: 'x-mac-ce',           // langLithuanian
6000    25: 'x-mac-ce',           // langPolish
6001    26: 'x-mac-ce',           // langHungarian
6002    27: 'x-mac-ce',           // langEstonian
6003    28: 'x-mac-ce',           // langLatvian
6004    30: 'x-mac-icelandic',    // langFaroese
6005    37: 'x-mac-romanian',     // langRomanian
6006    38: 'x-mac-ce',           // langCzech
6007    39: 'x-mac-ce',           // langSlovak
6008    40: 'x-mac-ce',           // langSlovenian
6009    143: 'x-mac-inuit',       // langInuktitut
6010    146: 'x-mac-gaelic'       // langIrishGaelicScript
6011};
6012
6013function getEncoding(platformID, encodingID, languageID) {
6014    switch (platformID) {
6015        case 0:  // Unicode
6016            return utf16;
6017
6018        case 1:  // Apple Macintosh
6019            return macLanguageEncodings[languageID] || macScriptEncodings[encodingID];
6020
6021        case 3:  // Microsoft Windows
6022            if (encodingID === 1 || encodingID === 10) {
6023                return utf16;
6024            }
6025
6026            break;
6027    }
6028
6029    return undefined;
6030}
6031
6032// Parse the naming `name` table.
6033// FIXME: Format 1 additional fields are not supported yet.
6034// ltag is the content of the `ltag' table, such as ['en', 'zh-Hans', 'de-CH-1904'].
6035function parseNameTable(data, start, ltag) {
6036    var name = {};
6037    var p = new parse.Parser(data, start);
6038    var format = p.parseUShort();
6039    var count = p.parseUShort();
6040    var stringOffset = p.offset + p.parseUShort();
6041    for (var i = 0; i < count; i++) {
6042        var platformID = p.parseUShort();
6043        var encodingID = p.parseUShort();
6044        var languageID = p.parseUShort();
6045        var nameID = p.parseUShort();
6046        var property = nameTableNames[nameID] || nameID;
6047        var byteLength = p.parseUShort();
6048        var offset = p.parseUShort();
6049        var language = getLanguageCode(platformID, languageID, ltag);
6050        var encoding = getEncoding(platformID, encodingID, languageID);
6051        if (encoding !== undefined && language !== undefined) {
6052            var text = (void 0);
6053            if (encoding === utf16) {
6054                text = decode.UTF16(data, stringOffset + offset, byteLength);
6055            } else {
6056                text = decode.MACSTRING(data, stringOffset + offset, byteLength, encoding);
6057            }
6058
6059            if (text) {
6060                var translations = name[property];
6061                if (translations === undefined) {
6062                    translations = name[property] = {};
6063                }
6064
6065                translations[language] = text;
6066            }
6067        }
6068    }
6069
6070    var langTagCount = 0;
6071    if (format === 1) {
6072        // FIXME: Also handle Microsoft's 'name' table 1.
6073        langTagCount = p.parseUShort();
6074    }
6075
6076    return name;
6077}
6078
6079// {23: 'foo'} → {'foo': 23}
6080// ['bar', 'baz'] → {'bar': 0, 'baz': 1}
6081function reverseDict(dict) {
6082    var result = {};
6083    for (var key in dict) {
6084        result[dict[key]] = parseInt(key);
6085    }
6086
6087    return result;
6088}
6089
6090function makeNameRecord(platformID, encodingID, languageID, nameID, length, offset) {
6091    return new table.Record('NameRecord', [
6092        {name: 'platformID', type: 'USHORT', value: platformID},
6093        {name: 'encodingID', type: 'USHORT', value: encodingID},
6094        {name: 'languageID', type: 'USHORT', value: languageID},
6095        {name: 'nameID', type: 'USHORT', value: nameID},
6096        {name: 'length', type: 'USHORT', value: length},
6097        {name: 'offset', type: 'USHORT', value: offset}
6098    ]);
6099}
6100
6101// Finds the position of needle in haystack, or -1 if not there.
6102// Like String.indexOf(), but for arrays.
6103function findSubArray(needle, haystack) {
6104    var needleLength = needle.length;
6105    var limit = haystack.length - needleLength + 1;
6106
6107    loop:
6108    for (var pos = 0; pos < limit; pos++) {
6109        for (; pos < limit; pos++) {
6110            for (var k = 0; k < needleLength; k++) {
6111                if (haystack[pos + k] !== needle[k]) {
6112                    continue loop;
6113                }
6114            }
6115
6116            return pos;
6117        }
6118    }
6119
6120    return -1;
6121}
6122
6123function addStringToPool(s, pool) {
6124    var offset = findSubArray(s, pool);
6125    if (offset < 0) {
6126        offset = pool.length;
6127        var i = 0;
6128        var len = s.length;
6129        for (; i < len; ++i) {
6130            pool.push(s[i]);
6131        }
6132
6133    }
6134
6135    return offset;
6136}
6137
6138function makeNameTable(names, ltag) {
6139    var nameID;
6140    var nameIDs = [];
6141
6142    var namesWithNumericKeys = {};
6143    var nameTableIds = reverseDict(nameTableNames);
6144    for (var key in names) {
6145        var id = nameTableIds[key];
6146        if (id === undefined) {
6147            id = key;
6148        }
6149
6150        nameID = parseInt(id);
6151
6152        if (isNaN(nameID)) {
6153            throw new Error('Name table entry "' + key + '" does not exist, see nameTableNames for complete list.');
6154        }
6155
6156        namesWithNumericKeys[nameID] = names[key];
6157        nameIDs.push(nameID);
6158    }
6159
6160    var macLanguageIds = reverseDict(macLanguages);
6161    var windowsLanguageIds = reverseDict(windowsLanguages);
6162
6163    var nameRecords = [];
6164    var stringPool = [];
6165
6166    for (var i = 0; i < nameIDs.length; i++) {
6167        nameID = nameIDs[i];
6168        var translations = namesWithNumericKeys[nameID];
6169        for (var lang in translations) {
6170            var text = translations[lang];
6171
6172            // For MacOS, we try to emit the name in the form that was introduced
6173            // in the initial version of the TrueType spec (in the late 1980s).
6174            // However, this can fail for various reasons: the requested BCP 47
6175            // language code might not have an old-style Mac equivalent;
6176            // we might not have a codec for the needed character encoding;
6177            // or the name might contain characters that cannot be expressed
6178            // in the old-style Macintosh encoding. In case of failure, we emit
6179            // the name in a more modern fashion (Unicode encoding with BCP 47
6180            // language tags) that is recognized by MacOS 10.5, released in 2009.
6181            // If fonts were only read by operating systems, we could simply
6182            // emit all names in the modern form; this would be much easier.
6183            // However, there are many applications and libraries that read
6184            // 'name' tables directly, and these will usually only recognize
6185            // the ancient form (silently skipping the unrecognized names).
6186            var macPlatform = 1;  // Macintosh
6187            var macLanguage = macLanguageIds[lang];
6188            var macScript = macLanguageToScript[macLanguage];
6189            var macEncoding = getEncoding(macPlatform, macScript, macLanguage);
6190            var macName = encode.MACSTRING(text, macEncoding);
6191            if (macName === undefined) {
6192                macPlatform = 0;  // Unicode
6193                macLanguage = ltag.indexOf(lang);
6194                if (macLanguage < 0) {
6195                    macLanguage = ltag.length;
6196                    ltag.push(lang);
6197                }
6198
6199                macScript = 4;  // Unicode 2.0 and later
6200                macName = encode.UTF16(text);
6201            }
6202
6203            var macNameOffset = addStringToPool(macName, stringPool);
6204            nameRecords.push(makeNameRecord(macPlatform, macScript, macLanguage,
6205                                            nameID, macName.length, macNameOffset));
6206
6207            var winLanguage = windowsLanguageIds[lang];
6208            if (winLanguage !== undefined) {
6209                var winName = encode.UTF16(text);
6210                var winNameOffset = addStringToPool(winName, stringPool);
6211                nameRecords.push(makeNameRecord(3, 1, winLanguage,
6212                                                nameID, winName.length, winNameOffset));
6213            }
6214        }
6215    }
6216
6217    nameRecords.sort(function(a, b) {
6218        return ((a.platformID - b.platformID) ||
6219                (a.encodingID - b.encodingID) ||
6220                (a.languageID - b.languageID) ||
6221                (a.nameID - b.nameID));
6222    });
6223
6224    var t = new table.Table('name', [
6225        {name: 'format', type: 'USHORT', value: 0},
6226        {name: 'count', type: 'USHORT', value: nameRecords.length},
6227        {name: 'stringOffset', type: 'USHORT', value: 6 + nameRecords.length * 12}
6228    ]);
6229
6230    for (var r = 0; r < nameRecords.length; r++) {
6231        t.fields.push({name: 'record_' + r, type: 'RECORD', value: nameRecords[r]});
6232    }
6233
6234    t.fields.push({name: 'strings', type: 'LITERAL', value: stringPool});
6235    return t;
6236}
6237
6238var _name = { parse: parseNameTable, make: makeNameTable };
6239
6240// The `OS/2` table contains metrics required in OpenType fonts.
6241
6242var unicodeRanges = [
6243    {begin: 0x0000, end: 0x007F}, // Basic Latin
6244    {begin: 0x0080, end: 0x00FF}, // Latin-1 Supplement
6245    {begin: 0x0100, end: 0x017F}, // Latin Extended-A
6246    {begin: 0x0180, end: 0x024F}, // Latin Extended-B
6247    {begin: 0x0250, end: 0x02AF}, // IPA Extensions
6248    {begin: 0x02B0, end: 0x02FF}, // Spacing Modifier Letters
6249    {begin: 0x0300, end: 0x036F}, // Combining Diacritical Marks
6250    {begin: 0x0370, end: 0x03FF}, // Greek and Coptic
6251    {begin: 0x2C80, end: 0x2CFF}, // Coptic
6252    {begin: 0x0400, end: 0x04FF}, // Cyrillic
6253    {begin: 0x0530, end: 0x058F}, // Armenian
6254    {begin: 0x0590, end: 0x05FF}, // Hebrew
6255    {begin: 0xA500, end: 0xA63F}, // Vai
6256    {begin: 0x0600, end: 0x06FF}, // Arabic
6257    {begin: 0x07C0, end: 0x07FF}, // NKo
6258    {begin: 0x0900, end: 0x097F}, // Devanagari
6259    {begin: 0x0980, end: 0x09FF}, // Bengali
6260    {begin: 0x0A00, end: 0x0A7F}, // Gurmukhi
6261    {begin: 0x0A80, end: 0x0AFF}, // Gujarati
6262    {begin: 0x0B00, end: 0x0B7F}, // Oriya
6263    {begin: 0x0B80, end: 0x0BFF}, // Tamil
6264    {begin: 0x0C00, end: 0x0C7F}, // Telugu
6265    {begin: 0x0C80, end: 0x0CFF}, // Kannada
6266    {begin: 0x0D00, end: 0x0D7F}, // Malayalam
6267    {begin: 0x0E00, end: 0x0E7F}, // Thai
6268    {begin: 0x0E80, end: 0x0EFF}, // Lao
6269    {begin: 0x10A0, end: 0x10FF}, // Georgian
6270    {begin: 0x1B00, end: 0x1B7F}, // Balinese
6271    {begin: 0x1100, end: 0x11FF}, // Hangul Jamo
6272    {begin: 0x1E00, end: 0x1EFF}, // Latin Extended Additional
6273    {begin: 0x1F00, end: 0x1FFF}, // Greek Extended
6274    {begin: 0x2000, end: 0x206F}, // General Punctuation
6275    {begin: 0x2070, end: 0x209F}, // Superscripts And Subscripts
6276    {begin: 0x20A0, end: 0x20CF}, // Currency Symbol
6277    {begin: 0x20D0, end: 0x20FF}, // Combining Diacritical Marks For Symbols
6278    {begin: 0x2100, end: 0x214F}, // Letterlike Symbols
6279    {begin: 0x2150, end: 0x218F}, // Number Forms
6280    {begin: 0x2190, end: 0x21FF}, // Arrows
6281    {begin: 0x2200, end: 0x22FF}, // Mathematical Operators
6282    {begin: 0x2300, end: 0x23FF}, // Miscellaneous Technical
6283    {begin: 0x2400, end: 0x243F}, // Control Pictures
6284    {begin: 0x2440, end: 0x245F}, // Optical Character Recognition
6285    {begin: 0x2460, end: 0x24FF}, // Enclosed Alphanumerics
6286    {begin: 0x2500, end: 0x257F}, // Box Drawing
6287    {begin: 0x2580, end: 0x259F}, // Block Elements
6288    {begin: 0x25A0, end: 0x25FF}, // Geometric Shapes
6289    {begin: 0x2600, end: 0x26FF}, // Miscellaneous Symbols
6290    {begin: 0x2700, end: 0x27BF}, // Dingbats
6291    {begin: 0x3000, end: 0x303F}
6291, // CJK Symbols And Punctuation
6292    {begin: 0x3040, end: 0x309F}, // Hiragana
6293    {begin: 0x30A0, end: 0x30FF}, // Katakana
6294    {begin: 0x3100, end: 0x312F}, // Bopomofo
6295    {begin: 0x3130, end: 0x318F}, // Hangul Compatibility Jamo
6296    {begin: 0xA840, end: 0xA87F}, // Phags-pa
6297    {begin: 0x3200, end: 0x32FF}, // Enclosed CJK Letters And Months
6298    {begin: 0x3300, end: 0x33FF}, // CJK Compatibility
6299    {begin: 0xAC00, end: 0xD7AF}, // Hangul Syllables
6300    {begin: 0xD800, end: 0xDFFF}, // Non-Plane 0 *
6301    {begin: 0x10900, end: 0x1091F}, // Phoenicia
6302    {begin: 0x4E00, end: 0x9FFF}, // CJK Unified Ideographs
6303    {begin: 0xE000, end: 0xF8FF}, // Private Use Area (plane 0)
6304    {begin: 0x31C0, end: 0x31EF}, // CJK Strokes
6305    {begin: 0xFB00, end: 0xFB4F}, // Alphabetic Presentation Forms
6306    {begin: 0xFB50, end: 0xFDFF}, // Arabic Presentation Forms-A
6307    {begin: 0xFE20, end: 0xFE2F}, // Combining Half Marks
6308    {begin: 0xFE10, end: 0xFE1F}, // Vertical Forms
6309    {begin: 0xFE50, end: 0xFE6F}, // Small Form Variants
6310    {begin: 0xFE70, end: 0xFEFF}, // Arabic Presentation Forms-B
6311    {begin: 0xFF00, end: 0xFFEF}, // Halfwidth And Fullwidth Forms
6312    {begin: 0xFFF0, end: 0xFFFF}, // Specials
6313    {begin: 0x0F00, end: 0x0FFF}, // Tibetan
6314    {begin: 0x0700, end: 0x074F}, // Syriac
6315    {begin: 0x0780, end: 0x07BF}, // Thaana
6316    {begin: 0x0D80, end: 0x0DFF}, // Sinhala
6317    {begin: 0x1000, end: 0x109F}, // Myanmar
6318    {begin: 0x1200, end: 0x137F}, // Ethiopic
6319    {begin: 0x13A0, end: 0x13FF}, // Cherokee
6320    {begin: 0x1400, end: 0x167F}, // Unified Canadian Aboriginal Syllabics
6321    {begin: 0x1680, end: 0x169F}, // Ogham
6322    {begin: 0x16A0, end: 0x16FF}, // Runic
6323    {begin: 0x1780, end: 0x17FF}, // Khmer
6324    {begin: 0x1800, end: 0x18AF}, // Mongolian
6325    {begin: 0x2800, end: 0x28FF}, // Braille Patterns
6326    {begin: 0xA000, end: 0xA48F}, // Yi Syllables
6327    {begin: 0x1700, end: 0x171F}, // Tagalog
6328    {begin: 0x10300, end: 0x1032F}, // Old Italic
6329    {begin: 0x10330, end: 0x1034F}, // Gothic
6330    {begin: 0x10400, end: 0x1044F}, // Deseret
6331    {begin: 0x1D000, end: 0x1D0FF}, // Byzantine Musical Symbols
6332    {begin: 0x1D400, end: 0x1D7FF}, // Mathematical Alphanumeric Symbols
6333    {begin: 0xFF000, end: 0xFFFFD}, // Private Use (plane 15)
6334    {begin: 0xFE00, end: 0xFE0F}, // Variation Selectors
6335    {begin: 0xE0000, end: 0xE007F}, // Tags
6336    {begin: 0x1900, end: 0x194F}, // Limbu
6337    {begin: 0x1950, end: 0x197F}, // Tai Le
6338    {begin: 0x1980, end: 0x19DF}, // New Tai Lue
6339    {begin: 0x1A00, end: 0x1A1F}, // Buginese
6340    {begin: 0x2C00, end: 0x2C5F}, // Glagolitic
6341    {begin: 0x2D30, end: 0x2D7F}, // Tifinagh
6342    {begin: 0x4DC0, end: 0x4DFF}, // Yijing Hexagram Symbols
6343    {begin: 0xA800, end: 0xA82F}, // Syloti Nagri
6344    {begin: 0x10000, end: 0x1007F}, // Linear B Syllabary
6345    {begin: 0x10140, end: 0x1018F}, // Ancient Greek Numbers
6346    {begin: 0x10380, end: 0x1039F}, // Ugaritic
6347    {begin: 0x103A0, end: 0x103DF}, // Old Persian
6348    {begin: 0x10450, end: 0x1047F}, // Shavian
6349    {begin: 0x10480, end: 0x104AF}, // Osmanya
6350    {begin: 0x10800, end: 0x1083F}, // Cypriot Syllabary
6351    {begin: 0x10A00, end: 0x10A5F}, // Kharoshthi
6352    {begin: 0x1D300, end: 0x1D35F}, // Tai Xuan Jing Symbols
6353    {begin: 0x12000, end: 0x123FF}, // Cuneiform
6354    {begin: 0x1D360, end: 0x1D37F}, // Counting Rod Numerals
6355    {begin: 0x1B80, end: 0x1BBF}, // Sundanese
6356    {begin: 0x1C00, end: 0x1C4F}, // Lepcha
6357    {begin: 0x1C50, end: 0x1C7F}, // Ol Chiki
6358    {begin: 0xA880, end: 0xA8DF}, // Saurashtra
6359    {begin: 0xA900, end: 0xA92F}, // Kayah Li
6360    {begin: 0xA930, end: 0xA95F}, // Rejang
6361    {begin: 0xAA00, end: 0xAA5F}, // Cham
6362    {begin: 0x10190, end: 0x101CF}, // Ancient Symbols
6363    {begin: 0x101D0, end: 0x101FF}, // Phaistos Disc
6364    {begin: 0x102A0, end: 0x102DF}, // Carian
6365    {begin: 0x1F030, end: 0x1F09F}  // Domino Tiles
6366];
6367
6368function getUnicodeRange(unicode) {
6369    for (var i = 0; i < unicodeRanges.length; i += 1) {
6370        var range = unicodeRanges[i];
6371        if (unicode >= range.begin && unicode < range.end) {
6372            return i;
6373        }
6374    }
6375
6376    return -1;
6377}
6378
6379// Parse the OS/2 and Windows metrics `OS/2` table
6380function parseOS2Table(data, start) {
6381    var os2 = {};
6382    var p = new parse.Parser(data, start);
6383    os2.version = p.parseUShort();
6384    os2.xAvgCharWidth = p.parseShort();
6385    os2.usWeightClass = p.parseUShort();
6386    os2.usWidthClass = p.parseUShort();
6387    os2.fsType = p.parseUShort();
6388    os2.ySubscriptXSize = p.parseShort();
6389    os2.ySubscriptYSize = p.parseShort();
6390    os2.ySubscriptXOffset = p.parseShort();
6391    os2.ySubscriptYOffset = p.parseShort();
6392    os2.ySuperscriptXSize = p.parseShort();
6393    os2.ySuperscriptYSize = p.parseShort();
6394    os2.ySuperscriptXOffset = p.parseShort();
6395    os2.ySuperscriptYOffset = p.parseShort();
6396    os2.yStrikeoutSize = p.parseShort();
6397    os2.yStrikeoutPosition = p.parseShort();
6398    os2.sFamilyClass = p.parseShort();
6399    os2.panose = [];
6400    for (var i = 0; i < 10; i++) {
6401        os2.panose[i] = p.parseByte();
6402    }
6403
6404    os2.ulUnicodeRange1 = p.parseULong();
6405    os2.ulUnicodeRange2 = p.parseULong();
6406    os2.ulUnicodeRange3 = p.parseULong();
6407    os2.ulUnicodeRange4 = p.parseULong();
6408    os2.achVendID = String.fromCharCode(p.parseByte(), p.parseByte(), p.parseByte(), p.parseByte());
6409    os2.fsSelection = p.parseUShort();
6410    os2.usFirstCharIndex = p.parseUShort();
6411    os2.usLastCharIndex = p.parseUShort();
6412    os2.sTypoAscender = p.parseShort();
6413    os2.sTypoDescender = p.parseShort();
6414    os2.sTypoLineGap = p.parseShort();
6415    os2.usWinAscent = p.parseUShort();
6416    os2.usWinDescent = p.parseUShort();
6417    if (os2.version >= 1) {
6418        os2.ulCodePageRange1 = p.parseULong();
6419        os2.ulCodePageRange2 = p.parseULong();
6420    }
6421
6422    if (os2.version >= 2) {
6423        os2.sxHeight = p.parseShort();
6424        os2.sCapHeight = p.parseShort();
6425        os2.usDefaultChar = p.parseUShort();
6426        os2.usBreakChar = p.parseUShort();
6427        os2.usMaxContent = p.parseUShort();
6428    }
6429
6430    return os2;
6431}
6432
6433function makeOS2Table(options) {
6434    return new table.Table('OS/2', [
6435        {name: 'version', type: 'USHORT', value: 0x0003},
6436        {name: 'xAvgCharWidth', type: 'SHORT', value: 0},
6437        {name: 'usWeightClass', type: 'USHORT', value: 0},
6438        {name: 'usWidthClass', type: 'USHORT', value: 0},
6439        {name: 'fsType', type: 'USHORT', value: 0},
6440        {name: 'ySubscriptXSize', type: 'SHORT', value: 650},
6441        {name: 'ySubscriptYSize', type: 'SHORT', value: 699},
6442        {name: 'ySubscriptXOffset', type: 'SHORT', value: 0},
6443        {name: 'ySubscriptYOffset', type: 'SHORT', value: 140},
6444        {name: 'ySuperscriptXSize', type: 'SHORT', value: 650},
6445        {name: 'ySuperscriptYSize', type: 'SHORT', value: 699},
6446        {name: 'ySuperscriptXOffset', type: 'SHORT', value: 0},
6447        {name: 'ySuperscriptYOffset', type: 'SHORT', value: 479},
6448        {name: 'yStrikeoutSize', type: 'SHORT', value: 49},
6449        {name: 'yStrikeoutPosition', type: 'SHORT', value: 258},
6450        {name: 'sFamilyClass', type: 'SHORT', value: 0},
6451        {name: 'bFamilyType', type: 'BYTE', value: 0},
6452        {name: 'bSerifStyle', type: 'BYTE', value: 0},
6453        {name: 'bWeight', type: 'BYTE', value: 0},
6454        {name: 'bProportion', type: 'BYTE', value: 0},
6455        {name: 'bContrast', type: 'BYTE', value: 0},
6456        {name: 'bStrokeVariation', type: 'BYTE', value: 0},
6457        {name: 'bArmStyle', type: 'BYTE', value: 0},
6458        {name: 'bLetterform', type: 'BYTE', value: 0},
6459        {name: 'bMidline', type: 'BYTE', value: 0},
6460        {name: 'bXHeight', type: 'BYTE', value: 0},
6461        {name: 'ulUnicodeRange1', type: 'ULONG', value: 0},
6462        {name: 'ulUnicodeRange2', type: 'ULONG', value: 0},
6463        {name: 'ulUnicodeRange3', type: 'ULONG', value: 0},
6464        {name: 'ulUnicodeRange4', type: 'ULONG', value: 0},
6465        {name: 'achVendID', type: 'CHARARRAY', value: 'XXXX'},
6466        {name: 'fsSelection', type: 'USHORT', value: 0},
6467        {name: 'usFirstCharIndex', type: 'USHORT', value: 0},
6468        {name: 'usLastCharIndex', type: 'USHORT', value: 0},
6469        {name: 'sTypoAscender', type: 'SHORT', value: 0},
6470        {name: 'sTypoDescender', type: 'SHORT', value: 0},
6471        {name: 'sTypoLineGap', type: 'SHORT', value: 0},
6472        {name: 'usWinAscent', type: 'USHORT', value: 0},
6473        {name: 'usWinDescent', type: 'USHORT', value: 0},
6474        {name: 'ulCodePageRange1', type: 'ULONG', value: 0},
6475        {name: 'ulCodePageRange2', type: 'ULONG', value: 0},
6476        {name: 'sxHeight', type: 'SHORT', value: 0},
6477        {name: 'sCapHeight', type: 'SHORT', value: 0},
6478        {name: 'usDefaultChar', type: 'USHORT', value: 0},
vendor: 13,645 bytes, lines 6479-6815
6479        {name: 'usBreakChar', type: 'USHORT', value: 0},
6480        {name: 'usMaxContext', type: 'USHORT', value: 0}
6481    ], options);
6482}
6483
6484var os2 = { parse: parseOS2Table, make: makeOS2Table, unicodeRanges: unicodeRanges, getUnicodeRange: getUnicodeRange };
6485
6486// The `post` table stores additional PostScript information, such as glyph names.
6487
6488// Parse the PostScript `post` table
6489function parsePostTable(data, start) {
6490    var post = {};
6491    var p = new parse.Parser(data, start);
6492    post.version = p.parseVersion();
6493    post.italicAngle = p.parseFixed();
6494    post.underlinePosition = p.parseShort();
6495    post.underlineThickness = p.parseShort();
6496    post.isFixedPitch = p.parseULong();
6497    post.minMemType42 = p.parseULong();
6498    post.maxMemType42 = p.parseULong();
6499    post.minMemType1 = p.parseULong();
6500    post.maxMemType1 = p.parseULong();
6501    switch (post.version) {
6502        case 1:
6503            post.names = standardNames.slice();
6504            break;
6505        case 2:
6506            post.numberOfGlyphs = p.parseUShort();
6507            post.glyphNameIndex = new Array(post.numberOfGlyphs);
6508            for (var i = 0; i < post.numberOfGlyphs; i++) {
6509                post.glyphNameIndex[i] = p.parseUShort();
6510            }
6511
6512            post.names = [];
6513            for (var i$1 = 0; i$1 < post.numberOfGlyphs; i$1++) {
6514                if (post.glyphNameIndex[i$1] >= standardNames.length) {
6515                    var nameLength = p.parseChar();
6516                    post.names.push(p.parseString(nameLength));
6517                }
6518            }
6519
6520            break;
6521        case 2.5:
6522            post.numberOfGlyphs = p.parseUShort();
6523            post.offset = new Array(post.numberOfGlyphs);
6524            for (var i$2 = 0; i$2 < post.numberOfGlyphs; i$2++) {
6525                post.offset[i$2] = p.parseChar();
6526            }
6527
6528            break;
6529    }
6530    return post;
6531}
6532
6533function makePostTable() {
6534    return new table.Table('post', [
6535        {name: 'version', type: 'FIXED', value: 0x00030000},
6536        {name: 'italicAngle', type: 'FIXED', value: 0},
6537        {name: 'underlinePosition', type: 'FWORD', value: 0},
6538        {name: 'underlineThickness', type: 'FWORD', value: 0},
6539        {name: 'isFixedPitch', type: 'ULONG', value: 0},
6540        {name: 'minMemType42', type: 'ULONG', value: 0},
6541        {name: 'maxMemType42', type: 'ULONG', value: 0},
6542        {name: 'minMemType1', type: 'ULONG', value: 0},
6543        {name: 'maxMemType1', type: 'ULONG', value: 0}
6544    ]);
6545}
6546
6547var post = { parse: parsePostTable, make: makePostTable };
6548
6549// The `GSUB` table contains ligatures, among other things.
6550
6551var subtableParsers = new Array(9);         // subtableParsers[0] is unused
6552
6553// https://www.microsoft.com/typography/OTSPEC/GSUB.htm#SS
6554subtableParsers[1] = function parseLookup1() {
6555    var start = this.offset + this.relativeOffset;
6556    var substFormat = this.parseUShort();
6557    if (substFormat === 1) {
6558        return {
6559            substFormat: 1,
6560            coverage: this.parsePointer(Parser.coverage),
6561            deltaGlyphId: this.parseUShort()
6562        };
6563    } else if (substFormat === 2) {
6564        return {
6565            substFormat: 2,
6566            coverage: this.parsePointer(Parser.coverage),
6567            substitute: this.parseOffset16List()
6568        };
6569    }
6570    check.assert(false, '0x' + start.toString(16) + ': lookup type 1 format must be 1 or 2.');
6571};
6572
6573// https://www.microsoft.com/typography/OTSPEC/GSUB.htm#MS
6574subtableParsers[2] = function parseLookup2() {
6575    var substFormat = this.parseUShort();
6576    check.argument(substFormat === 1, 'GSUB Multiple Substitution Subtable identifier-format must be 1');
6577    return {
6578        substFormat: substFormat,
6579        coverage: this.parsePointer(Parser.coverage),
6580        sequences: this.parseListOfLists()
6581    };
6582};
6583
6584// https://www.microsoft.com/typography/OTSPEC/GSUB.htm#AS
6585subtableParsers[3] = function parseLookup3() {
6586    var substFormat = this.parseUShort();
6587    check.argument(substFormat === 1, 'GSUB Alternate Substitution Subtable identifier-format must be 1');
6588    return {
6589        substFormat: substFormat,
6590        coverage: this.parsePointer(Parser.coverage),
6591        alternateSets: this.parseListOfLists()
6592    };
6593};
6594
6595// https://www.microsoft.com/typography/OTSPEC/GSUB.htm#LS
6596subtableParsers[4] = function parseLookup4() {
6597    var substFormat = this.parseUShort();
6598    check.argument(substFormat === 1, 'GSUB ligature table identifier-format must be 1');
6599    return {
6600        substFormat: substFormat,
6601        coverage: this.parsePointer(Parser.coverage),
6602        ligatureSets: this.parseListOfLists(function() {
6603            return {
6604                ligGlyph: this.parseUShort(),
6605                components: this.parseUShortList(this.parseUShort() - 1)
6606            };
6607        })
6608    };
6609};
6610
6611var lookupRecordDesc = {
6612    sequenceIndex: Parser.uShort,
6613    lookupListIndex: Parser.uShort
6614};
6615
6616// https://www.microsoft.com/typography/OTSPEC/GSUB.htm#CSF
6617subtableParsers[5] = function parseLookup5() {
6618    var start = this.offset + this.relativeOffset;
6619    var substFormat = this.parseUShort();
6620
6621    if (substFormat === 1) {
6622        return {
6623            substFormat: substFormat,
6624            coverage: this.parsePointer(Parser.coverage),
6625            ruleSets: this.parseListOfLists(function() {
6626                var glyphCount = this.parseUShort();
6627                var substCount = this.parseUShort();
6628                return {
6629                    input: this.parseUShortList(glyphCount - 1),
6630                    lookupRecords: this.parseRecordList(substCount, lookupRecordDesc)
6631                };
6632            })
6633        };
6634    } else if (substFormat === 2) {
6635        return {
6636            substFormat: substFormat,
6637            coverage: this.parsePointer(Parser.coverage),
6638            classDef: this.parsePointer(Parser.classDef),
6639            classSets: this.parseListOfLists(function() {
6640                var glyphCount = this.parseUShort();
6641                var substCount = this.parseUShort();
6642                return {
6643                    classes: this.parseUShortList(glyphCount - 1),
6644                    lookupRecords: this.parseRecordList(substCount, lookupRecordDesc)
6645                };
6646            })
6647        };
6648    } else if (substFormat === 3) {
6649        var glyphCount = this.parseUShort();
6650        var substCount = this.parseUShort();
6651        return {
6652            substFormat: substFormat,
6653            coverages: this.parseList(glyphCount, Parser.pointer(Parser.coverage)),
6654            lookupRecords: this.parseRecordList(substCount, lookupRecordDesc)
6655        };
6656    }
6657    check.assert(false, '0x' + start.toString(16) + ': lookup type 5 format must be 1, 2 or 3.');
6658};
6659
6660// https://www.microsoft.com/typography/OTSPEC/GSUB.htm#CC
6661subtableParsers[6] = function parseLookup6() {
6662    var start = this.offset + this.relativeOffset;
6663    var substFormat = this.parseUShort();
6664    if (substFormat === 1) {
6665        return {
6666            substFormat: 1,
6667            coverage: this.parsePointer(Parser.coverage),
6668            chainRuleSets: this.parseListOfLists(function() {
6669                return {
6670                    backtrack: this.parseUShortList(),
6671                    input: this.parseUShortList(this.parseShort() - 1),
6672                    lookahead: this.parseUShortList(),
6673                    lookupRecords: this.parseRecordList(lookupRecordDesc)
6674                };
6675            })
6676        };
6677    } else if (substFormat === 2) {
6678        return {
6679            substFormat: 2,
6680            coverage: this.parsePointer(Parser.coverage),
6681            backtrackClassDef: this.parsePointer(Parser.classDef),
6682            inputClassDef: this.parsePointer(Parser.classDef),
6683            lookaheadClassDef: this.parsePointer(Parser.classDef),
6684            chainClassSet: this.parseListOfLists(function() {
6685                return {
6686                    backtrack: this.parseUShortList(),
6687                    input: this.parseUShortList(this.parseShort() - 1),
6688                    lookahead: this.parseUShortList(),
6689                    lookupRecords: this.parseRecordList(lookupRecordDesc)
6690                };
6691            })
6692        };
6693    } else if (substFormat === 3) {
6694        return {
6695            substFormat: 3,
6696            backtrackCoverage: this.parseList(Parser.pointer(Parser.coverage)),
6697            inputCoverage: this.parseList(Parser.pointer(Parser.coverage)),
6698            lookaheadCoverage: this.parseList(Parser.pointer(Parser.coverage)),
6699            lookupRecords: this.parseRecordList(lookupRecordDesc)
6700        };
6701    }
6702    check.assert(false, '0x' + start.toString(16) + ': lookup type 6 format must be 1, 2 or 3.');
6703};
6704
6705// https://www.microsoft.com/typography/OTSPEC/GSUB.htm#ES
6706subtableParsers[7] = function parseLookup7() {
6707    // Extension Substitution subtable
6708    var substFormat = this.parseUShort();
6709    check.argument(substFormat === 1, 'GSUB Extension Substitution subtable identifier-format must be 1');
6710    var extensionLookupType = this.parseUShort();
6711    var extensionParser = new Parser(this.data, this.offset + this.parseULong());
6712    return {
6713        substFormat: 1,
6714        lookupType: extensionLookupType,
6715        extension: subtableParsers[extensionLookupType].call(extensionParser)
6716    };
6717};
6718
6719// https://www.microsoft.com/typography/OTSPEC/GSUB.htm#RCCS
6720subtableParsers[8] = function parseLookup8() {
6721    var substFormat = this.parseUShort();
6722    check.argument(substFormat === 1, 'GSUB Reverse Chaining Contextual Single Substitution Subtable identifier-format must be 1');
6723    return {
6724        substFormat: substFormat,
6725        coverage: this.parsePointer(Parser.coverage),
6726        backtrackCoverage: this.parseList(Parser.pointer(Parser.coverage)),
6727        lookaheadCoverage: this.parseList(Parser.pointer(Parser.coverage)),
6728        substitutes: this.parseUShortList()
6729    };
6730};
6731
6732// https://www.microsoft.com/typography/OTSPEC/gsub.htm
6733function parseGsubTable(data, start) {
6734    start = start || 0;
6735    var p = new Parser(data, start);
6736    var tableVersion = p.parseVersion(1);
6737    check.argument(tableVersion === 1 || tableVersion === 1.1, 'Unsupported GSUB table version.');
6738    if (tableVersion === 1) {
6739        return {
6740            version: tableVersion,
6741            scripts: p.parseScriptList(),
6742            features: p.parseFeatureList(),
6743            lookups: p.parseLookupList(subtableParsers)
6744        };
6745    } else {
6746        return {
6747            version: tableVersion,
6748            scripts: p.parseScriptList(),
6749            features: p.parseFeatureList(),
6750            lookups: p.parseLookupList(subtableParsers),
6751            variations: p.parseFeatureVariationsList()
6752        };
6753    }
6754
6755}
6756
6757// GSUB Writing //////////////////////////////////////////////
6758var subtableMakers = new Array(9);
6759
6760subtableMakers[1] = function makeLookup1(subtable) {
6761    if (subtable.substFormat === 1) {
6762        return new table.Table('substitutionTable', [
6763            {name: 'substFormat', type: 'USHORT', value: 1},
6764            {name: 'coverage', type: 'TABLE', value: new table.Coverage(subtable.coverage)},
6765            {name: 'deltaGlyphID', type: 'USHORT', value: subtable.deltaGlyphId}
6766        ]);
6767    } else {
6768        return new table.Table('substitutionTable', [
6769            {name: 'substFormat', type: 'USHORT', value: 2},
6770            {name: 'coverage', type: 'TABLE', value: new table.Coverage(subtable.coverage)}
6771        ].concat(table.ushortList('substitute', subtable.substitute)));
6772    }
6773};
6774
6775subtableMakers[2] = function makeLookup2(subtable) {
6776    check.assert(subtable.substFormat === 1, 'Lookup type 2 substFormat must be 1.');
6777    return new table.Table('substitutionTable', [
6778        {name: 'substFormat', type: 'USHORT', value: 1},
6779        {name: 'coverage', type: 'TABLE', value: new table.Coverage(subtable.coverage)}
6780    ].concat(table.tableList('seqSet', subtable.sequences, function(sequenceSet) {
6781        return new table.Table('sequenceSetTable', table.ushortList('sequence', sequenceSet));
6782    })));
6783};
6784
6785subtableMakers[3] = function makeLookup3(subtable) {
6786    check.assert(subtable.substFormat === 1, 'Lookup type 3 substFormat must be 1.');
6787    return new table.Table('substitutionTable', [
6788        {name: 'substFormat', type: 'USHORT', value: 1},
6789        {name: 'coverage', type: 'TABLE', value: new table.Coverage(subtable.coverage)}
6790    ].concat(table.tableList('altSet', subtable.alternateSets, function(alternateSet) {
6791        return new table.Table('alternateSetTable', table.ushortList('alternate', alternateSet));
6792    })));
6793};
6794
6795subtableMakers[4] = function makeLookup4(subtable) {
6796    check.assert(subtable.substFormat === 1, 'Lookup type 4 substFormat must be 1.');
6797    return new table.Table('substitutionTable', [
6798        {name: 'substFormat', type: 'USHORT', value: 1},
6799        {name: 'coverage', type: 'TABLE', value: new table.Coverage(subtable.coverage)}
6800    ].concat(table.tableList('ligSet', subtable.ligatureSets, function(ligatureSet) {
6801        return new table.Table('ligatureSetTable', table.tableList('ligature', ligatureSet, function(ligature) {
6802            return new table.Table('ligatureTable',
6803                [{name: 'ligGlyph', type: 'USHORT', value: ligature.ligGlyph}]
6804                .concat(table.ushortList('component', ligature.components, ligature.components.length + 1))
6805            );
6806        }));
6807    })));
6808};
6809
6810subtableMakers[6] = function makeLookup6(subtable) {
6811    if (subtable.substFormat === 1) {
6812        var returnTable = new table.Table('chainContextTable', [
6813            {name: 'substFormat', type: 'USHORT', value: subtable.substFormat},
6814            {name: 'coverage', type: 'TABLE', value: new table.Coverage(subtable.coverage)}
6815        ].concat(table.tableList('chainRuleSet', subtable.chainRuleSets, function(ch
6815ainRuleSet) {
6816            return new table.Table('chainRuleSetTable', table.tableList('chainRule', chainRuleSet, function(chainRule) {
6817                var tableData = table.ushortList('backtrackGlyph', chainRule.backtrack, chainRule.backtrack.length)
6818                    .concat(table.ushortList('inputGlyph', chainRule.input, chainRule.input.length + 1))
6819                    .concat(table.ushortList('lookaheadGlyph', chainRule.lookahead, chainRule.lookahead.length))
6820                    .concat(table.ushortList('substitution', [], chainRule.lookupRecords.length));
6821
6822                chainRule.lookupRecords.forEach(function (record, i) {
6823                    tableData = tableData
6824                        .concat({name: 'sequenceIndex' + i, type: 'USHORT', value: record.sequenceIndex})
6825                        .concat({name: 'lookupListIndex' + i, type: 'USHORT', value: record.lookupListIndex});
6826                });
6827                return new table.Table('chainRuleTable', tableData);
6828            }));
6829        })));
6830        return returnTable;
6831    } else if (subtable.substFormat === 2) {
6832        check.assert(false, 'lookup type 6 format 2 is not yet supported.');
6833    } else if (subtable.substFormat === 3) {
6834        var tableData = [
6835            {name: 'substFormat', type: 'USHORT', value: subtable.substFormat} ];
6836
6837        tableData.push({name: 'backtrackGlyphCount', type: 'USHORT', value: subtable.backtrackCoverage.length});
6838        subtable.backtrackCoverage.forEach(function (coverage, i) {
6839            tableData.push({name: 'backtrackCoverage' + i, type: 'TABLE', value: new table.Coverage(coverage)});
6840        });
6841        tableData.push({name: 'inputGlyphCount', type: 'USHORT', value: subtable.inputCoverage.length});
6842        subtable.inputCoverage.forEach(function (coverage, i) {
6843            tableData.push({name: 'inputCoverage' + i, type: 'TABLE', value: new table.Coverage(coverage)});
6844        });
6845        tableData.push({name: 'lookaheadGlyphCount', type: 'USHORT', value: subtable.lookaheadCoverage.length});
6846        subtable.lookaheadCoverage.forEach(function (coverage, i) {
6847            tableData.push({name: 'lookaheadCoverage' + i, type: 'TABLE', value: new table.Coverage(coverage)});
6848        });
6849
6850        tableData.push({name: 'substitutionCount', type: 'USHORT', value: subtable.lookupRecords.length});
6851        subtable.lookupRecords.forEach(function (record, i) {
6852            tableData = tableData
6853                .concat({name: 'sequenceIndex' + i, type: 'USHORT', value: record.sequenceIndex})
6854                .concat({name: 'lookupListIndex' + i, type: 'USHORT', value: record.lookupListIndex});
6855        });
6856
6857        var returnTable$1 = new table.Table('chainContextTable', tableData);
6858
6859        return returnTable$1;
6860    }
6861
6862    check.assert(false, 'lookup type 6 format must be 1, 2 or 3.');
6863};
6864
6865function makeGsubTable(gsub) {
6866    return new table.Table('GSUB', [
6867        {name: 'version', type: 'ULONG', value: 0x10000},
6868        {name: 'scripts', type: 'TABLE', value: new table.ScriptList(gsub.scripts)},
6869        {name: 'features', type: 'TABLE', value: new table.FeatureList(gsub.features)},
6870        {name: 'lookups', type: 'TABLE', value: new table.LookupList(gsub.lookups, subtableMakers)}
6871    ]);
6872}
6873
6874var gsub = { parse: parseGsubTable, make: makeGsubTable };
6875
6876// The `GPOS` table contains kerning pairs, among other things.
6877
6878// Parse the metadata `meta` table.
6879// https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6meta.html
6880function parseMetaTable(data, start) {
6881    var p = new parse.Parser(data, start);
6882    var tableVersion = p.parseULong();
6883    check.argument(tableVersion === 1, 'Unsupported META table version.');
6884    p.parseULong(); // flags - currently unused and set to 0
6885    p.parseULong(); // tableOffset
6886    var numDataMaps = p.parseULong();
6887
6888    var tags = {};
6889    for (var i = 0; i < numDataMaps; i++) {
6890        var tag = p.parseTag();
6891        var dataOffset = p.parseULong();
6892        var dataLength = p.parseULong();
6893        var text = decode.UTF8(data, start + dataOffset, dataLength);
6894
6895        tags[tag] = text;
6896    }
6897    return tags;
6898}
6899
6900function makeMetaTable(tags) {
6901    var numTags = Object.keys(tags).length;
6902    var stringPool = '';
6903    var stringPoolOffset = 16 + numTags * 12;
6904
6905    var result = new table.Table('meta', [
6906        {name: 'version', type: 'ULONG', value: 1},
6907        {name: 'flags', type: 'ULONG', value: 0},
6908        {name: 'offset', type: 'ULONG', value: stringPoolOffset},
6909        {name: 'numTags', type: 'ULONG', value: numTags}
6910    ]);
6911
6912    for (var tag in tags) {
6913        var pos = stringPool.length;
6914        stringPool += tags[tag];
6915
6916        result.fields.push({name: 'tag ' + tag, type: 'TAG', value: tag});
6917        result.fields.push({name: 'offset ' + tag, type: 'ULONG', value: stringPoolOffset + pos});
6918        result.fields.push({name: 'length ' + tag, type: 'ULONG', value: tags[tag].length});
6919    }
6920
6921    result.fields.push({name: 'stringPool', type: 'CHARARRAY', value: stringPool});
6922
6923    return result;
6924}
6925
6926var meta = { parse: parseMetaTable, make: makeMetaTable };
6927
6928// The `sfnt` wrapper provides organization for the tables in the font.
6929
6930function log2(v) {
6931    return Math.log(v) / Math.log(2) | 0;
6932}
6933
6934function computeCheckSum(bytes) {
6935    while (bytes.length % 4 !== 0) {
6936        bytes.push(0);
6937    }
6938
6939    var sum = 0;
6940    for (var i = 0; i < bytes.length; i += 4) {
6941        sum += (bytes[i] << 24) +
6942            (bytes[i + 1] << 16) +
6943            (bytes[i + 2] << 8) +
6944            (bytes[i + 3]);
6945    }
6946
6947    sum %= Math.pow(2, 32);
6948    return sum;
6949}
6950
6951function makeTableRecord(tag, checkSum, offset, length) {
6952    return new table.Record('Table Record', [
6953        {name: 'tag', type: 'TAG', value: tag !== undefined ? tag : ''},
6954        {name: 'checkSum', type: 'ULONG', value: checkSum !== undefined ? checkSum : 0},
6955        {name: 'offset', type: 'ULONG', value: offset !== undefined ? offset : 0},
6956        {name: 'length', type: 'ULONG', value: length !== undefined ? length : 0}
6957    ]);
6958}
6959
6960function makeSfntTable(tables) {
6961    var sfnt = new table.Table('sfnt', [
6962        {name: 'version', type: 'TAG', value: 'OTTO'},
6963        {name: 'numTables', type: 'USHORT', value: 0},
6964        {name: 'searchRange', type: 'USHORT', value: 0},
6965        {name: 'entrySelector', type: 'USHORT', value: 0},
6966        {name: 'rangeShift', type: 'USHORT', value: 0}
6967    ]);
6968    sfnt.tables = tables;
6969    sfnt.numTables = tables.length;
6970    var highestPowerOf2 = Math.pow(2, log2(sfnt.numTables));
6971    sfnt.searchRange = 16 * highestPowerOf2;
6972    sfnt.entrySelector = log2(highestPowerOf2);
6973    sfnt.rangeShift = sfnt.numTables * 16 - sfnt.searchRange;
6974
6975    var recordFields = [];
6976    var tableFields = [];
6977
6978    var offset = sfnt.sizeOf() + (makeTableRecord().sizeOf() * sfnt.numTables);
6979    while (offset % 4 !== 0) {
6980        offset += 1;
6981        tableFields.push({name: 'padding', type: 'BYTE', value: 0});
6982    }
6983
6984    for (var i = 0; i < tables.length; i += 1) {
6985        var t = tables[i];
6986        check.argument(t.tableName.length === 4, 'Table name' + t.tableName + ' is invalid.');
6987        var tableLength = t.sizeOf();
6988        var tableRecord = makeTableRecord(t.tableName, computeCheckSum(t.encode()), offset, tableLength);
6989        recordFields.push({name: tableRecord.tag + ' Table Record', type: 'RECORD', value: tableRecord});
6990        tableFields.push({name: t.tableName + ' table', type: 'RECORD', value: t});
6991        offset += tableLength;
6992        check.argument(!isNaN(offset), 'Something went wrong calculating the offset.');
6993        while (offset % 4 !== 0) {
6994            offset += 1;
6995            tableFields.push({name: 'padding', type: 'BYTE', value: 0});
6996        }
6997    }
6998
6999    // Table records need to be sorted alphabetically.
7000    recordFields.sort(function(r1, r2) {
7001        if (r1.value.tag > r2.value.tag) {
7002            return 1;
7003        } else {
7004            return -1;
7005        }
7006    });
7007
7008    sfnt.fields = sfnt.fields.concat(recordFields);
7009    sfnt.fields = sfnt.fields.concat(tableFields);
7010    return sfnt;
7011}
7012
7013// Get the metrics for a character. If the string has more than one character
7014// this function returns metrics for the first available character.
7015// You can provide optional fallback metrics if no characters are available.
7016function metricsForChar(font, chars, notFoundMetrics) {
7017    for (var i = 0; i < chars.length; i += 1) {
7018        var glyphIndex = font.charToGlyphIndex(chars[i]);
7019        if (glyphIndex > 0) {
7020            var glyph = font.glyphs.get(glyphIndex);
7021            return glyph.getMetrics();
7022        }
7023    }
7024
7025    return notFoundMetrics;
7026}
7027
7028function average(vs) {
7029    var sum = 0;
7030    for (var i = 0; i < vs.length; i += 1) {
7031        sum += vs[i];
7032    }
7033
7034    return sum / vs.length;
7035}
7036
7037// Convert the font object to a SFNT data structure.
7038// This structure contains all the necessary tables and metadata to create a binary OTF file.
7039function fontToSfntTable(font) {
7040    var xMins = [];
7041    var yMins = [];
7042    var xMaxs = [];
7043    var yMaxs = [];
7044    var advanceWidths = [];
7045    var leftSideBearings = [];
7046    var rightSideBearings = [];
7047    var firstCharIndex;
7048    var lastCharIndex = 0;
7049    var ulUnicodeRange1 = 0;
7050    var ulUnicodeRange2 = 0;
7051    var ulUnicodeRange3 = 0;
7052    var ulUnicodeRange4 = 0;
7053
7054    for (var i = 0; i < font.glyphs.length; i += 1) {
7055        var glyph = font.glyphs.get(i);
7056        var unicode = glyph.unicode | 0;
7057
7058        if (isNaN(glyph.advanceWidth)) {
7059            throw new Error('Glyph ' + glyph.name + ' (' + i + '): advanceWidth is not a number.');
7060        }
7061
7062        if (firstCharIndex > unicode || firstCharIndex === undefined) {
7063            // ignore .notdef char
7064            if (unicode > 0) {
7065                firstCharIndex = unicode;
7066            }
7067        }
7068
7069        if (lastCharIndex < unicode) {
7070            lastCharIndex = unicode;
7071        }
7072
7073        var position = os2.getUnicodeRange(unicode);
7074        if (position < 32) {
7075            ulUnicodeRange1 |= 1 << position;
7076        } else if (position < 64) {
7077            ulUnicodeRange2 |= 1 << position - 32;
7078        } else if (position < 96) {
7079            ulUnicodeRange3 |= 1 << position - 64;
7080        } else if (position < 123) {
7081            ulUnicodeRange4 |= 1 << position - 96;
7082        } else {
7083            throw new Error('Unicode ranges bits > 123 are reserved for internal usage');
7084        }
7085        // Skip non-important characters.
7086        if (glyph.name === '.notdef') { continue; }
7087        var metrics = glyph.getMetrics();
7088        xMins.push(metrics.xMin);
7089        yMins.push(metrics.yMin);
7090        xMaxs.push(metrics.xMax);
7091        yMaxs.push(metrics.yMax);
7092        leftSideBearings.push(metrics.leftSideBearing);
7093        rightSideBearings.push(metrics.rightSideBearing);
7094        advanceWidths.push(glyph.advanceWidth);
7095    }
7096
7097    var globals = {
7098        xMin: Math.min.apply(null, xMins),
7099        yMin: Math.min.apply(null, yMins),
7100        xMax: Math.max.apply(null, xMaxs),
7101        yMax: Math.max.apply(null, yMaxs),
7102        advanceWidthMax: Math.max.apply(null, advanceWidths),
7103        advanceWidthAvg: average(advanceWidths),
7104        minLeftSideBearing: Math.min.apply(null, leftSideBearings),
7105        maxLeftSideBearing: Math.max.apply(null, leftSideBearings),
7106        minRightSideBearing: Math.min.apply(null, rightSideBearings)
7107    };
7108    globals.ascender = font.ascender;
7109    globals.descender = font.descender;
7110
7111    var headTable = head.make({
7112        flags: 3, // 00000011 (baseline for font at y=0; left sidebearing point at x=0)
7113        unitsPerEm: font.unitsPerEm,
7114        xMin: globals.xMin,
7115        yMin: globals.yMin,
7116        xMax: globals.xMax,
7117        yMax: globals.yMax,
7118        lowestRecPPEM: 3,
7119        createdTimestamp: font.createdTimestamp
7120    });
7121
7122    var hheaTable = hhea.make({
7123        ascender: globals.ascender,
7124        descender: globals.descender,
7125        advanceWidthMax: globals.advanceWidthMax,
7126        minLeftSideBearing: globals.minLeftSideBearing,
7127        minRightSideBearing: globals.minRightSideBearing,
7128        xMaxExtent: globals.maxLeftSideBearing + (globals.xMax - globals.xMin),
7129        numberOfHMetrics: font.glyphs.length
7130    });
7131
7132    var maxpTable = maxp.make(font.glyphs.length);
7133
7134    var os2Table = os2.make(Object.assign({
7135        xAvgCharWidth: Math.round(globals.advanceWidthAvg),
7136        usFirstCharIndex: firstCharIndex,
7137        usLastCharIndex: lastCharIndex,
7138        ulUnicodeRange1: ulUnicodeRange1,
7139        ulUnicodeRange2: ulUnicodeRange2,
7140        ulUnicodeRange3: ulUnicodeRange3,
7141        ulUnicodeRange4: ulUnicodeRange4,
7142        // See http://typophile.com/node/13081 for more info on vertical metrics.
7143        // We get metrics for typical characters (such as "x" for xHeight).
7144        // We provide some fallback characters if characters are unavailable: their
7145        // ordering was chosen experimentally.
7146        sTypoAscender: globals.ascender,
7147        sTypoDescender: globals.descender,
7148        sTypoLineGap: 0,
7149        usWinAscent: globals.yMax,
7150        usWinDescent: Math.abs(globals.yMin),
7151        ulCodePageRange1: 1, // FIXME: hard-code Latin 1 support for now
7152        sxHeight: metricsForChar(font, 'xyvw', {yMax: Math.round(globals.ascender / 2)}).yMax,
7153        sCapHeight: metricsForChar(font, 'HIKLEFJMNTZBDPRAGOQSUVWXY', globals).yMax,
7154        usDefaultChar: font.hasChar(' ') ? 32 : 0, // Use space as the default character, if available.
7155        usBreakChar: font.hasChar(' ') ? 32 : 0, // Use space as the break character, if available.
7156    }, font.tables.os2));
7157
7158    var hmtxTable = hmtx.make(font.glyphs);
7159    var cmapTable = cmap.make(font.glyphs);
7160
7161    var englishFamilyName = font.getEnglishName('fontFamily');
7162    var englishStyleName = font.getEnglishName('fontSubfamily');
7163    var englishFullName = englishFamilyName + ' ' + englishStyleName;
7164    var postScriptName = font.getEnglishName('postScriptName');
7165    if (!postScriptName) {
7166        postScriptName = englishFamilyName.replace(/\s/g, '') + '-' + englishStyleName;
7167    }
7168
7169    var names = {};
7170    for (var n in font.names) {
7171        names[n] = font.names[n];
7172    }
7173
7174    if (!names.uniqueID) {
7175        names.uniqueID = {en: font.getEnglishName('manufacturer') + ':' + englishFullName};
7176    }
7177
7178    if (!names.postScriptName) {
7179        names.postScriptName = {en: postScriptName};
7180    }
7181
7182    if (!names.preferredFamily) {
7183        names.preferredFamily = font.names.fontFamily;
7184    }
7185
7186    if (!names.preferredSubfamily) {
7187        names.preferredSubfamily = font.names.fontSubfamily;
7188    }
7189
7190    var languageTags = [];
7191    var nameTable = _name.make(names, languageTags);
7192    var ltagTable = (languageTags.length > 0 ? ltag.make(languageTags) : undefined);
7193
7194    var postTable = post.make();
7195    var cffTable = cff.make(font.glyphs, {
7196        version: font.getEnglishName('version'),
7197        fullName: englishFullName,
7198        familyName: englishFamilyName,
7199        weightName: englishStyleName,
7200        postScriptName: postScriptName,
7201        unitsPerEm: font.unitsPerEm,
7202        fontBBox: [0, globals.yMin, globals.ascender, globals.advanceWidthMax]
7203    });
7204
7205    var metaTable = (font.metas && Object.keys(font.metas).length > 0) ? meta.make(font.metas) : undefined;
7206
7207    // The order does not matter because makeSfntTable() will sort them.
7208    var tables = [headTable, hheaTable, maxpTable, os2Table, nameTable, cmapTable, postTable, cffTable, hmtxTable];
7209    if (ltagTable) {
7210        tables.push(ltagTable);
7211    }
7212    // Optional tables
7213    if (font.tables.gsub) {
7214        tables.push(gsub.make(font.tables.gsub));
7215    }
7216    if (metaTable) {
7217        tables.push(metaTable);
7218    }
7219
7220    var sfntTable = makeSfntTable(tables);
7221
7222    // Compute the font's checkSum and store it in head.checkSumAdjustment.
7223    var bytes = sfntTable.encode();
7224    var checkSum = computeCheckSum(bytes);
7225    var tableFields = sfntTable.fields;
7226    var checkSumAdjusted = false;
7227    for (var i$1 = 0; i$1 < tableFields.length; i$1 += 1) {
7228        if (tableFields[i$1].name === 'head table') {
7229            tableFields[i$1].value.checkSumAdjustment = 0xB1B0AFBA - checkSum;
7230            checkSumAdjusted = true;
7231            break;
7232        }
7233    }
7234
7235    if (!checkSumAdjusted) {
7236        throw new Error('Could not find head table with checkSum to adjust.');
7237    }
7238
7239    return sfntTable;
7240}
7241
7242var sfnt = { make: makeSfntTable, fontToTable: fontToSfntTable, computeCheckSum: computeCheckSum };
7243
7244// The Layout object is the prototype of Substitution objects, and provides
7245
7246function searchTag(arr, tag) {
7247    /* jshint bitwise: false */
7248    var imin = 0;
7249    var imax = arr.length - 1;
7250    while (imin <= imax) {
7251        var imid = (imin + imax) >>> 1;
7252        var val = arr[imid].tag;
7253        if (val === tag) {
7254            return imid;
7255        } else if (val < tag) {
7256            imin = imid + 1;
7257        } else { imax = imid - 1; }
7258    }
7259    // Not found: return -1-insertion point
7260    return -imin - 1;
7261}
7262
7263function binSearch(arr, value) {
7264    /* jshint bitwise: false */
7265    var imin = 0;
7266    var imax = arr.length - 1;
7267    while (imin <= imax) {
7268        var imid = (imin + imax) >>> 1;
7269        var val = arr[imid];
7270        if (val === value) {
7271            return imid;
7272        } else if (val < value) {
7273            imin = imid + 1;
7274        } else { imax = imid - 1; }
7275    }
7276    // Not found: return -1-insertion point
7277    return -imin - 1;
7278}
7279
7280// binary search in a list of ranges (coverage, class definition)
7281function searchRange(ranges, value) {
7282    // jshint bitwise: false
7283    var range;
7284    var imin = 0;
7285    var imax = ranges.length - 1;
7286    while (imin <= imax) {
7287        var imid = (imin + imax) >>> 1;
7288        range = ranges[imid];
7289        var start = range.start;
7290        if (start === value) {
7291            return range;
7292        } else if (start < value) {
7293            imin = imid + 1;
7294        } else { imax = imid - 1; }
7295    }
7296    if (imin > 0) {
7297        range = ranges[imin - 1];
7298        if (value > range.end) { return 0; }
7299        return range;
7300    }
7301}
7302
7303/**
vendor: 99,243 bytes, lines 7304-10962
7304 * @exports opentype.Layout
7305 * @class
7306 */
7307function Layout(font, tableName) {
7308    this.font = font;
7309    this.tableName = tableName;
7310}
7311
7312Layout.prototype = {
7313
7314    /**
7315     * Binary search an object by "tag" property
7316     * @instance
7317     * @function searchTag
7318     * @memberof opentype.Layout
7319     * @param  {Array} arr
7320     * @param  {string} tag
7321     * @return {number}
7322     */
7323    searchTag: searchTag,
7324
7325    /**
7326     * Binary search in a list of numbers
7327     * @instance
7328     * @function binSearch
7329     * @memberof opentype.Layout
7330     * @param  {Array} arr
7331     * @param  {number} value
7332     * @return {number}
7333     */
7334    binSearch: binSearch,
7335
7336    /**
7337     * Get or create the Layout table (GSUB, GPOS etc).
7338     * @param  {boolean} create - Whether to create a new one.
7339     * @return {Object} The GSUB or GPOS table.
7340     */
7341    getTable: function(create) {
7342        var layout = this.font.tables[this.tableName];
7343        if (!layout && create) {
7344            layout = this.font.tables[this.tableName] = this.createDefaultTable();
7345        }
7346        return layout;
7347    },
7348
7349    /**
7350     * Returns all scripts in the substitution table.
7351     * @instance
7352     * @return {Array}
7353     */
7354    getScriptNames: function() {
7355        var layout = this.getTable();
7356        if (!layout) { return []; }
7357        return layout.scripts.map(function(script) {
7358            return script.tag;
7359        });
7360    },
7361
7362    /**
7363     * Returns the best bet for a script name.
7364     * Returns 'DFLT' if it exists.
7365     * If not, returns 'latn' if it exists.
7366     * If neither exist, returns undefined.
7367     */
7368    getDefaultScriptName: function() {
7369        var layout = this.getTable();
7370        if (!layout) { return; }
7371        var hasLatn = false;
7372        for (var i = 0; i < layout.scripts.length; i++) {
7373            var name = layout.scripts[i].tag;
7374            if (name === 'DFLT') { return name; }
7375            if (name === 'latn') { hasLatn = true; }
7376        }
7377        if (hasLatn) { return 'latn'; }
7378    },
7379
7380    /**
7381     * Returns all LangSysRecords in the given script.
7382     * @instance
7383     * @param {string} [script='DFLT']
7384     * @param {boolean} create - forces the creation of this script table if it doesn't exist.
7385     * @return {Object} An object with tag and script properties.
7386     */
7387    getScriptTable: function(script, create) {
7388        var layout = this.getTable(create);
7389        if (layout) {
7390            script = script || 'DFLT';
7391            var scripts = layout.scripts;
7392            var pos = searchTag(layout.scripts, script);
7393            if (pos >= 0) {
7394                return scripts[pos].script;
7395            } else if (create) {
7396                var scr = {
7397                    tag: script,
7398                    script: {
7399                        defaultLangSys: {reserved: 0, reqFeatureIndex: 0xffff, featureIndexes: []},
7400                        langSysRecords: []
7401                    }
7402                };
7403                scripts.splice(-1 - pos, 0, scr);
7404                return scr.script;
7405            }
7406        }
7407    },
7408
7409    /**
7410     * Returns a language system table
7411     * @instance
7412     * @param {string} [script='DFLT']
7413     * @param {string} [language='dlft']
7414     * @param {boolean} create - forces the creation of this langSysTable if it doesn't exist.
7415     * @return {Object}
7416     */
7417    getLangSysTable: function(script, language, create) {
7418        var scriptTable = this.getScriptTable(script, create);
7419        if (scriptTable) {
7420            if (!language || language === 'dflt' || language === 'DFLT') {
7421                return scriptTable.defaultLangSys;
7422            }
7423            var pos = searchTag(scriptTable.langSysRecords, language);
7424            if (pos >= 0) {
7425                return scriptTable.langSysRecords[pos].langSys;
7426            } else if (create) {
7427                var langSysRecord = {
7428                    tag: language,
7429                    langSys: {reserved: 0, reqFeatureIndex: 0xffff, featureIndexes: []}
7430                };
7431                scriptTable.langSysRecords.splice(-1 - pos, 0, langSysRecord);
7432                return langSysRecord.langSys;
7433            }
7434        }
7435    },
7436
7437    /**
7438     * Get a specific feature table.
7439     * @instance
7440     * @param {string} [script='DFLT']
7441     * @param {string} [language='dlft']
7442     * @param {string} feature - One of the codes listed at https://www.microsoft.com/typography/OTSPEC/featurelist.htm
7443     * @param {boolean} create - forces the creation of the feature table if it doesn't exist.
7444     * @return {Object}
7445     */
7446    getFeatureTable: function(script, language, feature, create) {
7447        var langSysTable = this.getLangSysTable(script, language, create);
7448        if (langSysTable) {
7449            var featureRecord;
7450            var featIndexes = langSysTable.featureIndexes;
7451            var allFeatures = this.font.tables[this.tableName].features;
7452            // The FeatureIndex array of indices is in arbitrary order,
7453            // even if allFeatures is sorted alphabetically by feature tag.
7454            for (var i = 0; i < featIndexes.length; i++) {
7455                featureRecord = allFeatures[featIndexes[i]];
7456                if (featureRecord.tag === feature) {
7457                    return featureRecord.feature;
7458                }
7459            }
7460            if (create) {
7461                var index = allFeatures.length;
7462                // Automatic ordering of features would require to shift feature indexes in the script list.
7463                check.assert(index === 0 || feature >= allFeatures[index - 1].tag, 'Features must be added in alphabetical order.');
7464                featureRecord = {
7465                    tag: feature,
7466                    feature: { params: 0, lookupListIndexes: [] }
7467                };
7468                allFeatures.push(featureRecord);
7469                featIndexes.push(index);
7470                return featureRecord.feature;
7471            }
7472        }
7473    },
7474
7475    /**
7476     * Get the lookup tables of a given type for a script/language/feature.
7477     * @instance
7478     * @param {string} [script='DFLT']
7479     * @param {string} [language='dlft']
7480     * @param {string} feature - 4-letter feature code
7481     * @param {number} lookupType - 1 to 9
7482     * @param {boolean} create - forces the creation of the lookup table if it doesn't exist, with no subtables.
7483     * @return {Object[]}
7484     */
7485    getLookupTables: function(script, language, feature, lookupType, create) {
7486        var featureTable = this.getFeatureTable(script, language, feature, create);
7487        var tables = [];
7488        if (featureTable) {
7489            var lookupTable;
7490            var lookupListIndexes = featureTable.lookupListIndexes;
7491            var allLookups = this.font.tables[this.tableName].lookups;
7492            // lookupListIndexes are in no particular order, so use naive search.
7493            for (var i = 0; i < lookupListIndexes.length; i++) {
7494                lookupTable = allLookups[lookupListIndexes[i]];
7495                if (lookupTable.lookupType === lookupType) {
7496                    tables.push(lookupTable);
7497                }
7498            }
7499            if (tables.length === 0 && create) {
7500                lookupTable = {
7501                    lookupType: lookupType,
7502                    lookupFlag: 0,
7503                    subtables: [],
7504                    markFilteringSet: undefined
7505                };
7506                var index = allLookups.length;
7507                allLookups.push(lookupTable);
7508                lookupListIndexes.push(index);
7509                return [lookupTable];
7510            }
7511        }
7512        return tables;
7513    },
7514
7515    /**
7516     * Find a glyph in a class definition table
7517     * https://docs.microsoft.com/en-us/typography/opentype/spec/chapter2#class-definition-table
7518     * @param {object} classDefTable - an OpenType Layout class definition table
7519     * @param {number} glyphIndex - the index of the glyph to find
7520     * @returns {number} -1 if not found
7521     */
7522    getGlyphClass: function(classDefTable, glyphIndex) {
7523        switch (classDefTable.format) {
7524            case 1:
7525                if (classDefTable.startGlyph <= glyphIndex && glyphIndex < classDefTable.startGlyph + classDefTable.classes.length) {
7526                    return classDefTable.classes[glyphIndex - classDefTable.startGlyph];
7527                }
7528                return 0;
7529            case 2:
7530                var range = searchRange(classDefTable.ranges, glyphIndex);
7531                return range ? range.classId : 0;
7532        }
7533    },
7534
7535    /**
7536     * Find a glyph in a coverage table
7537     * https://docs.microsoft.com/en-us/typography/opentype/spec/chapter2#coverage-table
7538     * @param {object} coverageTable - an OpenType Layout coverage table
7539     * @param {number} glyphIndex - the index of the glyph to find
7540     * @returns {number} -1 if not found
7541     */
7542    getCoverageIndex: function(coverageTable, glyphIndex) {
7543        switch (coverageTable.format) {
7544            case 1:
7545                var index = binSearch(coverageTable.glyphs, glyphIndex);
7546                return index >= 0 ? index : -1;
7547            case 2:
7548                var range = searchRange(coverageTable.ranges, glyphIndex);
7549                return range ? range.index + glyphIndex - range.start : -1;
7550        }
7551    },
7552
7553    /**
7554     * Returns the list of glyph indexes of a coverage table.
7555     * Format 1: the list is stored raw
7556     * Format 2: compact list as range records.
7557     * @instance
7558     * @param  {Object} coverageTable
7559     * @return {Array}
7560     */
7561    expandCoverage: function(coverageTable) {
7562        if (coverageTable.format === 1) {
7563            return coverageTable.glyphs;
7564        } else {
7565            var glyphs = [];
7566            var ranges = coverageTable.ranges;
7567            for (var i = 0; i < ranges.length; i++) {
7568                var range = ranges[i];
7569                var start = range.start;
7570                var end = range.end;
7571                for (var j = start; j <= end; j++) {
7572                    glyphs.push(j);
7573                }
7574            }
7575            return glyphs;
7576        }
7577    }
7578
7579};
7580
7581// The Position object provides utility methods to manipulate
7582
7583/**
7584 * @exports opentype.Position
7585 * @class
7586 * @extends opentype.Layout
7587 * @param {opentype.Font}
7588 * @constructor
7589 */
7590function Position(font) {
7591    Layout.call(this, font, 'gpos');
7592}
7593
7594Position.prototype = Layout.prototype;
7595
7596/**
7597 * Init some data for faster and easier access later.
7598 */
7599Position.prototype.init = function() {
7600    var script = this.getDefaultScriptName();
7601    this.defaultKerningTables = this.getKerningTables(script);
7602};
7603
7604/**
7605 * Find a glyph pair in a list of lookup tables of type 2 and retrieve the xAdvance kerning value.
7606 *
7607 * @param {integer} leftIndex - left glyph index
7608 * @param {integer} rightIndex - right glyph index
7609 * @returns {integer}
7610 */
7611Position.prototype.getKerningValue = function(kerningLookups, leftIndex, rightIndex) {
7612    for (var i = 0; i < kerningLookups.length; i++) {
7613        var subtables = kerningLookups[i].subtables;
7614        for (var j = 0; j < subtables.length; j++) {
7615            var subtable = subtables[j];
7616            var covIndex = this.getCoverageIndex(subtable.coverage, leftIndex);
7617            if (covIndex < 0) { continue; }
7618            switch (subtable.posFormat) {
7619                case 1:
7620                    // Search Pair Adjustment Positioning Format 1
7621                    var pairSet = subtable.pairSets[covIndex];
7622                    for (var k = 0; k < pairSet.length; k++) {
7623                        var pair = pairSet[k];
7624                        if (pair.secondGlyph === rightIndex) {
7625                            return pair.value1 && pair.value1.xAdvance || 0;
7626                        }
7627                    }
7628                    break;      // left glyph found, not right glyph - try next subtable
7629                case 2:
7630                    // Search Pair Adjustment Positioning Format 2
7631                    var class1 = this.getGlyphClass(subtable.classDef1, leftIndex);
7632                    var class2 = this.getGlyphClass(subtable.classDef2, rightIndex);
7633                    var pair$1 = subtable.classRecords[class1][class2];
7634                    return pair$1.value1 && pair$1.value1.xAdvance || 0;
7635            }
7636        }
7637    }
7638    return 0;
7639};
7640
7641/**
7642 * List all kerning lookup tables.
7643 *
7644 * @param {string} [script='DFLT'] - use font.position.getDefaultScriptName() for a better default value
7645 * @param {string} [language='dflt']
7646 * @return {object[]} The list of kerning lookup tables (may be empty), or undefined if there is no GPOS table (and we should use the kern table)
7647 */
7648Position.prototype.getKerningTables = function(script, language) {
7649    if (this.font.tables.gpos) {
7650        return this.getLookupTables(script, language, 'kern', 2);
7651    }
7652};
7653
7654// The Substitution object provides utility methods to manipulate
7655
7656/**
7657 * @exports opentype.Substitution
7658 * @class
7659 * @extends opentype.Layout
7660 * @param {opentype.Font}
7661 * @constructor
7662 */
7663function Substitution(font) {
7664    Layout.call(this, font, 'gsub');
7665}
7666
7667// Check if 2 arrays of primitives are equal.
7668function arraysEqual(ar1, ar2) {
7669    var n = ar1.length;
7670    if (n !== ar2.length) { return false; }
7671    for (var i = 0; i < n; i++) {
7672        if (ar1[i] !== ar2[i]) { return false; }
7673    }
7674    return true;
7675}
7676
7677// Find the first subtable of a lookup table in a particular format.
7678function getSubstFormat(lookupTable, format, defaultSubtable) {
7679    var subtables = lookupTable.subtables;
7680    for (var i = 0; i < subtables.length; i++) {
7681        var subtable = subtables[i];
7682        if (subtable.substFormat === format) {
7683            return subtable;
7684        }
7685    }
7686    if (defaultSubtable) {
7687        subtables.push(defaultSubtable);
7688        return defaultSubtable;
7689    }
7690    return undefined;
7691}
7692
7693Substitution.prototype = Layout.prototype;
7694
7695/**
7696 * Create a default GSUB table.
7697 * @return {Object} gsub - The GSUB table.
7698 */
7699Substitution.prototype.createDefaultTable = function() {
7700    // Generate a default empty GSUB table with just a DFLT script and dflt lang sys.
7701    return {
7702        version: 1,
7703        scripts: [{
7704            tag: 'DFLT',
7705            script: {
7706                defaultLangSys: { reserved: 0, reqFeatureIndex: 0xffff, featureIndexes: [] },
7707                langSysRecords: []
7708            }
7709        }],
7710        features: [],
7711        lookups: []
7712    };
7713};
7714
7715/**
7716 * List all single substitutions (lookup type 1) for a given script, language, and feature.
7717 * @param {string} [script='DFLT']
7718 * @param {string} [language='dflt']
7719 * @param {string} feature - 4-character feature name ('aalt', 'salt', 'ss01'...)
7720 * @return {Array} substitutions - The list of substitutions.
7721 */
7722Substitution.prototype.getSingle = function(feature, script, language) {
7723    var substitutions = [];
7724    var lookupTables = this.getLookupTables(script, language, feature, 1);
7725    for (var idx = 0; idx < lookupTables.length; idx++) {
7726        var subtables = lookupTables[idx].subtables;
7727        for (var i = 0; i < subtables.length; i++) {
7728            var subtable = subtables[i];
7729            var glyphs = this.expandCoverage(subtable.coverage);
7730            var j = (void 0);
7731            if (subtable.substFormat === 1) {
7732                var delta = subtable.deltaGlyphId;
7733                for (j = 0; j < glyphs.length; j++) {
7734                    var glyph = glyphs[j];
7735                    substitutions.push({ sub: glyph, by: glyph + delta });
7736                }
7737            } else {
7738                var substitute = subtable.substitute;
7739                for (j = 0; j < glyphs.length; j++) {
7740                    substitutions.push({ sub: glyphs[j], by: substitute[j] });
7741                }
7742            }
7743        }
7744    }
7745    return substitutions;
7746};
7747
7748/**
7749 * List all multiple substitutions (lookup type 2) for a given script, language, and feature.
7750 * @param {string} [script='DFLT']
7751 * @param {string} [language='dflt']
7752 * @param {string} feature - 4-character feature name ('ccmp', 'stch')
7753 * @return {Array} substitutions - The list of substitutions.
7754 */
7755Substitution.prototype.getMultiple = function(feature, script, language) {
7756    var substitutions = [];
7757    var lookupTables = this.getLookupTables(script, language, feature, 2);
7758    for (var idx = 0; idx < lookupTables.length; idx++) {
7759        var subtables = lookupTables[idx].subtables;
7760        for (var i = 0; i < subtables.length; i++) {
7761            var subtable = subtables[i];
7762            var glyphs = this.expandCoverage(subtable.coverage);
7763            var j = (void 0);
7764
7765            for (j = 0; j < glyphs.length; j++) {
7766                var glyph = glyphs[j];
7767                var replacements = subtable.sequences[j];
7768                substitutions.push({ sub: glyph, by: replacements });
7769            }
7770        }
7771    }
7772    return substitutions;
7773};
7774
7775/**
7776 * List all alternates (lookup type 3) for a given script, language, and feature.
7777 * @param {string} [script='DFLT']
7778 * @param {string} [language='dflt']
7779 * @param {string} feature - 4-character feature name ('aalt', 'salt'...)
7780 * @return {Array} alternates - The list of alternates
7781 */
7782Substitution.prototype.getAlternates = function(feature, script, language) {
7783    var alternates = [];
7784    var lookupTables = this.getLookupTables(script, language, feature, 3);
7785    for (var idx = 0; idx < lookupTables.length; idx++) {
7786        var subtables = lookupTables[idx].subtables;
7787        for (var i = 0; i < subtables.length; i++) {
7788            var subtable = subtables[i];
7789            var glyphs = this.expandCoverage(subtable.coverage);
7790            var alternateSets = subtable.alternateSets;
7791            for (var j = 0; j < glyphs.length; j++) {
7792                alternates.push({ sub: glyphs[j], by: alternateSets[j] });
7793            }
7794        }
7795    }
7796    return alternates;
7797};
7798
7799/**
7800 * List all ligatures (lookup type 4) for a given script, language, and feature.
7801 * The result is an array of ligature objects like { sub: [ids], by: id }
7802 * @param {string} feature - 4-letter feature name ('liga', 'rlig', 'dlig'...)
7803 * @param {string} [script='DFLT']
7804 * @param {string} [language='dflt']
7805 * @return {Array} ligatures - The list of ligatures.
7806 */
7807Substitution.prototype.getLigatures = function(feature, script, language) {
7808    var ligatures = [];
7809    var lookupTables = this.getLookupTables(script, language, feature, 4);
7810    for (var idx = 0; idx < lookupTables.length; idx++) {
7811        var subtables = lookupTables[idx].subtables;
7812        for (var i = 0; i < subtables.length; i++) {
7813            var subtable = subtables[i];
7814            var glyphs = this.expandCoverage(subtable.coverage);
7815            var ligatureSets = subtable.ligatureSets;
7816            for (var j = 0; j < glyphs.length; j++) {
7817                var startGlyph = glyphs[j];
7818                var ligSet = ligatureSets[j];
7819                for (var k = 0; k < ligSet.length; k++) {
7820                    var lig = ligSet[k];
7821                    ligatures.push({
7822                        sub: [startGlyph].concat(lig.components),
7823                        by: lig.ligGlyph
7824                    });
7825                }
7826            }
7827        }
7828    }
7829    return ligatures;
7830};
7831
7832/**
7833 * Add or modify a single substitution (lookup type 1)
7834 * Format 2, more flexible, is always used.
7835 * @param {string} feature - 4-letter feature name ('liga', 'rlig', 'dlig'...)
7836 * @param {Object} substitution - { sub: id, by: id } (format 1 is not supported)
7837 * @param {string} [script='DFLT']
7838 * @param {string} [language='dflt']
7839 */
7840Substitution.prototype.addSingle = function(feature, substitution, script, language) {
7841    var lookupTable = this.getLookupTables(script, language, feature, 1, true)[0];
7842    var subtable = getSubstFormat(lookupTable, 2, {                // lookup type 1 subtable, format 2, coverage format 1
7843        substFormat: 2,
7844        coverage: {format: 1, glyphs: []},
7845        substitute: []
7846    });
7847    check.assert(subtable.coverage.format === 1, 'Single: unable to modify coverage table format ' + subtable.coverage.format);
7848    var coverageGlyph = substitution.sub;
7849    var pos = this.binSearch(subtable.coverage.glyphs, coverageGlyph);
7850    if (pos < 0) {
7851        pos = -1 - pos;
7852        subtable.coverage.glyphs.splice(pos, 0, coverageGlyph);
7853        subtable.substitute.splice(pos, 0, 0);
7854    }
7855    subtable.substitute[pos] = substitution.by;
7856};
7857
7858/**
7859 * Add or modify a multiple substitution (lookup type 2)
7860 * @param {string} feature - 4-letter feature name ('ccmp', 'stch')
7861 * @param {Object} substitution - { sub: id, by: [id] } for format 2.
7862 * @param {string} [script='DFLT']
7863 * @param {string} [language='dflt']
7864 */
7865Substitution.prototype.addMultiple = function(feature, substitution, script, language) {
7866    check.assert(substitution.by instanceof Array && substitution.by.length > 1, 'Multiple: "by" must be an array of two or more ids');
7867    var lookupTable = this.getLookupTables(script, language, feature, 2, true)[0];
7868    var subtable = getSubstFormat(lookupTable, 1, {                // lookup type 2 subtable, format 1, coverage format 1
7869        substFormat: 1,
7870        coverage: {format: 1, glyphs: []},
7871        sequences: []
7872    });
7873    check.assert(subtable.coverage.format === 1, 'Multiple: unable to modify coverage table format ' + subtable.coverage.format);
7874    var coverageGlyph = substitution.sub;
7875    var pos = this.binSearch(subtable.coverage.glyphs, coverageGlyph);
7876    if (pos < 0) {
7877        pos = -1 - pos;
7878        subtable.coverage.glyphs.splice(pos, 0, coverageGlyph);
7879        subtable.sequences.splice(pos, 0, 0);
7880    }
7881    subtable.sequences[pos] = substitution.by;
7882};
7883
7884/**
7885 * Add or modify an alternate substitution (lookup type 3)
7886 * @param {string} feature - 4-letter feature name ('liga', 'rlig', 'dlig'...)
7887 * @param {Object} substitution - { sub: id, by: [ids] }
7888 * @param {string} [script='DFLT']
7889 * @param {string} [language='dflt']
7890 */
7891Substitution.prototype.addAlternate = function(feature, substitution, script, language) {
7892    var lookupTable = this.getLookupTables(script, language, feature, 3, true)[0];
7893    var subtable = getSubstFormat(lookupTable, 1, {                // lookup type 3 subtable, format 1, coverage format 1
7894        substFormat: 1,
7895        coverage: {format: 1, glyphs: []},
7896        alternateSets: []
7897    });
7898    check.assert(subtable.coverage.format === 1, 'Alternate: unable to modify coverage table format ' + subtable.coverage.format);
7899    var coverageGlyph = substitution.sub;
7900    var pos = this.binSearch(subtable.coverage.glyphs, coverageGlyph);
7901    if (pos < 0) {
7902        pos = -1 - pos;
7903        subtable.coverage.glyphs.splice(pos, 0, coverageGlyph);
7904        subtable.alternateSets.splice(pos, 0, 0);
7905    }
7906    subtable.alternateSets[pos] = substitution.by;
7907};
7908
7909/**
7910 * Add a ligature (lookup type 4)
7911 * Ligatures with more components must be stored ahead of those with fewer components in order to be found
7912 * @param {string} feature - 4-letter feature name ('liga', 'rlig', 'dlig'...)
7913 * @param {Object} ligature - { sub: [ids], by: id }
7914 * @param {string} [script='DFLT']
7915 * @param {string} [language='dflt']
7916 */
7917Substitution.prototype.addLigature = function(feature, ligature, script, language) {
7918    var lookupTable = this.getLookupTables(script, language, feature, 4, true)[0];
7919    var subtable = lookupTable.subtables[0];
7920    if (!subtable) {
7921        subtable = {                // lookup type 4 subtable, format 1, coverage format 1
7922            substFormat: 1,
7923            coverage: { format: 1, glyphs: [] },
7924            ligatureSets: []
7925        };
7926        lookupTable.subtables[0] = subtable;
7927    }
7928    check.assert(subtable.coverage.format === 1, 'Ligature: unable to modify coverage table format ' + subtable.coverage.format);
7929    var coverageGlyph = ligature.sub[0];
7930    var ligComponents = ligature.sub.slice(1);
7931    var ligatureTable = {
7932        ligGlyph: ligature.by,
7933        components: ligComponents
7934    };
7935    var pos = this.binSearch(subtable.coverage.glyphs, coverageGlyph);
7936    if (pos >= 0) {
7937        // ligatureSet already exists
7938        var ligatureSet = subtable.ligatureSets[pos];
7939        for (var i = 0; i < ligatureSet.length; i++) {
7940            // If ligature already exists, return.
7941            if (arraysEqual(ligatureSet[i].components, ligComponents)) {
7942                return;
7943            }
7944        }
7945        // ligature does not exist: add it.
7946        ligatureSet.push(ligatureTable);
7947    } else {
7948        // Create a new ligatureSet and add coverage for the first glyph.
7949        pos = -1 - pos;
7950        subtable.coverage.glyphs.splice(pos, 0, coverageGlyph);
7951        subtable.ligatureSets.splice(pos, 0, [ligatureTable]);
7952    }
7953};
7954
7955/**
7956 * List all feature data for a given script and language.
7957 * @param {string} feature - 4-letter feature name
7958 * @param {string} [script='DFLT']
7959 * @param {string} [language='dflt']
7960 * @return {Array} substitutions - The list of substitutions.
7961 */
7962Substitution.prototype.getFeature = function(feature, script, language) {
7963    if (/ss\d\d/.test(feature)) {
7964        // ss01 - ss20
7965        return this.getSingle(feature, script, language);
7966    }
7967    switch (feature) {
7968        case 'aalt':
7969        case 'salt':
7970            return this.getSingle(feature, script, language)
7971                    .concat(this.getAlternates(feature, script, language));
7972        case 'dlig':
7973        case 'liga':
7974        case 'rlig':
7975            return this.getLigatures(feature, script, language);
7976        case 'ccmp':
7977            return this.getMultiple(feature, script, language)
7978                .concat(this.getLigatures(feature, script, language));
7979        case 'stch':
7980            return this.getMultiple(feature, script, language);
7981    }
7982    return undefined;
7983};
7984
7985/**
7986 * Add a substitution to a feature for a given script and language.
7987 * @param {string} feature - 4-letter feature name
7988 * @param {Object} sub - the substitution to add (an object like { sub: id or [ids], by: id or [ids] })
7989 * @param {string} [script='DFLT']
7990 * @param {string} [language='dflt']
7991 */
7992Substitution.prototype.add = function(feature, sub, script, language) {
7993    if (/ss\d\d/.test(feature)) {
7994        // ss01 - ss20
7995        return this.addSingle(feature, sub, script, language);
7996    }
7997    switch (feature) {
7998        case 'aalt':
7999        case 'salt':
8000            if (typeof sub.by === 'number') {
8001                return this.addSingle(feature, sub, script, language);
8002            }
8003            return this.addAlternate(feature, sub, script, language);
8004        case 'dlig':
8005        case 'liga':
8006        case 'rlig':
8007            return this.addLigature(feature, sub, script, language);
8008        case 'ccmp':
8009            if (sub.by instanceof Array) {
8010                return this.addMultiple(feature, sub, script, language);
8011            }
8012            return this.addLigature(feature, sub, script, language);
8013    }
8014    return undefined;
8015};
8016
8017function isBrowser() {
8018    return typeof window !== 'undefined';
8019}
8020
8021function nodeBufferToArrayBuffer(buffer) {
8022    var ab = new ArrayBuffer(buffer.length);
8023    var view = new Uint8Array(ab);
8024    for (var i = 0; i < buffer.length; ++i) {
8025        view[i] = buffer[i];
8026    }
8027
8028    return ab;
8029}
8030
8031function arrayBufferToNodeBuffer(ab) {
8032    var buffer = new Buffer(ab.byteLength);
8033    var view = new Uint8Array(ab);
8034    for (var i = 0; i < buffer.length; ++i) {
8035        buffer[i] = view[i];
8036    }
8037
8038    return buffer;
8039}
8040
8041function checkArgument(expression, message) {
8042    if (!expression) {
8043        throw message;
8044    }
8045}
8046
8047// The `glyf` table describes the glyphs in TrueType outline format.
8048
8049// Parse the coordinate data for a glyph.
8050function parseGlyphCoordinate(p, flag, previousValue, shortVectorBitMask, sameBitMask) {
8051    var v;
8052    if ((flag & shortVectorBitMask) > 0) {
8053        // The coordinate is 1 byte long.
8054        v = p.parseByte();
8055        // The `same` bit is re-used for short values to signify the sign of the value.
8056        if ((flag & sameBitMask) === 0) {
8057            v = -v;
8058        }
8059
8060        v = previousValue + v;
8061    } else {
8062        //  The coordinate is 2 bytes long.
8063        // If the `same` bit is set, the coordinate is the same as the previous coordinate.
8064        if ((flag & sameBitMask) > 0) {
8065            v = previousValue;
8066        } else {
8067            // Parse the coordinate as a signed 16-bit delta value.
8068            v = previousValue + p.parseShort();
8069        }
8070    }
8071
8072    return v;
8073}
8074
8075// Parse a TrueType glyph.
8076function parseGlyph(glyph, data, start) {
8077    var p = new parse.Parser(data, start);
8078    glyph.numberOfContours = p.parseShort();
8079    glyph._xMin = p.parseShort();
8080    glyph._yMin = p.parseShort();
8081    glyph._xMax = p.parseShort();
8082    glyph._yMax = p.parseShort();
8083    var flags;
8084    var flag;
8085
8086    if (glyph.numberOfContours > 0) {
8087        // This glyph is not a composite.
8088        var endPointIndices = glyph.endPointIndices = [];
8089        for (var i = 0; i < glyph.numberOfContours; i += 1) {
8090            endPointIndices.push(p.parseUShort());
8091        }
8092
8093        glyph.instructionLength = p.parseUShort();
8094        glyph.instructions = [];
8095        for (var i$1 = 0; i$1 < glyph.instructionLength; i$1 += 1) {
8096            glyph.instructions.push(p.parseByte());
8097        }
8098
8099        var numberOfCoordinates = endPointIndices[endPointIndices.length - 1] + 1;
8100        flags = [];
8101        for (var i$2 = 0; i$2 < numberOfCoordinates; i$2 += 1) {
8102            flag = p.parseByte();
8103            flags.push(flag);
8104            // If bit 3 is set, we repeat this flag n times, where n is the next byte.
8105            if ((flag & 8) > 0) {
8106                var repeatCount = p.parseByte();
8107                for (var j = 0; j < repeatCount; j += 1) {
8108                    flags.push(flag);
8109                    i$2 += 1;
8110                }
8111            }
8112        }
8113
8114        check.argument(flags.length === numberOfCoordinates, 'Bad flags.');
8115
8116        if (endPointIndices.length > 0) {
8117            var points = [];
8118            var point;
8119            // X/Y coordinates are relative to the previous point, except for the first point which is relative to 0,0.
8120            if (numberOfCoordinates > 0) {
8121                for (var i$3 = 0; i$3 < numberOfCoordinates; i$3 += 1) {
8122                    flag = flags[i$3];
8123                    point = {};
8124                    point.onCurve = !!(flag & 1);
8125                    point.lastPointOfContour = endPointIndices.indexOf(i$3) >= 0;
8126                    points.push(point);
8127                }
8128
8129                var px = 0;
8130                for (var i$4 = 0; i$4 < numberOfCoordinates; i$4 += 1) {
8131                    flag = flags[i$4];
8132                    point = points[i$4];
8133                    point.x = parseGlyphCoordinate(p, flag, px, 2, 16);
8134                    px = point.x;
8135                }
8136
8137                var py = 0;
8138                for (var i$5 = 0; i$5 < numberOfCoordinates; i$5 += 1) {
8139                    flag = flags[i$5];
8140                    point = points[i$5];
8141                    point.y = parseGlyphCoordinate(p, flag, py, 4, 32);
8142                    py = point.y;
8143                }
8144            }
8145
8146            glyph.points = points;
8147        } else {
8148            glyph.points = [];
8149        }
8150    } else if (glyph.numberOfContours === 0) {
8151        glyph.points = [];
8152    } else {
8153        glyph.isComposite = true;
8154        glyph.points = [];
8155        glyph.components = [];
8156        var moreComponents = true;
8157        while (moreComponents) {
8158            flags = p.parseUShort();
8159            var component = {
8160                glyphIndex: p.parseUShort(),
8161                xScale: 1,
8162                scale01: 0,
8163                scale10: 0,
8164                yScale: 1,
8165                dx: 0,
8166                dy: 0
8167            };
8168            if ((flags & 1) > 0) {
8169                // The arguments are words
8170                if ((flags & 2) > 0) {
8171                    // values are offset
8172                    component.dx = p.parseShort();
8173                    component.dy = p.parseShort();
8174                } else {
8175                    // values are matched points
8176                    component.matchedPoints = [p.parseUShort(), p.parseUShort()];
8177                }
8178
8179            } else {
8180                // The arguments are bytes
8181                if ((flags & 2) > 0) {
8182                    // values are offset
8183                    component.dx = p.parseChar();
8184                    component.dy = p.parseChar();
8185                } else {
8186                    // values are matched points
8187                    component.matchedPoints = [p.parseByte(), p.parseByte()];
8188                }
8189            }
8190
8191            if ((flags & 8) > 0) {
8192                // We have a scale
8193                component.xScale = component.yScale = p.parseF2Dot14();
8194            } else if ((flags & 64) > 0) {
8195                // We have an X / Y scale
8196                component.xScale = p.parseF2Dot14();
8197                component.yScale = p.parseF2Dot14();
8198            } else if ((flags & 128) > 0) {
8199                // We have a 2x2 transformation
8200                component.xScale = p.parseF2Dot14();
8201                component.scale01 = p.parseF2Dot14();
8202                component.scale10 = p.parseF2Dot14();
8203                component.yScale = p.parseF2Dot14();
8204            }
8205
8206            glyph.components.push(component);
8207            moreComponents = !!(flags & 32);
8208        }
8209        if (flags & 0x100) {
8210            // We have instructions
8211            glyph.instructionLength = p.parseUShort();
8212            glyph.instructions = [];
8213            for (var i$6 = 0; i$6 < glyph.instructionLength; i$6 += 1) {
8214                glyph.instructions.push(p.parseByte());
8215            }
8216        }
8217    }
8218}
8219
8220// Transform an array of points and return a new array.
8221function transformPoints(points, transform) {
8222    var newPoints = [];
8223    for (var i = 0; i < points.length; i += 1) {
8224        var pt = points[i];
8225        var newPt = {
8226            x: transform.xScale * pt.x + transform.scale01 * pt.y + transform.dx,
8227            y: transform.scale10 * pt.x + transform.yScale * pt.y + transform.dy,
8228            onCurve: pt.onCurve,
8229            lastPointOfContour: pt.lastPointOfContour
8230        };
8231        newPoints.push(newPt);
8232    }
8233
8234    return newPoints;
8235}
8236
8237function getContours(points) {
8238    var contours = [];
8239    var currentContour = [];
8240    for (var i = 0; i < points.length; i += 1) {
8241        var pt = points[i];
8242        currentContour.push(pt);
8243        if (pt.lastPointOfContour) {
8244            contours.push(currentContour);
8245            currentContour = [];
8246        }
8247    }
8248
8249    check.argument(currentContour.length === 0, 'There are still points left in the current contour.');
8250    return contours;
8251}
8252
8253// Convert the TrueType glyph outline to a Path.
8254function getPath(points) {
8255    var p = new Path();
8256    if (!points) {
8257        return p;
8258    }
8259
8260    var contours = getContours(points);
8261
8262    for (var contourIndex = 0; contourIndex < contours.length; ++contourIndex) {
8263        var contour = contours[contourIndex];
8264
8265        var prev = null;
8266        var curr = contour[contour.length - 1];
8267        var next = contour[0];
8268
8269        if (curr.onCurve) {
8270            p.moveTo(curr.x, curr.y);
8271        } else {
8272            if (next.onCurve) {
8273                p.moveTo(next.x, next.y);
8274            } else {
8275                // If both first and last points are off-curve, start at their middle.
8276                var start = {x: (curr.x + next.x) * 0.5, y: (curr.y + next.y) * 0.5};
8277                p.moveTo(start.x, start.y);
8278            }
8279        }
8280
8281        for (var i = 0; i < contour.length; ++i) {
8282            prev = curr;
8283            curr = next;
8284            next = contour[(i + 1) % contour.length];
8285
8286            if (curr.onCurve) {
8287                // This is a straight line.
8288                p.lineTo(curr.x, curr.y);
8289            } else {
8290                var prev2 = prev;
8291                var next2 = next;
8292
8293                if (!prev.onCurve) {
8294                    prev2 = { x: (curr.x + prev.x) * 0.5, y: (curr.y + prev.y) * 0.5 };
8295                }
8296
8297                if (!next.onCurve) {
8298                    next2 = { x: (curr.x + next.x) * 0.5, y: (curr.y + next.y) * 0.5 };
8299                }
8300
8301                p.quadraticCurveTo(curr.x, curr.y, next2.x, next2.y);
8302            }
8303        }
8304
8305        p.closePath();
8306    }
8307    return p;
8308}
8309
8310function buildPath(glyphs, glyph) {
8311    if (glyph.isComposite) {
8312        for (var j = 0; j < glyph.components.length; j += 1) {
8313            var component = glyph.components[j];
8314            var componentGlyph = glyphs.get(component.glyphIndex);
8315            // Force the ttfGlyphLoader to parse the glyph.
8316            componentGlyph.getPath();
8317            if (componentGlyph.points) {
8318                var transformedPoints = (void 0);
8319                if (component.matchedPoints === undefined) {
8320                    // component positioned by offset
8321                    transformedPoints = transformPoints(componentGlyph.points, component);
8322                } else {
8323                    // component positioned by matched points
8324                    if ((component.matchedPoints[0] > glyph.points.length - 1) ||
8325                        (component.matchedPoints[1] > componentGlyph.points.length - 1)) {
8326                        throw Error('Matched points out of range in ' + glyph.name);
8327                    }
8328                    var firstPt = glyph.points[component.matchedPoints[0]];
8329                    var secondPt = componentGlyph.points[component.matchedPoints[1]];
8330                    var transform = {
8331                        xScale: component.xScale, scale01: component.scale01,
8332                        scale10: component.scale10, yScale: component.yScale,
8333                        dx: 0, dy: 0
8334                    };
8335                    secondPt = transformPoints([secondPt], transform)[0];
8336                    transform.dx = firstPt.x - secondPt.x;
8337                    transform.dy = firstPt.y - secondPt.y;
8338                    transformedPoints = transformPoints(componentGlyph.points, transform);
8339                }
8340                glyph.points = glyph.points.concat(transformedPoints);
8341            }
8342        }
8343    }
8344
8345    return getPath(glyph.points);
8346}
8347
8348function parseGlyfTableAll(data, start, loca, font) {
8349    var glyphs = new glyphset.GlyphSet(font);
8350
8351    // The last element of the loca table is invalid.
8352    for (var i = 0; i < loca.length - 1; i += 1) {
8353        var offset = loca[i];
8354        var nextOffset = loca[i + 1];
8355        if (offset !== nextOffset) {
8356            glyphs.push(i, glyphset.ttfGlyphLoader(font, i, parseGlyph, data, start + offset, buildPath));
8357        } else {
8358            glyphs.push(i, glyphset.glyphLoader(font, i));
8359        }
8360    }
8361
8362    return glyphs;
8363}
8364
8365function parseGlyfTableOnLowMemory(data, start, loca, font) {
8366    var glyphs = new glyphset.GlyphSet(font);
8367
8368    font._push = function(i) {
8369        var offset = loca[i];
8370        var nextOffset = loca[i + 1];
8371        if (offset !== nextOffset) {
8372            glyphs.push(i, glyphset.ttfGlyphLoader(font, i, parseGlyph, data, start + offset, buildPath));
8373        } else {
8374            glyphs.push(i, glyphset.glyphLoader(font, i));
8375        }
8376    };
8377
8378    return glyphs;
8379}
8380
8381// Parse all the glyphs according to the offsets from the `loca` table.
8382function parseGlyfTable(data, start, loca, font, opt) {
8383    if (opt.lowMemory)
8384        { return parseGlyfTableOnLowMemory(data, start, loca, font); }
8385    else
8386        { return parseGlyfTableAll(data, start, loca, font); }
8387}
8388
8389var glyf = { getPath: getPath, parse: parseGlyfTable};
8390
8391/* A TrueType font hinting interpreter.
8392*
8393* (c) 2017 Axel Kittenberger
8394*
8395* This interpreter has been implemented according to this documentation:
8396* https://developer.apple.com/fonts/TrueType-Reference-Manual/RM05/Chap5.html
8397*
8398* According to the documentation F24DOT6 values are used for pixels.
8399* That means calculation is 1/64 pixel accurate and uses integer operations.
8400* However, Javascript has floating point operations by default and only
8401* those are available. One could make a case to simulate the 1/64 accuracy
8402* exactly by truncating after every division operation
8403* (for example with << 0) to get pixel exactly results as other TrueType
8404* implementations. It may make sense since some fonts are pixel optimized
8405* by hand using DELTAP instructions. The current implementation doesn't
8406* and rather uses full floating point precision.
8407*
8408* xScale, yScale and rotation is currently ignored.
8409*
8410* A few non-trivial instructions are missing as I didn't encounter yet
8411* a font that used them to test a possible implementation.
8412*
8413* Some fonts seem to use undocumented features regarding the twilight zone.
8414* Only some of them are implemented as they were encountered.
8415*
8416* The exports.DEBUG statements are removed on the minified distribution file.
8417*/
8418
8419var instructionTable;
8420var exec;
8421var execGlyph;
8422var execComponent;
8423
8424/*
8425* Creates a hinting object.
8426*
8427* There ought to be exactly one
8428* for each truetype font that is used for hinting.
8429*/
8430function Hinting(font) {
8431    // the font this hinting object is for
8432    this.font = font;
8433
8434    this.getCommands = function (hPoints) {
8435        return glyf.getPath(hPoints).commands;
8436    };
8437
8438    // cached states
8439    this._fpgmState  =
8440    this._prepState  =
8441        undefined;
8442
8443    // errorState
8444    // 0 ... all okay
8445    // 1 ... had an error in a glyf,
8446    //       continue working but stop spamming
8447    //       the console
8448    // 2 ... error at prep, stop hinting at this ppem
8449    // 3 ... error at fpeg, stop hinting for this font at all
8450    this._errorState = 0;
8451}
8452
8453/*
8454* Not rounding.
8455*/
8456function roundOff(v) {
8457    return v;
8458}
8459
8460/*
8461* Rounding to grid.
8462*/
8463function roundToGrid(v) {
8464    //Rounding in TT is supposed to "symmetrical around zero"
8465    return Math.sign(v) * Math.round(Math.abs(v));
8466}
8467
8468/*
8469* Rounding to double grid.
8470*/
8471function roundToDoubleGrid(v) {
8472    return Math.sign(v) * Math.round(Math.abs(v * 2)) / 2;
8473}
8474
8475/*
8476* Rounding to half grid.
8477*/
8478function roundToHalfGrid(v) {
8479    return Math.sign(v) * (Math.round(Math.abs(v) + 0.5) - 0.5);
8480}
8481
8482/*
8483* Rounding to up to grid.
8484*/
8485function roundUpToGrid(v) {
8486    return Math.sign(v) * Math.ceil(Math.abs(v));
8487}
8488
8489/*
8490* Rounding to down to grid.
8491*/
8492function roundDownToGrid(v) {
8493    return Math.sign(v) * Math.floor(Math.abs(v));
8494}
8495
8496/*
8497* Super rounding.
8498*/
8499var roundSuper = function (v) {
8500    var period = this.srPeriod;
8501    var phase = this.srPhase;
8502    var threshold = this.srThreshold;
8503    var sign = 1;
8504
8505    if (v < 0) {
8506        v = -v;
8507        sign = -1;
8508    }
8509
8510    v += threshold - phase;
8511
8512    v = Math.trunc(v / period) * period;
8513
8514    v += phase;
8515
8516    // according to http://xgridfit.sourceforge.net/round.html
8517    if (v < 0) { return phase * sign; }
8518
8519    return v * sign;
8520};
8521
8522/*
8523* Unit vector of x-axis.
8524*/
8525var xUnitVector = {
8526    x: 1,
8527
8528    y: 0,
8529
8530    axis: 'x',
8531
8532    // Gets the projected distance between two points.
8533    // o1/o2 ... if true, respective original position is used.
8534    distance: function (p1, p2, o1, o2) {
8535        return (o1 ? p1.xo : p1.x) - (o2 ? p2.xo : p2.x);
8536    },
8537
8538    // Moves point p so the moved position has the same relative
8539    // position to the moved positions of rp1 and rp2 than the
8540    // original positions had.
8541    //
8542    // See APPENDIX on INTERPOLATE at the bottom of this file.
8543    interpolate: function (p, rp1, rp2, pv) {
8544        var do1;
8545        var do2;
8546        var doa1;
8547        var doa2;
8548        var dm1;
8549        var dm2;
8550        var dt;
8551
8552        if (!pv || pv === this) {
8553            do1 = p.xo - rp1.xo;
8554            do2 = p.xo - rp2.xo;
8555            dm1 = rp1.x - rp1.xo;
8556            dm2 = rp2.x - rp2.xo;
8557            doa1 = Math.abs(do1);
8558            doa2 = Math.abs(do2);
8559            dt = doa1 + doa2;
8560
8561            if (dt === 0) {
8562                p.x = p.xo + (dm1 + dm2) / 2;
8563                return;
8564            }
8565
8566            p.x = p.xo + (dm1 * doa2 + dm2 * doa1) / dt;
8567            return;
8568        }
8569
8570        do1 = pv.distance(p, rp1, true, true);
8571        do2 = pv.distance(p, rp2, true, true);
8572        dm1 = pv.distance(rp1, rp1, false, true);
8573        dm2 = pv.distance(rp2, rp2, false, true);
8574        doa1 = Math.abs(do1);
8575        doa2 = Math.abs(do2);
8576        dt = doa1 + doa2;
8577
8578        if (dt === 0) {
8579            xUnitVector.setRelative(p, p, (dm1 + dm2) / 2, pv, true);
8580            return;
8581        }
8582
8583        xUnitVector.setRelative(p, p, (dm1 * doa2 + dm2 * doa1) / dt, pv, true);
8584    },
8585
8586    // Slope of line normal to this
8587    normalSlope: Number.NEGATIVE_INFINITY,
8588
8589    // Sets the point 'p' relative to point 'rp'
8590    // by the distance 'd'.
8591    //
8592    // See APPENDIX on SETRELATIVE at the bottom of this file.
8593    //
8594    // p   ... point to set
8595    // rp  ... reference point
8596    // d   ... distance on projection vector
8597    // pv  ... projection vector (undefined = this)
8598    // org ... if true, uses the original position of rp as reference.
8599    setRelative: function (p, rp, d, pv, org) {
8600        if (!pv || pv === this) {
8601            p.x = (org ? rp.xo : rp.x) + d;
8602            return;
8603        }
8604
8605        var rpx = org ? rp.xo : rp.x;
8606        var rpy = org ? rp.yo : rp.y;
8607        var rpdx = rpx + d * pv.x;
8608        var rpdy = rpy + d * pv.y;
8609
8610        p.x = rpdx + (p.y - rpdy) / pv.normalSlope;
8611    },
8612
8613    // Slope of vector line.
8614    slope: 0,
8615
8616    // Touches the point p.
8617    touch: function (p) {
8618        p.xTouched = true;
8619    },
8620
8621    // Tests if a point p is touched.
8622    touched: function (p) {
8623        return p.xTouched;
8624    },
8625
8626    // Untouches the point p.
8627    untouch: function (p) {
8628        p.xTouched = false;
8629    }
8630};
8631
8632/*
8633* Unit vector of y-axis.
8634*/
8635var yUnitVector = {
8636    x: 0,
8637
8638    y: 1,
8639
8640    axis: 'y',
8641
8642    // Gets the projected distance between two points.
8643    // o1/o2 ... if true, respective original position is used.
8644    distance: function (p1, p2, o1, o2) {
8645        return (o1 ? p1.yo : p1.y) - (o2 ? p2.yo : p2.y);
8646    },
8647
8648    // Moves point p so the moved position has the same relative
8649    // position to the moved positions of rp1 and rp2 than the
8650    // original positions had.
8651    //
8652    // See APPENDIX on INTERPOLATE at the bottom of this file.
8653    interpolate: function (p, rp1, rp2, pv) {
8654        var do1;
8655        var do2;
8656        var doa1;
8657        var doa2;
8658        var dm1;
8659        var dm2;
8660        var dt;
8661
8662        if (!pv || pv === this) {
8663            do1 = p.yo - rp1.yo;
8664            do2 = p.yo - rp2.yo;
8665            dm1 = rp1.y - rp1.yo;
8666            dm2 = rp2.y - rp2.yo;
8667            doa1 = Math.abs(do1);
8668            doa2 = Math.abs(do2);
8669            dt = doa1 + doa2;
8670
8671            if (dt === 0) {
8672                p.y = p.yo + (dm1 + dm2) / 2;
8673                return;
8674            }
8675
8676            p.y = p.yo + (dm1 * doa2 + dm2 * doa1) / dt;
8677            return;
8678        }
8679
8680        do1 = pv.distance(p, rp1, true, true);
8681        do2 = pv.distance(p, rp2, true, true);
8682        dm1 = pv.distance(rp1, rp1, false, true);
8683        dm2 = pv.distance(rp2, rp2, false, true);
8684        doa1 = Math.abs(do1);
8685        doa2 = Math.abs(do2);
8686        dt = doa1 + doa2;
8687
8688        if (dt === 0) {
8689            yUnitVector.setRelative(p, p, (dm1 + dm2) / 2, pv, true);
8690            return;
8691        }
8692
8693        yUnitVector.setRelative(p, p, (dm1 * doa2 + dm2 * doa1) / dt, pv, true);
8694    },
8695
8696    // Slope of line normal to this.
8697    normalSlope: 0,
8698
8699    // Sets the point 'p' relative to point 'rp'
8700    // by the distance 'd'
8701    //
8702    // See APPENDIX on SETRELATIVE at the bottom of this file.
8703    //
8704    // p   ... point to set
8705    // rp  ... reference point
8706    // d   ... distance on projection vector
8707    // pv  ... projection vector (undefined = this)
8708    // org ... if true, uses the original position of rp as reference.
8709    setRelative: function (p, rp, d, pv, org) {
8710        if (!pv || pv === this) {
8711            p.y = (org ? rp.yo : rp.y) + d;
8712            return;
8713        }
8714
8715        var rpx = org ? rp.xo : rp.x;
8716        var rpy = org ? rp.yo : rp.y;
8717        var rpdx = rpx + d * pv.x;
8718        var rpdy = rpy + d * pv.y;
8719
8720        p.y = rpdy + pv.normalSlope * (p.x - rpdx);
8721    },
8722
8723    // Slope of vector line.
8724    slope: Number.POSITIVE_INFINITY,
8725
8726    // Touches the point p.
8727    touch: function (p) {
8728        p.yTouched = true;
8729    },
8730
8731    // Tests if a point p is touched.
8732    touched: function (p) {
8733        return p.yTouched;
8734    },
8735
8736    // Untouches the point p.
8737    untouch: function (p) {
8738        p.yTouched = false;
8739    }
8740};
8741
8742Object.freeze(xUnitVector);
8743Object.freeze(yUnitVector);
8744
8745/*
8746* Creates a unit vector that is not x- or y-axis.
8747*/
8748function UnitVector(x, y) {
8749    this.x = x;
8750    this.y = y;
8751    this.axis = undefined;
8752    this.slope = y / x;
8753    this.normalSlope = -x / y;
8754    Object.freeze(this);
8755}
8756
8757/*
8758* Gets the projected distance between two points.
8759* o1/o2 ... if true, respective original position is used.
8760*/
8761UnitVector.prototype.distance = function(p1, p2, o1, o2) {
8762    return (
8763        this.x * xUnitVector.distance(p1, p2, o1, o2) +
8764        this.y * yUnitVector.distance(p1, p2, o1, o2)
8765    );
8766};
8767
8768/*
8769* Moves point p so the moved position has the same relative
8770* position to the moved positions of rp1 and rp2 than the
8771* original positions had.
8772*
8773* See APPENDIX on INTERPOLATE at the bottom of this file.
8774*/
8775UnitVector.prototype.interpolate = function(p, rp1, rp2, pv) {
8776    var dm1;
8777    var dm2;
8778    var do1;
8779    var do2;
8780    var doa1;
8781    var doa2;
8782    var dt;
8783
8784    do1 = pv.distance(p, rp1, true, true);
8785    do2 = pv.distance(p, rp2, true, true);
8786    dm1 = pv.distance(rp1, rp1, false, true);
8787    dm2 = pv.distance(rp2, rp2, false, true);
8788    doa1 = Math.abs(do1);
8789    doa2 = Math.abs(do2);
8790    dt = doa1 + doa2;
8791
8792    if (dt === 0) {
8793        this.setRelative(p, p, (dm1 + dm2) / 2, pv, true);
8794        return;
8795    }
8796
8797    this.setRelative(p, p, (dm1 * doa2 + dm2 * doa1) / dt, pv, true);
8798};
8799
8800/*
8801* Sets the point 'p' relative to point 'rp'
8802* by the distance 'd'
8803*
8804* See APPENDIX on SETRELATIVE at the bottom of this file.
8805*
8806* p   ...  point to set
8807* rp  ... reference point
8808* d   ... distance on projection vector
8809* pv  ... projection vector (undefined = this)
8810* org ... if true, uses the original position of rp as reference.
8811*/
8812UnitVector.prototype.setRelative = function(p, rp, d, pv, org) {
8813    pv = pv || this;
8814
8815    var rpx = org ? rp.xo : rp.x;
8816    var rpy = org ? rp.yo : rp.y;
8817    var rpdx = rpx + d * pv.x;
8818    var rpdy = rpy + d * pv.y;
8819
8820    var pvns = pv.normalSlope;
8821    var fvs = this.slope;
8822
8823    var px = p.x;
8824    var py = p.y;
8825
8826    p.x = (fvs * px - pvns * rpdx + rpdy - py) / (fvs - pvns);
8827    p.y = fvs * (p.x - px) + py;
8828};
8829
8830/*
8831* Touches the point p.
8832*/
8833UnitVector.prototype.touch = function(p) {
8834    p.xTouched = true;
8835    p.yTouched = true;
8836};
8837
8838/*
8839* Returns a unit vector with x/y coordinates.
8840*/
8841function getUnitVector(x, y) {
8842    var d = Math.sqrt(x * x + y * y);
8843
8844    x /= d;
8845    y /= d;
8846
8847    if (x === 1 && y === 0) { return xUnitVector; }
8848    else if (x === 0 && y === 1) { return yUnitVector; }
8849    else { return new UnitVector(x, y); }
8850}
8851
8852/*
8853* Creates a point in the hinting engine.
8854*/
8855function HPoint(
8856    x,
8857    y,
8858    lastPointOfContour,
8859    onCurve
8860) {
8861    this.x = this.xo = Math.round(x * 64) / 64; // hinted x value and original x-value
8862    this.y = this.yo = Math.round(y * 64) / 64; // hinted y value and original y-value
8863
8864    this.lastPointOfContour = lastPointOfContour;
8865    this.onCurve = onCurve;
8866    this.prevPointOnContour = undefined;
8867    this.nextPointOnContour = undefined;
8868    this.xTouched = false;
8869    this.yTouched = false;
8870
8871    Object.preventExtensions(this);
8872}
8873
8874/*
8875* Returns the next touched point on the contour.
8876*
8877* v  ... unit vector to test touch axis.
8878*/
8879HPoint.prototype.nextTouched = function(v) {
8880    var p = this.nextPointOnContour;
8881
8882    while (!v.touched(p) && p !== this) { p = p.nextPointOnContour; }
8883
8884    return p;
8885};
8886
8887/*
8888* Returns the previous touched point on the contour
8889*
8890* v  ... unit vector to test touch axis.
8891*/
8892HPoint.prototype.prevTouched = function(v) {
8893    var p = this.prevPointOnContour;
8894
8895    while (!v.touched(p) && p !== this) { p = p.prevPointOnContour; }
8896
8897    return p;
8898};
8899
8900/*
8901* The zero point.
8902*/
8903var HPZero = Object.freeze(new HPoint(0, 0));
8904
8905/*
8906* The default state of the interpreter.
8907*
8908* Note: Freezing the defaultState and then deriving from it
8909* makes the V8 Javascript engine going awkward,
8910* so this is avoided, albeit the defaultState shouldn't
8911* ever change.
8912*/
8913var defaultState = {
8914    cvCutIn: 17 / 16,    // control value cut in
8915    deltaBase: 9,
8916    deltaShift: 0.125,
8917    loop: 1,             // loops some instructions
8918    minDis: 1,           // minimum distance
8919    autoFlip: true
8920};
8921
8922/*
8923* The current state of the interpreter.
8924*
8925* env  ... 'fpgm' or 'prep' or 'glyf'
8926* prog ... the program
8927*/
8928function State(env, prog) {
8929    this.env = env;
8930    this.stack = [];
8931    this.prog = prog;
8932
8933    switch (env) {
8934        case 'glyf' :
8935            this.zp0 = this.zp1 = this.zp2 = 1;
8936            this.rp0 = this.rp1 = this.rp2 = 0;
8937            /* fall through */
8938        case 'prep' :
8939            this.fv = this.pv = this.dpv = xUnitVector;
8940            this.round = roundToGrid;
8941    }
8942}
8943
8944/*
8945* Executes a glyph program.
8946*
8947* This does the hinting for each glyph.
8948*
8949* Returns an array of moved points.
8950*
8951* glyph: the glyph to hint
8952* ppem: the size the glyph is rendered for
8953*/
8954Hinting.prototype.exec = function(glyph, ppem) {
8955    if (typeof ppem !== 'number') {
8956        throw new Error('Point size is not a number!');
8957    }
8958
8959    // Received a fatal error, don't do any hinting anymore.
8960    if (this._errorState > 2) { return; }
8961
8962    var font = this.font;
8963    var prepState = this._prepState;
8964
8965    if (!prepState || prepState.ppem !== ppem) {
8966        var fpgmState = this._fpgmState;
8967
8968        if (!fpgmState) {
8969            // Executes the fpgm state.
8970            // This is used by fonts to define functions.
8971            State.prototype = defaultState;
8972
8973            fpgmState =
8974            this._fpgmState =
8975                new State('fpgm', font.tables.fpgm);
8976
8977            fpgmState.funcs = [ ];
8978            fpgmState.font = font;
8979
8980            if (exports.DEBUG) {
8981                console.log('---EXEC FPGM---');
8982                fpgmState.step = -1;
8983            }
8984
8985            try {
8986                exec(fpgmState);
8987            } catch (e) {
8988                console.log('Hinting error in FPGM:' + e);
8989                this._errorState = 3;
8990                return;
8991            }
8992        }
8993
8994        // Executes the prep program for this ppem setting.
8995        // This is used by fonts to set cvt values
8996        // depending on to be rendered font size.
8997
8998        State.prototype = fpgmState;
8999        prepState =
9000        this._prepState =
9001            new State('prep', font.tables.prep);
9002
9003        prepState.ppem = ppem;
9004
9005        // Creates a copy of the cvt table
9006        // and scales it to the current ppem setting.
9007        var oCvt = font.tables.cvt;
9008        if (oCvt) {
9009            var cvt = prepState.cvt = new Array(oCvt.length);
9010            var scale = ppem / font.unitsPerEm;
9011            for (var c = 0; c < oCvt.length; c++) {
9012                cvt[c] = oCvt[c] * scale;
9013            }
9014        } else {
9015            prepState.cvt = [];
9016        }
9017
9018        if (exports.DEBUG) {
9019            console.log('---EXEC PREP---');
9020            prepState.step = -1;
9021        }
9022
9023        try {
9024            exec(prepState);
9025        } catch (e) {
9026            if (this._errorState < 2) {
9027                console.log('Hinting error in PREP:' + e);
9028            }
9029            this._errorState = 2;
9030        }
9031    }
9032
9033    if (this._errorState > 1) { return; }
9034
9035    try {
9036        return execGlyph(glyph, prepState);
9037    } catch (e) {
9038        if (this._errorState < 1) {
9039            console.log('Hinting error:' + e);
9040            console.log('Note: further hinting errors are silenced');
9041        }
9042        this._errorState = 1;
9043        return undefined;
9044    }
9045};
9046
9047/*
9048* Executes the hinting program for a glyph.
9049*/
9050execGlyph = function(glyph, prepState) {
9051    // original point positions
9052    var xScale = prepState.ppem / prepState.font.unitsPerEm;
9053    var yScale = xScale;
9054    var components = glyph.components;
9055    var contours;
9056    var gZone;
9057    var state;
9058
9059    State.prototype = prepState;
9060    if (!components) {
9061        state = new State('glyf', glyph.instructions);
9062        if (exports.DEBUG) {
9063            console.log('---EXEC GLYPH---');
9064            state.step = -1;
9065        }
9066        execComponent(glyph, state, xScale, yScale);
9067        gZone = state.gZone;
9068    } else {
9069        var font = prepState.font;
9070        gZone = [];
9071        contours = [];
9072        for (var i = 0; i < components.length; i++) {
9073            var c = components[i];
9074            var cg = font.glyphs.get(c.glyphIndex);
9075
9076            state = new State('glyf', cg.instructions);
9077
9078            if (exports.DEBUG) {
9079                console.log('---EXEC COMP ' + i + '---');
9080                state.step = -1;
9081            }
9082
9083            execComponent(cg, state, xScale, yScale);
9084            // appends the computed points to the result array
9085            // post processes the component points
9086            var dx = Math.round(c.dx * xScale);
9087            var dy = Math.round(c.dy * yScale);
9088            var gz = state.gZone;
9089            var cc = state.contours;
9090            for (var pi = 0; pi < gz.length; pi++) {
9091                var p = gz[pi];
9092                p.xTouched = p.yTouched = false;
9093                p.xo = p.x = p.x + dx;
9094                p.yo = p.y = p.y + dy;
9095            }
9096
9097            var gLen = gZone.length;
9098            gZone.push.apply(gZone, gz);
9099            for (var j = 0; j < cc.length; j++) {
9100                contours.push(cc[j] + gLen);
9101            }
9102        }
9103
9104        if (glyph.instructions && !state.inhibitGridFit) {
9105            // the composite has instructions on its own
9106            state = new State('glyf', glyph.instructions);
9107
9108            state.gZone = state.z0 = state.z1 = state.z2 = gZone;
9109
9110            state.contours = contours;
9111
9112            // note: HPZero cannot be used here, since
9113            //       the point might be modified
9114            gZone.push(
9115                new HPoint(0, 0),
9116                new HPoint(Math.round(glyph.advanceWidth * xScale), 0)
9117            );
9118
9119            if (exports.DEBUG) {
9120                console.log('---EXEC COMPOSITE---');
9121                state.step = -1;
9122            }
9123
9124            exec(state);
9125
9126            gZone.length -= 2;
9127        }
9128    }
9129
9130    return gZone;
9131};
9132
9133/*
9134* Executes the hinting program for a component of a multi-component glyph
9135* or of the glyph itself for a non-component glyph.
9136*/
9137execComponent = function(glyph, state, xScale, yScale)
9138{
9139    var points = glyph.points || [];
9140    var pLen = points.length;
9141    var gZone = state.gZone = state.z0 = state.z1 = state.z2 = [];
9142    var contours = state.contours = [];
9143
9144    // Scales the original points and
9145    // makes copies for the hinted points.
9146    var cp; // current point
9147    for (var i = 0; i < pLen; i++) {
9148        cp = points[i];
9149
9150        gZone[i] = new HPoint(
9151            cp.x * xScale,
9152            cp.y * yScale,
9153            cp.lastPointOfContour,
9154            cp.onCurve
9155        );
9156    }
9157
9158    // Chain links the contours.
9159    var sp; // start point
9160    var np; // next point
9161
9162    for (var i$1 = 0; i$1 < pLen; i$1++) {
9163        cp = gZone[i$1];
9164
9165        if (!sp) {
9166            sp = cp;
9167            contours.push(i$1);
9168        }
9169
9170        if (cp.lastPointOfContour) {
9171            cp.nextPointOnContour = sp;
9172            sp.prevPointOnContour = cp;
9173            sp = undefined;
9174        } else {
9175            np = gZone[i$1 + 1];
9176            cp.nextPointOnContour = np;
9177            np.prevPointOnContour = cp;
9178        }
9179    }
9180
9181    if (state.inhibitGridFit) { return; }
9182
9183    if (exports.DEBUG) {
9184        console.log('PROCESSING GLYPH', state.stack);
9185        for (var i$2 = 0; i$2 < pLen; i$2++) {
9186            console.log(i$2, gZone[i$2].x, gZone[i$2].y);
9187        }
9188    }
9189
9190    gZone.push(
9191        new HPoint(0, 0),
9192        new HPoint(Math.round(glyph.advanceWidth * xScale), 0)
9193    );
9194
9195    exec(state);
9196
9197    // Removes the extra points.
9198    gZone.length -= 2;
9199
9200    if (exports.DEBUG) {
9201        console.log('FINISHED GLYPH', state.stack);
9202        for (var i$3 = 0; i$3 < pLen; i$3++) {
9203            console.log(i$3, gZone[i$3].x, gZone[i$3].y);
9204        }
9205    }
9206};
9207
9208/*
9209* Executes the program loaded in state.
9210*/
9211exec = function(state) {
9212    var prog = state.prog;
9213
9214    if (!prog) { return; }
9215
9216    var pLen = prog.length;
9217    var ins;
9218
9219    for (state.ip = 0; state.ip < pLen; state.ip++) {
9220        if (exports.DEBUG) { state.step++; }
9221        ins = instructionTable[prog[state.ip]];
9222
9223        if (!ins) {
9224            throw new Error(
9225                'unknown instruction: 0x' +
9226                Number(prog[state.ip]).toString(16)
9227            );
9228        }
9229
9230        ins(state);
9231
9232        // very extensive debugging for each step
9233        /*
9234        if (exports.DEBUG) {
9235            var da;
9236            if (state.gZone) {
9237                da = [];
9238                for (let i = 0; i < state.gZone.length; i++)
9239                {
9240                    da.push(i + ' ' +
9241                        state.gZone[i].x * 64 + ' ' +
9242                        state.gZone[i].y * 64 + ' ' +
9243                        (state.gZone[i].xTouched ? 'x' : '') +
9244                        (state.gZone[i].yTouched ? 'y' : '')
9245                    );
9246                }
9247                console.log('GZ', da);
9248            }
9249
9250            if (state.tZone) {
9251                da = [];
9252                for (let i = 0; i < state.tZone.length; i++) {
9253                    da.push(i + ' ' +
9254                        state.tZone[i].x * 64 + ' ' +
9255                        state.tZone[i].y * 64 + ' ' +
9256                        (state.tZone[i].xTouched ? 'x' : '') +
9257                        (state.tZone[i].yTouched ? 'y' : '')
9258                    );
9259                }
9260                console.log('TZ', da);
9261            }
9262
9263            if (state.stack.length > 10) {
9264                console.log(
9265                    state.stack.length,
9266                    '...', state.stack.slice(state.stack.length - 10)
9267                );
9268            } else {
9269                console.log(state.stack.length, state.stack);
9270            }
9271        }
9272        */
9273    }
9274};
9275
9276/*
9277* Initializes the twilight zone.
9278*
9279* This is only done if a SZPx instruction
9280* refers to the twilight zone.
9281*/
9282function initTZone(state)
9283{
9284    var tZone = state.tZone = new Array(state.gZone.length);
9285
9286    // no idea if this is actually correct...
9287    for (var i = 0; i < tZone.length; i++)
9288    {
9289        tZone[i] = new HPoint(0, 0);
9290    }
9291}
9292
9293/*
9294* Skips the instruction pointer ahead over an IF/ELSE block.
9295* handleElse .. if true breaks on matching ELSE
9296*/
9297function skip(state, handleElse)
9298{
9299    var prog = state.prog;
9300    var ip = state.ip;
9301    var nesting = 1;
9302    var ins;
9303
9304    do {
9305        ins = prog[++ip];
9306        if (ins === 0x58) // IF
9307            { nesting++; }
9308        else if (ins === 0x59) // EIF
9309            { nesting--; }
9310        else if (ins === 0x40) // NPUSHB
9311            { ip += prog[ip + 1] + 1; }
9312        else if (ins === 0x41) // NPUSHW
9313            { ip += 2 * prog[ip + 1] + 1; }
9314        else if (ins >= 0xB0 && ins <= 0xB7) // PUSHB
9315            { ip += ins - 0xB0 + 1; }
9316        else if (ins >= 0xB8 && ins <= 0xBF) // PUSHW
9317            { ip += (ins - 0xB8 + 1) * 2; }
9318        else if (handleElse && nesting === 1 && ins === 0x1B) // ELSE
9319            { break; }
9320    } while (nesting > 0);
9321
9322    state.ip = ip;
9323}
9324
9325/*----------------------------------------------------------*
9326*          And then a lot of instructions...                *
9327*----------------------------------------------------------*/
9328
9329// SVTCA[a] Set freedom and projection Vectors To Coordinate Axis
9330// 0x00-0x01
9331function SVTCA(v, state) {
9332    if (exports.DEBUG) { console.log(state.step, 'SVTCA[' + v.axis + ']'); }
9333
9334    state.fv = state.pv = state.dpv = v;
9335}
9336
9337// SPVTCA[a] Set Projection Vector to Coordinate Axis
9338// 0x02-0x03
9339function SPVTCA(v, state) {
9340    if (exports.DEBUG) { console.log(state.step, 'SPVTCA[' + v.axis + ']'); }
9341
9342    state.pv = state.dpv = v;
9343}
9344
9345// SFVTCA[a] Set Freedom Vector to Coordinate Axis
9346// 0x04-0x05
9347function SFVTCA(v, state) {
9348    if (exports.DEBUG) { console.log(state.step, 'SFVTCA[' + v.axis + ']'); }
9349
9350    state.fv = v;
9351}
9352
9353// SPVTL[a] Set Projection Vector To Line
9354// 0x06-0x07
9355function SPVTL(a, state) {
9356    var stack = state.stack;
9357    var p2i = stack.pop();
9358    var p1i = stack.pop();
9359    var p2 = state.z2[p2i];
9360    var p1 = state.z1[p1i];
9361
9362    if (exports.DEBUG) { console.log('SPVTL[' + a + ']', p2i, p1i); }
9363
9364    var dx;
9365    var dy;
9366
9367    if (!a) {
9368        dx = p1.x - p2.x;
9369        dy = p1.y - p2.y;
9370    } else {
9371        dx = p2.y - p1.y;
9372        dy = p1.x - p2.x;
9373    }
9374
9375    state.pv = state.dpv = getUnitVector(dx, dy);
9376}
9377
9378// SFVTL[a] Set Freedom Vector To Line
9379// 0x08-0x09
9380function SFVTL(a, state) {
9381    var stack = state.stack;
9382    var p2i = stack.pop();
9383    var p1i = stack.pop();
9384    var p2 = state.z2[p2i];
9385    var p1 = state.z1[p1i];
9386
9387    if (exports.DEBUG) { console.log('SFVTL[' + a + ']', p2i, p1i); }
9388
9389    var dx;
9390    var dy;
9391
9392    if (!a) {
9393        dx = p1.x - p2.x;
9394        dy = p1.y - p2.y;
9395    } else {
9396        dx = p2.y - p1.y;
9397        dy = p1.x - p2.x;
9398    }
9399
9400    state.fv = getUnitVector(dx, dy);
9401}
9402
9403// SPVFS[] Set Projection Vector From Stack
9404// 0x0A
9405function SPVFS(state) {
9406    var stack = state.stack;
9407    var y = stack.pop();
9408    var x = stack.pop();
9409
9410    if (exports.DEBUG) { console.log(state.step, 'SPVFS[]', y, x); }
9411
9412    state.pv = state.dpv = getUnitVector(x, y);
9413}
9414
9415// SFVFS[] Set Freedom Vector From Stack
9416// 0x0B
9417function SFVFS(state) {
9418    var stack = state.stack;
9419    var y = stack.pop();
9420    var x = stack.pop();
9421
9422    if (exports.DEBUG) { console.log(state.step, 'SPVFS[]', y, x); }
9423
9424    state.fv = getUnitVector(x, y);
9425}
9426
9427// GPV[] Get Projection Vector
9428// 0x0C
9429function GPV(state) {
9430    var stack = state.stack;
9431    var pv = state.pv;
9432
9433    if (exports.DEBUG) { console.log(state.step, 'GPV[]'); }
9434
9435    stack.push(pv.x * 0x4000);
9436    stack.push(pv.y * 0x4000);
9437}
9438
9439// GFV[] Get Freedom Vector
9440// 0x0C
9441function GFV(state) {
9442    var stack = state.stack;
9443    var fv = state.fv;
9444
9445    if (exports.DEBUG) { console.log(state.step, 'GFV[]'); }
9446
9447    stack.push(fv.x * 0x4000);
9448    stack.push(fv.y * 0x4000);
9449}
9450
9451// SFVTPV[] Set Freedom Vector To Projection Vector
9452// 0x0E
9453function SFVTPV(state) {
9454    state.fv = state.pv;
9455
9456    if (exports.DEBUG) { console.log(state.step, 'SFVTPV[]'); }
9457}
9458
9459// ISECT[] moves point p to the InterSECTion of two lines
9460// 0x0F
9461function ISECT(state)
9462{
9463    var stack = state.stack;
9464    var pa0i = stack.pop();
9465    var pa1i = stack.pop();
9466    var pb0i = stack.pop();
9467    var pb1i = stack.pop();
9468    var pi = stack.pop();
9469    var z0 = state.z0;
9470    var z1 = state.z1;
9471    var pa0 = z0[pa0i];
9472    var pa1 = z0[pa1i];
9473    var pb0 = z1[pb0i];
9474    var pb1 = z1[pb1i];
9475    var p = state.z2[pi];
9476
9477    if (exports.DEBUG) { console.log('ISECT[], ', pa0i, pa1i, pb0i, pb1i, pi); }
9478
9479    // math from
9480    // en.wikipedia.org/wiki/Line%E2%80%93line_intersection#Given_two_points_on_each_line
9481
9482    var x1 = pa0.x;
9483    var y1 = pa0.y;
9484    var x2 = pa1.x;
9485    var y2 = pa1.y;
9486    var x3 = pb0.x;
9487    var y3 = pb0.y;
9488    var x4 = pb1.x;
9489    var y4 = pb1.y;
9490
9491    var div = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4);
9492    var f1 = x1 * y2 - y1 * x2;
9493    var f2 = x3 * y4 - y3 * x4;
9494
9495    p.x = (f1 * (x3 - x4) - f2 * (x1 - x2)) / div;
9496    p.y = (f1 * (y3 - y4) - f2 * (y1 - y2)) / div;
9497}
9498
9499// SRP0[] Set Reference Point 0
9500// 0x10
9501function SRP0(state) {
9502    state.rp0 = state.stack.pop();
9503
9504    if (exports.DEBUG) { console.log(state.step, 'SRP0[]', state.rp0); }
9505}
9506
9507// SRP1[] Set Reference Point 1
9508// 0x11
9509function SRP1(state) {
9510    state.rp1 = state.stack.pop();
9511
9512    if (exports.DEBUG) { console.log(state.step, 'SRP1[]', state.rp1); }
9513}
9514
9515// SRP1[] Set Reference Point 2
9516// 0x12
9517function SRP2(state) {
9518    state.rp2 = state.stack.pop();
9519
9520    if (exports.DEBUG) { console.log(state.step, 'SRP2[]', state.rp2); }
9521}
9522
9523// SZP0[] Set Zone Pointer 0
9524// 0x13
9525function SZP0(state) {
9526    var n = state.stack.pop();
9527
9528    if (exports.DEBUG) { console.log(state.step, 'SZP0[]', n); }
9529
9530    state.zp0 = n;
9531
9532    switch (n) {
9533        case 0:
9534            if (!state.tZone) { initTZone(state); }
9535            state.z0 = state.tZone;
9536            break;
9537        case 1 :
9538            state.z0 = state.gZone;
9539            break;
9540        default :
9541            throw new Error('Invalid zone pointer');
9542    }
9543}
9544
9545// SZP1[] Set Zone Pointer 1
9546// 0x14
9547function SZP1(state) {
9548    var n = state.stack.pop();
9549
9550    if (exports.DEBUG) { console.log(state.step, 'SZP1[]', n); }
9551
9552    state.zp1 = n;
9553
9554    switch (n) {
9555        case 0:
9556            if (!state.tZone) { initTZone(state); }
9557            state.z1 = state.tZone;
9558            break;
9559        case 1 :
9560            state.z1 = state.gZone;
9561            break;
9562        default :
9563            throw new Error('Invalid zone pointer');
9564    }
9565}
9566
9567// SZP2[] Set Zone Pointer 2
9568// 0x15
9569function SZP2(state) {
9570    var n = state.stack.pop();
9571
9572    if (exports.DEBUG) { console.log(state.step, 'SZP2[]', n); }
9573
9574    state.zp2 = n;
9575
9576    switch (n) {
9577        case 0:
9578            if (!state.tZone) { initTZone(state); }
9579            state.z2 = state.tZone;
9580            break;
9581        case 1 :
9582            state.z2 = state.gZone;
9583            break;
9584        default :
9585            throw new Error('Invalid zone pointer');
9586    }
9587}
9588
9589// SZPS[] Set Zone PointerS
9590// 0x16
9591function SZPS(state) {
9592    var n = state.stack.pop();
9593
9594    if (exports.DEBUG) { console.log(state.step, 'SZPS[]', n); }
9595
9596    state.zp0 = state.zp1 = state.zp2 = n;
9597
9598    switch (n) {
9599        case 0:
9600            if (!state.tZone) { initTZone(state); }
9601            state.z0 = state.z1 = state.z2 = state.tZone;
9602            break;
9603        case 1 :
9604            state.z0 = state.z1 = state.z2 = state.gZone;
9605            break;
9606        default :
9607            throw new Error('Invalid zone pointer');
9608    }
9609}
9610
9611// SLOOP[] Set LOOP variable
9612// 0x17
9613function SLOOP(state) {
9614    state.loop = state.stack.pop();
9615
9616    if (exports.DEBUG) { console.log(state.step, 'SLOOP[]', state.loop); }
9617}
9618
9619// RTG[] Round To Grid
9620// 0x18
9621function RTG(state) {
9622    if (exports.DEBUG) { console.log(state.step, 'RTG[]'); }
9623
9624    state.round = roundToGrid;
9625}
9626
9627// RTHG[] Round To Half Grid
9628// 0x19
9629function RTHG(state) {
9630    if (exports.DEBUG) { console.log(state.step, 'RTHG[]'); }
9631
9632    state.round = roundToHalfGrid;
9633}
9634
9635// SMD[] Set Minimum Distance
9636// 0x1A
9637function SMD(state) {
9638    var d = state.stack.pop();
9639
9640    if (exports.DEBUG) { console.log(state.step, 'SMD[]', d); }
9641
9642    state.minDis = d / 0x40;
9643}
9644
9645// ELSE[] ELSE clause
9646// 0x1B
9647function ELSE(state) {
9648    // This instruction has been reached by executing a then branch
9649    // so it just skips ahead until matching EIF.
9650    //
9651    // In case the IF was negative the IF[] instruction already
9652    // skipped forward over the ELSE[]
9653
9654    if (exports.DEBUG) { console.log(state.step, 'ELSE[]'); }
9655
9656    skip(state, false);
9657}
9658
9659// JMPR[] JuMP Relative
9660// 0x1C
9661function JMPR(state) {
9662    var o = state.stack.pop();
9663
9664    if (exports.DEBUG) { console.log(state.step, 'JMPR[]', o); }
9665
9666    // A jump by 1 would do nothing.
9667    state.ip += o - 1;
9668}
9669
9670// SCVTCI[] Set Control Value Table Cut-In
9671// 0x1D
9672function SCVTCI(state) {
9673    var n = state.stack.pop();
9674
9675    if (exports.DEBUG) { console.log(state.step, 'SCVTCI[]', n); }
9676
9677    state.cvCutIn = n / 0x40;
9678}
9679
9680// DUP[] DUPlicate top stack element
9681// 0x20
9682function DUP(state) {
9683    var stack = state.stack;
9684
9685    if (exports.DEBUG) { console.log(state.step, 'DUP[]'); }
9686
9687    stack.push(stack[stack.length - 1]);
9688}
9689
9690// POP[] POP top stack element
9691// 0x21
9692function POP(state) {
9693    if (exports.DEBUG) { console.log(state.step, 'POP[]'); }
9694
9695    state.stack.pop();
9696}
9697
9698// CLEAR[] CLEAR the stack
9699// 0x22
9700function CLEAR(state) {
9701    if (exports.DEBUG) { console.log(state.step, 'CLEAR[]'); }
9702
9703    state.stack.length = 0;
9704}
9705
9706// SWAP[] SWAP the top two elements on the stack
9707// 0x23
9708function SWAP(state) {
9709    var stack = state.stack;
9710
9711    var a = stack.pop();
9712    var b = stack.pop();
9713
9714    if (exports.DEBUG) { console.log(state.step, 'SWAP[]'); }
9715
9716    stack.push(a);
9717    stack.push(b);
9718}
9719
9720// DEPTH[] DEPTH of the stack
9721// 0x24
9722function DEPTH(state) {
9723    var stack = state.stack;
9724
9725    if (exports.DEBUG) { console.log(state.step, 'DEPTH[]'); }
9726
9727    stack.push(stack.length);
9728}
9729
9730// LOOPCALL[] LOOPCALL function
9731// 0x2A
9732function LOOPCALL(state) {
9733    var stack = state.stack;
9734    var fn = stack.pop();
9735    var c = stack.pop();
9736
9737    if (exports.DEBUG) { console.log(state.step, 'LOOPCALL[]', fn, c); }
9738
9739    // saves callers program
9740    var cip = state.ip;
9741    var cprog = state.prog;
9742
9743    state.prog = state.funcs[fn];
9744
9745    // executes the function
9746    for (var i = 0; i < c; i++) {
9747        exec(state);
9748
9749        if (exports.DEBUG) { console.log(
9750            ++state.step,
9751            i + 1 < c ? 'next loopcall' : 'done loopcall',
9752            i
9753        ); }
9754    }
9755
9756    // restores the callers program
9757    state.ip = cip;
9758    state.prog = cprog;
9759}
9760
9761// CALL[] CALL function
9762// 0x2B
9763function CALL(state) {
9764    var fn = state.stack.pop();
9765
9766    if (exports.DEBUG) { console.log(state.step, 'CALL[]', fn); }
9767
9768    // saves callers program
9769    var cip = state.ip;
9770    var cprog = state.prog;
9771
9772    state.prog = state.funcs[fn];
9773
9774    // executes the function
9775    exec(state);
9776
9777    // restores the callers program
9778    state.ip = cip;
9779    state.prog = cprog;
9780
9781    if (exports.DEBUG) { console.log(++state.step, 'returning from', fn); }
9782}
9783
9784// CINDEX[] Copy the INDEXed element to the top of the stack
9785// 0x25
9786function CINDEX(state) {
9787    var stack = state.stack;
9788    var k = stack.pop();
9789
9790    if (exports.DEBUG) { console.log(state.step, 'CINDEX[]', k); }
9791
9792    // In case of k == 1, it copies the last element after popping
9793    // thus stack.length - k.
9794    stack.push(stack[stack.length - k]);
9795}
9796
9797// MINDEX[] Move the INDEXed element to the top of the stack
9798// 0x26
9799function MINDEX(state) {
9800    var stack = state.stack;
9801    var k = stack.pop();
9802
9803    if (exports.DEBUG) { console.log(state.step, 'MINDEX[]', k); }
9804
9805    stack.push(stack.splice(stack.length - k, 1)[0]);
9806}
9807
9808// FDEF[] Function DEFinition
9809// 0x2C
9810function FDEF(state) {
9811    if (state.env !== 'fpgm') { throw new Error('FDEF not allowed here'); }
9812    var stack = state.stack;
9813    var prog = state.prog;
9814    var ip = state.ip;
9815
9816    var fn = stack.pop();
9817    var ipBegin = ip;
9818
9819    if (exports.DEBUG) { console.log(state.step, 'FDEF[]', fn); }
9820
9821    while (prog[++ip] !== 0x2D){ }
9822
9823    state.ip = ip;
9824    state.funcs[fn] = prog.slice(ipBegin + 1, ip);
9825}
9826
9827// MDAP[a] Move Direct Absolute Point
9828// 0x2E-0x2F
9829function MDAP(round, state) {
9830    var pi = state.stack.pop();
9831    var p = state.z0[pi];
9832    var fv = state.fv;
9833    var pv = state.pv;
9834
9835    if (exports.DEBUG) { console.log(state.step, 'MDAP[' + round + ']', pi); }
9836
9837    var d = pv.distance(p, HPZero);
9838
9839    if (round) { d = state.round(d); }
9840
9841    fv.setRelative(p, HPZero, d, pv);
9842    fv.touch(p);
9843
9844    state.rp0 = state.rp1 = pi;
9845}
9846
9847// IUP[a] Interpolate Untouched Points through the outline
9848// 0x30
9849function IUP(v, state) {
9850    var z2 = state.z2;
9851    var pLen = z2.length - 2;
9852    var cp;
9853    var pp;
9854    var np;
9855
9856    if (exports.DEBUG) { console.log(state.step, 'IUP[' + v.axis + ']'); }
9857
9858    for (var i = 0; i < pLen; i++) {
9859        cp = z2[i]; // current point
9860
9861        // if this point has been touched go on
9862        if (v.touched(cp)) { continue; }
9863
9864        pp = cp.prevTouched(v);
9865
9866        // no point on the contour has been touched?
9867        if (pp === cp) { continue; }
9868
9869        np = cp.nextTouched(v);
9870
9871        if (pp === np) {
9872            // only one point on the contour has been touched
9873            // so simply moves the point like that
9874
9875            v.setRelative(cp, cp, v.distance(pp, pp, false, true), v, true);
9876        }
9877
9878        v.interpolate(cp, pp, np, v);
9879    }
9880}
9881
9882// SHP[] SHift Point using reference point
9883// 0x32-0x33
9884function SHP(a, state) {
9885    var stack = state.stack;
9886    var rpi = a ? state.rp1 : state.rp2;
9887    var rp = (a ? state.z0 : state.z1)[rpi];
9888    var fv = state.fv;
9889    var pv = state.pv;
9890    var loop = state.loop;
9891    var z2 = state.z2;
9892
9893    while (loop--)
9894    {
9895        var pi = stack.pop();
9896        var p = z2[pi];
9897
9898        var d = pv.distance(rp, rp, false, true);
9899        fv.setRelative(p, p, d, pv);
9900        fv.touch(p);
9901
9902        if (exports.DEBUG) {
9903            console.log(
9904                state.step,
9905                (state.loop > 1 ?
9906                   'loop ' + (state.loop - loop) + ': ' :
9907                   ''
9908                ) +
9909                'SHP[' + (a ? 'rp1' : 'rp2') + ']', pi
9910            );
9911        }
9912    }
9913
9914    state.loop = 1;
9915}
9916
9917// SHC[] SHift Contour using reference point
9918// 0x36-0x37
9919function SHC(a, state) {
9920    var stack = state.stack;
9921    var rpi = a ? state.rp1 : state.rp2;
9922    var rp = (a ? state.z0 : state.z1)[rpi];
9923    var fv = state.fv;
9924    var pv = state.pv;
9925    var ci = stack.pop();
9926    var sp = state.z2[state.contours[ci]];
9927    var p = sp;
9928
9929    if (exports.DEBUG) { console.log(state.step, 'SHC[' + a + ']', ci); }
9930
9931    var d = pv.distance(rp, rp, false, true);
9932
9933    do {
9934        if (p !== rp) { fv.setRelative(p, p, d, pv); }
9935        p = p.nextPointOnContour;
9936    } while (p !== sp);
9937}
9938
9939// SHZ[] SHift Zone using reference point
9940// 0x36-0x37
9941function SHZ(a, state) {
9942    var stack = state.stack;
9943    var rpi = a ? state.rp1 : state.rp2;
9944    var rp = (a ? state.z0 : state.z1)[rpi];
9945    var fv = state.fv;
9946    var pv = state.pv;
9947
9948    var e = stack.pop();
9949
9950    if (exports.DEBUG) { console.log(state.step, 'SHZ[' + a + ']', e); }
9951
9952    var z;
9953    switch (e) {
9954        case 0 : z = state.tZone; break;
9955        case 1 : z = state.gZone; break;
9956        default : throw new Error('Invalid zone');
9957    }
9958
9959    var p;
9960    var d = pv.distance(rp, rp, false, true);
9961    var pLen = z.length - 2;
9962    for (var i = 0; i < pLen; i++)
9963    {
9964        p = z[i];
9965        fv.setRelative(p, p, d, pv);
9966        //if (p !== rp) fv.setRelative(p, p, d, pv);
9967    }
9968}
9969
9970// SHPIX[] SHift point by a PIXel amount
9971// 0x38
9972function SHPIX(state) {
9973    var stack = state.stack;
9974    var loop = state.loop;
9975    var fv = state.fv;
9976    var d = stack.pop() / 0x40;
9977    var z2 = state.z2;
9978
9979    while (loop--) {
9980        var pi = stack.pop();
9981        var p = z2[pi];
9982
9983        if (exports.DEBUG) {
9984            console.log(
9985                state.step,
9986                (state.loop > 1 ? 'loop ' + (state.loop - loop) + ': ' : '') +
9987                'SHPIX[]', pi, d
9988            );
9989        }
9990
9991        fv.setRelative(p, p, d);
9992        fv.touch(p);
9993    }
9994
9995    state.loop = 1;
9996}
9997
9998// IP[] Interpolate Point
9999// 0x39
10000function IP(state) {
10001    var stack = state.stack;
10002    var rp1i = state.rp1;
10003    var rp2i = state.rp2;
10004    var loop = state.loop;
10005    var rp1 = state.z0[rp1i];
10006    var rp2 = state.z1[rp2i];
10007    var fv = state.fv;
10008    var pv = state.dpv;
10009    var z2 = state.z2;
10010
10011    while (loop--) {
10012        var pi = stack.pop();
10013        var p = z2[pi];
10014
10015        if (exports.DEBUG) {
10016            console.log(
10017                state.step,
10018                (state.loop > 1 ? 'loop ' + (state.loop - loop) + ': ' : '') +
10019                'IP[]', pi, rp1i, '<->', rp2i
10020            );
10021        }
10022
10023        fv.interpolate(p, rp1, rp2, pv);
10024
10025        fv.touch(p);
10026    }
10027
10028    state.loop = 1;
10029}
10030
10031// MSIRP[a] Move Stack Indirect Relative Point
10032// 0x3A-0x3B
10033function MSIRP(a, state) {
10034    var stack = state.stack;
10035    var d = stack.pop() / 64;
10036    var pi = stack.pop();
10037    var p = state.z1[pi];
10038    var rp0 = state.z0[state.rp0];
10039    var fv = state.fv;
10040    var pv = state.pv;
10041
10042    fv.setRelative(p, rp0, d, pv);
10043    fv.touch(p);
10044
10045    if (exports.DEBUG) { console.log(state.step, 'MSIRP[' + a + ']', d, pi); }
10046
10047    state.rp1 = state.rp0;
10048    state.rp2 = pi;
10049    if (a) { state.rp0 = pi; }
10050}
10051
10052// ALIGNRP[] Align to reference point.
10053// 0x3C
10054function ALIGNRP(state) {
10055    var stack = state.stack;
10056    var rp0i = state.rp0;
10057    var rp0 = state.z0[rp0i];
10058    var loop = state.loop;
10059    var fv = state.fv;
10060    var pv = state.pv;
10061    var z1 = state.z1;
10062
10063    while (loop--) {
10064        var pi = stack.pop();
10065        var p = z1[pi];
10066
10067        if (exports.DEBUG) {
10068            console.log(
10069                state.step,
10070                (state.loop > 1 ? 'loop ' + (state.loop - loop) + ': ' : '') +
10071                'ALIGNRP[]', pi
10072            );
10073        }
10074
10075        fv.setRelative(p, rp0, 0, pv);
10076        fv.touch(p);
10077    }
10078
10079    state.loop = 1;
10080}
10081
10082// RTG[] Round To Double Grid
10083// 0x3D
10084function RTDG(state) {
10085    if (exports.DEBUG) { console.log(state.step, 'RTDG[]'); }
10086
10087    state.round = roundToDoubleGrid;
10088}
10089
10090// MIAP[a] Move Indirect Absolute Point
10091// 0x3E-0x3F
10092function MIAP(round, state) {
10093    var stack = state.stack;
10094    var n = stack.pop();
10095    var pi = stack.pop();
10096    var p = state.z0[pi];
10097    var fv = state.fv;
10098    var pv = state.pv;
10099    var cv = state.cvt[n];
10100
10101    if (exports.DEBUG) {
10102        console.log(
10103            state.step,
10104            'MIAP[' + round + ']',
10105            n, '(', cv, ')', pi
10106        );
10107    }
10108
10109    var d = pv.distance(p, HPZero);
10110
10111    if (round) {
10112        if (Math.abs(d - cv) < state.cvCutIn) { d = cv; }
10113
10114        d = state.round(d);
10115    }
10116
10117    fv.setRelative(p, HPZero, d, pv);
10118
10119    if (state.zp0 === 0) {
10120        p.xo = p.x;
10121        p.yo = p.y;
10122    }
10123
10124    fv.touch(p);
10125
10126    state.rp0 = state.rp1 = pi;
10127}
10128
10129// NPUSB[] PUSH N Bytes
10130// 0x40
10131function NPUSHB(state) {
10132    var prog = state.prog;
10133    var ip = state.ip;
10134    var stack = state.stack;
10135
10136    var n = prog[++ip];
10137
10138    if (exports.DEBUG) { console.log(state.step, 'NPUSHB[]', n); }
10139
10140    for (var i = 0; i < n; i++) { stack.push(prog[++ip]); }
10141
10142    state.ip = ip;
10143}
10144
10145// NPUSHW[] PUSH N Words
10146// 0x41
10147function NPUSHW(state) {
10148    var ip = state.ip;
10149    var prog = state.prog;
10150    var stack = state.stack;
10151    var n = prog[++ip];
10152
10153    if (exports.DEBUG) { console.log(state.step, 'NPUSHW[]', n); }
10154
10155    for (var i = 0; i < n; i++) {
10156        var w = (prog[++ip] << 8) | prog[++ip];
10157        if (w & 0x8000) { w = -((w ^ 0xffff) + 1); }
10158        stack.push(w);
10159    }
10160
10161    state.ip = ip;
10162}
10163
10164// WS[] Write Store
10165// 0x42
10166function WS(state) {
10167    var stack = state.stack;
10168    var store = state.store;
10169
10170    if (!store) { store = state.store = []; }
10171
10172    var v = stack.pop();
10173    var l = stack.pop();
10174
10175    if (exports.DEBUG) { console.log(state.step, 'WS', v, l); }
10176
10177    store[l] = v;
10178}
10179
10180// RS[] Read Store
10181// 0x43
10182function RS(state) {
10183    var stack = state.stack;
10184    var store = state.store;
10185
10186    var l = stack.pop();
10187
10188    if (exports.DEBUG) { console.log(state.step, 'RS', l); }
10189
10190    var v = (store && store[l]) || 0;
10191
10192    stack.push(v);
10193}
10194
10195// WCVTP[] Write Control Value Table in Pixel units
10196// 0x44
10197function WCVTP(state) {
10198    var stack = state.stack;
10199
10200    var v = stack.pop();
10201    var l = stack.pop();
10202
10203    if (exports.DEBUG) { console.log(state.step, 'WCVTP', v, l); }
10204
10205    state.cvt[l] = v / 0x40;
10206}
10207
10208// RCVT[] Read Control Value Table entry
10209// 0x45
10210function RCVT(state) {
10211    var stack = state.stack;
10212    var cvte = stack.pop();
10213
10214    if (exports.DEBUG) { console.log(state.step, 'RCVT', cvte); }
10215
10216    stack.push(state.cvt[cvte] * 0x40);
10217}
10218
10219// GC[] Get Coordinate projected onto the projection vector
10220// 0x46-0x47
10221function GC(a, state) {
10222    var stack = state.stack;
10223    var pi = stack.pop();
10224    var p = state.z2[pi];
10225
10226    if (exports.DEBUG) { console.log(state.step, 'GC[' + a + ']', pi); }
10227
10228    stack.push(state.dpv.distance(p, HPZero, a, false) * 0x40);
10229}
10230
10231// MD[a] Measure Distance
10232// 0x49-0x4A
10233function MD(a, state) {
10234    var stack = state.stack;
10235    var pi2 = stack.pop();
10236    var pi1 = stack.pop();
10237    var p2 = state.z1[pi2];
10238    var p1 = state.z0[pi1];
10239    var d = state.dpv.distance(p1, p2, a, a);
10240
10241    if (exports.DEBUG) { console.log(state.step, 'MD[' + a + ']', pi2, pi1, '->', d); }
10242
10243    state.stack.push(Math.round(d * 64));
10244}
10245
10246// MPPEM[] Measure Pixels Per EM
10247// 0x4B
10248function MPPEM(state) {
10249    if (exports.DEBUG) { console.log(state.step, 'MPPEM[]'); }
10250    state.stack.push(state.ppem);
10251}
10252
10253// FLIPON[] set the auto FLIP Boolean to ON
10254// 0x4D
10255function FLIPON(state) {
10256    if (exports.DEBUG) { console.log(state.step, 'FLIPON[]'); }
10257    state.autoFlip = true;
10258}
10259
10260// LT[] Less Than
10261// 0x50
10262function LT(state) {
10263    var stack = state.stack;
10264    var e2 = stack.pop();
10265    var e1 = stack.pop();
10266
10267    if (exports.DEBUG) { console.log(state.step, 'LT[]', e2, e1); }
10268
10269    stack.push(e1 < e2 ? 1 : 0);
10270}
10271
10272// LTEQ[] Less Than or EQual
10273// 0x53
10274function LTEQ(state) {
10275    var stack = state.stack;
10276    var e2 = stack.pop();
10277    var e1 = stack.pop();
10278
10279    if (exports.DEBUG) { console.log(state.step, 'LTEQ[]', e2, e1); }
10280
10281    stack.push(e1 <= e2 ? 1 : 0);
10282}
10283
10284// GTEQ[] Greater Than
10285// 0x52
10286function GT(state) {
10287    var stack = state.stack;
10288    var e2 = stack.pop();
10289    var e1 = stack.pop();
10290
10291    if (exports.DEBUG) { console.log(state.step, 'GT[]', e2, e1); }
10292
10293    stack.push(e1 > e2 ? 1 : 0);
10294}
10295
10296// GTEQ[] Greater Than or EQual
10297// 0x53
10298function GTEQ(state) {
10299    var stack = state.stack;
10300    var e2 = stack.pop();
10301    var e1 = stack.pop();
10302
10303    if (exports.DEBUG) { console.log(state.step, 'GTEQ[]', e2, e1); }
10304
10305    stack.push(e1 >= e2 ? 1 : 0);
10306}
10307
10308// EQ[] EQual
10309// 0x54
10310function EQ(state) {
10311    var stack = state.stack;
10312    var e2 = stack.pop();
10313    var e1 = stack.pop();
10314
10315    if (exports.DEBUG) { console.log(state.step, 'EQ[]', e2, e1); }
10316
10317    stack.push(e2 === e1 ? 1 : 0);
10318}
10319
10320// NEQ[] Not EQual
10321// 0x55
10322function NEQ(state) {
10323    var stack = state.stack;
10324    var e2 = stack.pop();
10325    var e1 = stack.pop();
10326
10327    if (exports.DEBUG) { console.log(state.step, 'NEQ[]', e2, e1); }
10328
10329    stack.push(e2 !== e1 ? 1 : 0);
10330}
10331
10332// ODD[] ODD
10333// 0x56
10334function ODD(state) {
10335    var stack = state.stack;
10336    var n = stack.pop();
10337
10338    if (exports.DEBUG) { console.log(state.step, 'ODD[]', n); }
10339
10340    stack.push(Math.trunc(n) % 2 ? 1 : 0);
10341}
10342
10343// EVEN[] EVEN
10344// 0x57
10345function EVEN(state) {
10346    var stack = state.stack;
10347    var n = stack.pop();
10348
10349    if (exports.DEBUG) { console.log(state.step, 'EVEN[]', n); }
10350
10351    stack.push(Math.trunc(n) % 2 ? 0 : 1);
10352}
10353
10354// IF[] IF test
10355// 0x58
10356function IF(state) {
10357    var test = state.stack.pop();
10358
10359    if (exports.DEBUG) { console.log(state.step, 'IF[]', test); }
10360
10361    // if test is true it just continues
10362    // if not the ip is skipped until matching ELSE or EIF
10363    if (!test) {
10364        skip(state, true);
10365
10366        if (exports.DEBUG) { console.log(state.step,  'EIF[]'); }
10367    }
10368}
10369
10370// EIF[] End IF
10371// 0x59
10372function EIF(state) {
10373    // this can be reached normally when
10374    // executing an else branch.
10375    // -> just ignore it
10376
10377    if (exports.DEBUG) { console.log(state.step, 'EIF[]'); }
10378}
10379
10380// AND[] logical AND
10381// 0x5A
10382function AND(state) {
10383    var stack = state.stack;
10384    var e2 = stack.pop();
10385    var e1 = stack.pop();
10386
10387    if (exports.DEBUG) { console.log(state.step, 'AND[]', e2, e1); }
10388
10389    stack.push(e2 && e1 ? 1 : 0);
10390}
10391
10392// OR[] logical OR
10393// 0x5B
10394function OR(state) {
10395    var stack = state.stack;
10396    var e2 = stack.pop();
10397    var e1 = stack.pop();
10398
10399    if (exports.DEBUG) { console.log(state.step, 'OR[]', e2, e1); }
10400
10401    stack.push(e2 || e1 ? 1 : 0);
10402}
10403
10404// NOT[] logical NOT
10405// 0x5C
10406function NOT(state) {
10407    var stack = state.stack;
10408    var e = stack.pop();
10409
10410    if (exports.DEBUG) { console.log(state.step, 'NOT[]', e); }
10411
10412    stack.push(e ? 0 : 1);
10413}
10414
10415// DELTAP1[] DELTA exception P1
10416// DELTAP2[] DELTA exception P2
10417// DELTAP3[] DELTA exception P3
10418// 0x5D, 0x71, 0x72
10419function DELTAP123(b, state) {
10420    var stack = state.stack;
10421    var n = stack.pop();
10422    var fv = state.fv;
10423    var pv = state.pv;
10424    var ppem = state.ppem;
10425    var base = state.deltaBase + (b - 1) * 16;
10426    var ds = state.deltaShift;
10427    var z0 = state.z0;
10428
10429    if (exports.DEBUG) { console.log(state.step, 'DELTAP[' + b + ']', n, stack); }
10430
10431    for (var i = 0; i < n; i++) {
10432        var pi = stack.pop();
10433        var arg = stack.pop();
10434        var appem = base + ((arg & 0xF0) >> 4);
10435        if (appem !== ppem) { continue; }
10436
10437        var mag = (arg & 0x0F) - 8;
10438        if (mag >= 0) { mag++; }
10439        if (exports.DEBUG) { console.log(state.step, 'DELTAPFIX', pi, 'by', mag * ds); }
10440
10441        var p = z0[pi];
10442        fv.setRelative(p, p, mag * ds, pv);
10443    }
10444}
10445
10446// SDB[] Set Delta Base in the graphics state
10447// 0x5E
10448function SDB(state) {
10449    var stack = state.stack;
10450    var n = stack.pop();
10451
10452    if (exports.DEBUG) { console.log(state.step, 'SDB[]', n); }
10453
10454    state.deltaBase = n;
10455}
10456
10457// SDS[] Set Delta Shift in the graphics state
10458// 0x5F
10459function SDS(state) {
10460    var stack = state.stack;
10461    var n = stack.pop();
10462
10463    if (exports.DEBUG) { console.log(state.step, 'SDS[]', n); }
10464
10465    state.deltaShift = Math.pow(0.5, n);
10466}
10467
10468// ADD[] ADD
10469// 0x60
10470function ADD(state) {
10471    var stack = state.stack;
10472    var n2 = stack.pop();
10473    var n1 = stack.pop();
10474
10475    if (exports.DEBUG) { console.log(state.step, 'ADD[]', n2, n1); }
10476
10477    stack.push(n1 + n2);
10478}
10479
10480// SUB[] SUB
10481// 0x61
10482function SUB(state) {
10483    var stack = state.stack;
10484    var n2 = stack.pop();
10485    var n1 = stack.pop();
10486
10487    if (exports.DEBUG) { console.log(state.step, 'SUB[]', n2, n1); }
10488
10489    stack.push(n1 - n2);
10490}
10491
10492// DIV[] DIV
10493// 0x62
10494function DIV(state) {
10495    var stack = state.stack;
10496    var n2 = stack.pop();
10497    var n1 = stack.pop();
10498
10499    if (exports.DEBUG) { console.log(state.step, 'DIV[]', n2, n1); }
10500
10501    stack.push(n1 * 64 / n2);
10502}
10503
10504// MUL[] MUL
10505// 0x63
10506function MUL(state) {
10507    var stack = state.stack;
10508    var n2 = stack.pop();
10509    var n1 = stack.pop();
10510
10511    if (exports.DEBUG) { console.log(state.step, 'MUL[]', n2, n1); }
10512
10513    stack.push(n1 * n2 / 64);
10514}
10515
10516// ABS[] ABSolute value
10517// 0x64
10518function ABS(state) {
10519    var stack = state.stack;
10520    var n = stack.pop();
10521
10522    if (exports.DEBUG) { console.log(state.step, 'ABS[]', n); }
10523
10524    stack.push(Math.abs(n));
10525}
10526
10527// NEG[] NEGate
10528// 0x65
10529function NEG(state) {
10530    var stack = state.stack;
10531    var n = stack.pop();
10532
10533    if (exports.DEBUG) { console.log(state.step, 'NEG[]', n); }
10534
10535    stack.push(-n);
10536}
10537
10538// FLOOR[] FLOOR
10539// 0x66
10540function FLOOR(state) {
10541    var stack = state.stack;
10542    var n = stack.pop();
10543
10544    if (exports.DEBUG) { console.log(state.step, 'FLOOR[]', n); }
10545
10546    stack.push(Math.floor(n / 0x40) * 0x40);
10547}
10548
10549// CEILING[] CEILING
10550// 0x67
10551function CEILING(state) {
10552    var stack = state.stack;
10553    var n = stack.pop();
10554
10555    if (exports.DEBUG) { console.log(state.step, 'CEILING[]', n); }
10556
10557    stack.push(Math.ceil(n / 0x40) * 0x40);
10558}
10559
10560// ROUND[ab] ROUND value
10561// 0x68-0x6B
10562function ROUND(dt, state) {
10563    var stack = state.stack;
10564    var n = stack.pop();
10565
10566    if (exports.DEBUG) { console.log(state.step, 'ROUND[]'); }
10567
10568    stack.push(state.round(n / 0x40) * 0x40);
10569}
10570
10571// WCVTF[] Write Control Value Table in Funits
10572// 0x70
10573function WCVTF(state) {
10574    var stack = state.stack;
10575    var v = stack.pop();
10576    var l = stack.pop();
10577
10578    if (exports.DEBUG) { console.log(state.step, 'WCVTF[]', v, l); }
10579
10580    state.cvt[l] = v * state.ppem / state.font.unitsPerEm;
10581}
10582
10583// DELTAC1[] DELTA exception C1
10584// DELTAC2[] DELTA exception C2
10585// DELTAC3[] DELTA exception C3
10586// 0x73, 0x74, 0x75
10587function DELTAC123(b, state) {
10588    var stack = state.stack;
10589    var n = stack.pop();
10590    var ppem = state.ppem;
10591    var base = state.deltaBase + (b - 1) * 16;
10592    var ds = state.deltaShift;
10593
10594    if (exports.DEBUG) { console.log(state.step, 'DELTAC[' + b + ']', n, stack); }
10595
10596    for (var i = 0; i < n; i++) {
10597        var c = stack.pop();
10598        var arg = stack.pop();
10599        var appem = base + ((arg & 0xF0) >> 4);
10600        if (appem !== ppem) { continue; }
10601
10602        var mag = (arg & 0x0F) - 8;
10603        if (mag >= 0) { mag++; }
10604
10605        var delta = mag * ds;
10606
10607        if (exports.DEBUG) { console.log(state.step, 'DELTACFIX', c, 'by', delta); }
10608
10609        state.cvt[c] += delta;
10610    }
10611}
10612
10613// SROUND[] Super ROUND
10614// 0x76
10615function SROUND(state) {
10616    var n = state.stack.pop();
10617
10618    if (exports.DEBUG) { console.log(state.step, 'SROUND[]', n); }
10619
10620    state.round = roundSuper;
10621
10622    var period;
10623
10624    switch (n & 0xC0) {
10625        case 0x00:
10626            period = 0.5;
10627            break;
10628        case 0x40:
10629            period = 1;
10630            break;
10631        case 0x80:
10632            period = 2;
10633            break;
10634        default:
10635            throw new Error('invalid SROUND value');
10636    }
10637
10638    state.srPeriod = period;
10639
10640    switch (n & 0x30) {
10641        case 0x00:
10642            state.srPhase = 0;
10643            break;
10644        case 0x10:
10645            state.srPhase = 0.25 * period;
10646            break;
10647        case 0x20:
10648            state.srPhase = 0.5  * period;
10649            break;
10650        case 0x30:
10651            state.srPhase = 0.75 * period;
10652            break;
10653        default: throw new Error('invalid SROUND value');
10654    }
10655
10656    n &= 0x0F;
10657
10658    if (n === 0) { state.srThreshold = 0; }
10659    else { state.srThreshold = (n / 8 - 0.5) * period; }
10660}
10661
10662// S45ROUND[] Super ROUND 45 degrees
10663// 0x77
10664function S45ROUND(state) {
10665    var n = state.stack.pop();
10666
10667    if (exports.DEBUG) { console.log(state.step, 'S45ROUND[]', n); }
10668
10669    state.round = roundSuper;
10670
10671    var period;
10672
10673    switch (n & 0xC0) {
10674        case 0x00:
10675            period = Math.sqrt(2) / 2;
10676            break;
10677        case 0x40:
10678            period = Math.sqrt(2);
10679            break;
10680        case 0x80:
10681            period = 2 * Math.sqrt(2);
10682            break;
10683        default:
10684            throw new Error('invalid S45ROUND value');
10685    }
10686
10687    state.srPeriod = period;
10688
10689    switch (n & 0x30) {
10690        case 0x00:
10691            state.srPhase = 0;
10692            break;
10693        case 0x10:
10694            state.srPhase = 0.25 * period;
10695            break;
10696        case 0x20:
10697            state.srPhase = 0.5  * period;
10698            break;
10699        case 0x30:
10700            state.srPhase = 0.75 * period;
10701            break;
10702        default:
10703            throw new Error('invalid S45ROUND value');
10704    }
10705
10706    n &= 0x0F;
10707
10708    if (n === 0) { state.srThreshold = 0; }
10709    else { state.srThreshold = (n / 8 - 0.5) * period; }
10710}
10711
10712// ROFF[] Round Off
10713// 0x7A
10714function ROFF(state) {
10715    if (exports.DEBUG) { console.log(state.step, 'ROFF[]'); }
10716
10717    state.round = roundOff;
10718}
10719
10720// RUTG[] Round Up To Grid
10721// 0x7C
10722function RUTG(state) {
10723    if (exports.DEBUG) { console.log(state.step, 'RUTG[]'); }
10724
10725    state.round = roundUpToGrid;
10726}
10727
10728// RDTG[] Round Down To Grid
10729// 0x7D
10730function RDTG(state) {
10731    if (exports.DEBUG) { console.log(state.step, 'RDTG[]'); }
10732
10733    state.round = roundDownToGrid;
10734}
10735
10736// SCANCTRL[] SCAN conversion ConTRoL
10737// 0x85
10738function SCANCTRL(state) {
10739    var n = state.stack.pop();
10740
10741    // ignored by opentype.js
10742
10743    if (exports.DEBUG) { console.log(state.step, 'SCANCTRL[]', n); }
10744}
10745
10746// SDPVTL[a] Set Dual Projection Vector To Line
10747// 0x86-0x87
10748function SDPVTL(a, state) {
10749    var stack = state.stack;
10750    var p2i = stack.pop();
10751    var p1i = stack.pop();
10752    var p2 = state.z2[p2i];
10753    var p1 = state.z1[p1i];
10754
10755    if (exports.DEBUG) { console.log(state.step, 'SDPVTL[' + a + ']', p2i, p1i); }
10756
10757    var dx;
10758    var dy;
10759
10760    if (!a) {
10761        dx = p1.x - p2.x;
10762        dy = p1.y - p2.y;
10763    } else {
10764        dx = p2.y - p1.y;
10765        dy = p1.x - p2.x;
10766    }
10767
10768    state.dpv = getUnitVector(dx, dy);
10769}
10770
10771// GETINFO[] GET INFOrmation
10772// 0x88
10773function GETINFO(state) {
10774    var stack = state.stack;
10775    var sel = stack.pop();
10776    var r = 0;
10777
10778    if (exports.DEBUG) { console.log(state.step, 'GETINFO[]', sel); }
10779
10780    // v35 as in no subpixel hinting
10781    if (sel & 0x01) { r = 35; }
10782
10783    // TODO rotation and stretch currently not supported
10784    // and thus those GETINFO are always 0.
10785
10786    // opentype.js is always gray scaling
10787    if (sel & 0x20) { r |= 0x1000; }
10788
10789    stack.push(r);
10790}
10791
10792// ROLL[] ROLL the top three stack elements
10793// 0x8A
10794function ROLL(state) {
10795    var stack = state.stack;
10796    var a = stack.pop();
10797    var b = stack.pop();
10798    var c = stack.pop();
10799
10800    if (exports.DEBUG) { console.log(state.step, 'ROLL[]'); }
10801
10802    stack.push(b);
10803    stack.push(a);
10804    stack.push(c);
10805}
10806
10807// MAX[] MAXimum of top two stack elements
10808// 0x8B
10809function MAX(state) {
10810    var stack = state.stack;
10811    var e2 = stack.pop();
10812    var e1 = stack.pop();
10813
10814    if (exports.DEBUG) { console.log(state.step, 'MAX[]', e2, e1); }
10815
10816    stack.push(Math.max(e1, e2));
10817}
10818
10819// MIN[] MINimum of top two stack elements
10820// 0x8C
10821function MIN(state) {
10822    var stack = state.stack;
10823    var e2 = stack.pop();
10824    var e1 = stack.pop();
10825
10826    if (exports.DEBUG) { console.log(state.step, 'MIN[]', e2, e1); }
10827
10828    stack.push(Math.min(e1, e2));
10829}
10830
10831// SCANTYPE[] SCANTYPE
10832// 0x8D
10833function SCANTYPE(state) {
10834    var n = state.stack.pop();
10835    // ignored by opentype.js
10836    if (exports.DEBUG) { console.log(state.step, 'SCANTYPE[]', n); }
10837}
10838
10839// INSTCTRL[] INSTCTRL
10840// 0x8D
10841function INSTCTRL(state) {
10842    var s = state.stack.pop();
10843    var v = state.stack.pop();
10844
10845    if (exports.DEBUG) { console.log(state.step, 'INSTCTRL[]', s, v); }
10846
10847    switch (s) {
10848        case 1 : state.inhibitGridFit = !!v; return;
10849        case 2 : state.ignoreCvt = !!v; return;
10850        default: throw new Error('invalid INSTCTRL[] selector');
10851    }
10852}
10853
10854// PUSHB[abc] PUSH Bytes
10855// 0xB0-0xB7
10856function PUSHB(n, state) {
10857    var stack = state.stack;
10858    var prog = state.prog;
10859    var ip = state.ip;
10860
10861    if (exports.DEBUG) { console.log(state.step, 'PUSHB[' + n + ']'); }
10862
10863    for (var i = 0; i < n; i++) { stack.push(prog[++ip]); }
10864
10865    state.ip = ip;
10866}
10867
10868// PUSHW[abc] PUSH Words
10869// 0xB8-0xBF
10870function PUSHW(n, state) {
10871    var ip = state.ip;
10872    var prog = state.prog;
10873    var stack = state.stack;
10874
10875    if (exports.DEBUG) { console.log(state.ip, 'PUSHW[' + n + ']'); }
10876
10877    for (var i = 0; i < n; i++) {
10878        var w = (prog[++ip] << 8) | prog[++ip];
10879        if (w & 0x8000) { w = -((w ^ 0xffff) + 1); }
10880        stack.push(w);
10881    }
10882
10883    state.ip = ip;
10884}
10885
10886// MDRP[abcde] Move Direct Relative Point
10887// 0xD0-0xEF
10888// (if indirect is 0)
10889//
10890// and
10891//
10892// MIRP[abcde] Move Indirect Relative Point
10893// 0xE0-0xFF
10894// (if indirect is 1)
10895
10896function MDRP_MIRP(indirect, setRp0, keepD, ro, dt, state) {
10897    var stack = state.stack;
10898    var cvte = indirect && stack.pop();
10899    var pi = stack.pop();
10900    var rp0i = state.rp0;
10901    var rp = state.z0[rp0i];
10902    var p = state.z1[pi];
10903
10904    var md = state.minDis;
10905    var fv = state.fv;
10906    var pv = state.dpv;
10907    var od; // original distance
10908    var d; // moving distance
10909    var sign; // sign of distance
10910    var cv;
10911
10912    d = od = pv.distance(p, rp, true, true);
10913    sign = d >= 0 ? 1 : -1; // Math.sign would be 0 in case of 0
10914
10915    // TODO consider autoFlip
10916    d = Math.abs(d);
10917
10918    if (indirect) {
10919        cv = state.cvt[cvte];
10920
10921        if (ro && Math.abs(d - cv) < state.cvCutIn) { d = cv; }
10922    }
10923
10924    if (keepD && d < md) { d = md; }
10925
10926    if (ro) { d = state.round(d); }
10927
10928    fv.setRelative(p, rp, sign * d, pv);
10929    fv.touch(p);
10930
10931    if (exports.DEBUG) {
10932        console.log(
10933            state.step,
10934            (indirect ? 'MIRP[' : 'MDRP[') +
10935            (setRp0 ? 'M' : 'm') +
10936            (keepD ? '>' : '_') +
10937            (ro ? 'R' : '_') +
10938            (dt === 0 ? 'Gr' : (dt === 1 ? 'Bl' : (dt === 2 ? 'Wh' : ''))) +
10939            ']',
10940            indirect ?
10941                cvte + '(' + state.cvt[cvte] + ',' +  cv + ')' :
10942                '',
10943            pi,
10944            '(d =', od, '->', sign * d, ')'
10945        );
10946    }
10947
10948    state.rp1 = state.rp0;
10949    state.rp2 = pi;
10950    if (setRp0) { state.rp0 = pi; }
10951}
10952
10953/*
10954* The instruction table.
10955*/
10956instructionTable = [
10957    /* 0x00 */ SVTCA.bind(undefined, yUnitVector),
10958    /* 0x01 */ SVTCA.bind(undefined, xUnitVector),
10959    /* 0x02 */ SPVTCA.bind(undefined, yUnitVector),
10960    /* 0x03 */ SPVTCA.bind(undefined, xUnitVector),
10961    /* 0x04 */ SFVTCA.bind(undefined, yUnitVector),
10962    /* 0x05 */ SFVTCA.bi
10962nd(undefined, xUnitVector),
10963    /* 0x06 */ SPVTL.bind(undefined, 0),
10964    /* 0x07 */ SPVTL.bind(undefined, 1),
10965    /* 0x08 */ SFVTL.bind(undefined, 0),
10966    /* 0x09 */ SFVTL.bind(undefined, 1),
10967    /* 0x0A */ SPVFS,
10968    /* 0x0B */ SFVFS,
10969    /* 0x0C */ GPV,
10970    /* 0x0D */ GFV,
10971    /* 0x0E */ SFVTPV,
10972    /* 0x0F */ ISECT,
10973    /* 0x10 */ SRP0,
10974    /* 0x11 */ SRP1,
10975    /* 0x12 */ SRP2,
10976    /* 0x13 */ SZP0,
10977    /* 0x14 */ SZP1,
10978    /* 0x15 */ SZP2,
10979    /* 0x16 */ SZPS,
10980    /* 0x17 */ SLOOP,
10981    /* 0x18 */ RTG,
10982    /* 0x19 */ RTHG,
10983    /* 0x1A */ SMD,
10984    /* 0x1B */ ELSE,
10985    /* 0x1C */ JMPR,
10986    /* 0x1D */ SCVTCI,
10987    /* 0x1E */ undefined,   // TODO SSWCI
10988    /* 0x1F */ undefined,   // TODO SSW
10989    /* 0x20 */ DUP,
10990    /* 0x21 */ POP,
10991    /* 0x22 */ CLEAR,
10992    /* 0x23 */ SWAP,
10993    /* 0x24 */ DEPTH,
10994    /* 0x25 */ CINDEX,
10995    /* 0x26 */ MINDEX,
10996    /* 0x27 */ undefined,   // TODO ALIGNPTS
10997    /* 0x28 */ undefined,
10998    /* 0x29 */ undefined,   // TODO UTP
10999    /* 0x2A */ LOOPCALL,
11000    /* 0x2B */ CALL,
11001    /* 0x2C */ FDEF,
11002    /* 0x2D */ undefined,   // ENDF (eaten by FDEF)
11003    /* 0x2E */ MDAP.bind(undefined, 0),
11004    /* 0x2F */ MDAP.bind(undefined, 1),
11005    /* 0x30 */ IUP.bind(undefined, yUnitVector),
11006    /* 0x31 */ IUP.bind(undefined, xUnitVector),
11007    /* 0x32 */ SHP.bind(undefined, 0),
11008    /* 0x33 */ SHP.bind(undefined, 1),
11009    /* 0x34 */ SHC.bind(undefined, 0),
11010    /* 0x35 */ SHC.bind(undefined, 1),
11011    /* 0x36 */ SHZ.bind(undefined, 0),
11012    /* 0x37 */ SHZ.bind(undefined, 1),
11013    /* 0x38 */ SHPIX,
11014    /* 0x39 */ IP,
11015    /* 0x3A */ MSIRP.bind(undefined, 0),
11016    /* 0x3B */ MSIRP.bind(undefined, 1),
11017    /* 0x3C */ ALIGNRP,
11018    /* 0x3D */ RTDG,
11019    /* 0x3E */ MIAP.bind(undefined, 0),
11020    /* 0x3F */ MIAP.bind(undefined, 1),
11021    /* 0x40 */ NPUSHB,
11022    /* 0x41 */ NPUSHW,
11023    /* 0x42 */ WS,
11024    /* 0x43 */ RS,
11025    /* 0x44 */ WCVTP,
11026    /* 0x45 */ RCVT,
11027    /* 0x46 */ GC.bind(undefined, 0),
11028    /* 0x47 */ GC.bind(undefined, 1),
11029    /* 0x48 */ undefined,   // TODO SCFS
11030    /* 0x49 */ MD.bind(undefined, 0),
11031    /* 0x4A */ MD.bind(undefined, 1),
11032    /* 0x4B */ MPPEM,
11033    /* 0x4C */ undefined,   // TODO MPS
11034    /* 0x4D */ FLIPON,
11035    /* 0x4E */ undefined,   // TODO FLIPOFF
11036    /* 0x4F */ undefined,   // TODO DEBUG
11037    /* 0x50 */ LT,
11038    /* 0x51 */ LTEQ,
11039    /* 0x52 */ GT,
11040    /* 0x53 */ GTEQ,
11041    /* 0x54 */ EQ,
11042    /* 0x55 */ NEQ,
11043    /* 0x56 */ ODD,
11044    /* 0x57 */ EVEN,
11045    /* 0x58 */ IF,
11046    /* 0x59 */ EIF,
11047    /* 0x5A */ AND,
11048    /* 0x5B */ OR,
11049    /* 0x5C */ NOT,
11050    /* 0x5D */ DELTAP123.bind(undefined, 1),
11051    /* 0x5E */ SDB,
11052    /* 0x5F */ SDS,
11053    /* 0x60 */ ADD,
11054    /* 0x61 */ SUB,
11055    /* 0x62 */ DIV,
11056    /* 0x63 */ MUL,
11057    /* 0x64 */ ABS,
11058    /* 0x65 */ NEG,
11059    /* 0x66 */ FLOOR,
11060    /* 0x67 */ CEILING,
11061    /* 0x68 */ ROUND.bind(undefined, 0),
11062    /* 0x69 */ ROUND.bind(undefined, 1),
11063    /* 0x6A */ ROUND.bind(undefined, 2),
11064    /* 0x6B */ ROUND.bind(undefined, 3),
11065    /* 0x6C */ undefined,   // TODO NROUND[ab]
11066    /* 0x6D */ undefined,   // TODO NROUND[ab]
11067    /* 0x6E */ undefined,   // TODO NROUND[ab]
11068    /* 0x6F */ undefined,   // TODO NROUND[ab]
11069    /* 0x70 */ WCVTF,
11070    /* 0x71 */ DELTAP123.bind(undefined, 2),
11071    /* 0x72 */ DELTAP123.bind(undefined, 3),
11072    /* 0x73 */ DELTAC123.bind(undefined, 1),
11073    /* 0x74 */ DELTAC123.bind(undefined, 2),
11074    /* 0x75 */ DELTAC123.bind(undefined, 3),
11075    /* 0x76 */ SROUND,
11076    /* 0x77 */ S45ROUND,
11077    /* 0x78 */ undefined,   // TODO JROT[]
11078    /* 0x79 */ undefined,   // TODO JROF[]
11079    /* 0x7A */ ROFF,
11080    /* 0x7B */ undefined,
11081    /* 0x7C */ RUTG,
11082    /* 0x7D */ RDTG,
11083    /* 0x7E */ POP, // actually SANGW, supposed to do only a pop though
11084    /* 0x7F */ POP, // actually AA, supposed to do only a pop though
11085    /* 0x80 */ undefined,   // TODO FLIPPT
11086    /* 0x81 */ undefined,   // TODO FLIPRGON
11087    /* 0x82 */ undefined,   // TODO FLIPRGOFF
11088    /* 0x83 */ undefined,
11089    /* 0x84 */ undefined,
11090    /* 0x85 */ SCANCTRL,
11091    /* 0x86 */ SDPVTL.bind(undefined, 0),
11092    /* 0x87 */ SDPVTL.bind(undefined, 1),
11093    /* 0x88 */ GETINFO,
11094    /* 0x89 */ undefined,   // TODO IDEF
11095    /* 0x8A */ ROLL,
11096    /* 0x8B */ MAX,
11097    /* 0x8C */ MIN,
11098    /* 0x8D */ SCANTYPE,
11099    /* 0x8E */ INSTCTRL,
11100    /* 0x8F */ undefined,
11101    /* 0x90 */ undefined,
11102    /* 0x91 */ undefined,
11103    /* 0x92 */ undefined,
11104    /* 0x93 */ undefined,
11105    /* 0x94 */ undefined,
11106    /* 0x95 */ undefined,
11107    /* 0x96 */ undefined,
11108    /* 0x97 */ undefined,
11109    /* 0x98 */ undefined,
11110    /* 0x99 */ undefined,
11111    /* 0x9A */ undefined,
11112    /* 0x9B */ undefined,
11113    /* 0x9C */ undefined,
11114    /* 0x9D */ undefined,
11115    /* 0x9E */ undefined,
11116    /* 0x9F */ undefined,
11117    /* 0xA0 */ undefined,
11118    /* 0xA1 */ undefined,
11119    /* 0xA2 */ undefined,
11120    /* 0xA3 */ undefined,
11121    /* 0xA4 */ undefined,
11122    /* 0xA5 */ undefined,
11123    /* 0xA6 */ undefined,
11124    /* 0xA7 */ undefined,
11125    /* 0xA8 */ undefined,
11126    /* 0xA9 */ undefined,
11127    /* 0xAA */ undefined,
11128    /* 0xAB */ undefined,
11129    /* 0xAC */ undefined,
11130    /* 0xAD */ undefined,
11131    /* 0xAE */ undefined,
11132    /* 0xAF */ undefined,
11133    /* 0xB0 */ PUSHB.bind(undefined, 1),
11134    /* 0xB1 */ PUSHB.bind(undefined, 2),
11135    /* 0xB2 */ PUSHB.bind(undefined, 3),
11136    /* 0xB3 */ PUSHB.bind(undefined, 4),
11137    /* 0xB4 */ PUSHB.bind(undefined, 5),
11138    /* 0xB5 */ PUSHB.bind(undefined, 6),
11139    /* 0xB6 */ PUSHB.bind(undefined, 7),
11140    /* 0xB7 */ PUSHB.bind(undefined, 8),
11141    /* 0xB8 */ PUSHW.bind(undefined, 1),
11142    /* 0xB9 */ PUSHW.bind(undefined, 2),
11143    /* 0xBA */ PUSHW.bind(undefined, 3),
11144    /* 0xBB */ PUSHW.bind(undefined, 4),
11145    /* 0xBC */ PUSHW.bind(undefined, 5),
11146    /* 0xBD */ PUSHW.bind(undefined, 6),
11147    /* 0xBE */ PUSHW.bind(undefined, 7),
11148    /* 0xBF */ PUSHW.bind(undefined, 8),
11149    /* 0xC0 */ MDRP_MIRP.bind(undefined, 0, 0, 0, 0, 0),
11150    /* 0xC1 */ MDRP_MIRP.bind(undefined, 0, 0, 0, 0, 1),
11151    /* 0xC2 */ MDRP_MIRP.bind(undefined, 0, 0, 0, 0, 2),
11152    /* 0xC3 */ MDRP_MIRP.bind(undefined, 0, 0, 0, 0, 3),
11153    /* 0xC4 */ MDRP_MIRP.bind(undefined, 0, 0, 0, 1, 0),
11154    /* 0xC5 */ MDRP_MIRP.bind(undefined, 0, 0, 0, 1, 1),
11155    /* 0xC6 */ MDRP_MIRP.bind(undefined, 0, 0, 0, 1, 2),
11156    /* 0xC7 */ MDRP_MIRP.bind(undefined, 0, 0, 0, 1, 3),
11157    /* 0xC8 */ MDRP_MIRP.bind(undefined, 0, 0, 1, 0, 0),
11158    /* 0xC9 */ MDRP_MIRP.bind(undefined, 0, 0, 1, 0, 1),
11159    /* 0xCA */ MDRP_MIRP.bind(undefined, 0, 0, 1, 0, 2),
11160    /* 0xCB */ MDRP_MIRP.bind(undefined, 0, 0, 1, 0, 3),
11161    /* 0xCC */ MDRP_MIRP.bind(undefined, 0, 0, 1, 1, 0),
11162    /* 0xCD */ MDRP_MIRP.bind(undefined, 0, 0, 1, 1, 1),
11163    /* 0xCE */ MDRP_MIRP.bind(undefined, 0, 0, 1, 1, 2),
11164    /* 0xCF */ MDRP_MIRP.bind(undefined, 0, 0, 1, 1, 3),
11165    /* 0xD0 */ MDRP_MIRP.bind(undefined, 0, 1, 0, 0, 0),
11166    /* 0xD1 */ MDRP_MIRP.bind(undefined, 0, 1, 0, 0, 1),
11167    /* 0xD2 */ MDRP_MIRP.bind(undefined, 0, 1, 0, 0, 2),
11168    /* 0xD3 */ MDRP_MIRP.bind(undefined, 0, 1, 0, 0, 3),
11169    /* 0xD4 */ MDRP_MIRP.bind(undefined, 0, 1, 0, 1, 0),
11170    /* 0xD5 */ MDRP_MIRP.bind(undefined, 0, 1, 0, 1, 1),
11171    /* 0xD6 */ MDRP_MIRP.bind(undefined, 0, 1, 0, 1, 2),
11172    /* 0xD7 */ MDRP_MIRP.bind(undefined, 0, 1, 0, 1, 3),
11173    /* 0xD8 */ MDRP_MIRP.bind(undefined, 0, 1, 1, 0, 0),
11174    /* 0xD9 */ MDRP_MIRP.bind(undefined, 0, 1, 1, 0, 1),
11175    /* 0xDA */ MDRP_MIRP.bind(undefined, 0, 1, 1, 0, 2),
11176    /* 0xDB */ MDRP_MIRP.bind(undefined, 0, 1, 1, 0, 3),
11177    /* 0xDC */ MDRP_MIRP.bind(undefined, 0, 1, 1, 1, 0),
11178    /* 0xDD */ MDRP_MIRP.bind(undefined, 0, 1, 1, 1, 1),
11179    /* 0xDE */ MDRP_MIRP.bind(undefined, 0, 1, 1, 1, 2),
11180    /* 0xDF */ MDRP_MIRP.bind(undefined, 0, 1, 1, 1, 3),
11181    /* 0xE0 */ MDRP_MIRP.bind(undefined, 1, 0, 0, 0, 0),
11182    /* 0xE1 */ MDRP_MIRP.bind(undefined, 1, 0, 0, 0, 1),
11183    /* 0xE2 */ MDRP_MIRP.bind(undefined, 1, 0, 0, 0, 2),
11184    /* 0xE3 */ MDRP_MIRP.bind(undefined, 1, 0, 0, 0, 3),
11185    /* 0xE4 */ MDRP_MIRP.bind(undefined, 1, 0, 0, 1, 0),
11186    /* 0xE5 */ MDRP_MIRP.bind(undefined, 1, 0, 0, 1, 1),
11187    /* 0xE6 */ MDRP_MIRP.bind(undefined, 1, 0, 0, 1, 2),
11188    /* 0xE7 */ MDRP_MIRP.bind(undefined, 1, 0, 0, 1, 3),
11189    /* 0xE8 */ MDRP_MIRP.bind(undefined, 1, 0, 1, 0, 0),
11190    /* 0xE9 */ MDRP_MIRP.bind(undefined, 1, 0, 1, 0, 1),
11191    /* 0xEA */ MDRP_MIRP.bind(undefined, 1, 0, 1, 0, 2),
11192    /* 0xEB */ MDRP_MIRP.bind(undefined, 1, 0, 1, 0, 3),
11193    /* 0xEC */ MDRP_MIRP.bind(undefined, 1, 0, 1, 1, 0),
11194    /* 0xED */ MDRP_MIRP.bind(undefined, 1, 0, 1, 1, 1),
11195    /* 0xEE */ MDRP_MIRP.bind(undefined, 1, 0, 1, 1, 2),
11196    /* 0xEF */ MDRP_MIRP.bind(undefined, 1, 0, 1, 1, 3),
11197    /* 0xF0 */ MDRP_MIRP.bind(undefined, 1, 1, 0, 0, 0),
11198    /* 0xF1 */ MDRP_MIRP.bind(undefined, 1, 1, 0, 0, 1),
11199    /* 0xF2 */ MDRP_MIRP.bind(undefined, 1, 1, 0, 0, 2),
11200    /* 0xF3 */ MDRP_MIRP.bind(undefined, 1, 1, 0, 0, 3),
11201    /* 0xF4 */ MDRP_MIRP.bind(undefined, 1, 1, 0, 1, 0),
11202    /* 0xF5 */ MDRP_MIRP.bind(undefined, 1, 1, 0, 1, 1),
11203    /* 0xF6 */ MDRP_MIRP.bind(undefined, 1, 1, 0, 1, 2),
11204    /* 0xF7 */ MDRP_MIRP.bind(undefined, 1, 1, 0, 1, 3),
11205    /* 0xF8 */ MDRP_MIRP.bind(undefined, 1, 1, 1, 0, 0),
11206    /* 0xF9 */ MDRP_MIRP.bind(undefined, 1, 1, 1, 0, 1),
11207    /* 0xFA */ MDRP_MIRP.bind(undefined, 1, 1, 1, 0, 2),
11208    /* 0xFB */ MDRP_MIRP.bind(undefined, 1, 1, 1, 0, 3),
11209    /* 0xFC */ MDRP_MIRP.bind(undefined, 1, 1, 1, 1, 0),
11210    /* 0xFD */ MDRP_MIRP.bind(undefined, 1, 1, 1, 1, 1),
11211    /* 0xFE */ MDRP_MIRP.bind(undefined, 1, 1, 1, 1, 2),
11212    /* 0xFF */ MDRP_MIRP.bind(undefined, 1, 1, 1, 1, 3)
11213];
11214
11215/*****************************
11216  Mathematical Considerations
11217******************************
11218
11219fv ... refers to freedom vector
11220pv ... refers to projection vector
11221rp ... refers to reference point
11222p  ... refers to to point being operated on
11223d  ... refers to distance
11224
11225SETRELATIVE:
11226============
11227
11228case freedom vector == x-axis:
11229------------------------------
11230
11231                        (pv)
11232                     .-'
11233              rpd .-'
11234               .-*
11235          d .-'90°'
11236         .-'       '
11237      .-'           '
11238   *-'               ' b
11239  rp                  '
11240                       '
11241                        '
11242            p *----------*-------------- (fv)
11243                          pm
11244
11245  rpdx = rpx + d * pv.x
11246  rpdy = rpy + d * pv.y
11247
11248  equation of line b
11249
11250   y - rpdy = pvns * (x- rpdx)
11251
11252   y = p.y
11253
11254   x = rpdx + ( p.y - rpdy ) / pvns
11255
11256
11257case freedom vector == y-axis:
11258------------------------------
11259
11260    * pm
11261    |\
11262    | \
11263    |  \
11264    |   \
11265    |    \
11266    |     \
11267    |      \
11268    |       \
11269    |        \
11270    |         \ b
11271    |          \
11272    |           \
11273    |            \    .-' (pv)
11274    |         90° \.-'
11275    |           .-'* rpd
11276    |        .-'
11277    *     *-'  d
11278    p     rp
11279
11280  rpdx = rpx + d * pv.x
11281  rpdy = rpy + d * pv.y
11282
11283  equation of line b:
11284           pvns ... normal slope to pv
11285
11286   y - rpdy = pvns * (x - rpdx)
11287
11288   x = p.x
11289
11290   y = rpdy +  pvns * (p.x - rpdx)
11291
11292
11293
11294generic case:
11295-------------
11296
11297
11298                              .'(fv)
11299                            .'
11300                          .* pm
11301                        .' !
11302                      .'    .
11303                    .'      !
11304                  .'         . b
11305                .'           !
11306               *              .
11307              p               !
11308                         90°   .    ... (pv)
11309                           ...-*-'''
11310                  ...---'''    rpd
11311         ...---'''   d
11312   *--'''
11313  rp
11314
11315    rpdx = rpx + d * pv.x
11316    rpdy = rpy + d * pv.y
11317
11318 equation of line b:
11319    pvns... normal slope to pv
11320
11321    y - rpdy = pvns * (x - rpdx)
11322
11323 equation of freedom vector line:
11324    fvs ... slope of freedom vector (=fy/fx)
11325
11326    y - py = fvs * (x - px)
11327
11328
11329  on pm both equations are true for same x/y
11330
11331    y - rpdy = pvns * (x - rpdx)
11332
11333    y - py = fvs * (x - px)
11334
11335  form to y and set equal:
11336
11337    pvns * (x - rpdx) + rpdy = fvs * (x - px) + py
11338
11339  expand:
11340
11341    pvns * x - pvns * rpdx + rpdy = fvs * x - fvs * px + py
11342
11343  switch:
11344
11345    fvs * x - fvs * px + py = pvns * x - pvns * rpdx + rpdy
11346
11347  solve for x:
11348
11349    fvs * x - pvns * x = fvs * px - pvns * rpdx - py + rpdy
11350
11351
11352
11353          fvs * px - pvns * rpdx + rpdy - py
11354    x =  -----------------------------------
11355                 fvs - pvns
11356
11357  and:
11358
11359    y = fvs * (x - px) + py
11360
11361
11362
11363INTERPOLATE:
11364============
11365
11366Examples of point interpolation.
11367
11368The weight of the movement of the reference point gets bigger
11369the further the other reference point is away, thus the safest
11370option (that is avoiding 0/0 divisions) is to weight the
11371original distance of the other point by the sum of both distances.
11372
11373If the sum of both distances is 0, then move the point by the
11374arithmetic average of the movement of both reference points.
11375
11376
11377
11378
11379           (+6)
11380    rp1o *---->*rp1
11381         .     .                          (+12)
11382         .     .                  rp2o *---------->* rp2
11383         .     .                       .           .
11384         .     .                       .           .
11385         .    10          20           .           .
11386         |.........|...................|           .
11387               .   .                               .
11388               .   . (+8)                          .
11389                po *------>*p                      .
11390               .           .                       .
11391               .    12     .          24           .
11392               |...........|.......................|
11393                                  36
11394
11395
11396-------
11397
11398
11399
11400           (+10)
11401    rp1o *-------->*rp1
11402         .         .                      (-10)
11403         .         .              rp2 *<---------* rpo2
11404         .         .                   .         .
11405         .         .                   .         .
11406         .    10   .          30       .         .
11407         |.........|.............................|
11408                   .                   .
11409                   . (+5)              .
11410                po *--->* p            .
11411                   .    .              .
11412                   .    .   20         .
11413                   |....|..............|
11414                     5        15
11415
11416
11417-------
11418
11419
11420           (+10)
11421    rp1o *-------->*rp1
11422         .         .
11423         .         .
11424    rp2o *-------->*rp2
11425
11426
11427                               (+10)
11428                          po *-------->* p
11429
11430-------
11431
11432
11433           (+10)
11434    rp1o *-------->*rp1
11435         .         .
11436         .         .(+30)
11437    rp2o *---------------------------->*rp2
11438
11439
11440                                        (+25)
11441                          po *----------------------->* p
11442
11443
11444
11445vim: set ts=4 sw=4 expandtab:
11446*****/
11447
11448/**
11449 * Converts a string into a list of tokens.
11450 */
11451
11452/**
11453 * Create a new token
11454 * @param {string} char a single char
11455 */
11456function Token(char) {
11457    this.char = char;
11458    this.state = {};
11459    this.activeState = null;
11460}
11461
11462/**
11463 * Create a new context range
11464 * @param {number} startIndex range start index
11465 * @param {number} endOffset range end index offset
11466 * @param {string} contextName owner context name
11467 */
11468function ContextRange(startIndex, endOffset, contextName) {
11469    this.contextName = contextName;
11470    this.startIndex = startIndex;
11471    this.endOffset = endOffset;
11472}
11473
11474/**
11475 * Check context start and end
11476 * @param {string} contextName a unique context name
11477 * @param {function} checkStart a predicate function the indicates a context's start
11478 * @param {function} checkEnd a predicate function the indicates a context's end
11479 */
11480function ContextChecker(contextName, checkStart, checkEnd) {
11481    this.contextName = contextName;
11482    this.openRange = null;
11483    this.ranges = [];
11484    this.checkStart = checkStart;
11485    this.checkEnd = checkEnd;
11486}
11487
11488/**
11489 * @typedef ContextParams
11490 * @type Object
11491 * @property {array} context context items
11492 * @property {number} currentIndex current item index
11493 */
11494
11495/**
11496 * Create a context params
11497 * @param {array} context a list of items
11498 * @param {number} currentIndex current item index
11499 */
11500function ContextParams(context, currentIndex) {
11501    this.context = context;
11502    this.index = currentIndex;
11503    this.length = context.length;
11504    this.current = context[currentIndex];
11505    this.backtrack = context.slice(0, currentIndex);
11506    this.lookahead = context.slice(currentIndex + 1);
11507}
11508
11509/**
11510 * Create an event instance
11511 * @param {string} eventId event unique id
11512 */
11513function Event(eventId) {
11514    this.eventId = eventId;
11515    this.subscribers = [];
11516}
11517
11518/**
11519 * Initialize a core events and auto subscribe required event handlers
11520 * @param {any} events an object that enlists core events handlers
11521 */
11522function initializeCoreEvents(events) {
11523    var this$1 = this;
11524
11525    var coreEvents = [
11526        'start', 'end', 'next', 'newToken', 'contextStart',
11527        'contextEnd', 'insertToken', 'removeToken', 'removeRange',
11528        'replaceToken', 'replaceRange', 'composeRUD', 'updateContextsRanges'
11529    ];
11530
vendor: 4,943 bytes, lines 11531-11678
11531    coreEvents.forEach(function (eventId) {
11532        Object.defineProperty(this$1.events, eventId, {
11533            value: new Event(eventId)
11534        });
11535    });
11536
11537    if (!!events) {
11538        coreEvents.forEach(function (eventId) {
11539            var event = events[eventId];
11540            if (typeof event === 'function') {
11541                this$1.events[eventId].subscribe(event);
11542            }
11543        });
11544    }
11545    var requiresContextUpdate = [
11546        'insertToken', 'removeToken', 'removeRange',
11547        'replaceToken', 'replaceRange', 'composeRUD'
11548    ];
11549    requiresContextUpdate.forEach(function (eventId) {
11550        this$1.events[eventId].subscribe(
11551            this$1.updateContextsRanges
11552        );
11553    });
11554}
11555
11556/**
11557 * Converts a string into a list of tokens
11558 * @param {any} events tokenizer core events
11559 */
11560function Tokenizer(events) {
11561    this.tokens = [];
11562    this.registeredContexts = {};
11563    this.contextCheckers = [];
11564    this.events = {};
11565    this.registeredModifiers = [];
11566
11567    initializeCoreEvents.call(this, events);
11568}
11569
11570/**
11571 * Sets the state of a token, usually called by a state modifier.
11572 * @param {string} key state item key
11573 * @param {any} value state item value
11574 */
11575Token.prototype.setState = function(key, value) {
11576    this.state[key] = value;
11577    this.activeState = { key: key, value: this.state[key] };
11578    return this.activeState;
11579};
11580
11581Token.prototype.getState = function (stateId) {
11582    return this.state[stateId] || null;
11583};
11584
11585/**
11586 * Checks if an index exists in the tokens list.
11587 * @param {number} index token index
11588 */
11589Tokenizer.prototype.inboundIndex = function(index) {
11590    return index >= 0 && index < this.tokens.length;
11591};
11592
11593/**
11594 * Compose and apply a list of operations (replace, update, delete)
11595 * @param {array} RUDs replace, update and delete operations
11596 * TODO: Perf. Optimization (lengthBefore === lengthAfter ? dispatch once)
11597 */
11598Tokenizer.prototype.composeRUD = function (RUDs) {
11599    var this$1 = this;
11600
11601    var silent = true;
11602    var state = RUDs.map(function (RUD) { return (
11603        this$1[RUD[0]].apply(this$1, RUD.slice(1).concat(silent))
11604    ); });
11605    var hasFAILObject = function (obj) { return (
11606        typeof obj === 'object' &&
11607        obj.hasOwnProperty('FAIL')
11608    ); };
11609    if (state.every(hasFAILObject)) {
11610        return {
11611            FAIL: "composeRUD: one or more operations hasn't completed successfully",
11612            report: state.filter(hasFAILObject)
11613        };
11614    }
11615    this.dispatch('composeRUD', [state.filter(function (op) { return !hasFAILObject(op); })]);
11616};
11617
11618/**
11619 * Replace a range of tokens with a list of tokens
11620 * @param {number} startIndex range start index
11621 * @param {number} offset range offset
11622 * @param {token} tokens a list of tokens to replace
11623 * @param {boolean} silent dispatch events and update context ranges
11624 */
11625Tokenizer.prototype.replaceRange = function (startIndex, offset, tokens, silent) {
11626    offset = offset !== null ? offset : this.tokens.length;
11627    var isTokenType = tokens.every(function (token) { return token instanceof Token; });
11628    if (!isNaN(startIndex) && this.inboundIndex(startIndex) && isTokenType) {
11629        var replaced = this.tokens.splice.apply(
11630            this.tokens, [startIndex, offset].concat(tokens)
11631        );
11632        if (!silent) { this.dispatch('replaceToken', [startIndex, offset, tokens]); }
11633        return [replaced, tokens];
11634    } else {
11635        return { FAIL: 'replaceRange: invalid tokens or startIndex.' };
11636    }
11637};
11638
11639/**
11640 * Replace a token with another token
11641 * @param {number} index token index
11642 * @param {token} token a token to replace
11643 * @param {boolean} silent dispatch events and update context ranges
11644 */
11645Tokenizer.prototype.replaceToken = function (index, token, silent) {
11646    if (!isNaN(index) && this.inboundIndex(index) && token instanceof Token) {
11647        var replaced = this.tokens.splice(index, 1, token);
11648        if (!silent) { this.dispatch('replaceToken', [index, token]); }
11649        return [replaced[0], token];
11650    } else {
11651        return { FAIL: 'replaceToken: invalid token or index.' };
11652    }
11653};
11654
11655/**
11656 * Removes a range of tokens
11657 * @param {number} startIndex range start index
11658 * @param {number} offset range offset
11659 * @param {boolean} silent dispatch events and update context ranges
11660 */
11661Tokenizer.prototype.removeRange = function(startIndex, offset, silent) {
11662    offset = !isNaN(offset) ? offset : this.tokens.length;
11663    var tokens = this.tokens.splice(startIndex, offset);
11664    if (!silent) { this.dispatch('removeRange', [tokens, startIndex, offset]); }
11665    return tokens;
11666};
11667
11668/**
11669 * Remove a token at a certain index
11670 * @param {number} index token index
11671 * @param {boolean} silent dispatch events and update context ranges
11672 */
11673Tokenizer.prototype.removeToken = function(index, silent) {
11674    if (!isNaN(index) && this.inboundIndex(index)) {
11675        var token = this.tokens.splice(index, 1);
11676        if (!silent) { this.dispatch('removeToken', [token, index]); }
11677        return token;
11678    } else {
11679        return { FAIL: 'removeToken: invalid token index.' };
11680    }
11681};
11682
11683/**
11684 * Insert a list of tokens at a certain index
11685 * @param {array} tokens a list of tokens to insert
11686 * @param {number} index insert the list of tokens at index
11687 * @param {boolean} silent dispatch events and update context ranges
11688 */
11689Tokenizer.prototype.insertToken = function (tokens, index, silent) {
11690    var tokenType = tokens.every(
11691        function (token) { return token instanceof Token; }
11692    );
11693    if (tokenType) {
11694        this.tokens.splice.apply(
11695            this.tokens, [index, 0].concat(tokens)
11696        );
11697        if (!silent) { this.dispatch('insertToken', [tokens, index]); }
11698        return tokens;
11699    } else {
11700        return { FAIL: 'insertToken: invalid token(s).' };
11701    }
11702};
11703
11704/**
11705 * A state modifier that is called on 'newToken' event
11706 * @param {string} modifierId state modifier id
11707 * @param {function} condition a predicate function that returns true or false
11708 * @param {function} modifier a function to update token state
11709 */
11710Tokenizer.prototype.registerModifier = function(modifierId, condition, modifier) {
11711    this.events.newToken.subscribe(function(token, contextParams) {
11712        var conditionParams = [token, contextParams];
11713        var canApplyModifier = (
11714            condition === null ||
11715            condition.apply(this, conditionParams) === true
11716        );
11717        var modifierParams = [token, contextParams];
11718        if (canApplyModifier) {
11719            var newStateValue = modifier.apply(this, modifierParams);
11720            token.setState(modifierId, newStateValue);
11721        }
11722    });
11723    this.registeredModifiers.push(modifierId);
11724};
11725
11726/**
11727 * Subscribe a handler to an event
11728 * @param {function} eventHandler an event handler function
11729 */
11730Event.prototype.subscribe = function (eventHandler) {
11731    if (typeof eventHandler === 'function') {
11732        return ((this.subscribers.push(eventHandler)) - 1);
11733    } else {
11734        return { FAIL: ("invalid '" + (this.eventId) + "' event handler")};
11735    }
11736};
11737
11738/**
11739 * Unsubscribe an event handler
11740 * @param {string} subsId subscription id
11741 */
11742Event.prototype.unsubscribe = function (subsId) {
11743    this.subscribers.splice(subsId, 1);
11744};
11745
11746/**
11747 * Sets context params current value index
11748 * @param {number} index context params current value index
11749 */
11750ContextParams.prototype.setCurrentIndex = function(index) {
11751    this.index = index;
11752    this.current = this.context[index];
11753    this.backtrack = this.context.slice(0, index);
11754    this.lookahead = this.context.slice(index + 1);
11755};
11756
11757/**
11758 * Get an item at an offset from the current value
11759 * example (current value is 3):
11760 *  1    2   [3]   4    5   |   items values
11761 * -2   -1    0    1    2   |   offset values
11762 * @param {number} offset an offset from current value index
11763 */
11764ContextParams.prototype.get = function (offset) {
11765    switch (true) {
11766        case (offset === 0):
11767            return this.current;
11768        case (offset < 0 && Math.abs(offset) <= this.backtrack.length):
11769            return this.backtrack.slice(offset)[0];
11770        case (offset > 0 && offset <= this.lookahead.length):
11771            return this.lookahead[offset - 1];
11772        default:
11773            return null;
11774    }
11775};
11776
11777/**
11778 * Converts a context range into a string value
11779 * @param {contextRange} range a context range
11780 */
11781Tokenizer.prototype.rangeToText = function (range) {
11782    if (range instanceof ContextRange) {
11783        return (
11784            this.getRangeTokens(range)
11785                .map(function (token) { return token.char; }).join('')
11786        );
11787    }
11788};
11789
11790/**
11791 * Converts all tokens into a string
11792 */
11793Tokenizer.prototype.getText = function () {
11794    return this.tokens.map(function (token) { return token.char; }).join('');
11795};
11796
11797/**
11798 * Get a context by name
11799 * @param {string} contextName context name to get
11800 */
11801Tokenizer.prototype.getContext = function (contextName) {
11802    var context = this.registeredContexts[contextName];
11803    return !!context ? context : null;
11804};
11805
11806/**
11807 * Subscribes a new event handler to an event
11808 * @param {string} eventName event name to subscribe to
11809 * @param {function} eventHandler a function to be invoked on event
11810 */
11811Tokenizer.prototype.on = function(eventName, eventHandler) {
11812    var event = this.events[eventName];
11813    if (!!event) {
11814        return event.subscribe(eventHandler);
11815    } else {
11816        return null;
11817    }
11818};
11819
11820/**
11821 * Dispatches an event
11822 * @param {string} eventName event name
11823 * @param {any} args event handler arguments
11824 */
11825Tokenizer.prototype.dispatch = function(eventName, args) {
11826    var this$1 = this;
11827
11828    var event = this.events[eventName];
11829    if (event instanceof Event) {
vendor: 16,866 bytes, lines 11830-12312
11830        event.subscribers.forEach(function (subscriber) {
11831            subscriber.apply(this$1, args || []);
11832        });
11833    }
11834};
11835
11836/**
11837 * Register a new context checker
11838 * @param {string} contextName a unique context name
11839 * @param {function} contextStartCheck a predicate function that returns true on context start
11840 * @param {function} contextEndCheck  a predicate function that returns true on context end
11841 * TODO: call tokenize on registration to update context ranges with the new context.
11842 */
11843Tokenizer.prototype.registerContextChecker = function(contextName, contextStartCheck, contextEndCheck) {
11844    if (!!this.getContext(contextName)) { return {
11845        FAIL:
11846        ("context name '" + contextName + "' is already registered.")
11847    }; }
11848    if (typeof contextStartCheck !== 'function') { return {
11849        FAIL:
11850        "missing context start check."
11851    }; }
11852    if (typeof contextEndCheck !== 'function') { return {
11853        FAIL:
11854        "missing context end check."
11855    }; }
11856    var contextCheckers = new ContextChecker(
11857        contextName, contextStartCheck, contextEndCheck
11858    );
11859    this.registeredContexts[contextName] = contextCheckers;
11860    this.contextCheckers.push(contextCheckers);
11861    return contextCheckers;
11862};
11863
11864/**
11865 * Gets a context range tokens
11866 * @param {contextRange} range a context range
11867 */
11868Tokenizer.prototype.getRangeTokens = function(range) {
11869    var endIndex = range.startIndex + range.endOffset;
11870    return [].concat(
11871        this.tokens
11872            .slice(range.startIndex, endIndex)
11873    );
11874};
11875
11876/**
11877 * Gets the ranges of a context
11878 * @param {string} contextName context name
11879 */
11880Tokenizer.prototype.getContextRanges = function(contextName) {
11881    var context = this.getContext(contextName);
11882    if (!!context) {
11883        return context.ranges;
11884    } else {
11885        return { FAIL: ("context checker '" + contextName + "' is not registered.") };
11886    }
11887};
11888
11889/**
11890 * Resets context ranges to run context update
11891 */
11892Tokenizer.prototype.resetContextsRanges = function () {
11893    var registeredContexts = this.registeredContexts;
11894    for (var contextName in registeredContexts) {
11895        if (registeredContexts.hasOwnProperty(contextName)) {
11896            var context = registeredContexts[contextName];
11897            context.ranges = [];
11898        }
11899    }
11900};
11901
11902/**
11903 * Updates context ranges
11904 */
11905Tokenizer.prototype.updateContextsRanges = function () {
11906    this.resetContextsRanges();
11907    var chars = this.tokens.map(function (token) { return token.char; });
11908    for (var i = 0; i < chars.length; i++) {
11909        var contextParams = new ContextParams(chars, i);
11910        this.runContextCheck(contextParams);
11911    }
11912    this.dispatch('updateContextsRanges', [this.registeredContexts]);
11913};
11914
11915/**
11916 * Sets the end offset of an open range
11917 * @param {number} offset range end offset
11918 * @param {string} contextName context name
11919 */
11920Tokenizer.prototype.setEndOffset = function (offset, contextName) {
11921    var startIndex = this.getContext(contextName).openRange.startIndex;
11922    var range = new ContextRange(startIndex, offset, contextName);
11923    var ranges = this.getContext(contextName).ranges;
11924    range.rangeId = contextName + "." + (ranges.length);
11925    ranges.push(range);
11926    this.getContext(contextName).openRange = null;
11927    return range;
11928};
11929
11930/**
11931 * Runs a context check on the current context
11932 * @param {contextParams} contextParams current context params
11933 */
11934Tokenizer.prototype.runContextCheck = function(contextParams) {
11935    var this$1 = this;
11936
11937    var index = contextParams.index;
11938    this.contextCheckers.forEach(function (contextChecker) {
11939        var contextName = contextChecker.contextName;
11940        var openRange = this$1.getContext(contextName).openRange;
11941        if (!openRange && contextChecker.checkStart(contextParams)) {
11942            openRange = new ContextRange(index, null, contextName);
11943            this$1.getContext(contextName).openRange = openRange;
11944            this$1.dispatch('contextStart', [contextName, index]);
11945        }
11946        if (!!openRange && contextChecker.checkEnd(contextParams)) {
11947            var offset = (index - openRange.startIndex) + 1;
11948            var range = this$1.setEndOffset(offset, contextName);
11949            this$1.dispatch('contextEnd', [contextName, range]);
11950        }
11951    });
11952};
11953
11954/**
11955 * Converts a text into a list of tokens
11956 * @param {string} text a text to tokenize
11957 */
11958Tokenizer.prototype.tokenize = function (text) {
11959    this.tokens = [];
11960    this.resetContextsRanges();
11961    var chars = Array.from(text);
11962    this.dispatch('start');
11963    for (var i = 0; i < chars.length; i++) {
11964        var char = chars[i];
11965        var contextParams = new ContextParams(chars, i);
11966        this.dispatch('next', [contextParams]);
11967        this.runContextCheck(contextParams);
11968        var token = new Token(char);
11969        this.tokens.push(token);
11970        this.dispatch('newToken', [token, contextParams]);
11971    }
11972    this.dispatch('end', [this.tokens]);
11973    return this.tokens;
11974};
11975
11976// ╭─┄┄┄────────────────────────┄─────────────────────────────────────────────╮
11977// ┊ Character Class Assertions ┊ Checks if a char belongs to a certain class ┊
11978// ╰─╾──────────────────────────┄─────────────────────────────────────────────╯
11979// jscs:disable maximumLineLength
11980/**
11981 * Check if a char is Arabic
11982 * @param {string} c a single char
11983 */
11984function isArabicChar(c) {
11985    return /[\u0600-\u065F\u066A-\u06D2\u06FA-\u06FF]/.test(c);
11986}
11987
11988/**
11989 * Check if a char is an isolated arabic char
11990 * @param {string} c a single char
11991 */
11992function isIsolatedArabicChar(char) {
11993    return /[\u0630\u0690\u0621\u0631\u0661\u0671\u0622\u0632\u0672\u0692\u06C2\u0623\u0673\u0693\u06C3\u0624\u0694\u06C4\u0625\u0675\u0695\u06C5\u06E5\u0676\u0696\u06C6\u0627\u0677\u0697\u06C7\u0648\u0688\u0698\u06C8\u0689\u0699\u06C9\u068A\u06CA\u066B\u068B\u06CB\u068C\u068D\u06CD\u06FD\u068E\u06EE\u06FE\u062F\u068F\u06CF\u06EF]/.test(char);
11994}
11995
11996/**
11997 * Check if a char is an Arabic Tashkeel char
11998 * @param {string} c a single char
11999 */
12000function isTashkeelArabicChar(char) {
12001    return /[\u0600-\u0605\u060C-\u060E\u0610-\u061B\u061E\u064B-\u065F\u0670\u06D6-\u06DC\u06DF-\u06E4\u06E7\u06E8\u06EA-\u06ED]/.test(char);
12002}
12003
12004/**
12005 * Check if a char is Latin
12006 * @param {string} c a single char
12007 */
12008function isLatinChar(c) {
12009    return /[A-z]/.test(c);
12010}
12011
12012/**
12013 * Check if a char is whitespace char
12014 * @param {string} c a single char
12015 */
12016function isWhiteSpace(c) {
12017    return /\s/.test(c);
12018}
12019
12020/**
12021 * Query a feature by some of it's properties to lookup a glyph substitution.
12022 */
12023
12024/**
12025 * Create feature query instance
12026 * @param {Font} font opentype font instance
12027 */
12028function FeatureQuery(font) {
12029    this.font = font;
12030    this.features = {};
12031}
12032
12033/**
12034 * @typedef SubstitutionAction
12035 * @type Object
12036 * @property {number} id substitution type
12037 * @property {string} tag feature tag
12038 * @property {any} substitution substitution value(s)
12039 */
12040
12041/**
12042 * Create a substitution action instance
12043 * @param {SubstitutionAction} action
12044 */
12045function SubstitutionAction(action) {
12046    this.id = action.id;
12047    this.tag = action.tag;
12048    this.substitution = action.substitution;
12049}
12050
12051/**
12052 * Lookup a coverage table
12053 * @param {number} glyphIndex glyph index
12054 * @param {CoverageTable} coverage coverage table
12055 */
12056function lookupCoverage(glyphIndex, coverage) {
12057    if (!glyphIndex) { return -1; }
12058    switch (coverage.format) {
12059        case 1:
12060            return coverage.glyphs.indexOf(glyphIndex);
12061
12062        case 2:
12063            var ranges = coverage.ranges;
12064            for (var i = 0; i < ranges.length; i++) {
12065                var range = ranges[i];
12066                if (glyphIndex >= range.start && glyphIndex <= range.end) {
12067                    var offset = glyphIndex - range.start;
12068                    return range.index + offset;
12069                }
12070            }
12071            break;
12072        default:
12073            return -1; // not found
12074    }
12075    return -1;
12076}
12077
12078/**
12079 * Handle a single substitution - format 1
12080 * @param {ContextParams} contextParams context params to lookup
12081 */
12082function singleSubstitutionFormat1(glyphIndex, subtable) {
12083    var substituteIndex = lookupCoverage(glyphIndex, subtable.coverage);
12084    if (substituteIndex === -1) { return null; }
12085    return glyphIndex + subtable.deltaGlyphId;
12086}
12087
12088/**
12089 * Handle a single substitution - format 2
12090 * @param {ContextParams} contextParams context params to lookup
12091 */
12092function singleSubstitutionFormat2(glyphIndex, subtable) {
12093    var substituteIndex = lookupCoverage(glyphIndex, subtable.coverage);
12094    if (substituteIndex === -1) { return null; }
12095    return subtable.substitute[substituteIndex];
12096}
12097
12098/**
12099 * Lookup a list of coverage tables
12100 * @param {any} coverageList a list of coverage tables
12101 * @param {ContextParams} contextParams context params to lookup
12102 */
12103function lookupCoverageList(coverageList, contextParams) {
12104    var lookupList = [];
12105    for (var i = 0; i < coverageList.length; i++) {
12106        var coverage = coverageList[i];
12107        var glyphIndex = contextParams.current;
12108        glyphIndex = Array.isArray(glyphIndex) ? glyphIndex[0] : glyphIndex;
12109        var lookupIndex = lookupCoverage(glyphIndex, coverage);
12110        if (lookupIndex !== -1) {
12111            lookupList.push(lookupIndex);
12112        }
12113    }
12114    if (lookupList.length !== coverageList.length) { return -1; }
12115    return lookupList;
12116}
12117
12118/**
12119 * Handle chaining context substitution - format 3
12120 * @param {ContextParams} contextParams context params to lookup
12121 */
12122function chainingSubstitutionFormat3(contextParams, subtable) {
12123    var lookupsCount = (
12124        subtable.inputCoverage.length +
12125        subtable.lookaheadCoverage.length +
12126        subtable.backtrackCoverage.length
12127    );
12128    if (contextParams.context.length < lookupsCount) { return []; }
12129    // INPUT LOOKUP //
12130    var inputLookups = lookupCoverageList(
12131        subtable.inputCoverage, contextParams
12132    );
12133    if (inputLookups === -1) { return []; }
12134    // LOOKAHEAD LOOKUP //
12135    var lookaheadOffset = subtable.inputCoverage.length - 1;
12136    if (contextParams.lookahead.length < subtable.lookaheadCoverage.length) { return []; }
12137    var lookaheadContext = contextParams.lookahead.slice(lookaheadOffset);
12138    while (lookaheadContext.length && isTashkeelArabicChar(lookaheadContext[0].char)) {
12139        lookaheadContext.shift();
12140    }
12141    var lookaheadParams = new ContextParams(lookaheadContext, 0);
12142    var lookaheadLookups = lookupCoverageList(
12143        subtable.lookaheadCoverage, lookaheadParams
12144    );
12145    // BACKTRACK LOOKUP //
12146    var backtrackContext = [].concat(contextParams.backtrack);
12147    backtrackContext.reverse();
12148    while (backtrackContext.length && isTashkeelArabicChar(backtrackContext[0].char)) {
12149        backtrackContext.shift();
12150    }
12151    if (backtrackContext.length < subtable.backtrackCoverage.length) { return []; }
12152    var backtrackParams = new ContextParams(backtrackContext, 0);
12153    var backtrackLookups = lookupCoverageList(
12154        subtable.backtrackCoverage, backtrackParams
12155    );
12156    var contextRulesMatch = (
12157        inputLookups.length === subtable.inputCoverage.length &&
12158        lookaheadLookups.length === subtable.lookaheadCoverage.length &&
12159        backtrackLookups.length === subtable.backtrackCoverage.length
12160    );
12161    var substitutions = [];
12162    if (contextRulesMatch) {
12163        for (var i = 0; i < subtable.lookupRecords.length; i++) {
12164            var lookupRecord = subtable.lookupRecords[i];
12165            var lookupListIndex = lookupRecord.lookupListIndex;
12166            var lookupTable = this.getLookupByIndex(lookupListIndex);
12167            for (var s = 0; s < lookupTable.subtables.length; s++) {
12168                var subtable$1 = lookupTable.subtables[s];
12169                var lookup = this.getLookupMethod(lookupTable, subtable$1);
12170                var substitutionType = this.getSubstitutionType(lookupTable, subtable$1);
12171                if (substitutionType === '12') {
12172                    for (var n = 0; n < inputLookups.length; n++) {
12173                        var glyphIndex = contextParams.get(n);
12174                        var substitution = lookup(glyphIndex);
12175                        if (substitution) { substitutions.push(substitution); }
12176                    }
12177                }
12178            }
12179        }
12180    }
12181    return substitutions;
12182}
12183
12184/**
12185 * Handle ligature substitution - format 1
12186 * @param {ContextParams} contextParams context params to lookup
12187 */
12188function ligatureSubstitutionFormat1(contextParams, subtable) {
12189    // COVERAGE LOOKUP //
12190    var glyphIndex = contextParams.current;
12191    var ligSetIndex = lookupCoverage(glyphIndex, subtable.coverage);
12192    if (ligSetIndex === -1) { return null; }
12193    // COMPONENTS LOOKUP
12194    // (!) note, components are ordered in the written direction.
12195    var ligature;
12196    var ligatureSet = subtable.ligatureSets[ligSetIndex];
12197    for (var s = 0; s < ligatureSet.length; s++) {
12198        ligature = ligatureSet[s];
12199        for (var l = 0; l < ligature.components.length; l++) {
12200            var lookaheadItem = contextParams.lookahead[l];
12201            var component = ligature.components[l];
12202            if (lookaheadItem !== component) { break; }
12203            if (l === ligature.components.length - 1) { return ligature; }
12204        }
12205    }
12206    return null;
12207}
12208
12209/**
12210 * Handle decomposition substitution - format 1
12211 * @param {number} glyphIndex glyph index
12212 * @param {any} subtable subtable
12213 */
12214function decompositionSubstitutionFormat1(glyphIndex, subtable) {
12215    var substituteIndex = lookupCoverage(glyphIndex, subtable.coverage);
12216    if (substituteIndex === -1) { return null; }
12217    return subtable.sequences[substituteIndex];
12218}
12219
12220/**
12221 * Get default script features indexes
12222 */
12223FeatureQuery.prototype.getDefaultScriptFeaturesIndexes = function () {
12224    var scripts = this.font.tables.gsub.scripts;
12225    for (var s = 0; s < scripts.length; s++) {
12226        var script = scripts[s];
12227        if (script.tag === 'DFLT') { return (
12228            script.script.defaultLangSys.featureIndexes
12229        ); }
12230    }
12231    return [];
12232};
12233
12234/**
12235 * Get feature indexes of a specific script
12236 * @param {string} scriptTag script tag
12237 */
12238FeatureQuery.prototype.getScriptFeaturesIndexes = function(scriptTag) {
12239    var tables = this.font.tables;
12240    if (!tables.gsub) { return []; }
12241    if (!scriptTag) { return this.getDefaultScriptFeaturesIndexes(); }
12242    var scripts = this.font.tables.gsub.scripts;
12243    for (var i = 0; i < scripts.length; i++) {
12244        var script = scripts[i];
12245        if (script.tag === scriptTag && script.script.defaultLangSys) {
12246            return script.script.defaultLangSys.featureIndexes;
12247        } else {
12248            var langSysRecords = script.langSysRecords;
12249            if (!!langSysRecords) {
12250                for (var j = 0; j < langSysRecords.length; j++) {
12251                    var langSysRecord = langSysRecords[j];
12252                    if (langSysRecord.tag === scriptTag) {
12253                        var langSys = langSysRecord.langSys;
12254                        return langSys.featureIndexes;
12255                    }
12256                }
12257            }
12258        }
12259    }
12260    return this.getDefaultScriptFeaturesIndexes();
12261};
12262
12263/**
12264 * Map a feature tag to a gsub feature
12265 * @param {any} features gsub features
12266 * @param {string} scriptTag script tag
12267 */
12268FeatureQuery.prototype.mapTagsToFeatures = function (features, scriptTag) {
12269    var tags = {};
12270    for (var i = 0; i < features.length; i++) {
12271        var tag = features[i].tag;
12272        var feature = features[i].feature;
12273        tags[tag] = feature;
12274    }
12275    this.features[scriptTag].tags = tags;
12276};
12277
12278/**
12279 * Get features of a specific script
12280 * @param {string} scriptTag script tag
12281 */
12282FeatureQuery.prototype.getScriptFeatures = function (scriptTag) {
12283    var features = this.features[scriptTag];
12284    if (this.features.hasOwnProperty(scriptTag)) { return features; }
12285    var featuresIndexes = this.getScriptFeaturesIndexes(scriptTag);
12286    if (!featuresIndexes) { return null; }
12287    var gsub = this.font.tables.gsub;
12288    features = featuresIndexes.map(function (index) { return gsub.features[index]; });
12289    this.features[scriptTag] = features;
12290    this.mapTagsToFeatures(features, scriptTag);
12291    return features;
12292};
12293
12294/**
12295 * Get substitution type
12296 * @param {any} lookupTable lookup table
12297 * @param {any} subtable subtable
12298 */
12299FeatureQuery.prototype.getSubstitutionType = function(lookupTable, subtable) {
12300    var lookupType = lookupTable.lookupType.toString();
12301    var substFormat = subtable.substFormat.toString();
12302    return lookupType + substFormat;
12303};
12304
12305/**
12306 * Get lookup method
12307 * @param {any} lookupTable lookup table
12308 * @param {any} subtable subtable
12309 */
12310FeatureQuery.prototype.getLookupMethod = function(lookupTable, subtable) {
12311    var this$1 = this;
12312
12313    var substitutionType = this.getSubstitutionType(lookupTable, subtable);
12314    switch (substitutionType) {
12315        case '11':
12316            return function (glyphIndex) { return singleSubstitutionFormat1.apply(
12317                this$1, [glyphIndex, subtable]
12318            ); };
12319        case '12':
12320            return function (glyphIndex) { return singleSubstitutionFormat2.apply(
12321                this$1, [glyphIndex, subtable]
12322            ); };
12323        case '63':
12324            return function (contextParams) { return chainingSubstitutionFormat3.apply(
12325                this$1, [contextParams, subtable]
12326            ); };
12327        case '41':
12328            return function (contextParams) { return ligatureSubstitutionFormat1.apply(
12329                this$1, [contextParams, subtable]
12330            ); };
12331        case '21':
12332            return function (glyphIndex) { return decompositionSubstitutionFormat1.apply(
12333                this$1, [glyphIndex, subtable]
12334            ); };
12335        default:
12336            throw new Error(
12337                "lookupType: " + (lookupTable.lookupType) + " - " +
12338                "substFormat: " + (subtable.substFormat) + " " +
12339                "is not yet supported"
12340            );
12341    }
12342};
12343
12344/**
12345 * [ LOOKUP TYPES ]
12346 * -------------------------------
12347 * Single                        1;
12348 * Multiple                      2;
12349 * Alternate                     3;
12350 * Ligature                      4;
12351 * Context                       5;
12352 * ChainingContext               6;
12353 * ExtensionSubstitution         7;
12354 * ReverseChainingContext        8;
12355 * -------------------------------
12356 *
12357 */
12358
12359/**
12360 * @typedef FQuery
12361 * @type Object
12362 * @param {string} tag feature tag
12363 * @param {string} script feature script
12364 * @param {ContextParams} contextParams context params
12365 */
12366
12367/**
12368 * Lookup a feature using a query parameters
12369 * @param {FQuery} query feature query
12370 */
12371FeatureQuery.prototype.lookupFeature = function (query) {
12372    var contextParams = query.contextParams;
12373    var currentIndex = contextParams.index;
12374    var feature = this.getFeature({
12375        tag: query.tag, script: query.script
12376    });
12377    if (!feature) { return new Error(
12378        "font '" + (this.font.names.fullName.en) + "' " +
12379        "doesn't support feature '" + (query.tag) + "' " +
12380        "for script '" + (query.script) + "'."
12381    ); }
12382    var lookups = this.getFeatureLookups(feature);
12383    var substitutions = [].concat(contextParams.context);
12384    for (var l = 0; l < lookups.length; l++) {
12385        var lookupTable = lookups[l];
12386        var subtables = this.getLookupSubtables(lookupTable);
12387        for (var s = 0; s < subtables.length; s++) {
12388            var subtable = subtables[s];
12389            var substType = this.getSubstitutionType(lookupTable, subtable);
12390            var lookup = this.getLookupMethod(lookupTable, subtable);
12391            var substitution = (void 0);
12392            switch (substType) {
12393                case '11':
12394                    substitution = lookup(contextParams.current);
12395                    if (substitution) {
12396                        substitutions.splice(currentIndex, 1, new SubstitutionAction({
12397                            id: 11, tag: query.tag, substitution: substitution
12398                        }));
12399                    }
12400                    break;
12401                case '12':
12402                    substitution = lookup(contextParams.current);
12403                    if (substitution) {
12404                        substitutions.splice(currentIndex, 1, new SubstitutionAction({
12405                            id: 12, tag: query.tag, substitution: substitution
12406                        }));
12407                    }
12408                    break;
12409                case '63':
12410                    substitution = lookup(contextParams);
12411                    if (Array.isArray(substitution) && substitution.length) {
12412                        substitutions.splice(currentIndex, 1, new SubstitutionAction({
12413                            id: 63, tag: query.tag, substitution: substitution
12414                        }));
12415                    }
12416                    break;
12417                case '41':
12418                    substitution = lookup(contextParams);
12419                    if (substitution) {
12420                        substitutions.splice(currentIndex, 1, new SubstitutionAction({
12421                            id: 41, tag: query.tag, substitution: substitution
12422                        }));
12423                    }
12424                    break;
12425                case '21':
12426                    substitution = lookup(contextParams.current);
12427                    if (substitution) {
12428                        substitutions.splice(currentIndex, 1, new SubstitutionAction({
12429                            id: 21, tag: query.tag, substitution: substitution
12430                        }));
12431                    }
12432                    break;
12433            }
12434            contextParams = new ContextParams(substitutions, currentIndex);
12435            if (Array.isArray(substitution) && !substitution.length) { continue; }
12436            substitution = null;
12437        }
12438    }
12439    return substitutions.length ? substitutions : null;
12440};
12441
12442/**
12443 * Checks if a font supports a specific features
12444 * @param {FQuery} query feature query object
12445 */
12446FeatureQuery.prototype.supports = function (query) {
12447    if (!query.script) { return false; }
12448    this.getScriptFeatures(query.script);
12449    var supportedScript = this.features.hasOwnProperty(query.script);
12450    if (!query.tag) { return supportedScript; }
12451    var supportedFeature = (
12452        this.features[query.script].some(function (feature) { return feature.tag === query.tag; })
12453    );
12454    return supportedScript && supportedFeature;
12455};
12456
12457/**
12458 * Get lookup table subtables
12459 * @param {any} lookupTable lookup table
12460 */
12461FeatureQuery.prototype.getLookupSubtables = function (lookupTable) {
12462    return lookupTable.subtables || null;
12463};
12464
12465/**
12466 * Get lookup table by index
12467 * @param {number} index lookup table index
12468 */
12469FeatureQuery.prototype.getLookupByIndex = function (index) {
12470    var lookups = this.font.tables.gsub.lookups;
12471    return lookups[index] || null;
12472};
12473
12474/**
12475 * Get lookup tables for a feature
12476 * @param {string} feature
12477 */
12478FeatureQuery.prototype.getFeatureLookups = function (feature) {
12479    // TODO: memoize
12480    return feature.lookupListIndexes.map(this.getLookupByIndex.bind(this));
12481};
12482
12483/**
12484 * Query a feature by it's properties
12485 * @param {any} query an object that describes the properties of a query
12486 */
12487FeatureQuery.prototype.getFeature = function getFeature(query) {
12488    if (!this.font) { return { FAIL: "No font was found"}; }
12489    if (!this.features.hasOwnProperty(query.script)) {
12490        this.getScriptFeatures(query.script);
12491    }
12492    var scriptFeatures = this.features[query.script];
12493    if (!scriptFeatures) { return (
12494        { FAIL: ("No feature for script " + (query.script))}
12495    ); }
12496    if (!scriptFeatures.tags[query.tag]) { return null; }
12497    return this.features[query.script].tags[query.tag];
12498};
12499
12500/**
12501 * Arabic word context checkers
12502 */
12503
12504function arabicWordStartCheck(contextParams) {
12505    var char = contextParams.current;
12506    var prevChar = contextParams.get(-1);
12507    return (
12508        // ? arabic first char
12509        (prevChar === null && isArabicChar(char)) ||
12510        // ? arabic char preceded with a non arabic char
12511        (!isArabicChar(prevChar) && isArabicChar(char))
12512    );
12513}
12514
12515function arabicWordEndCheck(contextParams) {
12516    var nextChar = contextParams.get(1);
12517    return (
12518        // ? last arabic char
12519        (nextChar === null) ||
12520        // ? next char is not arabic
12521        (!isArabicChar(nextChar))
12522    );
12523}
12524
12525var arabicWordCheck = {
12526    startCheck: arabicWordStartCheck,
12527    endCheck: arabicWordEndCheck
12528};
12529
12530/**
12531 * Arabic sentence context checkers
12532 */
12533
12534function arabicSentenceStartCheck(contextParams) {
12535    var char = contextParams.current;
12536    var prevChar = contextParams.get(-1);
12537    return (
12538        // ? an arabic char preceded with a non arabic char
12539        (isArabicChar(char) || isTashkeelArabicChar(char)) &&
12540        !isArabicChar(prevChar)
12541    );
12542}
12543
12544function arabicSentenceEndCheck(contextParams) {
12545    var nextChar = contextParams.get(1);
12546    switch (true) {
12547        case nextChar === null:
12548            return true;
12549        case (!isArabicChar(nextChar) && !isTashkeelArabicChar(nextChar)):
12550            var nextIsWhitespace = isWhiteSpace(nextChar);
12551            if (!nextIsWhitespace) { return true; }
12552            if (nextIsWhitespace) {
12553                var arabicCharAhead = false;
12554                arabicCharAhead = (
12555                    contextParams.lookahead.some(
12556                        function (c) { return isArabicChar(c) || isTashkeelArabicChar(c); }
12557                    )
12558                );
12559                if (!arabicCharAhead) { return true; }
12560            }
12561            break;
12562        default:
12563            return false;
12564    }
12565}
12566
vendor: 13,096 bytes, lines 12567-13002
12567var arabicSentenceCheck = {
12568    startCheck: arabicSentenceStartCheck,
12569    endCheck: arabicSentenceEndCheck
12570};
12571
12572/**
12573 * Apply single substitution format 1
12574 * @param {Array} substitutions substitutions
12575 * @param {any} tokens a list of tokens
12576 * @param {number} index token index
12577 */
12578function singleSubstitutionFormat1$1(action, tokens, index) {
12579    tokens[index].setState(action.tag, action.substitution);
12580}
12581
12582/**
12583 * Apply single substitution format 2
12584 * @param {Array} substitutions substitutions
12585 * @param {any} tokens a list of tokens
12586 * @param {number} index token index
12587 */
12588function singleSubstitutionFormat2$1(action, tokens, index) {
12589    tokens[index].setState(action.tag, action.substitution);
12590}
12591
12592/**
12593 * Apply chaining context substitution format 3
12594 * @param {Array} substitutions substitutions
12595 * @param {any} tokens a list of tokens
12596 * @param {number} index token index
12597 */
12598function chainingSubstitutionFormat3$1(action, tokens, index) {
12599    action.substitution.forEach(function (subst, offset) {
12600        var token = tokens[index + offset];
12601        token.setState(action.tag, subst);
12602    });
12603}
12604
12605/**
12606 * Apply ligature substitution format 1
12607 * @param {Array} substitutions substitutions
12608 * @param {any} tokens a list of tokens
12609 * @param {number} index token index
12610 */
12611function ligatureSubstitutionFormat1$1(action, tokens, index) {
12612    var token = tokens[index];
12613    token.setState(action.tag, action.substitution.ligGlyph);
12614    var compsCount = action.substitution.components.length;
12615    for (var i = 0; i < compsCount; i++) {
12616        token = tokens[index + i + 1];
12617        token.setState('deleted', true);
12618    }
12619}
12620
12621/**
12622 * Supported substitutions
12623 */
12624var SUBSTITUTIONS = {
12625    11: singleSubstitutionFormat1$1,
12626    12: singleSubstitutionFormat2$1,
12627    63: chainingSubstitutionFormat3$1,
12628    41: ligatureSubstitutionFormat1$1
12629};
12630
12631/**
12632 * Apply substitutions to a list of tokens
12633 * @param {Array} substitutions substitutions
12634 * @param {any} tokens a list of tokens
12635 * @param {number} index token index
12636 */
12637function applySubstitution(action, tokens, index) {
12638    if (action instanceof SubstitutionAction && SUBSTITUTIONS[action.id]) {
12639        SUBSTITUTIONS[action.id](action, tokens, index);
12640    }
12641}
12642
12643/**
12644 * Apply Arabic presentation forms to a range of tokens
12645 */
12646
12647/**
12648 * Check if a char can be connected to it's preceding char
12649 * @param {ContextParams} charContextParams context params of a char
12650 */
12651function willConnectPrev(charContextParams) {
12652    var backtrack = [].concat(charContextParams.backtrack);
12653    for (var i = backtrack.length - 1; i >= 0; i--) {
12654        var prevChar = backtrack[i];
12655        var isolated = isIsolatedArabicChar(prevChar);
12656        var tashkeel = isTashkeelArabicChar(prevChar);
12657        if (!isolated && !tashkeel) { return true; }
12658        if (isolated) { return false; }
12659    }
12660    return false;
12661}
12662
12663/**
12664 * Check if a char can be connected to it's proceeding char
12665 * @param {ContextParams} charContextParams context params of a char
12666 */
12667function willConnectNext(charContextParams) {
12668    if (isIsolatedArabicChar(charContextParams.current)) { return false; }
12669    for (var i = 0; i < charContextParams.lookahead.length; i++) {
12670        var nextChar = charContextParams.lookahead[i];
12671        var tashkeel = isTashkeelArabicChar(nextChar);
12672        if (!tashkeel) { return true; }
12673    }
12674    return false;
12675}
12676
12677/**
12678 * Apply arabic presentation forms to a list of tokens
12679 * @param {ContextRange} range a range of tokens
12680 */
12681function arabicPresentationForms(range) {
12682    var this$1 = this;
12683
12684    var script = 'arab';
12685    var tags = this.featuresTags[script];
12686    var tokens = this.tokenizer.getRangeTokens(range);
12687    if (tokens.length === 1) { return; }
12688    var contextParams = new ContextParams(
12689        tokens.map(function (token) { return token.getState('glyphIndex'); }
12690    ), 0);
12691    var charContextParams = new ContextParams(
12692        tokens.map(function (token) { return token.char; }
12693    ), 0);
12694    tokens.forEach(function (token, index) {
12695        if (isTashkeelArabicChar(token.char)) { return; }
12696        contextParams.setCurrentIndex(index);
12697        charContextParams.setCurrentIndex(index);
12698        var CONNECT = 0; // 2 bits 00 (10: can connect next) (01: can connect prev)
12699        if (willConnectPrev(charContextParams)) { CONNECT |= 1; }
12700        if (willConnectNext(charContextParams)) { CONNECT |= 2; }
12701        var tag;
12702        switch (CONNECT) {
12703            case 1: (tag = 'fina'); break;
12704            case 2: (tag = 'init'); break;
12705            case 3: (tag = 'medi'); break;
12706        }
12707        if (tags.indexOf(tag) === -1) { return; }
12708        var substitutions = this$1.query.lookupFeature({
12709            tag: tag, script: script, contextParams: contextParams
12710        });
12711        if (substitutions instanceof Error) { return console.info(substitutions.message); }
12712        substitutions.forEach(function (action, index) {
12713            if (action instanceof SubstitutionAction) {
12714                applySubstitution(action, tokens, index);
12715                contextParams.context[index] = action.substitution;
12716            }
12717        });
12718    });
12719}
12720
12721/**
12722 * Apply Arabic required ligatures feature to a range of tokens
12723 */
12724
12725/**
12726 * Update context params
12727 * @param {any} tokens a list of tokens
12728 * @param {number} index current item index
12729 */
12730function getContextParams(tokens, index) {
12731    var context = tokens.map(function (token) { return token.activeState.value; });
12732    return new ContextParams(context, index || 0);
12733}
12734
12735/**
12736 * Apply Arabic required ligatures to a context range
12737 * @param {ContextRange} range a range of tokens
12738 */
12739function arabicRequiredLigatures(range) {
12740    var this$1 = this;
12741
12742    var script = 'arab';
12743    var tokens = this.tokenizer.getRangeTokens(range);
12744    var contextParams = getContextParams(tokens);
12745    contextParams.context.forEach(function (glyphIndex, index) {
12746        contextParams.setCurrentIndex(index);
12747        var substitutions = this$1.query.lookupFeature({
12748            tag: 'rlig', script: script, contextParams: contextParams
12749        });
12750        if (substitutions.length) {
12751            substitutions.forEach(
12752                function (action) { return applySubstitution(action, tokens, index); }
12753            );
12754            contextParams = getContextParams(tokens);
12755        }
12756    });
12757}
12758
12759/**
12760 * Latin word context checkers
12761 */
12762
12763function latinWordStartCheck(contextParams) {
12764    var char = contextParams.current;
12765    var prevChar = contextParams.get(-1);
12766    return (
12767        // ? latin first char
12768        (prevChar === null && isLatinChar(char)) ||
12769        // ? latin char preceded with a non latin char
12770        (!isLatinChar(prevChar) && isLatinChar(char))
12771    );
12772}
12773
12774function latinWordEndCheck(contextParams) {
12775    var nextChar = contextParams.get(1);
12776    return (
12777        // ? last latin char
12778        (nextChar === null) ||
12779        // ? next char is not latin
12780        (!isLatinChar(nextChar))
12781    );
12782}
12783
12784var latinWordCheck = {
12785    startCheck: latinWordStartCheck,
12786    endCheck: latinWordEndCheck
12787};
12788
12789/**
12790 * Apply Latin ligature feature to a range of tokens
12791 */
12792
12793/**
12794 * Update context params
12795 * @param {any} tokens a list of tokens
12796 * @param {number} index current item index
12797 */
12798function getContextParams$1(tokens, index) {
12799    var context = tokens.map(function (token) { return token.activeState.value; });
12800    return new ContextParams(context, index || 0);
12801}
12802
12803/**
12804 * Apply Arabic required ligatures to a context range
12805 * @param {ContextRange} range a range of tokens
12806 */
12807function latinLigature(range) {
12808    var this$1 = this;
12809
12810    var script = 'latn';
12811    var tokens = this.tokenizer.getRangeTokens(range);
12812    var contextParams = getContextParams$1(tokens);
12813    contextParams.context.forEach(function (glyphIndex, index) {
12814        contextParams.setCurrentIndex(index);
12815        var substitutions = this$1.query.lookupFeature({
12816            tag: 'liga', script: script, contextParams: contextParams
12817        });
12818        if (substitutions.length) {
12819            substitutions.forEach(
12820                function (action) { return applySubstitution(action, tokens, index); }
12821            );
12822            contextParams = getContextParams$1(tokens);
12823        }
12824    });
12825}
12826
12827/**
12828 * Infer bidirectional properties for a given text and apply
12829 * the corresponding layout rules.
12830 */
12831
12832/**
12833 * Create Bidi. features
12834 * @param {string} baseDir text base direction. value either 'ltr' or 'rtl'
12835 */
12836function Bidi(baseDir) {
12837    this.baseDir = baseDir || 'ltr';
12838    this.tokenizer = new Tokenizer();
12839    this.featuresTags = {};
12840}
12841
12842/**
12843 * Sets Bidi text
12844 * @param {string} text a text input
12845 */
12846Bidi.prototype.setText = function (text) {
12847    this.text = text;
12848};
12849
12850/**
12851 * Store essential context checks:
12852 * arabic word check for applying gsub features
12853 * arabic sentence check for adjusting arabic layout
12854 */
12855Bidi.prototype.contextChecks = ({
12856    latinWordCheck: latinWordCheck,
12857    arabicWordCheck: arabicWordCheck,
12858    arabicSentenceCheck: arabicSentenceCheck
12859});
12860
12861/**
12862 * Register arabic word check
12863 */
12864function registerContextChecker(checkId) {
12865    var check = this.contextChecks[(checkId + "Check")];
12866    return this.tokenizer.registerContextChecker(
12867        checkId, check.startCheck, check.endCheck
12868    );
12869}
12870
12871/**
12872 * Perform pre tokenization procedure then
12873 * tokenize text input
12874 */
12875function tokenizeText() {
12876    registerContextChecker.call(this, 'latinWord');
12877    registerContextChecker.call(this, 'arabicWord');
12878    registerContextChecker.call(this, 'arabicSentence');
12879    return this.tokenizer.tokenize(this.text);
12880}
12881
12882/**
12883 * Reverse arabic sentence layout
12884 * TODO: check base dir before applying adjustments - priority low
12885 */
12886function reverseArabicSentences() {
12887    var this$1 = this;
12888
12889    var ranges = this.tokenizer.getContextRanges('arabicSentence');
12890    ranges.forEach(function (range) {
12891        var rangeTokens = this$1.tokenizer.getRangeTokens(range);
12892        this$1.tokenizer.replaceRange(
12893            range.startIndex,
12894            range.endOffset,
12895            rangeTokens.reverse()
12896        );
12897    });
12898}
12899
12900/**
12901 * Register supported features tags
12902 * @param {script} script script tag
12903 * @param {Array} tags features tags list
12904 */
12905Bidi.prototype.registerFeatures = function (script, tags) {
12906    var this$1 = this;
12907
12908    var supportedTags = tags.filter(
12909        function (tag) { return this$1.query.supports({script: script, tag: tag}); }
12910    );
12911    if (!this.featuresTags.hasOwnProperty(script)) {
12912        this.featuresTags[script] = supportedTags;
12913    } else {
12914        this.featuresTags[script] =
12915        this.featuresTags[script].concat(supportedTags);
12916    }
12917};
12918
12919/**
12920 * Apply GSUB features
12921 * @param {Array} tagsList a list of features tags
12922 * @param {string} script a script tag
12923 * @param {Font} font opentype font instance
12924 */
12925Bidi.prototype.applyFeatures = function (font, features) {
12926    if (!font) { throw new Error(
12927        'No valid font was provided to apply features'
12928    ); }
12929    if (!this.query) { this.query = new FeatureQuery(font); }
12930    for (var f = 0; f < features.length; f++) {
12931        var feature = features[f];
12932        if (!this.query.supports({script: feature.script})) { continue; }
12933        this.registerFeatures(feature.script, feature.tags);
12934    }
12935};
12936
12937/**
12938 * Register a state modifier
12939 * @param {string} modifierId state modifier id
12940 * @param {function} condition a predicate function that returns true or false
12941 * @param {function} modifier a modifier function to set token state
12942 */
12943Bidi.prototype.registerModifier = function (modifierId, condition, modifier) {
12944    this.tokenizer.registerModifier(modifierId, condition, modifier);
12945};
12946
12947/**
12948 * Check if 'glyphIndex' is registered
12949 */
12950function checkGlyphIndexStatus() {
12951    if (this.tokenizer.registeredModifiers.indexOf('glyphIndex') === -1) {
12952        throw new Error(
12953            'glyphIndex modifier is required to apply ' +
12954            'arabic presentation features.'
12955        );
12956    }
12957}
12958
12959/**
12960 * Apply arabic presentation forms features
12961 */
12962function applyArabicPresentationForms() {
12963    var this$1 = this;
12964
12965    var script = 'arab';
12966    if (!this.featuresTags.hasOwnProperty(script)) { return; }
12967    checkGlyphIndexStatus.call(this);
12968    var ranges = this.tokenizer.getContextRanges('arabicWord');
12969    ranges.forEach(function (range) {
12970        arabicPresentationForms.call(this$1, range);
12971    });
12972}
12973
12974/**
12975 * Apply required arabic ligatures
12976 */
12977function applyArabicRequireLigatures() {
12978    var this$1 = this;
12979
12980    var script = 'arab';
12981    if (!this.featuresTags.hasOwnProperty(script)) { return; }
12982    var tags = this.featuresTags[script];
12983    if (tags.indexOf('rlig') === -1) { return; }
12984    checkGlyphIndexStatus.call(this);
12985    var ranges = this.tokenizer.getContextRanges('arabicWord');
12986    ranges.forEach(function (range) {
12987        arabicRequiredLigatures.call(this$1, range);
12988    });
12989}
12990
12991/**
12992 * Apply required arabic ligatures
12993 */
12994function applyLatinLigatures() {
12995    var this$1 = this;
12996
12997    var script = 'latn';
12998    if (!this.featuresTags.hasOwnProperty(script)) { return; }
12999    var tags = this.featuresTags[script];
13000    if (tags.indexOf('liga') === -1) { return; }
13001    checkGlyphIndexStatus.call(this);
13002    var ranges = this.tokenizer.getContextRanges('latinWord');
13003    ranges.forEach(function (range) {
13004        latinLigature.call(this$1, range);
13005    });
13006}
13007
13008/**
13009 * Check if a context is registered
13010 * @param {string} contextId context id
13011 */
13012Bidi.prototype.checkContextReady = function (contextId) {
13013    return !!this.tokenizer.getContext(contextId);
13014};
13015
13016/**
13017 * Apply features to registered contexts
13018 */
13019Bidi.prototype.applyFeaturesToContexts = function () {
13020    if (this.checkContextReady('arabicWord')) {
13021        applyArabicPresentationForms.call(this);
13022        applyArabicRequireLigatures.call(this);
13023    }
13024    if (this.checkContextReady('latinWord')) {
13025        applyLatinLigatures.call(this);
13026    }
13027    if (this.checkContextReady('arabicSentence')) {
13028        reverseArabicSentences.call(this);
13029    }
13030};
13031
13032/**
13033 * process text input
13034 * @param {string} text an input text
13035 */
13036Bidi.prototype.processText = function(text) {
13037    if (!this.text || this.text !== text) {
13038        this.setText(text);
13039        tokenizeText.call(this);
13040        this.applyFeaturesToContexts();
13041    }
13042};
13043
13044/**
13045 * Process a string of text to identify and adjust
13046 * bidirectional text entities.
13047 * @param {string} text input text
13048 */
13049Bidi.prototype.getBidiText = function (text) {
13050    this.processText(text);
13051    return this.tokenizer.getText();
13052};
13053
13054/**
13055 * Get the current state index of each token
13056 * @param {text} text an input text
13057 */
13058Bidi.prototype.getTextGlyphs = function (text) {
13059    this.processText(text);
13060    var indexes = [];
13061    for (var i = 0; i < this.tokenizer.tokens.length; i++) {
13062        var token = this.tokenizer.tokens[i];
13063        if (token.state.deleted) { continue; }
13064        var index = token.activeState.value;
13065        indexes.push(Array.isArray(index) ? index[0] : index);
13066    }
13067    return indexes;
13068};
13069
13070// The Font object
13071
13072/**
13073 * @typedef FontOptions
13074 * @type Object
13075 * @property {Boolean} empty - whether to create a new empty font
13076 * @property {string} familyName
13077 * @property {string} styleName
13078 * @property {string=} fullName
13079 * @property {string=} postScriptName
13080 * @property {string=} designer
13081 * @property {string=} designerURL
13082 * @property {string=} manufacturer
13083 * @property {string=} manufacturerURL
13084 * @property {string=} license
13085 * @property {string=} licenseURL
13086 * @property {string=} version
13087 * @property {string=} description
13088 * @property {string=} copyright
13089 * @property {string=} trademark
13090 * @property {Number} unitsPerEm
13091 * @property {Number} ascender
13092 * @property {Number} descender
13093 * @property {Number} createdTimestamp
13094 * @property {string=} weightClass
13095 * @property {string=} widthClass
13096 * @property {string=} fsSelection
13097 */
13098
13099/**
13100 * A Font represents a loaded OpenType font file.
13101 * It contains a set of glyphs and methods to draw text on a drawing context,
13102 * or to get a path representing the text.
13103 * @exports opentype.Font
13104 * @class
13105 * @param {FontOptions}
13106 * @constructor
13107 */
13108function Font(options) {
13109    options = options || {};
13110    options.tables = options.tables || {};
13111
13112    if (!options.empty) {
13113        // Check that we've provided the minimum set of names.
13114        checkArgument(options.familyName, 'When creating a new Font object, familyName is required.');
13115        checkArgument(options.styleName, 'When creating a new Font object, styleName is required.');
13116        checkArgument(options.unitsPerEm, 'When creating a new Font object, unitsPerEm is required.');
13117        checkArgument(options.ascender, 'When creating a new Font object, ascender is required.');
13118        checkArgument(options.descender <= 0, 'When creating a new Font object, negative descender value is required.');
13119
13120        // OS X will complain if the names are empty, so we put a single space everywhere by default.
13121        this.names = {
13122            fontFamily: {en: options.familyName || ' '},
13123            fontSubfamily: {en: options.styleName || ' '},
13124            fullName: {en: options.fullName || options.familyName + ' ' + options.styleName},
13125            // postScriptName may not contain any whitespace
13126            postScriptName: {en: options.postScriptName || (options.familyName + options.styleName).replace(/\s/g, '')},
13127            designer: {en: options.designer || ' '},
vendor: 11,854 bytes, lines 13128-13451
13128            designerURL: {en: options.designerURL || ' '},
13129            manufacturer: {en: options.manufacturer || ' '},
13130            manufacturerURL: {en: options.manufacturerURL || ' '},
13131            license: {en: options.license || ' '},
13132            licenseURL: {en: options.licenseURL || ' '},
13133            version: {en: options.version || 'Version 0.1'},
13134            description: {en: options.description || ' '},
13135            copyright: {en: options.copyright || ' '},
13136            trademark: {en: options.trademark || ' '}
13137        };
13138        this.unitsPerEm = options.unitsPerEm || 1000;
13139        this.ascender = options.ascender;
13140        this.descender = options.descender;
13141        this.createdTimestamp = options.createdTimestamp;
13142        this.tables = Object.assign(options.tables, {
13143            os2: Object.assign({
13144                usWeightClass: options.weightClass || this.usWeightClasses.MEDIUM,
13145                usWidthClass: options.widthClass || this.usWidthClasses.MEDIUM,
13146                fsSelection: options.fsSelection || this.fsSelectionValues.REGULAR,
13147            }, options.tables.os2)
13148        });
13149    }
13150
13151    this.supported = true; // Deprecated: parseBuffer will throw an error if font is not supported.
13152    this.glyphs = new glyphset.GlyphSet(this, options.glyphs || []);
13153    this.encoding = new DefaultEncoding(this);
13154    this.position = new Position(this);
13155    this.substitution = new Substitution(this);
13156    this.tables = this.tables || {};
13157
13158    // needed for low memory mode only.
13159    this._push = null;
13160    this._hmtxTableData = {};
13161
13162    Object.defineProperty(this, 'hinting', {
13163        get: function() {
13164            if (this._hinting) { return this._hinting; }
13165            if (this.outlinesFormat === 'truetype') {
13166                return (this._hinting = new Hinting(this));
13167            }
13168        }
13169    });
13170}
13171
13172/**
13173 * Check if the font has a glyph for the given character.
13174 * @param  {string}
13175 * @return {Boolean}
13176 */
13177Font.prototype.hasChar = function(c) {
13178    return this.encoding.charToGlyphIndex(c) !== null;
13179};
13180
13181/**
13182 * Convert the given character to a single glyph index.
13183 * Note that this function assumes that there is a one-to-one mapping between
13184 * the given character and a glyph; for complex scripts this might not be the case.
13185 * @param  {string}
13186 * @return {Number}
13187 */
13188Font.prototype.charToGlyphIndex = function(s) {
13189    return this.encoding.charToGlyphIndex(s);
13190};
13191
13192/**
13193 * Convert the given character to a single Glyph object.
13194 * Note that this function assumes that there is a one-to-one mapping between
13195 * the given character and a glyph; for complex scripts this might not be the case.
13196 * @param  {string}
13197 * @return {opentype.Glyph}
13198 */
13199Font.prototype.charToGlyph = function(c) {
13200    var glyphIndex = this.charToGlyphIndex(c);
13201    var glyph = this.glyphs.get(glyphIndex);
13202    if (!glyph) {
13203        // .notdef
13204        glyph = this.glyphs.get(0);
13205    }
13206
13207    return glyph;
13208};
13209
13210/**
13211 * Update features
13212 * @param {any} options features options
13213 */
13214Font.prototype.updateFeatures = function (options) {
13215    // TODO: update all features options not only 'latn'.
13216    return this.defaultRenderOptions.features.map(function (feature) {
13217        if (feature.script === 'latn') {
13218            return {
13219                script: 'latn',
13220                tags: feature.tags.filter(function (tag) { return options[tag]; })
13221            };
13222        } else {
13223            return feature;
13224        }
13225    });
13226};
13227
13228/**
13229 * Convert the given text to a list of Glyph objects.
13230 * Note that there is no strict one-to-one mapping between characters and
13231 * glyphs, so the list of returned glyphs can be larger or smaller than the
13232 * length of the given string.
13233 * @param  {string}
13234 * @param  {GlyphRenderOptions} [options]
13235 * @return {opentype.Glyph[]}
13236 */
13237Font.prototype.stringToGlyphs = function(s, options) {
13238    var this$1 = this;
13239
13240
13241    var bidi = new Bidi();
13242
13243    // Create and register 'glyphIndex' state modifier
13244    var charToGlyphIndexMod = function (token) { return this$1.charToGlyphIndex(token.char); };
13245    bidi.registerModifier('glyphIndex', null, charToGlyphIndexMod);
13246
13247    // roll-back to default features
13248    var features = options ?
13249    this.updateFeatures(options.features) :
13250    this.defaultRenderOptions.features;
13251
13252    bidi.applyFeatures(this, features);
13253
13254    var indexes = bidi.getTextGlyphs(s);
13255
13256    var length = indexes.length;
13257
13258    // convert glyph indexes to glyph objects
13259    var glyphs = new Array(length);
13260    var notdef = this.glyphs.get(0);
13261    for (var i = 0; i < length; i += 1) {
13262        glyphs[i] = this.glyphs.get(indexes[i]) || notdef;
13263    }
13264    return glyphs;
13265};
13266
13267/**
13268 * @param  {string}
13269 * @return {Number}
13270 */
13271Font.prototype.nameToGlyphIndex = function(name) {
13272    return this.glyphNames.nameToGlyphIndex(name);
13273};
13274
13275/**
13276 * @param  {string}
13277 * @return {opentype.Glyph}
13278 */
13279Font.prototype.nameToGlyph = function(name) {
13280    var glyphIndex = this.nameToGlyphIndex(name);
13281    var glyph = this.glyphs.get(glyphIndex);
13282    if (!glyph) {
13283        // .notdef
13284        glyph = this.glyphs.get(0);
13285    }
13286
13287    return glyph;
13288};
13289
13290/**
13291 * @param  {Number}
13292 * @return {String}
13293 */
13294Font.prototype.glyphIndexToName = function(gid) {
13295    if (!this.glyphNames.glyphIndexToName) {
13296        return '';
13297    }
13298
13299    return this.glyphNames.glyphIndexToName(gid);
13300};
13301
13302/**
13303 * Retrieve the value of the kerning pair between the left glyph (or its index)
13304 * and the right glyph (or its index). If no kerning pair is found, return 0.
13305 * The kerning value gets added to the advance width when calculating the spacing
13306 * between glyphs.
13307 * For GPOS kerning, this method uses the default script and language, which covers
13308 * most use cases. To have greater control, use font.position.getKerningValue .
13309 * @param  {opentype.Glyph} leftGlyph
13310 * @param  {opentype.Glyph} rightGlyph
13311 * @return {Number}
13312 */
13313Font.prototype.getKerningValue = function(leftGlyph, rightGlyph) {
13314    leftGlyph = leftGlyph.index || leftGlyph;
13315    rightGlyph = rightGlyph.index || rightGlyph;
13316    var gposKerning = this.position.defaultKerningTables;
13317    if (gposKerning) {
13318        return this.position.getKerningValue(gposKerning, leftGlyph, rightGlyph);
13319    }
13320    // "kern" table
13321    return this.kerningPairs[leftGlyph + ',' + rightGlyph] || 0;
13322};
13323
13324/**
13325 * @typedef GlyphRenderOptions
13326 * @type Object
13327 * @property {string} [script] - script used to determine which features to apply. By default, 'DFLT' or 'latn' is used.
13328 *                               See https://www.microsoft.com/typography/otspec/scripttags.htm
13329 * @property {string} [language='dflt'] - language system used to determine which features to apply.
13330 *                                        See https://www.microsoft.com/typography/developers/opentype/languagetags.aspx
13331 * @property {boolean} [kerning=true] - whether to include kerning values
13332 * @property {object} [features] - OpenType Layout feature tags. Used to enable or disable the features of the given script/language system.
13333 *                                 See https://www.microsoft.com/typography/otspec/featuretags.htm
13334 */
13335Font.prototype.defaultRenderOptions = {
13336    kerning: true,
13337    features: [
13338        /**
13339         * these 4 features are required to render Arabic text properly
13340         * and shouldn't be turned off when rendering arabic text.
13341         */
13342        { script: 'arab', tags: ['init', 'medi', 'fina', 'rlig'] },
13343        { script: 'latn', tags: ['liga', 'rlig'] }
13344    ]
13345};
13346
13347/**
13348 * Helper function that invokes the given callback for each glyph in the given text.
13349 * The callback gets `(glyph, x, y, fontSize, options)`.* @param  {string} text
13350 * @param {string} text - The text to apply.
13351 * @param  {number} [x=0] - Horizontal position of the beginning of the text.
13352 * @param  {number} [y=0] - Vertical position of the *baseline* of the text.
13353 * @param  {number} [fontSize=72] - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`.
13354 * @param  {GlyphRenderOptions=} options
13355 * @param  {Function} callback
13356 */
13357Font.prototype.forEachGlyph = function(text, x, y, fontSize, options, callback) {
13358    x = x !== undefined ? x : 0;
13359    y = y !== undefined ? y : 0;
13360    fontSize = fontSize !== undefined ? fontSize : 72;
13361    options = Object.assign({}, this.defaultRenderOptions, options);
13362    var fontScale = 1 / this.unitsPerEm * fontSize;
13363    var glyphs = this.stringToGlyphs(text, options);
13364    var kerningLookups;
13365    if (options.kerning) {
13366        var script = options.script || this.position.getDefaultScriptName();
13367        kerningLookups = this.position.getKerningTables(script, options.language);
13368    }
13369    for (var i = 0; i < glyphs.length; i += 1) {
13370        var glyph = glyphs[i];
13371        callback.call(this, glyph, x, y, fontSize, options);
13372        if (glyph.advanceWidth) {
13373            x += glyph.advanceWidth * fontScale;
13374        }
13375
13376        if (options.kerning && i < glyphs.length - 1) {
13377            // We should apply position adjustment lookups in a more generic way.
13378            // Here we only use the xAdvance value.
13379            var kerningValue = kerningLookups ?
13380                  this.position.getKerningValue(kerningLookups, glyph.index, glyphs[i + 1].index) :
13381                  this.getKerningValue(glyph, glyphs[i + 1]);
13382            x += kerningValue * fontScale;
13383        }
13384
13385        if (options.letterSpacing) {
13386            x += options.letterSpacing * fontSize;
13387        } else if (options.tracking) {
13388            x += (options.tracking / 1000) * fontSize;
13389        }
13390    }
13391    return x;
13392};
13393
13394/**
13395 * Create a Path object that represents the given text.
13396 * @param  {string} text - The text to create.
13397 * @param  {number} [x=0] - Horizontal position of the beginning of the text.
13398 * @param  {number} [y=0] - Vertical position of the *baseline* of the text.
13399 * @param  {number} [fontSize=72] - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`.
13400 * @param  {GlyphRenderOptions=} options
13401 * @return {opentype.Path}
13402 */
13403Font.prototype.getPath = function(text, x, y, fontSize, options) {
13404    var fullPath = new Path();
13405    this.forEachGlyph(text, x, y, fontSize, options, function(glyph, gX, gY, gFontSize) {
13406        var glyphPath = glyph.getPath(gX, gY, gFontSize, options, this);
13407        fullPath.extend(glyphPath);
13408    });
13409    return fullPath;
13410};
13411
13412/**
13413 * Create an array of Path objects that represent the glyphs of a given text.
13414 * @param  {string} text - The text to create.
13415 * @param  {number} [x=0] - Horizontal position of the beginning of the text.
13416 * @param  {number} [y=0] - Vertical position of the *baseline* of the text.
13417 * @param  {number} [fontSize=72] - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`.
13418 * @param  {GlyphRenderOptions=} options
13419 * @return {opentype.Path[]}
13420 */
13421Font.prototype.getPaths = function(text, x, y, fontSize, options) {
13422    var glyphPaths = [];
13423    this.forEachGlyph(text, x, y, fontSize, options, function(glyph, gX, gY, gFontSize) {
13424        var glyphPath = glyph.getPath(gX, gY, gFontSize, options, this);
13425        glyphPaths.push(glyphPath);
13426    });
13427
13428    return glyphPaths;
13429};
13430
13431/**
13432 * Returns the advance width of a text.
13433 *
13434 * This is something different than Path.getBoundingBox() as for example a
13435 * suffixed whitespace increases the advanceWidth but not the bounding box
13436 * or an overhanging letter like a calligraphic 'f' might have a quite larger
13437 * bounding box than its advance width.
13438 *
13439 * This corresponds to canvas2dContext.measureText(text).width
13440 *
13441 * @param  {string} text - The text to create.
13442 * @param  {number} [fontSize=72] - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`.
13443 * @param  {GlyphRenderOptions=} options
13444 * @return advance width
13445 */
13446Font.prototype.getAdvanceWidth = function(text, fontSize, options) {
13447    return this.forEachGlyph(text, 0, 0, fontSize, options, function() {});
13448};
13449
13450/**
13451 * Draw the text on the given drawing context.
13452 * @param  {CanvasRenderingContext2D} ctx - A 2D drawing context, like Canvas.
13453 * @param  {string} text - The text to create.
13454 * @param  {number} [x=0] - Horizontal position of the beginning of the text.
13455 * @param  {number} [y=0] - Vertical position of the *baseline* of the text.
13456 * @param  {number} [fontSize=72] - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`.
13457 * @param  {GlyphRenderOptions=} options
13458 */
13459Font.prototype.draw = function(ctx, text, x, y, fontSize, options) {
13460    this.getPath(text, x, y, fontSize, options).draw(ctx);
13461};
13462
13463/**
13464 * Draw the points of all glyphs in the text.
13465 * On-curve points will be drawn in blue, off-curve points will be drawn in red.
13466 * @param {CanvasRenderingContext2D} ctx - A 2D drawing context, like Canvas.
13467 * @param {string} text - The text to create.
13468 * @param {number} [x=0] - Horizontal position of the beginning of the text.
13469 * @param {number} [y=0] - Vertical position of the *baseline* of the text.
13470 * @param {number} [fontSize=72] - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`.
13471 * @param {GlyphRenderOptions=} options
13472 */
13473Font.prototype.drawPoints = function(ctx, text, x, y, fontSize, options) {
13474    this.forEachGlyph(text, x, y, fontSize, options, function(glyph, gX, gY, gFontSize) {
13475        glyph.drawPoints(ctx, gX, gY, gFontSize);
13476    });
13477};
13478
13479/**
13480 * Draw lines indicating important font measurements for all glyphs in the text.
13481 * Black lines indicate the origin of the coordinate system (point 0,0).
13482 * Blue lines indicate the glyph bounding box.
13483 * Green line indicates the advance width of the glyph.
13484 * @param {CanvasRenderingContext2D} ctx - A 2D drawing context, like Canvas.
13485 * @param {string} text - The text to create.
13486 * @param {number} [x=0] - Horizontal position of the beginning of the text.
13487 * @param {number} [y=0] - Vertical position of the *baseline* of the text.
13488 * @param {number} [fontSize=72] - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`.
13489 * @param {GlyphRenderOptions=} options
13490 */
13491Font.prototype.drawMetrics = function(ctx, text, x, y, fontSize, options) {
13492    this.forEachGlyph(text, x, y, fontSize, options, function(glyph, gX, gY, gFontSize) {
13493        glyph.drawMetrics(ctx, gX, gY, gFontSize);
13494    });
13495};
13496
13497/**
13498 * @param  {string}
13499 * @return {string}
13500 */
13501Font.prototype.getEnglishName = function(name) {
13502    var translations = this.names[name];
13503    if (translations) {
13504        return translations.en;
13505    }
13506};
13507
13508/**
13509 * Validate
13510 */
13511Font.prototype.validate = function() {
13512    var _this = this;
13513
13514    function assert(predicate, message) {
13515    }
13516
13517    function assertNamePresent(name) {
13518        var englishName = _this.getEnglishName(name);
13519        assert(englishName && englishName.trim().length > 0);
13520    }
13521
13522    // Identification information
13523    assertNamePresent('fontFamily');
13524    assertNamePresent('weightName');
13525    assertNamePresent('manufacturer');
13526    assertNamePresent('copyright');
13527    assertNamePresent('version');
13528
13529    // Dimension information
13530    assert(this.unitsPerEm > 0);
13531};
13532
13533/**
13534 * Convert the font object to a SFNT data structure.
13535 * This structure contains all the necessary tables and metadata to create a binary OTF file.
13536 * @return {opentype.Table}
13537 */
13538Font.prototype.toTables = function() {
13539    return sfnt.fontToTable(this);
13540};
13541/**
13542 * @deprecated Font.toBuffer is deprecated. Use Font.toArrayBuffer instead.
13543 */
13544Font.prototype.toBuffer = function() {
13545    console.warn('Font.toBuffer is deprecated. Use Font.toArrayBuffer instead.');
13546    return this.toArrayBuffer();
13547};
13548/**
13549 * Converts a `opentype.Font` into an `ArrayBuffer`
13550 * @return {ArrayBuffer}
13551 */
13552Font.prototype.toArrayBuffer = function() {
13553    var sfntTable = this.toTables();
13554    var bytes = sfntTable.encode();
13555    var buffer = new ArrayBuffer(bytes.length);
13556    var intArray = new Uint8Array(buffer);
13557    for (var i = 0; i < bytes.length; i++) {
13558        intArray[i] = bytes[i];
13559    }
13560
13561    return buffer;
13562};
13563
13564/**
13565 * Initiate a download of the OpenType font.
13566 */
13567Font.prototype.download = function(fileName) {
13568    var familyName = this.getEnglishName('fontFamily');
13569    var styleName = this.getEnglishName('fontSubfamily');
13570    fileName = fileName || familyName.replace(/\s/g, '') + '-' + styleName + '.otf';
13571    var arrayBuffer = this.toArrayBuffer();
13572
13573    if (isBrowser()) {
13574        window.URL = window.URL || window.webkitURL;
13575
13576        if (window.URL) {
13577            var dataView = new DataView(arrayBuffer);
13578            var blob = new Blob([dataView], {type: 'font/opentype'});
13579
13580            var link = document.createElement('a');
13581            link.href = window.URL.createObjectURL(blob);
13582            link.download = fileName;
13583
13584            var event = document.createEvent('MouseEvents');
13585            event.initEvent('click', true, false);
13586            link.dispatchEvent(event);
13587        } else {
13588            console.warn('Font file could not be downloaded. Try using a different browser.');
13589        }
13590    } else {
13591        var fs = require('fs');
13592        var buffer = arrayBufferToNodeBuffer(arrayBuffer);
13593        fs.writeFileSync(fileName, buffer);
13594    }
13595};
13596/**
13597 * @private
13598 */
13599Font.prototype.fsSelectionValues = {
13600    ITALIC:              0x001, //1
13601    UNDERSCORE:          0x002, //2
13602    NEGATIVE:            0x004, //4
13603    OUTLINED:            0x008, //8
13604    STRIKEOUT:           0x010, //16
13605    BOLD:                0x020, //32
13606    REGULAR:             0x040, //64
13607    USER_TYPO_METRICS:   0x080, //128
13608    WWS:                 0x100, //256
13609    OBLIQUE:             0x200  //512
13610};
13611
13612/**
13613 * @private
13614 */
13615Font.prototype.usWidthClasses = {
13616    ULTRA_CONDENSED: 1,
13617    EXTRA_CONDENSED: 2,
13618    CONDENSED: 3,
13619    SEMI_CONDENSED: 4,
13620    MEDIUM: 5,
13621    SEMI_EXPANDED: 6,
13622    EXPANDED: 7,
13623    EXTRA_EXPANDED: 8,
13624    ULTRA_EXPANDED: 9
13625};
13626
13627/**
13628 * @private
13629 */
13630Font.prototype.usWeightClasses = {
13631    THIN: 100,
13632    EXTRA_LIGHT: 200,
13633    LIGHT: 300,
13634    NORMAL: 400,
13635    MEDIUM: 500,
13636    SEMI_BOLD: 600,
13637    BOLD: 700,
13638    EXTRA_BOLD: 800,
13639    BLACK:    900
13640};
13641
13642// The `fvar` table stores font variation axes and instances.
13643
13644function addName(name, names) {
13645    var nameString = JSON.stringify(name);
13646    var nameID = 256;
13647    for (var nameKey in names) {
13648        var n = parseInt(nameKey);
13649        if (!n || n < 256) {
13650            continue;
13651        }
13652
13653        if (JSON.stringify(names[nameKey]) === nameString) {
13654            return n;
13655        }
13656
13657        if (nameID <= n) {
13658            nameID = n + 1;
13659        }
13660    }
13661
13662    names[nameID] = name;
13663    return nameID;
13664}
13665
13666function makeFvarAxis(n, axis, names) {
13667    var nameID = addName(axis.name, names);
13668    return [
13669        {name: 'tag_' + n, type: 'TAG', value: axis.tag},
13670        {name: 'minValue_' + n, type: 'FIXED', value: axis.minValue << 16},
13671        {name: 'defaultValue_' + n, type: 'FIXED', value: axis.defaultValue << 16},
13672        {name: 'maxValue_' + n, type: 'FIXED', value: axis.maxValue << 16},
13673        {name: 'flags_' + n, type: 'USHORT', value: 0},
13674        {name: 'nameID_' + n, type: 'USHORT', value: nameID}
13675    ];
13676}
13677
13678function parseFvarAxis(data, start, names) {
13679    var axis = {};
13680    var p = new parse.Parser(data, start);
13681    axis.tag = p.parseTag();
13682    axis.minValue = p.parseFixed();
13683    axis.defaultValue = p.parseFixed();
13684    axis.maxValue = p.parseFixed();
13685    p.skip('uShort', 1);  // reserved for flags; no values defined
13686    axis.name = names[p.parseUShort()] || {};
13687    return axis;
13688}
13689
13690function makeFvarInstance(n, inst, axes, names) {
13691    var nameID = addName(inst.name, names);
13692    var fields = [
13693        {name: 'nameID_' + n, type: 'USHORT', value: nameID},
13694        {name: 'flags_' + n, type: 'USHORT', value: 0}
13695    ];
13696
13697    for (var i = 0; i < axes.length; ++i) {
13698        var axisTag = axes[i].tag;
13699        fields.push({
13700            name: 'axis_' + n + ' ' + axisTag,
13701            type: 'FIXED',
13702            value: inst.coordinates[axisTag] << 16
13703        });
13704    }
13705
13706    return fields;
13707}
13708
13709function parseFvarInstance(data, start, axes, names) {
13710    var inst = {};
13711    var p = new parse.Parser(data, start);
13712    inst.name = names[p.parseUShort()] || {};
13713    p.skip('uShort', 1);  // reserved for flags; no values defined
13714
13715    inst.coordinates = {};
13716    for (var i = 0; i < axes.length; ++i) {
13717        inst.coordinates[axes[i].tag] = p.parseFixed();
13718    }
13719
13720    return inst;
13721}
13722
13723function makeFvarTable(fvar, names) {
13724    var result = new table.Table('fvar', [
13725        {name: 'version', type: 'ULONG', value: 0x10000},
13726        {name: 'offsetToData', type: 'USHORT', value: 0},
13727        {name: 'countSizePairs', type: 'USHORT', value: 2},
13728        {name: 'axisCount', type: 'USHORT', value: fvar.axes.length},
13729        {name: 'axisSize', type: 'USHORT', value: 20},
13730        {name: 'instanceCount', type: 'USHORT', value: fvar.instances.length},
13731        {name: 'instanceSize', type: 'USHORT', value: 4 + fvar.axes.length * 4}
13732    ]);
13733    result.offsetToData = result.sizeOf();
13734
13735    for (var i = 0; i < fvar.axes.length; i++) {
13736        result.fields = result.fields.concat(makeFvarAxis(i, fvar.axes[i], names));
13737    }
13738
13739    for (var j = 0; j < fvar.instances.length; j++) {
13740        result.fields = result.fields.concat(makeFvarInstance(j, fvar.instances[j], fvar.axes, names));
13741    }
13742
13743    return result;
13744}
13745
13746function parseFvarTable(data, start, names) {
13747    var p = new parse.Parser(data, start);
13748    var tableVersion = p.parseULong();
13749    check.argument(tableVersion === 0x00010000, 'Unsupported fvar table version.');
13750    var offsetToData = p.parseOffset16();
13751    // Skip countSizePairs.
vendor: 4,714 bytes, lines 13752-13884
13752    p.skip('uShort', 1);
13753    var axisCount = p.parseUShort();
13754    var axisSize = p.parseUShort();
13755    var instanceCount = p.parseUShort();
13756    var instanceSize = p.parseUShort();
13757
13758    var axes = [];
13759    for (var i = 0; i < axisCount; i++) {
13760        axes.push(parseFvarAxis(data, start + offsetToData + i * axisSize, names));
13761    }
13762
13763    var instances = [];
13764    var instanceStart = start + offsetToData + axisCount * axisSize;
13765    for (var j = 0; j < instanceCount; j++) {
13766        instances.push(parseFvarInstance(data, instanceStart + j * instanceSize, axes, names));
13767    }
13768
13769    return {axes: axes, instances: instances};
13770}
13771
13772var fvar = { make: makeFvarTable, parse: parseFvarTable };
13773
13774// The `GDEF` table contains various glyph properties
13775
13776var attachList = function() {
13777    return {
13778        coverage: this.parsePointer(Parser.coverage),
13779        attachPoints: this.parseList(Parser.pointer(Parser.uShortList))
13780    };
13781};
13782
13783var caretValue = function() {
13784    var format = this.parseUShort();
13785    check.argument(format === 1 || format === 2 || format === 3,
13786        'Unsupported CaretValue table version.');
13787    if (format === 1) {
13788        return { coordinate: this.parseShort() };
13789    } else if (format === 2) {
13790        return { pointindex: this.parseShort() };
13791    } else if (format === 3) {
13792        // Device / Variation Index tables unsupported
13793        return { coordinate: this.parseShort() };
13794    }
13795};
13796
13797var ligGlyph = function() {
13798    return this.parseList(Parser.pointer(caretValue));
13799};
13800
13801var ligCaretList = function() {
13802    return {
13803        coverage: this.parsePointer(Parser.coverage),
13804        ligGlyphs: this.parseList(Parser.pointer(ligGlyph))
13805    };
13806};
13807
13808var markGlyphSets = function() {
13809    this.parseUShort(); // Version
13810    return this.parseList(Parser.pointer(Parser.coverage));
13811};
13812
13813function parseGDEFTable(data, start) {
13814    start = start || 0;
13815    var p = new Parser(data, start);
13816    var tableVersion = p.parseVersion(1);
13817    check.argument(tableVersion === 1 || tableVersion === 1.2 || tableVersion === 1.3,
13818        'Unsupported GDEF table version.');
13819    var gdef = {
13820        version: tableVersion,
13821        classDef: p.parsePointer(Parser.classDef),
13822        attachList: p.parsePointer(attachList),
13823        ligCaretList: p.parsePointer(ligCaretList),
13824        markAttachClassDef: p.parsePointer(Parser.classDef)
13825    };
13826    if (tableVersion >= 1.2) {
13827        gdef.markGlyphSets = p.parsePointer(markGlyphSets);
13828    }
13829    return gdef;
13830}
13831var gdef = { parse: parseGDEFTable };
13832
13833// The `GPOS` table contains kerning pairs, among other things.
13834
13835var subtableParsers$1 = new Array(10);         // subtableParsers[0] is unused
13836
13837// https://docs.microsoft.com/en-us/typography/opentype/spec/gpos#lookup-type-1-single-adjustment-positioning-subtable
13838// this = Parser instance
13839subtableParsers$1[1] = function parseLookup1() {
13840    var start = this.offset + this.relativeOffset;
13841    var posformat = this.parseUShort();
13842    if (posformat === 1) {
13843        return {
13844            posFormat: 1,
13845            coverage: this.parsePointer(Parser.coverage),
13846            value: this.parseValueRecord()
13847        };
13848    } else if (posformat === 2) {
13849        return {
13850            posFormat: 2,
13851            coverage: this.parsePointer(Parser.coverage),
13852            values: this.parseValueRecordList()
13853        };
13854    }
13855    check.assert(false, '0x' + start.toString(16) + ': GPOS lookup type 1 format must be 1 or 2.');
13856};
13857
13858// https://docs.microsoft.com/en-us/typography/opentype/spec/gpos#lookup-type-2-pair-adjustment-positioning-subtable
13859subtableParsers$1[2] = function parseLookup2() {
13860    var start = this.offset + this.relativeOffset;
13861    var posFormat = this.parseUShort();
13862    check.assert(posFormat === 1 || posFormat === 2, '0x' + start.toString(16) + ': GPOS lookup type 2 format must be 1 or 2.');
13863    var coverage = this.parsePointer(Parser.coverage);
13864    var valueFormat1 = this.parseUShort();
13865    var valueFormat2 = this.parseUShort();
13866    if (posFormat === 1) {
13867        // Adjustments for Glyph Pairs
13868        return {
13869            posFormat: posFormat,
13870            coverage: coverage,
13871            valueFormat1: valueFormat1,
13872            valueFormat2: valueFormat2,
13873            pairSets: this.parseList(Parser.pointer(Parser.list(function() {
13874                return {        // pairValueRecord
13875                    secondGlyph: this.parseUShort(),
13876                    value1: this.parseValueRecord(valueFormat1),
13877                    value2: this.parseValueRecord(valueFormat2)
13878                };
13879            })))
13880        };
13881    } else if (posFormat === 2) {
13882        var classDef1 = this.parsePointer(Parser.classDef);
13883        var classDef2 = this.parsePointer(Parser.classDef);
13884        var class1Count = this.parseUShort();
13885        var class2Count = this.parseUShort();
13886        return {
13887            // Class Pair Adjustment
13888            posFormat: posFormat,
13889            coverage: coverage,
13890            valueFormat1: valueFormat1,
13891            valueFormat2: valueFormat2,
13892            classDef1: classDef1,
13893            classDef2: classDef2,
13894            class1Count: class1Count,
13895            class2Count: class2Count,
13896            classRecords: this.parseList(class1Count, Parser.list(class2Count, function() {
13897                return {
13898                    value1: this.parseValueRecord(valueFormat1),
13899                    value2: this.parseValueRecord(valueFormat2)
13900                };
13901            }))
13902        };
13903    }
13904};
13905
13906subtableParsers$1[3] = function parseLookup3() { return { error: 'GPOS Lookup 3 not supported' }; };
13907subtableParsers$1[4] = function parseLookup4() { return { error: 'GPOS Lookup 4 not supported' }; };
13908subtableParsers$1[5] = function parseLookup5() { return { error: 'GPOS Lookup 5 not supported' }; };
13909subtableParsers$1[6] = function parseLookup6() { return { error: 'GPOS Lookup 6 n
13909ot supported' }; };
13910subtableParsers$1[7] = function parseLookup7() { return { error: 'GPOS Lookup 7 not supported' }; };
13911subtableParsers$1[8] = function parseLookup8() { return { error: 'GPOS Lookup 8 not supported' }; };
13912subtableParsers$1[9] = function parseLookup9() { return { error: 'GPOS Lookup 9 not supported' }; };
13913
13914// https://docs.microsoft.com/en-us/typography/opentype/spec/gpos
13915function parseGposTable(data, start) {
13916    start = start || 0;
13917    var p = new Parser(data, start);
13918    var tableVersion = p.parseVersion(1);
13919    check.argument(tableVersion === 1 || tableVersion === 1.1, 'Unsupported GPOS table version ' + tableVersion);
13920
13921    if (tableVersion === 1) {
13922        return {
13923            version: tableVersion,
13924            scripts: p.parseScriptList(),
13925            features: p.parseFeatureList(),
13926            lookups: p.parseLookupList(subtableParsers$1)
13927        };
13928    } else {
13929        return {
13930            version: tableVersion,
13931            scripts: p.parseScriptList(),
13932            features: p.parseFeatureList(),
13933            lookups: p.parseLookupList(subtableParsers$1),
13934            variations: p.parseFeatureVariationsList()
13935        };
13936    }
13937
13938}
13939
13940// GPOS Writing //////////////////////////////////////////////
13941// NOT SUPPORTED
13942var subtableMakers$1 = new Array(10);
13943
13944function makeGposTable(gpos) {
13945    return new table.Table('GPOS', [
13946        {name: 'version', type: 'ULONG', value: 0x10000},
13947        {name: 'scripts', type: 'TABLE', value: new table.ScriptList(gpos.scripts)},
13948        {name: 'features', type: 'TABLE', value: new table.FeatureList(gpos.features)},
13949        {name: 'lookups', type: 'TABLE', value: new table.LookupList(gpos.lookups, subtableMakers$1)}
13950    ]);
13951}
13952
13953var gpos = { parse: parseGposTable, make: makeGposTable };
13954
13955// The `kern` table contains kerning pairs.
13956
13957function parseWindowsKernTable(p) {
13958    var pairs = {};
13959    // Skip nTables.
13960    p.skip('uShort');
13961    var subtableVersion = p.parseUShort();
13962    check.argument(subtableVersion === 0, 'Unsupported kern sub-table version.');
13963    // Skip subtableLength, subtableCoverage
13964    p.skip('uShort', 2);
13965    var nPairs = p.parseUShort();
13966    // Skip searchRange, entrySelector, rangeShift.
13967    p.skip('uShort', 3);
13968    for (var i = 0; i < nPairs; i += 1) {
13969        var leftIndex = p.parseUShort();
13970        var rightIndex = p.parseUShort();
13971        var value = p.parseShort();
13972        pairs[leftIndex + ',' + rightIndex] = value;
13973    }
13974    return pairs;
13975}
13976
13977function parseMacKernTable(p) {
13978    var pairs = {};
13979    // The Mac kern table stores the version as a fixed (32 bits) but we only loaded the first 16 bits.
13980    // Skip the rest.
13981    p.skip('uShort');
13982    var nTables = p.parseULong();
13983    //check.argument(nTables === 1, 'Only 1 subtable is supported (got ' + nTables + ').');
13984    if (nTables > 1) {
13985        console.warn('Only the first kern subtable is supported.');
13986    }
13987    p.skip('uLong');
13988    var coverage = p.parseUShort();
13989    var subtableVersion = coverage & 0xFF;
13990    p.skip('uShort');
13991    if (subtableVersion === 0) {
13992        var nPairs = p.parseUShort();
13993        // Skip searchRange, entrySelector, rangeShift.
13994        p.skip('uShort', 3);
13995        for (var i = 0; i < nPairs; i += 1) {
13996            var leftIndex = p.parseUShort();
13997            var rightIndex = p.parseUShort();
13998            var value = p.parseShort();
13999            pairs[leftIndex + ',' + rightIndex] = value;
14000        }
14001    }
14002    return pairs;
14003}
14004
14005// Parse the `kern` table which contains kerning pairs.
14006function parseKernTable(data, start) {
14007    var p = new parse.Parser(data, start);
14008    var tableVersion = p.parseUShort();
14009    if (tableVersion === 0) {
14010        return parseWindowsKernTable(p);
14011    } else if (tableVersion === 1) {
14012        return parseMacKernTable(p);
14013    } else {
14014        throw new Error('Unsupported kern table version (' + tableVersion + ').');
14015    }
14016}
14017
14018var kern = { parse: parseKernTable };
14019
14020// The `loca` table stores the offsets to the locations of the glyphs in the font.
14021
14022// Parse the `loca` table. This table stores the offsets to the locations of the glyphs in the font,
14023// relative to the beginning of the glyphData table.
14024// The number of glyphs stored in the `loca` table is specified in the `maxp` table (under numGlyphs)
14025// The loca table has two versions: a short version where offsets are stored as uShorts, and a long
14026// version where offsets are stored as uLongs. The `head` table specifies which version to use
14027// (under indexToLocFormat).
14028function parseLocaTable(data, start, numGlyphs, shortVersion) {
14029    var p = new parse.Parser(data, start);
14030    var parseFn = shortVersion ? p.parseUShort : p.parseULong;
14031    // There is an extra entry after the last index element to compute the length of the last glyph.
14032    // That's why we use numGlyphs + 1.
14033    var glyphOffsets = [];
14034    for (var i = 0; i < numGlyphs + 1; i += 1) {
14035        var glyphOffset = parseFn.call(p);
14036        if (shortVersion) {
14037            // The short table version stores the actual offset divided by 2.
14038            glyphOffset *= 2;
14039        }
14040
14041        glyphOffsets.push(glyphOffset);
14042    }
14043
14044    return glyphOffsets;
14045}
14046
14047var loca = { parse: parseLocaTable };
14048
14049// opentype.js
14050
14051/**
14052 * The opentype library.
14053 * @namespace opentype
14054 */
14055
14056// File loaders /////////////////////////////////////////////////////////
14057/**
14058 * Loads a font from a file. The callback throws an error message as the first parameter if it fails
14059 * and the font as an ArrayBuffer in the second parameter if it succeeds.
14060 * @param  {string} path - The path of the file
14061 * @param  {Function} callback - The function to call when the font load completes
14062 */
14063function loadFromFile(path, callback) {
14064    var fs = require('fs');
14065    fs.readFile(path, function(err, buffer) {
14066        if (err) {
14067            return callback(err.message);
14068        }
14069
14070        callback(null, nodeBufferToArrayBuffer(buffer));
14071    });
14072}
14073/**
14074 * Loads a font from a URL. The callback throws an error message as the first parameter if it fails
14075 * and the font as an ArrayBuffer in the second parameter if it succeeds.
14076 * @param  {string} url - The URL of the font file.
14077 * @param  {Function} callback - The function to call when the font load completes
14078 */
14079function loadFromUrl(url, callback) {
14080    var request = new XMLHttpRequest();
14081    request.open('get', url, true);
14082    request.responseType = 'arraybuffer';
14083    request.onload = function() {
14084        if (request.response) {
14085            return callback(null, request.response);
14086        } else {
14087            return callback('Font could not be loaded: ' + request.statusText);
14088        }
14089    };
14090
14091    request.onerror = function () {
14092        callback('Font could not be loaded');
14093    };
14094
14095    request.send();
14096}
14097
14098// Table Directory Entries //////////////////////////////////////////////
14099/**
14100 * Parses OpenType table entries.
14101 * @param  {DataView}
14102 * @param  {Number}
14103 * @return {Object[]}
14104 */
14105function parseOpenTypeTableEntries(data, numTables) {
14106    var tableEntries = [];
14107    var p = 12;
14108    for (var i = 0; i < numTables; i += 1) {
14109        var tag = parse.getTag(data, p);
14110        var checksum = parse.getULong(data, p + 4);
14111        var offset = parse.getULong(data, p + 8);
14112        var length = parse.getULong(data, p + 12);
14113        tableEntries.push({tag: tag, checksum: checksum, offset: offset, length: length, compression: false});
14114        p += 16;
14115    }
14116
14117    return tableEntries;
14118}
14119
14120/**
14121 * Parses WOFF table entries.
14122 * @param  {DataView}
14123 * @param  {Number}
14124 * @return {Object[]}
14125 */
14126function parseWOFFTableEntries(data, numTables) {
14127    var tableEntries = [];
14128    var p = 44; // offset to the first table directory entry.
14129    for (var i = 0; i < numTables; i += 1) {
14130        var tag = parse.getTag(data, p);
14131        var offset = parse.getULong(data, p + 4);
14132        var compLength = parse.getULong(data, p + 8);
14133        var origLength = parse.getULong(data, p + 12);
14134        var compression = (void 0);
14135        if (compLength < origLength) {
14136            compression = 'WOFF';
14137        } else {
14138            compression = false;
14139        }
14140
14141        tableEntries.push({tag: tag, offset: offset, compression: compression,
14142            compressedLength: compLength, length: origLength});
14143        p += 20;
14144    }
14145
14146    return tableEntries;
14147}
14148
14149/**
14150 * @typedef TableData
14151 * @type Object
14152 * @property {DataView} data - The DataView
14153 * @property {number} offset - The data offset.
14154 */
14155
14156/**
14157 * @param  {DataView}
14158 * @param  {Object}
14159 * @return {TableData}
14160 */
vendor: 8,474 bytes, lines 14161-14374
14161function uncompressTable(data, tableEntry) {
14162    if (tableEntry.compression === 'WOFF') {
14163        var inBuffer = new Uint8Array(data.buffer, tableEntry.offset + 2, tableEntry.compressedLength - 2);
14164        var outBuffer = new Uint8Array(tableEntry.length);
14165        tinyInflate(inBuffer, outBuffer);
14166        if (outBuffer.byteLength !== tableEntry.length) {
14167            throw new Error('Decompression error: ' + tableEntry.tag + ' decompressed length doesn\'t match recorded length');
14168        }
14169
14170        var view = new DataView(outBuffer.buffer, 0);
14171        return {data: view, offset: 0};
14172    } else {
14173        return {data: data, offset: tableEntry.offset};
14174    }
14175}
14176
14177// Public API ///////////////////////////////////////////////////////////
14178
14179/**
14180 * Parse the OpenType file data (as an ArrayBuffer) and return a Font object.
14181 * Throws an error if the font could not be parsed.
14182 * @param  {ArrayBuffer}
14183 * @param  {Object} opt - options for parsing
14184 * @return {opentype.Font}
14185 */
14186function parseBuffer(buffer, opt) {
14187    opt = (opt === undefined || opt === null) ?  {} : opt;
14188
14189    var indexToLocFormat;
14190    var ltagTable;
14191
14192    // Since the constructor can also be called to create new fonts from scratch, we indicate this
14193    // should be an empty font that we'll fill with our own data.
14194    var font = new Font({empty: true});
14195
14196    // OpenType fonts use big endian byte ordering.
14197    // We can't rely on typed array view types, because they operate with the endianness of the host computer.
14198    // Instead we use DataViews where we can specify endianness.
14199    var data = new DataView(buffer, 0);
14200    var numTables;
14201    var tableEntries = [];
14202    var signature = parse.getTag(data, 0);
14203    if (signature === String.fromCharCode(0, 1, 0, 0) || signature === 'true' || signature === 'typ1') {
14204        font.outlinesFormat = 'truetype';
14205        numTables = parse.getUShort(data, 4);
14206        tableEntries = parseOpenTypeTableEntries(data, numTables);
14207    } else if (signature === 'OTTO') {
14208        font.outlinesFormat = 'cff';
14209        numTables = parse.getUShort(data, 4);
14210        tableEntries = parseOpenTypeTableEntries(data, numTables);
14211    } else if (signature === 'wOFF') {
14212        var flavor = parse.getTag(data, 4);
14213        if (flavor === String.fromCharCode(0, 1, 0, 0)) {
14214            font.outlinesFormat = 'truetype';
14215        } else if (flavor === 'OTTO') {
14216            font.outlinesFormat = 'cff';
14217        } else {
14218            throw new Error('Unsupported OpenType flavor ' + signature);
14219        }
14220
14221        numTables = parse.getUShort(data, 12);
14222        tableEntries = parseWOFFTableEntries(data, numTables);
14223    } else {
14224        throw new Error('Unsupported OpenType signature ' + signature);
14225    }
14226
14227    var cffTableEntry;
14228    var fvarTableEntry;
14229    var glyfTableEntry;
14230    var gdefTableEntry;
14231    var gposTableEntry;
14232    var gsubTableEntry;
14233    var hmtxTableEntry;
14234    var kernTableEntry;
14235    var locaTableEntry;
14236    var nameTableEntry;
14237    var metaTableEntry;
14238    var p;
14239
14240    for (var i = 0; i < numTables; i += 1) {
14241        var tableEntry = tableEntries[i];
14242        var table = (void 0);
14243        switch (tableEntry.tag) {
14244            case 'cmap':
14245                table = uncompressTable(data, tableEntry);
14246                font.tables.cmap = cmap.parse(table.data, table.offset);
14247                font.encoding = new CmapEncoding(font.tables.cmap);
14248                break;
14249            case 'cvt ' :
14250                table = uncompressTable(data, tableEntry);
14251                p = new parse.Parser(table.data, table.offset);
14252                font.tables.cvt = p.parseShortList(tableEntry.length / 2);
14253                break;
14254            case 'fvar':
14255                fvarTableEntry = tableEntry;
14256                break;
14257            case 'fpgm' :
14258                table = uncompressTable(data, tableEntry);
14259                p = new parse.Parser(table.data, table.offset);
14260                font.tables.fpgm = p.parseByteList(tableEntry.length);
14261                break;
14262            case 'head':
14263                table = uncompressTable(data, tableEntry);
14264                font.tables.head = head.parse(table.data, table.offset);
14265                font.unitsPerEm = font.tables.head.unitsPerEm;
14266                indexToLocFormat = font.tables.head.indexToLocFormat;
14267                break;
14268            case 'hhea':
14269                table = uncompressTable(data, tableEntry);
14270                font.tables.hhea = hhea.parse(table.data, table.offset);
14271                font.ascender = font.tables.hhea.ascender;
14272                font.descender = font.tables.hhea.descender;
14273                font.numberOfHMetrics = font.tables.hhea.numberOfHMetrics;
14274                break;
14275            case 'hmtx':
14276                hmtxTableEntry = tableEntry;
14277                break;
14278            case 'ltag':
14279                table = uncompressTable(data, tableEntry);
14280                ltagTable = ltag.parse(table.data, table.offset);
14281                break;
14282            case 'maxp':
14283                table = uncompressTable(data, tableEntry);
14284                font.tables.maxp = maxp.parse(table.data, table.offset);
14285                font.numGlyphs = font.tables.maxp.numGlyphs;
14286                break;
14287            case 'name':
14288                nameTableEntry = tableEntry;
14289                break;
14290            case 'OS/2':
14291                table = uncompressTable(data, tableEntry);
14292                font.tables.os2 = os2.parse(table.data, table.offset);
14293                break;
14294            case 'post':
14295                table = uncompressTable(data, tableEntry);
14296                font.tables.post = post.parse(table.data, table.offset);
14297                font.glyphNames = new GlyphNames(font.tables.post);
14298                break;
14299            case 'prep' :
14300                table = uncompressTable(data, tableEntry);
14301                p = new parse.Parser(table.data, table.offset);
14302                font.tables.prep = p.parseByteList(tableEntry.length);
14303                break;
14304            case 'glyf':
14305                glyfTableEntry = tableEntry;
14306                break;
14307            case 'loca':
14308                locaTableEntry = tableEntry;
14309                break;
14310            case 'CFF ':
14311                cffTableEntry = tableEntry;
14312                break;
14313            case 'kern':
14314                kernTableEntry = tableEntry;
14315                break;
14316            case 'GDEF':
14317                gdefTableEntry = tableEntry;
14318                break;
14319            case 'GPOS':
14320                gposTableEntry = tableEntry;
14321                break;
14322            case 'GSUB':
14323                gsubTableEntry = tableEntry;
14324                break;
14325            case 'meta':
14326                metaTableEntry = tableEntry;
14327                break;
14328        }
14329    }
14330
14331    var nameTable = uncompressTable(data, nameTableEntry);
14332    font.tables.name = _name.parse(nameTable.data, nameTable.offset, ltagTable);
14333    font.names = font.tables.name;
14334
14335    if (glyfTableEntry && locaTableEntry) {
14336        var shortVersion = indexToLocFormat === 0;
14337        var locaTable = uncompressTable(data, locaTableEntry);
14338        var locaOffsets = loca.parse(locaTable.data, locaTable.offset, font.numGlyphs, shortVersion);
14339        var glyfTable = uncompressTable(data, glyfTableEntry);
14340        font.glyphs = glyf.parse(glyfTable.data, glyfTable.offset, locaOffsets, font, opt);
14341    } else if (cffTableEntry) {
14342        var cffTable = uncompressTable(data, cffTableEntry);
14343        cff.parse(cffTable.data, cffTable.offset, font, opt);
14344    } else {
14345        throw new Error('Font doesn\'t contain TrueType or CFF outlines.');
14346    }
14347
14348    var hmtxTable = uncompressTable(data, hmtxTableEntry);
14349    hmtx.parse(font, hmtxTable.data, hmtxTable.offset, font.numberOfHMetrics, font.numGlyphs, font.glyphs, opt);
14350    addGlyphNames(font, opt);
14351
14352    if (kernTableEntry) {
14353        var kernTable = uncompressTable(data, kernTableEntry);
14354        font.kerningPairs = kern.parse(kernTable.data, kernTable.offset);
14355    } else {
14356        font.kerningPairs = {};
14357    }
14358
14359    if (gdefTableEntry) {
14360        var gdefTable = uncompressTable(data, gdefTableEntry);
14361        font.tables.gdef = gdef.parse(gdefTable.data, gdefTable.offset);
14362    }
14363
14364    if (gposTableEntry) {
14365        var gposTable = uncompressTable(data, gposTableEntry);
14366        font.tables.gpos = gpos.parse(gposTable.data, gposTable.offset);
14367        font.position.init();
14368    }
14369
14370    if (gsubTableEntry) {
14371        var gsubTable = uncompressTable(data, gsubTableEntry);
14372        font.tables.gsub = gsub.parse(gsubTable.data, gsubTable.offset);
14373    }
14374
14375    if (fvarTableEntry) {
14376        var fvarTable = uncompressTable(data, fvarTableEntry);
14377        font.tables.fvar = fvar.parse(fvarTable.data, fvarTable.offset, font.names);
14378    }
14379
14380    if (metaTableEntry) {
14381        var metaTable = uncompressTable(data, metaTableEntry);
14382        font.tables.meta = meta.parse(metaTable.data, metaTable.offset);
14383        font.metas = font.tables.meta;
14384    }
14385
14386    return font;
14387}
14388
14389/**
14390 * Asynchronously load the font from a URL or a filesystem. When done, call the callback
14391 * with two arguments `(err, font)`. The `err` will be null on success,
14392 * the `font` is a Font object.
14393 * We use the node.js callback convention so that
14394 * opentype.js can integrate with frameworks like async.js.
14395 * @alias opentype.load
14396 * @param  {string} url - The URL of the font to load.
14397 * @param  {Function} callback - The callback.
14398 */
14399function load(url, callback, opt) {
14400    opt = (opt === undefined || opt === null) ?  {} : opt;
14401    var isNode = typeof window === 'undefined';
14402    var loadFn = isNode && !opt.isUrl ? loadFromFile : loadFromUrl;
14403
14404    return new Promise(function (resolve, reject) {
14405        loadFn(url, function(err, arrayBuffer) {
14406            if (err) {
14407                if (callback) {
14408                    return callback(err);
14409                } else {
14410                    reject(err);
14411                }
14412            }
14413            var font;
14414            try {
14415                font = parseBuffer(arrayBuffer, opt);
14416            } catch (e) {
14417                if (callback) {
14418                    return callback(e, null);
14419                } else {
14420                    reject(e);
14421                }
14422            }
14423            if (callback) {
14424                return callback(null, font);
14425            } else {
14426                resolve(font);
14427            }
14428        });
14429    });
14430}
14431
14432/**
14433 * Synchronously load the font from a URL or file.
14434 * When done, returns the font object or throws an error.
14435 * @alias opentype.loadSync
14436 * @param  {string} url - The URL of the font to load.
14437 * @param  {Object} opt - opt.lowMemory
14438 * @return {opentype.Font}
14439 */
14440function loadSync(url, opt) {
14441    var fs = require('fs');
14442    var buffer = fs.readFileSync(url);
14443    return parseBuffer(nodeBufferToArrayBuffer(buffer), opt);
14444}
14445
14446var opentype = /*#__PURE__*/Object.freeze({
14447	__proto__: null,
14448	Font: Font,
14449	Glyph: Glyph,
14450	Path: Path,
14451	BoundingBox: BoundingBox,
14452	_parse: parse,
14453	parse: parseBuffer,
14454	load: load,
14455	loadSync: loadSync
14456});
14457
14458export default opentype;
14459export { BoundingBox, Font, Glyph, Path, parse as _parse, load, loadSync, parseBuffer as parse };
14460//# sourceMappingURL=opentype.module.js.map

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.