Plot = () => {
const cmds = [];
const stack = [];
let pos = [0, 0];
const movePos = (dx, dy) => (pos = add(pos, [dx, dy]));
const move = (dx, dy) => {
movePos(dx, dy);
const last = cmds.at(-1);
if (last?.mv) last.mv = add(last.mv, [dx, dy]);
else cmds.push({ mv: [dx, dy] });
};
const draw = (dx, dy, angle) => (
movePos(dx, dy), cmds.push({ dr: angle ? [dx, dy, angle] : [dx, dy] })
);
const vec = (dx, dy, angle) => {
const [cos, sin] = [Math.cos(angle), Math.sin(angle)];
return [dx * cos - dy * sin, dx * sin + dy * cos];
};
const vect = ([x1, y1], [x2, y2]) => [x2 - x1, y2 - y1];
const push = () => stack.push(pos);
const pop = () => cmds.push({ mv: vect(pos, (pos = stack.pop())) });
const add = ([x, y], [dx, dy]) => [x + dx, y + dy];
const mul = ([x, y], r) => (r == 1 ? [x, y] : [x * r, y * r]);
const chain = (plot) => {
for (const cmd of plot) {
if (cmd.mv) move(...cmd.mv);
else draw(...cmd.dr);
}
};
const plot = {
move: (dx, dy) => ((dx != 0 || dy != 0) && move(dx, dy), plot),
draw: (dx, dy, angle) => (
(dx != 0 || dy != 0) && draw(dx, dy, angle), plot
),
text: (lines, options) => {
const defaults = {
halign: "center",
valign: "middle",
inter: 0,
scale: [1, 1],
rotation: 0
};
const { halign, valign, inter, scale, rotation } = Object.assign(
defaults,
options
);
const LINE_HEIGHT = 32;
const ht = LINE_HEIGHT * scale[1];
const it = inter * scale[1];
const nb = lines.length;
const h = ht * nb + it * (nb - 1);
const dys = { top: 0, middle: -h / 2, bottom: -h };
const dy = dys[valign];
const angle = rotation * (Math.PI / 180);
push();
move(...vec(0, dy + ht / 2, angle));
for (const str of lines) {
const { x: xt, width: wt } = bounds(str, options);
const dxs = { left: 0, center: -wt / 2, right: -wt };
const dx = dxs[halign];
push();
move(...vec(dx - xt, 0, angle));
let [cx, cy] = [0, 0];
for (const path of str2paths(str, options)) {
if (path.length > 0) {
const [px, py] = path[0];
move(...vec(-cx + (cx = px), -cy + (cy = py), angle));
for (const [px, py] of path.slice(1)) {
draw(...vec(-cx + (cx = px), -cy + (cy = py), angle));
}
}
}
pop();
move(...vec(0, ht, angle));
}
pop();
return plot;
},
push: () => (push(), plot),
pop: () => (pop(), plot),
chain: (p) => (chain(p), plot),
insert: (p) => plot.push().chain(p).pop(),
[Symbol.iterator]: () => cmds[Symbol.iterator]()
};
return plot;
}