Build NapCat Artifacts / Windows Loader Test (push) Canceled after 0s
Build NapCat Artifacts / Type Check & Test (push) Canceled after 0s
Build NapCat Artifacts / Build NapCat.Framework (push) Canceled after 0s
Build NapCat Artifacts / Build NapCat.Shell (push) Canceled after 0s
490 lines
16 KiB
Plaintext
490 lines
16 KiB
Plaintext
/**
|
|
* @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;
|
|
}
|