/ node_modules / opentype.js / src / hintingtt.js
hintingtt.js
   1  /* A TrueType font hinting interpreter.
   2  *
   3  * (c) 2017 Axel Kittenberger
   4  *
   5  * This interpreter has been implemented according to this documentation:
   6  * https://developer.apple.com/fonts/TrueType-Reference-Manual/RM05/Chap5.html
   7  *
   8  * According to the documentation F24DOT6 values are used for pixels.
   9  * That means calculation is 1/64 pixel accurate and uses integer operations.
  10  * However, Javascript has floating point operations by default and only
  11  * those are available. One could make a case to simulate the 1/64 accuracy
  12  * exactly by truncating after every division operation
  13  * (for example with << 0) to get pixel exactly results as other TrueType
  14  * implementations. It may make sense since some fonts are pixel optimized
  15  * by hand using DELTAP instructions. The current implementation doesn't
  16  * and rather uses full floating point precision.
  17  *
  18  * xScale, yScale and rotation is currently ignored.
  19  *
  20  * A few non-trivial instructions are missing as I didn't encounter yet
  21  * a font that used them to test a possible implementation.
  22  *
  23  * Some fonts seem to use undocumented features regarding the twilight zone.
  24  * Only some of them are implemented as they were encountered.
  25  *
  26  * The exports.DEBUG statements are removed on the minified distribution file.
  27  */
  28  'use strict';
  29  
  30  let instructionTable;
  31  let exec;
  32  let execGlyph;
  33  let execComponent;
  34  
  35  /*
  36  * Creates a hinting object.
  37  *
  38  * There ought to be exactly one
  39  * for each truetype font that is used for hinting.
  40  */
  41  function Hinting(font) {
  42      // the font this hinting object is for
  43      this.font = font;
  44  
  45      // cached states
  46      this._fpgmState  =
  47      this._prepState  =
  48          undefined;
  49  
  50      // errorState
  51      // 0 ... all okay
  52      // 1 ... had an error in a glyf,
  53      //       continue working but stop spamming
  54      //       the console
  55      // 2 ... error at prep, stop hinting at this ppem
  56      // 3 ... error at fpeg, stop hinting for this font at all
  57      this._errorState = 0;
  58  }
  59  
  60  /*
  61  * Not rounding.
  62  */
  63  function roundOff(v) {
  64      return v;
  65  }
  66  
  67  /*
  68  * Rounding to grid.
  69  */
  70  function roundToGrid(v) {
  71      //Rounding in TT is supposed to "symmetrical around zero"
  72      return Math.sign(v) * Math.round(Math.abs(v));
  73  }
  74  
  75  /*
  76  * Rounding to double grid.
  77  */
  78  function roundToDoubleGrid(v) {
  79      return Math.sign(v) * Math.round(Math.abs(v * 2)) / 2;
  80  }
  81  
  82  /*
  83  * Rounding to half grid.
  84  */
  85  function roundToHalfGrid(v) {
  86      return Math.sign(v) * (Math.round(Math.abs(v) + 0.5) - 0.5);
  87  }
  88  
  89  /*
  90  * Rounding to up to grid.
  91  */
  92  function roundUpToGrid(v) {
  93      return Math.sign(v) * Math.ceil(Math.abs(v));
  94  }
  95  
  96  /*
  97  * Rounding to down to grid.
  98  */
  99  function roundDownToGrid(v) {
 100      return Math.sign(v) * Math.floor(Math.abs(v));
 101  }
 102  
 103  /*
 104  * Super rounding.
 105  */
 106  const roundSuper = function (v) {
 107      const period = this.srPeriod;
 108      let phase = this.srPhase;
 109      const threshold = this.srThreshold;
 110      let sign = 1;
 111  
 112      if (v < 0) {
 113          v = -v;
 114          sign = -1;
 115      }
 116  
 117      v += threshold - phase;
 118  
 119      v = Math.trunc(v / period) * period;
 120  
 121      v += phase;
 122  
 123      // according to http://xgridfit.sourceforge.net/round.html
 124      if (sign > 0 && v < 0) return phase;
 125      if (sign < 0 && v > 0) return -phase;
 126  
 127      return v * sign;
 128  };
 129  
 130  /*
 131  * Unit vector of x-axis.
 132  */
 133  const xUnitVector = {
 134      x: 1,
 135  
 136      y: 0,
 137  
 138      axis: 'x',
 139  
 140      // Gets the projected distance between two points.
 141      // o1/o2 ... if true, respective original position is used.
 142      distance: function (p1, p2, o1, o2) {
 143          return (o1 ? p1.xo : p1.x) - (o2 ? p2.xo : p2.x);
 144      },
 145  
 146      // Moves point p so the moved position has the same relative
 147      // position to the moved positions of rp1 and rp2 than the
 148      // original positions had.
 149      //
 150      // See APPENDIX on INTERPOLATE at the bottom of this file.
 151      interpolate: function (p, rp1, rp2, pv) {
 152          let do1;
 153          let do2;
 154          let doa1;
 155          let doa2;
 156          let dm1;
 157          let dm2;
 158          let dt;
 159  
 160          if (!pv || pv === this) {
 161              do1 = p.xo - rp1.xo;
 162              do2 = p.xo - rp2.xo;
 163              dm1 = rp1.x - rp1.xo;
 164              dm2 = rp2.x - rp2.xo;
 165              doa1 = Math.abs(do1);
 166              doa2 = Math.abs(do2);
 167              dt = doa1 + doa2;
 168  
 169              if (dt === 0) {
 170                  p.x = p.xo + (dm1 + dm2) / 2;
 171                  return;
 172              }
 173  
 174              p.x = p.xo + (dm1 * doa2 + dm2 * doa1) / dt;
 175              return;
 176          }
 177  
 178          do1 = pv.distance(p, rp1, true, true);
 179          do2 = pv.distance(p, rp2, true, true);
 180          dm1 = pv.distance(rp1, rp1, false, true);
 181          dm2 = pv.distance(rp2, rp2, false, true);
 182          doa1 = Math.abs(do1);
 183          doa2 = Math.abs(do2);
 184          dt = doa1 + doa2;
 185  
 186          if (dt === 0) {
 187              xUnitVector.setRelative(p, p, (dm1 + dm2) / 2, pv, true);
 188              return;
 189          }
 190  
 191          xUnitVector.setRelative(p, p, (dm1 * doa2 + dm2 * doa1) / dt, pv, true);
 192      },
 193  
 194      // Slope of line normal to this
 195      normalSlope: Number.NEGATIVE_INFINITY,
 196  
 197      // Sets the point 'p' relative to point 'rp'
 198      // by the distance 'd'.
 199      //
 200      // See APPENDIX on SETRELATIVE at the bottom of this file.
 201      //
 202      // p   ... point to set
 203      // rp  ... reference point
 204      // d   ... distance on projection vector
 205      // pv  ... projection vector (undefined = this)
 206      // org ... if true, uses the original position of rp as reference.
 207      setRelative: function (p, rp, d, pv, org) {
 208          if (!pv || pv === this) {
 209              p.x = (org ? rp.xo : rp.x) + d;
 210              return;
 211          }
 212  
 213          const rpx = org ? rp.xo : rp.x;
 214          const rpy = org ? rp.yo : rp.y;
 215          const rpdx = rpx + d * pv.x;
 216          const rpdy = rpy + d * pv.y;
 217  
 218          p.x = rpdx + (p.y - rpdy) / pv.normalSlope;
 219      },
 220  
 221      // Slope of vector line.
 222      slope: 0,
 223  
 224      // Touches the point p.
 225      touch: function (p) {
 226          p.xTouched = true;
 227      },
 228  
 229      // Tests if a point p is touched.
 230      touched: function (p) {
 231          return p.xTouched;
 232      },
 233  
 234      // Untouches the point p.
 235      untouch: function (p) {
 236          p.xTouched = false;
 237      }
 238  };
 239  
 240  /*
 241  * Unit vector of y-axis.
 242  */
 243  const yUnitVector = {
 244      x: 0,
 245  
 246      y: 1,
 247  
 248      axis: 'y',
 249  
 250      // Gets the projected distance between two points.
 251      // o1/o2 ... if true, respective original position is used.
 252      distance: function (p1, p2, o1, o2) {
 253          return (o1 ? p1.yo : p1.y) - (o2 ? p2.yo : p2.y);
 254      },
 255  
 256      // Moves point p so the moved position has the same relative
 257      // position to the moved positions of rp1 and rp2 than the
 258      // original positions had.
 259      //
 260      // See APPENDIX on INTERPOLATE at the bottom of this file.
 261      interpolate: function (p, rp1, rp2, pv) {
 262          let do1;
 263          let do2;
 264          let doa1;
 265          let doa2;
 266          let dm1;
 267          let dm2;
 268          let dt;
 269  
 270          if (!pv || pv === this) {
 271              do1 = p.yo - rp1.yo;
 272              do2 = p.yo - rp2.yo;
 273              dm1 = rp1.y - rp1.yo;
 274              dm2 = rp2.y - rp2.yo;
 275              doa1 = Math.abs(do1);
 276              doa2 = Math.abs(do2);
 277              dt = doa1 + doa2;
 278  
 279              if (dt === 0) {
 280                  p.y = p.yo + (dm1 + dm2) / 2;
 281                  return;
 282              }
 283  
 284              p.y = p.yo + (dm1 * doa2 + dm2 * doa1) / dt;
 285              return;
 286          }
 287  
 288          do1 = pv.distance(p, rp1, true, true);
 289          do2 = pv.distance(p, rp2, true, true);
 290          dm1 = pv.distance(rp1, rp1, false, true);
 291          dm2 = pv.distance(rp2, rp2, false, true);
 292          doa1 = Math.abs(do1);
 293          doa2 = Math.abs(do2);
 294          dt = doa1 + doa2;
 295  
 296          if (dt === 0) {
 297              yUnitVector.setRelative(p, p, (dm1 + dm2) / 2, pv, true);
 298              return;
 299          }
 300  
 301          yUnitVector.setRelative(p, p, (dm1 * doa2 + dm2 * doa1) / dt, pv, true);
 302      },
 303  
 304      // Slope of line normal to this.
 305      normalSlope: 0,
 306  
 307      // Sets the point 'p' relative to point 'rp'
 308      // by the distance 'd'
 309      //
 310      // See APPENDIX on SETRELATIVE at the bottom of this file.
 311      //
 312      // p   ... point to set
 313      // rp  ... reference point
 314      // d   ... distance on projection vector
 315      // pv  ... projection vector (undefined = this)
 316      // org ... if true, uses the original position of rp as reference.
 317      setRelative: function (p, rp, d, pv, org) {
 318          if (!pv || pv === this) {
 319              p.y = (org ? rp.yo : rp.y) + d;
 320              return;
 321          }
 322  
 323          const rpx = org ? rp.xo : rp.x;
 324          const rpy = org ? rp.yo : rp.y;
 325          const rpdx = rpx + d * pv.x;
 326          const rpdy = rpy + d * pv.y;
 327  
 328          p.y = rpdy + pv.normalSlope * (p.x - rpdx);
 329      },
 330  
 331      // Slope of vector line.
 332      slope: Number.POSITIVE_INFINITY,
 333  
 334      // Touches the point p.
 335      touch: function (p) {
 336          p.yTouched = true;
 337      },
 338  
 339      // Tests if a point p is touched.
 340      touched: function (p) {
 341          return p.yTouched;
 342      },
 343  
 344      // Untouches the point p.
 345      untouch: function (p) {
 346          p.yTouched = false;
 347      }
 348  };
 349  
 350  Object.freeze(xUnitVector);
 351  Object.freeze(yUnitVector);
 352  
 353  /*
 354  * Creates a unit vector that is not x- or y-axis.
 355  */
 356  function UnitVector(x, y) {
 357      this.x = x;
 358      this.y = y;
 359      this.axis = undefined;
 360      this.slope = y / x;
 361      this.normalSlope = -x / y;
 362      Object.freeze(this);
 363  }
 364  
 365  /*
 366  * Gets the projected distance between two points.
 367  * o1/o2 ... if true, respective original position is used.
 368  */
 369  UnitVector.prototype.distance = function(p1, p2, o1, o2) {
 370      return (
 371          this.x * xUnitVector.distance(p1, p2, o1, o2) +
 372          this.y * yUnitVector.distance(p1, p2, o1, o2)
 373      );
 374  };
 375  
 376  /*
 377  * Moves point p so the moved position has the same relative
 378  * position to the moved positions of rp1 and rp2 than the
 379  * original positions had.
 380  *
 381  * See APPENDIX on INTERPOLATE at the bottom of this file.
 382  */
 383  UnitVector.prototype.interpolate = function(p, rp1, rp2, pv) {
 384      let dm1;
 385      let dm2;
 386      let do1;
 387      let do2;
 388      let doa1;
 389      let doa2;
 390      let dt;
 391  
 392      do1 = pv.distance(p, rp1, true, true);
 393      do2 = pv.distance(p, rp2, true, true);
 394      dm1 = pv.distance(rp1, rp1, false, true);
 395      dm2 = pv.distance(rp2, rp2, false, true);
 396      doa1 = Math.abs(do1);
 397      doa2 = Math.abs(do2);
 398      dt = doa1 + doa2;
 399  
 400      if (dt === 0) {
 401          this.setRelative(p, p, (dm1 + dm2) / 2, pv, true);
 402          return;
 403      }
 404  
 405      this.setRelative(p, p, (dm1 * doa2 + dm2 * doa1) / dt, pv, true);
 406  };
 407  
 408  /*
 409  * Sets the point 'p' relative to point 'rp'
 410  * by the distance 'd'
 411  *
 412  * See APPENDIX on SETRELATIVE at the bottom of this file.
 413  *
 414  * p   ...  point to set
 415  * rp  ... reference point
 416  * d   ... distance on projection vector
 417  * pv  ... projection vector (undefined = this)
 418  * org ... if true, uses the original position of rp as reference.
 419  */
 420  UnitVector.prototype.setRelative = function(p, rp, d, pv, org) {
 421      pv = pv || this;
 422  
 423      const rpx = org ? rp.xo : rp.x;
 424      const rpy = org ? rp.yo : rp.y;
 425      const rpdx = rpx + d * pv.x;
 426      const rpdy = rpy + d * pv.y;
 427  
 428      const pvns = pv.normalSlope;
 429      const fvs = this.slope;
 430  
 431      const px = p.x;
 432      const py = p.y;
 433  
 434      p.x = (fvs * px - pvns * rpdx + rpdy - py) / (fvs - pvns);
 435      p.y = fvs * (p.x - px) + py;
 436  };
 437  
 438  /*
 439  * Touches the point p.
 440  */
 441  UnitVector.prototype.touch = function(p) {
 442      p.xTouched = true;
 443      p.yTouched = true;
 444  };
 445  
 446  /*
 447  * Returns a unit vector with x/y coordinates.
 448  */
 449  function getUnitVector(x, y) {
 450      const d = Math.sqrt(x * x + y * y);
 451  
 452      x /= d;
 453      y /= d;
 454  
 455      if (x === 1 && y === 0) return xUnitVector;
 456      else if (x === 0 && y === 1) return yUnitVector;
 457      else return new UnitVector(x, y);
 458  }
 459  
 460  /*
 461  * Creates a point in the hinting engine.
 462  */
 463  function HPoint(
 464      x,
 465      y,
 466      lastPointOfContour,
 467      onCurve
 468  ) {
 469      this.x = this.xo = Math.round(x * 64) / 64; // hinted x value and original x-value
 470      this.y = this.yo = Math.round(y * 64) / 64; // hinted y value and original y-value
 471  
 472      this.lastPointOfContour = lastPointOfContour;
 473      this.onCurve = onCurve;
 474      this.prevPointOnContour = undefined;
 475      this.nextPointOnContour = undefined;
 476      this.xTouched = false;
 477      this.yTouched = false;
 478  
 479      Object.preventExtensions(this);
 480  }
 481  
 482  /*
 483  * Returns the next touched point on the contour.
 484  *
 485  * v  ... unit vector to test touch axis.
 486  */
 487  HPoint.prototype.nextTouched = function(v) {
 488      let p = this.nextPointOnContour;
 489  
 490      while (!v.touched(p) && p !== this) p = p.nextPointOnContour;
 491  
 492      return p;
 493  };
 494  
 495  /*
 496  * Returns the previous touched point on the contour
 497  *
 498  * v  ... unit vector to test touch axis.
 499  */
 500  HPoint.prototype.prevTouched = function(v) {
 501      let p = this.prevPointOnContour;
 502  
 503      while (!v.touched(p) && p !== this) p = p.prevPointOnContour;
 504  
 505      return p;
 506  };
 507  
 508  /*
 509  * The zero point.
 510  */
 511  const HPZero = Object.freeze(new HPoint(0, 0));
 512  
 513  /*
 514  * The default state of the interpreter.
 515  *
 516  * Note: Freezing the defaultState and then deriving from it
 517  * makes the V8 Javascript engine going awkward,
 518  * so this is avoided, albeit the defaultState shouldn't
 519  * ever change.
 520  */
 521  const defaultState = {
 522      cvCutIn: 17 / 16,    // control value cut in
 523      deltaBase: 9,
 524      deltaShift: 0.125,
 525      loop: 1,             // loops some instructions
 526      minDis: 1,           // minimum distance
 527      autoFlip: true
 528  };
 529  
 530  /*
 531  * The current state of the interpreter.
 532  *
 533  * env  ... 'fpgm' or 'prep' or 'glyf'
 534  * prog ... the program
 535  */
 536  function State(env, prog) {
 537      this.env = env;
 538      this.stack = [];
 539      this.prog = prog;
 540  
 541      switch (env) {
 542          case 'glyf' :
 543              this.zp0 = this.zp1 = this.zp2 = 1;
 544              this.rp0 = this.rp1 = this.rp2 = 0;
 545              /* fall through */
 546          case 'prep' :
 547              this.fv = this.pv = this.dpv = xUnitVector;
 548              this.round = roundToGrid;
 549      }
 550  }
 551  
 552  /*
 553  * Executes a glyph program.
 554  *
 555  * This does the hinting for each glyph.
 556  *
 557  * Returns an array of moved points.
 558  *
 559  * glyph: the glyph to hint
 560  * ppem: the size the glyph is rendered for
 561  */
 562  Hinting.prototype.exec = function(glyph, ppem) {
 563      if (typeof ppem !== 'number') {
 564          throw new Error('Point size is not a number!');
 565      }
 566  
 567      // Received a fatal error, don't do any hinting anymore.
 568      if (this._errorState > 2) return;
 569  
 570      const font = this.font;
 571      let prepState = this._prepState;
 572  
 573      if (!prepState || prepState.ppem !== ppem) {
 574          let fpgmState = this._fpgmState;
 575  
 576          if (!fpgmState) {
 577              // Executes the fpgm state.
 578              // This is used by fonts to define functions.
 579              State.prototype = defaultState;
 580  
 581              fpgmState =
 582              this._fpgmState =
 583                  new State('fpgm', font.tables.fpgm);
 584  
 585              fpgmState.funcs = [ ];
 586              fpgmState.font = font;
 587  
 588              if (exports.DEBUG) {
 589                  console.log('---EXEC FPGM---');
 590                  fpgmState.step = -1;
 591              }
 592  
 593              try {
 594                  exec(fpgmState);
 595              } catch (e) {
 596                  console.log('Hinting error in FPGM:' + e);
 597                  this._errorState = 3;
 598                  return;
 599              }
 600          }
 601  
 602          // Executes the prep program for this ppem setting.
 603          // This is used by fonts to set cvt values
 604          // depending on to be rendered font size.
 605  
 606          State.prototype = fpgmState;
 607          prepState =
 608          this._prepState =
 609              new State('prep', font.tables.prep);
 610  
 611          prepState.ppem = ppem;
 612  
 613          // Creates a copy of the cvt table
 614          // and scales it to the current ppem setting.
 615          const oCvt = font.tables.cvt;
 616          if (oCvt) {
 617              const cvt = prepState.cvt = new Array(oCvt.length);
 618              const scale = ppem / font.unitsPerEm;
 619              for (let c = 0; c < oCvt.length; c++) {
 620                  cvt[c] = oCvt[c] * scale;
 621              }
 622          } else {
 623              prepState.cvt = [];
 624          }
 625  
 626          if (exports.DEBUG) {
 627              console.log('---EXEC PREP---');
 628              prepState.step = -1;
 629          }
 630  
 631          try {
 632              exec(prepState);
 633          } catch (e) {
 634              if (this._errorState < 2) {
 635                  console.log('Hinting error in PREP:' + e);
 636              }
 637              this._errorState = 2;
 638          }
 639      }
 640  
 641      if (this._errorState > 1) return;
 642  
 643      try {
 644          return execGlyph(glyph, prepState);
 645      } catch (e) {
 646          if (this._errorState < 1) {
 647              console.log('Hinting error:' + e);
 648              console.log('Note: further hinting errors are silenced');
 649          }
 650          this._errorState = 1;
 651          return undefined;
 652      }
 653  };
 654  
 655  /*
 656  * Executes the hinting program for a glyph.
 657  */
 658  execGlyph = function(glyph, prepState) {
 659      // original point positions
 660      const xScale = prepState.ppem / prepState.font.unitsPerEm;
 661      const yScale = xScale;
 662      let components = glyph.components;
 663      let contours;
 664      let gZone;
 665      let state;
 666  
 667      State.prototype = prepState;
 668      if (!components) {
 669          state = new State('glyf', glyph.instructions);
 670          if (exports.DEBUG) {
 671              console.log('---EXEC GLYPH---');
 672              state.step = -1;
 673          }
 674          execComponent(glyph, state, xScale, yScale);
 675          gZone = state.gZone;
 676      } else {
 677          const font = prepState.font;
 678          gZone = [];
 679          contours = [];
 680          for (let i = 0; i < components.length; i++) {
 681              const c = components[i];
 682              const cg = font.glyphs.get(c.glyphIndex);
 683  
 684              state = new State('glyf', cg.instructions);
 685  
 686              if (exports.DEBUG) {
 687                  console.log('---EXEC COMP ' + i + '---');
 688                  state.step = -1;
 689              }
 690  
 691              execComponent(cg, state, xScale, yScale);
 692              // appends the computed points to the result array
 693              // post processes the component points
 694              const dx = Math.round(c.dx * xScale);
 695              const dy = Math.round(c.dy * yScale);
 696              const gz = state.gZone;
 697              const cc = state.contours;
 698              for (let pi = 0; pi < gz.length; pi++) {
 699                  const p = gz[pi];
 700                  p.xTouched = p.yTouched = false;
 701                  p.xo = p.x = p.x + dx;
 702                  p.yo = p.y = p.y + dy;
 703              }
 704  
 705              const gLen = gZone.length;
 706              gZone.push.apply(gZone, gz);
 707              for (let j = 0; j < cc.length; j++) {
 708                  contours.push(cc[j] + gLen);
 709              }
 710          }
 711  
 712          if (glyph.instructions && !state.inhibitGridFit) {
 713              // the composite has instructions on its own
 714              state = new State('glyf', glyph.instructions);
 715  
 716              state.gZone = state.z0 = state.z1 = state.z2 = gZone;
 717  
 718              state.contours = contours;
 719  
 720              // note: HPZero cannot be used here, since
 721              //       the point might be modified
 722              gZone.push(
 723                  new HPoint(0, 0),
 724                  new HPoint(Math.round(glyph.advanceWidth * xScale), 0)
 725              );
 726  
 727              if (exports.DEBUG) {
 728                  console.log('---EXEC COMPOSITE---');
 729                  state.step = -1;
 730              }
 731  
 732              exec(state);
 733  
 734              gZone.length -= 2;
 735          }
 736      }
 737  
 738      return gZone;
 739  };
 740  
 741  /*
 742  * Executes the hinting program for a component of a multi-component glyph
 743  * or of the glyph itself by a non-component glyph.
 744  */
 745  execComponent = function(glyph, state, xScale, yScale)
 746  {
 747      const points = glyph.points || [];
 748      const pLen = points.length;
 749      const gZone = state.gZone = state.z0 = state.z1 = state.z2 = [];
 750      const contours = state.contours = [];
 751  
 752      // Scales the original points and
 753      // makes copies for the hinted points.
 754      let cp; // current point
 755      for (let i = 0; i < pLen; i++) {
 756          cp = points[i];
 757  
 758          gZone[i] = new HPoint(
 759              cp.x * xScale,
 760              cp.y * yScale,
 761              cp.lastPointOfContour,
 762              cp.onCurve
 763          );
 764      }
 765  
 766      // Chain links the contours.
 767      let sp; // start point
 768      let np; // next point
 769  
 770      for (let i = 0; i < pLen; i++) {
 771          cp = gZone[i];
 772  
 773          if (!sp) {
 774              sp = cp;
 775              contours.push(i);
 776          }
 777  
 778          if (cp.lastPointOfContour) {
 779              cp.nextPointOnContour = sp;
 780              sp.prevPointOnContour = cp;
 781              sp = undefined;
 782          } else {
 783              np = gZone[i + 1];
 784              cp.nextPointOnContour = np;
 785              np.prevPointOnContour = cp;
 786          }
 787      }
 788  
 789      if (state.inhibitGridFit) return;
 790  
 791      gZone.push(
 792          new HPoint(0, 0),
 793          new HPoint(Math.round(glyph.advanceWidth * xScale), 0)
 794      );
 795  
 796      exec(state);
 797  
 798      // Removes the extra points.
 799      gZone.length -= 2;
 800  
 801      if (exports.DEBUG) {
 802          console.log('FINISHED GLYPH', state.stack);
 803          for (let i = 0; i < pLen; i++) {
 804              console.log(i, gZone[i].x, gZone[i].y);
 805          }
 806      }
 807  };
 808  
 809  /*
 810  * Executes the program loaded in state.
 811  */
 812  exec = function(state) {
 813      let prog = state.prog;
 814  
 815      if (!prog) return;
 816  
 817      const pLen = prog.length;
 818      let ins;
 819  
 820      for (state.ip = 0; state.ip < pLen; state.ip++) {
 821          if (exports.DEBUG) state.step++;
 822          ins = instructionTable[prog[state.ip]];
 823  
 824          if (!ins) {
 825              throw new Error(
 826                  'unknown instruction: 0x' +
 827                  Number(prog[state.ip]).toString(16)
 828              );
 829          }
 830  
 831          ins(state);
 832  
 833          // very extensive debugging for each step
 834          /*
 835          if (exports.DEBUG) {
 836              var da;
 837              if (state.gZone) {
 838                  da = [];
 839                  for (let i = 0; i < state.gZone.length; i++)
 840                  {
 841                      da.push(i + ' ' +
 842                          state.gZone[i].x * 64 + ' ' +
 843                          state.gZone[i].y * 64 + ' ' +
 844                          (state.gZone[i].xTouched ? 'x' : '') +
 845                          (state.gZone[i].yTouched ? 'y' : '')
 846                      );
 847                  }
 848                  console.log('GZ', da);
 849              }
 850  
 851              if (state.tZone) {
 852                  da = [];
 853                  for (let i = 0; i < state.tZone.length; i++) {
 854                      da.push(i + ' ' +
 855                          state.tZone[i].x * 64 + ' ' +
 856                          state.tZone[i].y * 64 + ' ' +
 857                          (state.tZone[i].xTouched ? 'x' : '') +
 858                          (state.tZone[i].yTouched ? 'y' : '')
 859                      );
 860                  }
 861                  console.log('TZ', da);
 862              }
 863  
 864              if (state.stack.length > 10) {
 865                  console.log(
 866                      state.stack.length,
 867                      '...', state.stack.slice(state.stack.length - 10)
 868                  );
 869              } else {
 870                  console.log(state.stack.length, state.stack);
 871              }
 872          }
 873          */
 874      }
 875  };
 876  
 877  /*
 878  * Initializes the twilight zone.
 879  *
 880  * This is only done if a SZPx instruction
 881  * refers to the twilight zone.
 882  */
 883  function initTZone(state)
 884  {
 885      const tZone = state.tZone = new Array(state.gZone.length);
 886  
 887      // no idea if this is actually correct...
 888      for (let i = 0; i < tZone.length; i++)
 889      {
 890          tZone[i] = new HPoint(0, 0);
 891      }
 892  }
 893  
 894  /*
 895  * Skips the instruction pointer ahead over an IF/ELSE block.
 896  * handleElse .. if true breaks on matching ELSE
 897  */
 898  function skip(state, handleElse)
 899  {
 900      const prog = state.prog;
 901      let ip = state.ip;
 902      let nesting = 1;
 903      let ins;
 904  
 905      do {
 906          ins = prog[++ip];
 907          if (ins === 0x58) // IF
 908              nesting++;
 909          else if (ins === 0x59) // EIF
 910              nesting--;
 911          else if (ins === 0x40) // NPUSHB
 912              ip += prog[ip + 1] + 1;
 913          else if (ins === 0x41) // NPUSHW
 914              ip += 2 * prog[ip + 1] + 1;
 915          else if (ins >= 0xB0 && ins <= 0xB7) // PUSHB
 916              ip += ins - 0xB0 + 1;
 917          else if (ins >= 0xB8 && ins <= 0xBF) // PUSHW
 918              ip += (ins - 0xB8 + 1) * 2;
 919          else if (handleElse && nesting === 1 && ins === 0x1B) // ELSE
 920              break;
 921      } while (nesting > 0);
 922  
 923      state.ip = ip;
 924  }
 925  
 926  /*----------------------------------------------------------*
 927  *          And then a lot of instructions...                *
 928  *----------------------------------------------------------*/
 929  
 930  // SVTCA[a] Set freedom and projection Vectors To Coordinate Axis
 931  // 0x00-0x01
 932  function SVTCA(v, state) {
 933      if (exports.DEBUG) console.log(state.step, 'SVTCA[' + v.axis + ']');
 934  
 935      state.fv = state.pv = state.dpv = v;
 936  }
 937  
 938  // SPVTCA[a] Set Projection Vector to Coordinate Axis
 939  // 0x02-0x03
 940  function SPVTCA(v, state) {
 941      if (exports.DEBUG) console.log(state.step, 'SPVTCA[' + v.axis + ']');
 942  
 943      state.pv = state.dpv = v;
 944  }
 945  
 946  // SFVTCA[a] Set Freedom Vector to Coordinate Axis
 947  // 0x04-0x05
 948  function SFVTCA(v, state) {
 949      if (exports.DEBUG) console.log(state.step, 'SFVTCA[' + v.axis + ']');
 950  
 951      state.fv = v;
 952  }
 953  
 954  // SPVTL[a] Set Projection Vector To Line
 955  // 0x06-0x07
 956  function SPVTL(a, state) {
 957      const stack = state.stack;
 958      const p2i = stack.pop();
 959      const p1i = stack.pop();
 960      const p2 = state.z2[p2i];
 961      const p1 = state.z1[p1i];
 962  
 963      if (exports.DEBUG) console.log('SPVTL[' + a + ']', p2i, p1i);
 964  
 965      let dx;
 966      let dy;
 967  
 968      if (!a) {
 969          dx = p1.x - p2.x;
 970          dy = p1.y - p2.y;
 971      } else {
 972          dx = p2.y - p1.y;
 973          dy = p1.x - p2.x;
 974      }
 975  
 976      state.pv = state.dpv = getUnitVector(dx, dy);
 977  }
 978  
 979  // SFVTL[a] Set Freedom Vector To Line
 980  // 0x08-0x09
 981  function SFVTL(a, state) {
 982      const stack = state.stack;
 983      const p2i = stack.pop();
 984      const p1i = stack.pop();
 985      const p2 = state.z2[p2i];
 986      const p1 = state.z1[p1i];
 987  
 988      if (exports.DEBUG) console.log('SFVTL[' + a + ']', p2i, p1i);
 989  
 990      let dx;
 991      let dy;
 992  
 993      if (!a) {
 994          dx = p1.x - p2.x;
 995          dy = p1.y - p2.y;
 996      } else {
 997          dx = p2.y - p1.y;
 998          dy = p1.x - p2.x;
 999      }
1000  
1001      state.fv = getUnitVector(dx, dy);
1002  }
1003  
1004  // SPVFS[] Set Projection Vector From Stack
1005  // 0x0A
1006  function SPVFS(state) {
1007      const stack = state.stack;
1008      const y = stack.pop();
1009      const x = stack.pop();
1010  
1011      if (exports.DEBUG) console.log(state.step, 'SPVFS[]', y, x);
1012  
1013      state.pv = state.dpv = getUnitVector(x, y);
1014  }
1015  
1016  // SFVFS[] Set Freedom Vector From Stack
1017  // 0x0B
1018  function SFVFS(state) {
1019      const stack = state.stack;
1020      const y = stack.pop();
1021      const x = stack.pop();
1022  
1023      if (exports.DEBUG) console.log(state.step, 'SPVFS[]', y, x);
1024  
1025      state.fv = getUnitVector(x, y);
1026  }
1027  
1028  // GPV[] Get Projection Vector
1029  // 0x0C
1030  function GPV(state) {
1031      const stack = state.stack;
1032      const pv = state.pv;
1033  
1034      if (exports.DEBUG) console.log(state.step, 'GPV[]');
1035  
1036      stack.push(pv.x * 0x4000);
1037      stack.push(pv.y * 0x4000);
1038  }
1039  
1040  // GFV[] Get Freedom Vector
1041  // 0x0C
1042  function GFV(state) {
1043      const stack = state.stack;
1044      const fv = state.fv;
1045  
1046      if (exports.DEBUG) console.log(state.step, 'GFV[]');
1047  
1048      stack.push(fv.x * 0x4000);
1049      stack.push(fv.y * 0x4000);
1050  }
1051  
1052  // SFVTPV[] Set Freedom Vector To Projection Vector
1053  // 0x0E
1054  function SFVTPV(state) {
1055      state.fv = state.pv;
1056  
1057      if (exports.DEBUG) console.log(state.step, 'SFVTPV[]');
1058  }
1059  
1060  // ISECT[] moves point p to the InterSECTion of two lines
1061  // 0x0F
1062  function ISECT(state)
1063  {
1064      const stack = state.stack;
1065      const pa0i = stack.pop();
1066      const pa1i = stack.pop();
1067      const pb0i = stack.pop();
1068      const pb1i = stack.pop();
1069      const pi = stack.pop();
1070      const z0 = state.z0;
1071      const z1 = state.z1;
1072      const pa0 = z0[pa0i];
1073      const pa1 = z0[pa1i];
1074      const pb0 = z1[pb0i];
1075      const pb1 = z1[pb1i];
1076      const p = state.z2[pi];
1077  
1078      if (exports.DEBUG) console.log('ISECT[], ', pa0i, pa1i, pb0i, pb1i, pi);
1079  
1080      // math from
1081      // en.wikipedia.org/wiki/Line%E2%80%93line_intersection#Given_two_points_on_each_line
1082  
1083      const x1 = pa0.x;
1084      const y1 = pa0.y;
1085      const x2 = pa1.x;
1086      const y2 = pa1.y;
1087      const x3 = pb0.x;
1088      const y3 = pb0.y;
1089      const x4 = pb1.x;
1090      const y4 = pb1.y;
1091  
1092      const div = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4);
1093      const f1 = x1 * y2 - y1 * x2;
1094      const f2 = x3 * y4 - y3 * x4;
1095  
1096      p.x = (f1 * (x3 - x4) - f2 * (x1 - x2)) / div;
1097      p.y = (f1 * (y3 - y4) - f2 * (y1 - y2)) / div;
1098  }
1099  
1100  // SRP0[] Set Reference Point 0
1101  // 0x10
1102  function SRP0(state) {
1103      state.rp0 = state.stack.pop();
1104  
1105      if (exports.DEBUG) console.log(state.step, 'SRP0[]', state.rp0);
1106  }
1107  
1108  // SRP1[] Set Reference Point 1
1109  // 0x11
1110  function SRP1(state) {
1111      state.rp1 = state.stack.pop();
1112  
1113      if (exports.DEBUG) console.log(state.step, 'SRP1[]', state.rp1);
1114  }
1115  
1116  // SRP1[] Set Reference Point 2
1117  // 0x12
1118  function SRP2(state) {
1119      state.rp2 = state.stack.pop();
1120  
1121      if (exports.DEBUG) console.log(state.step, 'SRP2[]', state.rp2);
1122  }
1123  
1124  // SZP0[] Set Zone Pointer 0
1125  // 0x13
1126  function SZP0(state) {
1127      const n = state.stack.pop();
1128  
1129      if (exports.DEBUG) console.log(state.step, 'SZP0[]', n);
1130  
1131      state.zp0 = n;
1132  
1133      switch (n) {
1134          case 0:
1135              if (!state.tZone) initTZone(state);
1136              state.z0 = state.tZone;
1137              break;
1138          case 1 :
1139              state.z0 = state.gZone;
1140              break;
1141          default :
1142              throw new Error('Invalid zone pointer');
1143      }
1144  }
1145  
1146  // SZP1[] Set Zone Pointer 1
1147  // 0x14
1148  function SZP1(state) {
1149      const n = state.stack.pop();
1150  
1151      if (exports.DEBUG) console.log(state.step, 'SZP1[]', n);
1152  
1153      state.zp1 = n;
1154  
1155      switch (n) {
1156          case 0:
1157              if (!state.tZone) initTZone(state);
1158              state.z1 = state.tZone;
1159              break;
1160          case 1 :
1161              state.z1 = state.gZone;
1162              break;
1163          default :
1164              throw new Error('Invalid zone pointer');
1165      }
1166  }
1167  
1168  // SZP2[] Set Zone Pointer 2
1169  // 0x15
1170  function SZP2(state) {
1171      const n = state.stack.pop();
1172  
1173      if (exports.DEBUG) console.log(state.step, 'SZP2[]', n);
1174  
1175      state.zp2 = n;
1176  
1177      switch (n) {
1178          case 0:
1179              if (!state.tZone) initTZone(state);
1180              state.z2 = state.tZone;
1181              break;
1182          case 1 :
1183              state.z2 = state.gZone;
1184              break;
1185          default :
1186              throw new Error('Invalid zone pointer');
1187      }
1188  }
1189  
1190  // SZPS[] Set Zone PointerS
1191  // 0x16
1192  function SZPS(state) {
1193      const n = state.stack.pop();
1194  
1195      if (exports.DEBUG) console.log(state.step, 'SZPS[]', n);
1196  
1197      state.zp0 = state.zp1 = state.zp2 = n;
1198  
1199      switch (n) {
1200          case 0:
1201              if (!state.tZone) initTZone(state);
1202              state.z0 = state.z1 = state.z2 = state.tZone;
1203              break;
1204          case 1 :
1205              state.z0 = state.z1 = state.z2 = state.gZone;
1206              break;
1207          default :
1208              throw new Error('Invalid zone pointer');
1209      }
1210  }
1211  
1212  // SLOOP[] Set LOOP variable
1213  // 0x17
1214  function SLOOP(state) {
1215      state.loop = state.stack.pop();
1216  
1217      if (exports.DEBUG) console.log(state.step, 'SLOOP[]', state.loop);
1218  }
1219  
1220  // RTG[] Round To Grid
1221  // 0x18
1222  function RTG(state) {
1223      if (exports.DEBUG) console.log(state.step, 'RTG[]');
1224  
1225      state.round = roundToGrid;
1226  }
1227  
1228  // RTHG[] Round To Half Grid
1229  // 0x19
1230  function RTHG(state) {
1231      if (exports.DEBUG) console.log(state.step, 'RTHG[]');
1232  
1233      state.round = roundToHalfGrid;
1234  }
1235  
1236  // SMD[] Set Minimum Distance
1237  // 0x1A
1238  function SMD(state) {
1239      const d = state.stack.pop();
1240  
1241      if (exports.DEBUG) console.log(state.step, 'SMD[]', d);
1242  
1243      state.minDis = d / 0x40;
1244  }
1245  
1246  // ELSE[] ELSE clause
1247  // 0x1B
1248  function ELSE(state) {
1249      // This instruction has been reached by executing a then branch
1250      // so it just skips ahead until matching EIF.
1251      //
1252      // In case the IF was negative the IF[] instruction already
1253      // skipped forward over the ELSE[]
1254  
1255      if (exports.DEBUG) console.log(state.step, 'ELSE[]');
1256  
1257      skip(state, false);
1258  }
1259  
1260  // JMPR[] JuMP Relative
1261  // 0x1C
1262  function JMPR(state) {
1263      const o = state.stack.pop();
1264  
1265      if (exports.DEBUG) console.log(state.step, 'JMPR[]', o);
1266  
1267      // A jump by 1 would do nothing.
1268      state.ip += o - 1;
1269  }
1270  
1271  // SCVTCI[] Set Control Value Table Cut-In
1272  // 0x1D
1273  function SCVTCI(state) {
1274      const n = state.stack.pop();
1275  
1276      if (exports.DEBUG) console.log(state.step, 'SCVTCI[]', n);
1277  
1278      state.cvCutIn = n / 0x40;
1279  }
1280  
1281  // DUP[] DUPlicate top stack element
1282  // 0x20
1283  function DUP(state) {
1284      const stack = state.stack;
1285  
1286      if (exports.DEBUG) console.log(state.step, 'DUP[]');
1287  
1288      stack.push(stack[stack.length - 1]);
1289  }
1290  
1291  // POP[] POP top stack element
1292  // 0x21
1293  function POP(state) {
1294      if (exports.DEBUG) console.log(state.step, 'POP[]');
1295  
1296      state.stack.pop();
1297  }
1298  
1299  // CLEAR[] CLEAR the stack
1300  // 0x22
1301  function CLEAR(state) {
1302      if (exports.DEBUG) console.log(state.step, 'CLEAR[]');
1303  
1304      state.stack.length = 0;
1305  }
1306  
1307  // SWAP[] SWAP the top two elements on the stack
1308  // 0x23
1309  function SWAP(state) {
1310      const stack = state.stack;
1311  
1312      const a = stack.pop();
1313      const b = stack.pop();
1314  
1315      if (exports.DEBUG) console.log(state.step, 'SWAP[]');
1316  
1317      stack.push(a);
1318      stack.push(b);
1319  }
1320  
1321  // DEPTH[] DEPTH of the stack
1322  // 0x24
1323  function DEPTH(state) {
1324      const stack = state.stack;
1325  
1326      if (exports.DEBUG) console.log(state.step, 'DEPTH[]');
1327  
1328      stack.push(stack.length);
1329  }
1330  
1331  // LOOPCALL[] LOOPCALL function
1332  // 0x2A
1333  function LOOPCALL(state) {
1334      const stack = state.stack;
1335      const fn = stack.pop();
1336      const c = stack.pop();
1337  
1338      if (exports.DEBUG) console.log(state.step, 'LOOPCALL[]', fn, c);
1339  
1340      // saves callers program
1341      const cip = state.ip;
1342      const cprog = state.prog;
1343  
1344      state.prog = state.funcs[fn];
1345  
1346      // executes the function
1347      for (let i = 0; i < c; i++) {
1348          exec(state);
1349  
1350          if (exports.DEBUG) console.log(
1351              ++state.step,
1352              i + 1 < c ? 'next loopcall' : 'done loopcall',
1353              i
1354          );
1355      }
1356  
1357      // restores the callers program
1358      state.ip = cip;
1359      state.prog = cprog;
1360  }
1361  
1362  // CALL[] CALL function
1363  // 0x2B
1364  function CALL(state) {
1365      const fn = state.stack.pop();
1366  
1367      if (exports.DEBUG) console.log(state.step, 'CALL[]', fn);
1368  
1369      // saves callers program
1370      const cip = state.ip;
1371      const cprog = state.prog;
1372  
1373      state.prog = state.funcs[fn];
1374  
1375      // executes the function
1376      exec(state);
1377  
1378      // restores the callers program
1379      state.ip = cip;
1380      state.prog = cprog;
1381  
1382      if (exports.DEBUG) console.log(++state.step, 'returning from', fn);
1383  }
1384  
1385  // CINDEX[] Copy the INDEXed element to the top of the stack
1386  // 0x25
1387  function CINDEX(state) {
1388      const stack = state.stack;
1389      const k = stack.pop();
1390  
1391      if (exports.DEBUG) console.log(state.step, 'CINDEX[]', k);
1392  
1393      // In case of k == 1, it copies the last element after popping
1394      // thus stack.length - k.
1395      stack.push(stack[stack.length - k]);
1396  }
1397  
1398  // MINDEX[] Move the INDEXed element to the top of the stack
1399  // 0x26
1400  function MINDEX(state) {
1401      const stack = state.stack;
1402      const k = stack.pop();
1403  
1404      if (exports.DEBUG) console.log(state.step, 'MINDEX[]', k);
1405  
1406      stack.push(stack.splice(stack.length - k, 1)[0]);
1407  }
1408  
1409  // FDEF[] Function DEFinition
1410  // 0x2C
1411  function FDEF(state) {
1412      if (state.env !== 'fpgm') throw new Error('FDEF not allowed here');
1413      const stack = state.stack;
1414      const prog = state.prog;
1415      let ip = state.ip;
1416  
1417      const fn = stack.pop();
1418      const ipBegin = ip;
1419  
1420      if (exports.DEBUG) console.log(state.step, 'FDEF[]', fn);
1421  
1422      while (prog[++ip] !== 0x2D);
1423  
1424      state.ip = ip;
1425      state.funcs[fn] = prog.slice(ipBegin + 1, ip);
1426  }
1427  
1428  // MDAP[a] Move Direct Absolute Point
1429  // 0x2E-0x2F
1430  function MDAP(round, state) {
1431      const pi = state.stack.pop();
1432      const p = state.z0[pi];
1433      const fv = state.fv;
1434      const pv = state.pv;
1435  
1436      if (exports.DEBUG) console.log(state.step, 'MDAP[' + round + ']', pi);
1437  
1438      let d = pv.distance(p, HPZero);
1439  
1440      if (round) d = state.round(d);
1441  
1442      fv.setRelative(p, HPZero, d, pv);
1443      fv.touch(p);
1444  
1445      state.rp0 = state.rp1 = pi;
1446  }
1447  
1448  // IUP[a] Interpolate Untouched Points through the outline
1449  // 0x30
1450  function IUP(v, state) {
1451      const z2 = state.z2;
1452      const pLen = z2.length - 2;
1453      let cp;
1454      let pp;
1455      let np;
1456  
1457      if (exports.DEBUG) console.log(state.step, 'IUP[' + v.axis + ']');
1458  
1459      for (let i = 0; i < pLen; i++) {
1460          cp = z2[i]; // current point
1461  
1462          // if this point has been touched go on
1463          if (v.touched(cp)) continue;
1464  
1465          pp = cp.prevTouched(v);
1466  
1467          // no point on the contour has been touched?
1468          if (pp === cp) continue;
1469  
1470          np = cp.nextTouched(v);
1471  
1472          if (pp === np) {
1473              // only one point on the contour has been touched
1474              // so simply moves the point like that
1475  
1476              v.setRelative(cp, cp, v.distance(pp, pp, false, true), v, true);
1477          }
1478  
1479          v.interpolate(cp, pp, np, v);
1480      }
1481  }
1482  
1483  // SHP[] SHift Point using reference point
1484  // 0x32-0x33
1485  function SHP(a, state) {
1486      const stack = state.stack;
1487      const rpi = a ? state.rp1 : state.rp2;
1488      const rp = (a ? state.z0 : state.z1)[rpi];
1489      const fv = state.fv;
1490      const pv = state.pv;
1491      let loop = state.loop;
1492      const z2 = state.z2;
1493  
1494      while (loop--)
1495      {
1496          const pi = stack.pop();
1497          const p = z2[pi];
1498  
1499          const d = pv.distance(rp, rp, false, true);
1500          fv.setRelative(p, p, d, pv);
1501          fv.touch(p);
1502  
1503          if (exports.DEBUG) {
1504              console.log(
1505                  state.step,
1506                  (state.loop > 1 ?
1507                     'loop ' + (state.loop - loop) + ': ' :
1508                     ''
1509                  ) +
1510                  'SHP[' + (a ? 'rp1' : 'rp2') + ']', pi
1511              );
1512          }
1513      }
1514  
1515      state.loop = 1;
1516  }
1517  
1518  // SHC[] SHift Contour using reference point
1519  // 0x36-0x37
1520  function SHC(a, state) {
1521      const stack = state.stack;
1522      const rpi = a ? state.rp1 : state.rp2;
1523      const rp = (a ? state.z0 : state.z1)[rpi];
1524      const fv = state.fv;
1525      const pv = state.pv;
1526      const ci = stack.pop();
1527      const sp = state.z2[state.contours[ci]];
1528      let p = sp;
1529  
1530      if (exports.DEBUG) console.log(state.step, 'SHC[' + a + ']', ci);
1531  
1532      const d = pv.distance(rp, rp, false, true);
1533  
1534      do {
1535          if (p !== rp) fv.setRelative(p, p, d, pv);
1536          p = p.nextPointOnContour;
1537      } while (p !== sp);
1538  }
1539  
1540  // SHZ[] SHift Zone using reference point
1541  // 0x36-0x37
1542  function SHZ(a, state) {
1543      const stack = state.stack;
1544      const rpi = a ? state.rp1 : state.rp2;
1545      const rp = (a ? state.z0 : state.z1)[rpi];
1546      const fv = state.fv;
1547      const pv = state.pv;
1548  
1549      const e = stack.pop();
1550  
1551      if (exports.DEBUG) console.log(state.step, 'SHZ[' + a + ']', e);
1552  
1553      let z;
1554      switch (e) {
1555          case 0 : z = state.tZone; break;
1556          case 1 : z = state.gZone; break;
1557          default : throw new Error('Invalid zone');
1558      }
1559  
1560      let p;
1561      const d = pv.distance(rp, rp, false, true);
1562      const pLen = z.length - 2;
1563      for (let i = 0; i < pLen; i++)
1564      {
1565          p = z[i];
1566          if (p !== rp) fv.setRelative(p, p, d, pv);
1567      }
1568  }
1569  
1570  // SHPIX[] SHift point by a PIXel amount
1571  // 0x38
1572  function SHPIX(state) {
1573      const stack = state.stack;
1574      let loop = state.loop;
1575      const fv = state.fv;
1576      const d = stack.pop() / 0x40;
1577      const z2 = state.z2;
1578  
1579      while (loop--) {
1580          const pi = stack.pop();
1581          const p = z2[pi];
1582  
1583          if (exports.DEBUG) {
1584              console.log(
1585                  state.step,
1586                  (state.loop > 1 ? 'loop ' + (state.loop - loop) + ': ' : '') +
1587                  'SHPIX[]', pi, d
1588              );
1589          }
1590  
1591          fv.setRelative(p, p, d);
1592          fv.touch(p);
1593      }
1594  
1595      state.loop = 1;
1596  }
1597  
1598  // IP[] Interpolate Point
1599  // 0x39
1600  function IP(state) {
1601      const stack = state.stack;
1602      const rp1i = state.rp1;
1603      const rp2i = state.rp2;
1604      let loop = state.loop;
1605      const rp1 = state.z0[rp1i];
1606      const rp2 = state.z1[rp2i];
1607      const fv = state.fv;
1608      const pv = state.dpv;
1609      const z2 = state.z2;
1610  
1611      while (loop--) {
1612          const pi = stack.pop();
1613          const p = z2[pi];
1614  
1615          if (exports.DEBUG) {
1616              console.log(
1617                  state.step,
1618                  (state.loop > 1 ? 'loop ' + (state.loop - loop) + ': ' : '') +
1619                  'IP[]', pi, rp1i, '<->', rp2i
1620              );
1621          }
1622  
1623          fv.interpolate(p, rp1, rp2, pv);
1624  
1625          fv.touch(p);
1626      }
1627  
1628      state.loop = 1;
1629  }
1630  
1631  // MSIRP[a] Move Stack Indirect Relative Point
1632  // 0x3A-0x3B
1633  function MSIRP(a, state) {
1634      const stack = state.stack;
1635      const d = stack.pop() / 64;
1636      const pi = stack.pop();
1637      const p = state.z1[pi];
1638      const rp0 = state.z0[state.rp0];
1639      const fv = state.fv;
1640      const pv = state.pv;
1641  
1642      fv.setRelative(p, rp0, d, pv);
1643      fv.touch(p);
1644  
1645      if (exports.DEBUG) console.log(state.step, 'MSIRP[' + a + ']', d, pi);
1646  
1647      state.rp1 = state.rp0;
1648      state.rp2 = pi;
1649      if (a) state.rp0 = pi;
1650  }
1651  
1652  // ALIGNRP[] Align to reference point.
1653  // 0x3C
1654  function ALIGNRP(state) {
1655      const stack = state.stack;
1656      const rp0i = state.rp0;
1657      const rp0 = state.z0[rp0i];
1658      let loop = state.loop;
1659      const fv = state.fv;
1660      const pv = state.pv;
1661      const z1 = state.z1;
1662  
1663      while (loop--) {
1664          const pi = stack.pop();
1665          const p = z1[pi];
1666  
1667          if (exports.DEBUG) {
1668              console.log(
1669                  state.step,
1670                  (state.loop > 1 ? 'loop ' + (state.loop - loop) + ': ' : '') +
1671                  'ALIGNRP[]', pi
1672              );
1673          }
1674  
1675          fv.setRelative(p, rp0, 0, pv);
1676          fv.touch(p);
1677      }
1678  
1679      state.loop = 1;
1680  }
1681  
1682  // RTG[] Round To Double Grid
1683  // 0x3D
1684  function RTDG(state) {
1685      if (exports.DEBUG) console.log(state.step, 'RTDG[]');
1686  
1687      state.round = roundToDoubleGrid;
1688  }
1689  
1690  // MIAP[a] Move Indirect Absolute Point
1691  // 0x3E-0x3F
1692  function MIAP(round, state) {
1693      const stack = state.stack;
1694      const n = stack.pop();
1695      const pi = stack.pop();
1696      const p = state.z0[pi];
1697      const fv = state.fv;
1698      const pv = state.pv;
1699      let cv = state.cvt[n];
1700  
1701      // TODO cvtcutin should be considered here
1702      if (round) cv = state.round(cv);
1703  
1704      if (exports.DEBUG) {
1705          console.log(
1706              state.step,
1707              'MIAP[' + round + ']',
1708              n, '(', cv, ')', pi
1709          );
1710      }
1711  
1712      fv.setRelative(p, HPZero, cv, pv);
1713  
1714      if (state.zp0 === 0) {
1715          p.xo = p.x;
1716          p.yo = p.y;
1717      }
1718  
1719      fv.touch(p);
1720  
1721      state.rp0 = state.rp1 = pi;
1722  }
1723  
1724  // NPUSB[] PUSH N Bytes
1725  // 0x40
1726  function NPUSHB(state) {
1727      const prog = state.prog;
1728      let ip = state.ip;
1729      const stack = state.stack;
1730  
1731      const n = prog[++ip];
1732  
1733      if (exports.DEBUG) console.log(state.step, 'NPUSHB[]', n);
1734  
1735      for (let i = 0; i < n; i++) stack.push(prog[++ip]);
1736  
1737      state.ip = ip;
1738  }
1739  
1740  // NPUSHW[] PUSH N Words
1741  // 0x41
1742  function NPUSHW(state) {
1743      let ip = state.ip;
1744      const prog = state.prog;
1745      const stack = state.stack;
1746      const n = prog[++ip];
1747  
1748      if (exports.DEBUG) console.log(state.step, 'NPUSHW[]', n);
1749  
1750      for (let i = 0; i < n; i++) {
1751          let w = (prog[++ip] << 8) | prog[++ip];
1752          if (w & 0x8000) w = -((w ^ 0xffff) + 1);
1753          stack.push(w);
1754      }
1755  
1756      state.ip = ip;
1757  }
1758  
1759  // WS[] Write Store
1760  // 0x42
1761  function WS(state) {
1762      const stack = state.stack;
1763      let store = state.store;
1764  
1765      if (!store) store = state.store = [];
1766  
1767      const v = stack.pop();
1768      const l = stack.pop();
1769  
1770      if (exports.DEBUG) console.log(state.step, 'WS', v, l);
1771  
1772      store[l] = v;
1773  }
1774  
1775  // RS[] Read Store
1776  // 0x43
1777  function RS(state) {
1778      const stack = state.stack;
1779      const store = state.store;
1780  
1781      const l = stack.pop();
1782  
1783      if (exports.DEBUG) console.log(state.step, 'RS', l);
1784  
1785      const v = (store && store[l]) || 0;
1786  
1787      stack.push(v);
1788  }
1789  
1790  // WCVTP[] Write Control Value Table in Pixel units
1791  // 0x44
1792  function WCVTP(state) {
1793      const stack = state.stack;
1794  
1795      const v = stack.pop();
1796      const l = stack.pop();
1797  
1798      if (exports.DEBUG) console.log(state.step, 'WCVTP', v, l);
1799  
1800      state.cvt[l] = v / 0x40;
1801  }
1802  
1803  // RCVT[] Read Control Value Table entry
1804  // 0x45
1805  function RCVT(state) {
1806      const stack = state.stack;
1807      const cvte = stack.pop();
1808  
1809      if (exports.DEBUG) console.log(state.step, 'RCVT', cvte);
1810  
1811      stack.push(state.cvt[cvte] * 0x40);
1812  }
1813  
1814  // GC[] Get Coordinate projected onto the projection vector
1815  // 0x46-0x47
1816  function GC(a, state) {
1817      const stack = state.stack;
1818      const pi = stack.pop();
1819      const p = state.z2[pi];
1820  
1821      if (exports.DEBUG) console.log(state.step, 'GC[' + a + ']', pi);
1822  
1823      stack.push(state.dpv.distance(p, HPZero, a, false) * 0x40);
1824  }
1825  
1826  // MD[a] Measure Distance
1827  // 0x49-0x4A
1828  function MD(a, state) {
1829      const stack = state.stack;
1830      const pi2 = stack.pop();
1831      const pi1 = stack.pop();
1832      const p2 = state.z1[pi2];
1833      const p1 = state.z0[pi1];
1834      const d = state.dpv.distance(p1, p2, a, a);
1835  
1836      if (exports.DEBUG) console.log(state.step, 'MD[' + a + ']', pi2, pi1, '->', d);
1837  
1838      state.stack.push(Math.round(d * 64));
1839  }
1840  
1841  // MPPEM[] Measure Pixels Per EM
1842  // 0x4B
1843  function MPPEM(state) {
1844      if (exports.DEBUG) console.log(state.step, 'MPPEM[]');
1845      state.stack.push(state.ppem);
1846  }
1847  
1848  // FLIPON[] set the auto FLIP Boolean to ON
1849  // 0x4D
1850  function FLIPON(state) {
1851      if (exports.DEBUG) console.log(state.step, 'FLIPON[]');
1852      state.autoFlip = true;
1853  }
1854  
1855  // LT[] Less Than
1856  // 0x50
1857  function LT(state) {
1858      const stack = state.stack;
1859      const e2 = stack.pop();
1860      const e1 = stack.pop();
1861  
1862      if (exports.DEBUG) console.log(state.step, 'LT[]', e2, e1);
1863  
1864      stack.push(e1 < e2 ? 1 : 0);
1865  }
1866  
1867  // LTEQ[] Less Than or EQual
1868  // 0x53
1869  function LTEQ(state) {
1870      const stack = state.stack;
1871      const e2 = stack.pop();
1872      const e1 = stack.pop();
1873  
1874      if (exports.DEBUG) console.log(state.step, 'LTEQ[]', e2, e1);
1875  
1876      stack.push(e1 <= e2 ? 1 : 0);
1877  }
1878  
1879  // GTEQ[] Greater Than
1880  // 0x52
1881  function GT(state) {
1882      const stack = state.stack;
1883      const e2 = stack.pop();
1884      const e1 = stack.pop();
1885  
1886      if (exports.DEBUG) console.log(state.step, 'GT[]', e2, e1);
1887  
1888      stack.push(e1 > e2 ? 1 : 0);
1889  }
1890  
1891  // GTEQ[] Greater Than or EQual
1892  // 0x53
1893  function GTEQ(state) {
1894      const stack = state.stack;
1895      const e2 = stack.pop();
1896      const e1 = stack.pop();
1897  
1898      if (exports.DEBUG) console.log(state.step, 'GTEQ[]', e2, e1);
1899  
1900      stack.push(e1 >= e2 ? 1 : 0);
1901  }
1902  
1903  // EQ[] EQual
1904  // 0x54
1905  function EQ(state) {
1906      const stack = state.stack;
1907      const e2 = stack.pop();
1908      const e1 = stack.pop();
1909  
1910      if (exports.DEBUG) console.log(state.step, 'EQ[]', e2, e1);
1911  
1912      stack.push(e2 === e1 ? 1 : 0);
1913  }
1914  
1915  // NEQ[] Not EQual
1916  // 0x55
1917  function NEQ(state) {
1918      const stack = state.stack;
1919      const e2 = stack.pop();
1920      const e1 = stack.pop();
1921  
1922      if (exports.DEBUG) console.log(state.step, 'NEQ[]', e2, e1);
1923  
1924      stack.push(e2 !== e1 ? 1 : 0);
1925  }
1926  
1927  // ODD[] ODD
1928  // 0x56
1929  function ODD(state) {
1930      const stack = state.stack;
1931      const n = stack.pop();
1932  
1933      if (exports.DEBUG) console.log(state.step, 'ODD[]', n);
1934  
1935      stack.push(Math.trunc(n) % 2 ? 1 : 0);
1936  }
1937  
1938  // EVEN[] EVEN
1939  // 0x57
1940  function EVEN(state) {
1941      const stack = state.stack;
1942      const n = stack.pop();
1943  
1944      if (exports.DEBUG) console.log(state.step, 'EVEN[]', n);
1945  
1946      stack.push(Math.trunc(n) % 2 ? 0 : 1);
1947  }
1948  
1949  // IF[] IF test
1950  // 0x58
1951  function IF(state) {
1952      let test = state.stack.pop();
1953      let ins;
1954  
1955      if (exports.DEBUG) console.log(state.step, 'IF[]', test);
1956  
1957      // if test is true it just continues
1958      // if not the ip is skipped until matching ELSE or EIF
1959      if (!test) {
1960          skip(state, true);
1961  
1962          if (exports.DEBUG) console.log(state.step, ins === 0x1B ? 'ELSE[]' : 'EIF[]');
1963      }
1964  }
1965  
1966  // EIF[] End IF
1967  // 0x59
1968  function EIF(state) {
1969      // this can be reached normally when
1970      // executing an else branch.
1971      // -> just ignore it
1972  
1973      if (exports.DEBUG) console.log(state.step, 'EIF[]');
1974  }
1975  
1976  // AND[] logical AND
1977  // 0x5A
1978  function AND(state) {
1979      const stack = state.stack;
1980      const e2 = stack.pop();
1981      const e1 = stack.pop();
1982  
1983      if (exports.DEBUG) console.log(state.step, 'AND[]', e2, e1);
1984  
1985      stack.push(e2 && e1 ? 1 : 0);
1986  }
1987  
1988  // OR[] logical OR
1989  // 0x5B
1990  function OR(state) {
1991      const stack = state.stack;
1992      const e2 = stack.pop();
1993      const e1 = stack.pop();
1994  
1995      if (exports.DEBUG) console.log(state.step, 'OR[]', e2, e1);
1996  
1997      stack.push(e2 || e1 ? 1 : 0);
1998  }
1999  
2000  // NOT[] logical NOT
2001  // 0x5C
2002  function NOT(state) {
2003      const stack = state.stack;
2004      const e = stack.pop();
2005  
2006      if (exports.DEBUG) console.log(state.step, 'NOT[]', e);
2007  
2008      stack.push(e ? 0 : 1);
2009  }
2010  
2011  // DELTAP1[] DELTA exception P1
2012  // DELTAP2[] DELTA exception P2
2013  // DELTAP3[] DELTA exception P3
2014  // 0x5D, 0x71, 0x72
2015  function DELTAP123(b, state) {
2016      const stack = state.stack;
2017      const n = stack.pop();
2018      const fv = state.fv;
2019      const pv = state.pv;
2020      const ppem = state.ppem;
2021      const base = state.deltaBase + (b - 1) * 16;
2022      const ds = state.deltaShift;
2023      const z0 = state.z0;
2024  
2025      if (exports.DEBUG) console.log(state.step, 'DELTAP[' + b + ']', n, stack);
2026  
2027      for (let i = 0; i < n; i++)
2028      {
2029          const pi = stack.pop();
2030          const arg = stack.pop();
2031          const appem = base + ((arg & 0xF0) >> 4);
2032          if (appem !== ppem) continue;
2033  
2034          let mag = (arg & 0x0F) - 8;
2035          if (mag >= 0) mag++;
2036          if (exports.DEBUG) console.log(state.step, 'DELTAPFIX', pi, 'by', mag * ds);
2037  
2038          const p = z0[pi];
2039          fv.setRelative(p, p, mag * ds, pv);
2040      }
2041  }
2042  
2043  // SDB[] Set Delta Base in the graphics state
2044  // 0x5E
2045  function SDB(state) {
2046      const stack = state.stack;
2047      const n = stack.pop();
2048  
2049      if (exports.DEBUG) console.log(state.step, 'SDB[]', n);
2050  
2051      state.deltaBase = n;
2052  }
2053  
2054  // SDS[] Set Delta Shift in the graphics state
2055  // 0x5F
2056  function SDS(state) {
2057      const stack = state.stack;
2058      const n = stack.pop();
2059  
2060      if (exports.DEBUG) console.log(state.step, 'SDS[]', n);
2061  
2062      state.deltaShift = Math.pow(0.5, n);
2063  }
2064  
2065  // ADD[] ADD
2066  // 0x60
2067  function ADD(state) {
2068      const stack = state.stack;
2069      const n2 = stack.pop();
2070      const n1 = stack.pop();
2071  
2072      if (exports.DEBUG) console.log(state.step, 'ADD[]', n2, n1);
2073  
2074      stack.push(n1 + n2);
2075  }
2076  
2077  // SUB[] SUB
2078  // 0x61
2079  function SUB(state) {
2080      const stack = state.stack;
2081      const n2 = stack.pop();
2082      const n1 = stack.pop();
2083  
2084      if (exports.DEBUG) console.log(state.step, 'SUB[]', n2, n1);
2085  
2086      stack.push(n1 - n2);
2087  }
2088  
2089  // DIV[] DIV
2090  // 0x62
2091  function DIV(state) {
2092      const stack = state.stack;
2093      const n2 = stack.pop();
2094      const n1 = stack.pop();
2095  
2096      if (exports.DEBUG) console.log(state.step, 'DIV[]', n2, n1);
2097  
2098      stack.push(n1 * 64 / n2);
2099  }
2100  
2101  // MUL[] MUL
2102  // 0x63
2103  function MUL(state) {
2104      const stack = state.stack;
2105      const n2 = stack.pop();
2106      const n1 = stack.pop();
2107  
2108      if (exports.DEBUG) console.log(state.step, 'MUL[]', n2, n1);
2109  
2110      stack.push(n1 * n2 / 64);
2111  }
2112  
2113  // ABS[] ABSolute value
2114  // 0x64
2115  function ABS(state) {
2116      const stack = state.stack;
2117      const n = stack.pop();
2118  
2119      if (exports.DEBUG) console.log(state.step, 'ABS[]', n);
2120  
2121      stack.push(Math.abs(n));
2122  }
2123  
2124  // NEG[] NEGate
2125  // 0x65
2126  function NEG(state) {
2127      const stack = state.stack;
2128      let n = stack.pop();
2129  
2130      if (exports.DEBUG) console.log(state.step, 'NEG[]', n);
2131  
2132      stack.push(-n);
2133  }
2134  
2135  // FLOOR[] FLOOR
2136  // 0x66
2137  function FLOOR(state) {
2138      const stack = state.stack;
2139      const n = stack.pop();
2140  
2141      if (exports.DEBUG) console.log(state.step, 'FLOOR[]', n);
2142  
2143      stack.push(Math.floor(n / 0x40) * 0x40);
2144  }
2145  
2146  // CEILING[] CEILING
2147  // 0x67
2148  function CEILING(state) {
2149      const stack = state.stack;
2150      const n = stack.pop();
2151  
2152      if (exports.DEBUG) console.log(state.step, 'CEILING[]', n);
2153  
2154      stack.push(Math.ceil(n / 0x40) * 0x40);
2155  }
2156  
2157  // ROUND[ab] ROUND value
2158  // 0x68-0x6B
2159  function ROUND(dt, state) {
2160      const stack = state.stack;
2161      const n = stack.pop();
2162  
2163      if (exports.DEBUG) console.log(state.step, 'ROUND[]');
2164  
2165      stack.push(state.round(n / 0x40) * 0x40);
2166  }
2167  
2168  // WCVTF[] Write Control Value Table in Funits
2169  // 0x70
2170  function WCVTF(state) {
2171      const stack = state.stack;
2172      const v = stack.pop();
2173      const l = stack.pop();
2174  
2175      if (exports.DEBUG) console.log(state.step, 'WCVTF[]', v, l);
2176  
2177      state.cvt[l] = v * state.ppem / state.font.unitsPerEm;
2178  }
2179  
2180  // DELTAC1[] DELTA exception C1
2181  // DELTAC2[] DELTA exception C2
2182  // DELTAC3[] DELTA exception C3
2183  // 0x73, 0x74, 0x75
2184  function DELTAC123(b, state) {
2185      const stack = state.stack;
2186      const n = stack.pop();
2187      const ppem = state.ppem;
2188      const base = state.deltaBase + (b - 1) * 16;
2189      const ds = state.deltaShift;
2190  
2191      if (exports.DEBUG) console.log(state.step, 'DELTAC[' + b + ']', n, stack);
2192  
2193      for (let i = 0; i < n; i++) {
2194          const c = stack.pop();
2195          const arg = stack.pop();
2196          const appem = base + ((arg & 0xF0) >> 4);
2197          if (appem !== ppem) continue;
2198  
2199          let mag = (arg & 0x0F) - 8;
2200          if (mag >= 0) mag++;
2201  
2202          const delta = mag * ds;
2203  
2204          if (exports.DEBUG) console.log(state.step, 'DELTACFIX', c, 'by', delta);
2205  
2206          state.cvt[c] += delta;
2207      }
2208  }
2209  
2210  // SROUND[] Super ROUND
2211  // 0x76
2212  function SROUND(state) {
2213      let n = state.stack.pop();
2214  
2215      if (exports.DEBUG) console.log(state.step, 'SROUND[]', n);
2216  
2217      state.round = roundSuper;
2218  
2219      let period;
2220  
2221      switch (n & 0xC0) {
2222          case 0x00:
2223              period = 0.5;
2224              break;
2225          case 0x40:
2226              period = 1;
2227              break;
2228          case 0x80:
2229              period = 2;
2230              break;
2231          default:
2232              throw new Error('invalid SROUND value');
2233      }
2234  
2235      state.srPeriod = period;
2236  
2237      switch (n & 0x30) {
2238          case 0x00:
2239              state.srPhase = 0;
2240              break;
2241          case 0x10:
2242              state.srPhase = 0.25 * period;
2243              break;
2244          case 0x20:
2245              state.srPhase = 0.5  * period;
2246              break;
2247          case 0x30:
2248              state.srPhase = 0.75 * period;
2249              break;
2250          default: throw new Error('invalid SROUND value');
2251      }
2252  
2253      n &= 0x0F;
2254  
2255      if (n === 0) state.srThreshold = 0;
2256      else state.srThreshold = (n / 8 - 0.5) * period;
2257  }
2258  
2259  // S45ROUND[] Super ROUND 45 degrees
2260  // 0x77
2261  function S45ROUND(state) {
2262      let n = state.stack.pop();
2263  
2264      if (exports.DEBUG) console.log(state.step, 'S45ROUND[]', n);
2265  
2266      state.round = roundSuper;
2267  
2268      let period;
2269  
2270      switch (n & 0xC0) {
2271          case 0x00:
2272              period = Math.sqrt(2) / 2;
2273              break;
2274          case 0x40:
2275              period = Math.sqrt(2);
2276              break;
2277          case 0x80:
2278              period = 2 * Math.sqrt(2);
2279              break;
2280          default:
2281              throw new Error('invalid S45ROUND value');
2282      }
2283  
2284      state.srPeriod = period;
2285  
2286      switch (n & 0x30) {
2287          case 0x00:
2288              state.srPhase = 0;
2289              break;
2290          case 0x10:
2291              state.srPhase = 0.25 * period;
2292              break;
2293          case 0x20:
2294              state.srPhase = 0.5  * period;
2295              break;
2296          case 0x30:
2297              state.srPhase = 0.75 * period;
2298              break;
2299          default:
2300              throw new Error('invalid S45ROUND value');
2301      }
2302  
2303      n &= 0x0F;
2304  
2305      if (n === 0) state.srThreshold = 0;
2306      else state.srThreshold = (n / 8 - 0.5) * period;
2307  }
2308  
2309  // ROFF[] Round Off
2310  // 0x7A
2311  function ROFF(state) {
2312      if (exports.DEBUG) console.log(state.step, 'ROFF[]');
2313  
2314      state.round = roundOff;
2315  }
2316  
2317  // RUTG[] Round Up To Grid
2318  // 0x7C
2319  function RUTG(state) {
2320      if (exports.DEBUG) console.log(state.step, 'RUTG[]');
2321  
2322      state.round = roundUpToGrid;
2323  }
2324  
2325  // RDTG[] Round Down To Grid
2326  // 0x7D
2327  function RDTG(state) {
2328      if (exports.DEBUG) console.log(state.step, 'RDTG[]');
2329  
2330      state.round = roundDownToGrid;
2331  }
2332  
2333  // SCANCTRL[] SCAN conversion ConTRoL
2334  // 0x85
2335  function SCANCTRL(state) {
2336      const n = state.stack.pop();
2337  
2338      // ignored by opentype.js
2339  
2340      if (exports.DEBUG) console.log(state.step, 'SCANCTRL[]', n);
2341  }
2342  
2343  // SDPVTL[a] Set Dual Projection Vector To Line
2344  // 0x86-0x87
2345  function SDPVTL(a, state) {
2346      const stack = state.stack;
2347      const p2i = stack.pop();
2348      const p1i = stack.pop();
2349      const p2 = state.z2[p2i];
2350      const p1 = state.z1[p1i];
2351  
2352      if (exports.DEBUG) console.log('SDPVTL[' + a + ']', p2i, p1i);
2353  
2354      let dx;
2355      let dy;
2356  
2357      if (!a) {
2358          dx = p1.x - p2.x;
2359          dy = p1.y - p2.y;
2360      } else {
2361          dx = p2.y - p1.y;
2362          dy = p1.x - p2.x;
2363      }
2364  
2365      state.dpv = getUnitVector(dx, dy);
2366  }
2367  
2368  // GETINFO[] GET INFOrmation
2369  // 0x88
2370  function GETINFO(state) {
2371      const stack = state.stack;
2372      const sel = stack.pop();
2373      let r = 0;
2374  
2375      if (exports.DEBUG) console.log(state.step, 'GETINFO[]', sel);
2376  
2377      // v35 as in no subpixel hinting
2378      if (sel & 0x01) r = 35;
2379  
2380      // TODO rotation and stretch currently not supported
2381      // and thus those GETINFO are always 0.
2382  
2383      // opentype.js is always gray scaling
2384      if (sel & 0x20) r |= 0x1000;
2385  
2386      stack.push(r);
2387  }
2388  
2389  // ROLL[] ROLL the top three stack elements
2390  // 0x8A
2391  function ROLL(state) {
2392      const stack = state.stack;
2393      const a = stack.pop();
2394      const b = stack.pop();
2395      const c = stack.pop();
2396  
2397      if (exports.DEBUG) console.log(state.step, 'ROLL[]');
2398  
2399      stack.push(b);
2400      stack.push(a);
2401      stack.push(c);
2402  }
2403  
2404  // MAX[] MAXimum of top two stack elements
2405  // 0x8B
2406  function MAX(state) {
2407      const stack = state.stack;
2408      const e2 = stack.pop();
2409      const e1 = stack.pop();
2410  
2411      if (exports.DEBUG) console.log(state.step, 'MAX[]', e2, e1);
2412  
2413      stack.push(Math.max(e1, e2));
2414  }
2415  
2416  // MIN[] MINimum of top two stack elements
2417  // 0x8C
2418  function MIN(state) {
2419      const stack = state.stack;
2420      const e2 = stack.pop();
2421      const e1 = stack.pop();
2422  
2423      if (exports.DEBUG) console.log(state.step, 'MIN[]', e2, e1);
2424  
2425      stack.push(Math.min(e1, e2));
2426  }
2427  
2428  // SCANTYPE[] SCANTYPE
2429  // 0x8D
2430  function SCANTYPE(state) {
2431      const n = state.stack.pop();
2432      // ignored by opentype.js
2433      if (exports.DEBUG) console.log(state.step, 'SCANTYPE[]', n);
2434  }
2435  
2436  // INSTCTRL[] INSTCTRL
2437  // 0x8D
2438  function INSTCTRL(state) {
2439      const s = state.stack.pop();
2440      let v = state.stack.pop();
2441  
2442      if (exports.DEBUG) console.log(state.step, 'INSTCTRL[]', s, v);
2443  
2444      switch (s) {
2445          case 1 : state.inhibitGridFit = !!v; return;
2446          case 2 : state.ignoreCvt = !!v; return;
2447          default: throw new Error('invalid INSTCTRL[] selector');
2448      }
2449  }
2450  
2451  // PUSHB[abc] PUSH Bytes
2452  // 0xB0-0xB7
2453  function PUSHB(n, state) {
2454      const stack = state.stack;
2455      const prog = state.prog;
2456      let ip = state.ip;
2457  
2458      if (exports.DEBUG) console.log(state.step, 'PUSHB[' + n + ']');
2459  
2460      for (let i = 0; i < n; i++) stack.push(prog[++ip]);
2461  
2462      state.ip = ip;
2463  }
2464  
2465  // PUSHW[abc] PUSH Words
2466  // 0xB8-0xBF
2467  function PUSHW(n, state) {
2468      let ip = state.ip;
2469      const prog = state.prog;
2470      const stack = state.stack;
2471  
2472      if (exports.DEBUG) console.log(state.ip, 'PUSHW[' + n + ']');
2473  
2474      for (let i = 0; i < n; i++) {
2475          let w = (prog[++ip] << 8) | prog[++ip];
2476          if (w & 0x8000) w = -((w ^ 0xffff) + 1);
2477          stack.push(w);
2478      }
2479  
2480      state.ip = ip;
2481  }
2482  
2483  // MDRP[abcde] Move Direct Relative Point
2484  // 0xD0-0xEF
2485  // (if indirect is 0)
2486  //
2487  // and
2488  //
2489  // MIRP[abcde] Move Indirect Relative Point
2490  // 0xE0-0xFF
2491  // (if indirect is 1)
2492  
2493  function MDRP_MIRP(indirect, setRp0, keepD, ro, dt, state) {
2494      const stack = state.stack;
2495      const cvte = indirect && stack.pop();
2496      const pi = stack.pop();
2497      const rp0i = state.rp0;
2498      const rp = state.z0[rp0i];
2499      const p = state.z1[pi];
2500  
2501      const md = state.minDis;
2502      const fv = state.fv;
2503      const pv = state.dpv;
2504      let od; // original distance
2505      let d; // moving distance
2506      let sign; // sign of distance
2507      let cv;
2508  
2509      d = od = pv.distance(p, rp, true, true);
2510      sign = d >= 0 ? 1 : -1; // Math.sign would be 0 in case of 0
2511  
2512      // TODO consider autoFlip
2513      d = Math.abs(d);
2514  
2515      if (indirect) {
2516          cv = state.cvt[cvte];
2517  
2518          if (ro && Math.abs(d - cv) < state.cvCutIn) d = cv;
2519      }
2520  
2521      if (keepD && d < md) d = md;
2522  
2523      if (ro) d = state.round(d);
2524  
2525      fv.setRelative(p, rp, sign * d, pv);
2526      fv.touch(p);
2527  
2528      if (exports.DEBUG) {
2529          console.log(
2530              state.step,
2531              (indirect ? 'MIRP[' : 'MDRP[') +
2532              (setRp0 ? 'M' : 'm') +
2533              (keepD ? '>' : '_') +
2534              (ro ? 'R' : '_') +
2535              (dt === 0 ? 'Gr' : (dt === 1 ? 'Bl' : (dt === 2 ? 'Wh' : ''))) +
2536              ']',
2537              indirect ?
2538                  cvte + '(' + state.cvt[cvte] + ',' +  cv + ')' :
2539                  '',
2540              pi,
2541              '(d =', od, '->', sign * d, ')'
2542          );
2543      }
2544  
2545      state.rp1 = state.rp0;
2546      state.rp2 = pi;
2547      if (setRp0) state.rp0 = pi;
2548  }
2549  
2550  /*
2551  * The instruction table.
2552  */
2553  instructionTable = [
2554      /* 0x00 */ SVTCA.bind(undefined, yUnitVector),
2555      /* 0x01 */ SVTCA.bind(undefined, xUnitVector),
2556      /* 0x02 */ SPVTCA.bind(undefined, yUnitVector),
2557      /* 0x03 */ SPVTCA.bind(undefined, xUnitVector),
2558      /* 0x04 */ SFVTCA.bind(undefined, yUnitVector),
2559      /* 0x05 */ SFVTCA.bind(undefined, xUnitVector),
2560      /* 0x06 */ SPVTL.bind(undefined, 0),
2561      /* 0x07 */ SPVTL.bind(undefined, 1),
2562      /* 0x08 */ SFVTL.bind(undefined, 0),
2563      /* 0x09 */ SFVTL.bind(undefined, 1),
2564      /* 0x0A */ SPVFS,
2565      /* 0x0B */ SFVFS,
2566      /* 0x0C */ GPV,
2567      /* 0x0D */ GFV,
2568      /* 0x0E */ SFVTPV,
2569      /* 0x0F */ ISECT,
2570      /* 0x10 */ SRP0,
2571      /* 0x11 */ SRP1,
2572      /* 0x12 */ SRP2,
2573      /* 0x13 */ SZP0,
2574      /* 0x14 */ SZP1,
2575      /* 0x15 */ SZP2,
2576      /* 0x16 */ SZPS,
2577      /* 0x17 */ SLOOP,
2578      /* 0x18 */ RTG,
2579      /* 0x19 */ RTHG,
2580      /* 0x1A */ SMD,
2581      /* 0x1B */ ELSE,
2582      /* 0x1C */ JMPR,
2583      /* 0x1D */ SCVTCI,
2584      /* 0x1E */ undefined,   // TODO SSWCI
2585      /* 0x1F */ undefined,   // TODO SSW
2586      /* 0x20 */ DUP,
2587      /* 0x21 */ POP,
2588      /* 0x22 */ CLEAR,
2589      /* 0x23 */ SWAP,
2590      /* 0x24 */ DEPTH,
2591      /* 0x25 */ CINDEX,
2592      /* 0x26 */ MINDEX,
2593      /* 0x27 */ undefined,   // TODO ALIGNPTS
2594      /* 0x28 */ undefined,
2595      /* 0x29 */ undefined,   // TODO UTP
2596      /* 0x2A */ LOOPCALL,
2597      /* 0x2B */ CALL,
2598      /* 0x2C */ FDEF,
2599      /* 0x2D */ undefined,   // ENDF (eaten by FDEF)
2600      /* 0x2E */ MDAP.bind(undefined, 0),
2601      /* 0x2F */ MDAP.bind(undefined, 1),
2602      /* 0x30 */ IUP.bind(undefined, yUnitVector),
2603      /* 0x31 */ IUP.bind(undefined, xUnitVector),
2604      /* 0x32 */ SHP.bind(undefined, 0),
2605      /* 0x33 */ SHP.bind(undefined, 1),
2606      /* 0x34 */ SHC.bind(undefined, 0),
2607      /* 0x35 */ SHC.bind(undefined, 1),
2608      /* 0x36 */ SHZ.bind(undefined, 0),
2609      /* 0x37 */ SHZ.bind(undefined, 1),
2610      /* 0x38 */ SHPIX,
2611      /* 0x39 */ IP,
2612      /* 0x3A */ MSIRP.bind(undefined, 0),
2613      /* 0x3B */ MSIRP.bind(undefined, 1),
2614      /* 0x3C */ ALIGNRP,
2615      /* 0x3D */ RTDG,
2616      /* 0x3E */ MIAP.bind(undefined, 0),
2617      /* 0x3F */ MIAP.bind(undefined, 1),
2618      /* 0x40 */ NPUSHB,
2619      /* 0x41 */ NPUSHW,
2620      /* 0x42 */ WS,
2621      /* 0x43 */ RS,
2622      /* 0x44 */ WCVTP,
2623      /* 0x45 */ RCVT,
2624      /* 0x46 */ GC.bind(undefined, 0),
2625      /* 0x47 */ GC.bind(undefined, 1),
2626      /* 0x48 */ undefined,   // TODO SCFS
2627      /* 0x49 */ MD.bind(undefined, 0),
2628      /* 0x4A */ MD.bind(undefined, 1),
2629      /* 0x4B */ MPPEM,
2630      /* 0x4C */ undefined,   // TODO MPS
2631      /* 0x4D */ FLIPON,
2632      /* 0x4E */ undefined,   // TODO FLIPOFF
2633      /* 0x4F */ undefined,   // TODO DEBUG
2634      /* 0x50 */ LT,
2635      /* 0x51 */ LTEQ,
2636      /* 0x52 */ GT,
2637      /* 0x53 */ GTEQ,
2638      /* 0x54 */ EQ,
2639      /* 0x55 */ NEQ,
2640      /* 0x56 */ ODD,
2641      /* 0x57 */ EVEN,
2642      /* 0x58 */ IF,
2643      /* 0x59 */ EIF,
2644      /* 0x5A */ AND,
2645      /* 0x5B */ OR,
2646      /* 0x5C */ NOT,
2647      /* 0x5D */ DELTAP123.bind(undefined, 1),
2648      /* 0x5E */ SDB,
2649      /* 0x5F */ SDS,
2650      /* 0x60 */ ADD,
2651      /* 0x61 */ SUB,
2652      /* 0x62 */ DIV,
2653      /* 0x63 */ MUL,
2654      /* 0x64 */ ABS,
2655      /* 0x65 */ NEG,
2656      /* 0x66 */ FLOOR,
2657      /* 0x67 */ CEILING,
2658      /* 0x68 */ ROUND.bind(undefined, 0),
2659      /* 0x69 */ ROUND.bind(undefined, 1),
2660      /* 0x6A */ ROUND.bind(undefined, 2),
2661      /* 0x6B */ ROUND.bind(undefined, 3),
2662      /* 0x6C */ undefined,   // TODO NROUND[ab]
2663      /* 0x6D */ undefined,   // TODO NROUND[ab]
2664      /* 0x6E */ undefined,   // TODO NROUND[ab]
2665      /* 0x6F */ undefined,   // TODO NROUND[ab]
2666      /* 0x70 */ WCVTF,
2667      /* 0x71 */ DELTAP123.bind(undefined, 2),
2668      /* 0x72 */ DELTAP123.bind(undefined, 3),
2669      /* 0x73 */ DELTAC123.bind(undefined, 1),
2670      /* 0x74 */ DELTAC123.bind(undefined, 2),
2671      /* 0x75 */ DELTAC123.bind(undefined, 3),
2672      /* 0x76 */ SROUND,
2673      /* 0x77 */ S45ROUND,
2674      /* 0x78 */ undefined,   // TODO JROT[]
2675      /* 0x79 */ undefined,   // TODO JROF[]
2676      /* 0x7A */ ROFF,
2677      /* 0x7B */ undefined,
2678      /* 0x7C */ RUTG,
2679      /* 0x7D */ RDTG,
2680      /* 0x7E */ POP, // actually SANGW, supposed to do only a pop though
2681      /* 0x7F */ POP, // actually AA, supposed to do only a pop though
2682      /* 0x80 */ undefined,   // TODO FLIPPT
2683      /* 0x81 */ undefined,   // TODO FLIPRGON
2684      /* 0x82 */ undefined,   // TODO FLIPRGOFF
2685      /* 0x83 */ undefined,
2686      /* 0x84 */ undefined,
2687      /* 0x85 */ SCANCTRL,
2688      /* 0x86 */ SDPVTL.bind(undefined, 0),
2689      /* 0x87 */ SDPVTL.bind(undefined, 1),
2690      /* 0x88 */ GETINFO,
2691      /* 0x89 */ undefined,   // TODO IDEF
2692      /* 0x8A */ ROLL,
2693      /* 0x8B */ MAX,
2694      /* 0x8C */ MIN,
2695      /* 0x8D */ SCANTYPE,
2696      /* 0x8E */ INSTCTRL,
2697      /* 0x8F */ undefined,
2698      /* 0x90 */ undefined,
2699      /* 0x91 */ undefined,
2700      /* 0x92 */ undefined,
2701      /* 0x93 */ undefined,
2702      /* 0x94 */ undefined,
2703      /* 0x95 */ undefined,
2704      /* 0x96 */ undefined,
2705      /* 0x97 */ undefined,
2706      /* 0x98 */ undefined,
2707      /* 0x99 */ undefined,
2708      /* 0x9A */ undefined,
2709      /* 0x9B */ undefined,
2710      /* 0x9C */ undefined,
2711      /* 0x9D */ undefined,
2712      /* 0x9E */ undefined,
2713      /* 0x9F */ undefined,
2714      /* 0xA0 */ undefined,
2715      /* 0xA1 */ undefined,
2716      /* 0xA2 */ undefined,
2717      /* 0xA3 */ undefined,
2718      /* 0xA4 */ undefined,
2719      /* 0xA5 */ undefined,
2720      /* 0xA6 */ undefined,
2721      /* 0xA7 */ undefined,
2722      /* 0xA8 */ undefined,
2723      /* 0xA9 */ undefined,
2724      /* 0xAA */ undefined,
2725      /* 0xAB */ undefined,
2726      /* 0xAC */ undefined,
2727      /* 0xAD */ undefined,
2728      /* 0xAE */ undefined,
2729      /* 0xAF */ undefined,
2730      /* 0xB0 */ PUSHB.bind(undefined, 1),
2731      /* 0xB1 */ PUSHB.bind(undefined, 2),
2732      /* 0xB2 */ PUSHB.bind(undefined, 3),
2733      /* 0xB3 */ PUSHB.bind(undefined, 4),
2734      /* 0xB4 */ PUSHB.bind(undefined, 5),
2735      /* 0xB5 */ PUSHB.bind(undefined, 6),
2736      /* 0xB6 */ PUSHB.bind(undefined, 7),
2737      /* 0xB7 */ PUSHB.bind(undefined, 8),
2738      /* 0xB8 */ PUSHW.bind(undefined, 1),
2739      /* 0xB9 */ PUSHW.bind(undefined, 2),
2740      /* 0xBA */ PUSHW.bind(undefined, 3),
2741      /* 0xBB */ PUSHW.bind(undefined, 4),
2742      /* 0xBC */ PUSHW.bind(undefined, 5),
2743      /* 0xBD */ PUSHW.bind(undefined, 6),
2744      /* 0xBE */ PUSHW.bind(undefined, 7),
2745      /* 0xBF */ PUSHW.bind(undefined, 8),
2746      /* 0xC0 */ MDRP_MIRP.bind(undefined, 0, 0, 0, 0, 0),
2747      /* 0xC1 */ MDRP_MIRP.bind(undefined, 0, 0, 0, 0, 1),
2748      /* 0xC2 */ MDRP_MIRP.bind(undefined, 0, 0, 0, 0, 2),
2749      /* 0xC3 */ MDRP_MIRP.bind(undefined, 0, 0, 0, 0, 3),
2750      /* 0xC4 */ MDRP_MIRP.bind(undefined, 0, 0, 0, 1, 0),
2751      /* 0xC5 */ MDRP_MIRP.bind(undefined, 0, 0, 0, 1, 1),
2752      /* 0xC6 */ MDRP_MIRP.bind(undefined, 0, 0, 0, 1, 2),
2753      /* 0xC7 */ MDRP_MIRP.bind(undefined, 0, 0, 0, 1, 3),
2754      /* 0xC8 */ MDRP_MIRP.bind(undefined, 0, 0, 1, 0, 0),
2755      /* 0xC9 */ MDRP_MIRP.bind(undefined, 0, 0, 1, 0, 1),
2756      /* 0xCA */ MDRP_MIRP.bind(undefined, 0, 0, 1, 0, 2),
2757      /* 0xCB */ MDRP_MIRP.bind(undefined, 0, 0, 1, 0, 3),
2758      /* 0xCC */ MDRP_MIRP.bind(undefined, 0, 0, 1, 1, 0),
2759      /* 0xCD */ MDRP_MIRP.bind(undefined, 0, 0, 1, 1, 1),
2760      /* 0xCE */ MDRP_MIRP.bind(undefined, 0, 0, 1, 1, 2),
2761      /* 0xCF */ MDRP_MIRP.bind(undefined, 0, 0, 1, 1, 3),
2762      /* 0xD0 */ MDRP_MIRP.bind(undefined, 0, 1, 0, 0, 0),
2763      /* 0xD1 */ MDRP_MIRP.bind(undefined, 0, 1, 0, 0, 1),
2764      /* 0xD2 */ MDRP_MIRP.bind(undefined, 0, 1, 0, 0, 2),
2765      /* 0xD3 */ MDRP_MIRP.bind(undefined, 0, 1, 0, 0, 3),
2766      /* 0xD4 */ MDRP_MIRP.bind(undefined, 0, 1, 0, 1, 0),
2767      /* 0xD5 */ MDRP_MIRP.bind(undefined, 0, 1, 0, 1, 1),
2768      /* 0xD6 */ MDRP_MIRP.bind(undefined, 0, 1, 0, 1, 2),
2769      /* 0xD7 */ MDRP_MIRP.bind(undefined, 0, 1, 0, 1, 3),
2770      /* 0xD8 */ MDRP_MIRP.bind(undefined, 0, 1, 1, 0, 0),
2771      /* 0xD9 */ MDRP_MIRP.bind(undefined, 0, 1, 1, 0, 1),
2772      /* 0xDA */ MDRP_MIRP.bind(undefined, 0, 1, 1, 0, 2),
2773      /* 0xDB */ MDRP_MIRP.bind(undefined, 0, 1, 1, 0, 3),
2774      /* 0xDC */ MDRP_MIRP.bind(undefined, 0, 1, 1, 1, 0),
2775      /* 0xDD */ MDRP_MIRP.bind(undefined, 0, 1, 1, 1, 1),
2776      /* 0xDE */ MDRP_MIRP.bind(undefined, 0, 1, 1, 1, 2),
2777      /* 0xDF */ MDRP_MIRP.bind(undefined, 0, 1, 1, 1, 3),
2778      /* 0xE0 */ MDRP_MIRP.bind(undefined, 1, 0, 0, 0, 0),
2779      /* 0xE1 */ MDRP_MIRP.bind(undefined, 1, 0, 0, 0, 1),
2780      /* 0xE2 */ MDRP_MIRP.bind(undefined, 1, 0, 0, 0, 2),
2781      /* 0xE3 */ MDRP_MIRP.bind(undefined, 1, 0, 0, 0, 3),
2782      /* 0xE4 */ MDRP_MIRP.bind(undefined, 1, 0, 0, 1, 0),
2783      /* 0xE5 */ MDRP_MIRP.bind(undefined, 1, 0, 0, 1, 1),
2784      /* 0xE6 */ MDRP_MIRP.bind(undefined, 1, 0, 0, 1, 2),
2785      /* 0xE7 */ MDRP_MIRP.bind(undefined, 1, 0, 0, 1, 3),
2786      /* 0xE8 */ MDRP_MIRP.bind(undefined, 1, 0, 1, 0, 0),
2787      /* 0xE9 */ MDRP_MIRP.bind(undefined, 1, 0, 1, 0, 1),
2788      /* 0xEA */ MDRP_MIRP.bind(undefined, 1, 0, 1, 0, 2),
2789      /* 0xEB */ MDRP_MIRP.bind(undefined, 1, 0, 1, 0, 3),
2790      /* 0xEC */ MDRP_MIRP.bind(undefined, 1, 0, 1, 1, 0),
2791      /* 0xED */ MDRP_MIRP.bind(undefined, 1, 0, 1, 1, 1),
2792      /* 0xEE */ MDRP_MIRP.bind(undefined, 1, 0, 1, 1, 2),
2793      /* 0xEF */ MDRP_MIRP.bind(undefined, 1, 0, 1, 1, 3),
2794      /* 0xF0 */ MDRP_MIRP.bind(undefined, 1, 1, 0, 0, 0),
2795      /* 0xF1 */ MDRP_MIRP.bind(undefined, 1, 1, 0, 0, 1),
2796      /* 0xF2 */ MDRP_MIRP.bind(undefined, 1, 1, 0, 0, 2),
2797      /* 0xF3 */ MDRP_MIRP.bind(undefined, 1, 1, 0, 0, 3),
2798      /* 0xF4 */ MDRP_MIRP.bind(undefined, 1, 1, 0, 1, 0),
2799      /* 0xF5 */ MDRP_MIRP.bind(undefined, 1, 1, 0, 1, 1),
2800      /* 0xF6 */ MDRP_MIRP.bind(undefined, 1, 1, 0, 1, 2),
2801      /* 0xF7 */ MDRP_MIRP.bind(undefined, 1, 1, 0, 1, 3),
2802      /* 0xF8 */ MDRP_MIRP.bind(undefined, 1, 1, 1, 0, 0),
2803      /* 0xF9 */ MDRP_MIRP.bind(undefined, 1, 1, 1, 0, 1),
2804      /* 0xFA */ MDRP_MIRP.bind(undefined, 1, 1, 1, 0, 2),
2805      /* 0xFB */ MDRP_MIRP.bind(undefined, 1, 1, 1, 0, 3),
2806      /* 0xFC */ MDRP_MIRP.bind(undefined, 1, 1, 1, 1, 0),
2807      /* 0xFD */ MDRP_MIRP.bind(undefined, 1, 1, 1, 1, 1),
2808      /* 0xFE */ MDRP_MIRP.bind(undefined, 1, 1, 1, 1, 2),
2809      /* 0xFF */ MDRP_MIRP.bind(undefined, 1, 1, 1, 1, 3)
2810  ];
2811  
2812  export default Hinting;
2813  
2814  /*****************************
2815    Mathematical Considerations
2816  ******************************
2817  
2818  fv ... refers to freedom vector
2819  pv ... refers to projection vector
2820  rp ... refers to reference point
2821  p  ... refers to to point being operated on
2822  d  ... refers to distance
2823  
2824  SETRELATIVE:
2825  ============
2826  
2827  case freedom vector == x-axis:
2828  ------------------------------
2829  
2830                          (pv)
2831                       .-'
2832                rpd .-'
2833                 .-*
2834            d .-'90°'
2835           .-'       '
2836        .-'           '
2837     *-'               ' b
2838    rp                  '
2839                         '
2840                          '
2841              p *----------*-------------- (fv)
2842                            pm
2843  
2844    rpdx = rpx + d * pv.x
2845    rpdy = rpy + d * pv.y
2846  
2847    equation of line b
2848  
2849     y - rpdy = pvns * (x- rpdx)
2850  
2851     y = p.y
2852  
2853     x = rpdx + ( p.y - rpdy ) / pvns
2854  
2855  
2856  case freedom vector == y-axis:
2857  ------------------------------
2858  
2859      * pm
2860      |\
2861      | \
2862      |  \
2863      |   \
2864      |    \
2865      |     \
2866      |      \
2867      |       \
2868      |        \
2869      |         \ b
2870      |          \
2871      |           \
2872      |            \    .-' (pv)
2873      |         90° \.-'
2874      |           .-'* rpd
2875      |        .-'
2876      *     *-'  d
2877      p     rp
2878  
2879    rpdx = rpx + d * pv.x
2880    rpdy = rpy + d * pv.y
2881  
2882    equation of line b:
2883             pvns ... normal slope to pv
2884  
2885     y - rpdy = pvns * (x - rpdx)
2886  
2887     x = p.x
2888  
2889     y = rpdy +  pvns * (p.x - rpdx)
2890  
2891  
2892  
2893  generic case:
2894  -------------
2895  
2896  
2897                                .'(fv)
2898                              .'
2899                            .* pm
2900                          .' !
2901                        .'    .
2902                      .'      !
2903                    .'         . b
2904                  .'           !
2905                 *              .
2906                p               !
2907                           90°   .    ... (pv)
2908                             ...-*-'''
2909                    ...---'''    rpd
2910           ...---'''   d
2911     *--'''
2912    rp
2913  
2914      rpdx = rpx + d * pv.x
2915      rpdy = rpy + d * pv.y
2916  
2917   equation of line b:
2918      pvns... normal slope to pv
2919  
2920      y - rpdy = pvns * (x - rpdx)
2921  
2922   equation of freedom vector line:
2923      fvs ... slope of freedom vector (=fy/fx)
2924  
2925      y - py = fvs * (x - px)
2926  
2927  
2928    on pm both equations are true for same x/y
2929  
2930      y - rpdy = pvns * (x - rpdx)
2931  
2932      y - py = fvs * (x - px)
2933  
2934    form to y and set equal:
2935  
2936      pvns * (x - rpdx) + rpdy = fvs * (x - px) + py
2937  
2938    expand:
2939  
2940      pvns * x - pvns * rpdx + rpdy = fvs * x - fvs * px + py
2941  
2942    switch:
2943  
2944      fvs * x - fvs * px + py = pvns * x - pvns * rpdx + rpdy
2945  
2946    solve for x:
2947  
2948      fvs * x - pvns * x = fvs * px - pvns * rpdx - py + rpdy
2949  
2950  
2951  
2952            fvs * px - pvns * rpdx + rpdy - py
2953      x =  -----------------------------------
2954                   fvs - pvns
2955  
2956    and:
2957  
2958      y = fvs * (x - px) + py
2959  
2960  
2961  
2962  INTERPOLATE:
2963  ============
2964  
2965  Examples of point interpolation.
2966  
2967  The weight of the movement of the reference point gets bigger
2968  the further the other reference point is away, thus the safest
2969  option (that is avoiding 0/0 divisions) is to weight the
2970  original distance of the other point by the sum of both distances.
2971  
2972  If the sum of both distances is 0, then move the point by the
2973  arithmetic average of the movement of both reference points.
2974  
2975  
2976  
2977  
2978             (+6)
2979      rp1o *---->*rp1
2980           .     .                          (+12)
2981           .     .                  rp2o *---------->* rp2
2982           .     .                       .           .
2983           .     .                       .           .
2984           .    10          20           .           .
2985           |.........|...................|           .
2986                 .   .                               .
2987                 .   . (+8)                          .
2988                  po *------>*p                      .
2989                 .           .                       .
2990                 .    12     .          24           .
2991                 |...........|.......................|
2992                                    36
2993  
2994  
2995  -------
2996  
2997  
2998  
2999             (+10)
3000      rp1o *-------->*rp1
3001           .         .                      (-10)
3002           .         .              rp2 *<---------* rpo2
3003           .         .                   .         .
3004           .         .                   .         .
3005           .    10   .          30       .         .
3006           |.........|.............................|
3007                     .                   .
3008                     . (+5)              .
3009                  po *--->* p            .
3010                     .    .              .
3011                     .    .   20         .
3012                     |....|..............|
3013                       5        15
3014  
3015  
3016  -------
3017  
3018  
3019             (+10)
3020      rp1o *-------->*rp1
3021           .         .
3022           .         .
3023      rp2o *-------->*rp2
3024  
3025  
3026                                 (+10)
3027                            po *-------->* p
3028  
3029  -------
3030  
3031  
3032             (+10)
3033      rp1o *-------->*rp1
3034           .         .
3035           .         .(+30)
3036      rp2o *---------------------------->*rp2
3037  
3038  
3039                                          (+25)
3040                            po *----------------------->* p
3041  
3042  
3043  
3044  vim: set ts=4 sw=4 expandtab:
3045  *****/