sketchmark 0.2.1 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ast/types.d.ts +0 -4
- package/dist/ast/types.d.ts.map +1 -1
- package/dist/index.cjs +249 -110
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +249 -110
- package/dist/index.js.map +1 -1
- package/dist/layout/index.d.ts.map +1 -1
- package/dist/parser/index.d.ts.map +1 -1
- package/dist/renderer/canvas/index.d.ts.map +1 -1
- package/dist/renderer/canvas/roughChartCanvas.d.ts.map +1 -1
- package/dist/renderer/svg/index.d.ts.map +1 -1
- package/dist/renderer/svg/roughChartSVG.d.ts.map +1 -1
- package/dist/sketchmark.iife.js +249 -110
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -261,10 +261,6 @@ function propsToStyle(p) {
|
|
|
261
261
|
s.color = p.color;
|
|
262
262
|
if (p.opacity)
|
|
263
263
|
s.opacity = parseFloat(p.opacity);
|
|
264
|
-
if (p.radius)
|
|
265
|
-
s.radius = parseFloat(p.radius);
|
|
266
|
-
if (p.shadow)
|
|
267
|
-
s.shadow = p.shadow === "true";
|
|
268
264
|
if (p["font-size"])
|
|
269
265
|
s.fontSize = parseFloat(p["font-size"]);
|
|
270
266
|
if (p["font-weight"])
|
|
@@ -281,8 +277,9 @@ function propsToStyle(p) {
|
|
|
281
277
|
s.letterSpacing = parseFloat(p["letter-spacing"]);
|
|
282
278
|
if (p.font)
|
|
283
279
|
s.font = p.font;
|
|
284
|
-
|
|
285
|
-
|
|
280
|
+
const dashVal = p["dash"] || p["stroke-dash"];
|
|
281
|
+
if (dashVal) {
|
|
282
|
+
const parts = dashVal
|
|
286
283
|
.split(",")
|
|
287
284
|
.map(Number)
|
|
288
285
|
.filter((n) => !isNaN(n));
|
|
@@ -1489,13 +1486,22 @@ function sizeNode(n) {
|
|
|
1489
1486
|
n.h = n.h || 50;
|
|
1490
1487
|
break;
|
|
1491
1488
|
case "text": {
|
|
1492
|
-
// read fontSize from style if set, otherwise use default
|
|
1493
1489
|
const fontSize = Number(n.style?.fontSize ?? 13);
|
|
1494
1490
|
const charWidth = fontSize * 0.55;
|
|
1495
|
-
const
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1491
|
+
const pad = Number(n.style?.padding ?? 8) * 2;
|
|
1492
|
+
if (n.width) {
|
|
1493
|
+
// User set width → word-wrap within it
|
|
1494
|
+
const approxLines = Math.ceil((n.label.length * charWidth) / (n.width - pad));
|
|
1495
|
+
n.w = n.width;
|
|
1496
|
+
n.h = n.height ?? Math.max(24, approxLines * fontSize * 1.5 + pad);
|
|
1497
|
+
}
|
|
1498
|
+
else {
|
|
1499
|
+
// Auto-size to content
|
|
1500
|
+
const lines = n.label.split("\\n");
|
|
1501
|
+
const longest = lines.reduce((a, b) => (a.length > b.length ? a : b), "");
|
|
1502
|
+
n.w = Math.max(MIN_W, Math.round(longest.length * charWidth + pad));
|
|
1503
|
+
n.h = n.height ?? Math.max(24, lines.length * fontSize * 1.5 + pad);
|
|
1504
|
+
}
|
|
1499
1505
|
break;
|
|
1500
1506
|
}
|
|
1501
1507
|
default:
|
|
@@ -1712,17 +1718,16 @@ function place(g, nm, gm, tm, ntm, cm, mdm) {
|
|
|
1712
1718
|
if (layout === "row") {
|
|
1713
1719
|
const ws = kids.map((r) => iW(r, nm, gm, tm, ntm, cm, mdm));
|
|
1714
1720
|
const hs = kids.map((r) => iH(r, nm, gm, tm, ntm, cm, mdm));
|
|
1715
|
-
const maxH = Math.max(...hs);
|
|
1716
1721
|
const { start, gaps } = distribute(ws, contentW, gap, justify);
|
|
1717
1722
|
let x = contentX + start;
|
|
1718
1723
|
for (let i = 0; i < kids.length; i++) {
|
|
1719
1724
|
let y;
|
|
1720
1725
|
switch (align) {
|
|
1721
1726
|
case "center":
|
|
1722
|
-
y = contentY + (
|
|
1727
|
+
y = contentY + (contentH - hs[i]) / 2;
|
|
1723
1728
|
break;
|
|
1724
1729
|
case "end":
|
|
1725
|
-
y = contentY +
|
|
1730
|
+
y = contentY + contentH - hs[i];
|
|
1726
1731
|
break;
|
|
1727
1732
|
default:
|
|
1728
1733
|
y = contentY;
|
|
@@ -1743,17 +1748,16 @@ function place(g, nm, gm, tm, ntm, cm, mdm) {
|
|
|
1743
1748
|
// column (default)
|
|
1744
1749
|
const ws = kids.map((r) => iW(r, nm, gm, tm, ntm, cm, mdm));
|
|
1745
1750
|
const hs = kids.map((r) => iH(r, nm, gm, tm, ntm, cm, mdm));
|
|
1746
|
-
const maxW = Math.max(...ws);
|
|
1747
1751
|
const { start, gaps } = distribute(hs, contentH, gap, justify);
|
|
1748
1752
|
let y = contentY + start;
|
|
1749
1753
|
for (let i = 0; i < kids.length; i++) {
|
|
1750
1754
|
let x;
|
|
1751
1755
|
switch (align) {
|
|
1752
1756
|
case "center":
|
|
1753
|
-
x = contentX + (
|
|
1757
|
+
x = contentX + (contentW - ws[i]) / 2;
|
|
1754
1758
|
break;
|
|
1755
1759
|
case "end":
|
|
1756
|
-
x = contentX +
|
|
1760
|
+
x = contentX + contentW - ws[i];
|
|
1757
1761
|
break;
|
|
1758
1762
|
default:
|
|
1759
1763
|
x = contentX;
|
|
@@ -2177,13 +2181,13 @@ function mkG(id, cls) {
|
|
|
2177
2181
|
g.setAttribute('class', cls);
|
|
2178
2182
|
return g;
|
|
2179
2183
|
}
|
|
2180
|
-
function mkT(txt, x, y, sz = 10, wt = 400, col = '#4a2e10', anchor = 'middle') {
|
|
2184
|
+
function mkT(txt, x, y, sz = 10, wt = 400, col = '#4a2e10', anchor = 'middle', font = 'system-ui, sans-serif') {
|
|
2181
2185
|
const t = se$1('text');
|
|
2182
2186
|
t.setAttribute('x', String(x));
|
|
2183
2187
|
t.setAttribute('y', String(y));
|
|
2184
2188
|
t.setAttribute('text-anchor', anchor);
|
|
2185
2189
|
t.setAttribute('dominant-baseline', 'middle');
|
|
2186
|
-
t.setAttribute('font-family',
|
|
2190
|
+
t.setAttribute('font-family', font);
|
|
2187
2191
|
t.setAttribute('font-size', String(sz));
|
|
2188
2192
|
t.setAttribute('font-weight', String(wt));
|
|
2189
2193
|
t.setAttribute('fill', col);
|
|
@@ -2199,7 +2203,7 @@ function hashStr$4(s) {
|
|
|
2199
2203
|
}
|
|
2200
2204
|
const BASE = { roughness: 1.2, bowing: 0.7 };
|
|
2201
2205
|
// ── Axes ───────────────────────────────────────────────────
|
|
2202
|
-
function drawAxes$1(rc, g, c, px, py, pw, ph, allY, labelCol) {
|
|
2206
|
+
function drawAxes$1(rc, g, c, px, py, pw, ph, allY, labelCol, font = 'system-ui, sans-serif') {
|
|
2203
2207
|
// Y axis
|
|
2204
2208
|
g.appendChild(rc.line(px, py, px, py + ph, {
|
|
2205
2209
|
roughness: 0.4, seed: hashStr$4(c.id + 'ya'), stroke: labelCol, strokeWidth: 1,
|
|
@@ -2218,7 +2222,7 @@ function drawAxes$1(rc, g, c, px, py, pw, ph, allY, labelCol) {
|
|
|
2218
2222
|
g.appendChild(rc.line(px - 3, ty, px, ty, {
|
|
2219
2223
|
roughness: 0.2, seed: hashStr$4(c.id + 'yt' + tick), stroke: labelCol, strokeWidth: 0.7,
|
|
2220
2224
|
}));
|
|
2221
|
-
g.appendChild(mkT(fmtNum$1(tick), px - 5, ty, 9, 400, labelCol, 'end'));
|
|
2225
|
+
g.appendChild(mkT(fmtNum$1(tick), px - 5, ty, 9, 400, labelCol, 'end', font));
|
|
2222
2226
|
}
|
|
2223
2227
|
}
|
|
2224
2228
|
function fmtNum$1(v) {
|
|
@@ -2227,7 +2231,7 @@ function fmtNum$1(v) {
|
|
|
2227
2231
|
return String(v);
|
|
2228
2232
|
}
|
|
2229
2233
|
// ── Legend row ─────────────────────────────────────────────
|
|
2230
|
-
function legend(g, labels, colors, x, y, labelCol) {
|
|
2234
|
+
function legend(g, labels, colors, x, y, labelCol, font = 'system-ui, sans-serif') {
|
|
2231
2235
|
labels.forEach((lbl, i) => {
|
|
2232
2236
|
const dot = se$1('rect');
|
|
2233
2237
|
dot.setAttribute('x', String(x));
|
|
@@ -2237,7 +2241,7 @@ function legend(g, labels, colors, x, y, labelCol) {
|
|
|
2237
2241
|
dot.setAttribute('fill', colors[i % colors.length]);
|
|
2238
2242
|
dot.setAttribute('rx', '1');
|
|
2239
2243
|
g.appendChild(dot);
|
|
2240
|
-
g.appendChild(mkT(lbl, x + 12, y + i * 14 + 4, 9, 400, labelCol, 'start'));
|
|
2244
|
+
g.appendChild(mkT(lbl, x + 12, y + i * 14 + 4, 9, 400, labelCol, 'start', font));
|
|
2241
2245
|
});
|
|
2242
2246
|
}
|
|
2243
2247
|
// ── Public entry ───────────────────────────────────────────
|
|
@@ -2248,6 +2252,11 @@ function renderRoughChartSVG(rc, c, palette, isDark) {
|
|
|
2248
2252
|
const bgFill = String(s.fill ?? palette.nodeFill);
|
|
2249
2253
|
const bgStroke = String(s.stroke ?? (isDark ? '#5a4a30' : '#c8b898'));
|
|
2250
2254
|
const lc = String(s.color ?? palette.titleText);
|
|
2255
|
+
const cFont = String(s.font ? `${s.font}, system-ui, sans-serif` : 'system-ui, sans-serif');
|
|
2256
|
+
const cFontSize = Number(s.fontSize ?? 12);
|
|
2257
|
+
const cFontWeight = s.fontWeight ?? 600;
|
|
2258
|
+
if (s.opacity != null)
|
|
2259
|
+
cg.setAttribute('opacity', String(s.opacity));
|
|
2251
2260
|
// Background box
|
|
2252
2261
|
cg.appendChild(rc.rectangle(c.x, c.y, c.w, c.h, {
|
|
2253
2262
|
...BASE, seed: hashStr$4(c.id),
|
|
@@ -2257,7 +2266,7 @@ function renderRoughChartSVG(rc, c, palette, isDark) {
|
|
|
2257
2266
|
}));
|
|
2258
2267
|
// Title
|
|
2259
2268
|
if (c.title) {
|
|
2260
|
-
cg.appendChild(mkT(c.title, c.x + c.w / 2, c.y + 14,
|
|
2269
|
+
cg.appendChild(mkT(c.title, c.x + c.w / 2, c.y + 14, cFontSize, cFontWeight, lc, 'middle', cFont));
|
|
2261
2270
|
}
|
|
2262
2271
|
const { px, py, pw, ph, cx, cy } = chartLayout(c);
|
|
2263
2272
|
// ── Pie / Donut ──────────────────────────────────────────
|
|
@@ -2283,7 +2292,7 @@ function renderRoughChartSVG(rc, c, palette, isDark) {
|
|
|
2283
2292
|
angle += sweep;
|
|
2284
2293
|
}
|
|
2285
2294
|
// Mini legend on left
|
|
2286
|
-
legend(cg, segments.map(s => `${s.label} ${Math.round(s.value / total * 100)}%`), segments.map(s => s.color), legendX, legendY, lc);
|
|
2295
|
+
legend(cg, segments.map(s => `${s.label} ${Math.round(s.value / total * 100)}%`), segments.map(s => s.color), legendX, legendY, lc, cFont);
|
|
2287
2296
|
return cg;
|
|
2288
2297
|
}
|
|
2289
2298
|
// ── Scatter ───────────────────────────────────────────────
|
|
@@ -2304,7 +2313,7 @@ function renderRoughChartSVG(rc, c, palette, isDark) {
|
|
|
2304
2313
|
strokeWidth: 1.2,
|
|
2305
2314
|
}));
|
|
2306
2315
|
});
|
|
2307
|
-
legend(cg, pts.map(p => p.label), CHART_COLORS, c.x + 8, c.y + (c.title ? 28 : 12), lc);
|
|
2316
|
+
legend(cg, pts.map(p => p.label), CHART_COLORS, c.x + 8, c.y + (c.title ? 28 : 12), lc, cFont);
|
|
2308
2317
|
return cg;
|
|
2309
2318
|
}
|
|
2310
2319
|
// ── Bar / Line / Area ─────────────────────────────────────
|
|
@@ -2313,10 +2322,10 @@ function renderRoughChartSVG(rc, c, palette, isDark) {
|
|
|
2313
2322
|
const toY = makeValueToY(allY, py, ph);
|
|
2314
2323
|
const baseline = toY(0);
|
|
2315
2324
|
const n = labels.length;
|
|
2316
|
-
drawAxes$1(rc, cg, c, px, py, pw, ph, allY, lc);
|
|
2325
|
+
drawAxes$1(rc, cg, c, px, py, pw, ph, allY, lc, cFont);
|
|
2317
2326
|
// X labels
|
|
2318
2327
|
labels.forEach((lbl, i) => {
|
|
2319
|
-
cg.appendChild(mkT(lbl, px + (i + 0.5) * (pw / n), py + ph + 14, 9, 400, lc));
|
|
2328
|
+
cg.appendChild(mkT(lbl, px + (i + 0.5) * (pw / n), py + ph + 14, 9, 400, lc, 'middle', cFont));
|
|
2320
2329
|
});
|
|
2321
2330
|
if (c.chartType === 'bar') {
|
|
2322
2331
|
const groupW = pw / n;
|
|
@@ -2387,7 +2396,7 @@ function renderRoughChartSVG(rc, c, palette, isDark) {
|
|
|
2387
2396
|
}
|
|
2388
2397
|
// Multi-series legend
|
|
2389
2398
|
if (series.length > 1) {
|
|
2390
|
-
legend(cg, series.map(s => s.name), series.map(s => s.color), px, py - 2, lc);
|
|
2399
|
+
legend(cg, series.map(s => s.name), series.map(s => s.color), px, py - 2, lc, cFont);
|
|
2391
2400
|
}
|
|
2392
2401
|
return cg;
|
|
2393
2402
|
}
|
|
@@ -4853,6 +4862,14 @@ function hashStr$3(s) {
|
|
|
4853
4862
|
return h;
|
|
4854
4863
|
}
|
|
4855
4864
|
const BASE_ROUGH = { roughness: 1.3, bowing: 0.7 };
|
|
4865
|
+
/** Darken a CSS hex colour by `amount` (0–1). Falls back to input for non-hex. */
|
|
4866
|
+
function darkenHex$1(hex, amount = 0.12) {
|
|
4867
|
+
const m = /^#?([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/i.exec(hex);
|
|
4868
|
+
if (!m)
|
|
4869
|
+
return hex;
|
|
4870
|
+
const d = (v) => Math.max(0, Math.round(parseInt(v, 16) * (1 - amount)));
|
|
4871
|
+
return `#${d(m[1]).toString(16).padStart(2, "0")}${d(m[2]).toString(16).padStart(2, "0")}${d(m[3]).toString(16).padStart(2, "0")}`;
|
|
4872
|
+
}
|
|
4856
4873
|
// ── Small helper: load + resolve font from style or fall back ─────────────
|
|
4857
4874
|
function resolveStyleFont$1(style, fallback) {
|
|
4858
4875
|
const raw = String(style["font"] ?? "");
|
|
@@ -5023,6 +5040,7 @@ function renderShape$1(rc, n, palette) {
|
|
|
5023
5040
|
fillStyle: "solid",
|
|
5024
5041
|
stroke,
|
|
5025
5042
|
strokeWidth: Number(s.strokeWidth ?? 1.9),
|
|
5043
|
+
...(s.strokeDash ? { strokeLineDash: s.strokeDash } : {}),
|
|
5026
5044
|
};
|
|
5027
5045
|
const cx = n.x + n.w / 2, cy = n.y + n.h / 2;
|
|
5028
5046
|
const hw = n.w / 2 - 2;
|
|
@@ -5205,6 +5223,8 @@ function renderToSVG(sg, container, options = {}) {
|
|
|
5205
5223
|
continue;
|
|
5206
5224
|
const gs = g.style ?? {};
|
|
5207
5225
|
const gg = mkGroup(`group-${g.id}`, "gg");
|
|
5226
|
+
if (gs.opacity != null)
|
|
5227
|
+
gg.setAttribute("opacity", String(gs.opacity));
|
|
5208
5228
|
gg.appendChild(rc.rectangle(g.x, g.y, g.w, g.h, {
|
|
5209
5229
|
...BASE_ROUGH,
|
|
5210
5230
|
roughness: 1.7,
|
|
@@ -5217,14 +5237,26 @@ function renderToSVG(sg, container, options = {}) {
|
|
|
5217
5237
|
strokeLineDash: gs.strokeDash ?? palette.groupDash,
|
|
5218
5238
|
}));
|
|
5219
5239
|
// ── Group label typography ──────────────────────────
|
|
5220
|
-
// supports: font, font-size, letter-spacing
|
|
5221
|
-
// always left-anchored (single line)
|
|
5222
5240
|
const gLabelColor = gs.color ? String(gs.color) : palette.groupLabel;
|
|
5223
5241
|
const gFontSize = Number(gs.fontSize ?? 12);
|
|
5242
|
+
const gFontWeight = gs.fontWeight ?? 500;
|
|
5224
5243
|
const gFont = resolveStyleFont$1(gs, diagramFont);
|
|
5225
5244
|
const gLetterSpacing = gs.letterSpacing;
|
|
5245
|
+
const gPad = Number(gs.padding ?? 14);
|
|
5246
|
+
const gTextAlign = String(gs.textAlign ?? "left");
|
|
5247
|
+
const gAnchorMap = {
|
|
5248
|
+
left: "start",
|
|
5249
|
+
center: "middle",
|
|
5250
|
+
right: "end",
|
|
5251
|
+
};
|
|
5252
|
+
const gAnchor = gAnchorMap[gTextAlign] ?? "start";
|
|
5253
|
+
const gTextX = gTextAlign === "right"
|
|
5254
|
+
? g.x + g.w - gPad
|
|
5255
|
+
: gTextAlign === "center"
|
|
5256
|
+
? g.x + g.w / 2
|
|
5257
|
+
: g.x + gPad;
|
|
5226
5258
|
if (g.label) {
|
|
5227
|
-
gg.appendChild(mkText(g.label,
|
|
5259
|
+
gg.appendChild(mkText(g.label, gTextX, g.y + gPad, gFontSize, gFontWeight, gLabelColor, gAnchor, gFont, gLetterSpacing));
|
|
5228
5260
|
}
|
|
5229
5261
|
GL.appendChild(gg);
|
|
5230
5262
|
}
|
|
@@ -5245,6 +5277,8 @@ function renderToSVG(sg, container, options = {}) {
|
|
|
5245
5277
|
const [x1, y1] = getConnPoint$1(src, dstCX, dstCY);
|
|
5246
5278
|
const [x2, y2] = getConnPoint$1(dst, srcCX, srcCY);
|
|
5247
5279
|
const eg = mkGroup(`edge-${e.from}-${e.to}`, "eg");
|
|
5280
|
+
if (e.style?.opacity != null)
|
|
5281
|
+
eg.setAttribute("opacity", String(e.style.opacity));
|
|
5248
5282
|
const len = Math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2) || 1;
|
|
5249
5283
|
const nx = (x2 - x1) / len, ny = (y2 - y1) / len;
|
|
5250
5284
|
const ecol = String(e.style?.stroke ?? palette.edgeStroke);
|
|
@@ -5285,7 +5319,9 @@ function renderToSVG(sg, container, options = {}) {
|
|
|
5285
5319
|
const eFontSize = Number(e.style?.fontSize ?? 11);
|
|
5286
5320
|
const eFont = resolveStyleFont$1(e.style ?? {}, diagramFont);
|
|
5287
5321
|
const eLetterSpacing = e.style?.letterSpacing;
|
|
5288
|
-
|
|
5322
|
+
const eFontWeight = e.style?.fontWeight ?? 400;
|
|
5323
|
+
const eLabelColor = String(e.style?.color ?? palette.edgeLabelText);
|
|
5324
|
+
eg.appendChild(mkText(e.label, mx, my, eFontSize, eFontWeight, eLabelColor, "middle", eFont, eLetterSpacing));
|
|
5289
5325
|
}
|
|
5290
5326
|
EL.appendChild(eg);
|
|
5291
5327
|
}
|
|
@@ -5294,6 +5330,8 @@ function renderToSVG(sg, container, options = {}) {
|
|
|
5294
5330
|
const NL = mkGroup("node-layer");
|
|
5295
5331
|
for (const n of sg.nodes) {
|
|
5296
5332
|
const ng = mkGroup(`node-${n.id}`, "ng");
|
|
5333
|
+
if (n.style?.opacity != null)
|
|
5334
|
+
ng.setAttribute("opacity", String(n.style.opacity));
|
|
5297
5335
|
renderShape$1(rc, n, palette).forEach((s) => ng.appendChild(s));
|
|
5298
5336
|
// ── Node / text typography ─────────────────────────
|
|
5299
5337
|
// supports: font, font-size, letter-spacing, text-align, line-height
|
|
@@ -5312,18 +5350,19 @@ function renderToSVG(sg, container, options = {}) {
|
|
|
5312
5350
|
// line-height is a multiplier (e.g. 1.4 = 140% of font-size)
|
|
5313
5351
|
const lineHeight = Number(n.style?.lineHeight ?? 1.3) * fontSize;
|
|
5314
5352
|
const letterSpacing = n.style?.letterSpacing;
|
|
5353
|
+
const pad = Number(n.style?.padding ?? 8);
|
|
5315
5354
|
// x shifts for left / right alignment
|
|
5316
5355
|
const textX = textAlign === "left"
|
|
5317
|
-
? n.x +
|
|
5356
|
+
? n.x + pad
|
|
5318
5357
|
: textAlign === "right"
|
|
5319
|
-
? n.x + n.w -
|
|
5358
|
+
? n.x + n.w - pad
|
|
5320
5359
|
: n.x + n.w / 2;
|
|
5321
5360
|
const lines = n.shape === 'text' && !n.label.includes('\n')
|
|
5322
|
-
? wrapText$1(n.label, n.w -
|
|
5361
|
+
? wrapText$1(n.label, n.w - pad * 2, fontSize)
|
|
5323
5362
|
: n.label.split('\n');
|
|
5324
5363
|
const verticalAlign = String(n.style?.verticalAlign ?? "middle");
|
|
5325
|
-
const nodeBodyTop = n.y +
|
|
5326
|
-
const nodeBodyBottom = n.y + n.h -
|
|
5364
|
+
const nodeBodyTop = n.y + pad;
|
|
5365
|
+
const nodeBodyBottom = n.y + n.h - pad;
|
|
5327
5366
|
const nodeBodyMid = n.y + n.h / 2;
|
|
5328
5367
|
const blockH = (lines.length - 1) * lineHeight;
|
|
5329
5368
|
const textCY = verticalAlign === "top"
|
|
@@ -5355,15 +5394,19 @@ function renderToSVG(sg, container, options = {}) {
|
|
|
5355
5394
|
const fill = String(gs.fill ?? palette.tableFill);
|
|
5356
5395
|
const strk = String(gs.stroke ?? palette.tableStroke);
|
|
5357
5396
|
const textCol = String(gs.color ?? palette.tableText);
|
|
5358
|
-
const hdrFill = palette.tableHeaderFill;
|
|
5397
|
+
const hdrFill = gs.fill ? darkenHex$1(fill, 0.08) : palette.tableHeaderFill;
|
|
5359
5398
|
const hdrText = String(gs.color ?? palette.tableHeaderText);
|
|
5360
5399
|
const divCol = palette.tableDivider;
|
|
5361
5400
|
const pad = t.labelH;
|
|
5401
|
+
const tStrokeWidth = Number(gs.strokeWidth ?? 1.5);
|
|
5402
|
+
const tFontWeight = gs.fontWeight ?? 500;
|
|
5362
5403
|
// ── Table-level font (applies to label + all cells) ─
|
|
5363
5404
|
// supports: font, font-size, letter-spacing
|
|
5364
5405
|
const tFontSize = Number(gs.fontSize ?? 12);
|
|
5365
5406
|
const tFont = resolveStyleFont$1(gs, diagramFont);
|
|
5366
5407
|
const tLetterSpacing = gs.letterSpacing;
|
|
5408
|
+
if (gs.opacity != null)
|
|
5409
|
+
tg.setAttribute("opacity", String(gs.opacity));
|
|
5367
5410
|
// outer border
|
|
5368
5411
|
tg.appendChild(rc.rectangle(t.x, t.y, t.w, t.h, {
|
|
5369
5412
|
...BASE_ROUGH,
|
|
@@ -5371,7 +5414,8 @@ function renderToSVG(sg, container, options = {}) {
|
|
|
5371
5414
|
fill,
|
|
5372
5415
|
fillStyle: "solid",
|
|
5373
5416
|
stroke: strk,
|
|
5374
|
-
strokeWidth:
|
|
5417
|
+
strokeWidth: tStrokeWidth,
|
|
5418
|
+
...(gs.strokeDash ? { strokeLineDash: gs.strokeDash } : {}),
|
|
5375
5419
|
}));
|
|
5376
5420
|
// label strip separator
|
|
5377
5421
|
tg.appendChild(rc.line(t.x, t.y + pad, t.x + t.w, t.y + pad, {
|
|
@@ -5380,8 +5424,8 @@ function renderToSVG(sg, container, options = {}) {
|
|
|
5380
5424
|
stroke: strk,
|
|
5381
5425
|
strokeWidth: 1,
|
|
5382
5426
|
}));
|
|
5383
|
-
// ── Table label: font, font-size, letter-spacing (always left) ──
|
|
5384
|
-
tg.appendChild(mkText(t.label, t.x + 10, t.y + pad / 2, tFontSize,
|
|
5427
|
+
// ── Table label: font, font-size, font-weight, letter-spacing (always left) ──
|
|
5428
|
+
tg.appendChild(mkText(t.label, t.x + 10, t.y + pad / 2, tFontSize, tFontWeight, textCol, "start", tFont, tLetterSpacing));
|
|
5385
5429
|
// rows
|
|
5386
5430
|
let rowY = t.y + pad;
|
|
5387
5431
|
for (const row of t.rows) {
|
|
@@ -5410,7 +5454,7 @@ function renderToSVG(sg, container, options = {}) {
|
|
|
5410
5454
|
right: "end",
|
|
5411
5455
|
};
|
|
5412
5456
|
const cellAnchor = cellAnchorMap[cellAlignProp] ?? "middle";
|
|
5413
|
-
const cellFw = row.kind === "header" ? 600 : 400;
|
|
5457
|
+
const cellFw = row.kind === "header" ? 600 : (gs.fontWeight ?? 400);
|
|
5414
5458
|
const cellColor = row.kind === "header" ? hdrText : textCol;
|
|
5415
5459
|
let cx = t.x;
|
|
5416
5460
|
row.cells.forEach((cell, i) => {
|
|
@@ -5449,27 +5493,31 @@ function renderToSVG(sg, container, options = {}) {
|
|
|
5449
5493
|
const gs = n.style ?? {};
|
|
5450
5494
|
const fill = String(gs.fill ?? palette.noteFill);
|
|
5451
5495
|
const strk = String(gs.stroke ?? palette.noteStroke);
|
|
5496
|
+
const nStrokeWidth = Number(gs.strokeWidth ?? 1.2);
|
|
5452
5497
|
const fold = 14;
|
|
5453
5498
|
const { x, y, w, h } = n;
|
|
5499
|
+
if (gs.opacity != null)
|
|
5500
|
+
ng.setAttribute("opacity", String(gs.opacity));
|
|
5454
5501
|
// ── Note typography ─────────────────────────────────
|
|
5455
|
-
// supports: font, font-size, letter-spacing, text-align, line-height
|
|
5456
5502
|
const nFontSize = Number(gs.fontSize ?? 12);
|
|
5503
|
+
const nFontWeight = gs.fontWeight ?? 400;
|
|
5457
5504
|
const nFont = resolveStyleFont$1(gs, diagramFont);
|
|
5458
5505
|
const nLetterSpacing = gs.letterSpacing;
|
|
5459
5506
|
const nLineHeight = Number(gs.lineHeight ?? 1.4) * nFontSize;
|
|
5460
5507
|
const nTextAlign = String(gs.textAlign ?? "left");
|
|
5508
|
+
const nPad = Number(gs.padding ?? 12);
|
|
5461
5509
|
const nAnchorMap = {
|
|
5462
5510
|
left: "start",
|
|
5463
5511
|
center: "middle",
|
|
5464
5512
|
right: "end",
|
|
5465
5513
|
};
|
|
5466
5514
|
const nAnchor = nAnchorMap[nTextAlign] ?? "start";
|
|
5467
|
-
// x position for the text block (pad from left, with alignment)
|
|
5468
5515
|
const nTextX = nTextAlign === "right"
|
|
5469
|
-
? x + w - fold -
|
|
5516
|
+
? x + w - fold - nPad
|
|
5470
5517
|
: nTextAlign === "center"
|
|
5471
5518
|
? x + (w - fold) / 2
|
|
5472
|
-
: x +
|
|
5519
|
+
: x + nPad;
|
|
5520
|
+
const nFoldPad = fold + nPad; // text starts below fold + user padding
|
|
5473
5521
|
ng.appendChild(rc.polygon([
|
|
5474
5522
|
[x, y],
|
|
5475
5523
|
[x + w - fold, y],
|
|
@@ -5482,7 +5530,8 @@ function renderToSVG(sg, container, options = {}) {
|
|
|
5482
5530
|
fill,
|
|
5483
5531
|
fillStyle: "solid",
|
|
5484
5532
|
stroke: strk,
|
|
5485
|
-
strokeWidth:
|
|
5533
|
+
strokeWidth: nStrokeWidth,
|
|
5534
|
+
...(gs.strokeDash ? { strokeLineDash: gs.strokeDash } : {}),
|
|
5486
5535
|
}));
|
|
5487
5536
|
ng.appendChild(rc.polygon([
|
|
5488
5537
|
[x + w - fold, y],
|
|
@@ -5494,11 +5543,11 @@ function renderToSVG(sg, container, options = {}) {
|
|
|
5494
5543
|
fill: palette.noteFold,
|
|
5495
5544
|
fillStyle: "solid",
|
|
5496
5545
|
stroke: strk,
|
|
5497
|
-
strokeWidth: 0.8,
|
|
5546
|
+
strokeWidth: Math.min(nStrokeWidth, 0.8),
|
|
5498
5547
|
}));
|
|
5499
5548
|
const nVerticalAlign = String(gs.verticalAlign ?? "top");
|
|
5500
|
-
const bodyTop = y +
|
|
5501
|
-
const bodyBottom = y + h -
|
|
5549
|
+
const bodyTop = y + nFoldPad;
|
|
5550
|
+
const bodyBottom = y + h - nPad;
|
|
5502
5551
|
const bodyMid = (bodyTop + bodyBottom) / 2;
|
|
5503
5552
|
const blockH = (n.lines.length - 1) * nLineHeight;
|
|
5504
5553
|
const blockCY = nVerticalAlign === "bottom"
|
|
@@ -5506,13 +5555,11 @@ function renderToSVG(sg, container, options = {}) {
|
|
|
5506
5555
|
: nVerticalAlign === "middle"
|
|
5507
5556
|
? bodyMid
|
|
5508
5557
|
: bodyTop + blockH / 2;
|
|
5509
|
-
// multiline: use mkMultilineText so line-height is respected
|
|
5510
5558
|
if (n.lines.length > 1) {
|
|
5511
|
-
|
|
5512
|
-
ng.appendChild(mkMultilineText(n.lines, nTextX, blockCY, nFontSize, 400, String(gs.color ?? palette.noteText), nAnchor, nLineHeight, nFont, nLetterSpacing));
|
|
5559
|
+
ng.appendChild(mkMultilineText(n.lines, nTextX, blockCY, nFontSize, nFontWeight, String(gs.color ?? palette.noteText), nAnchor, nLineHeight, nFont, nLetterSpacing));
|
|
5513
5560
|
}
|
|
5514
5561
|
else {
|
|
5515
|
-
ng.appendChild(mkText(n.lines[0] ?? "", nTextX, blockCY, nFontSize,
|
|
5562
|
+
ng.appendChild(mkText(n.lines[0] ?? "", nTextX, blockCY, nFontSize, nFontWeight, String(gs.color ?? palette.noteText), nAnchor, nFont, nLetterSpacing));
|
|
5516
5563
|
}
|
|
5517
5564
|
NoteL.appendChild(ng);
|
|
5518
5565
|
}
|
|
@@ -5521,13 +5568,27 @@ function renderToSVG(sg, container, options = {}) {
|
|
|
5521
5568
|
const MDL = mkGroup('markdown-layer');
|
|
5522
5569
|
for (const m of sg.markdowns) {
|
|
5523
5570
|
const mg = mkGroup(`markdown-${m.id}`, 'mdg');
|
|
5524
|
-
const
|
|
5525
|
-
const
|
|
5526
|
-
const
|
|
5571
|
+
const gs = m.style ?? {};
|
|
5572
|
+
const mFont = resolveStyleFont$1(gs, diagramFont);
|
|
5573
|
+
const baseColor = String(gs.color ?? palette.nodeText);
|
|
5574
|
+
const textAlign = String(gs.textAlign ?? 'left');
|
|
5527
5575
|
const anchor = textAlign === 'right' ? 'end'
|
|
5528
5576
|
: textAlign === 'center' ? 'middle'
|
|
5529
5577
|
: 'start';
|
|
5530
|
-
const PAD = Number(
|
|
5578
|
+
const PAD = Number(gs.padding ?? 16);
|
|
5579
|
+
const mLetterSpacing = gs.letterSpacing;
|
|
5580
|
+
if (gs.opacity != null)
|
|
5581
|
+
mg.setAttribute('opacity', String(gs.opacity));
|
|
5582
|
+
// Background + border
|
|
5583
|
+
if (gs.fill || gs.stroke) {
|
|
5584
|
+
mg.appendChild(rc.rectangle(m.x, m.y, m.w, m.h, {
|
|
5585
|
+
...BASE_ROUGH, seed: hashStr$3(m.id),
|
|
5586
|
+
fill: String(gs.fill ?? 'none'), fillStyle: 'solid',
|
|
5587
|
+
stroke: String(gs.stroke ?? 'none'),
|
|
5588
|
+
strokeWidth: Number(gs.strokeWidth ?? 1.2),
|
|
5589
|
+
...(gs.strokeDash ? { strokeLineDash: gs.strokeDash } : {}),
|
|
5590
|
+
}));
|
|
5591
|
+
}
|
|
5531
5592
|
const textX = textAlign === 'right' ? m.x + m.w - PAD
|
|
5532
5593
|
: textAlign === 'center' ? m.x + m.w / 2
|
|
5533
5594
|
: m.x + PAD;
|
|
@@ -5550,6 +5611,8 @@ function renderToSVG(sg, container, options = {}) {
|
|
|
5550
5611
|
t.setAttribute('fill', baseColor);
|
|
5551
5612
|
t.setAttribute('pointer-events', 'none');
|
|
5552
5613
|
t.setAttribute('user-select', 'none');
|
|
5614
|
+
if (mLetterSpacing != null)
|
|
5615
|
+
t.setAttribute('letter-spacing', String(mLetterSpacing));
|
|
5553
5616
|
for (const run of line.runs) {
|
|
5554
5617
|
const span = se('tspan');
|
|
5555
5618
|
span.textContent = run.text;
|
|
@@ -5639,7 +5702,7 @@ function drawPieArc(rc, ctx, cx, cy, r, ir, startAngle, endAngle, color, seed) {
|
|
|
5639
5702
|
});
|
|
5640
5703
|
}
|
|
5641
5704
|
// ── Axes ───────────────────────────────────────────────────
|
|
5642
|
-
function drawAxes(rc, ctx, c, px, py, pw, ph, allY, labelCol, R) {
|
|
5705
|
+
function drawAxes(rc, ctx, c, px, py, pw, ph, allY, labelCol, R, font = 'system-ui, sans-serif') {
|
|
5643
5706
|
const toY = makeValueToY(allY, py, ph);
|
|
5644
5707
|
const baseline = toY(0);
|
|
5645
5708
|
// Y axis
|
|
@@ -5653,7 +5716,7 @@ function drawAxes(rc, ctx, c, px, py, pw, ph, allY, labelCol, R) {
|
|
|
5653
5716
|
continue;
|
|
5654
5717
|
rc.line(px - 3, ty, px, ty, { roughness: 0.2, seed: hashStr$2(c.id + 'yt' + tick), stroke: labelCol, strokeWidth: 0.7 });
|
|
5655
5718
|
ctx.save();
|
|
5656
|
-
ctx.font =
|
|
5719
|
+
ctx.font = `400 9px ${font}`;
|
|
5657
5720
|
ctx.fillStyle = labelCol;
|
|
5658
5721
|
ctx.textAlign = 'right';
|
|
5659
5722
|
ctx.textBaseline = 'middle';
|
|
@@ -5662,9 +5725,9 @@ function drawAxes(rc, ctx, c, px, py, pw, ph, allY, labelCol, R) {
|
|
|
5662
5725
|
}
|
|
5663
5726
|
}
|
|
5664
5727
|
// ── Legend ─────────────────────────────────────────────────
|
|
5665
|
-
function drawLegend(ctx, labels, colors, x, y, labelCol) {
|
|
5728
|
+
function drawLegend(ctx, labels, colors, x, y, labelCol, font = 'system-ui, sans-serif') {
|
|
5666
5729
|
ctx.save();
|
|
5667
|
-
ctx.font =
|
|
5730
|
+
ctx.font = `400 9px ${font}`;
|
|
5668
5731
|
ctx.textAlign = 'left';
|
|
5669
5732
|
ctx.textBaseline = 'middle';
|
|
5670
5733
|
labels.forEach((lbl, i) => {
|
|
@@ -5682,6 +5745,11 @@ function drawRoughChartCanvas(rc, ctx, c, pal, R) {
|
|
|
5682
5745
|
const bgFill = String(s.fill ?? pal.nodeFill);
|
|
5683
5746
|
const bgStroke = String(s.stroke ?? (pal.nodeStroke === 'none' ? '#c8b898' : pal.nodeStroke));
|
|
5684
5747
|
const lc = String(s.color ?? pal.labelText);
|
|
5748
|
+
const cFont = String(s.font ? `${s.font}, system-ui, sans-serif` : 'system-ui, sans-serif');
|
|
5749
|
+
const cFontSize = Number(s.fontSize ?? 12);
|
|
5750
|
+
const cFontWeight = s.fontWeight ?? 600;
|
|
5751
|
+
if (s.opacity != null)
|
|
5752
|
+
ctx.globalAlpha = Number(s.opacity);
|
|
5685
5753
|
// Background
|
|
5686
5754
|
rc.rectangle(c.x, c.y, c.w, c.h, {
|
|
5687
5755
|
...R, seed: hashStr$2(c.id),
|
|
@@ -5694,7 +5762,7 @@ function drawRoughChartCanvas(rc, ctx, c, pal, R) {
|
|
|
5694
5762
|
// Title
|
|
5695
5763
|
if (c.title) {
|
|
5696
5764
|
ctx.save();
|
|
5697
|
-
ctx.font =
|
|
5765
|
+
ctx.font = `${cFontWeight} ${cFontSize}px ${cFont}`;
|
|
5698
5766
|
ctx.fillStyle = lc;
|
|
5699
5767
|
ctx.textAlign = 'center';
|
|
5700
5768
|
ctx.textBaseline = 'middle';
|
|
@@ -5715,7 +5783,8 @@ function drawRoughChartCanvas(rc, ctx, c, pal, R) {
|
|
|
5715
5783
|
drawPieArc(rc, ctx, cx, cy, r, ir, angle, angle + sweep, seg.color, hashStr$2(c.id + seg.label + i));
|
|
5716
5784
|
angle += sweep;
|
|
5717
5785
|
});
|
|
5718
|
-
drawLegend(ctx, segments.map(s => `${s.label} ${Math.round(s.value / total * 100)}%`), segments.map(s => s.color), legendX, legendY, lc);
|
|
5786
|
+
drawLegend(ctx, segments.map(s => `${s.label} ${Math.round(s.value / total * 100)}%`), segments.map(s => s.color), legendX, legendY, lc, cFont);
|
|
5787
|
+
ctx.globalAlpha = 1;
|
|
5719
5788
|
return;
|
|
5720
5789
|
}
|
|
5721
5790
|
// ── Scatter ───────────────────────────────────────────────
|
|
@@ -5735,7 +5804,8 @@ function drawRoughChartCanvas(rc, ctx, c, pal, R) {
|
|
|
5735
5804
|
strokeWidth: 1.2,
|
|
5736
5805
|
});
|
|
5737
5806
|
});
|
|
5738
|
-
drawLegend(ctx, pts.map(p => p.label), CHART_COLORS, c.x + 8, c.y + (c.title ? 28 : 12), lc);
|
|
5807
|
+
drawLegend(ctx, pts.map(p => p.label), CHART_COLORS, c.x + 8, c.y + (c.title ? 28 : 12), lc, cFont);
|
|
5808
|
+
ctx.globalAlpha = 1;
|
|
5739
5809
|
return;
|
|
5740
5810
|
}
|
|
5741
5811
|
// ── Bar / Line / Area ─────────────────────────────────────
|
|
@@ -5744,10 +5814,10 @@ function drawRoughChartCanvas(rc, ctx, c, pal, R) {
|
|
|
5744
5814
|
const toY = makeValueToY(allY, py, ph);
|
|
5745
5815
|
const baseline = toY(0);
|
|
5746
5816
|
const n = labels.length;
|
|
5747
|
-
drawAxes(rc, ctx, c, px, py, pw, ph, allY, lc, R);
|
|
5817
|
+
drawAxes(rc, ctx, c, px, py, pw, ph, allY, lc, R, cFont);
|
|
5748
5818
|
// X labels
|
|
5749
5819
|
ctx.save();
|
|
5750
|
-
ctx.font =
|
|
5820
|
+
ctx.font = `400 9px ${cFont}`;
|
|
5751
5821
|
ctx.fillStyle = lc;
|
|
5752
5822
|
ctx.textAlign = 'center';
|
|
5753
5823
|
ctx.textBaseline = 'top';
|
|
@@ -5824,8 +5894,9 @@ function drawRoughChartCanvas(rc, ctx, c, pal, R) {
|
|
|
5824
5894
|
}
|
|
5825
5895
|
// Multi-series legend
|
|
5826
5896
|
if (series.length > 1) {
|
|
5827
|
-
drawLegend(ctx, series.map(s => s.name), series.map(s => s.color), px, py - 2, lc);
|
|
5897
|
+
drawLegend(ctx, series.map(s => s.name), series.map(s => s.color), px, py - 2, lc, cFont);
|
|
5828
5898
|
}
|
|
5899
|
+
ctx.globalAlpha = 1;
|
|
5829
5900
|
}
|
|
5830
5901
|
|
|
5831
5902
|
// ============================================================
|
|
@@ -5838,6 +5909,14 @@ function hashStr$1(s) {
|
|
|
5838
5909
|
h = ((h * 33) ^ s.charCodeAt(i)) & 0xffff;
|
|
5839
5910
|
return h;
|
|
5840
5911
|
}
|
|
5912
|
+
/** Darken a CSS hex colour by `amount` (0–1). Falls back to input for non-hex. */
|
|
5913
|
+
function darkenHex(hex, amount = 0.12) {
|
|
5914
|
+
const m = /^#?([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/i.exec(hex);
|
|
5915
|
+
if (!m)
|
|
5916
|
+
return hex;
|
|
5917
|
+
const d = (v) => Math.max(0, Math.round(parseInt(v, 16) * (1 - amount)));
|
|
5918
|
+
return `#${d(m[1]).toString(16).padStart(2, "0")}${d(m[2]).toString(16).padStart(2, "0")}${d(m[3]).toString(16).padStart(2, "0")}`;
|
|
5919
|
+
}
|
|
5841
5920
|
// ── Small helper: load + resolve font from a style map ────────────────────
|
|
5842
5921
|
function resolveStyleFont(style, fallback) {
|
|
5843
5922
|
const raw = String(style['font'] ?? '');
|
|
@@ -5955,6 +6034,7 @@ function renderShape(rc, ctx, n, palette, R) {
|
|
|
5955
6034
|
...R, seed: hashStr$1(n.id),
|
|
5956
6035
|
fill, fillStyle: 'solid',
|
|
5957
6036
|
stroke, strokeWidth: Number(s.strokeWidth ?? 1.9),
|
|
6037
|
+
...(s.strokeDash ? { strokeLineDash: s.strokeDash } : {}),
|
|
5958
6038
|
};
|
|
5959
6039
|
const cx = n.x + n.w / 2, cy = n.y + n.h / 2;
|
|
5960
6040
|
const hw = n.w / 2 - 2;
|
|
@@ -6090,6 +6170,8 @@ function renderToCanvas(sg, canvas, options = {}) {
|
|
|
6090
6170
|
if (!g.w)
|
|
6091
6171
|
continue;
|
|
6092
6172
|
const gs = g.style ?? {};
|
|
6173
|
+
if (gs.opacity != null)
|
|
6174
|
+
ctx.globalAlpha = Number(gs.opacity);
|
|
6093
6175
|
rc.rectangle(g.x, g.y, g.w, g.h, {
|
|
6094
6176
|
...R, roughness: 1.7, bowing: 0.4, seed: hashStr$1(g.id),
|
|
6095
6177
|
fill: String(gs.fill ?? palette.groupFill),
|
|
@@ -6098,16 +6180,21 @@ function renderToCanvas(sg, canvas, options = {}) {
|
|
|
6098
6180
|
strokeWidth: Number(gs.strokeWidth ?? 1.2),
|
|
6099
6181
|
strokeLineDash: gs.strokeDash ?? palette.groupDash,
|
|
6100
6182
|
});
|
|
6101
|
-
// ── Group label ──────────────────────────────────────
|
|
6102
|
-
// Only render when label has content — empty label = no reserved space
|
|
6103
|
-
// supports: font, font-size, letter-spacing (always left-anchored)
|
|
6104
6183
|
if (g.label) {
|
|
6105
6184
|
const gFontSize = Number(gs.fontSize ?? 12);
|
|
6185
|
+
const gFontWeight = gs.fontWeight ?? 500;
|
|
6106
6186
|
const gFont = resolveStyleFont(gs, diagramFont);
|
|
6107
6187
|
const gLetterSpacing = gs.letterSpacing;
|
|
6108
6188
|
const gLabelColor = gs.color ? String(gs.color) : palette.groupLabel;
|
|
6109
|
-
|
|
6189
|
+
const gPad = Number(gs.padding ?? 14);
|
|
6190
|
+
const gTextAlign = String(gs.textAlign ?? 'left');
|
|
6191
|
+
const gTextX = gTextAlign === 'right' ? g.x + g.w - gPad
|
|
6192
|
+
: gTextAlign === 'center' ? g.x + g.w / 2
|
|
6193
|
+
: g.x + gPad;
|
|
6194
|
+
drawText(ctx, g.label, gTextX, g.y + gPad + 2, gFontSize, gFontWeight, gLabelColor, gTextAlign, gFont, gLetterSpacing);
|
|
6110
6195
|
}
|
|
6196
|
+
if (gs.opacity != null)
|
|
6197
|
+
ctx.globalAlpha = 1;
|
|
6111
6198
|
}
|
|
6112
6199
|
// ── Edges ─────────────────────────────────────────────────
|
|
6113
6200
|
for (const e of sg.edges) {
|
|
@@ -6119,6 +6206,8 @@ function renderToCanvas(sg, canvas, options = {}) {
|
|
|
6119
6206
|
const srcCX = src.x + src.w / 2, srcCY = src.y + src.h / 2;
|
|
6120
6207
|
const [x1, y1] = getConnPoint(src, dstCX, dstCY);
|
|
6121
6208
|
const [x2, y2] = getConnPoint(dst, srcCX, srcCY);
|
|
6209
|
+
if (e.style?.opacity != null)
|
|
6210
|
+
ctx.globalAlpha = Number(e.style.opacity);
|
|
6122
6211
|
const ecol = String(e.style?.stroke ?? palette.edgeStroke);
|
|
6123
6212
|
const { arrowAt, dashed } = connMeta(e.connector);
|
|
6124
6213
|
const len = Math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2) || 1;
|
|
@@ -6147,17 +6236,22 @@ function renderToCanvas(sg, canvas, options = {}) {
|
|
|
6147
6236
|
const eFontSize = Number(e.style?.fontSize ?? 11);
|
|
6148
6237
|
const eFont = resolveStyleFont(e.style ?? {}, diagramFont);
|
|
6149
6238
|
const eLetterSpacing = e.style?.letterSpacing;
|
|
6239
|
+
const eFontWeight = e.style?.fontWeight ?? 400;
|
|
6240
|
+
const eLabelColor = String(e.style?.color ?? palette.edgeLabelText);
|
|
6150
6241
|
ctx.save();
|
|
6151
|
-
ctx.font =
|
|
6242
|
+
ctx.font = `${eFontWeight} ${eFontSize}px ${eFont}`;
|
|
6152
6243
|
const tw = ctx.measureText(e.label).width + 12;
|
|
6153
6244
|
ctx.restore();
|
|
6154
6245
|
ctx.fillStyle = palette.edgeLabelBg;
|
|
6155
6246
|
ctx.fillRect(mx - tw / 2, my - 8, tw, 15);
|
|
6156
|
-
drawText(ctx, e.label, mx, my + 3, eFontSize,
|
|
6247
|
+
drawText(ctx, e.label, mx, my + 3, eFontSize, eFontWeight, eLabelColor, 'center', eFont, eLetterSpacing);
|
|
6157
6248
|
}
|
|
6249
|
+
ctx.globalAlpha = 1;
|
|
6158
6250
|
}
|
|
6159
6251
|
// ── Nodes ─────────────────────────────────────────────────
|
|
6160
6252
|
for (const n of sg.nodes) {
|
|
6253
|
+
if (n.style?.opacity != null)
|
|
6254
|
+
ctx.globalAlpha = Number(n.style.opacity);
|
|
6161
6255
|
renderShape(rc, ctx, n, palette, R);
|
|
6162
6256
|
// ── Node / text typography ─────────────────────────
|
|
6163
6257
|
// supports: font, font-size, letter-spacing, text-align,
|
|
@@ -6171,18 +6265,19 @@ function renderToCanvas(sg, canvas, options = {}) {
|
|
|
6171
6265
|
const lineHeight = Number(n.style?.lineHeight ?? 1.3) * fontSize;
|
|
6172
6266
|
const letterSpacing = n.style?.letterSpacing;
|
|
6173
6267
|
const vertAlign = String(n.style?.verticalAlign ?? 'middle');
|
|
6268
|
+
const pad = Number(n.style?.padding ?? 8);
|
|
6174
6269
|
// x shifts for left/right alignment
|
|
6175
|
-
const textX = textAlign === 'left' ? n.x +
|
|
6176
|
-
: textAlign === 'right' ? n.x + n.w -
|
|
6270
|
+
const textX = textAlign === 'left' ? n.x + pad
|
|
6271
|
+
: textAlign === 'right' ? n.x + n.w - pad
|
|
6177
6272
|
: n.x + n.w / 2;
|
|
6178
6273
|
// word-wrap for text shape; explicit \n for all others
|
|
6179
6274
|
const rawLines = n.label.split('\n');
|
|
6180
6275
|
const lines = n.shape === 'text' && rawLines.length === 1
|
|
6181
|
-
? wrapText(n.label, n.w -
|
|
6276
|
+
? wrapText(n.label, n.w - pad * 2, fontSize)
|
|
6182
6277
|
: rawLines;
|
|
6183
6278
|
// vertical-align: compute textCY from top/middle/bottom
|
|
6184
|
-
const nodeBodyTop = n.y +
|
|
6185
|
-
const nodeBodyBottom = n.y + n.h -
|
|
6279
|
+
const nodeBodyTop = n.y + pad;
|
|
6280
|
+
const nodeBodyBottom = n.y + n.h - pad;
|
|
6186
6281
|
const blockH = (lines.length - 1) * lineHeight;
|
|
6187
6282
|
const textCY = vertAlign === 'top' ? nodeBodyTop + blockH / 2
|
|
6188
6283
|
: vertAlign === 'bottom' ? nodeBodyBottom - blockH / 2
|
|
@@ -6193,6 +6288,8 @@ function renderToCanvas(sg, canvas, options = {}) {
|
|
|
6193
6288
|
else {
|
|
6194
6289
|
drawText(ctx, lines[0] ?? '', textX, textCY, fontSize, fontWeight, textColor, textAlign, nodeFont, letterSpacing);
|
|
6195
6290
|
}
|
|
6291
|
+
if (n.style?.opacity != null)
|
|
6292
|
+
ctx.globalAlpha = 1;
|
|
6196
6293
|
}
|
|
6197
6294
|
// ── Tables ────────────────────────────────────────────────
|
|
6198
6295
|
for (const t of sg.tables) {
|
|
@@ -6202,25 +6299,28 @@ function renderToCanvas(sg, canvas, options = {}) {
|
|
|
6202
6299
|
const textCol = String(gs.color ?? palette.tableText);
|
|
6203
6300
|
const pad = t.labelH;
|
|
6204
6301
|
// ── Table-level font ────────────────────────────────
|
|
6205
|
-
// supports: font, font-size, letter-spacing
|
|
6206
|
-
// cells also support text-align
|
|
6207
6302
|
const tFontSize = Number(gs.fontSize ?? 12);
|
|
6208
6303
|
const tFont = resolveStyleFont(gs, diagramFont);
|
|
6209
6304
|
const tLetterSpacing = gs.letterSpacing;
|
|
6305
|
+
const tStrokeWidth = Number(gs.strokeWidth ?? 1.5);
|
|
6306
|
+
const tFontWeight = gs.fontWeight ?? 500;
|
|
6307
|
+
if (gs.opacity != null)
|
|
6308
|
+
ctx.globalAlpha = Number(gs.opacity);
|
|
6210
6309
|
rc.rectangle(t.x, t.y, t.w, t.h, {
|
|
6211
6310
|
...R, seed: hashStr$1(t.id),
|
|
6212
|
-
fill, fillStyle: 'solid', stroke: strk, strokeWidth:
|
|
6311
|
+
fill, fillStyle: 'solid', stroke: strk, strokeWidth: tStrokeWidth,
|
|
6312
|
+
...(gs.strokeDash ? { strokeLineDash: gs.strokeDash } : {}),
|
|
6213
6313
|
});
|
|
6214
6314
|
rc.line(t.x, t.y + pad, t.x + t.w, t.y + pad, {
|
|
6215
6315
|
roughness: 0.6, seed: hashStr$1(t.id + 'l'), stroke: strk, strokeWidth: 1,
|
|
6216
6316
|
});
|
|
6217
6317
|
// ── Table label: always left-anchored ───────────────
|
|
6218
|
-
drawText(ctx, t.label, t.x + 10, t.y + pad / 2, tFontSize,
|
|
6318
|
+
drawText(ctx, t.label, t.x + 10, t.y + pad / 2, tFontSize, tFontWeight, textCol, 'left', tFont, tLetterSpacing);
|
|
6219
6319
|
let rowY = t.y + pad;
|
|
6220
6320
|
for (const row of t.rows) {
|
|
6221
6321
|
const rh = row.kind === 'header' ? t.headerH : t.rowH;
|
|
6222
6322
|
if (row.kind === 'header') {
|
|
6223
|
-
ctx.fillStyle = palette.tableHeaderFill;
|
|
6323
|
+
ctx.fillStyle = gs.fill ? darkenHex(fill, 0.08) : palette.tableHeaderFill;
|
|
6224
6324
|
ctx.fillRect(t.x + 1, rowY + 1, t.w - 2, rh - 1);
|
|
6225
6325
|
}
|
|
6226
6326
|
rc.line(t.x, rowY + rh, t.x + t.w, rowY + rh, {
|
|
@@ -6233,7 +6333,7 @@ function renderToCanvas(sg, canvas, options = {}) {
|
|
|
6233
6333
|
const cellAlignProp = (row.kind === 'header'
|
|
6234
6334
|
? 'center'
|
|
6235
6335
|
: String(gs.textAlign ?? 'center'));
|
|
6236
|
-
const cellFw = row.kind === 'header' ? 600 : 400;
|
|
6336
|
+
const cellFw = row.kind === 'header' ? 600 : (gs.fontWeight ?? 400);
|
|
6237
6337
|
const cellColor = row.kind === 'header'
|
|
6238
6338
|
? String(gs.color ?? palette.tableHeaderText)
|
|
6239
6339
|
: textCol;
|
|
@@ -6254,63 +6354,87 @@ function renderToCanvas(sg, canvas, options = {}) {
|
|
|
6254
6354
|
});
|
|
6255
6355
|
rowY += rh;
|
|
6256
6356
|
}
|
|
6357
|
+
ctx.globalAlpha = 1;
|
|
6257
6358
|
}
|
|
6258
6359
|
// ── Notes ─────────────────────────────────────────────────
|
|
6259
6360
|
for (const n of sg.notes) {
|
|
6260
6361
|
const gs = n.style ?? {};
|
|
6261
6362
|
const fill = String(gs.fill ?? palette.noteFill);
|
|
6262
6363
|
const strk = String(gs.stroke ?? palette.noteStroke);
|
|
6364
|
+
const nStrokeWidth = Number(gs.strokeWidth ?? 1.2);
|
|
6263
6365
|
const fold = 14;
|
|
6264
6366
|
const { x, y, w, h } = n;
|
|
6367
|
+
if (gs.opacity != null)
|
|
6368
|
+
ctx.globalAlpha = Number(gs.opacity);
|
|
6265
6369
|
rc.polygon([
|
|
6266
6370
|
[x, y],
|
|
6267
6371
|
[x + w - fold, y],
|
|
6268
6372
|
[x + w, y + fold],
|
|
6269
6373
|
[x + w, y + h],
|
|
6270
6374
|
[x, y + h],
|
|
6271
|
-
], { ...R, seed: hashStr$1(n.id), fill, fillStyle: 'solid', stroke: strk,
|
|
6375
|
+
], { ...R, seed: hashStr$1(n.id), fill, fillStyle: 'solid', stroke: strk,
|
|
6376
|
+
strokeWidth: nStrokeWidth,
|
|
6377
|
+
...(gs.strokeDash ? { strokeLineDash: gs.strokeDash } : {}),
|
|
6378
|
+
});
|
|
6272
6379
|
rc.polygon([
|
|
6273
6380
|
[x + w - fold, y],
|
|
6274
6381
|
[x + w, y + fold],
|
|
6275
6382
|
[x + w - fold, y + fold],
|
|
6276
6383
|
], { roughness: 0.4, seed: hashStr$1(n.id + 'f'),
|
|
6277
|
-
fill: palette.noteFold, fillStyle: 'solid', stroke: strk,
|
|
6278
|
-
|
|
6279
|
-
|
|
6280
|
-
// vertical-align, line-height
|
|
6384
|
+
fill: palette.noteFold, fillStyle: 'solid', stroke: strk,
|
|
6385
|
+
strokeWidth: Math.min(nStrokeWidth, 0.8),
|
|
6386
|
+
});
|
|
6281
6387
|
const nFontSize = Number(gs.fontSize ?? 12);
|
|
6388
|
+
const nFontWeight = gs.fontWeight ?? 400;
|
|
6282
6389
|
const nFont = resolveStyleFont(gs, diagramFont);
|
|
6283
6390
|
const nLetterSpacing = gs.letterSpacing;
|
|
6284
6391
|
const nLineHeight = Number(gs.lineHeight ?? 1.4) * nFontSize;
|
|
6285
6392
|
const nTextAlign = String(gs.textAlign ?? 'left');
|
|
6286
6393
|
const nVertAlign = String(gs.verticalAlign ?? 'top');
|
|
6287
6394
|
const nColor = String(gs.color ?? palette.noteText);
|
|
6288
|
-
const
|
|
6395
|
+
const nPad = Number(gs.padding ?? 12);
|
|
6396
|
+
const nTextX = nTextAlign === 'right' ? x + w - fold - nPad
|
|
6289
6397
|
: nTextAlign === 'center' ? x + (w - fold) / 2
|
|
6290
|
-
: x +
|
|
6291
|
-
|
|
6292
|
-
const bodyTop = y +
|
|
6293
|
-
const bodyBottom = y + h -
|
|
6398
|
+
: x + nPad;
|
|
6399
|
+
const nFoldPad = fold + nPad;
|
|
6400
|
+
const bodyTop = y + nFoldPad;
|
|
6401
|
+
const bodyBottom = y + h - nPad;
|
|
6294
6402
|
const blockH = (n.lines.length - 1) * nLineHeight;
|
|
6295
6403
|
const blockCY = nVertAlign === 'bottom' ? bodyBottom - blockH / 2
|
|
6296
6404
|
: nVertAlign === 'middle' ? (bodyTop + bodyBottom) / 2
|
|
6297
|
-
: bodyTop + blockH / 2;
|
|
6405
|
+
: bodyTop + blockH / 2;
|
|
6298
6406
|
if (n.lines.length > 1) {
|
|
6299
|
-
drawMultilineText(ctx, n.lines, nTextX, blockCY, nFontSize,
|
|
6407
|
+
drawMultilineText(ctx, n.lines, nTextX, blockCY, nFontSize, nFontWeight, nColor, nTextAlign, nLineHeight, nFont, nLetterSpacing);
|
|
6300
6408
|
}
|
|
6301
6409
|
else {
|
|
6302
|
-
drawText(ctx, n.lines[0] ?? '', nTextX, blockCY, nFontSize,
|
|
6410
|
+
drawText(ctx, n.lines[0] ?? '', nTextX, blockCY, nFontSize, nFontWeight, nColor, nTextAlign, nFont, nLetterSpacing);
|
|
6303
6411
|
}
|
|
6412
|
+
if (gs.opacity != null)
|
|
6413
|
+
ctx.globalAlpha = 1;
|
|
6304
6414
|
}
|
|
6305
6415
|
// ── Markdown blocks ────────────────────────────────────────
|
|
6306
6416
|
// Renders prose with Markdown headings and bold/italic inline spans.
|
|
6307
6417
|
// Canvas has no native bold-within-a-run, so each run is drawn
|
|
6308
6418
|
// individually with its own ctx.font setting.
|
|
6309
6419
|
for (const m of (sg.markdowns ?? [])) {
|
|
6310
|
-
const
|
|
6311
|
-
const
|
|
6312
|
-
const
|
|
6313
|
-
const
|
|
6420
|
+
const gs = m.style ?? {};
|
|
6421
|
+
const mFont = resolveStyleFont(gs, diagramFont);
|
|
6422
|
+
const baseColor = String(gs.color ?? palette.nodeText);
|
|
6423
|
+
const textAlign = String(gs.textAlign ?? 'left');
|
|
6424
|
+
const PAD = Number(gs.padding ?? 16);
|
|
6425
|
+
const mLetterSpacing = gs.letterSpacing;
|
|
6426
|
+
if (gs.opacity != null)
|
|
6427
|
+
ctx.globalAlpha = Number(gs.opacity);
|
|
6428
|
+
// Background + border
|
|
6429
|
+
if (gs.fill || gs.stroke) {
|
|
6430
|
+
rc.rectangle(m.x, m.y, m.w, m.h, {
|
|
6431
|
+
...R, seed: hashStr$1(m.id),
|
|
6432
|
+
fill: String(gs.fill ?? 'none'), fillStyle: 'solid',
|
|
6433
|
+
stroke: String(gs.stroke ?? 'none'),
|
|
6434
|
+
strokeWidth: Number(gs.strokeWidth ?? 1.2),
|
|
6435
|
+
...(gs.strokeDash ? { strokeLineDash: gs.strokeDash } : {}),
|
|
6436
|
+
});
|
|
6437
|
+
}
|
|
6314
6438
|
const anchorX = textAlign === 'right' ? m.x + m.w - PAD
|
|
6315
6439
|
: textAlign === 'center' ? m.x + m.w / 2
|
|
6316
6440
|
: m.x + PAD;
|
|
@@ -6328,14 +6452,29 @@ function renderToCanvas(sg, canvas, options = {}) {
|
|
|
6328
6452
|
ctx.save();
|
|
6329
6453
|
ctx.textBaseline = 'middle';
|
|
6330
6454
|
ctx.fillStyle = baseColor;
|
|
6455
|
+
const ls = mLetterSpacing ?? 0;
|
|
6456
|
+
// measure run width including letter-spacing
|
|
6457
|
+
const runW = (run) => {
|
|
6458
|
+
return ctx.measureText(run.text).width + ls * run.text.length;
|
|
6459
|
+
};
|
|
6460
|
+
const drawRun = (run, rx) => {
|
|
6461
|
+
if (ls) {
|
|
6462
|
+
for (const ch of run.text) {
|
|
6463
|
+
ctx.fillText(ch, rx, lineY);
|
|
6464
|
+
rx += ctx.measureText(ch).width + ls;
|
|
6465
|
+
}
|
|
6466
|
+
}
|
|
6467
|
+
else {
|
|
6468
|
+
ctx.fillText(run.text, rx, lineY);
|
|
6469
|
+
}
|
|
6470
|
+
};
|
|
6331
6471
|
if (textAlign === 'center' || textAlign === 'right') {
|
|
6332
|
-
// Measure full line width first
|
|
6333
6472
|
let totalW = 0;
|
|
6334
6473
|
for (const run of line.runs) {
|
|
6335
6474
|
const runStyle = run.italic ? 'italic ' : '';
|
|
6336
6475
|
const runWeight = run.bold ? 700 : fontWeight;
|
|
6337
6476
|
ctx.font = `${runStyle}${runWeight} ${fontSize}px ${mFont}`;
|
|
6338
|
-
totalW +=
|
|
6477
|
+
totalW += runW(run);
|
|
6339
6478
|
}
|
|
6340
6479
|
let runX = textAlign === 'center' ? anchorX - totalW / 2 : anchorX - totalW;
|
|
6341
6480
|
ctx.textAlign = 'left';
|
|
@@ -6343,25 +6482,25 @@ function renderToCanvas(sg, canvas, options = {}) {
|
|
|
6343
6482
|
const runStyle = run.italic ? 'italic ' : '';
|
|
6344
6483
|
const runWeight = run.bold ? 700 : fontWeight;
|
|
6345
6484
|
ctx.font = `${runStyle}${runWeight} ${fontSize}px ${mFont}`;
|
|
6346
|
-
|
|
6347
|
-
runX +=
|
|
6485
|
+
drawRun(run, runX);
|
|
6486
|
+
runX += runW(run);
|
|
6348
6487
|
}
|
|
6349
6488
|
}
|
|
6350
6489
|
else {
|
|
6351
|
-
// left-aligned — draw runs left to right from anchorX
|
|
6352
6490
|
let runX = anchorX;
|
|
6353
6491
|
ctx.textAlign = 'left';
|
|
6354
6492
|
for (const run of line.runs) {
|
|
6355
6493
|
const runStyle = run.italic ? 'italic ' : '';
|
|
6356
6494
|
const runWeight = run.bold ? 700 : fontWeight;
|
|
6357
6495
|
ctx.font = `${runStyle}${runWeight} ${fontSize}px ${mFont}`;
|
|
6358
|
-
|
|
6359
|
-
runX +=
|
|
6496
|
+
drawRun(run, runX);
|
|
6497
|
+
runX += runW(run);
|
|
6360
6498
|
}
|
|
6361
6499
|
}
|
|
6362
6500
|
ctx.restore();
|
|
6363
6501
|
y += LINE_SPACING[line.kind];
|
|
6364
6502
|
}
|
|
6503
|
+
ctx.globalAlpha = 1;
|
|
6365
6504
|
}
|
|
6366
6505
|
// ── Charts ────────────────────────────────────────────────
|
|
6367
6506
|
for (const c of sg.charts) {
|