pdfkit/lib/path.js
2018-11-29 08:14:45 -08:00

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;