fonteditor-core
Version:
fonts (ttf, woff, woff2, eot, svg, otf) parse, write, transform, glyph adjust.
490 lines (452 loc) • 16.6 kB
JavaScript
/**
* @file 解析cff字形
* @author mengke01(kekee000@gmail.com)
*/
/**
* 解析cff字形,返回直线和三次bezier曲线点数组
*
* @param {Array} code 操作码
* @param {Object} font 相关联的font对象
* @param {number} index glyf索引
* @return {Object} glyf对象
*/
export default function parseCFFCharstring(code, font, index) {
let c1x;
let c1y;
let c2x;
let c2y;
const contours = [];
let contour = [];
const stack = [];
const glyfs = [];
let nStems = 0;
let haveWidth = false;
let width = font.defaultWidthX;
let open = false;
let x = 0;
let y = 0;
function lineTo(x, y) {
contour.push({
onCurve: true,
x,
y
});
}
function curveTo(c1x, c1y, c2x, c2y, x, y) {
contour.push({
x: c1x,
y: c1y
});
contour.push({
x: c2x,
y: c2y
});
contour.push({
onCurve: true,
x,
y
});
}
function newContour(x, y) {
if (open) {
contours.push(contour);
}
contour = [];
lineTo(x, y);
open = true;
}
function parseStems() {
// The number of stem operators on the stack is always even.
// If the value is uneven, that means a width is specified.
const hasWidthArg = stack.length % 2 !== 0;
if (hasWidthArg && !haveWidth) {
width = stack.shift() + font.nominalWidthX;
}
nStems += stack.length >> 1;
stack.length = 0;
haveWidth = true;
}
function parse(code) {
let b1;
let b2;
let b3;
let b4;
let codeIndex;
let subrCode;
let jpx;
let jpy;
let c3x;
let c3y;
let c4x;
let c4y;
let i = 0;
while (i < code.length) {
let v = code[i];
i += 1;
switch (v) {
case 1: // hstem
parseStems();
break;
case 3: // vstem
parseStems();
break;
case 4: // vmoveto
if (stack.length > 1 && !haveWidth) {
width = stack.shift() + font.nominalWidthX;
haveWidth = true;
}
y += stack.pop();
newContour(x, y);
break;
case 5: // rlineto
while (stack.length > 0) {
x += stack.shift();
y += stack.shift();
lineTo(x, y);
}
break;
case 6: // hlineto
while (stack.length > 0) {
x += stack.shift();
lineTo(x, y);
if (stack.length === 0) {
break;
}
y += stack.shift();
lineTo(x, y);
}
break;
case 7: // vlineto
while (stack.length > 0) {
y += stack.shift();
lineTo(x, y);
if (stack.length === 0) {
break;
}
x += stack.shift();
lineTo(x, y);
}
break;
case 8: // rrcurveto
while (stack.length > 0) {
c1x = x + stack.shift();
c1y = y + stack.shift();
c2x = c1x + stack.shift();
c2y = c1y + stack.shift();
x = c2x + stack.shift();
y = c2y + stack.shift();
curveTo(c1x, c1y, c2x, c2y, x, y);
}
break;
case 10: // callsubr
codeIndex = stack.pop() + font.subrsBias;
subrCode = font.subrs[codeIndex];
if (subrCode) {
parse(subrCode);
}
break;
case 11: // return
return;
case 12: // flex operators
v = code[i];
i += 1;
switch (v) {
case 35: // flex
// |- dx1 dy1 dx2 dy2 dx3 dy3 dx4 dy4 dx5 dy5 dx6 dy6 fd flex (12 35) |-
c1x = x + stack.shift(); // dx1
c1y = y + stack.shift(); // dy1
c2x = c1x + stack.shift(); // dx2
c2y = c1y + stack.shift(); // dy2
jpx = c2x + stack.shift(); // dx3
jpy = c2y + stack.shift(); // dy3
c3x = jpx + stack.shift(); // dx4
c3y = jpy + stack.shift(); // dy4
c4x = c3x + stack.shift(); // dx5
c4y = c3y + stack.shift(); // dy5
x = c4x + stack.shift(); // dx6
y = c4y + stack.shift(); // dy6
stack.shift(); // flex depth
curveTo(c1x, c1y, c2x, c2y, jpx, jpy);
curveTo(c3x, c3y, c4x, c4y, x, y);
break;
case 34: // hflex
// |- dx1 dx2 dy2 dx3 dx4 dx5 dx6 hflex (12 34) |-
c1x = x + stack.shift(); // dx1
c1y = y; // dy1
c2x = c1x + stack.shift(); // dx2
c2y = c1y + stack.shift(); // dy2
jpx = c2x + stack.shift(); // dx3
jpy = c2y; // dy3
c3x = jpx + stack.shift(); // dx4
c3y = c2y; // dy4
c4x = c3x + stack.shift(); // dx5
c4y = y; // dy5
x = c4x + stack.shift(); // dx6
curveTo(c1x, c1y, c2x, c2y, jpx, jpy);
curveTo(c3x, c3y, c4x, c4y, x, y);
break;
case 36: // hflex1
// |- dx1 dy1 dx2 dy2 dx3 dx4 dx5 dy5 dx6 hflex1 (12 36) |-
c1x = x + stack.shift(); // dx1
c1y = y + stack.shift(); // dy1
c2x = c1x + stack.shift(); // dx2
c2y = c1y + stack.shift(); // dy2
jpx = c2x + stack.shift(); // dx3
jpy = c2y; // dy3
c3x = jpx + stack.shift(); // dx4
c3y = c2y; // dy4
c4x = c3x + stack.shift(); // dx5
c4y = c3y + stack.shift(); // dy5
x = c4x + stack.shift(); // dx6
curveTo(c1x, c1y, c2x, c2y, jpx, jpy);
curveTo(c3x, c3y, c4x, c4y, x, y);
break;
case 37: // flex1
// |- dx1 dy1 dx2 dy2 dx3 dy3 dx4 dy4 dx5 dy5 d6 flex1 (12 37) |-
c1x = x + stack.shift(); // dx1
c1y = y + stack.shift(); // dy1
c2x = c1x + stack.shift(); // dx2
c2y = c1y + stack.shift(); // dy2
jpx = c2x + stack.shift(); // dx3
jpy = c2y + stack.shift(); // dy3
c3x = jpx + stack.shift(); // dx4
c3y = jpy + stack.shift(); // dy4
c4x = c3x + stack.shift(); // dx5
c4y = c3y + stack.shift(); // dy5
if (Math.abs(c4x - x) > Math.abs(c4y - y)) {
x = c4x + stack.shift();
}
else {
y = c4y + stack.shift();
}
curveTo(c1x, c1y, c2x, c2y, jpx, jpy);
curveTo(c3x, c3y, c4x, c4y, x, y);
break;
default:
console.warn('Glyph ' + index + ': unknown operator ' + (1200 + v));
stack.length = 0;
}
break;
case 14: // endchar
if (stack.length === 1 && !haveWidth) {
width = stack.shift() + font.nominalWidthX;
haveWidth = true;
}
else if (stack.length === 4) {
glyfs[1] = {
glyphIndex: font.charset.indexOf(font.encoding[stack.pop()]),
transform: {a: 1, b: 0, c: 0, d: 1, e: 0, f: 0}
};
glyfs[0] = {
glyphIndex: font.charset.indexOf(font.encoding[stack.pop()]),
transform: {a: 1, b: 0, c: 0, d: 1, e: 0, f: 0}
};
glyfs[1].transform.f = stack.pop();
glyfs[1].transform.e = stack.pop();
}
else if (stack.length === 5) {
if (!haveWidth) {
width = stack.shift() + font.nominalWidthX;
}
haveWidth = true;
glyfs[1] = {
glyphIndex: font.charset.indexOf(font.encoding[stack.pop()]),
transform: {a: 1, b: 0, c: 0, d: 1, e: 0, f: 0}
};
glyfs[0] = {
glyphIndex: font.charset.indexOf(font.encoding[stack.pop()]),
transform: {a: 1, b: 0, c: 0, d: 1, e: 0, f: 0}
};
glyfs[1].transform.f = stack.pop();
glyfs[1].transform.e = stack.pop();
}
if (open) {
contours.push(contour);
open = false;
}
break;
case 18: // hstemhm
parseStems();
break;
case 19: // hintmask
case 20: // cntrmask
parseStems();
i += (nStems + 7) >> 3;
break;
case 21: // rmoveto
if (stack.length > 2 && !haveWidth) {
width = stack.shift() + font.nominalWidthX;
haveWidth = true;
}
y += stack.pop();
x += stack.pop();
newContour(x, y);
break;
case 22: // hmoveto
if (stack.length > 1 && !haveWidth) {
width = stack.shift() + font.nominalWidthX;
haveWidth = true;
}
x += stack.pop();
newContour(x, y);
break;
case 23: // vstemhm
parseStems();
break;
case 24: // rcurveline
while (stack.length > 2) {
c1x = x + stack.shift();
c1y = y + stack.shift();
c2x = c1x + stack.shift();
c2y = c1y + stack.shift();
x = c2x + stack.shift();
y = c2y + stack.shift();
curveTo(c1x, c1y, c2x, c2y, x, y);
}
x += stack.shift();
y += stack.shift();
lineTo(x, y);
break;
case 25: // rlinecurve
while (stack.length > 6) {
x += stack.shift();
y += stack.shift();
lineTo(x, y);
}
c1x = x + stack.shift();
c1y = y + stack.shift();
c2x = c1x + stack.shift();
c2y = c1y + stack.shift();
x = c2x + stack.shift();
y = c2y + stack.shift();
curveTo(c1x, c1y, c2x, c2y, x, y);
break;
case 26: // vvcurveto
if (stack.length % 2) {
x += stack.shift();
}
while (stack.length > 0) {
c1x = x;
c1y = y + stack.shift();
c2x = c1x + stack.shift();
c2y = c1y + stack.shift();
x = c2x;
y = c2y + stack.shift();
curveTo(c1x, c1y, c2x, c2y, x, y);
}
break;
case 27: // hhcurveto
if (stack.length % 2) {
y += stack.shift();
}
while (stack.length > 0) {
c1x = x + stack.shift();
c1y = y;
c2x = c1x + stack.shift();
c2y = c1y + stack.shift();
x = c2x + stack.shift();
y = c2y;
curveTo(c1x, c1y, c2x, c2y, x, y);
}
break;
case 28: // shortint
b1 = code[i];
b2 = code[i + 1];
stack.push(((b1 << 24) | (b2 << 16)) >> 16);
i += 2;
break;
case 29: // callgsubr
codeIndex = stack.pop() + font.gsubrsBias;
subrCode = font.gsubrs[codeIndex];
if (subrCode) {
parse(subrCode);
}
break;
case 30: // vhcurveto
while (stack.length > 0) {
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);
curveTo(c1x, c1y, c2x, c2y, x, y);
if (stack.length === 0) {
break;
}
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);
curveTo(c1x, c1y, c2x, c2y, x, y);
}
break;
case 31: // hvcurveto
while (stack.length > 0) {
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);
curveTo(c1x, c1y, c2x, c2y, x, y);
if (stack.length === 0) {
break;
}
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);
curveTo(c1x, c1y, c2x, c2y, x, y);
}
break;
default:
if (v < 32) {
console.warn('Glyph ' + index + ': unknown operator ' + v);
}
else if (v < 247) {
stack.push(v - 139);
}
else if (v < 251) {
b1 = code[i];
i += 1;
stack.push((v - 247) * 256 + b1 + 108);
}
else if (v < 255) {
b1 = code[i];
i += 1;
stack.push(-(v - 251) * 256 - b1 - 108);
}
else {
b1 = code[i];
b2 = code[i + 1];
b3 = code[i + 2];
b4 = code[i + 3];
i += 4;
stack.push(((b1 << 24) | (b2 << 16) | (b3 << 8) | b4) / 65536);
}
}
}
}
parse(code);
const glyf = {
// 移除重复的起点和终点
contours: contours.map(contour => {
const last = contour.length - 1;
if (contour[0].x === contour[last].x && contour[0].y === contour[last].y) {
contour.splice(last, 1);
}
return contour;
}),
advanceWidth: width
};
if (glyfs.length) {
glyf.compound = true;
glyf.glyfs = glyfs;
}
return glyf;
}