芝麻web文件管理V1.00
编辑当前文件:/home/freeclou/app.optimyar.com/backend/node_modules/opentype.js/src/hintingtt.js
/* 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'; 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; // 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 (sign > 0 && v < 0) return phase; if (sign < 0 && v > 0) return -phase; 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 by 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; 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]; 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]; // TODO cvtcutin should be considered here if (round) cv = state.round(cv); if (exports.DEBUG) { console.log( state.step, 'MIAP[' + round + ']', n, '(', cv, ')', pi ); } fv.setRelative(p, HPZero, cv, 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('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: *****/