mirror of
https://github.com/foliojs/pdfkit.git
synced 2025-12-08 20:15:54 +00:00
376 lines
7.7 KiB
JavaScript
376 lines
7.7 KiB
JavaScript
let cx, cy, px, py, sx, sy;
|
|
|
|
cx = cy = px = py = sx = sy = 0;
|
|
|
|
const parameters = {
|
|
A: 7,
|
|
a: 7,
|
|
C: 6,
|
|
c: 6,
|
|
H: 1,
|
|
h: 1,
|
|
L: 2,
|
|
l: 2,
|
|
M: 2,
|
|
m: 2,
|
|
Q: 4,
|
|
q: 4,
|
|
S: 4,
|
|
s: 4,
|
|
T: 2,
|
|
t: 2,
|
|
V: 1,
|
|
v: 1,
|
|
Z: 0,
|
|
z: 0
|
|
};
|
|
|
|
const parse = function(path) {
|
|
let cmd;
|
|
const ret = [];
|
|
let args = [];
|
|
let curArg = "";
|
|
let foundDecimal = false;
|
|
let params = 0;
|
|
|
|
for (let c of path) {
|
|
if (parameters[c] != null) {
|
|
params = parameters[c];
|
|
if (cmd) { // save existing command
|
|
if (curArg.length > 0) { args[args.length] = +curArg; }
|
|
ret[ret.length] = {cmd,args};
|
|
|
|
args = [];
|
|
curArg = "";
|
|
foundDecimal = false;
|
|
}
|
|
|
|
cmd = c;
|
|
|
|
} else if ([" ", ","].includes(c) || ((c === "-") && (curArg.length > 0) && (curArg[curArg.length - 1] !== 'e')) || ((c === ".") && foundDecimal)) {
|
|
if (curArg.length === 0) { continue; }
|
|
|
|
if (args.length === params) { // handle reused commands
|
|
ret[ret.length] = {cmd,args};
|
|
args = [+curArg];
|
|
|
|
// handle assumed commands
|
|
if (cmd === "M") { cmd = "L"; }
|
|
if (cmd === "m") { cmd = "l"; }
|
|
|
|
} else {
|
|
args[args.length] = +curArg;
|
|
}
|
|
|
|
foundDecimal = (c === ".");
|
|
|
|
// fix for negative numbers or repeated decimals with no delimeter between commands
|
|
curArg = ['-', '.'].includes(c) ? c : '';
|
|
|
|
} else {
|
|
curArg += c;
|
|
if (c === '.') { foundDecimal = true; }
|
|
}
|
|
}
|
|
|
|
// add the last command
|
|
if (curArg.length > 0) {
|
|
if (args.length === params) { // handle reused commands
|
|
ret[ret.length] = {cmd, args};
|
|
args = [+curArg];
|
|
|
|
// handle assumed commands
|
|
if (cmd === "M") { cmd = "L"; }
|
|
if (cmd === "m") { cmd = "l"; }
|
|
} else {
|
|
args[args.length] = +curArg;
|
|
}
|
|
}
|
|
|
|
ret[ret.length] = {cmd,args};
|
|
|
|
return ret;
|
|
};
|
|
|
|
const apply = function(commands, doc) {
|
|
// current point, control point, and subpath starting point
|
|
cx = cy = px = py = sx = sy = 0;
|
|
|
|
// run the commands
|
|
for (let i = 0; i < commands.length; i++) {
|
|
const c = commands[i];
|
|
if (typeof runners[c.cmd] === 'function') {
|
|
runners[c.cmd](doc, c.args);
|
|
}
|
|
}
|
|
};
|
|
|
|
const runners = {
|
|
M(doc, a) {
|
|
cx = a[0];
|
|
cy = a[1];
|
|
px = (py = null);
|
|
sx = cx;
|
|
sy = cy;
|
|
return doc.moveTo(cx, cy);
|
|
},
|
|
|
|
m(doc, a) {
|
|
cx += a[0];
|
|
cy += a[1];
|
|
px = (py = null);
|
|
sx = cx;
|
|
sy = cy;
|
|
return doc.moveTo(cx, cy);
|
|
},
|
|
|
|
C(doc, a) {
|
|
cx = a[4];
|
|
cy = a[5];
|
|
px = a[2];
|
|
py = a[3];
|
|
return doc.bezierCurveTo(...(a || []));
|
|
},
|
|
|
|
c(doc, a) {
|
|
doc.bezierCurveTo(a[0] + cx, a[1] + cy, a[2] + cx, a[3] + cy, a[4] + cx, a[5] + cy);
|
|
px = cx + a[2];
|
|
py = cy + a[3];
|
|
cx += a[4];
|
|
return cy += a[5];
|
|
},
|
|
|
|
S(doc, a) {
|
|
if (px === null) {
|
|
px = cx;
|
|
py = cy;
|
|
}
|
|
|
|
doc.bezierCurveTo(cx-(px-cx), cy-(py-cy), a[0], a[1], a[2], a[3]);
|
|
px = a[0];
|
|
py = a[1];
|
|
cx = a[2];
|
|
return cy = a[3];
|
|
},
|
|
|
|
s(doc, a) {
|
|
if (px === null) {
|
|
px = cx;
|
|
py = cy;
|
|
}
|
|
|
|
doc.bezierCurveTo(cx-(px-cx), cy-(py-cy), cx + a[0], cy + a[1], cx + a[2], cy + a[3]);
|
|
px = cx + a[0];
|
|
py = cy + a[1];
|
|
cx += a[2];
|
|
return cy += a[3];
|
|
},
|
|
|
|
Q(doc, a) {
|
|
px = a[0];
|
|
py = a[1];
|
|
cx = a[2];
|
|
cy = a[3];
|
|
return doc.quadraticCurveTo(a[0], a[1], cx, cy);
|
|
},
|
|
|
|
q(doc, a) {
|
|
doc.quadraticCurveTo(a[0] + cx, a[1] + cy, a[2] + cx, a[3] + cy);
|
|
px = cx + a[0];
|
|
py = cy + a[1];
|
|
cx += a[2];
|
|
return cy += a[3];
|
|
},
|
|
|
|
T(doc, a) {
|
|
if (px === null) {
|
|
px = cx;
|
|
py = cy;
|
|
} else {
|
|
px = cx-(px-cx);
|
|
py = cy-(py-cy);
|
|
}
|
|
|
|
doc.quadraticCurveTo(px, py, a[0], a[1]);
|
|
px = cx-(px-cx);
|
|
py = cy-(py-cy);
|
|
cx = a[0];
|
|
return cy = a[1];
|
|
},
|
|
|
|
t(doc, a) {
|
|
if (px === null) {
|
|
px = cx;
|
|
py = cy;
|
|
} else {
|
|
px = cx-(px-cx);
|
|
py = cy-(py-cy);
|
|
}
|
|
|
|
doc.quadraticCurveTo(px, py, cx + a[0], cy + a[1]);
|
|
cx += a[0];
|
|
return cy += a[1];
|
|
},
|
|
|
|
A(doc, a) {
|
|
solveArc(doc, cx, cy, a);
|
|
cx = a[5];
|
|
return cy = a[6];
|
|
},
|
|
|
|
a(doc, a) {
|
|
a[5] += cx;
|
|
a[6] += cy;
|
|
solveArc(doc, cx, cy, a);
|
|
cx = a[5];
|
|
return cy = a[6];
|
|
},
|
|
|
|
L(doc, a) {
|
|
cx = a[0];
|
|
cy = a[1];
|
|
px = (py = null);
|
|
return doc.lineTo(cx, cy);
|
|
},
|
|
|
|
l(doc, a) {
|
|
cx += a[0];
|
|
cy += a[1];
|
|
px = (py = null);
|
|
return doc.lineTo(cx, cy);
|
|
},
|
|
|
|
H(doc, a) {
|
|
cx = a[0];
|
|
px = (py = null);
|
|
return doc.lineTo(cx, cy);
|
|
},
|
|
|
|
h(doc, a) {
|
|
cx += a[0];
|
|
px = (py = null);
|
|
return doc.lineTo(cx, cy);
|
|
},
|
|
|
|
V(doc, a) {
|
|
cy = a[0];
|
|
px = (py = null);
|
|
return doc.lineTo(cx, cy);
|
|
},
|
|
|
|
v(doc, a) {
|
|
cy += a[0];
|
|
px = (py = null);
|
|
return doc.lineTo(cx, cy);
|
|
},
|
|
|
|
Z(doc) {
|
|
doc.closePath();
|
|
cx = sx;
|
|
return cy = sy;
|
|
},
|
|
|
|
z(doc) {
|
|
doc.closePath();
|
|
cx = sx;
|
|
return cy = sy;
|
|
}
|
|
};
|
|
|
|
const solveArc = function(doc, x, y, coords) {
|
|
const [rx,ry,rot,large,sweep,ex,ey] = coords;
|
|
const segs = arcToSegments(ex, ey, rx, ry, large, sweep, rot, x, y);
|
|
|
|
for (let seg of segs) {
|
|
const bez = segmentToBezier(...(seg || []));
|
|
doc.bezierCurveTo(...(bez || []));
|
|
}
|
|
};
|
|
|
|
// from Inkscape svgtopdf, thanks!
|
|
const arcToSegments = function(x, y, rx, ry, large, sweep, rotateX, ox, oy) {
|
|
const th = rotateX * (Math.PI/180);
|
|
const sin_th = Math.sin(th);
|
|
const cos_th = Math.cos(th);
|
|
rx = Math.abs(rx);
|
|
ry = Math.abs(ry);
|
|
px = (cos_th * (ox - x) * 0.5) + (sin_th * (oy - y) * 0.5);
|
|
py = (cos_th * (oy - y) * 0.5) - (sin_th * (ox - x) * 0.5);
|
|
let pl = ((px*px) / (rx*rx)) + ((py*py) / (ry*ry));
|
|
if (pl > 1) {
|
|
pl = Math.sqrt(pl);
|
|
rx *= pl;
|
|
ry *= pl;
|
|
}
|
|
|
|
const a00 = cos_th / rx;
|
|
const a01 = sin_th / rx;
|
|
const a10 = (-sin_th) / ry;
|
|
const a11 = (cos_th) / ry;
|
|
const x0 = (a00 * ox) + (a01 * oy);
|
|
const y0 = (a10 * ox) + (a11 * oy);
|
|
const x1 = (a00 * x) + (a01 * y);
|
|
const y1 = (a10 * x) + (a11 * y);
|
|
|
|
const d = ((x1-x0) * (x1-x0)) + ((y1-y0) * (y1-y0));
|
|
let sfactor_sq = (1 / d) - 0.25;
|
|
if (sfactor_sq < 0) { sfactor_sq = 0; }
|
|
let sfactor = Math.sqrt(sfactor_sq);
|
|
if (sweep === large) { sfactor = -sfactor; }
|
|
|
|
const xc = (0.5 * (x0 + x1)) - (sfactor * (y1-y0));
|
|
const yc = (0.5 * (y0 + y1)) + (sfactor * (x1-x0));
|
|
|
|
const th0 = Math.atan2(y0-yc, x0-xc);
|
|
const th1 = Math.atan2(y1-yc, x1-xc);
|
|
|
|
let th_arc = th1-th0;
|
|
if ((th_arc < 0) && (sweep === 1)) {
|
|
th_arc += 2*Math.PI;
|
|
} else if ((th_arc > 0) && (sweep === 0)) {
|
|
th_arc -= 2 * Math.PI;
|
|
}
|
|
|
|
const segments = Math.ceil(Math.abs(th_arc / ((Math.PI * 0.5) + 0.001)));
|
|
const result = [];
|
|
|
|
for (let i = 0, end = segments, asc = 0 <= end; asc ? i < end : i > end; asc ? i++ : i--) {
|
|
const th2 = th0 + ((i * th_arc) / segments);
|
|
const th3 = th0 + (((i+1) * th_arc) / segments);
|
|
result[i] = [xc, yc, th2, th3, rx, ry, sin_th, cos_th];
|
|
}
|
|
|
|
return result;
|
|
};
|
|
|
|
const segmentToBezier = function(cx, cy, th0, th1, rx, ry, sin_th, cos_th) {
|
|
const a00 = cos_th * rx;
|
|
const a01 = -sin_th * ry;
|
|
const a10 = sin_th * rx;
|
|
const a11 = cos_th * ry;
|
|
|
|
const th_half = 0.5 * (th1 - th0);
|
|
const t = ((8 / 3) * Math.sin(th_half * 0.5) * Math.sin(th_half * 0.5)) / Math.sin(th_half);
|
|
const x1 = (cx + Math.cos(th0)) - (t * Math.sin(th0));
|
|
const y1 = cy + Math.sin(th0) + (t * Math.cos(th0));
|
|
const x3 = cx + Math.cos(th1);
|
|
const y3 = cy + Math.sin(th1);
|
|
const x2 = x3 + (t * Math.sin(th1));
|
|
const y2 = y3 - (t * Math.cos(th1));
|
|
|
|
return [
|
|
(a00 * x1) + (a01 * y1), (a10 * x1) + (a11 * y1),
|
|
(a00 * x2) + (a01 * y2), (a10 * x2) + (a11 * y2),
|
|
(a00 * x3) + (a01 * y3), (a10 * x3) + (a11 * y3)
|
|
];
|
|
};
|
|
|
|
class SVGPath {
|
|
static apply(doc, path) {
|
|
const commands = parse(path);
|
|
apply(commands, doc);
|
|
}
|
|
};
|
|
|
|
export default SVGPath;
|