@thi.ng/hiccup-canvas
Version:
Hiccup shape tree renderer for vanilla Canvas 2D contexts
147 lines (146 loc) • 3.63 kB
JavaScript
import { isArrayLike } from "@thi.ng/checks/is-arraylike";
import { resolveGradientOrColor } from "../color.js";
const DEFAULTS = {
align: "left",
alpha: 1,
baseline: "alphabetic",
compose: "source-over",
dash: [],
dashOffset: 0,
direction: "inherit",
fill: "#000",
filter: "none",
font: "10px sans-serif",
lineCap: "butt",
lineJoin: "miter",
miterLimit: 10,
shadowBlur: 0,
shadowColor: "rgba(0,0,0,0)",
shadowX: 0,
shadowY: 0,
smooth: true,
stroke: "#000",
weight: 1
};
const CTX_ATTRIBS = {
align: "textAlign",
alpha: "globalAlpha",
baseline: "textBaseline",
clip: "clip",
compose: "globalCompositeOperation",
dash: "setLineDash",
dashOffset: "lineDashOffset",
direction: "direction",
fill: "fillStyle",
fillRule: "fillRule",
filter: "filter",
font: "font",
lineCap: "lineCap",
lineJoin: "lineJoin",
miterLimit: "miterLimit",
shadowBlur: "shadowBlur",
shadowColor: "shadowColor",
shadowX: "shadowOffsetX",
shadowY: "shadowOffsetY",
smooth: "imageSmoothingEnabled",
stroke: "strokeStyle",
weight: "lineWidth"
};
const __newState = (state, restore = false) => ({
attribs: { ...state.attribs },
grads: { ...state.grads },
edits: [],
restore
});
const __mergeState = (ctx, state, attribs) => {
let res;
if (!attribs) return;
if (__applyTransform(ctx, attribs)) {
res = __newState(state, true);
}
for (let id in attribs) {
const k = CTX_ATTRIBS[id];
if (k) {
const v = attribs[id];
if (v != null && state.attribs[id] !== v) {
!res && (res = __newState(state));
res.attribs[id] = v;
res.edits.push(id);
__setAttrib(ctx, state, id, k, v);
}
} else if (id === "__background" || id === "__clear") {
ctx.save();
ctx.resetTransform();
if (id === "__clear") {
attribs[id] && ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
} else {
ctx.fillStyle = resolveGradientOrColor(state, attribs[id]);
ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height);
}
ctx.restore();
}
}
return res;
};
const __restoreState = (ctx, prev, curr) => {
if (curr.restore) {
ctx.restore();
return;
}
const edits = curr.edits;
const attribs = prev.attribs;
for (let i = edits.length; i-- > 0; ) {
const id = edits[i];
const v = attribs[id];
__setAttrib(
ctx,
prev,
id,
CTX_ATTRIBS[id],
v != null ? v : DEFAULTS[id]
);
}
};
const __registerGradient = (state, id, g) => {
!state.grads && (state.grads = {});
state.grads[id] = g;
};
const __setAttrib = (ctx, state, id, k, val) => {
switch (id) {
case "fill":
case "stroke":
case "shadowColor":
ctx[k] = resolveGradientOrColor(state, val);
break;
case "dash":
ctx[k].call(ctx, val);
break;
case "clip":
case "fillRule":
break;
default:
ctx[k] = val;
}
};
const __applyTransform = (ctx, attribs) => {
let v;
if ((v = attribs.transform) || attribs.setTransform || attribs.translate || attribs.scale || attribs.rotate) {
ctx.save();
if (v) {
ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]);
} else if (v = attribs.setTransform) {
ctx.setTransform(v[0], v[1], v[2], v[3], v[4], v[5]);
} else {
(v = attribs.translate) && ctx.translate(v[0], v[1]);
(v = attribs.rotate) && ctx.rotate(v);
(v = attribs.scale) && (isArrayLike(v) ? ctx.scale(v[0], v[1]) : ctx.scale(v, v));
}
return true;
}
return false;
};
export {
__mergeState,
__registerGradient,
__restoreState
};