sketchmark 0.1.1 → 0.1.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.
Potentially problematic release.
This version of sketchmark might be problematic. Click here for more details.
- package/dist/ast/types.d.ts +7 -0
- package/dist/ast/types.d.ts.map +1 -1
- package/dist/fonts/index.d.ts +11 -0
- package/dist/fonts/index.d.ts.map +1 -0
- package/dist/index.cjs +513 -303
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +510 -304
- 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 +1 -1
- package/dist/renderer/canvas/index.d.ts.map +1 -1
- package/dist/renderer/svg/index.d.ts.map +1 -1
- package/dist/scene/index.d.ts +2 -0
- package/dist/scene/index.d.ts.map +1 -1
- package/dist/sketchmark.iife.js +513 -303
- package/package.json +1 -1
package/dist/sketchmark.iife.js
CHANGED
|
@@ -251,6 +251,16 @@ var AIDiagram = (function (exports) {
|
|
|
251
251
|
s.fontSize = parseFloat(p["font-size"]);
|
|
252
252
|
if (p["font-weight"])
|
|
253
253
|
s.fontWeight = p["font-weight"];
|
|
254
|
+
if (p['text-align'])
|
|
255
|
+
s.textAlign = p['text-align'];
|
|
256
|
+
if (p['vertical-align'])
|
|
257
|
+
s.verticalAlign = p['vertical-align'];
|
|
258
|
+
if (p['line-height'])
|
|
259
|
+
s.lineHeight = parseFloat(p['line-height']);
|
|
260
|
+
if (p['letter-spacing'])
|
|
261
|
+
s.letterSpacing = parseFloat(p['letter-spacing']);
|
|
262
|
+
if (p.font)
|
|
263
|
+
s.font = p.font;
|
|
254
264
|
if (p["dash"]) {
|
|
255
265
|
const parts = p["dash"]
|
|
256
266
|
.split(",")
|
|
@@ -477,6 +487,8 @@ var AIDiagram = (function (exports) {
|
|
|
477
487
|
label: rawLabel.replace(/\\n/g, "\n"),
|
|
478
488
|
theme: props.theme,
|
|
479
489
|
style: propsToStyle(props),
|
|
490
|
+
...(props.width ? { width: parseFloat(props.width) } : {}),
|
|
491
|
+
...(props.height ? { height: parseFloat(props.height) } : {}),
|
|
480
492
|
};
|
|
481
493
|
}
|
|
482
494
|
// ── parseGroup ───────────────────────────────────────────
|
|
@@ -1063,8 +1075,6 @@ var AIDiagram = (function (exports) {
|
|
|
1063
1075
|
node.style = { ...ast.styles[node.id], ...node.style };
|
|
1064
1076
|
}
|
|
1065
1077
|
}
|
|
1066
|
-
console.log("[parse] charts:", ast.charts.map((c) => c.id));
|
|
1067
|
-
console.log("[parse] rootOrder:", ast.rootOrder.map((r) => r.kind + ":" + r.id));
|
|
1068
1078
|
return ast;
|
|
1069
1079
|
}
|
|
1070
1080
|
|
|
@@ -1140,6 +1150,8 @@ var AIDiagram = (function (exports) {
|
|
|
1140
1150
|
y: 0,
|
|
1141
1151
|
w: 0,
|
|
1142
1152
|
h: 0,
|
|
1153
|
+
width: n.width,
|
|
1154
|
+
height: n.height,
|
|
1143
1155
|
};
|
|
1144
1156
|
});
|
|
1145
1157
|
const charts = ast.charts.map((c) => {
|
|
@@ -1284,6 +1296,10 @@ var AIDiagram = (function (exports) {
|
|
|
1284
1296
|
const maxChars = Math.max(...n.lines.map(l => l.length));
|
|
1285
1297
|
n.w = Math.max(120, Math.ceil(maxChars * NOTE_FONT) + NOTE_PAD_X * 2);
|
|
1286
1298
|
n.h = n.lines.length * NOTE_LINE_H + NOTE_PAD_Y * 2;
|
|
1299
|
+
if (n.width && n.w < n.width)
|
|
1300
|
+
n.w = n.width; // ← add
|
|
1301
|
+
if (n.height && n.h < n.height)
|
|
1302
|
+
n.h = n.height; // ← add
|
|
1287
1303
|
}
|
|
1288
1304
|
// ── Table auto-sizing ─────────────────────────────────────
|
|
1289
1305
|
function sizeTable(t) {
|
|
@@ -2412,6 +2428,83 @@ var AIDiagram = (function (exports) {
|
|
|
2412
2428
|
}
|
|
2413
2429
|
const THEME_NAMES = Object.keys(PALETTES);
|
|
2414
2430
|
|
|
2431
|
+
// ============================================================
|
|
2432
|
+
// sketchmark — Font Registry
|
|
2433
|
+
// ============================================================
|
|
2434
|
+
// built-in named fonts — user can reference these by short name
|
|
2435
|
+
const BUILTIN_FONTS = {
|
|
2436
|
+
// hand-drawn
|
|
2437
|
+
caveat: {
|
|
2438
|
+
family: "'Caveat', cursive",
|
|
2439
|
+
url: 'https://fonts.googleapis.com/css2?family=Caveat:wght@400;500;600&display=swap',
|
|
2440
|
+
},
|
|
2441
|
+
handlee: {
|
|
2442
|
+
family: "'Handlee', cursive",
|
|
2443
|
+
url: 'https://fonts.googleapis.com/css2?family=Handlee&display=swap',
|
|
2444
|
+
},
|
|
2445
|
+
'indie-flower': {
|
|
2446
|
+
family: "'Indie Flower', cursive",
|
|
2447
|
+
url: 'https://fonts.googleapis.com/css2?family=Indie+Flower&display=swap',
|
|
2448
|
+
},
|
|
2449
|
+
'patrick-hand': {
|
|
2450
|
+
family: "'Patrick Hand', cursive",
|
|
2451
|
+
url: 'https://fonts.googleapis.com/css2?family=Patrick+Hand&display=swap',
|
|
2452
|
+
},
|
|
2453
|
+
// clean / readable
|
|
2454
|
+
'dm-mono': {
|
|
2455
|
+
family: "'DM Mono', monospace",
|
|
2456
|
+
url: 'https://fonts.googleapis.com/css2?family=DM+Mono:wght@300;400;500&display=swap',
|
|
2457
|
+
},
|
|
2458
|
+
'jetbrains': {
|
|
2459
|
+
family: "'JetBrains Mono', monospace",
|
|
2460
|
+
url: 'https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@300;400;500&display=swap',
|
|
2461
|
+
},
|
|
2462
|
+
'instrument': {
|
|
2463
|
+
family: "'Instrument Serif', serif",
|
|
2464
|
+
url: 'https://fonts.googleapis.com/css2?family=Instrument+Serif&display=swap',
|
|
2465
|
+
},
|
|
2466
|
+
'playfair': {
|
|
2467
|
+
family: "'Playfair Display', serif",
|
|
2468
|
+
url: 'https://fonts.googleapis.com/css2?family=Playfair+Display:wght@400;500&display=swap',
|
|
2469
|
+
},
|
|
2470
|
+
// system fallbacks (no URL needed)
|
|
2471
|
+
system: { family: 'system-ui, sans-serif' },
|
|
2472
|
+
mono: { family: "'Courier New', monospace" },
|
|
2473
|
+
serif: { family: 'Georgia, serif' },
|
|
2474
|
+
};
|
|
2475
|
+
// default — what renders when no font is specified
|
|
2476
|
+
const DEFAULT_FONT = 'system-ui, sans-serif';
|
|
2477
|
+
// resolve a short name or pass-through a quoted CSS family
|
|
2478
|
+
function resolveFont(nameOrFamily) {
|
|
2479
|
+
const key = nameOrFamily.toLowerCase().trim();
|
|
2480
|
+
if (BUILTIN_FONTS[key])
|
|
2481
|
+
return BUILTIN_FONTS[key].family;
|
|
2482
|
+
return nameOrFamily; // treat as raw CSS font-family
|
|
2483
|
+
}
|
|
2484
|
+
// inject a <link> into <head> for a built-in font (browser only)
|
|
2485
|
+
function loadFont(name) {
|
|
2486
|
+
if (typeof document === 'undefined')
|
|
2487
|
+
return;
|
|
2488
|
+
const key = name.toLowerCase().trim();
|
|
2489
|
+
const def = BUILTIN_FONTS[key];
|
|
2490
|
+
if (!def?.url || def.loaded)
|
|
2491
|
+
return;
|
|
2492
|
+
if (document.querySelector(`link[data-sketchmark-font="${key}"]`))
|
|
2493
|
+
return;
|
|
2494
|
+
const link = document.createElement('link');
|
|
2495
|
+
link.rel = 'stylesheet';
|
|
2496
|
+
link.href = def.url;
|
|
2497
|
+
link.setAttribute('data-sketchmark-font', key);
|
|
2498
|
+
document.head.appendChild(link);
|
|
2499
|
+
def.loaded = true;
|
|
2500
|
+
}
|
|
2501
|
+
// user registers their own font (already loaded via CSS/link)
|
|
2502
|
+
function registerFont(name, family, url) {
|
|
2503
|
+
BUILTIN_FONTS[name.toLowerCase()] = { family, url };
|
|
2504
|
+
if (url)
|
|
2505
|
+
loadFont(name);
|
|
2506
|
+
}
|
|
2507
|
+
|
|
2415
2508
|
// ============================================================
|
|
2416
2509
|
// sketchmark — SVG Renderer (rough.js hand-drawn)
|
|
2417
2510
|
// ============================================================
|
|
@@ -2424,17 +2517,73 @@ var AIDiagram = (function (exports) {
|
|
|
2424
2517
|
return h;
|
|
2425
2518
|
}
|
|
2426
2519
|
const BASE_ROUGH = { roughness: 1.3, bowing: 0.7 };
|
|
2427
|
-
// ──
|
|
2428
|
-
function
|
|
2429
|
-
|
|
2520
|
+
// ── Small helper: load + resolve font from style or fall back ─────────────
|
|
2521
|
+
function resolveStyleFont$1(style, fallback) {
|
|
2522
|
+
const raw = String(style["font"] ?? "");
|
|
2523
|
+
if (!raw)
|
|
2524
|
+
return fallback;
|
|
2525
|
+
loadFont(raw);
|
|
2526
|
+
return resolveFont(raw);
|
|
2527
|
+
}
|
|
2528
|
+
// ── SVG text helpers ──────────────────────────────────────────────────────
|
|
2529
|
+
/**
|
|
2530
|
+
* Single-line SVG text element.
|
|
2531
|
+
*
|
|
2532
|
+
* | param | maps to SVG attr |
|
|
2533
|
+
* |---------------|--------------------------|
|
|
2534
|
+
* txt | textContent |
|
|
2535
|
+
* x, y | x, y |
|
|
2536
|
+
* sz | font-size |
|
|
2537
|
+
* wt | font-weight |
|
|
2538
|
+
* col | fill |
|
|
2539
|
+
* anchor | text-anchor |
|
|
2540
|
+
* font | font-family |
|
|
2541
|
+
* letterSpacing | letter-spacing |
|
|
2542
|
+
*/
|
|
2543
|
+
function mkText(txt, x, y, sz = 14, wt = 500, col = "#1a1208", anchor = "middle", font, letterSpacing) {
|
|
2544
|
+
const t = se("text");
|
|
2545
|
+
t.setAttribute("x", String(x));
|
|
2546
|
+
t.setAttribute("y", String(y));
|
|
2547
|
+
t.setAttribute("text-anchor", anchor);
|
|
2548
|
+
t.setAttribute("dominant-baseline", "middle");
|
|
2549
|
+
t.setAttribute("font-family", font ?? "var(--font-sans, system-ui, sans-serif)");
|
|
2550
|
+
t.setAttribute("font-size", String(sz));
|
|
2551
|
+
t.setAttribute("font-weight", String(wt));
|
|
2552
|
+
t.setAttribute("fill", col);
|
|
2553
|
+
t.setAttribute("pointer-events", "none");
|
|
2554
|
+
t.setAttribute("user-select", "none");
|
|
2555
|
+
if (letterSpacing != null)
|
|
2556
|
+
t.setAttribute("letter-spacing", String(letterSpacing));
|
|
2557
|
+
t.textContent = txt;
|
|
2558
|
+
return t;
|
|
2559
|
+
}
|
|
2560
|
+
/**
|
|
2561
|
+
* Multi-line SVG text element using <tspan> per line.
|
|
2562
|
+
*
|
|
2563
|
+
* | param | maps to SVG attr |
|
|
2564
|
+
* |---------------|--------------------------|
|
|
2565
|
+
* lines | one <tspan> each |
|
|
2566
|
+
* x | tspan x |
|
|
2567
|
+
* cy | vertical centre of block |
|
|
2568
|
+
* sz | font-size |
|
|
2569
|
+
* wt | font-weight |
|
|
2570
|
+
* col | fill |
|
|
2571
|
+
* anchor | text-anchor |
|
|
2572
|
+
* lineH | dy between tspans (px) |
|
|
2573
|
+
* font | font-family |
|
|
2574
|
+
* letterSpacing | letter-spacing |
|
|
2575
|
+
*/
|
|
2576
|
+
function mkMultilineText(lines, x, cy, sz = 14, wt = 500, col = "#1a1208", anchor = "middle", lineH = 18, font, letterSpacing) {
|
|
2430
2577
|
const t = se("text");
|
|
2431
2578
|
t.setAttribute("text-anchor", anchor);
|
|
2432
|
-
t.setAttribute("font-family", "var(--font-sans, system-ui, sans-serif)");
|
|
2579
|
+
t.setAttribute("font-family", font ?? "var(--font-sans, system-ui, sans-serif)");
|
|
2433
2580
|
t.setAttribute("font-size", String(sz));
|
|
2434
2581
|
t.setAttribute("font-weight", String(wt));
|
|
2435
2582
|
t.setAttribute("fill", col);
|
|
2436
2583
|
t.setAttribute("pointer-events", "none");
|
|
2437
2584
|
t.setAttribute("user-select", "none");
|
|
2585
|
+
if (letterSpacing != null)
|
|
2586
|
+
t.setAttribute("letter-spacing", String(letterSpacing));
|
|
2438
2587
|
// vertically centre the whole block
|
|
2439
2588
|
const totalH = (lines.length - 1) * lineH;
|
|
2440
2589
|
const startY = cy - totalH / 2;
|
|
@@ -2448,21 +2597,6 @@ var AIDiagram = (function (exports) {
|
|
|
2448
2597
|
});
|
|
2449
2598
|
return t;
|
|
2450
2599
|
}
|
|
2451
|
-
function mkText(txt, x, y, sz = 14, wt = 500, col = "#1a1208", anchor = "middle") {
|
|
2452
|
-
const t = se("text");
|
|
2453
|
-
t.setAttribute("x", String(x));
|
|
2454
|
-
t.setAttribute("y", String(y));
|
|
2455
|
-
t.setAttribute("text-anchor", anchor);
|
|
2456
|
-
t.setAttribute("dominant-baseline", "middle");
|
|
2457
|
-
t.setAttribute("font-family", "var(--font-sans, system-ui, sans-serif)");
|
|
2458
|
-
t.setAttribute("font-size", String(sz));
|
|
2459
|
-
t.setAttribute("font-weight", String(wt));
|
|
2460
|
-
t.setAttribute("fill", col);
|
|
2461
|
-
t.setAttribute("pointer-events", "none");
|
|
2462
|
-
t.setAttribute("user-select", "none");
|
|
2463
|
-
t.textContent = txt;
|
|
2464
|
-
return t;
|
|
2465
|
-
}
|
|
2466
2600
|
function mkGroup(id, cls) {
|
|
2467
2601
|
const g = se("g");
|
|
2468
2602
|
if (id)
|
|
@@ -2471,7 +2605,7 @@ var AIDiagram = (function (exports) {
|
|
|
2471
2605
|
g.setAttribute("class", cls);
|
|
2472
2606
|
return g;
|
|
2473
2607
|
}
|
|
2474
|
-
// ── Arrow direction from connector
|
|
2608
|
+
// ── Arrow direction from connector ────────────────────────────────────────
|
|
2475
2609
|
function connMeta$1(connector) {
|
|
2476
2610
|
if (connector === "--")
|
|
2477
2611
|
return { arrowAt: "none", dashed: false };
|
|
@@ -2486,7 +2620,7 @@ var AIDiagram = (function (exports) {
|
|
|
2486
2620
|
return { arrowAt: "start", dashed };
|
|
2487
2621
|
return { arrowAt: "end", dashed };
|
|
2488
2622
|
}
|
|
2489
|
-
// ── Generic rect connection point
|
|
2623
|
+
// ── Generic rect connection point ─────────────────────────────────────────
|
|
2490
2624
|
function rectConnPoint$1(rx, ry, rw, rh, ox, oy) {
|
|
2491
2625
|
const cx = rx + rw / 2, cy = ry + rh / 2;
|
|
2492
2626
|
const dx = ox - cx, dy = oy - cy;
|
|
@@ -2511,7 +2645,7 @@ var AIDiagram = (function (exports) {
|
|
|
2511
2645
|
}
|
|
2512
2646
|
return rectConnPoint$1(src.x, src.y, src.w, src.h, dstCX, dstCY);
|
|
2513
2647
|
}
|
|
2514
|
-
// ── Group depth (for paint order)
|
|
2648
|
+
// ── Group depth (for paint order) ─────────────────────────────────────────
|
|
2515
2649
|
function groupDepth$1(g, gm) {
|
|
2516
2650
|
let d = 0;
|
|
2517
2651
|
let cur = g;
|
|
@@ -2521,7 +2655,7 @@ var AIDiagram = (function (exports) {
|
|
|
2521
2655
|
}
|
|
2522
2656
|
return d;
|
|
2523
2657
|
}
|
|
2524
|
-
// ── Node shapes
|
|
2658
|
+
// ── Node shapes ───────────────────────────────────────────────────────────
|
|
2525
2659
|
function renderShape$1(rc, n, palette) {
|
|
2526
2660
|
const s = n.style ?? {};
|
|
2527
2661
|
const fill = String(s.fill ?? palette.nodeFill);
|
|
@@ -2594,19 +2728,18 @@ var AIDiagram = (function (exports) {
|
|
|
2594
2728
|
return [];
|
|
2595
2729
|
case "image": {
|
|
2596
2730
|
if (n.imageUrl) {
|
|
2597
|
-
const img = document.createElementNS(
|
|
2731
|
+
const img = document.createElementNS(NS, "image");
|
|
2598
2732
|
img.setAttribute("href", n.imageUrl);
|
|
2599
2733
|
img.setAttribute("x", String(n.x + 1));
|
|
2600
2734
|
img.setAttribute("y", String(n.y + 1));
|
|
2601
2735
|
img.setAttribute("width", String(n.w - 2));
|
|
2602
2736
|
img.setAttribute("height", String(n.h - 2));
|
|
2603
2737
|
img.setAttribute("preserveAspectRatio", "xMidYMid meet");
|
|
2604
|
-
// optional: clip to rounded rect
|
|
2605
2738
|
const clipId = `clip-${n.id}`;
|
|
2606
|
-
const defs = document.createElementNS(
|
|
2607
|
-
const clip = document.createElementNS(
|
|
2739
|
+
const defs = document.createElementNS(NS, "defs");
|
|
2740
|
+
const clip = document.createElementNS(NS, "clipPath");
|
|
2608
2741
|
clip.setAttribute("id", clipId);
|
|
2609
|
-
const rect = document.createElementNS(
|
|
2742
|
+
const rect = document.createElementNS(NS, "rect");
|
|
2610
2743
|
rect.setAttribute("x", String(n.x + 1));
|
|
2611
2744
|
rect.setAttribute("y", String(n.y + 1));
|
|
2612
2745
|
rect.setAttribute("width", String(n.w - 2));
|
|
@@ -2615,15 +2748,12 @@ var AIDiagram = (function (exports) {
|
|
|
2615
2748
|
clip.appendChild(rect);
|
|
2616
2749
|
defs.appendChild(clip);
|
|
2617
2750
|
img.setAttribute("clip-path", `url(#${clipId})`);
|
|
2618
|
-
// border box drawn on top
|
|
2619
2751
|
const border = rc.rectangle(n.x + 1, n.y + 1, n.w - 2, n.h - 2, {
|
|
2620
2752
|
...opts,
|
|
2621
2753
|
fill: "none",
|
|
2622
|
-
fillStyle: "solid",
|
|
2623
2754
|
});
|
|
2624
2755
|
return [defs, img, border];
|
|
2625
2756
|
}
|
|
2626
|
-
// fallback: no URL → grey placeholder box
|
|
2627
2757
|
return [
|
|
2628
2758
|
rc.rectangle(n.x + 1, n.y + 1, n.w - 2, n.h - 2, {
|
|
2629
2759
|
...opts,
|
|
@@ -2636,7 +2766,7 @@ var AIDiagram = (function (exports) {
|
|
|
2636
2766
|
return [rc.rectangle(n.x + 1, n.y + 1, n.w - 2, n.h - 2, opts)];
|
|
2637
2767
|
}
|
|
2638
2768
|
}
|
|
2639
|
-
// ── Arrowhead
|
|
2769
|
+
// ── Arrowhead ─────────────────────────────────────────────────────────────
|
|
2640
2770
|
function arrowHead(rc, x, y, angle, col, seed) {
|
|
2641
2771
|
const as = 12;
|
|
2642
2772
|
return rc.polygon([
|
|
@@ -2660,14 +2790,22 @@ var AIDiagram = (function (exports) {
|
|
|
2660
2790
|
}
|
|
2661
2791
|
function renderToSVG(sg, container, options = {}) {
|
|
2662
2792
|
if (typeof rough === "undefined") {
|
|
2663
|
-
throw new Error(
|
|
2793
|
+
throw new Error("rough.js is not loaded.");
|
|
2664
2794
|
}
|
|
2665
2795
|
const isDark = options.theme === "dark" ||
|
|
2666
2796
|
(options.theme === "auto" &&
|
|
2667
2797
|
window.matchMedia?.("(prefers-color-scheme:dark)").matches);
|
|
2668
|
-
// Resolve palette: DSL config takes priority, then options.theme, then light
|
|
2669
2798
|
const themeName = String(sg.config[THEME_CONFIG_KEY] ?? (isDark ? "dark" : "light"));
|
|
2670
2799
|
const palette = resolvePalette(themeName);
|
|
2800
|
+
// ── Diagram-level font ──────────────────────────────────
|
|
2801
|
+
const diagramFont = (() => {
|
|
2802
|
+
const raw = String(sg.config["font"] ?? "");
|
|
2803
|
+
if (raw) {
|
|
2804
|
+
loadFont(raw);
|
|
2805
|
+
return resolveFont(raw);
|
|
2806
|
+
}
|
|
2807
|
+
return DEFAULT_FONT;
|
|
2808
|
+
})();
|
|
2671
2809
|
BASE_ROUGH.roughness = options.roughness ?? 1.3;
|
|
2672
2810
|
BASE_ROUGH.bowing = options.bowing ?? 0.7;
|
|
2673
2811
|
let svg;
|
|
@@ -2684,14 +2822,7 @@ var AIDiagram = (function (exports) {
|
|
|
2684
2822
|
svg.setAttribute("height", String(sg.height));
|
|
2685
2823
|
svg.setAttribute("viewBox", `0 0 ${sg.width} ${sg.height}`);
|
|
2686
2824
|
svg.style.fontFamily = "var(--font-sans, system-ui, sans-serif)";
|
|
2687
|
-
// Background
|
|
2688
|
-
// const bgRect = se("rect") as SVGRectElement;
|
|
2689
|
-
// bgRect.setAttribute("x", "0");
|
|
2690
|
-
// bgRect.setAttribute("y", "0");
|
|
2691
|
-
// bgRect.setAttribute("width", String(sg.width));
|
|
2692
|
-
// bgRect.setAttribute("height", String(sg.height));
|
|
2693
|
-
// bgRect.setAttribute("fill", palette.background);
|
|
2694
|
-
// svg.appendChild(bgRect);
|
|
2825
|
+
// ── Background ─────────────────────────────────────────
|
|
2695
2826
|
if (!options.transparent) {
|
|
2696
2827
|
const bgRect = se("rect");
|
|
2697
2828
|
bgRect.setAttribute("x", "0");
|
|
@@ -2707,9 +2838,9 @@ var AIDiagram = (function (exports) {
|
|
|
2707
2838
|
const titleColor = String(sg.config["title-color"] ?? palette.titleText);
|
|
2708
2839
|
const titleSize = Number(sg.config["title-size"] ?? 18);
|
|
2709
2840
|
const titleWeight = Number(sg.config["title-weight"] ?? 600);
|
|
2710
|
-
svg.appendChild(mkText(sg.title, sg.width / 2, 26, titleSize, titleWeight, titleColor));
|
|
2841
|
+
svg.appendChild(mkText(sg.title, sg.width / 2, 26, titleSize, titleWeight, titleColor, "middle", diagramFont));
|
|
2711
2842
|
}
|
|
2712
|
-
// ── Groups
|
|
2843
|
+
// ── Groups ───────────────────────────────────────────────
|
|
2713
2844
|
const gmMap = new Map(sg.groups.map((g) => [g.id, g]));
|
|
2714
2845
|
const sortedGroups = [...sg.groups].sort((a, b) => groupDepth$1(a, gmMap) - groupDepth$1(b, gmMap));
|
|
2715
2846
|
const GL = mkGroup("grp-layer");
|
|
@@ -2729,8 +2860,14 @@ var AIDiagram = (function (exports) {
|
|
|
2729
2860
|
strokeWidth: Number(gs.strokeWidth ?? 1.2),
|
|
2730
2861
|
strokeLineDash: gs.strokeDash ?? palette.groupDash,
|
|
2731
2862
|
}));
|
|
2732
|
-
|
|
2733
|
-
|
|
2863
|
+
// ── Group label typography ──────────────────────────
|
|
2864
|
+
// supports: font, font-size, letter-spacing
|
|
2865
|
+
// always left-anchored (single line)
|
|
2866
|
+
const gLabelColor = gs.color ? String(gs.color) : palette.groupLabel;
|
|
2867
|
+
const gFontSize = Number(gs.fontSize ?? 12);
|
|
2868
|
+
const gFont = resolveStyleFont$1(gs, diagramFont);
|
|
2869
|
+
const gLetterSpacing = gs.letterSpacing;
|
|
2870
|
+
gg.appendChild(mkText(g.label, g.x + 14, g.y + 14, gFontSize, 500, gLabelColor, "start", gFont, gLetterSpacing));
|
|
2734
2871
|
GL.appendChild(gg);
|
|
2735
2872
|
}
|
|
2736
2873
|
svg.appendChild(GL);
|
|
@@ -2784,7 +2921,13 @@ var AIDiagram = (function (exports) {
|
|
|
2784
2921
|
bg.setAttribute("rx", "3");
|
|
2785
2922
|
bg.setAttribute("opacity", "0.9");
|
|
2786
2923
|
eg.appendChild(bg);
|
|
2787
|
-
|
|
2924
|
+
// ── Edge label typography ───────────────────────
|
|
2925
|
+
// supports: font, font-size, letter-spacing
|
|
2926
|
+
// always center-anchored (single line floating on edge)
|
|
2927
|
+
const eFontSize = Number(e.style?.fontSize ?? 11);
|
|
2928
|
+
const eFont = resolveStyleFont$1(e.style ?? {}, diagramFont);
|
|
2929
|
+
const eLetterSpacing = e.style?.letterSpacing;
|
|
2930
|
+
eg.appendChild(mkText(e.label, mx, my, eFontSize, 400, palette.edgeLabelText, "middle", eFont, eLetterSpacing));
|
|
2788
2931
|
}
|
|
2789
2932
|
EL.appendChild(eg);
|
|
2790
2933
|
}
|
|
@@ -2794,14 +2937,43 @@ var AIDiagram = (function (exports) {
|
|
|
2794
2937
|
for (const n of sg.nodes) {
|
|
2795
2938
|
const ng = mkGroup(`node-${n.id}`, "ng");
|
|
2796
2939
|
renderShape$1(rc, n, palette).forEach((s) => ng.appendChild(s));
|
|
2940
|
+
// ── Node / text typography ─────────────────────────
|
|
2941
|
+
// supports: font, font-size, letter-spacing, text-align, line-height
|
|
2797
2942
|
const fontSize = Number(n.style?.fontSize ?? (n.shape === "text" ? 13 : 14));
|
|
2798
2943
|
const fontWeight = n.style?.fontWeight ?? (n.shape === "text" ? 400 : 500);
|
|
2944
|
+
const textColor = String(n.style?.color ??
|
|
2945
|
+
(n.shape === "text" ? palette.edgeLabelText : palette.nodeText));
|
|
2946
|
+
const nodeFont = resolveStyleFont$1(n.style ?? {}, diagramFont);
|
|
2947
|
+
const textAlign = String(n.style?.textAlign ?? "center");
|
|
2948
|
+
const anchorMap = {
|
|
2949
|
+
left: "start",
|
|
2950
|
+
center: "middle",
|
|
2951
|
+
right: "end",
|
|
2952
|
+
};
|
|
2953
|
+
const textAnchor = anchorMap[textAlign] ?? "middle";
|
|
2954
|
+
// line-height is a multiplier (e.g. 1.4 = 140% of font-size)
|
|
2955
|
+
const lineHeight = Number(n.style?.lineHeight ?? 1.3) * fontSize;
|
|
2956
|
+
const letterSpacing = n.style?.letterSpacing;
|
|
2957
|
+
// x shifts for left / right alignment
|
|
2958
|
+
const textX = textAlign === "left"
|
|
2959
|
+
? n.x + 8
|
|
2960
|
+
: textAlign === "right"
|
|
2961
|
+
? n.x + n.w - 8
|
|
2962
|
+
: n.x + n.w / 2;
|
|
2799
2963
|
const lines = n.label.split("\n");
|
|
2964
|
+
const verticalAlign = String(n.style?.verticalAlign ?? "middle");
|
|
2965
|
+
const nodeBodyTop = n.y + 6;
|
|
2966
|
+
const nodeBodyBottom = n.y + n.h - 6;
|
|
2967
|
+
const nodeBodyMid = n.y + n.h / 2;
|
|
2968
|
+
const blockH = (lines.length - 1) * lineHeight;
|
|
2969
|
+
const textCY = verticalAlign === "top"
|
|
2970
|
+
? nodeBodyTop + blockH / 2
|
|
2971
|
+
: verticalAlign === "bottom"
|
|
2972
|
+
? nodeBodyBottom - blockH / 2
|
|
2973
|
+
: nodeBodyMid;
|
|
2800
2974
|
ng.appendChild(lines.length > 1
|
|
2801
|
-
? mkMultilineText(lines,
|
|
2802
|
-
|
|
2803
|
-
: mkText(n.label, n.x + n.w / 2, n.y + n.h / 2, fontSize, fontWeight, String(n.style?.color ??
|
|
2804
|
-
(n.shape === "text" ? palette.edgeLabelText : palette.nodeText))));
|
|
2975
|
+
? mkMultilineText(lines, textX, textCY, fontSize, fontWeight, textColor, textAnchor, lineHeight, nodeFont, letterSpacing)
|
|
2976
|
+
: mkText(n.label, textX, textCY, fontSize, fontWeight, textColor, textAnchor, nodeFont, letterSpacing));
|
|
2805
2977
|
if (options.interactive) {
|
|
2806
2978
|
ng.style.cursor = "pointer";
|
|
2807
2979
|
ng.addEventListener("click", () => options.onNodeClick?.(n.id));
|
|
@@ -2827,7 +2999,12 @@ var AIDiagram = (function (exports) {
|
|
|
2827
2999
|
const hdrText = String(gs.color ?? palette.tableHeaderText);
|
|
2828
3000
|
const divCol = palette.tableDivider;
|
|
2829
3001
|
const pad = t.labelH;
|
|
2830
|
-
//
|
|
3002
|
+
// ── Table-level font (applies to label + all cells) ─
|
|
3003
|
+
// supports: font, font-size, letter-spacing
|
|
3004
|
+
const tFontSize = Number(gs.fontSize ?? 12);
|
|
3005
|
+
const tFont = resolveStyleFont$1(gs, diagramFont);
|
|
3006
|
+
const tLetterSpacing = gs.letterSpacing;
|
|
3007
|
+
// outer border
|
|
2831
3008
|
tg.appendChild(rc.rectangle(t.x, t.y, t.w, t.h, {
|
|
2832
3009
|
...BASE_ROUGH,
|
|
2833
3010
|
seed: hashStr$3(t.id),
|
|
@@ -2836,20 +3013,19 @@ var AIDiagram = (function (exports) {
|
|
|
2836
3013
|
stroke: strk,
|
|
2837
3014
|
strokeWidth: 1.5,
|
|
2838
3015
|
}));
|
|
2839
|
-
//
|
|
3016
|
+
// label strip separator
|
|
2840
3017
|
tg.appendChild(rc.line(t.x, t.y + pad, t.x + t.w, t.y + pad, {
|
|
2841
3018
|
roughness: 0.6,
|
|
2842
3019
|
seed: hashStr$3(t.id + "l"),
|
|
2843
3020
|
stroke: strk,
|
|
2844
3021
|
strokeWidth: 1,
|
|
2845
3022
|
}));
|
|
2846
|
-
//
|
|
2847
|
-
tg.appendChild(mkText(t.label, t.x + 10, t.y + pad / 2,
|
|
2848
|
-
//
|
|
3023
|
+
// ── Table label: font, font-size, letter-spacing (always left) ──
|
|
3024
|
+
tg.appendChild(mkText(t.label, t.x + 10, t.y + pad / 2, tFontSize, 500, textCol, "start", tFont, tLetterSpacing));
|
|
3025
|
+
// rows
|
|
2849
3026
|
let rowY = t.y + pad;
|
|
2850
3027
|
for (const row of t.rows) {
|
|
2851
3028
|
const rh = row.kind === "header" ? t.headerH : t.rowH;
|
|
2852
|
-
// Header background fill
|
|
2853
3029
|
if (row.kind === "header") {
|
|
2854
3030
|
const hdrBg = se("rect");
|
|
2855
3031
|
hdrBg.setAttribute("x", String(t.x + 1));
|
|
@@ -2859,19 +3035,34 @@ var AIDiagram = (function (exports) {
|
|
|
2859
3035
|
hdrBg.setAttribute("fill", hdrFill);
|
|
2860
3036
|
tg.appendChild(hdrBg);
|
|
2861
3037
|
}
|
|
2862
|
-
// Row separator
|
|
2863
3038
|
tg.appendChild(rc.line(t.x, rowY + rh, t.x + t.w, rowY + rh, {
|
|
2864
3039
|
roughness: 0.4,
|
|
2865
3040
|
seed: hashStr$3(t.id + rowY),
|
|
2866
3041
|
stroke: row.kind === "header" ? strk : divCol,
|
|
2867
3042
|
strokeWidth: row.kind === "header" ? 1.2 : 0.6,
|
|
2868
3043
|
}));
|
|
2869
|
-
// Cell text
|
|
3044
|
+
// ── Cell text: font, font-size, letter-spacing, text-align ──
|
|
3045
|
+
// text-align applies to data rows; header is always centered
|
|
3046
|
+
const cellAlignProp = row.kind === "header" ? "center" : String(gs.textAlign ?? "center");
|
|
3047
|
+
const cellAnchorMap = {
|
|
3048
|
+
left: "start",
|
|
3049
|
+
center: "middle",
|
|
3050
|
+
right: "end",
|
|
3051
|
+
};
|
|
3052
|
+
const cellAnchor = cellAnchorMap[cellAlignProp] ?? "middle";
|
|
3053
|
+
const cellFw = row.kind === "header" ? 600 : 400;
|
|
3054
|
+
const cellColor = row.kind === "header" ? hdrText : textCol;
|
|
2870
3055
|
let cx = t.x;
|
|
2871
3056
|
row.cells.forEach((cell, i) => {
|
|
2872
3057
|
const cw = t.colWidths[i] ?? 60;
|
|
2873
|
-
|
|
2874
|
-
|
|
3058
|
+
// x position shifts with alignment
|
|
3059
|
+
const cellX = cellAnchor === "start"
|
|
3060
|
+
? cx + 6
|
|
3061
|
+
: cellAnchor === "end"
|
|
3062
|
+
? cx + cw - 6
|
|
3063
|
+
: cx + cw / 2;
|
|
3064
|
+
// ← was missing tg.appendChild — cells were invisible before
|
|
3065
|
+
tg.appendChild(mkText(cell, cellX, rowY + rh / 2, tFontSize, cellFw, cellColor, cellAnchor, tFont, tLetterSpacing));
|
|
2875
3066
|
if (i < row.cells.length - 1) {
|
|
2876
3067
|
tg.appendChild(rc.line(cx + cw, t.y + pad, cx + cw, t.y + t.h, {
|
|
2877
3068
|
roughness: 0.3,
|
|
@@ -2900,6 +3091,25 @@ var AIDiagram = (function (exports) {
|
|
|
2900
3091
|
const strk = String(gs.stroke ?? palette.noteStroke);
|
|
2901
3092
|
const fold = 14;
|
|
2902
3093
|
const { x, y, w, h } = n;
|
|
3094
|
+
// ── Note typography ─────────────────────────────────
|
|
3095
|
+
// supports: font, font-size, letter-spacing, text-align, line-height
|
|
3096
|
+
const nFontSize = Number(gs.fontSize ?? 12);
|
|
3097
|
+
const nFont = resolveStyleFont$1(gs, diagramFont);
|
|
3098
|
+
const nLetterSpacing = gs.letterSpacing;
|
|
3099
|
+
const nLineHeight = Number(gs.lineHeight ?? 1.4) * nFontSize;
|
|
3100
|
+
const nTextAlign = String(gs.textAlign ?? "left");
|
|
3101
|
+
const nAnchorMap = {
|
|
3102
|
+
left: "start",
|
|
3103
|
+
center: "middle",
|
|
3104
|
+
right: "end",
|
|
3105
|
+
};
|
|
3106
|
+
const nAnchor = nAnchorMap[nTextAlign] ?? "start";
|
|
3107
|
+
// x position for the text block (pad from left, with alignment)
|
|
3108
|
+
const nTextX = nTextAlign === "right"
|
|
3109
|
+
? x + w - fold - 6
|
|
3110
|
+
: nTextAlign === "center"
|
|
3111
|
+
? x + (w - fold) / 2
|
|
3112
|
+
: x + 12;
|
|
2903
3113
|
ng.appendChild(rc.polygon([
|
|
2904
3114
|
[x, y],
|
|
2905
3115
|
[x + w - fold, y],
|
|
@@ -2926,9 +3136,24 @@ var AIDiagram = (function (exports) {
|
|
|
2926
3136
|
stroke: strk,
|
|
2927
3137
|
strokeWidth: 0.8,
|
|
2928
3138
|
}));
|
|
2929
|
-
|
|
2930
|
-
|
|
2931
|
-
|
|
3139
|
+
const nVerticalAlign = String(gs.verticalAlign ?? "top");
|
|
3140
|
+
const bodyTop = y + fold + 8; // below the fold triangle
|
|
3141
|
+
const bodyBottom = y + h - 8; // above bottom edge
|
|
3142
|
+
const bodyMid = (bodyTop + bodyBottom) / 2;
|
|
3143
|
+
const blockH = (n.lines.length - 1) * nLineHeight;
|
|
3144
|
+
const blockCY = nVerticalAlign === "bottom"
|
|
3145
|
+
? bodyBottom - blockH / 2
|
|
3146
|
+
: nVerticalAlign === "middle"
|
|
3147
|
+
? bodyMid
|
|
3148
|
+
: bodyTop + blockH / 2;
|
|
3149
|
+
// multiline: use mkMultilineText so line-height is respected
|
|
3150
|
+
if (n.lines.length > 1) {
|
|
3151
|
+
// vertical centre of the text block inside the note
|
|
3152
|
+
ng.appendChild(mkMultilineText(n.lines, nTextX, blockCY, nFontSize, 400, String(gs.color ?? palette.noteText), nAnchor, nLineHeight, nFont, nLetterSpacing));
|
|
3153
|
+
}
|
|
3154
|
+
else {
|
|
3155
|
+
ng.appendChild(mkText(n.lines[0] ?? "", nTextX, blockCY, nFontSize, 400, String(gs.color ?? palette.noteText), nAnchor, nFont, nLetterSpacing));
|
|
3156
|
+
}
|
|
2932
3157
|
NoteL.appendChild(ng);
|
|
2933
3158
|
}
|
|
2934
3159
|
svg.appendChild(NoteL);
|
|
@@ -3205,22 +3430,69 @@ var AIDiagram = (function (exports) {
|
|
|
3205
3430
|
h = ((h * 33) ^ s.charCodeAt(i)) & 0xffff;
|
|
3206
3431
|
return h;
|
|
3207
3432
|
}
|
|
3208
|
-
// ──
|
|
3433
|
+
// ── Small helper: load + resolve font from a style map ────────────────────
|
|
3434
|
+
function resolveStyleFont(style, fallback) {
|
|
3435
|
+
const raw = String(style['font'] ?? '');
|
|
3436
|
+
if (!raw)
|
|
3437
|
+
return fallback;
|
|
3438
|
+
loadFont(raw);
|
|
3439
|
+
return resolveFont(raw);
|
|
3440
|
+
}
|
|
3441
|
+
// ── Canvas text helpers ────────────────────────────────────────────────────
|
|
3442
|
+
/**
|
|
3443
|
+
* Draw a single line of text.
|
|
3444
|
+
* align: 'left' | 'center' | 'right' (maps to ctx.textAlign)
|
|
3445
|
+
*/
|
|
3446
|
+
function drawText(ctx, txt, x, y, sz = 14, wt = 500, col = '#1a1208', align = 'center', font = 'system-ui, sans-serif', letterSpacing) {
|
|
3447
|
+
ctx.save();
|
|
3448
|
+
ctx.font = `${wt} ${sz}px ${font}`;
|
|
3449
|
+
ctx.fillStyle = col;
|
|
3450
|
+
ctx.textAlign = align;
|
|
3451
|
+
ctx.textBaseline = 'middle';
|
|
3452
|
+
if (letterSpacing) {
|
|
3453
|
+
// Canvas has no native letter-spacing — draw char by char
|
|
3454
|
+
const chars = txt.split('');
|
|
3455
|
+
const totalW = ctx.measureText(txt).width + letterSpacing * (chars.length - 1);
|
|
3456
|
+
let startX = align === 'center' ? x - totalW / 2
|
|
3457
|
+
: align === 'right' ? x - totalW
|
|
3458
|
+
: x;
|
|
3459
|
+
ctx.textAlign = 'left';
|
|
3460
|
+
for (const ch of chars) {
|
|
3461
|
+
ctx.fillText(ch, startX, y);
|
|
3462
|
+
startX += ctx.measureText(ch).width + letterSpacing;
|
|
3463
|
+
}
|
|
3464
|
+
}
|
|
3465
|
+
else {
|
|
3466
|
+
ctx.fillText(txt, x, y);
|
|
3467
|
+
}
|
|
3468
|
+
ctx.restore();
|
|
3469
|
+
}
|
|
3470
|
+
/**
|
|
3471
|
+
* Draw multiple lines of text, vertically centred around cy.
|
|
3472
|
+
*/
|
|
3473
|
+
function drawMultilineText(ctx, lines, x, cy, sz = 14, wt = 500, col = '#1a1208', align = 'center', lineH = 18, font = 'system-ui, sans-serif', letterSpacing) {
|
|
3474
|
+
const totalH = (lines.length - 1) * lineH;
|
|
3475
|
+
const startY = cy - totalH / 2;
|
|
3476
|
+
lines.forEach((line, i) => {
|
|
3477
|
+
drawText(ctx, line, x, startY + i * lineH, sz, wt, col, align, font, letterSpacing);
|
|
3478
|
+
});
|
|
3479
|
+
}
|
|
3480
|
+
// ── Arrow direction ────────────────────────────────────────────────────────
|
|
3209
3481
|
function connMeta(connector) {
|
|
3210
|
-
if (connector ===
|
|
3211
|
-
return { arrowAt:
|
|
3212
|
-
if (connector ===
|
|
3213
|
-
return { arrowAt:
|
|
3214
|
-
const bidir = connector.includes(
|
|
3482
|
+
if (connector === '--')
|
|
3483
|
+
return { arrowAt: 'none', dashed: false };
|
|
3484
|
+
if (connector === '---')
|
|
3485
|
+
return { arrowAt: 'none', dashed: true };
|
|
3486
|
+
const bidir = connector.includes('<') && connector.includes('>');
|
|
3215
3487
|
if (bidir)
|
|
3216
|
-
return { arrowAt:
|
|
3217
|
-
const back = connector.startsWith(
|
|
3218
|
-
const dashed = connector.includes(
|
|
3488
|
+
return { arrowAt: 'both', dashed: connector.includes('--') };
|
|
3489
|
+
const back = connector.startsWith('<');
|
|
3490
|
+
const dashed = connector.includes('--');
|
|
3219
3491
|
if (back)
|
|
3220
|
-
return { arrowAt:
|
|
3221
|
-
return { arrowAt:
|
|
3492
|
+
return { arrowAt: 'start', dashed };
|
|
3493
|
+
return { arrowAt: 'end', dashed };
|
|
3222
3494
|
}
|
|
3223
|
-
// ──
|
|
3495
|
+
// ── Rect connection point ──────────────────────────────────────────────────
|
|
3224
3496
|
function rectConnPoint(rx, ry, rw, rh, ox, oy) {
|
|
3225
3497
|
const cx = rx + rw / 2, cy = ry + rh / 2;
|
|
3226
3498
|
const dx = ox - cx, dy = oy - cy;
|
|
@@ -3233,19 +3505,16 @@ var AIDiagram = (function (exports) {
|
|
|
3233
3505
|
return [cx + t * dx, cy + t * dy];
|
|
3234
3506
|
}
|
|
3235
3507
|
function resolveEndpoint(id, nm, tm, gm, cm, ntm) {
|
|
3236
|
-
return
|
|
3508
|
+
return nm.get(id) ?? tm.get(id) ?? gm.get(id) ?? cm.get(id) ?? ntm.get(id) ?? null;
|
|
3237
3509
|
}
|
|
3238
3510
|
function getConnPoint(src, dstCX, dstCY) {
|
|
3239
|
-
if (
|
|
3511
|
+
if ('shape' in src && src.shape) {
|
|
3240
3512
|
return connPoint(src, {
|
|
3241
|
-
x: dstCX - 1,
|
|
3242
|
-
y: dstCY - 1,
|
|
3243
|
-
w: 2,
|
|
3244
|
-
h: 2});
|
|
3513
|
+
x: dstCX - 1, y: dstCY - 1, w: 2, h: 2});
|
|
3245
3514
|
}
|
|
3246
3515
|
return rectConnPoint(src.x, src.y, src.w, src.h, dstCX, dstCY);
|
|
3247
3516
|
}
|
|
3248
|
-
// ── Group depth
|
|
3517
|
+
// ── Group depth ────────────────────────────────────────────────────────────
|
|
3249
3518
|
function groupDepth(g, gm) {
|
|
3250
3519
|
let d = 0;
|
|
3251
3520
|
let cur = g;
|
|
@@ -3255,80 +3524,57 @@ var AIDiagram = (function (exports) {
|
|
|
3255
3524
|
}
|
|
3256
3525
|
return d;
|
|
3257
3526
|
}
|
|
3258
|
-
// ── Node shapes
|
|
3527
|
+
// ── Node shapes ────────────────────────────────────────────────────────────
|
|
3259
3528
|
function renderShape(rc, ctx, n, palette, R) {
|
|
3260
3529
|
const s = n.style ?? {};
|
|
3261
3530
|
const fill = String(s.fill ?? palette.nodeFill);
|
|
3262
3531
|
const stroke = String(s.stroke ?? palette.nodeStroke);
|
|
3263
3532
|
const opts = {
|
|
3264
|
-
...R,
|
|
3265
|
-
|
|
3266
|
-
|
|
3267
|
-
fillStyle: "solid",
|
|
3268
|
-
stroke,
|
|
3269
|
-
strokeWidth: Number(s.strokeWidth ?? 1.9),
|
|
3533
|
+
...R, seed: hashStr$1(n.id),
|
|
3534
|
+
fill, fillStyle: 'solid',
|
|
3535
|
+
stroke, strokeWidth: Number(s.strokeWidth ?? 1.9),
|
|
3270
3536
|
};
|
|
3271
3537
|
const cx = n.x + n.w / 2, cy = n.y + n.h / 2;
|
|
3272
3538
|
const hw = n.w / 2 - 2;
|
|
3273
3539
|
switch (n.shape) {
|
|
3274
|
-
case
|
|
3540
|
+
case 'circle':
|
|
3275
3541
|
rc.ellipse(cx, cy, n.w * 0.88, n.h * 0.88, opts);
|
|
3276
3542
|
break;
|
|
3277
|
-
case
|
|
3278
|
-
rc.polygon([
|
|
3279
|
-
[cx, n.y + 2],
|
|
3280
|
-
[cx + hw, cy],
|
|
3281
|
-
[cx, n.y + n.h - 2],
|
|
3282
|
-
[cx - hw, cy],
|
|
3283
|
-
], opts);
|
|
3543
|
+
case 'diamond':
|
|
3544
|
+
rc.polygon([[cx, n.y + 2], [cx + hw, cy], [cx, n.y + n.h - 2], [cx - hw, cy]], opts);
|
|
3284
3545
|
break;
|
|
3285
|
-
case
|
|
3546
|
+
case 'hexagon': {
|
|
3286
3547
|
const hw2 = hw * 0.56;
|
|
3287
3548
|
rc.polygon([
|
|
3288
|
-
[cx - hw2, n.y + 3],
|
|
3289
|
-
[cx + hw2, n.y + 3],
|
|
3290
|
-
[cx + hw, cy],
|
|
3291
|
-
[cx + hw2, n.y + n.h - 3],
|
|
3292
|
-
[cx - hw2, n.y + n.h - 3],
|
|
3293
|
-
[cx - hw, cy],
|
|
3549
|
+
[cx - hw2, n.y + 3], [cx + hw2, n.y + 3], [cx + hw, cy],
|
|
3550
|
+
[cx + hw2, n.y + n.h - 3], [cx - hw2, n.y + n.h - 3], [cx - hw, cy],
|
|
3294
3551
|
], opts);
|
|
3295
3552
|
break;
|
|
3296
3553
|
}
|
|
3297
|
-
case
|
|
3298
|
-
rc.polygon([
|
|
3299
|
-
[cx, n.y + 3],
|
|
3300
|
-
[n.x + n.w - 3, n.y + n.h - 3],
|
|
3301
|
-
[n.x + 3, n.y + n.h - 3],
|
|
3302
|
-
], opts);
|
|
3554
|
+
case 'triangle':
|
|
3555
|
+
rc.polygon([[cx, n.y + 3], [n.x + n.w - 3, n.y + n.h - 3], [n.x + 3, n.y + n.h - 3]], opts);
|
|
3303
3556
|
break;
|
|
3304
|
-
case
|
|
3557
|
+
case 'cylinder': {
|
|
3305
3558
|
const eH = 18;
|
|
3306
3559
|
rc.rectangle(n.x + 3, n.y + eH / 2, n.w - 6, n.h - eH, opts);
|
|
3307
3560
|
rc.ellipse(cx, n.y + eH / 2, n.w - 8, eH, { ...opts, roughness: 0.6 });
|
|
3308
|
-
rc.ellipse(cx, n.y + n.h - eH / 2, n.w - 8, eH, {
|
|
3309
|
-
...opts,
|
|
3310
|
-
roughness: 0.6,
|
|
3311
|
-
fill: "none",
|
|
3312
|
-
});
|
|
3561
|
+
rc.ellipse(cx, n.y + n.h - eH / 2, n.w - 8, eH, { ...opts, roughness: 0.6, fill: 'none' });
|
|
3313
3562
|
break;
|
|
3314
3563
|
}
|
|
3315
|
-
case
|
|
3564
|
+
case 'parallelogram':
|
|
3316
3565
|
rc.polygon([
|
|
3317
|
-
[n.x + 18, n.y + 1],
|
|
3318
|
-
[n.x + n.w - 1, n.y + 1],
|
|
3319
|
-
[n.x + n.w - 18, n.y + n.h - 1],
|
|
3320
|
-
[n.x + 1, n.y + n.h - 1],
|
|
3566
|
+
[n.x + 18, n.y + 1], [n.x + n.w - 1, n.y + 1],
|
|
3567
|
+
[n.x + n.w - 18, n.y + n.h - 1], [n.x + 1, n.y + n.h - 1],
|
|
3321
3568
|
], opts);
|
|
3322
3569
|
break;
|
|
3323
|
-
case
|
|
3324
|
-
break;
|
|
3325
|
-
case
|
|
3570
|
+
case 'text':
|
|
3571
|
+
break;
|
|
3572
|
+
case 'image': {
|
|
3326
3573
|
if (n.imageUrl) {
|
|
3327
3574
|
const img = new Image();
|
|
3328
|
-
img.crossOrigin =
|
|
3575
|
+
img.crossOrigin = 'anonymous';
|
|
3329
3576
|
img.onload = () => {
|
|
3330
3577
|
ctx.save();
|
|
3331
|
-
// rounded clip
|
|
3332
3578
|
ctx.beginPath();
|
|
3333
3579
|
const r = 6;
|
|
3334
3580
|
ctx.moveTo(n.x + r, n.y);
|
|
@@ -3344,21 +3590,12 @@ var AIDiagram = (function (exports) {
|
|
|
3344
3590
|
ctx.clip();
|
|
3345
3591
|
ctx.drawImage(img, n.x + 1, n.y + 1, n.w - 2, n.h - 2);
|
|
3346
3592
|
ctx.restore();
|
|
3347
|
-
|
|
3348
|
-
rc.rectangle(n.x + 1, n.y + 1, n.w - 2, n.h - 2, {
|
|
3349
|
-
...opts,
|
|
3350
|
-
fill: "none",
|
|
3351
|
-
});
|
|
3593
|
+
rc.rectangle(n.x + 1, n.y + 1, n.w - 2, n.h - 2, { ...opts, fill: 'none' });
|
|
3352
3594
|
};
|
|
3353
3595
|
img.src = n.imageUrl;
|
|
3354
3596
|
}
|
|
3355
3597
|
else {
|
|
3356
|
-
|
|
3357
|
-
rc.rectangle(n.x + 1, n.y + 1, n.w - 2, n.h - 2, {
|
|
3358
|
-
...opts,
|
|
3359
|
-
fill: "#e0e0e0",
|
|
3360
|
-
stroke: "#999999",
|
|
3361
|
-
});
|
|
3598
|
+
rc.rectangle(n.x + 1, n.y + 1, n.w - 2, n.h - 2, { ...opts, fill: '#e0e0e0', stroke: '#999999' });
|
|
3362
3599
|
}
|
|
3363
3600
|
return;
|
|
3364
3601
|
}
|
|
@@ -3367,57 +3604,52 @@ var AIDiagram = (function (exports) {
|
|
|
3367
3604
|
break;
|
|
3368
3605
|
}
|
|
3369
3606
|
}
|
|
3370
|
-
// ── Arrowhead
|
|
3607
|
+
// ── Arrowhead ─────────────────────────────────────────────────────────────
|
|
3371
3608
|
function drawArrowHead(rc, x, y, angle, col, seed, R) {
|
|
3372
3609
|
const as = 12;
|
|
3373
3610
|
rc.polygon([
|
|
3374
3611
|
[x, y],
|
|
3375
|
-
[
|
|
3376
|
-
|
|
3377
|
-
|
|
3378
|
-
],
|
|
3379
|
-
[
|
|
3380
|
-
x - as * Math.cos(angle + Math.PI / 6.5),
|
|
3381
|
-
y - as * Math.sin(angle + Math.PI / 6.5),
|
|
3382
|
-
],
|
|
3383
|
-
], {
|
|
3384
|
-
roughness: 0.3,
|
|
3385
|
-
seed,
|
|
3386
|
-
fill: col,
|
|
3387
|
-
fillStyle: "solid",
|
|
3388
|
-
stroke: col,
|
|
3389
|
-
strokeWidth: 0.8,
|
|
3390
|
-
});
|
|
3612
|
+
[x - as * Math.cos(angle - Math.PI / 6.5), y - as * Math.sin(angle - Math.PI / 6.5)],
|
|
3613
|
+
[x - as * Math.cos(angle + Math.PI / 6.5), y - as * Math.sin(angle + Math.PI / 6.5)],
|
|
3614
|
+
], { roughness: 0.3, seed, fill: col, fillStyle: 'solid', stroke: col, strokeWidth: 0.8 });
|
|
3391
3615
|
}
|
|
3616
|
+
// ── Public API ─────────────────────────────────────────────────────────────
|
|
3392
3617
|
function renderToCanvas(sg, canvas, options = {}) {
|
|
3393
|
-
if (typeof rough ===
|
|
3394
|
-
throw new Error(
|
|
3618
|
+
if (typeof rough === 'undefined')
|
|
3619
|
+
throw new Error('rough.js not loaded');
|
|
3395
3620
|
const scale = options.scale ?? window.devicePixelRatio ?? 1;
|
|
3396
3621
|
canvas.width = sg.width * scale;
|
|
3397
3622
|
canvas.height = sg.height * scale;
|
|
3398
|
-
canvas.style.width = sg.width +
|
|
3399
|
-
canvas.style.height = sg.height +
|
|
3400
|
-
const ctx = canvas.getContext(
|
|
3623
|
+
canvas.style.width = sg.width + 'px';
|
|
3624
|
+
canvas.style.height = sg.height + 'px';
|
|
3625
|
+
const ctx = canvas.getContext('2d');
|
|
3401
3626
|
ctx.scale(scale, scale);
|
|
3402
|
-
|
|
3403
|
-
|
|
3404
|
-
|
|
3405
|
-
|
|
3406
|
-
|
|
3407
|
-
|
|
3408
|
-
window.matchMedia?.("(prefers-color-scheme:dark)").matches);
|
|
3409
|
-
const themeName = String(sg.config[THEME_CONFIG_KEY] ?? (isDark ? "dark" : "light"));
|
|
3627
|
+
// ── Palette ──────────────────────────────────────────────
|
|
3628
|
+
const isDark = options.theme === 'dark' ||
|
|
3629
|
+
(options.theme === 'auto' &&
|
|
3630
|
+
typeof window !== 'undefined' &&
|
|
3631
|
+
window.matchMedia?.('(prefers-color-scheme:dark)').matches);
|
|
3632
|
+
const themeName = String(sg.config[THEME_CONFIG_KEY] ?? (isDark ? 'dark' : 'light'));
|
|
3410
3633
|
const palette = resolvePalette(themeName);
|
|
3634
|
+
// ── Diagram-level font ───────────────────────────────────
|
|
3635
|
+
const diagramFont = (() => {
|
|
3636
|
+
const raw = String(sg.config['font'] ?? '');
|
|
3637
|
+
if (raw) {
|
|
3638
|
+
loadFont(raw);
|
|
3639
|
+
return resolveFont(raw);
|
|
3640
|
+
}
|
|
3641
|
+
return DEFAULT_FONT;
|
|
3642
|
+
})();
|
|
3643
|
+
// ── Background ───────────────────────────────────────────
|
|
3411
3644
|
if (!options.transparent) {
|
|
3412
3645
|
ctx.fillStyle = options.background ?? palette.background;
|
|
3413
3646
|
ctx.fillRect(0, 0, sg.width, sg.height);
|
|
3414
3647
|
}
|
|
3648
|
+
else {
|
|
3649
|
+
ctx.clearRect(0, 0, sg.width, sg.height);
|
|
3650
|
+
}
|
|
3415
3651
|
const rc = rough.canvas(canvas);
|
|
3416
|
-
const R = {
|
|
3417
|
-
roughness: options.roughness ?? 1.3,
|
|
3418
|
-
bowing: options.bowing ?? 0.7,
|
|
3419
|
-
};
|
|
3420
|
-
// ── Lookup maps ──────────────────────────────────────────
|
|
3652
|
+
const R = { roughness: options.roughness ?? 1.3, bowing: options.bowing ?? 0.7 };
|
|
3421
3653
|
const nm = nodeMap(sg);
|
|
3422
3654
|
const tm = tableMap(sg);
|
|
3423
3655
|
const gm = groupMap(sg);
|
|
@@ -3425,36 +3657,30 @@ var AIDiagram = (function (exports) {
|
|
|
3425
3657
|
const ntm = noteMap(sg);
|
|
3426
3658
|
// ── Title ────────────────────────────────────────────────
|
|
3427
3659
|
if (sg.title) {
|
|
3428
|
-
|
|
3429
|
-
ctx.
|
|
3430
|
-
ctx.fillStyle = palette.titleText;
|
|
3431
|
-
ctx.textAlign = "center";
|
|
3432
|
-
ctx.fillText(sg.title, sg.width / 2, 28);
|
|
3433
|
-
ctx.restore();
|
|
3660
|
+
const titleSize = Number(sg.config['title-size'] ?? 18);
|
|
3661
|
+
drawText(ctx, sg.title, sg.width / 2, 28, titleSize, 600, palette.titleText, 'center', diagramFont);
|
|
3434
3662
|
}
|
|
3435
|
-
// ── Groups (
|
|
3663
|
+
// ── Groups (outermost first) ─────────────────────────────
|
|
3436
3664
|
const sortedGroups = [...sg.groups].sort((a, b) => groupDepth(a, gm) - groupDepth(b, gm));
|
|
3437
3665
|
for (const g of sortedGroups) {
|
|
3438
3666
|
if (!g.w)
|
|
3439
3667
|
continue;
|
|
3440
3668
|
const gs = g.style ?? {};
|
|
3441
3669
|
rc.rectangle(g.x, g.y, g.w, g.h, {
|
|
3442
|
-
...R,
|
|
3443
|
-
roughness: 1.7,
|
|
3444
|
-
bowing: 0.4,
|
|
3445
|
-
seed: hashStr$1(g.id),
|
|
3670
|
+
...R, roughness: 1.7, bowing: 0.4, seed: hashStr$1(g.id),
|
|
3446
3671
|
fill: String(gs.fill ?? palette.groupFill),
|
|
3447
|
-
fillStyle:
|
|
3672
|
+
fillStyle: 'solid',
|
|
3448
3673
|
stroke: String(gs.stroke ?? palette.groupStroke),
|
|
3449
3674
|
strokeWidth: Number(gs.strokeWidth ?? 1.2),
|
|
3450
3675
|
strokeLineDash: gs.strokeDash ?? palette.groupDash,
|
|
3451
3676
|
});
|
|
3452
|
-
|
|
3453
|
-
|
|
3454
|
-
|
|
3455
|
-
|
|
3456
|
-
|
|
3457
|
-
|
|
3677
|
+
// ── Group label: font, font-size, letter-spacing ─────
|
|
3678
|
+
// always left-anchored (single line)
|
|
3679
|
+
const gFontSize = Number(gs.fontSize ?? 12);
|
|
3680
|
+
const gFont = resolveStyleFont(gs, diagramFont);
|
|
3681
|
+
const gLetterSpacing = gs.letterSpacing;
|
|
3682
|
+
const gLabelColor = gs.color ? String(gs.color) : palette.groupLabel;
|
|
3683
|
+
drawText(ctx, g.label, g.x + 14, g.y + 16, gFontSize, 500, gLabelColor, 'left', gFont, gLetterSpacing);
|
|
3458
3684
|
}
|
|
3459
3685
|
// ── Edges ─────────────────────────────────────────────────
|
|
3460
3686
|
for (const e of sg.edges) {
|
|
@@ -3471,62 +3697,61 @@ var AIDiagram = (function (exports) {
|
|
|
3471
3697
|
const len = Math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2) || 1;
|
|
3472
3698
|
const nx = (x2 - x1) / len, ny = (y2 - y1) / len;
|
|
3473
3699
|
const HEAD = 13;
|
|
3474
|
-
const sx1 = arrowAt ===
|
|
3475
|
-
const sy1 = arrowAt ===
|
|
3476
|
-
const sx2 = arrowAt ===
|
|
3477
|
-
const sy2 = arrowAt ===
|
|
3700
|
+
const sx1 = arrowAt === 'start' || arrowAt === 'both' ? x1 + nx * HEAD : x1;
|
|
3701
|
+
const sy1 = arrowAt === 'start' || arrowAt === 'both' ? y1 + ny * HEAD : y1;
|
|
3702
|
+
const sx2 = arrowAt === 'end' || arrowAt === 'both' ? x2 - nx * HEAD : x2;
|
|
3703
|
+
const sy2 = arrowAt === 'end' || arrowAt === 'both' ? y2 - ny * HEAD : y2;
|
|
3478
3704
|
rc.line(sx1, sy1, sx2, sy2, {
|
|
3479
|
-
...R,
|
|
3480
|
-
roughness: 0.9,
|
|
3481
|
-
seed: hashStr$1(e.from + e.to),
|
|
3705
|
+
...R, roughness: 0.9, seed: hashStr$1(e.from + e.to),
|
|
3482
3706
|
stroke: ecol,
|
|
3483
3707
|
strokeWidth: Number(e.style?.strokeWidth ?? 1.6),
|
|
3484
3708
|
...(dashed ? { strokeLineDash: [6, 5] } : {}),
|
|
3485
3709
|
});
|
|
3486
3710
|
const ang = Math.atan2(y2 - y1, x2 - x1);
|
|
3487
|
-
if (arrowAt ===
|
|
3711
|
+
if (arrowAt === 'end' || arrowAt === 'both')
|
|
3488
3712
|
drawArrowHead(rc, x2, y2, ang, ecol, hashStr$1(e.to));
|
|
3489
|
-
if (arrowAt ===
|
|
3490
|
-
drawArrowHead(rc, x1, y1, Math.atan2(y1 - y2, x1 - x2), ecol, hashStr$1(e.from +
|
|
3713
|
+
if (arrowAt === 'start' || arrowAt === 'both')
|
|
3714
|
+
drawArrowHead(rc, x1, y1, Math.atan2(y1 - y2, x1 - x2), ecol, hashStr$1(e.from + 'back'));
|
|
3491
3715
|
if (e.label) {
|
|
3492
3716
|
const mx = (x1 + x2) / 2 - ny * 14;
|
|
3493
3717
|
const my = (y1 + y2) / 2 + nx * 14;
|
|
3718
|
+
// ── Edge label: font, font-size, letter-spacing ──
|
|
3719
|
+
// always center-anchored (single line)
|
|
3720
|
+
const eFontSize = Number(e.style?.fontSize ?? 11);
|
|
3721
|
+
const eFont = resolveStyleFont(e.style ?? {}, diagramFont);
|
|
3722
|
+
const eLetterSpacing = e.style?.letterSpacing;
|
|
3494
3723
|
ctx.save();
|
|
3495
|
-
ctx.font =
|
|
3496
|
-
ctx.textAlign = "center";
|
|
3724
|
+
ctx.font = `400 ${eFontSize}px ${eFont}`;
|
|
3497
3725
|
const tw = ctx.measureText(e.label).width + 12;
|
|
3726
|
+
ctx.restore();
|
|
3498
3727
|
ctx.fillStyle = palette.edgeLabelBg;
|
|
3499
3728
|
ctx.fillRect(mx - tw / 2, my - 8, tw, 15);
|
|
3500
|
-
ctx.
|
|
3501
|
-
ctx.fillText(e.label, mx, my + 3);
|
|
3502
|
-
ctx.restore();
|
|
3729
|
+
drawText(ctx, e.label, mx, my + 3, eFontSize, 400, palette.edgeLabelText, 'center', eFont, eLetterSpacing);
|
|
3503
3730
|
}
|
|
3504
3731
|
}
|
|
3505
3732
|
// ── Nodes ─────────────────────────────────────────────────
|
|
3506
3733
|
for (const n of sg.nodes) {
|
|
3507
3734
|
renderShape(rc, ctx, n, palette, R);
|
|
3508
|
-
|
|
3509
|
-
|
|
3510
|
-
const
|
|
3511
|
-
const
|
|
3512
|
-
|
|
3513
|
-
|
|
3514
|
-
|
|
3515
|
-
|
|
3516
|
-
|
|
3517
|
-
|
|
3518
|
-
const
|
|
3519
|
-
|
|
3520
|
-
|
|
3735
|
+
// ── Node / text typography ─────────────────────────
|
|
3736
|
+
// supports: font, font-size, letter-spacing, text-align, line-height
|
|
3737
|
+
const fontSize = Number(n.style?.fontSize ?? (n.shape === 'text' ? 13 : 14));
|
|
3738
|
+
const fontWeight = n.style?.fontWeight ?? (n.shape === 'text' ? 400 : 500);
|
|
3739
|
+
const textColor = String(n.style?.color ??
|
|
3740
|
+
(n.shape === 'text' ? palette.edgeLabelText : palette.nodeText));
|
|
3741
|
+
const nodeFont = resolveStyleFont(n.style ?? {}, diagramFont);
|
|
3742
|
+
const textAlign = String(n.style?.textAlign ?? 'center');
|
|
3743
|
+
const lineHeight = Number(n.style?.lineHeight ?? 1.3) * fontSize;
|
|
3744
|
+
const letterSpacing = n.style?.letterSpacing;
|
|
3745
|
+
const textX = textAlign === 'left' ? n.x + 8
|
|
3746
|
+
: textAlign === 'right' ? n.x + n.w - 8
|
|
3747
|
+
: n.x + n.w / 2;
|
|
3748
|
+
const lines = n.label.split('\n');
|
|
3749
|
+
if (lines.length > 1) {
|
|
3750
|
+
drawMultilineText(ctx, lines, textX, n.y + n.h / 2, fontSize, fontWeight, textColor, textAlign, lineHeight, nodeFont, letterSpacing);
|
|
3521
3751
|
}
|
|
3522
3752
|
else {
|
|
3523
|
-
|
|
3524
|
-
const startY = n.y + n.h / 2 - ((lines.length - 1) * lineH) / 2;
|
|
3525
|
-
lines.forEach((line, i) => {
|
|
3526
|
-
ctx.fillText(line, n.x + n.w / 2, startY + i * lineH);
|
|
3527
|
-
});
|
|
3753
|
+
drawText(ctx, n.label, textX, n.y + n.h / 2, fontSize, fontWeight, textColor, textAlign, nodeFont, letterSpacing);
|
|
3528
3754
|
}
|
|
3529
|
-
ctx.restore();
|
|
3530
3755
|
}
|
|
3531
3756
|
// ── Tables ────────────────────────────────────────────────
|
|
3532
3757
|
for (const t of sg.tables) {
|
|
@@ -3535,65 +3760,53 @@ var AIDiagram = (function (exports) {
|
|
|
3535
3760
|
const strk = String(gs.stroke ?? palette.tableStroke);
|
|
3536
3761
|
const textCol = String(gs.color ?? palette.tableText);
|
|
3537
3762
|
const pad = t.labelH;
|
|
3538
|
-
//
|
|
3763
|
+
// ── Table-level font ────────────────────────────────
|
|
3764
|
+
// supports: font, font-size, letter-spacing (cells also support text-align)
|
|
3765
|
+
const tFontSize = Number(gs.fontSize ?? 12);
|
|
3766
|
+
const tFont = resolveStyleFont(gs, diagramFont);
|
|
3767
|
+
const tLetterSpacing = gs.letterSpacing;
|
|
3539
3768
|
rc.rectangle(t.x, t.y, t.w, t.h, {
|
|
3540
|
-
...R,
|
|
3541
|
-
|
|
3542
|
-
fill,
|
|
3543
|
-
fillStyle: "solid",
|
|
3544
|
-
stroke: strk,
|
|
3545
|
-
strokeWidth: 1.5,
|
|
3769
|
+
...R, seed: hashStr$1(t.id),
|
|
3770
|
+
fill, fillStyle: 'solid', stroke: strk, strokeWidth: 1.5,
|
|
3546
3771
|
});
|
|
3547
|
-
// Label strip separator
|
|
3548
3772
|
rc.line(t.x, t.y + pad, t.x + t.w, t.y + pad, {
|
|
3549
|
-
roughness: 0.6,
|
|
3550
|
-
seed: hashStr$1(t.id + "l"),
|
|
3551
|
-
stroke: strk,
|
|
3552
|
-
strokeWidth: 1,
|
|
3773
|
+
roughness: 0.6, seed: hashStr$1(t.id + 'l'), stroke: strk, strokeWidth: 1,
|
|
3553
3774
|
});
|
|
3554
|
-
//
|
|
3555
|
-
|
|
3556
|
-
ctx.
|
|
3557
|
-
ctx.fillStyle = textCol;
|
|
3558
|
-
ctx.textAlign = "left";
|
|
3559
|
-
ctx.textBaseline = "middle";
|
|
3560
|
-
ctx.fillText(t.label, t.x + 10, t.y + pad / 2);
|
|
3561
|
-
ctx.restore();
|
|
3562
|
-
// Rows
|
|
3775
|
+
// ── Table label: font, font-size, letter-spacing ────
|
|
3776
|
+
// always left-anchored
|
|
3777
|
+
drawText(ctx, t.label, t.x + 10, t.y + pad / 2, tFontSize, 500, textCol, 'left', tFont, tLetterSpacing);
|
|
3563
3778
|
let rowY = t.y + pad;
|
|
3564
3779
|
for (const row of t.rows) {
|
|
3565
|
-
const rh = row.kind ===
|
|
3566
|
-
|
|
3567
|
-
if (row.kind === "header") {
|
|
3780
|
+
const rh = row.kind === 'header' ? t.headerH : t.rowH;
|
|
3781
|
+
if (row.kind === 'header') {
|
|
3568
3782
|
ctx.fillStyle = palette.tableHeaderFill;
|
|
3569
3783
|
ctx.fillRect(t.x + 1, rowY + 1, t.w - 2, rh - 1);
|
|
3570
3784
|
}
|
|
3571
|
-
// Row separator
|
|
3572
3785
|
rc.line(t.x, rowY + rh, t.x + t.w, rowY + rh, {
|
|
3573
|
-
roughness: 0.4,
|
|
3574
|
-
|
|
3575
|
-
|
|
3576
|
-
strokeWidth: row.kind === "header" ? 1.2 : 0.6,
|
|
3786
|
+
roughness: 0.4, seed: hashStr$1(t.id + rowY),
|
|
3787
|
+
stroke: row.kind === 'header' ? strk : palette.tableDivider,
|
|
3788
|
+
strokeWidth: row.kind === 'header' ? 1.2 : 0.6,
|
|
3577
3789
|
});
|
|
3578
|
-
// Cell text
|
|
3790
|
+
// ── Cell text: font, font-size, letter-spacing, text-align ──
|
|
3791
|
+
// header always centered; data rows respect gs.textAlign
|
|
3792
|
+
const cellAlignProp = (row.kind === 'header'
|
|
3793
|
+
? 'center'
|
|
3794
|
+
: String(gs.textAlign ?? 'center'));
|
|
3795
|
+
const cellFw = row.kind === 'header' ? 600 : 400;
|
|
3796
|
+
const cellColor = row.kind === 'header'
|
|
3797
|
+
? String(gs.color ?? palette.tableHeaderText)
|
|
3798
|
+
: textCol;
|
|
3579
3799
|
let cx = t.x;
|
|
3580
3800
|
row.cells.forEach((cell, i) => {
|
|
3581
3801
|
const cw = t.colWidths[i] ?? 60;
|
|
3582
|
-
const
|
|
3583
|
-
|
|
3584
|
-
|
|
3585
|
-
ctx
|
|
3586
|
-
row.kind === "header" ? palette.tableHeaderText : textCol;
|
|
3587
|
-
ctx.textAlign = "center";
|
|
3588
|
-
ctx.textBaseline = "middle";
|
|
3589
|
-
ctx.fillText(cell, cx + cw / 2, rowY + rh / 2);
|
|
3590
|
-
ctx.restore();
|
|
3802
|
+
const cellX = cellAlignProp === 'left' ? cx + 6
|
|
3803
|
+
: cellAlignProp === 'right' ? cx + cw - 6
|
|
3804
|
+
: cx + cw / 2;
|
|
3805
|
+
drawText(ctx, cell, cellX, rowY + rh / 2, tFontSize, cellFw, cellColor, cellAlignProp, tFont, tLetterSpacing);
|
|
3591
3806
|
if (i < row.cells.length - 1) {
|
|
3592
3807
|
rc.line(cx + cw, t.y + pad, cx + cw, t.y + t.h, {
|
|
3593
|
-
roughness: 0.3,
|
|
3594
|
-
|
|
3595
|
-
stroke: palette.tableDivider,
|
|
3596
|
-
strokeWidth: 0.5,
|
|
3808
|
+
roughness: 0.3, seed: hashStr$1(t.id + 'c' + i),
|
|
3809
|
+
stroke: palette.tableDivider, strokeWidth: 0.5,
|
|
3597
3810
|
});
|
|
3598
3811
|
}
|
|
3599
3812
|
cx += cw;
|
|
@@ -3608,44 +3821,37 @@ var AIDiagram = (function (exports) {
|
|
|
3608
3821
|
const strk = String(gs.stroke ?? palette.noteStroke);
|
|
3609
3822
|
const fold = 14;
|
|
3610
3823
|
const { x, y, w, h } = n;
|
|
3611
|
-
// Note body (folded corner polygon)
|
|
3612
3824
|
rc.polygon([
|
|
3613
3825
|
[x, y],
|
|
3614
3826
|
[x + w - fold, y],
|
|
3615
3827
|
[x + w, y + fold],
|
|
3616
3828
|
[x + w, y + h],
|
|
3617
3829
|
[x, y + h],
|
|
3618
|
-
], {
|
|
3619
|
-
...R,
|
|
3620
|
-
seed: hashStr$1(n.id),
|
|
3621
|
-
fill,
|
|
3622
|
-
fillStyle: "solid",
|
|
3623
|
-
stroke: strk,
|
|
3624
|
-
strokeWidth: 1.2,
|
|
3625
|
-
});
|
|
3626
|
-
// Folded corner triangle
|
|
3830
|
+
], { ...R, seed: hashStr$1(n.id), fill, fillStyle: 'solid', stroke: strk, strokeWidth: 1.2 });
|
|
3627
3831
|
rc.polygon([
|
|
3628
3832
|
[x + w - fold, y],
|
|
3629
3833
|
[x + w, y + fold],
|
|
3630
3834
|
[x + w - fold, y + fold],
|
|
3631
|
-
], {
|
|
3632
|
-
|
|
3633
|
-
|
|
3634
|
-
|
|
3635
|
-
|
|
3636
|
-
|
|
3637
|
-
|
|
3638
|
-
|
|
3639
|
-
|
|
3640
|
-
|
|
3641
|
-
|
|
3642
|
-
|
|
3643
|
-
|
|
3644
|
-
|
|
3645
|
-
|
|
3646
|
-
ctx.
|
|
3647
|
-
}
|
|
3648
|
-
|
|
3835
|
+
], { roughness: 0.4, seed: hashStr$1(n.id + 'f'),
|
|
3836
|
+
fill: palette.noteFold, fillStyle: 'solid', stroke: strk, strokeWidth: 0.8 });
|
|
3837
|
+
// ── Note typography ─────────────────────────────────
|
|
3838
|
+
// supports: font, font-size, letter-spacing, text-align, line-height
|
|
3839
|
+
const nFontSize = Number(gs.fontSize ?? 12);
|
|
3840
|
+
const nFont = resolveStyleFont(gs, diagramFont);
|
|
3841
|
+
const nLetterSpacing = gs.letterSpacing;
|
|
3842
|
+
const nLineHeight = Number(gs.lineHeight ?? 1.4) * nFontSize;
|
|
3843
|
+
const nTextAlign = String(gs.textAlign ?? 'left');
|
|
3844
|
+
const nColor = String(gs.color ?? palette.noteText);
|
|
3845
|
+
const nTextX = nTextAlign === 'right' ? x + w - fold - 6
|
|
3846
|
+
: nTextAlign === 'center' ? x + (w - fold) / 2
|
|
3847
|
+
: x + 12;
|
|
3848
|
+
if (n.lines.length > 1) {
|
|
3849
|
+
const blockCY = y + fold / 2 + (h - fold) / 2;
|
|
3850
|
+
drawMultilineText(ctx, n.lines, nTextX, blockCY, nFontSize, 400, nColor, nTextAlign, nLineHeight, nFont, nLetterSpacing);
|
|
3851
|
+
}
|
|
3852
|
+
else {
|
|
3853
|
+
drawText(ctx, n.lines[0] ?? '', nTextX, y + h / 2, nFontSize, 400, nColor, nTextAlign, nFont, nLetterSpacing);
|
|
3854
|
+
}
|
|
3649
3855
|
}
|
|
3650
3856
|
// ── Charts ────────────────────────────────────────────────
|
|
3651
3857
|
for (const c of sg.charts) {
|
|
@@ -3657,19 +3863,19 @@ var AIDiagram = (function (exports) {
|
|
|
3657
3863
|
}, R);
|
|
3658
3864
|
}
|
|
3659
3865
|
}
|
|
3660
|
-
// ── Export
|
|
3866
|
+
// ── Export helpers ─────────────────────────────────────────────────────────
|
|
3661
3867
|
function canvasToPNGBlob(canvas) {
|
|
3662
3868
|
return new Promise((resolve, reject) => {
|
|
3663
|
-
canvas.toBlob(
|
|
3869
|
+
canvas.toBlob(blob => {
|
|
3664
3870
|
if (blob)
|
|
3665
3871
|
resolve(blob);
|
|
3666
3872
|
else
|
|
3667
|
-
reject(new Error(
|
|
3668
|
-
},
|
|
3873
|
+
reject(new Error('Canvas toBlob failed'));
|
|
3874
|
+
}, 'image/png');
|
|
3669
3875
|
});
|
|
3670
3876
|
}
|
|
3671
3877
|
function canvasToPNGDataURL(canvas) {
|
|
3672
|
-
return canvas.toDataURL(
|
|
3878
|
+
return canvas.toDataURL('image/png');
|
|
3673
3879
|
}
|
|
3674
3880
|
|
|
3675
3881
|
// ============================================================
|
|
@@ -4670,6 +4876,7 @@ var AIDiagram = (function (exports) {
|
|
|
4670
4876
|
|
|
4671
4877
|
exports.ANIMATION_CSS = ANIMATION_CSS;
|
|
4672
4878
|
exports.AnimationController = AnimationController;
|
|
4879
|
+
exports.BUILTIN_FONTS = BUILTIN_FONTS;
|
|
4673
4880
|
exports.EventEmitter = EventEmitter;
|
|
4674
4881
|
exports.PALETTES = PALETTES;
|
|
4675
4882
|
exports.ParseError = ParseError;
|
|
@@ -4693,12 +4900,15 @@ var AIDiagram = (function (exports) {
|
|
|
4693
4900
|
exports.layout = layout;
|
|
4694
4901
|
exports.lerp = lerp;
|
|
4695
4902
|
exports.listThemes = listThemes;
|
|
4903
|
+
exports.loadFont = loadFont;
|
|
4696
4904
|
exports.nodeMap = nodeMap;
|
|
4697
4905
|
exports.parse = parse;
|
|
4698
4906
|
exports.parseHex = parseHex;
|
|
4907
|
+
exports.registerFont = registerFont;
|
|
4699
4908
|
exports.render = render;
|
|
4700
4909
|
exports.renderToCanvas = renderToCanvas;
|
|
4701
4910
|
exports.renderToSVG = renderToSVG;
|
|
4911
|
+
exports.resolveFont = resolveFont;
|
|
4702
4912
|
exports.resolvePalette = resolvePalette;
|
|
4703
4913
|
exports.sleep = sleep;
|
|
4704
4914
|
exports.svgToPNGDataURL = svgToPNGDataURL;
|