|
|
import Glyph from './Glyph';
|
|
|
import Path from './Path';
|
|
|
|
|
|
/**
|
|
|
* Represents an OpenType PostScript glyph, in the Compact Font Format.
|
|
|
*/
|
|
|
export default class CFFGlyph extends Glyph {
|
|
|
_getName() {
|
|
|
if (this._font.CFF2) {
|
|
|
return super._getName();
|
|
|
}
|
|
|
|
|
|
return this._font['CFF '].getGlyphName(this.id);
|
|
|
}
|
|
|
|
|
|
bias(s) {
|
|
|
if (s.length < 1240) {
|
|
|
return 107;
|
|
|
} else if (s.length < 33900) {
|
|
|
return 1131;
|
|
|
} else {
|
|
|
return 32768;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
_getPath() {
|
|
|
let { stream } = this._font;
|
|
|
let { pos } = stream;
|
|
|
|
|
|
let cff = this._font.CFF2 || this._font['CFF '];
|
|
|
let str = cff.topDict.CharStrings[this.id];
|
|
|
let end = str.offset + str.length;
|
|
|
stream.pos = str.offset;
|
|
|
|
|
|
let path = new Path;
|
|
|
let stack = [];
|
|
|
let trans = [];
|
|
|
|
|
|
let width = null;
|
|
|
let nStems = 0;
|
|
|
let x = 0, y = 0;
|
|
|
let usedGsubrs;
|
|
|
let usedSubrs;
|
|
|
let open = false;
|
|
|
|
|
|
this._usedGsubrs = usedGsubrs = {};
|
|
|
this._usedSubrs = usedSubrs = {};
|
|
|
|
|
|
let gsubrs = cff.globalSubrIndex || [];
|
|
|
let gsubrsBias = this.bias(gsubrs);
|
|
|
|
|
|
let privateDict = cff.privateDictForGlyph(this.id) || {};
|
|
|
let subrs = privateDict.Subrs || [];
|
|
|
let subrsBias = this.bias(subrs);
|
|
|
|
|
|
let vstore = cff.topDict.vstore && cff.topDict.vstore.itemVariationStore;
|
|
|
let vsindex = privateDict.vsindex;
|
|
|
let variationProcessor = this._font._variationProcessor;
|
|
|
|
|
|
function checkWidth() {
|
|
|
if (width == null) {
|
|
|
width = stack.shift() + privateDict.nominalWidthX;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
function parseStems() {
|
|
|
if (stack.length % 2 !== 0) {
|
|
|
checkWidth();
|
|
|
}
|
|
|
|
|
|
nStems += stack.length >> 1;
|
|
|
return stack.length = 0;
|
|
|
}
|
|
|
|
|
|
function moveTo(x, y) {
|
|
|
if (open) {
|
|
|
path.closePath();
|
|
|
}
|
|
|
|
|
|
path.moveTo(x, y);
|
|
|
open = true;
|
|
|
}
|
|
|
|
|
|
let parse = function() {
|
|
|
while (stream.pos < end) {
|
|
|
let op = stream.readUInt8();
|
|
|
if (op < 32) {
|
|
|
switch (op) {
|
|
|
case 1: // hstem
|
|
|
case 3: // vstem
|
|
|
case 18: // hstemhm
|
|
|
case 23: // vstemhm
|
|
|
parseStems();
|
|
|
break;
|
|
|
|
|
|
case 4: // vmoveto
|
|
|
if (stack.length > 1) {
|
|
|
checkWidth();
|
|
|
}
|
|
|
|
|
|
y += stack.shift();
|
|
|
moveTo(x, y);
|
|
|
break;
|
|
|
|
|
|
case 5: // rlineto
|
|
|
while (stack.length >= 2) {
|
|
|
x += stack.shift();
|
|
|
y += stack.shift();
|
|
|
path.lineTo(x, y);
|
|
|
}
|
|
|
break;
|
|
|
|
|
|
case 6: // hlineto
|
|
|
case 7: // vlineto
|
|
|
let phase = op === 6;
|
|
|
while (stack.length >= 1) {
|
|
|
if (phase) {
|
|
|
x += stack.shift();
|
|
|
} else {
|
|
|
y += stack.shift();
|
|
|
}
|
|
|
|
|
|
path.lineTo(x, y);
|
|
|
phase = !phase;
|
|
|
}
|
|
|
break;
|
|
|
|
|
|
case 8: // rrcurveto
|
|
|
while (stack.length > 0) {
|
|
|
var c1x = x + stack.shift();
|
|
|
var c1y = y + stack.shift();
|
|
|
var c2x = c1x + stack.shift();
|
|
|
var c2y = c1y + stack.shift();
|
|
|
x = c2x + stack.shift();
|
|
|
y = c2y + stack.shift();
|
|
|
path.bezierCurveTo(c1x, c1y, c2x, c2y, x, y);
|
|
|
}
|
|
|
break;
|
|
|
|
|
|
case 10: // callsubr
|
|
|
let index = stack.pop() + subrsBias;
|
|
|
let subr = subrs[index];
|
|
|
if (subr) {
|
|
|
usedSubrs[index] = true;
|
|
|
var p = stream.pos;
|
|
|
var e = end;
|
|
|
stream.pos = subr.offset;
|
|
|
end = subr.offset + subr.length;
|
|
|
parse();
|
|
|
stream.pos = p;
|
|
|
end = e;
|
|
|
}
|
|
|
break;
|
|
|
|
|
|
case 11: // return
|
|
|
if (cff.version >= 2) {
|
|
|
break;
|
|
|
}
|
|
|
return;
|
|
|
|
|
|
case 14: // endchar
|
|
|
if (cff.version >= 2) {
|
|
|
break;
|
|
|
}
|
|
|
|
|
|
if (stack.length > 0) {
|
|
|
checkWidth();
|
|
|
}
|
|
|
|
|
|
if (open) {
|
|
|
path.closePath();
|
|
|
open = false;
|
|
|
}
|
|
|
break;
|
|
|
|
|
|
case 15: { // vsindex
|
|
|
if (cff.version < 2) {
|
|
|
throw new Error('vsindex operator not supported in CFF v1');
|
|
|
}
|
|
|
|
|
|
vsindex = stack.pop();
|
|
|
break;
|
|
|
}
|
|
|
|
|
|
case 16: { // blend
|
|
|
if (cff.version < 2) {
|
|
|
throw new Error('blend operator not supported in CFF v1');
|
|
|
}
|
|
|
|
|
|
if (!variationProcessor) {
|
|
|
throw new Error('blend operator in non-variation font');
|
|
|
}
|
|
|
|
|
|
let blendVector = variationProcessor.getBlendVector(vstore, vsindex);
|
|
|
let numBlends = stack.pop();
|
|
|
let numOperands = numBlends * blendVector.length;
|
|
|
let delta = stack.length - numOperands;
|
|
|
let base = delta - numBlends;
|
|
|
|
|
|
for (let i = 0; i < numBlends; i++) {
|
|
|
let sum = stack[base + i];
|
|
|
for (let j = 0; j < blendVector.length; j++) {
|
|
|
sum += blendVector[j] * stack[delta++];
|
|
|
}
|
|
|
|
|
|
stack[base + i] = sum;
|
|
|
}
|
|
|
|
|
|
while (numOperands--) {
|
|
|
stack.pop();
|
|
|
}
|
|
|
|
|
|
break;
|
|
|
}
|
|
|
|
|
|
case 19: // hintmask
|
|
|
case 20: // cntrmask
|
|
|
parseStems();
|
|
|
stream.pos += (nStems + 7) >> 3;
|
|
|
break;
|
|
|
|
|
|
case 21: // rmoveto
|
|
|
if (stack.length > 2) {
|
|
|
checkWidth();
|
|
|
}
|
|
|
|
|
|
x += stack.shift();
|
|
|
y += stack.shift();
|
|
|
moveTo(x, y);
|
|
|
break;
|
|
|
|
|
|
case 22: // hmoveto
|
|
|
if (stack.length > 1) {
|
|
|
checkWidth();
|
|
|
}
|
|
|
|
|
|
x += stack.shift();
|
|
|
moveTo(x, y);
|
|
|
break;
|
|
|
|
|
|
case 24: // rcurveline
|
|
|
while (stack.length >= 8) {
|
|
|
var c1x = x + stack.shift();
|
|
|
var c1y = y + stack.shift();
|
|
|
var c2x = c1x + stack.shift();
|
|
|
var c2y = c1y + stack.shift();
|
|
|
x = c2x + stack.shift();
|
|
|
y = c2y + stack.shift();
|
|
|
path.bezierCurveTo(c1x, c1y, c2x, c2y, x, y);
|
|
|
}
|
|
|
|
|
|
x += stack.shift();
|
|
|
y += stack.shift();
|
|
|
path.lineTo(x, y);
|
|
|
break;
|
|
|
|
|
|
case 25: // rlinecurve
|
|
|
while (stack.length >= 8) {
|
|
|
x += stack.shift();
|
|
|
y += stack.shift();
|
|
|
path.lineTo(x, y);
|
|
|
}
|
|
|
|
|
|
var c1x = x + stack.shift();
|
|
|
var c1y = y + stack.shift();
|
|
|
var c2x = c1x + stack.shift();
|
|
|
var c2y = c1y + stack.shift();
|
|
|
x = c2x + stack.shift();
|
|
|
y = c2y + stack.shift();
|
|
|
path.bezierCurveTo(c1x, c1y, c2x, c2y, x, y);
|
|
|
break;
|
|
|
|
|
|
case 26: // vvcurveto
|
|
|
if (stack.length % 2) {
|
|
|
x += stack.shift();
|
|
|
}
|
|
|
|
|
|
while (stack.length >= 4) {
|
|
|
c1x = x;
|
|
|
c1y = y + stack.shift();
|
|
|
c2x = c1x + stack.shift();
|
|
|
c2y = c1y + stack.shift();
|
|
|
x = c2x;
|
|
|
y = c2y + stack.shift();
|
|
|
path.bezierCurveTo(c1x, c1y, c2x, c2y, x, y);
|
|
|
}
|
|
|
break;
|
|
|
|
|
|
case 27: // hhcurveto
|
|
|
if (stack.length % 2) {
|
|
|
y += stack.shift();
|
|
|
}
|
|
|
|
|
|
while (stack.length >= 4) {
|
|
|
c1x = x + stack.shift();
|
|
|
c1y = y;
|
|
|
c2x = c1x + stack.shift();
|
|
|
c2y = c1y + stack.shift();
|
|
|
x = c2x + stack.shift();
|
|
|
y = c2y;
|
|
|
path.bezierCurveTo(c1x, c1y, c2x, c2y, x, y);
|
|
|
}
|
|
|
break;
|
|
|
|
|
|
case 28: // shortint
|
|
|
stack.push(stream.readInt16BE());
|
|
|
break;
|
|
|
|
|
|
case 29: // callgsubr
|
|
|
index = stack.pop() + gsubrsBias;
|
|
|
subr = gsubrs[index];
|
|
|
if (subr) {
|
|
|
usedGsubrs[index] = true;
|
|
|
var p = stream.pos;
|
|
|
var e = end;
|
|
|
stream.pos = subr.offset;
|
|
|
end = subr.offset + subr.length;
|
|
|
parse();
|
|
|
stream.pos = p;
|
|
|
end = e;
|
|
|
}
|
|
|
break;
|
|
|
|
|
|
case 30: // vhcurveto
|
|
|
case 31: // hvcurveto
|
|
|
phase = op === 31;
|
|
|
while (stack.length >= 4) {
|
|
|
if (phase) {
|
|
|
c1x = x + stack.shift();
|
|
|
c1y = y;
|
|
|
c2x = c1x + stack.shift();
|
|
|
c2y = c1y + stack.shift();
|
|
|
y = c2y + stack.shift();
|
|
|
x = c2x + (stack.length === 1 ? stack.shift() : 0);
|
|
|
} else {
|
|
|
c1x = x;
|
|
|
c1y = y + stack.shift();
|
|
|
c2x = c1x + stack.shift();
|
|
|
c2y = c1y + stack.shift();
|
|
|
x = c2x + stack.shift();
|
|
|
y = c2y + (stack.length === 1 ? stack.shift() : 0);
|
|
|
}
|
|
|
|
|
|
path.bezierCurveTo(c1x, c1y, c2x, c2y, x, y);
|
|
|
phase = !phase;
|
|
|
}
|
|
|
break;
|
|
|
|
|
|
case 12:
|
|
|
op = stream.readUInt8();
|
|
|
switch (op) {
|
|
|
case 3: // and
|
|
|
let a = stack.pop();
|
|
|
let b = stack.pop();
|
|
|
stack.push(a && b ? 1 : 0);
|
|
|
break;
|
|
|
|
|
|
case 4: // or
|
|
|
a = stack.pop();
|
|
|
b = stack.pop();
|
|
|
stack.push(a || b ? 1 : 0);
|
|
|
break;
|
|
|
|
|
|
case 5: // not
|
|
|
a = stack.pop();
|
|
|
stack.push(a ? 0 : 1);
|
|
|
break;
|
|
|
|
|
|
case 9: // abs
|
|
|
a = stack.pop();
|
|
|
stack.push(Math.abs(a));
|
|
|
break;
|
|
|
|
|
|
case 10: // add
|
|
|
a = stack.pop();
|
|
|
b = stack.pop();
|
|
|
stack.push(a + b);
|
|
|
break;
|
|
|
|
|
|
case 11: // sub
|
|
|
a = stack.pop();
|
|
|
b = stack.pop();
|
|
|
stack.push(a - b);
|
|
|
break;
|
|
|
|
|
|
case 12: // div
|
|
|
a = stack.pop();
|
|
|
b = stack.pop();
|
|
|
stack.push(a / b);
|
|
|
break;
|
|
|
|
|
|
case 14: // neg
|
|
|
a = stack.pop();
|
|
|
stack.push(-a);
|
|
|
break;
|
|
|
|
|
|
case 15: // eq
|
|
|
a = stack.pop();
|
|
|
b = stack.pop();
|
|
|
stack.push(a === b ? 1 : 0);
|
|
|
break;
|
|
|
|
|
|
case 18: // drop
|
|
|
stack.pop();
|
|
|
break;
|
|
|
|
|
|
case 20: // put
|
|
|
let val = stack.pop();
|
|
|
let idx = stack.pop();
|
|
|
trans[idx] = val;
|
|
|
break;
|
|
|
|
|
|
case 21: // get
|
|
|
idx = stack.pop();
|
|
|
stack.push(trans[idx] || 0);
|
|
|
break;
|
|
|
|
|
|
case 22: // ifelse
|
|
|
let s1 = stack.pop();
|
|
|
let s2 = stack.pop();
|
|
|
let v1 = stack.pop();
|
|
|
let v2 = stack.pop();
|
|
|
stack.push(v1 <= v2 ? s1 : s2);
|
|
|
break;
|
|
|
|
|
|
case 23: // random
|
|
|
stack.push(Math.random());
|
|
|
break;
|
|
|
|
|
|
case 24: // mul
|
|
|
a = stack.pop();
|
|
|
b = stack.pop();
|
|
|
stack.push(a * b);
|
|
|
break;
|
|
|
|
|
|
case 26: // sqrt
|
|
|
a = stack.pop();
|
|
|
stack.push(Math.sqrt(a));
|
|
|
break;
|
|
|
|
|
|
case 27: // dup
|
|
|
a = stack.pop();
|
|
|
stack.push(a, a);
|
|
|
break;
|
|
|
|
|
|
case 28: // exch
|
|
|
a = stack.pop();
|
|
|
b = stack.pop();
|
|
|
stack.push(b, a);
|
|
|
break;
|
|
|
|
|
|
case 29: // index
|
|
|
idx = stack.pop();
|
|
|
if (idx < 0) {
|
|
|
idx = 0;
|
|
|
} else if (idx > stack.length - 1) {
|
|
|
idx = stack.length - 1;
|
|
|
}
|
|
|
|
|
|
stack.push(stack[idx]);
|
|
|
break;
|
|
|
|
|
|
case 30: // roll
|
|
|
let n = stack.pop();
|
|
|
let j = stack.pop();
|
|
|
|
|
|
if (j >= 0) {
|
|
|
while (j > 0) {
|
|
|
var t = stack[n - 1];
|
|
|
for (let i = n - 2; i >= 0; i--) {
|
|
|
stack[i + 1] = stack[i];
|
|
|
}
|
|
|
|
|
|
stack[0] = t;
|
|
|
j--;
|
|
|
}
|
|
|
} else {
|
|
|
while (j < 0) {
|
|
|
var t = stack[0];
|
|
|
for (let i = 0; i <= n; i++) {
|
|
|
stack[i] = stack[i + 1];
|
|
|
}
|
|
|
|
|
|
stack[n - 1] = t;
|
|
|
j++;
|
|
|
}
|
|
|
}
|
|
|
break;
|
|
|
|
|
|
case 34: // hflex
|
|
|
c1x = x + stack.shift();
|
|
|
c1y = y;
|
|
|
c2x = c1x + stack.shift();
|
|
|
c2y = c1y + stack.shift();
|
|
|
let c3x = c2x + stack.shift();
|
|
|
let c3y = c2y;
|
|
|
let c4x = c3x + stack.shift();
|
|
|
let c4y = c3y;
|
|
|
let c5x = c4x + stack.shift();
|
|
|
let c5y = c4y;
|
|
|
let c6x = c5x + stack.shift();
|
|
|
let c6y = c5y;
|
|
|
x = c6x;
|
|
|
y = c6y;
|
|
|
|
|
|
path.bezierCurveTo(c1x, c1y, c2x, c2y, c3x, c3y);
|
|
|
path.bezierCurveTo(c4x, c4y, c5x, c5y, c6x, c6y);
|
|
|
break;
|
|
|
|
|
|
case 35: // flex
|
|
|
let pts = [];
|
|
|
|
|
|
for (let i = 0; i <= 5; i++) {
|
|
|
x += stack.shift();
|
|
|
y += stack.shift();
|
|
|
pts.push(x, y);
|
|
|
}
|
|
|
|
|
|
path.bezierCurveTo(...pts.slice(0, 6));
|
|
|
path.bezierCurveTo(...pts.slice(6));
|
|
|
stack.shift(); // fd
|
|
|
break;
|
|
|
|
|
|
case 36: // hflex1
|
|
|
c1x = x + stack.shift();
|
|
|
c1y = y + stack.shift();
|
|
|
c2x = c1x + stack.shift();
|
|
|
c2y = c1y + stack.shift();
|
|
|
c3x = c2x + stack.shift();
|
|
|
c3y = c2y;
|
|
|
c4x = c3x + stack.shift();
|
|
|
c4y = c3y;
|
|
|
c5x = c4x + stack.shift();
|
|
|
c5y = c4y + stack.shift();
|
|
|
c6x = c5x + stack.shift();
|
|
|
c6y = c5y;
|
|
|
x = c6x;
|
|
|
y = c6y;
|
|
|
|
|
|
path.bezierCurveTo(c1x, c1y, c2x, c2y, c3x, c3y);
|
|
|
path.bezierCurveTo(c4x, c4y, c5x, c5y, c6x, c6y);
|
|
|
break;
|
|
|
|
|
|
case 37: // flex1
|
|
|
let startx = x;
|
|
|
let starty = y;
|
|
|
|
|
|
pts = [];
|
|
|
for (let i = 0; i <= 4; i++) {
|
|
|
x += stack.shift();
|
|
|
y += stack.shift();
|
|
|
pts.push(x, y);
|
|
|
}
|
|
|
|
|
|
if (Math.abs(x - startx) > Math.abs(y - starty)) { // horizontal
|
|
|
x += stack.shift();
|
|
|
y = starty;
|
|
|
} else {
|
|
|
x = startx;
|
|
|
y += stack.shift();
|
|
|
}
|
|
|
|
|
|
pts.push(x, y);
|
|
|
path.bezierCurveTo(...pts.slice(0, 6));
|
|
|
path.bezierCurveTo(...pts.slice(6));
|
|
|
break;
|
|
|
|
|
|
default:
|
|
|
throw new Error(`Unknown op: 12 ${op}`);
|
|
|
}
|
|
|
break;
|
|
|
|
|
|
default:
|
|
|
throw new Error(`Unknown op: ${op}`);
|
|
|
}
|
|
|
|
|
|
} else if (op < 247) {
|
|
|
stack.push(op - 139);
|
|
|
} else if (op < 251) {
|
|
|
var b1 = stream.readUInt8();
|
|
|
stack.push((op - 247) * 256 + b1 + 108);
|
|
|
} else if (op < 255) {
|
|
|
var b1 = stream.readUInt8();
|
|
|
stack.push(-(op - 251) * 256 - b1 - 108);
|
|
|
} else {
|
|
|
stack.push(stream.readInt32BE() / 65536);
|
|
|
}
|
|
|
}
|
|
|
};
|
|
|
|
|
|
parse();
|
|
|
|
|
|
if (open) {
|
|
|
path.closePath();
|
|
|
}
|
|
|
|
|
|
return path;
|
|
|
}
|
|
|
}
|
|
|
|