3063 lines
73 KiB
Plaintext
3063 lines
73 KiB
Plaintext
/* A TrueType font hinting interpreter.
|
|
*
|
|
* (c) 2017 Axel Kittenberger
|
|
*
|
|
* This interpreter has been implemented according to this documentation:
|
|
* https://developer.apple.com/fonts/TrueType-Reference-Manual/RM05/Chap5.html
|
|
*
|
|
* According to the documentation F24DOT6 values are used for pixels.
|
|
* That means calculation is 1/64 pixel accurate and uses integer operations.
|
|
* However, Javascript has floating point operations by default and only
|
|
* those are available. One could make a case to simulate the 1/64 accuracy
|
|
* exactly by truncating after every division operation
|
|
* (for example with << 0) to get pixel exactly results as other TrueType
|
|
* implementations. It may make sense since some fonts are pixel optimized
|
|
* by hand using DELTAP instructions. The current implementation doesn't
|
|
* and rather uses full floating point precision.
|
|
*
|
|
* xScale, yScale and rotation is currently ignored.
|
|
*
|
|
* A few non-trivial instructions are missing as I didn't encounter yet
|
|
* a font that used them to test a possible implementation.
|
|
*
|
|
* Some fonts seem to use undocumented features regarding the twilight zone.
|
|
* Only some of them are implemented as they were encountered.
|
|
*
|
|
* The exports.DEBUG statements are removed on the minified distribution file.
|
|
*/
|
|
'use strict';
|
|
|
|
import glyf from './tables/glyf';
|
|
|
|
let instructionTable;
|
|
let exec;
|
|
let execGlyph;
|
|
let execComponent;
|
|
|
|
/*
|
|
* Creates a hinting object.
|
|
*
|
|
* There ought to be exactly one
|
|
* for each truetype font that is used for hinting.
|
|
*/
|
|
function Hinting(font) {
|
|
// the font this hinting object is for
|
|
this.font = font;
|
|
|
|
this.getCommands = function (hPoints) {
|
|
return glyf.getPath(hPoints).commands;
|
|
};
|
|
|
|
// cached states
|
|
this._fpgmState =
|
|
this._prepState =
|
|
undefined;
|
|
|
|
// errorState
|
|
// 0 ... all okay
|
|
// 1 ... had an error in a glyf,
|
|
// continue working but stop spamming
|
|
// the console
|
|
// 2 ... error at prep, stop hinting at this ppem
|
|
// 3 ... error at fpeg, stop hinting for this font at all
|
|
this._errorState = 0;
|
|
}
|
|
|
|
/*
|
|
* Not rounding.
|
|
*/
|
|
function roundOff(v) {
|
|
return v;
|
|
}
|
|
|
|
/*
|
|
* Rounding to grid.
|
|
*/
|
|
function roundToGrid(v) {
|
|
//Rounding in TT is supposed to "symmetrical around zero"
|
|
return Math.sign(v) * Math.round(Math.abs(v));
|
|
}
|
|
|
|
/*
|
|
* Rounding to double grid.
|
|
*/
|
|
function roundToDoubleGrid(v) {
|
|
return Math.sign(v) * Math.round(Math.abs(v * 2)) / 2;
|
|
}
|
|
|
|
/*
|
|
* Rounding to half grid.
|
|
*/
|
|
function roundToHalfGrid(v) {
|
|
return Math.sign(v) * (Math.round(Math.abs(v) + 0.5) - 0.5);
|
|
}
|
|
|
|
/*
|
|
* Rounding to up to grid.
|
|
*/
|
|
function roundUpToGrid(v) {
|
|
return Math.sign(v) * Math.ceil(Math.abs(v));
|
|
}
|
|
|
|
/*
|
|
* Rounding to down to grid.
|
|
*/
|
|
function roundDownToGrid(v) {
|
|
return Math.sign(v) * Math.floor(Math.abs(v));
|
|
}
|
|
|
|
/*
|
|
* Super rounding.
|
|
*/
|
|
const roundSuper = function (v) {
|
|
const period = this.srPeriod;
|
|
let phase = this.srPhase;
|
|
const threshold = this.srThreshold;
|
|
let sign = 1;
|
|
|
|
if (v < 0) {
|
|
v = -v;
|
|
sign = -1;
|
|
}
|
|
|
|
v += threshold - phase;
|
|
|
|
v = Math.trunc(v / period) * period;
|
|
|
|
v += phase;
|
|
|
|
// according to http://xgridfit.sourceforge.net/round.html
|
|
if (v < 0) return phase * sign;
|
|
|
|
return v * sign;
|
|
};
|
|
|
|
/*
|
|
* Unit vector of x-axis.
|
|
*/
|
|
const xUnitVector = {
|
|
x: 1,
|
|
|
|
y: 0,
|
|
|
|
axis: 'x',
|
|
|
|
// Gets the projected distance between two points.
|
|
// o1/o2 ... if true, respective original position is used.
|
|
distance: function (p1, p2, o1, o2) {
|
|
return (o1 ? p1.xo : p1.x) - (o2 ? p2.xo : p2.x);
|
|
},
|
|
|
|
// Moves point p so the moved position has the same relative
|
|
// position to the moved positions of rp1 and rp2 than the
|
|
// original positions had.
|
|
//
|
|
// See APPENDIX on INTERPOLATE at the bottom of this file.
|
|
interpolate: function (p, rp1, rp2, pv) {
|
|
let do1;
|
|
let do2;
|
|
let doa1;
|
|
let doa2;
|
|
let dm1;
|
|
let dm2;
|
|
let dt;
|
|
|
|
if (!pv || pv === this) {
|
|
do1 = p.xo - rp1.xo;
|
|
do2 = p.xo - rp2.xo;
|
|
dm1 = rp1.x - rp1.xo;
|
|
dm2 = rp2.x - rp2.xo;
|
|
doa1 = Math.abs(do1);
|
|
doa2 = Math.abs(do2);
|
|
dt = doa1 + doa2;
|
|
|
|
if (dt === 0) {
|
|
p.x = p.xo + (dm1 + dm2) / 2;
|
|
return;
|
|
}
|
|
|
|
p.x = p.xo + (dm1 * doa2 + dm2 * doa1) / dt;
|
|
return;
|
|
}
|
|
|
|
do1 = pv.distance(p, rp1, true, true);
|
|
do2 = pv.distance(p, rp2, true, true);
|
|
dm1 = pv.distance(rp1, rp1, false, true);
|
|
dm2 = pv.distance(rp2, rp2, false, true);
|
|
doa1 = Math.abs(do1);
|
|
doa2 = Math.abs(do2);
|
|
dt = doa1 + doa2;
|
|
|
|
if (dt === 0) {
|
|
xUnitVector.setRelative(p, p, (dm1 + dm2) / 2, pv, true);
|
|
return;
|
|
}
|
|
|
|
xUnitVector.setRelative(p, p, (dm1 * doa2 + dm2 * doa1) / dt, pv, true);
|
|
},
|
|
|
|
// Slope of line normal to this
|
|
normalSlope: Number.NEGATIVE_INFINITY,
|
|
|
|
// Sets the point 'p' relative to point 'rp'
|
|
// by the distance 'd'.
|
|
//
|
|
// See APPENDIX on SETRELATIVE at the bottom of this file.
|
|
//
|
|
// p ... point to set
|
|
// rp ... reference point
|
|
// d ... distance on projection vector
|
|
// pv ... projection vector (undefined = this)
|
|
// org ... if true, uses the original position of rp as reference.
|
|
setRelative: function (p, rp, d, pv, org) {
|
|
if (!pv || pv === this) {
|
|
p.x = (org ? rp.xo : rp.x) + d;
|
|
return;
|
|
}
|
|
|
|
const rpx = org ? rp.xo : rp.x;
|
|
const rpy = org ? rp.yo : rp.y;
|
|
const rpdx = rpx + d * pv.x;
|
|
const rpdy = rpy + d * pv.y;
|
|
|
|
p.x = rpdx + (p.y - rpdy) / pv.normalSlope;
|
|
},
|
|
|
|
// Slope of vector line.
|
|
slope: 0,
|
|
|
|
// Touches the point p.
|
|
touch: function (p) {
|
|
p.xTouched = true;
|
|
},
|
|
|
|
// Tests if a point p is touched.
|
|
touched: function (p) {
|
|
return p.xTouched;
|
|
},
|
|
|
|
// Untouches the point p.
|
|
untouch: function (p) {
|
|
p.xTouched = false;
|
|
}
|
|
};
|
|
|
|
/*
|
|
* Unit vector of y-axis.
|
|
*/
|
|
const yUnitVector = {
|
|
x: 0,
|
|
|
|
y: 1,
|
|
|
|
axis: 'y',
|
|
|
|
// Gets the projected distance between two points.
|
|
// o1/o2 ... if true, respective original position is used.
|
|
distance: function (p1, p2, o1, o2) {
|
|
return (o1 ? p1.yo : p1.y) - (o2 ? p2.yo : p2.y);
|
|
},
|
|
|
|
// Moves point p so the moved position has the same relative
|
|
// position to the moved positions of rp1 and rp2 than the
|
|
// original positions had.
|
|
//
|
|
// See APPENDIX on INTERPOLATE at the bottom of this file.
|
|
interpolate: function (p, rp1, rp2, pv) {
|
|
let do1;
|
|
let do2;
|
|
let doa1;
|
|
let doa2;
|
|
let dm1;
|
|
let dm2;
|
|
let dt;
|
|
|
|
if (!pv || pv === this) {
|
|
do1 = p.yo - rp1.yo;
|
|
do2 = p.yo - rp2.yo;
|
|
dm1 = rp1.y - rp1.yo;
|
|
dm2 = rp2.y - rp2.yo;
|
|
doa1 = Math.abs(do1);
|
|
doa2 = Math.abs(do2);
|
|
dt = doa1 + doa2;
|
|
|
|
if (dt === 0) {
|
|
p.y = p.yo + (dm1 + dm2) / 2;
|
|
return;
|
|
}
|
|
|
|
p.y = p.yo + (dm1 * doa2 + dm2 * doa1) / dt;
|
|
return;
|
|
}
|
|
|
|
do1 = pv.distance(p, rp1, true, true);
|
|
do2 = pv.distance(p, rp2, true, true);
|
|
dm1 = pv.distance(rp1, rp1, false, true);
|
|
dm2 = pv.distance(rp2, rp2, false, true);
|
|
doa1 = Math.abs(do1);
|
|
doa2 = Math.abs(do2);
|
|
dt = doa1 + doa2;
|
|
|
|
if (dt === 0) {
|
|
yUnitVector.setRelative(p, p, (dm1 + dm2) / 2, pv, true);
|
|
return;
|
|
}
|
|
|
|
yUnitVector.setRelative(p, p, (dm1 * doa2 + dm2 * doa1) / dt, pv, true);
|
|
},
|
|
|
|
// Slope of line normal to this.
|
|
normalSlope: 0,
|
|
|
|
// Sets the point 'p' relative to point 'rp'
|
|
// by the distance 'd'
|
|
//
|
|
// See APPENDIX on SETRELATIVE at the bottom of this file.
|
|
//
|
|
// p ... point to set
|
|
// rp ... reference point
|
|
// d ... distance on projection vector
|
|
// pv ... projection vector (undefined = this)
|
|
// org ... if true, uses the original position of rp as reference.
|
|
setRelative: function (p, rp, d, pv, org) {
|
|
if (!pv || pv === this) {
|
|
p.y = (org ? rp.yo : rp.y) + d;
|
|
return;
|
|
}
|
|
|
|
const rpx = org ? rp.xo : rp.x;
|
|
const rpy = org ? rp.yo : rp.y;
|
|
const rpdx = rpx + d * pv.x;
|
|
const rpdy = rpy + d * pv.y;
|
|
|
|
p.y = rpdy + pv.normalSlope * (p.x - rpdx);
|
|
},
|
|
|
|
// Slope of vector line.
|
|
slope: Number.POSITIVE_INFINITY,
|
|
|
|
// Touches the point p.
|
|
touch: function (p) {
|
|
p.yTouched = true;
|
|
},
|
|
|
|
// Tests if a point p is touched.
|
|
touched: function (p) {
|
|
return p.yTouched;
|
|
},
|
|
|
|
// Untouches the point p.
|
|
untouch: function (p) {
|
|
p.yTouched = false;
|
|
}
|
|
};
|
|
|
|
Object.freeze(xUnitVector);
|
|
Object.freeze(yUnitVector);
|
|
|
|
/*
|
|
* Creates a unit vector that is not x- or y-axis.
|
|
*/
|
|
function UnitVector(x, y) {
|
|
this.x = x;
|
|
this.y = y;
|
|
this.axis = undefined;
|
|
this.slope = y / x;
|
|
this.normalSlope = -x / y;
|
|
Object.freeze(this);
|
|
}
|
|
|
|
/*
|
|
* Gets the projected distance between two points.
|
|
* o1/o2 ... if true, respective original position is used.
|
|
*/
|
|
UnitVector.prototype.distance = function(p1, p2, o1, o2) {
|
|
return (
|
|
this.x * xUnitVector.distance(p1, p2, o1, o2) +
|
|
this.y * yUnitVector.distance(p1, p2, o1, o2)
|
|
);
|
|
};
|
|
|
|
/*
|
|
* Moves point p so the moved position has the same relative
|
|
* position to the moved positions of rp1 and rp2 than the
|
|
* original positions had.
|
|
*
|
|
* See APPENDIX on INTERPOLATE at the bottom of this file.
|
|
*/
|
|
UnitVector.prototype.interpolate = function(p, rp1, rp2, pv) {
|
|
let dm1;
|
|
let dm2;
|
|
let do1;
|
|
let do2;
|
|
let doa1;
|
|
let doa2;
|
|
let dt;
|
|
|
|
do1 = pv.distance(p, rp1, true, true);
|
|
do2 = pv.distance(p, rp2, true, true);
|
|
dm1 = pv.distance(rp1, rp1, false, true);
|
|
dm2 = pv.distance(rp2, rp2, false, true);
|
|
doa1 = Math.abs(do1);
|
|
doa2 = Math.abs(do2);
|
|
dt = doa1 + doa2;
|
|
|
|
if (dt === 0) {
|
|
this.setRelative(p, p, (dm1 + dm2) / 2, pv, true);
|
|
return;
|
|
}
|
|
|
|
this.setRelative(p, p, (dm1 * doa2 + dm2 * doa1) / dt, pv, true);
|
|
};
|
|
|
|
/*
|
|
* Sets the point 'p' relative to point 'rp'
|
|
* by the distance 'd'
|
|
*
|
|
* See APPENDIX on SETRELATIVE at the bottom of this file.
|
|
*
|
|
* p ... point to set
|
|
* rp ... reference point
|
|
* d ... distance on projection vector
|
|
* pv ... projection vector (undefined = this)
|
|
* org ... if true, uses the original position of rp as reference.
|
|
*/
|
|
UnitVector.prototype.setRelative = function(p, rp, d, pv, org) {
|
|
pv = pv || this;
|
|
|
|
const rpx = org ? rp.xo : rp.x;
|
|
const rpy = org ? rp.yo : rp.y;
|
|
const rpdx = rpx + d * pv.x;
|
|
const rpdy = rpy + d * pv.y;
|
|
|
|
const pvns = pv.normalSlope;
|
|
const fvs = this.slope;
|
|
|
|
const px = p.x;
|
|
const py = p.y;
|
|
|
|
p.x = (fvs * px - pvns * rpdx + rpdy - py) / (fvs - pvns);
|
|
p.y = fvs * (p.x - px) + py;
|
|
};
|
|
|
|
/*
|
|
* Touches the point p.
|
|
*/
|
|
UnitVector.prototype.touch = function(p) {
|
|
p.xTouched = true;
|
|
p.yTouched = true;
|
|
};
|
|
|
|
/*
|
|
* Returns a unit vector with x/y coordinates.
|
|
*/
|
|
function getUnitVector(x, y) {
|
|
const d = Math.sqrt(x * x + y * y);
|
|
|
|
x /= d;
|
|
y /= d;
|
|
|
|
if (x === 1 && y === 0) return xUnitVector;
|
|
else if (x === 0 && y === 1) return yUnitVector;
|
|
else return new UnitVector(x, y);
|
|
}
|
|
|
|
/*
|
|
* Creates a point in the hinting engine.
|
|
*/
|
|
function HPoint(
|
|
x,
|
|
y,
|
|
lastPointOfContour,
|
|
onCurve
|
|
) {
|
|
this.x = this.xo = Math.round(x * 64) / 64; // hinted x value and original x-value
|
|
this.y = this.yo = Math.round(y * 64) / 64; // hinted y value and original y-value
|
|
|
|
this.lastPointOfContour = lastPointOfContour;
|
|
this.onCurve = onCurve;
|
|
this.prevPointOnContour = undefined;
|
|
this.nextPointOnContour = undefined;
|
|
this.xTouched = false;
|
|
this.yTouched = false;
|
|
|
|
Object.preventExtensions(this);
|
|
}
|
|
|
|
/*
|
|
* Returns the next touched point on the contour.
|
|
*
|
|
* v ... unit vector to test touch axis.
|
|
*/
|
|
HPoint.prototype.nextTouched = function(v) {
|
|
let p = this.nextPointOnContour;
|
|
|
|
while (!v.touched(p) && p !== this) p = p.nextPointOnContour;
|
|
|
|
return p;
|
|
};
|
|
|
|
/*
|
|
* Returns the previous touched point on the contour
|
|
*
|
|
* v ... unit vector to test touch axis.
|
|
*/
|
|
HPoint.prototype.prevTouched = function(v) {
|
|
let p = this.prevPointOnContour;
|
|
|
|
while (!v.touched(p) && p !== this) p = p.prevPointOnContour;
|
|
|
|
return p;
|
|
};
|
|
|
|
/*
|
|
* The zero point.
|
|
*/
|
|
const HPZero = Object.freeze(new HPoint(0, 0));
|
|
|
|
/*
|
|
* The default state of the interpreter.
|
|
*
|
|
* Note: Freezing the defaultState and then deriving from it
|
|
* makes the V8 Javascript engine going awkward,
|
|
* so this is avoided, albeit the defaultState shouldn't
|
|
* ever change.
|
|
*/
|
|
const defaultState = {
|
|
cvCutIn: 17 / 16, // control value cut in
|
|
deltaBase: 9,
|
|
deltaShift: 0.125,
|
|
loop: 1, // loops some instructions
|
|
minDis: 1, // minimum distance
|
|
autoFlip: true
|
|
};
|
|
|
|
/*
|
|
* The current state of the interpreter.
|
|
*
|
|
* env ... 'fpgm' or 'prep' or 'glyf'
|
|
* prog ... the program
|
|
*/
|
|
function State(env, prog) {
|
|
this.env = env;
|
|
this.stack = [];
|
|
this.prog = prog;
|
|
|
|
switch (env) {
|
|
case 'glyf' :
|
|
this.zp0 = this.zp1 = this.zp2 = 1;
|
|
this.rp0 = this.rp1 = this.rp2 = 0;
|
|
/* fall through */
|
|
case 'prep' :
|
|
this.fv = this.pv = this.dpv = xUnitVector;
|
|
this.round = roundToGrid;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Executes a glyph program.
|
|
*
|
|
* This does the hinting for each glyph.
|
|
*
|
|
* Returns an array of moved points.
|
|
*
|
|
* glyph: the glyph to hint
|
|
* ppem: the size the glyph is rendered for
|
|
*/
|
|
Hinting.prototype.exec = function(glyph, ppem) {
|
|
if (typeof ppem !== 'number') {
|
|
throw new Error('Point size is not a number!');
|
|
}
|
|
|
|
// Received a fatal error, don't do any hinting anymore.
|
|
if (this._errorState > 2) return;
|
|
|
|
const font = this.font;
|
|
let prepState = this._prepState;
|
|
|
|
if (!prepState || prepState.ppem !== ppem) {
|
|
let fpgmState = this._fpgmState;
|
|
|
|
if (!fpgmState) {
|
|
// Executes the fpgm state.
|
|
// This is used by fonts to define functions.
|
|
State.prototype = defaultState;
|
|
|
|
fpgmState =
|
|
this._fpgmState =
|
|
new State('fpgm', font.tables.fpgm);
|
|
|
|
fpgmState.funcs = [ ];
|
|
fpgmState.font = font;
|
|
|
|
if (exports.DEBUG) {
|
|
console.log('---EXEC FPGM---');
|
|
fpgmState.step = -1;
|
|
}
|
|
|
|
try {
|
|
exec(fpgmState);
|
|
} catch (e) {
|
|
console.log('Hinting error in FPGM:' + e);
|
|
this._errorState = 3;
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Executes the prep program for this ppem setting.
|
|
// This is used by fonts to set cvt values
|
|
// depending on to be rendered font size.
|
|
|
|
State.prototype = fpgmState;
|
|
prepState =
|
|
this._prepState =
|
|
new State('prep', font.tables.prep);
|
|
|
|
prepState.ppem = ppem;
|
|
|
|
// Creates a copy of the cvt table
|
|
// and scales it to the current ppem setting.
|
|
const oCvt = font.tables.cvt;
|
|
if (oCvt) {
|
|
const cvt = prepState.cvt = new Array(oCvt.length);
|
|
const scale = ppem / font.unitsPerEm;
|
|
for (let c = 0; c < oCvt.length; c++) {
|
|
cvt[c] = oCvt[c] * scale;
|
|
}
|
|
} else {
|
|
prepState.cvt = [];
|
|
}
|
|
|
|
if (exports.DEBUG) {
|
|
console.log('---EXEC PREP---');
|
|
prepState.step = -1;
|
|
}
|
|
|
|
try {
|
|
exec(prepState);
|
|
} catch (e) {
|
|
if (this._errorState < 2) {
|
|
console.log('Hinting error in PREP:' + e);
|
|
}
|
|
this._errorState = 2;
|
|
}
|
|
}
|
|
|
|
if (this._errorState > 1) return;
|
|
|
|
try {
|
|
return execGlyph(glyph, prepState);
|
|
} catch (e) {
|
|
if (this._errorState < 1) {
|
|
console.log('Hinting error:' + e);
|
|
console.log('Note: further hinting errors are silenced');
|
|
}
|
|
this._errorState = 1;
|
|
return undefined;
|
|
}
|
|
};
|
|
|
|
/*
|
|
* Executes the hinting program for a glyph.
|
|
*/
|
|
execGlyph = function(glyph, prepState) {
|
|
// original point positions
|
|
const xScale = prepState.ppem / prepState.font.unitsPerEm;
|
|
const yScale = xScale;
|
|
let components = glyph.components;
|
|
let contours;
|
|
let gZone;
|
|
let state;
|
|
|
|
State.prototype = prepState;
|
|
if (!components) {
|
|
state = new State('glyf', glyph.instructions);
|
|
if (exports.DEBUG) {
|
|
console.log('---EXEC GLYPH---');
|
|
state.step = -1;
|
|
}
|
|
execComponent(glyph, state, xScale, yScale);
|
|
gZone = state.gZone;
|
|
} else {
|
|
const font = prepState.font;
|
|
gZone = [];
|
|
contours = [];
|
|
for (let i = 0; i < components.length; i++) {
|
|
const c = components[i];
|
|
const cg = font.glyphs.get(c.glyphIndex);
|
|
|
|
state = new State('glyf', cg.instructions);
|
|
|
|
if (exports.DEBUG) {
|
|
console.log('---EXEC COMP ' + i + '---');
|
|
state.step = -1;
|
|
}
|
|
|
|
execComponent(cg, state, xScale, yScale);
|
|
// appends the computed points to the result array
|
|
// post processes the component points
|
|
const dx = Math.round(c.dx * xScale);
|
|
const dy = Math.round(c.dy * yScale);
|
|
const gz = state.gZone;
|
|
const cc = state.contours;
|
|
for (let pi = 0; pi < gz.length; pi++) {
|
|
const p = gz[pi];
|
|
p.xTouched = p.yTouched = false;
|
|
p.xo = p.x = p.x + dx;
|
|
p.yo = p.y = p.y + dy;
|
|
}
|
|
|
|
const gLen = gZone.length;
|
|
gZone.push.apply(gZone, gz);
|
|
for (let j = 0; j < cc.length; j++) {
|
|
contours.push(cc[j] + gLen);
|
|
}
|
|
}
|
|
|
|
if (glyph.instructions && !state.inhibitGridFit) {
|
|
// the composite has instructions on its own
|
|
state = new State('glyf', glyph.instructions);
|
|
|
|
state.gZone = state.z0 = state.z1 = state.z2 = gZone;
|
|
|
|
state.contours = contours;
|
|
|
|
// note: HPZero cannot be used here, since
|
|
// the point might be modified
|
|
gZone.push(
|
|
new HPoint(0, 0),
|
|
new HPoint(Math.round(glyph.advanceWidth * xScale), 0)
|
|
);
|
|
|
|
if (exports.DEBUG) {
|
|
console.log('---EXEC COMPOSITE---');
|
|
state.step = -1;
|
|
}
|
|
|
|
exec(state);
|
|
|
|
gZone.length -= 2;
|
|
}
|
|
}
|
|
|
|
return gZone;
|
|
};
|
|
|
|
/*
|
|
* Executes the hinting program for a component of a multi-component glyph
|
|
* or of the glyph itself for a non-component glyph.
|
|
*/
|
|
execComponent = function(glyph, state, xScale, yScale)
|
|
{
|
|
const points = glyph.points || [];
|
|
const pLen = points.length;
|
|
const gZone = state.gZone = state.z0 = state.z1 = state.z2 = [];
|
|
const contours = state.contours = [];
|
|
|
|
// Scales the original points and
|
|
// makes copies for the hinted points.
|
|
let cp; // current point
|
|
for (let i = 0; i < pLen; i++) {
|
|
cp = points[i];
|
|
|
|
gZone[i] = new HPoint(
|
|
cp.x * xScale,
|
|
cp.y * yScale,
|
|
cp.lastPointOfContour,
|
|
cp.onCurve
|
|
);
|
|
}
|
|
|
|
// Chain links the contours.
|
|
let sp; // start point
|
|
let np; // next point
|
|
|
|
for (let i = 0; i < pLen; i++) {
|
|
cp = gZone[i];
|
|
|
|
if (!sp) {
|
|
sp = cp;
|
|
contours.push(i);
|
|
}
|
|
|
|
if (cp.lastPointOfContour) {
|
|
cp.nextPointOnContour = sp;
|
|
sp.prevPointOnContour = cp;
|
|
sp = undefined;
|
|
} else {
|
|
np = gZone[i + 1];
|
|
cp.nextPointOnContour = np;
|
|
np.prevPointOnContour = cp;
|
|
}
|
|
}
|
|
|
|
if (state.inhibitGridFit) return;
|
|
|
|
if (exports.DEBUG) {
|
|
console.log('PROCESSING GLYPH', state.stack);
|
|
for (let i = 0; i < pLen; i++) {
|
|
console.log(i, gZone[i].x, gZone[i].y);
|
|
}
|
|
}
|
|
|
|
gZone.push(
|
|
new HPoint(0, 0),
|
|
new HPoint(Math.round(glyph.advanceWidth * xScale), 0)
|
|
);
|
|
|
|
exec(state);
|
|
|
|
// Removes the extra points.
|
|
gZone.length -= 2;
|
|
|
|
if (exports.DEBUG) {
|
|
console.log('FINISHED GLYPH', state.stack);
|
|
for (let i = 0; i < pLen; i++) {
|
|
console.log(i, gZone[i].x, gZone[i].y);
|
|
}
|
|
}
|
|
};
|
|
|
|
/*
|
|
* Executes the program loaded in state.
|
|
*/
|
|
exec = function(state) {
|
|
let prog = state.prog;
|
|
|
|
if (!prog) return;
|
|
|
|
const pLen = prog.length;
|
|
let ins;
|
|
|
|
for (state.ip = 0; state.ip < pLen; state.ip++) {
|
|
if (exports.DEBUG) state.step++;
|
|
ins = instructionTable[prog[state.ip]];
|
|
|
|
if (!ins) {
|
|
throw new Error(
|
|
'unknown instruction: 0x' +
|
|
Number(prog[state.ip]).toString(16)
|
|
);
|
|
}
|
|
|
|
ins(state);
|
|
|
|
// very extensive debugging for each step
|
|
/*
|
|
if (exports.DEBUG) {
|
|
var da;
|
|
if (state.gZone) {
|
|
da = [];
|
|
for (let i = 0; i < state.gZone.length; i++)
|
|
{
|
|
da.push(i + ' ' +
|
|
state.gZone[i].x * 64 + ' ' +
|
|
state.gZone[i].y * 64 + ' ' +
|
|
(state.gZone[i].xTouched ? 'x' : '') +
|
|
(state.gZone[i].yTouched ? 'y' : '')
|
|
);
|
|
}
|
|
console.log('GZ', da);
|
|
}
|
|
|
|
if (state.tZone) {
|
|
da = [];
|
|
for (let i = 0; i < state.tZone.length; i++) {
|
|
da.push(i + ' ' +
|
|
state.tZone[i].x * 64 + ' ' +
|
|
state.tZone[i].y * 64 + ' ' +
|
|
(state.tZone[i].xTouched ? 'x' : '') +
|
|
(state.tZone[i].yTouched ? 'y' : '')
|
|
);
|
|
}
|
|
console.log('TZ', da);
|
|
}
|
|
|
|
if (state.stack.length > 10) {
|
|
console.log(
|
|
state.stack.length,
|
|
'...', state.stack.slice(state.stack.length - 10)
|
|
);
|
|
} else {
|
|
console.log(state.stack.length, state.stack);
|
|
}
|
|
}
|
|
*/
|
|
}
|
|
};
|
|
|
|
/*
|
|
* Initializes the twilight zone.
|
|
*
|
|
* This is only done if a SZPx instruction
|
|
* refers to the twilight zone.
|
|
*/
|
|
function initTZone(state)
|
|
{
|
|
const tZone = state.tZone = new Array(state.gZone.length);
|
|
|
|
// no idea if this is actually correct...
|
|
for (let i = 0; i < tZone.length; i++)
|
|
{
|
|
tZone[i] = new HPoint(0, 0);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Skips the instruction pointer ahead over an IF/ELSE block.
|
|
* handleElse .. if true breaks on matching ELSE
|
|
*/
|
|
function skip(state, handleElse)
|
|
{
|
|
const prog = state.prog;
|
|
let ip = state.ip;
|
|
let nesting = 1;
|
|
let ins;
|
|
|
|
do {
|
|
ins = prog[++ip];
|
|
if (ins === 0x58) // IF
|
|
nesting++;
|
|
else if (ins === 0x59) // EIF
|
|
nesting--;
|
|
else if (ins === 0x40) // NPUSHB
|
|
ip += prog[ip + 1] + 1;
|
|
else if (ins === 0x41) // NPUSHW
|
|
ip += 2 * prog[ip + 1] + 1;
|
|
else if (ins >= 0xB0 && ins <= 0xB7) // PUSHB
|
|
ip += ins - 0xB0 + 1;
|
|
else if (ins >= 0xB8 && ins <= 0xBF) // PUSHW
|
|
ip += (ins - 0xB8 + 1) * 2;
|
|
else if (handleElse && nesting === 1 && ins === 0x1B) // ELSE
|
|
break;
|
|
} while (nesting > 0);
|
|
|
|
state.ip = ip;
|
|
}
|
|
|
|
/*----------------------------------------------------------*
|
|
* And then a lot of instructions... *
|
|
*----------------------------------------------------------*/
|
|
|
|
// SVTCA[a] Set freedom and projection Vectors To Coordinate Axis
|
|
// 0x00-0x01
|
|
function SVTCA(v, state) {
|
|
if (exports.DEBUG) console.log(state.step, 'SVTCA[' + v.axis + ']');
|
|
|
|
state.fv = state.pv = state.dpv = v;
|
|
}
|
|
|
|
// SPVTCA[a] Set Projection Vector to Coordinate Axis
|
|
// 0x02-0x03
|
|
function SPVTCA(v, state) {
|
|
if (exports.DEBUG) console.log(state.step, 'SPVTCA[' + v.axis + ']');
|
|
|
|
state.pv = state.dpv = v;
|
|
}
|
|
|
|
// SFVTCA[a] Set Freedom Vector to Coordinate Axis
|
|
// 0x04-0x05
|
|
function SFVTCA(v, state) {
|
|
if (exports.DEBUG) console.log(state.step, 'SFVTCA[' + v.axis + ']');
|
|
|
|
state.fv = v;
|
|
}
|
|
|
|
// SPVTL[a] Set Projection Vector To Line
|
|
// 0x06-0x07
|
|
function SPVTL(a, state) {
|
|
const stack = state.stack;
|
|
const p2i = stack.pop();
|
|
const p1i = stack.pop();
|
|
const p2 = state.z2[p2i];
|
|
const p1 = state.z1[p1i];
|
|
|
|
if (exports.DEBUG) console.log('SPVTL[' + a + ']', p2i, p1i);
|
|
|
|
let dx;
|
|
let dy;
|
|
|
|
if (!a) {
|
|
dx = p1.x - p2.x;
|
|
dy = p1.y - p2.y;
|
|
} else {
|
|
dx = p2.y - p1.y;
|
|
dy = p1.x - p2.x;
|
|
}
|
|
|
|
state.pv = state.dpv = getUnitVector(dx, dy);
|
|
}
|
|
|
|
// SFVTL[a] Set Freedom Vector To Line
|
|
// 0x08-0x09
|
|
function SFVTL(a, state) {
|
|
const stack = state.stack;
|
|
const p2i = stack.pop();
|
|
const p1i = stack.pop();
|
|
const p2 = state.z2[p2i];
|
|
const p1 = state.z1[p1i];
|
|
|
|
if (exports.DEBUG) console.log('SFVTL[' + a + ']', p2i, p1i);
|
|
|
|
let dx;
|
|
let dy;
|
|
|
|
if (!a) {
|
|
dx = p1.x - p2.x;
|
|
dy = p1.y - p2.y;
|
|
} else {
|
|
dx = p2.y - p1.y;
|
|
dy = p1.x - p2.x;
|
|
}
|
|
|
|
state.fv = getUnitVector(dx, dy);
|
|
}
|
|
|
|
// SPVFS[] Set Projection Vector From Stack
|
|
// 0x0A
|
|
function SPVFS(state) {
|
|
const stack = state.stack;
|
|
const y = stack.pop();
|
|
const x = stack.pop();
|
|
|
|
if (exports.DEBUG) console.log(state.step, 'SPVFS[]', y, x);
|
|
|
|
state.pv = state.dpv = getUnitVector(x, y);
|
|
}
|
|
|
|
// SFVFS[] Set Freedom Vector From Stack
|
|
// 0x0B
|
|
function SFVFS(state) {
|
|
const stack = state.stack;
|
|
const y = stack.pop();
|
|
const x = stack.pop();
|
|
|
|
if (exports.DEBUG) console.log(state.step, 'SPVFS[]', y, x);
|
|
|
|
state.fv = getUnitVector(x, y);
|
|
}
|
|
|
|
// GPV[] Get Projection Vector
|
|
// 0x0C
|
|
function GPV(state) {
|
|
const stack = state.stack;
|
|
const pv = state.pv;
|
|
|
|
if (exports.DEBUG) console.log(state.step, 'GPV[]');
|
|
|
|
stack.push(pv.x * 0x4000);
|
|
stack.push(pv.y * 0x4000);
|
|
}
|
|
|
|
// GFV[] Get Freedom Vector
|
|
// 0x0C
|
|
function GFV(state) {
|
|
const stack = state.stack;
|
|
const fv = state.fv;
|
|
|
|
if (exports.DEBUG) console.log(state.step, 'GFV[]');
|
|
|
|
stack.push(fv.x * 0x4000);
|
|
stack.push(fv.y * 0x4000);
|
|
}
|
|
|
|
// SFVTPV[] Set Freedom Vector To Projection Vector
|
|
// 0x0E
|
|
function SFVTPV(state) {
|
|
state.fv = state.pv;
|
|
|
|
if (exports.DEBUG) console.log(state.step, 'SFVTPV[]');
|
|
}
|
|
|
|
// ISECT[] moves point p to the InterSECTion of two lines
|
|
// 0x0F
|
|
function ISECT(state)
|
|
{
|
|
const stack = state.stack;
|
|
const pa0i = stack.pop();
|
|
const pa1i = stack.pop();
|
|
const pb0i = stack.pop();
|
|
const pb1i = stack.pop();
|
|
const pi = stack.pop();
|
|
const z0 = state.z0;
|
|
const z1 = state.z1;
|
|
const pa0 = z0[pa0i];
|
|
const pa1 = z0[pa1i];
|
|
const pb0 = z1[pb0i];
|
|
const pb1 = z1[pb1i];
|
|
const p = state.z2[pi];
|
|
|
|
if (exports.DEBUG) console.log('ISECT[], ', pa0i, pa1i, pb0i, pb1i, pi);
|
|
|
|
// math from
|
|
// en.wikipedia.org/wiki/Line%E2%80%93line_intersection#Given_two_points_on_each_line
|
|
|
|
const x1 = pa0.x;
|
|
const y1 = pa0.y;
|
|
const x2 = pa1.x;
|
|
const y2 = pa1.y;
|
|
const x3 = pb0.x;
|
|
const y3 = pb0.y;
|
|
const x4 = pb1.x;
|
|
const y4 = pb1.y;
|
|
|
|
const div = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4);
|
|
const f1 = x1 * y2 - y1 * x2;
|
|
const f2 = x3 * y4 - y3 * x4;
|
|
|
|
p.x = (f1 * (x3 - x4) - f2 * (x1 - x2)) / div;
|
|
p.y = (f1 * (y3 - y4) - f2 * (y1 - y2)) / div;
|
|
}
|
|
|
|
// SRP0[] Set Reference Point 0
|
|
// 0x10
|
|
function SRP0(state) {
|
|
state.rp0 = state.stack.pop();
|
|
|
|
if (exports.DEBUG) console.log(state.step, 'SRP0[]', state.rp0);
|
|
}
|
|
|
|
// SRP1[] Set Reference Point 1
|
|
// 0x11
|
|
function SRP1(state) {
|
|
state.rp1 = state.stack.pop();
|
|
|
|
if (exports.DEBUG) console.log(state.step, 'SRP1[]', state.rp1);
|
|
}
|
|
|
|
// SRP1[] Set Reference Point 2
|
|
// 0x12
|
|
function SRP2(state) {
|
|
state.rp2 = state.stack.pop();
|
|
|
|
if (exports.DEBUG) console.log(state.step, 'SRP2[]', state.rp2);
|
|
}
|
|
|
|
// SZP0[] Set Zone Pointer 0
|
|
// 0x13
|
|
function SZP0(state) {
|
|
const n = state.stack.pop();
|
|
|
|
if (exports.DEBUG) console.log(state.step, 'SZP0[]', n);
|
|
|
|
state.zp0 = n;
|
|
|
|
switch (n) {
|
|
case 0:
|
|
if (!state.tZone) initTZone(state);
|
|
state.z0 = state.tZone;
|
|
break;
|
|
case 1 :
|
|
state.z0 = state.gZone;
|
|
break;
|
|
default :
|
|
throw new Error('Invalid zone pointer');
|
|
}
|
|
}
|
|
|
|
// SZP1[] Set Zone Pointer 1
|
|
// 0x14
|
|
function SZP1(state) {
|
|
const n = state.stack.pop();
|
|
|
|
if (exports.DEBUG) console.log(state.step, 'SZP1[]', n);
|
|
|
|
state.zp1 = n;
|
|
|
|
switch (n) {
|
|
case 0:
|
|
if (!state.tZone) initTZone(state);
|
|
state.z1 = state.tZone;
|
|
break;
|
|
case 1 :
|
|
state.z1 = state.gZone;
|
|
break;
|
|
default :
|
|
throw new Error('Invalid zone pointer');
|
|
}
|
|
}
|
|
|
|
// SZP2[] Set Zone Pointer 2
|
|
// 0x15
|
|
function SZP2(state) {
|
|
const n = state.stack.pop();
|
|
|
|
if (exports.DEBUG) console.log(state.step, 'SZP2[]', n);
|
|
|
|
state.zp2 = n;
|
|
|
|
switch (n) {
|
|
case 0:
|
|
if (!state.tZone) initTZone(state);
|
|
state.z2 = state.tZone;
|
|
break;
|
|
case 1 :
|
|
state.z2 = state.gZone;
|
|
break;
|
|
default :
|
|
throw new Error('Invalid zone pointer');
|
|
}
|
|
}
|
|
|
|
// SZPS[] Set Zone PointerS
|
|
// 0x16
|
|
function SZPS(state) {
|
|
const n = state.stack.pop();
|
|
|
|
if (exports.DEBUG) console.log(state.step, 'SZPS[]', n);
|
|
|
|
state.zp0 = state.zp1 = state.zp2 = n;
|
|
|
|
switch (n) {
|
|
case 0:
|
|
if (!state.tZone) initTZone(state);
|
|
state.z0 = state.z1 = state.z2 = state.tZone;
|
|
break;
|
|
case 1 :
|
|
state.z0 = state.z1 = state.z2 = state.gZone;
|
|
break;
|
|
default :
|
|
throw new Error('Invalid zone pointer');
|
|
}
|
|
}
|
|
|
|
// SLOOP[] Set LOOP variable
|
|
// 0x17
|
|
function SLOOP(state) {
|
|
state.loop = state.stack.pop();
|
|
|
|
if (exports.DEBUG) console.log(state.step, 'SLOOP[]', state.loop);
|
|
}
|
|
|
|
// RTG[] Round To Grid
|
|
// 0x18
|
|
function RTG(state) {
|
|
if (exports.DEBUG) console.log(state.step, 'RTG[]');
|
|
|
|
state.round = roundToGrid;
|
|
}
|
|
|
|
// RTHG[] Round To Half Grid
|
|
// 0x19
|
|
function RTHG(state) {
|
|
if (exports.DEBUG) console.log(state.step, 'RTHG[]');
|
|
|
|
state.round = roundToHalfGrid;
|
|
}
|
|
|
|
// SMD[] Set Minimum Distance
|
|
// 0x1A
|
|
function SMD(state) {
|
|
const d = state.stack.pop();
|
|
|
|
if (exports.DEBUG) console.log(state.step, 'SMD[]', d);
|
|
|
|
state.minDis = d / 0x40;
|
|
}
|
|
|
|
// ELSE[] ELSE clause
|
|
// 0x1B
|
|
function ELSE(state) {
|
|
// This instruction has been reached by executing a then branch
|
|
// so it just skips ahead until matching EIF.
|
|
//
|
|
// In case the IF was negative the IF[] instruction already
|
|
// skipped forward over the ELSE[]
|
|
|
|
if (exports.DEBUG) console.log(state.step, 'ELSE[]');
|
|
|
|
skip(state, false);
|
|
}
|
|
|
|
// JMPR[] JuMP Relative
|
|
// 0x1C
|
|
function JMPR(state) {
|
|
const o = state.stack.pop();
|
|
|
|
if (exports.DEBUG) console.log(state.step, 'JMPR[]', o);
|
|
|
|
// A jump by 1 would do nothing.
|
|
state.ip += o - 1;
|
|
}
|
|
|
|
// SCVTCI[] Set Control Value Table Cut-In
|
|
// 0x1D
|
|
function SCVTCI(state) {
|
|
const n = state.stack.pop();
|
|
|
|
if (exports.DEBUG) console.log(state.step, 'SCVTCI[]', n);
|
|
|
|
state.cvCutIn = n / 0x40;
|
|
}
|
|
|
|
// DUP[] DUPlicate top stack element
|
|
// 0x20
|
|
function DUP(state) {
|
|
const stack = state.stack;
|
|
|
|
if (exports.DEBUG) console.log(state.step, 'DUP[]');
|
|
|
|
stack.push(stack[stack.length - 1]);
|
|
}
|
|
|
|
// POP[] POP top stack element
|
|
// 0x21
|
|
function POP(state) {
|
|
if (exports.DEBUG) console.log(state.step, 'POP[]');
|
|
|
|
state.stack.pop();
|
|
}
|
|
|
|
// CLEAR[] CLEAR the stack
|
|
// 0x22
|
|
function CLEAR(state) {
|
|
if (exports.DEBUG) console.log(state.step, 'CLEAR[]');
|
|
|
|
state.stack.length = 0;
|
|
}
|
|
|
|
// SWAP[] SWAP the top two elements on the stack
|
|
// 0x23
|
|
function SWAP(state) {
|
|
const stack = state.stack;
|
|
|
|
const a = stack.pop();
|
|
const b = stack.pop();
|
|
|
|
if (exports.DEBUG) console.log(state.step, 'SWAP[]');
|
|
|
|
stack.push(a);
|
|
stack.push(b);
|
|
}
|
|
|
|
// DEPTH[] DEPTH of the stack
|
|
// 0x24
|
|
function DEPTH(state) {
|
|
const stack = state.stack;
|
|
|
|
if (exports.DEBUG) console.log(state.step, 'DEPTH[]');
|
|
|
|
stack.push(stack.length);
|
|
}
|
|
|
|
// LOOPCALL[] LOOPCALL function
|
|
// 0x2A
|
|
function LOOPCALL(state) {
|
|
const stack = state.stack;
|
|
const fn = stack.pop();
|
|
const c = stack.pop();
|
|
|
|
if (exports.DEBUG) console.log(state.step, 'LOOPCALL[]', fn, c);
|
|
|
|
// saves callers program
|
|
const cip = state.ip;
|
|
const cprog = state.prog;
|
|
|
|
state.prog = state.funcs[fn];
|
|
|
|
// executes the function
|
|
for (let i = 0; i < c; i++) {
|
|
exec(state);
|
|
|
|
if (exports.DEBUG) console.log(
|
|
++state.step,
|
|
i + 1 < c ? 'next loopcall' : 'done loopcall',
|
|
i
|
|
);
|
|
}
|
|
|
|
// restores the callers program
|
|
state.ip = cip;
|
|
state.prog = cprog;
|
|
}
|
|
|
|
// CALL[] CALL function
|
|
// 0x2B
|
|
function CALL(state) {
|
|
const fn = state.stack.pop();
|
|
|
|
if (exports.DEBUG) console.log(state.step, 'CALL[]', fn);
|
|
|
|
// saves callers program
|
|
const cip = state.ip;
|
|
const cprog = state.prog;
|
|
|
|
state.prog = state.funcs[fn];
|
|
|
|
// executes the function
|
|
exec(state);
|
|
|
|
// restores the callers program
|
|
state.ip = cip;
|
|
state.prog = cprog;
|
|
|
|
if (exports.DEBUG) console.log(++state.step, 'returning from', fn);
|
|
}
|
|
|
|
// CINDEX[] Copy the INDEXed element to the top of the stack
|
|
// 0x25
|
|
function CINDEX(state) {
|
|
const stack = state.stack;
|
|
const k = stack.pop();
|
|
|
|
if (exports.DEBUG) console.log(state.step, 'CINDEX[]', k);
|
|
|
|
// In case of k == 1, it copies the last element after popping
|
|
// thus stack.length - k.
|
|
stack.push(stack[stack.length - k]);
|
|
}
|
|
|
|
// MINDEX[] Move the INDEXed element to the top of the stack
|
|
// 0x26
|
|
function MINDEX(state) {
|
|
const stack = state.stack;
|
|
const k = stack.pop();
|
|
|
|
if (exports.DEBUG) console.log(state.step, 'MINDEX[]', k);
|
|
|
|
stack.push(stack.splice(stack.length - k, 1)[0]);
|
|
}
|
|
|
|
// FDEF[] Function DEFinition
|
|
// 0x2C
|
|
function FDEF(state) {
|
|
if (state.env !== 'fpgm') throw new Error('FDEF not allowed here');
|
|
const stack = state.stack;
|
|
const prog = state.prog;
|
|
let ip = state.ip;
|
|
|
|
const fn = stack.pop();
|
|
const ipBegin = ip;
|
|
|
|
if (exports.DEBUG) console.log(state.step, 'FDEF[]', fn);
|
|
|
|
while (prog[++ip] !== 0x2D);
|
|
|
|
state.ip = ip;
|
|
state.funcs[fn] = prog.slice(ipBegin + 1, ip);
|
|
}
|
|
|
|
// MDAP[a] Move Direct Absolute Point
|
|
// 0x2E-0x2F
|
|
function MDAP(round, state) {
|
|
const pi = state.stack.pop();
|
|
const p = state.z0[pi];
|
|
const fv = state.fv;
|
|
const pv = state.pv;
|
|
|
|
if (exports.DEBUG) console.log(state.step, 'MDAP[' + round + ']', pi);
|
|
|
|
let d = pv.distance(p, HPZero);
|
|
|
|
if (round) d = state.round(d);
|
|
|
|
fv.setRelative(p, HPZero, d, pv);
|
|
fv.touch(p);
|
|
|
|
state.rp0 = state.rp1 = pi;
|
|
}
|
|
|
|
// IUP[a] Interpolate Untouched Points through the outline
|
|
// 0x30
|
|
function IUP(v, state) {
|
|
const z2 = state.z2;
|
|
const pLen = z2.length - 2;
|
|
let cp;
|
|
let pp;
|
|
let np;
|
|
|
|
if (exports.DEBUG) console.log(state.step, 'IUP[' + v.axis + ']');
|
|
|
|
for (let i = 0; i < pLen; i++) {
|
|
cp = z2[i]; // current point
|
|
|
|
// if this point has been touched go on
|
|
if (v.touched(cp)) continue;
|
|
|
|
pp = cp.prevTouched(v);
|
|
|
|
// no point on the contour has been touched?
|
|
if (pp === cp) continue;
|
|
|
|
np = cp.nextTouched(v);
|
|
|
|
if (pp === np) {
|
|
// only one point on the contour has been touched
|
|
// so simply moves the point like that
|
|
|
|
v.setRelative(cp, cp, v.distance(pp, pp, false, true), v, true);
|
|
}
|
|
|
|
v.interpolate(cp, pp, np, v);
|
|
}
|
|
}
|
|
|
|
// SHP[] SHift Point using reference point
|
|
// 0x32-0x33
|
|
function SHP(a, state) {
|
|
const stack = state.stack;
|
|
const rpi = a ? state.rp1 : state.rp2;
|
|
const rp = (a ? state.z0 : state.z1)[rpi];
|
|
const fv = state.fv;
|
|
const pv = state.pv;
|
|
let loop = state.loop;
|
|
const z2 = state.z2;
|
|
|
|
while (loop--)
|
|
{
|
|
const pi = stack.pop();
|
|
const p = z2[pi];
|
|
|
|
const d = pv.distance(rp, rp, false, true);
|
|
fv.setRelative(p, p, d, pv);
|
|
fv.touch(p);
|
|
|
|
if (exports.DEBUG) {
|
|
console.log(
|
|
state.step,
|
|
(state.loop > 1 ?
|
|
'loop ' + (state.loop - loop) + ': ' :
|
|
''
|
|
) +
|
|
'SHP[' + (a ? 'rp1' : 'rp2') + ']', pi
|
|
);
|
|
}
|
|
}
|
|
|
|
state.loop = 1;
|
|
}
|
|
|
|
// SHC[] SHift Contour using reference point
|
|
// 0x36-0x37
|
|
function SHC(a, state) {
|
|
const stack = state.stack;
|
|
const rpi = a ? state.rp1 : state.rp2;
|
|
const rp = (a ? state.z0 : state.z1)[rpi];
|
|
const fv = state.fv;
|
|
const pv = state.pv;
|
|
const ci = stack.pop();
|
|
const sp = state.z2[state.contours[ci]];
|
|
let p = sp;
|
|
|
|
if (exports.DEBUG) console.log(state.step, 'SHC[' + a + ']', ci);
|
|
|
|
const d = pv.distance(rp, rp, false, true);
|
|
|
|
do {
|
|
if (p !== rp) fv.setRelative(p, p, d, pv);
|
|
p = p.nextPointOnContour;
|
|
} while (p !== sp);
|
|
}
|
|
|
|
// SHZ[] SHift Zone using reference point
|
|
// 0x36-0x37
|
|
function SHZ(a, state) {
|
|
const stack = state.stack;
|
|
const rpi = a ? state.rp1 : state.rp2;
|
|
const rp = (a ? state.z0 : state.z1)[rpi];
|
|
const fv = state.fv;
|
|
const pv = state.pv;
|
|
|
|
const e = stack.pop();
|
|
|
|
if (exports.DEBUG) console.log(state.step, 'SHZ[' + a + ']', e);
|
|
|
|
let z;
|
|
switch (e) {
|
|
case 0 : z = state.tZone; break;
|
|
case 1 : z = state.gZone; break;
|
|
default : throw new Error('Invalid zone');
|
|
}
|
|
|
|
let p;
|
|
const d = pv.distance(rp, rp, false, true);
|
|
const pLen = z.length - 2;
|
|
for (let i = 0; i < pLen; i++)
|
|
{
|
|
p = z[i];
|
|
fv.setRelative(p, p, d, pv);
|
|
//if (p !== rp) fv.setRelative(p, p, d, pv);
|
|
}
|
|
}
|
|
|
|
// SHPIX[] SHift point by a PIXel amount
|
|
// 0x38
|
|
function SHPIX(state) {
|
|
const stack = state.stack;
|
|
let loop = state.loop;
|
|
const fv = state.fv;
|
|
const d = stack.pop() / 0x40;
|
|
const z2 = state.z2;
|
|
|
|
while (loop--) {
|
|
const pi = stack.pop();
|
|
const p = z2[pi];
|
|
|
|
if (exports.DEBUG) {
|
|
console.log(
|
|
state.step,
|
|
(state.loop > 1 ? 'loop ' + (state.loop - loop) + ': ' : '') +
|
|
'SHPIX[]', pi, d
|
|
);
|
|
}
|
|
|
|
fv.setRelative(p, p, d);
|
|
fv.touch(p);
|
|
}
|
|
|
|
state.loop = 1;
|
|
}
|
|
|
|
// IP[] Interpolate Point
|
|
// 0x39
|
|
function IP(state) {
|
|
const stack = state.stack;
|
|
const rp1i = state.rp1;
|
|
const rp2i = state.rp2;
|
|
let loop = state.loop;
|
|
const rp1 = state.z0[rp1i];
|
|
const rp2 = state.z1[rp2i];
|
|
const fv = state.fv;
|
|
const pv = state.dpv;
|
|
const z2 = state.z2;
|
|
|
|
while (loop--) {
|
|
const pi = stack.pop();
|
|
const p = z2[pi];
|
|
|
|
if (exports.DEBUG) {
|
|
console.log(
|
|
state.step,
|
|
(state.loop > 1 ? 'loop ' + (state.loop - loop) + ': ' : '') +
|
|
'IP[]', pi, rp1i, '<->', rp2i
|
|
);
|
|
}
|
|
|
|
fv.interpolate(p, rp1, rp2, pv);
|
|
|
|
fv.touch(p);
|
|
}
|
|
|
|
state.loop = 1;
|
|
}
|
|
|
|
// MSIRP[a] Move Stack Indirect Relative Point
|
|
// 0x3A-0x3B
|
|
function MSIRP(a, state) {
|
|
const stack = state.stack;
|
|
const d = stack.pop() / 64;
|
|
const pi = stack.pop();
|
|
const p = state.z1[pi];
|
|
const rp0 = state.z0[state.rp0];
|
|
const fv = state.fv;
|
|
const pv = state.pv;
|
|
|
|
fv.setRelative(p, rp0, d, pv);
|
|
fv.touch(p);
|
|
|
|
if (exports.DEBUG) console.log(state.step, 'MSIRP[' + a + ']', d, pi);
|
|
|
|
state.rp1 = state.rp0;
|
|
state.rp2 = pi;
|
|
if (a) state.rp0 = pi;
|
|
}
|
|
|
|
// ALIGNRP[] Align to reference point.
|
|
// 0x3C
|
|
function ALIGNRP(state) {
|
|
const stack = state.stack;
|
|
const rp0i = state.rp0;
|
|
const rp0 = state.z0[rp0i];
|
|
let loop = state.loop;
|
|
const fv = state.fv;
|
|
const pv = state.pv;
|
|
const z1 = state.z1;
|
|
|
|
while (loop--) {
|
|
const pi = stack.pop();
|
|
const p = z1[pi];
|
|
|
|
if (exports.DEBUG) {
|
|
console.log(
|
|
state.step,
|
|
(state.loop > 1 ? 'loop ' + (state.loop - loop) + ': ' : '') +
|
|
'ALIGNRP[]', pi
|
|
);
|
|
}
|
|
|
|
fv.setRelative(p, rp0, 0, pv);
|
|
fv.touch(p);
|
|
}
|
|
|
|
state.loop = 1;
|
|
}
|
|
|
|
// RTG[] Round To Double Grid
|
|
// 0x3D
|
|
function RTDG(state) {
|
|
if (exports.DEBUG) console.log(state.step, 'RTDG[]');
|
|
|
|
state.round = roundToDoubleGrid;
|
|
}
|
|
|
|
// MIAP[a] Move Indirect Absolute Point
|
|
// 0x3E-0x3F
|
|
function MIAP(round, state) {
|
|
const stack = state.stack;
|
|
const n = stack.pop();
|
|
const pi = stack.pop();
|
|
const p = state.z0[pi];
|
|
const fv = state.fv;
|
|
const pv = state.pv;
|
|
let cv = state.cvt[n];
|
|
|
|
if (exports.DEBUG) {
|
|
console.log(
|
|
state.step,
|
|
'MIAP[' + round + ']',
|
|
n, '(', cv, ')', pi
|
|
);
|
|
}
|
|
|
|
let d = pv.distance(p, HPZero);
|
|
|
|
if (round) {
|
|
if (Math.abs(d - cv) < state.cvCutIn) d = cv;
|
|
|
|
d = state.round(d);
|
|
}
|
|
|
|
fv.setRelative(p, HPZero, d, pv);
|
|
|
|
if (state.zp0 === 0) {
|
|
p.xo = p.x;
|
|
p.yo = p.y;
|
|
}
|
|
|
|
fv.touch(p);
|
|
|
|
state.rp0 = state.rp1 = pi;
|
|
}
|
|
|
|
// NPUSB[] PUSH N Bytes
|
|
// 0x40
|
|
function NPUSHB(state) {
|
|
const prog = state.prog;
|
|
let ip = state.ip;
|
|
const stack = state.stack;
|
|
|
|
const n = prog[++ip];
|
|
|
|
if (exports.DEBUG) console.log(state.step, 'NPUSHB[]', n);
|
|
|
|
for (let i = 0; i < n; i++) stack.push(prog[++ip]);
|
|
|
|
state.ip = ip;
|
|
}
|
|
|
|
// NPUSHW[] PUSH N Words
|
|
// 0x41
|
|
function NPUSHW(state) {
|
|
let ip = state.ip;
|
|
const prog = state.prog;
|
|
const stack = state.stack;
|
|
const n = prog[++ip];
|
|
|
|
if (exports.DEBUG) console.log(state.step, 'NPUSHW[]', n);
|
|
|
|
for (let i = 0; i < n; i++) {
|
|
let w = (prog[++ip] << 8) | prog[++ip];
|
|
if (w & 0x8000) w = -((w ^ 0xffff) + 1);
|
|
stack.push(w);
|
|
}
|
|
|
|
state.ip = ip;
|
|
}
|
|
|
|
// WS[] Write Store
|
|
// 0x42
|
|
function WS(state) {
|
|
const stack = state.stack;
|
|
let store = state.store;
|
|
|
|
if (!store) store = state.store = [];
|
|
|
|
const v = stack.pop();
|
|
const l = stack.pop();
|
|
|
|
if (exports.DEBUG) console.log(state.step, 'WS', v, l);
|
|
|
|
store[l] = v;
|
|
}
|
|
|
|
// RS[] Read Store
|
|
// 0x43
|
|
function RS(state) {
|
|
const stack = state.stack;
|
|
const store = state.store;
|
|
|
|
const l = stack.pop();
|
|
|
|
if (exports.DEBUG) console.log(state.step, 'RS', l);
|
|
|
|
const v = (store && store[l]) || 0;
|
|
|
|
stack.push(v);
|
|
}
|
|
|
|
// WCVTP[] Write Control Value Table in Pixel units
|
|
// 0x44
|
|
function WCVTP(state) {
|
|
const stack = state.stack;
|
|
|
|
const v = stack.pop();
|
|
const l = stack.pop();
|
|
|
|
if (exports.DEBUG) console.log(state.step, 'WCVTP', v, l);
|
|
|
|
state.cvt[l] = v / 0x40;
|
|
}
|
|
|
|
// RCVT[] Read Control Value Table entry
|
|
// 0x45
|
|
function RCVT(state) {
|
|
const stack = state.stack;
|
|
const cvte = stack.pop();
|
|
|
|
if (exports.DEBUG) console.log(state.step, 'RCVT', cvte);
|
|
|
|
stack.push(state.cvt[cvte] * 0x40);
|
|
}
|
|
|
|
// GC[] Get Coordinate projected onto the projection vector
|
|
// 0x46-0x47
|
|
function GC(a, state) {
|
|
const stack = state.stack;
|
|
const pi = stack.pop();
|
|
const p = state.z2[pi];
|
|
|
|
if (exports.DEBUG) console.log(state.step, 'GC[' + a + ']', pi);
|
|
|
|
stack.push(state.dpv.distance(p, HPZero, a, false) * 0x40);
|
|
}
|
|
|
|
// MD[a] Measure Distance
|
|
// 0x49-0x4A
|
|
function MD(a, state) {
|
|
const stack = state.stack;
|
|
const pi2 = stack.pop();
|
|
const pi1 = stack.pop();
|
|
const p2 = state.z1[pi2];
|
|
const p1 = state.z0[pi1];
|
|
const d = state.dpv.distance(p1, p2, a, a);
|
|
|
|
if (exports.DEBUG) console.log(state.step, 'MD[' + a + ']', pi2, pi1, '->', d);
|
|
|
|
state.stack.push(Math.round(d * 64));
|
|
}
|
|
|
|
// MPPEM[] Measure Pixels Per EM
|
|
// 0x4B
|
|
function MPPEM(state) {
|
|
if (exports.DEBUG) console.log(state.step, 'MPPEM[]');
|
|
state.stack.push(state.ppem);
|
|
}
|
|
|
|
// FLIPON[] set the auto FLIP Boolean to ON
|
|
// 0x4D
|
|
function FLIPON(state) {
|
|
if (exports.DEBUG) console.log(state.step, 'FLIPON[]');
|
|
state.autoFlip = true;
|
|
}
|
|
|
|
// LT[] Less Than
|
|
// 0x50
|
|
function LT(state) {
|
|
const stack = state.stack;
|
|
const e2 = stack.pop();
|
|
const e1 = stack.pop();
|
|
|
|
if (exports.DEBUG) console.log(state.step, 'LT[]', e2, e1);
|
|
|
|
stack.push(e1 < e2 ? 1 : 0);
|
|
}
|
|
|
|
// LTEQ[] Less Than or EQual
|
|
// 0x53
|
|
function LTEQ(state) {
|
|
const stack = state.stack;
|
|
const e2 = stack.pop();
|
|
const e1 = stack.pop();
|
|
|
|
if (exports.DEBUG) console.log(state.step, 'LTEQ[]', e2, e1);
|
|
|
|
stack.push(e1 <= e2 ? 1 : 0);
|
|
}
|
|
|
|
// GTEQ[] Greater Than
|
|
// 0x52
|
|
function GT(state) {
|
|
const stack = state.stack;
|
|
const e2 = stack.pop();
|
|
const e1 = stack.pop();
|
|
|
|
if (exports.DEBUG) console.log(state.step, 'GT[]', e2, e1);
|
|
|
|
stack.push(e1 > e2 ? 1 : 0);
|
|
}
|
|
|
|
// GTEQ[] Greater Than or EQual
|
|
// 0x53
|
|
function GTEQ(state) {
|
|
const stack = state.stack;
|
|
const e2 = stack.pop();
|
|
const e1 = stack.pop();
|
|
|
|
if (exports.DEBUG) console.log(state.step, 'GTEQ[]', e2, e1);
|
|
|
|
stack.push(e1 >= e2 ? 1 : 0);
|
|
}
|
|
|
|
// EQ[] EQual
|
|
// 0x54
|
|
function EQ(state) {
|
|
const stack = state.stack;
|
|
const e2 = stack.pop();
|
|
const e1 = stack.pop();
|
|
|
|
if (exports.DEBUG) console.log(state.step, 'EQ[]', e2, e1);
|
|
|
|
stack.push(e2 === e1 ? 1 : 0);
|
|
}
|
|
|
|
// NEQ[] Not EQual
|
|
// 0x55
|
|
function NEQ(state) {
|
|
const stack = state.stack;
|
|
const e2 = stack.pop();
|
|
const e1 = stack.pop();
|
|
|
|
if (exports.DEBUG) console.log(state.step, 'NEQ[]', e2, e1);
|
|
|
|
stack.push(e2 !== e1 ? 1 : 0);
|
|
}
|
|
|
|
// ODD[] ODD
|
|
// 0x56
|
|
function ODD(state) {
|
|
const stack = state.stack;
|
|
const n = stack.pop();
|
|
|
|
if (exports.DEBUG) console.log(state.step, 'ODD[]', n);
|
|
|
|
stack.push(Math.trunc(n) % 2 ? 1 : 0);
|
|
}
|
|
|
|
// EVEN[] EVEN
|
|
// 0x57
|
|
function EVEN(state) {
|
|
const stack = state.stack;
|
|
const n = stack.pop();
|
|
|
|
if (exports.DEBUG) console.log(state.step, 'EVEN[]', n);
|
|
|
|
stack.push(Math.trunc(n) % 2 ? 0 : 1);
|
|
}
|
|
|
|
// IF[] IF test
|
|
// 0x58
|
|
function IF(state) {
|
|
let test = state.stack.pop();
|
|
let ins;
|
|
|
|
if (exports.DEBUG) console.log(state.step, 'IF[]', test);
|
|
|
|
// if test is true it just continues
|
|
// if not the ip is skipped until matching ELSE or EIF
|
|
if (!test) {
|
|
skip(state, true);
|
|
|
|
if (exports.DEBUG) console.log(state.step, ins === 0x1B ? 'ELSE[]' : 'EIF[]');
|
|
}
|
|
}
|
|
|
|
// EIF[] End IF
|
|
// 0x59
|
|
function EIF(state) {
|
|
// this can be reached normally when
|
|
// executing an else branch.
|
|
// -> just ignore it
|
|
|
|
if (exports.DEBUG) console.log(state.step, 'EIF[]');
|
|
}
|
|
|
|
// AND[] logical AND
|
|
// 0x5A
|
|
function AND(state) {
|
|
const stack = state.stack;
|
|
const e2 = stack.pop();
|
|
const e1 = stack.pop();
|
|
|
|
if (exports.DEBUG) console.log(state.step, 'AND[]', e2, e1);
|
|
|
|
stack.push(e2 && e1 ? 1 : 0);
|
|
}
|
|
|
|
// OR[] logical OR
|
|
// 0x5B
|
|
function OR(state) {
|
|
const stack = state.stack;
|
|
const e2 = stack.pop();
|
|
const e1 = stack.pop();
|
|
|
|
if (exports.DEBUG) console.log(state.step, 'OR[]', e2, e1);
|
|
|
|
stack.push(e2 || e1 ? 1 : 0);
|
|
}
|
|
|
|
// NOT[] logical NOT
|
|
// 0x5C
|
|
function NOT(state) {
|
|
const stack = state.stack;
|
|
const e = stack.pop();
|
|
|
|
if (exports.DEBUG) console.log(state.step, 'NOT[]', e);
|
|
|
|
stack.push(e ? 0 : 1);
|
|
}
|
|
|
|
// DELTAP1[] DELTA exception P1
|
|
// DELTAP2[] DELTA exception P2
|
|
// DELTAP3[] DELTA exception P3
|
|
// 0x5D, 0x71, 0x72
|
|
function DELTAP123(b, state) {
|
|
const stack = state.stack;
|
|
const n = stack.pop();
|
|
const fv = state.fv;
|
|
const pv = state.pv;
|
|
const ppem = state.ppem;
|
|
const base = state.deltaBase + (b - 1) * 16;
|
|
const ds = state.deltaShift;
|
|
const z0 = state.z0;
|
|
|
|
if (exports.DEBUG) console.log(state.step, 'DELTAP[' + b + ']', n, stack);
|
|
|
|
for (let i = 0; i < n; i++) {
|
|
const pi = stack.pop();
|
|
const arg = stack.pop();
|
|
const appem = base + ((arg & 0xF0) >> 4);
|
|
if (appem !== ppem) continue;
|
|
|
|
let mag = (arg & 0x0F) - 8;
|
|
if (mag >= 0) mag++;
|
|
if (exports.DEBUG) console.log(state.step, 'DELTAPFIX', pi, 'by', mag * ds);
|
|
|
|
const p = z0[pi];
|
|
fv.setRelative(p, p, mag * ds, pv);
|
|
}
|
|
}
|
|
|
|
// SDB[] Set Delta Base in the graphics state
|
|
// 0x5E
|
|
function SDB(state) {
|
|
const stack = state.stack;
|
|
const n = stack.pop();
|
|
|
|
if (exports.DEBUG) console.log(state.step, 'SDB[]', n);
|
|
|
|
state.deltaBase = n;
|
|
}
|
|
|
|
// SDS[] Set Delta Shift in the graphics state
|
|
// 0x5F
|
|
function SDS(state) {
|
|
const stack = state.stack;
|
|
const n = stack.pop();
|
|
|
|
if (exports.DEBUG) console.log(state.step, 'SDS[]', n);
|
|
|
|
state.deltaShift = Math.pow(0.5, n);
|
|
}
|
|
|
|
// ADD[] ADD
|
|
// 0x60
|
|
function ADD(state) {
|
|
const stack = state.stack;
|
|
const n2 = stack.pop();
|
|
const n1 = stack.pop();
|
|
|
|
if (exports.DEBUG) console.log(state.step, 'ADD[]', n2, n1);
|
|
|
|
stack.push(n1 + n2);
|
|
}
|
|
|
|
// SUB[] SUB
|
|
// 0x61
|
|
function SUB(state) {
|
|
const stack = state.stack;
|
|
const n2 = stack.pop();
|
|
const n1 = stack.pop();
|
|
|
|
if (exports.DEBUG) console.log(state.step, 'SUB[]', n2, n1);
|
|
|
|
stack.push(n1 - n2);
|
|
}
|
|
|
|
// DIV[] DIV
|
|
// 0x62
|
|
function DIV(state) {
|
|
const stack = state.stack;
|
|
const n2 = stack.pop();
|
|
const n1 = stack.pop();
|
|
|
|
if (exports.DEBUG) console.log(state.step, 'DIV[]', n2, n1);
|
|
|
|
stack.push(n1 * 64 / n2);
|
|
}
|
|
|
|
// MUL[] MUL
|
|
// 0x63
|
|
function MUL(state) {
|
|
const stack = state.stack;
|
|
const n2 = stack.pop();
|
|
const n1 = stack.pop();
|
|
|
|
if (exports.DEBUG) console.log(state.step, 'MUL[]', n2, n1);
|
|
|
|
stack.push(n1 * n2 / 64);
|
|
}
|
|
|
|
// ABS[] ABSolute value
|
|
// 0x64
|
|
function ABS(state) {
|
|
const stack = state.stack;
|
|
const n = stack.pop();
|
|
|
|
if (exports.DEBUG) console.log(state.step, 'ABS[]', n);
|
|
|
|
stack.push(Math.abs(n));
|
|
}
|
|
|
|
// NEG[] NEGate
|
|
// 0x65
|
|
function NEG(state) {
|
|
const stack = state.stack;
|
|
let n = stack.pop();
|
|
|
|
if (exports.DEBUG) console.log(state.step, 'NEG[]', n);
|
|
|
|
stack.push(-n);
|
|
}
|
|
|
|
// FLOOR[] FLOOR
|
|
// 0x66
|
|
function FLOOR(state) {
|
|
const stack = state.stack;
|
|
const n = stack.pop();
|
|
|
|
if (exports.DEBUG) console.log(state.step, 'FLOOR[]', n);
|
|
|
|
stack.push(Math.floor(n / 0x40) * 0x40);
|
|
}
|
|
|
|
// CEILING[] CEILING
|
|
// 0x67
|
|
function CEILING(state) {
|
|
const stack = state.stack;
|
|
const n = stack.pop();
|
|
|
|
if (exports.DEBUG) console.log(state.step, 'CEILING[]', n);
|
|
|
|
stack.push(Math.ceil(n / 0x40) * 0x40);
|
|
}
|
|
|
|
// ROUND[ab] ROUND value
|
|
// 0x68-0x6B
|
|
function ROUND(dt, state) {
|
|
const stack = state.stack;
|
|
const n = stack.pop();
|
|
|
|
if (exports.DEBUG) console.log(state.step, 'ROUND[]');
|
|
|
|
stack.push(state.round(n / 0x40) * 0x40);
|
|
}
|
|
|
|
// WCVTF[] Write Control Value Table in Funits
|
|
// 0x70
|
|
function WCVTF(state) {
|
|
const stack = state.stack;
|
|
const v = stack.pop();
|
|
const l = stack.pop();
|
|
|
|
if (exports.DEBUG) console.log(state.step, 'WCVTF[]', v, l);
|
|
|
|
state.cvt[l] = v * state.ppem / state.font.unitsPerEm;
|
|
}
|
|
|
|
// DELTAC1[] DELTA exception C1
|
|
// DELTAC2[] DELTA exception C2
|
|
// DELTAC3[] DELTA exception C3
|
|
// 0x73, 0x74, 0x75
|
|
function DELTAC123(b, state) {
|
|
const stack = state.stack;
|
|
const n = stack.pop();
|
|
const ppem = state.ppem;
|
|
const base = state.deltaBase + (b - 1) * 16;
|
|
const ds = state.deltaShift;
|
|
|
|
if (exports.DEBUG) console.log(state.step, 'DELTAC[' + b + ']', n, stack);
|
|
|
|
for (let i = 0; i < n; i++) {
|
|
const c = stack.pop();
|
|
const arg = stack.pop();
|
|
const appem = base + ((arg & 0xF0) >> 4);
|
|
if (appem !== ppem) continue;
|
|
|
|
let mag = (arg & 0x0F) - 8;
|
|
if (mag >= 0) mag++;
|
|
|
|
const delta = mag * ds;
|
|
|
|
if (exports.DEBUG) console.log(state.step, 'DELTACFIX', c, 'by', delta);
|
|
|
|
state.cvt[c] += delta;
|
|
}
|
|
}
|
|
|
|
// SROUND[] Super ROUND
|
|
// 0x76
|
|
function SROUND(state) {
|
|
let n = state.stack.pop();
|
|
|
|
if (exports.DEBUG) console.log(state.step, 'SROUND[]', n);
|
|
|
|
state.round = roundSuper;
|
|
|
|
let period;
|
|
|
|
switch (n & 0xC0) {
|
|
case 0x00:
|
|
period = 0.5;
|
|
break;
|
|
case 0x40:
|
|
period = 1;
|
|
break;
|
|
case 0x80:
|
|
period = 2;
|
|
break;
|
|
default:
|
|
throw new Error('invalid SROUND value');
|
|
}
|
|
|
|
state.srPeriod = period;
|
|
|
|
switch (n & 0x30) {
|
|
case 0x00:
|
|
state.srPhase = 0;
|
|
break;
|
|
case 0x10:
|
|
state.srPhase = 0.25 * period;
|
|
break;
|
|
case 0x20:
|
|
state.srPhase = 0.5 * period;
|
|
break;
|
|
case 0x30:
|
|
state.srPhase = 0.75 * period;
|
|
break;
|
|
default: throw new Error('invalid SROUND value');
|
|
}
|
|
|
|
n &= 0x0F;
|
|
|
|
if (n === 0) state.srThreshold = 0;
|
|
else state.srThreshold = (n / 8 - 0.5) * period;
|
|
}
|
|
|
|
// S45ROUND[] Super ROUND 45 degrees
|
|
// 0x77
|
|
function S45ROUND(state) {
|
|
let n = state.stack.pop();
|
|
|
|
if (exports.DEBUG) console.log(state.step, 'S45ROUND[]', n);
|
|
|
|
state.round = roundSuper;
|
|
|
|
let period;
|
|
|
|
switch (n & 0xC0) {
|
|
case 0x00:
|
|
period = Math.sqrt(2) / 2;
|
|
break;
|
|
case 0x40:
|
|
period = Math.sqrt(2);
|
|
break;
|
|
case 0x80:
|
|
period = 2 * Math.sqrt(2);
|
|
break;
|
|
default:
|
|
throw new Error('invalid S45ROUND value');
|
|
}
|
|
|
|
state.srPeriod = period;
|
|
|
|
switch (n & 0x30) {
|
|
case 0x00:
|
|
state.srPhase = 0;
|
|
break;
|
|
case 0x10:
|
|
state.srPhase = 0.25 * period;
|
|
break;
|
|
case 0x20:
|
|
state.srPhase = 0.5 * period;
|
|
break;
|
|
case 0x30:
|
|
state.srPhase = 0.75 * period;
|
|
break;
|
|
default:
|
|
throw new Error('invalid S45ROUND value');
|
|
}
|
|
|
|
n &= 0x0F;
|
|
|
|
if (n === 0) state.srThreshold = 0;
|
|
else state.srThreshold = (n / 8 - 0.5) * period;
|
|
}
|
|
|
|
// ROFF[] Round Off
|
|
// 0x7A
|
|
function ROFF(state) {
|
|
if (exports.DEBUG) console.log(state.step, 'ROFF[]');
|
|
|
|
state.round = roundOff;
|
|
}
|
|
|
|
// RUTG[] Round Up To Grid
|
|
// 0x7C
|
|
function RUTG(state) {
|
|
if (exports.DEBUG) console.log(state.step, 'RUTG[]');
|
|
|
|
state.round = roundUpToGrid;
|
|
}
|
|
|
|
// RDTG[] Round Down To Grid
|
|
// 0x7D
|
|
function RDTG(state) {
|
|
if (exports.DEBUG) console.log(state.step, 'RDTG[]');
|
|
|
|
state.round = roundDownToGrid;
|
|
}
|
|
|
|
// SCANCTRL[] SCAN conversion ConTRoL
|
|
// 0x85
|
|
function SCANCTRL(state) {
|
|
const n = state.stack.pop();
|
|
|
|
// ignored by opentype.js
|
|
|
|
if (exports.DEBUG) console.log(state.step, 'SCANCTRL[]', n);
|
|
}
|
|
|
|
// SDPVTL[a] Set Dual Projection Vector To Line
|
|
// 0x86-0x87
|
|
function SDPVTL(a, state) {
|
|
const stack = state.stack;
|
|
const p2i = stack.pop();
|
|
const p1i = stack.pop();
|
|
const p2 = state.z2[p2i];
|
|
const p1 = state.z1[p1i];
|
|
|
|
if (exports.DEBUG) console.log(state.step, 'SDPVTL[' + a + ']', p2i, p1i);
|
|
|
|
let dx;
|
|
let dy;
|
|
|
|
if (!a) {
|
|
dx = p1.x - p2.x;
|
|
dy = p1.y - p2.y;
|
|
} else {
|
|
dx = p2.y - p1.y;
|
|
dy = p1.x - p2.x;
|
|
}
|
|
|
|
state.dpv = getUnitVector(dx, dy);
|
|
}
|
|
|
|
// GETINFO[] GET INFOrmation
|
|
// 0x88
|
|
function GETINFO(state) {
|
|
const stack = state.stack;
|
|
const sel = stack.pop();
|
|
let r = 0;
|
|
|
|
if (exports.DEBUG) console.log(state.step, 'GETINFO[]', sel);
|
|
|
|
// v35 as in no subpixel hinting
|
|
if (sel & 0x01) r = 35;
|
|
|
|
// TODO rotation and stretch currently not supported
|
|
// and thus those GETINFO are always 0.
|
|
|
|
// opentype.js is always gray scaling
|
|
if (sel & 0x20) r |= 0x1000;
|
|
|
|
stack.push(r);
|
|
}
|
|
|
|
// ROLL[] ROLL the top three stack elements
|
|
// 0x8A
|
|
function ROLL(state) {
|
|
const stack = state.stack;
|
|
const a = stack.pop();
|
|
const b = stack.pop();
|
|
const c = stack.pop();
|
|
|
|
if (exports.DEBUG) console.log(state.step, 'ROLL[]');
|
|
|
|
stack.push(b);
|
|
stack.push(a);
|
|
stack.push(c);
|
|
}
|
|
|
|
// MAX[] MAXimum of top two stack elements
|
|
// 0x8B
|
|
function MAX(state) {
|
|
const stack = state.stack;
|
|
const e2 = stack.pop();
|
|
const e1 = stack.pop();
|
|
|
|
if (exports.DEBUG) console.log(state.step, 'MAX[]', e2, e1);
|
|
|
|
stack.push(Math.max(e1, e2));
|
|
}
|
|
|
|
// MIN[] MINimum of top two stack elements
|
|
// 0x8C
|
|
function MIN(state) {
|
|
const stack = state.stack;
|
|
const e2 = stack.pop();
|
|
const e1 = stack.pop();
|
|
|
|
if (exports.DEBUG) console.log(state.step, 'MIN[]', e2, e1);
|
|
|
|
stack.push(Math.min(e1, e2));
|
|
}
|
|
|
|
// SCANTYPE[] SCANTYPE
|
|
// 0x8D
|
|
function SCANTYPE(state) {
|
|
const n = state.stack.pop();
|
|
// ignored by opentype.js
|
|
if (exports.DEBUG) console.log(state.step, 'SCANTYPE[]', n);
|
|
}
|
|
|
|
// INSTCTRL[] INSTCTRL
|
|
// 0x8D
|
|
function INSTCTRL(state) {
|
|
const s = state.stack.pop();
|
|
let v = state.stack.pop();
|
|
|
|
if (exports.DEBUG) console.log(state.step, 'INSTCTRL[]', s, v);
|
|
|
|
switch (s) {
|
|
case 1 : state.inhibitGridFit = !!v; return;
|
|
case 2 : state.ignoreCvt = !!v; return;
|
|
default: throw new Error('invalid INSTCTRL[] selector');
|
|
}
|
|
}
|
|
|
|
// PUSHB[abc] PUSH Bytes
|
|
// 0xB0-0xB7
|
|
function PUSHB(n, state) {
|
|
const stack = state.stack;
|
|
const prog = state.prog;
|
|
let ip = state.ip;
|
|
|
|
if (exports.DEBUG) console.log(state.step, 'PUSHB[' + n + ']');
|
|
|
|
for (let i = 0; i < n; i++) stack.push(prog[++ip]);
|
|
|
|
state.ip = ip;
|
|
}
|
|
|
|
// PUSHW[abc] PUSH Words
|
|
// 0xB8-0xBF
|
|
function PUSHW(n, state) {
|
|
let ip = state.ip;
|
|
const prog = state.prog;
|
|
const stack = state.stack;
|
|
|
|
if (exports.DEBUG) console.log(state.ip, 'PUSHW[' + n + ']');
|
|
|
|
for (let i = 0; i < n; i++) {
|
|
let w = (prog[++ip] << 8) | prog[++ip];
|
|
if (w & 0x8000) w = -((w ^ 0xffff) + 1);
|
|
stack.push(w);
|
|
}
|
|
|
|
state.ip = ip;
|
|
}
|
|
|
|
// MDRP[abcde] Move Direct Relative Point
|
|
// 0xD0-0xEF
|
|
// (if indirect is 0)
|
|
//
|
|
// and
|
|
//
|
|
// MIRP[abcde] Move Indirect Relative Point
|
|
// 0xE0-0xFF
|
|
// (if indirect is 1)
|
|
|
|
function MDRP_MIRP(indirect, setRp0, keepD, ro, dt, state) {
|
|
const stack = state.stack;
|
|
const cvte = indirect && stack.pop();
|
|
const pi = stack.pop();
|
|
const rp0i = state.rp0;
|
|
const rp = state.z0[rp0i];
|
|
const p = state.z1[pi];
|
|
|
|
const md = state.minDis;
|
|
const fv = state.fv;
|
|
const pv = state.dpv;
|
|
let od; // original distance
|
|
let d; // moving distance
|
|
let sign; // sign of distance
|
|
let cv;
|
|
|
|
d = od = pv.distance(p, rp, true, true);
|
|
sign = d >= 0 ? 1 : -1; // Math.sign would be 0 in case of 0
|
|
|
|
// TODO consider autoFlip
|
|
d = Math.abs(d);
|
|
|
|
if (indirect) {
|
|
cv = state.cvt[cvte];
|
|
|
|
if (ro && Math.abs(d - cv) < state.cvCutIn) d = cv;
|
|
}
|
|
|
|
if (keepD && d < md) d = md;
|
|
|
|
if (ro) d = state.round(d);
|
|
|
|
fv.setRelative(p, rp, sign * d, pv);
|
|
fv.touch(p);
|
|
|
|
if (exports.DEBUG) {
|
|
console.log(
|
|
state.step,
|
|
(indirect ? 'MIRP[' : 'MDRP[') +
|
|
(setRp0 ? 'M' : 'm') +
|
|
(keepD ? '>' : '_') +
|
|
(ro ? 'R' : '_') +
|
|
(dt === 0 ? 'Gr' : (dt === 1 ? 'Bl' : (dt === 2 ? 'Wh' : ''))) +
|
|
']',
|
|
indirect ?
|
|
cvte + '(' + state.cvt[cvte] + ',' + cv + ')' :
|
|
'',
|
|
pi,
|
|
'(d =', od, '->', sign * d, ')'
|
|
);
|
|
}
|
|
|
|
state.rp1 = state.rp0;
|
|
state.rp2 = pi;
|
|
if (setRp0) state.rp0 = pi;
|
|
}
|
|
|
|
/*
|
|
* The instruction table.
|
|
*/
|
|
instructionTable = [
|
|
/* 0x00 */ SVTCA.bind(undefined, yUnitVector),
|
|
/* 0x01 */ SVTCA.bind(undefined, xUnitVector),
|
|
/* 0x02 */ SPVTCA.bind(undefined, yUnitVector),
|
|
/* 0x03 */ SPVTCA.bind(undefined, xUnitVector),
|
|
/* 0x04 */ SFVTCA.bind(undefined, yUnitVector),
|
|
/* 0x05 */ SFVTCA.bind(undefined, xUnitVector),
|
|
/* 0x06 */ SPVTL.bind(undefined, 0),
|
|
/* 0x07 */ SPVTL.bind(undefined, 1),
|
|
/* 0x08 */ SFVTL.bind(undefined, 0),
|
|
/* 0x09 */ SFVTL.bind(undefined, 1),
|
|
/* 0x0A */ SPVFS,
|
|
/* 0x0B */ SFVFS,
|
|
/* 0x0C */ GPV,
|
|
/* 0x0D */ GFV,
|
|
/* 0x0E */ SFVTPV,
|
|
/* 0x0F */ ISECT,
|
|
/* 0x10 */ SRP0,
|
|
/* 0x11 */ SRP1,
|
|
/* 0x12 */ SRP2,
|
|
/* 0x13 */ SZP0,
|
|
/* 0x14 */ SZP1,
|
|
/* 0x15 */ SZP2,
|
|
/* 0x16 */ SZPS,
|
|
/* 0x17 */ SLOOP,
|
|
/* 0x18 */ RTG,
|
|
/* 0x19 */ RTHG,
|
|
/* 0x1A */ SMD,
|
|
/* 0x1B */ ELSE,
|
|
/* 0x1C */ JMPR,
|
|
/* 0x1D */ SCVTCI,
|
|
/* 0x1E */ undefined, // TODO SSWCI
|
|
/* 0x1F */ undefined, // TODO SSW
|
|
/* 0x20 */ DUP,
|
|
/* 0x21 */ POP,
|
|
/* 0x22 */ CLEAR,
|
|
/* 0x23 */ SWAP,
|
|
/* 0x24 */ DEPTH,
|
|
/* 0x25 */ CINDEX,
|
|
/* 0x26 */ MINDEX,
|
|
/* 0x27 */ undefined, // TODO ALIGNPTS
|
|
/* 0x28 */ undefined,
|
|
/* 0x29 */ undefined, // TODO UTP
|
|
/* 0x2A */ LOOPCALL,
|
|
/* 0x2B */ CALL,
|
|
/* 0x2C */ FDEF,
|
|
/* 0x2D */ undefined, // ENDF (eaten by FDEF)
|
|
/* 0x2E */ MDAP.bind(undefined, 0),
|
|
/* 0x2F */ MDAP.bind(undefined, 1),
|
|
/* 0x30 */ IUP.bind(undefined, yUnitVector),
|
|
/* 0x31 */ IUP.bind(undefined, xUnitVector),
|
|
/* 0x32 */ SHP.bind(undefined, 0),
|
|
/* 0x33 */ SHP.bind(undefined, 1),
|
|
/* 0x34 */ SHC.bind(undefined, 0),
|
|
/* 0x35 */ SHC.bind(undefined, 1),
|
|
/* 0x36 */ SHZ.bind(undefined, 0),
|
|
/* 0x37 */ SHZ.bind(undefined, 1),
|
|
/* 0x38 */ SHPIX,
|
|
/* 0x39 */ IP,
|
|
/* 0x3A */ MSIRP.bind(undefined, 0),
|
|
/* 0x3B */ MSIRP.bind(undefined, 1),
|
|
/* 0x3C */ ALIGNRP,
|
|
/* 0x3D */ RTDG,
|
|
/* 0x3E */ MIAP.bind(undefined, 0),
|
|
/* 0x3F */ MIAP.bind(undefined, 1),
|
|
/* 0x40 */ NPUSHB,
|
|
/* 0x41 */ NPUSHW,
|
|
/* 0x42 */ WS,
|
|
/* 0x43 */ RS,
|
|
/* 0x44 */ WCVTP,
|
|
/* 0x45 */ RCVT,
|
|
/* 0x46 */ GC.bind(undefined, 0),
|
|
/* 0x47 */ GC.bind(undefined, 1),
|
|
/* 0x48 */ undefined, // TODO SCFS
|
|
/* 0x49 */ MD.bind(undefined, 0),
|
|
/* 0x4A */ MD.bind(undefined, 1),
|
|
/* 0x4B */ MPPEM,
|
|
/* 0x4C */ undefined, // TODO MPS
|
|
/* 0x4D */ FLIPON,
|
|
/* 0x4E */ undefined, // TODO FLIPOFF
|
|
/* 0x4F */ undefined, // TODO DEBUG
|
|
/* 0x50 */ LT,
|
|
/* 0x51 */ LTEQ,
|
|
/* 0x52 */ GT,
|
|
/* 0x53 */ GTEQ,
|
|
/* 0x54 */ EQ,
|
|
/* 0x55 */ NEQ,
|
|
/* 0x56 */ ODD,
|
|
/* 0x57 */ EVEN,
|
|
/* 0x58 */ IF,
|
|
/* 0x59 */ EIF,
|
|
/* 0x5A */ AND,
|
|
/* 0x5B */ OR,
|
|
/* 0x5C */ NOT,
|
|
/* 0x5D */ DELTAP123.bind(undefined, 1),
|
|
/* 0x5E */ SDB,
|
|
/* 0x5F */ SDS,
|
|
/* 0x60 */ ADD,
|
|
/* 0x61 */ SUB,
|
|
/* 0x62 */ DIV,
|
|
/* 0x63 */ MUL,
|
|
/* 0x64 */ ABS,
|
|
/* 0x65 */ NEG,
|
|
/* 0x66 */ FLOOR,
|
|
/* 0x67 */ CEILING,
|
|
/* 0x68 */ ROUND.bind(undefined, 0),
|
|
/* 0x69 */ ROUND.bind(undefined, 1),
|
|
/* 0x6A */ ROUND.bind(undefined, 2),
|
|
/* 0x6B */ ROUND.bind(undefined, 3),
|
|
/* 0x6C */ undefined, // TODO NROUND[ab]
|
|
/* 0x6D */ undefined, // TODO NROUND[ab]
|
|
/* 0x6E */ undefined, // TODO NROUND[ab]
|
|
/* 0x6F */ undefined, // TODO NROUND[ab]
|
|
/* 0x70 */ WCVTF,
|
|
/* 0x71 */ DELTAP123.bind(undefined, 2),
|
|
/* 0x72 */ DELTAP123.bind(undefined, 3),
|
|
/* 0x73 */ DELTAC123.bind(undefined, 1),
|
|
/* 0x74 */ DELTAC123.bind(undefined, 2),
|
|
/* 0x75 */ DELTAC123.bind(undefined, 3),
|
|
/* 0x76 */ SROUND,
|
|
/* 0x77 */ S45ROUND,
|
|
/* 0x78 */ undefined, // TODO JROT[]
|
|
/* 0x79 */ undefined, // TODO JROF[]
|
|
/* 0x7A */ ROFF,
|
|
/* 0x7B */ undefined,
|
|
/* 0x7C */ RUTG,
|
|
/* 0x7D */ RDTG,
|
|
/* 0x7E */ POP, // actually SANGW, supposed to do only a pop though
|
|
/* 0x7F */ POP, // actually AA, supposed to do only a pop though
|
|
/* 0x80 */ undefined, // TODO FLIPPT
|
|
/* 0x81 */ undefined, // TODO FLIPRGON
|
|
/* 0x82 */ undefined, // TODO FLIPRGOFF
|
|
/* 0x83 */ undefined,
|
|
/* 0x84 */ undefined,
|
|
/* 0x85 */ SCANCTRL,
|
|
/* 0x86 */ SDPVTL.bind(undefined, 0),
|
|
/* 0x87 */ SDPVTL.bind(undefined, 1),
|
|
/* 0x88 */ GETINFO,
|
|
/* 0x89 */ undefined, // TODO IDEF
|
|
/* 0x8A */ ROLL,
|
|
/* 0x8B */ MAX,
|
|
/* 0x8C */ MIN,
|
|
/* 0x8D */ SCANTYPE,
|
|
/* 0x8E */ INSTCTRL,
|
|
/* 0x8F */ undefined,
|
|
/* 0x90 */ undefined,
|
|
/* 0x91 */ undefined,
|
|
/* 0x92 */ undefined,
|
|
/* 0x93 */ undefined,
|
|
/* 0x94 */ undefined,
|
|
/* 0x95 */ undefined,
|
|
/* 0x96 */ undefined,
|
|
/* 0x97 */ undefined,
|
|
/* 0x98 */ undefined,
|
|
/* 0x99 */ undefined,
|
|
/* 0x9A */ undefined,
|
|
/* 0x9B */ undefined,
|
|
/* 0x9C */ undefined,
|
|
/* 0x9D */ undefined,
|
|
/* 0x9E */ undefined,
|
|
/* 0x9F */ undefined,
|
|
/* 0xA0 */ undefined,
|
|
/* 0xA1 */ undefined,
|
|
/* 0xA2 */ undefined,
|
|
/* 0xA3 */ undefined,
|
|
/* 0xA4 */ undefined,
|
|
/* 0xA5 */ undefined,
|
|
/* 0xA6 */ undefined,
|
|
/* 0xA7 */ undefined,
|
|
/* 0xA8 */ undefined,
|
|
/* 0xA9 */ undefined,
|
|
/* 0xAA */ undefined,
|
|
/* 0xAB */ undefined,
|
|
/* 0xAC */ undefined,
|
|
/* 0xAD */ undefined,
|
|
/* 0xAE */ undefined,
|
|
/* 0xAF */ undefined,
|
|
/* 0xB0 */ PUSHB.bind(undefined, 1),
|
|
/* 0xB1 */ PUSHB.bind(undefined, 2),
|
|
/* 0xB2 */ PUSHB.bind(undefined, 3),
|
|
/* 0xB3 */ PUSHB.bind(undefined, 4),
|
|
/* 0xB4 */ PUSHB.bind(undefined, 5),
|
|
/* 0xB5 */ PUSHB.bind(undefined, 6),
|
|
/* 0xB6 */ PUSHB.bind(undefined, 7),
|
|
/* 0xB7 */ PUSHB.bind(undefined, 8),
|
|
/* 0xB8 */ PUSHW.bind(undefined, 1),
|
|
/* 0xB9 */ PUSHW.bind(undefined, 2),
|
|
/* 0xBA */ PUSHW.bind(undefined, 3),
|
|
/* 0xBB */ PUSHW.bind(undefined, 4),
|
|
/* 0xBC */ PUSHW.bind(undefined, 5),
|
|
/* 0xBD */ PUSHW.bind(undefined, 6),
|
|
/* 0xBE */ PUSHW.bind(undefined, 7),
|
|
/* 0xBF */ PUSHW.bind(undefined, 8),
|
|
/* 0xC0 */ MDRP_MIRP.bind(undefined, 0, 0, 0, 0, 0),
|
|
/* 0xC1 */ MDRP_MIRP.bind(undefined, 0, 0, 0, 0, 1),
|
|
/* 0xC2 */ MDRP_MIRP.bind(undefined, 0, 0, 0, 0, 2),
|
|
/* 0xC3 */ MDRP_MIRP.bind(undefined, 0, 0, 0, 0, 3),
|
|
/* 0xC4 */ MDRP_MIRP.bind(undefined, 0, 0, 0, 1, 0),
|
|
/* 0xC5 */ MDRP_MIRP.bind(undefined, 0, 0, 0, 1, 1),
|
|
/* 0xC6 */ MDRP_MIRP.bind(undefined, 0, 0, 0, 1, 2),
|
|
/* 0xC7 */ MDRP_MIRP.bind(undefined, 0, 0, 0, 1, 3),
|
|
/* 0xC8 */ MDRP_MIRP.bind(undefined, 0, 0, 1, 0, 0),
|
|
/* 0xC9 */ MDRP_MIRP.bind(undefined, 0, 0, 1, 0, 1),
|
|
/* 0xCA */ MDRP_MIRP.bind(undefined, 0, 0, 1, 0, 2),
|
|
/* 0xCB */ MDRP_MIRP.bind(undefined, 0, 0, 1, 0, 3),
|
|
/* 0xCC */ MDRP_MIRP.bind(undefined, 0, 0, 1, 1, 0),
|
|
/* 0xCD */ MDRP_MIRP.bind(undefined, 0, 0, 1, 1, 1),
|
|
/* 0xCE */ MDRP_MIRP.bind(undefined, 0, 0, 1, 1, 2),
|
|
/* 0xCF */ MDRP_MIRP.bind(undefined, 0, 0, 1, 1, 3),
|
|
/* 0xD0 */ MDRP_MIRP.bind(undefined, 0, 1, 0, 0, 0),
|
|
/* 0xD1 */ MDRP_MIRP.bind(undefined, 0, 1, 0, 0, 1),
|
|
/* 0xD2 */ MDRP_MIRP.bind(undefined, 0, 1, 0, 0, 2),
|
|
/* 0xD3 */ MDRP_MIRP.bind(undefined, 0, 1, 0, 0, 3),
|
|
/* 0xD4 */ MDRP_MIRP.bind(undefined, 0, 1, 0, 1, 0),
|
|
/* 0xD5 */ MDRP_MIRP.bind(undefined, 0, 1, 0, 1, 1),
|
|
/* 0xD6 */ MDRP_MIRP.bind(undefined, 0, 1, 0, 1, 2),
|
|
/* 0xD7 */ MDRP_MIRP.bind(undefined, 0, 1, 0, 1, 3),
|
|
/* 0xD8 */ MDRP_MIRP.bind(undefined, 0, 1, 1, 0, 0),
|
|
/* 0xD9 */ MDRP_MIRP.bind(undefined, 0, 1, 1, 0, 1),
|
|
/* 0xDA */ MDRP_MIRP.bind(undefined, 0, 1, 1, 0, 2),
|
|
/* 0xDB */ MDRP_MIRP.bind(undefined, 0, 1, 1, 0, 3),
|
|
/* 0xDC */ MDRP_MIRP.bind(undefined, 0, 1, 1, 1, 0),
|
|
/* 0xDD */ MDRP_MIRP.bind(undefined, 0, 1, 1, 1, 1),
|
|
/* 0xDE */ MDRP_MIRP.bind(undefined, 0, 1, 1, 1, 2),
|
|
/* 0xDF */ MDRP_MIRP.bind(undefined, 0, 1, 1, 1, 3),
|
|
/* 0xE0 */ MDRP_MIRP.bind(undefined, 1, 0, 0, 0, 0),
|
|
/* 0xE1 */ MDRP_MIRP.bind(undefined, 1, 0, 0, 0, 1),
|
|
/* 0xE2 */ MDRP_MIRP.bind(undefined, 1, 0, 0, 0, 2),
|
|
/* 0xE3 */ MDRP_MIRP.bind(undefined, 1, 0, 0, 0, 3),
|
|
/* 0xE4 */ MDRP_MIRP.bind(undefined, 1, 0, 0, 1, 0),
|
|
/* 0xE5 */ MDRP_MIRP.bind(undefined, 1, 0, 0, 1, 1),
|
|
/* 0xE6 */ MDRP_MIRP.bind(undefined, 1, 0, 0, 1, 2),
|
|
/* 0xE7 */ MDRP_MIRP.bind(undefined, 1, 0, 0, 1, 3),
|
|
/* 0xE8 */ MDRP_MIRP.bind(undefined, 1, 0, 1, 0, 0),
|
|
/* 0xE9 */ MDRP_MIRP.bind(undefined, 1, 0, 1, 0, 1),
|
|
/* 0xEA */ MDRP_MIRP.bind(undefined, 1, 0, 1, 0, 2),
|
|
/* 0xEB */ MDRP_MIRP.bind(undefined, 1, 0, 1, 0, 3),
|
|
/* 0xEC */ MDRP_MIRP.bind(undefined, 1, 0, 1, 1, 0),
|
|
/* 0xED */ MDRP_MIRP.bind(undefined, 1, 0, 1, 1, 1),
|
|
/* 0xEE */ MDRP_MIRP.bind(undefined, 1, 0, 1, 1, 2),
|
|
/* 0xEF */ MDRP_MIRP.bind(undefined, 1, 0, 1, 1, 3),
|
|
/* 0xF0 */ MDRP_MIRP.bind(undefined, 1, 1, 0, 0, 0),
|
|
/* 0xF1 */ MDRP_MIRP.bind(undefined, 1, 1, 0, 0, 1),
|
|
/* 0xF2 */ MDRP_MIRP.bind(undefined, 1, 1, 0, 0, 2),
|
|
/* 0xF3 */ MDRP_MIRP.bind(undefined, 1, 1, 0, 0, 3),
|
|
/* 0xF4 */ MDRP_MIRP.bind(undefined, 1, 1, 0, 1, 0),
|
|
/* 0xF5 */ MDRP_MIRP.bind(undefined, 1, 1, 0, 1, 1),
|
|
/* 0xF6 */ MDRP_MIRP.bind(undefined, 1, 1, 0, 1, 2),
|
|
/* 0xF7 */ MDRP_MIRP.bind(undefined, 1, 1, 0, 1, 3),
|
|
/* 0xF8 */ MDRP_MIRP.bind(undefined, 1, 1, 1, 0, 0),
|
|
/* 0xF9 */ MDRP_MIRP.bind(undefined, 1, 1, 1, 0, 1),
|
|
/* 0xFA */ MDRP_MIRP.bind(undefined, 1, 1, 1, 0, 2),
|
|
/* 0xFB */ MDRP_MIRP.bind(undefined, 1, 1, 1, 0, 3),
|
|
/* 0xFC */ MDRP_MIRP.bind(undefined, 1, 1, 1, 1, 0),
|
|
/* 0xFD */ MDRP_MIRP.bind(undefined, 1, 1, 1, 1, 1),
|
|
/* 0xFE */ MDRP_MIRP.bind(undefined, 1, 1, 1, 1, 2),
|
|
/* 0xFF */ MDRP_MIRP.bind(undefined, 1, 1, 1, 1, 3)
|
|
];
|
|
|
|
export default Hinting;
|
|
|
|
/*****************************
|
|
Mathematical Considerations
|
|
******************************
|
|
|
|
fv ... refers to freedom vector
|
|
pv ... refers to projection vector
|
|
rp ... refers to reference point
|
|
p ... refers to to point being operated on
|
|
d ... refers to distance
|
|
|
|
SETRELATIVE:
|
|
============
|
|
|
|
case freedom vector == x-axis:
|
|
------------------------------
|
|
|
|
(pv)
|
|
.-'
|
|
rpd .-'
|
|
.-*
|
|
d .-'90°'
|
|
.-' '
|
|
.-' '
|
|
*-' ' b
|
|
rp '
|
|
'
|
|
'
|
|
p *----------*-------------- (fv)
|
|
pm
|
|
|
|
rpdx = rpx + d * pv.x
|
|
rpdy = rpy + d * pv.y
|
|
|
|
equation of line b
|
|
|
|
y - rpdy = pvns * (x- rpdx)
|
|
|
|
y = p.y
|
|
|
|
x = rpdx + ( p.y - rpdy ) / pvns
|
|
|
|
|
|
case freedom vector == y-axis:
|
|
------------------------------
|
|
|
|
* pm
|
|
|\
|
|
| \
|
|
| \
|
|
| \
|
|
| \
|
|
| \
|
|
| \
|
|
| \
|
|
| \
|
|
| \ b
|
|
| \
|
|
| \
|
|
| \ .-' (pv)
|
|
| 90° \.-'
|
|
| .-'* rpd
|
|
| .-'
|
|
* *-' d
|
|
p rp
|
|
|
|
rpdx = rpx + d * pv.x
|
|
rpdy = rpy + d * pv.y
|
|
|
|
equation of line b:
|
|
pvns ... normal slope to pv
|
|
|
|
y - rpdy = pvns * (x - rpdx)
|
|
|
|
x = p.x
|
|
|
|
y = rpdy + pvns * (p.x - rpdx)
|
|
|
|
|
|
|
|
generic case:
|
|
-------------
|
|
|
|
|
|
.'(fv)
|
|
.'
|
|
.* pm
|
|
.' !
|
|
.' .
|
|
.' !
|
|
.' . b
|
|
.' !
|
|
* .
|
|
p !
|
|
90° . ... (pv)
|
|
...-*-'''
|
|
...---''' rpd
|
|
...---''' d
|
|
*--'''
|
|
rp
|
|
|
|
rpdx = rpx + d * pv.x
|
|
rpdy = rpy + d * pv.y
|
|
|
|
equation of line b:
|
|
pvns... normal slope to pv
|
|
|
|
y - rpdy = pvns * (x - rpdx)
|
|
|
|
equation of freedom vector line:
|
|
fvs ... slope of freedom vector (=fy/fx)
|
|
|
|
y - py = fvs * (x - px)
|
|
|
|
|
|
on pm both equations are true for same x/y
|
|
|
|
y - rpdy = pvns * (x - rpdx)
|
|
|
|
y - py = fvs * (x - px)
|
|
|
|
form to y and set equal:
|
|
|
|
pvns * (x - rpdx) + rpdy = fvs * (x - px) + py
|
|
|
|
expand:
|
|
|
|
pvns * x - pvns * rpdx + rpdy = fvs * x - fvs * px + py
|
|
|
|
switch:
|
|
|
|
fvs * x - fvs * px + py = pvns * x - pvns * rpdx + rpdy
|
|
|
|
solve for x:
|
|
|
|
fvs * x - pvns * x = fvs * px - pvns * rpdx - py + rpdy
|
|
|
|
|
|
|
|
fvs * px - pvns * rpdx + rpdy - py
|
|
x = -----------------------------------
|
|
fvs - pvns
|
|
|
|
and:
|
|
|
|
y = fvs * (x - px) + py
|
|
|
|
|
|
|
|
INTERPOLATE:
|
|
============
|
|
|
|
Examples of point interpolation.
|
|
|
|
The weight of the movement of the reference point gets bigger
|
|
the further the other reference point is away, thus the safest
|
|
option (that is avoiding 0/0 divisions) is to weight the
|
|
original distance of the other point by the sum of both distances.
|
|
|
|
If the sum of both distances is 0, then move the point by the
|
|
arithmetic average of the movement of both reference points.
|
|
|
|
|
|
|
|
|
|
(+6)
|
|
rp1o *---->*rp1
|
|
. . (+12)
|
|
. . rp2o *---------->* rp2
|
|
. . . .
|
|
. . . .
|
|
. 10 20 . .
|
|
|.........|...................| .
|
|
. . .
|
|
. . (+8) .
|
|
po *------>*p .
|
|
. . .
|
|
. 12 . 24 .
|
|
|...........|.......................|
|
|
36
|
|
|
|
|
|
-------
|
|
|
|
|
|
|
|
(+10)
|
|
rp1o *-------->*rp1
|
|
. . (-10)
|
|
. . rp2 *<---------* rpo2
|
|
. . . .
|
|
. . . .
|
|
. 10 . 30 . .
|
|
|.........|.............................|
|
|
. .
|
|
. (+5) .
|
|
po *--->* p .
|
|
. . .
|
|
. . 20 .
|
|
|....|..............|
|
|
5 15
|
|
|
|
|
|
-------
|
|
|
|
|
|
(+10)
|
|
rp1o *-------->*rp1
|
|
. .
|
|
. .
|
|
rp2o *-------->*rp2
|
|
|
|
|
|
(+10)
|
|
po *-------->* p
|
|
|
|
-------
|
|
|
|
|
|
(+10)
|
|
rp1o *-------->*rp1
|
|
. .
|
|
. .(+30)
|
|
rp2o *---------------------------->*rp2
|
|
|
|
|
|
(+25)
|
|
po *----------------------->* p
|
|
|
|
|
|
|
|
vim: set ts=4 sw=4 expandtab:
|
|
*****/
|