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 '¿¡¬âÆââ«»â¦Â ÃÃÃÅÅââââââ÷âÿŸââ¬â¹âºï¬ï¬â¡Â·âââ°ÃÃÃÃÃÃÃÃÃÃÃÃÃÃÃıË˯ËË˸ËËË', 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.