@zombie-mermaid/svg-renderer 3.0.0 → 3.2.0
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/index.cjs +49 -37
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +20 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.js +1611 -1118
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
- package/src/__tests__/c4-mermaid-reference.test.ts +85 -0
- package/src/__tests__/c4-svg.test.ts +337 -0
- package/src/__tests__/class-diamond-markers-1232.test.ts +59 -0
- package/src/__tests__/er-cardinality-markers-1233.test.ts +35 -0
- package/src/__tests__/fixtures/c4-mermaid-reference.json +626 -0
- package/src/__tests__/xychart-smooth-curve.test.ts +42 -0
- package/src/c4/layout.ts +490 -0
- package/src/c4/metrics.ts +235 -0
- package/src/c4/renderer.ts +366 -0
- package/src/class/renderer.ts +72 -68
- package/src/edge-curves.ts +17 -16
- package/src/er/renderer.ts +64 -50
- package/src/layout-engine/elk-graph-builder.ts +4 -0
- package/src/registry.ts +60 -0
- package/src/renderer.ts +190 -189
- package/src/sequence/renderer.ts +66 -65
- package/src/xychart/layout.ts +27 -11
- package/src/xychart/renderer.ts +33 -42
package/src/renderer.ts
CHANGED
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
escapeAttr,
|
|
21
21
|
safeHref,
|
|
22
22
|
sanitizeClassName,
|
|
23
|
+
f,
|
|
23
24
|
} from '@zombie-mermaid/core'
|
|
24
25
|
import type { FontSizes } from './styles.ts'
|
|
25
26
|
import {
|
|
@@ -215,7 +216,7 @@ function arrowMarkerDefs(): string {
|
|
|
215
216
|
* IDs are suffixed with a sanitized color string to avoid collisions.
|
|
216
217
|
*/
|
|
217
218
|
function arrowMarkerDefsForColor(color: string): string {
|
|
218
|
-
return arrowMarkerPair(escapeAttr(color), `-${markerSuffix(color)}`)
|
|
219
|
+
return arrowMarkerPair(escapeAttr(color), f`-${markerSuffix(color)}`)
|
|
219
220
|
}
|
|
220
221
|
|
|
221
222
|
/**
|
|
@@ -233,16 +234,16 @@ function arrowMarkerPair(color: string, idSuffix: string): string {
|
|
|
233
234
|
// Pull arrowhead back slightly (refX = w - 1) to prevent clipping at node boundaries.
|
|
234
235
|
const refX = w - 1
|
|
235
236
|
// Both fill and a thin stroke for better definition at small sizes.
|
|
236
|
-
const style = `fill="${color}" stroke="${color}" stroke-width="0.75" stroke-linejoin="round"`
|
|
237
|
-
const polygon = `<polygon points="0 0, ${w} ${h / 2}, 0 ${h}" ${style} />`
|
|
237
|
+
const style = f`fill="${color}" stroke="${color}" stroke-width="0.75" stroke-linejoin="round"`
|
|
238
|
+
const polygon = f`<polygon points="0 0, ${w} ${h / 2}, 0 ${h}" ${style} />`
|
|
238
239
|
const marker = (id: string, orient: string) =>
|
|
239
|
-
` <marker id="${id}" markerWidth="${w}" markerHeight="${h}" refX="${refX}" refY="${h / 2}" orient="${orient}">` +
|
|
240
|
-
`\n ${polygon}` +
|
|
240
|
+
f` <marker id="${id}" markerWidth="${w}" markerHeight="${h}" refX="${refX}" refY="${h / 2}" orient="${orient}">` +
|
|
241
|
+
f`\n ${polygon}` +
|
|
241
242
|
`\n </marker>`
|
|
242
243
|
return (
|
|
243
|
-
marker(`arrowhead${idSuffix}`, 'auto') +
|
|
244
|
+
marker(f`arrowhead${idSuffix}`, 'auto') +
|
|
244
245
|
'\n' +
|
|
245
|
-
marker(`arrowhead-start${idSuffix}`, 'auto-start-reverse')
|
|
246
|
+
marker(f`arrowhead-start${idSuffix}`, 'auto-start-reverse')
|
|
246
247
|
)
|
|
247
248
|
}
|
|
248
249
|
|
|
@@ -269,19 +270,19 @@ function renderGroup(
|
|
|
269
270
|
// data-id: original Mermaid subgraph ID
|
|
270
271
|
// data-label: display label (may differ from ID)
|
|
271
272
|
parts.push(
|
|
272
|
-
`<g class="subgraph" data-id="${escapeAttr(group.id)}" data-label="${escapeAttr(group.label)}">`,
|
|
273
|
+
f`<g class="subgraph" data-id="${escapeAttr(group.id)}" data-label="${escapeAttr(group.label)}">`,
|
|
273
274
|
)
|
|
274
275
|
|
|
275
276
|
// Outer rectangle
|
|
276
277
|
parts.push(
|
|
277
|
-
` <rect x="${group.x}" y="${group.y}" width="${group.width}" height="${group.height}" ` +
|
|
278
|
-
`rx="0" ry="0" fill="var(--_group-fill)" stroke="var(--_node-stroke)" stroke-width="${STROKE_WIDTHS.outerBox}" />`,
|
|
278
|
+
f` <rect x="${group.x}" y="${group.y}" width="${group.width}" height="${group.height}" ` +
|
|
279
|
+
f`rx="0" ry="0" fill="var(--_group-fill)" stroke="var(--_node-stroke)" stroke-width="${STROKE_WIDTHS.outerBox}" />`,
|
|
279
280
|
)
|
|
280
281
|
|
|
281
282
|
// Header band
|
|
282
283
|
parts.push(
|
|
283
|
-
` <rect x="${group.x}" y="${group.y}" width="${group.width}" height="${headerHeight}" ` +
|
|
284
|
-
`rx="0" ry="0" fill="var(--_group-hdr)" stroke="var(--_node-stroke)" stroke-width="${STROKE_WIDTHS.outerBox}" />`,
|
|
284
|
+
f` <rect x="${group.x}" y="${group.y}" width="${group.width}" height="${headerHeight}" ` +
|
|
285
|
+
f`rx="0" ry="0" fill="var(--_group-hdr)" stroke="var(--_node-stroke)" stroke-width="${STROKE_WIDTHS.outerBox}" />`,
|
|
285
286
|
)
|
|
286
287
|
|
|
287
288
|
// Header label (supports multi-line via <br> tags)
|
|
@@ -292,7 +293,7 @@ function renderGroup(
|
|
|
292
293
|
group.x + 12,
|
|
293
294
|
group.y + headerHeight / 2,
|
|
294
295
|
fontSizes.groupHeader,
|
|
295
|
-
`font-size="${fontSizes.groupHeader}" font-weight="${FONT_WEIGHTS.groupHeader}" fill="var(--_text-sec)"`,
|
|
296
|
+
f`font-size="${fontSizes.groupHeader}" font-weight="${FONT_WEIGHTS.groupHeader}" fill="var(--_text-sec)"`,
|
|
296
297
|
),
|
|
297
298
|
)
|
|
298
299
|
|
|
@@ -356,13 +357,13 @@ function renderEdge(
|
|
|
356
357
|
// Build marker attributes based on arrow direction flags
|
|
357
358
|
// Use color-specific markers when edge has a custom stroke from linkStyle
|
|
358
359
|
const suffix = edge.inlineStyle?.stroke
|
|
359
|
-
? `-${markerSuffix(edge.inlineStyle.stroke)}`
|
|
360
|
+
? f`-${markerSuffix(edge.inlineStyle.stroke)}`
|
|
360
361
|
: ''
|
|
361
362
|
let markers = ''
|
|
362
363
|
if (!invisible) {
|
|
363
|
-
if (edge.hasArrowEnd) markers += ` marker-end="url(#arrowhead${suffix})"`
|
|
364
|
+
if (edge.hasArrowEnd) markers += f` marker-end="url(#arrowhead${suffix})"`
|
|
364
365
|
if (edge.hasArrowStart)
|
|
365
|
-
markers += ` marker-start="url(#arrowhead-start${suffix})"`
|
|
366
|
+
markers += f` marker-start="url(#arrowhead-start${suffix})"`
|
|
366
367
|
}
|
|
367
368
|
|
|
368
369
|
// Semantic data attributes for edge identification and inspection:
|
|
@@ -374,20 +375,20 @@ function renderEdge(
|
|
|
374
375
|
const dataAttrs = [
|
|
375
376
|
// An animated edge (`e1@{ animate: true }`, with animation enabled)
|
|
376
377
|
// carries an extra class; its keyframes live in the shared style block.
|
|
377
|
-
`class="edge${animate ? ' edge-animated' : ''}"`,
|
|
378
|
-
`data-from="${escapeAttr(edge.source)}"`,
|
|
379
|
-
`data-to="${escapeAttr(edge.target)}"`,
|
|
380
|
-
`data-style="${edge.style}"`,
|
|
381
|
-
`data-arrow-start="${edge.hasArrowStart}"`,
|
|
382
|
-
`data-arrow-end="${edge.hasArrowEnd}"`,
|
|
378
|
+
f`class="edge${animate ? ' edge-animated' : ''}"`,
|
|
379
|
+
f`data-from="${escapeAttr(edge.source)}"`,
|
|
380
|
+
f`data-to="${escapeAttr(edge.target)}"`,
|
|
381
|
+
f`data-style="${edge.style}"`,
|
|
382
|
+
f`data-arrow-start="${edge.hasArrowStart}"`,
|
|
383
|
+
f`data-arrow-end="${edge.hasArrowEnd}"`,
|
|
383
384
|
]
|
|
384
385
|
if (edge.label) {
|
|
385
|
-
dataAttrs.push(`data-label="${escapeAttr(edge.label)}"`)
|
|
386
|
+
dataAttrs.push(f`data-label="${escapeAttr(edge.label)}"`)
|
|
386
387
|
}
|
|
387
388
|
if (edge.id) {
|
|
388
389
|
// Mermaid edge id (`A e1@--> B`), used to target the edge from CSS and
|
|
389
390
|
// to attach the animation below.
|
|
390
|
-
dataAttrs.push(`data-id="${escapeAttr(edge.id)}"`)
|
|
391
|
+
dataAttrs.push(f`data-id="${escapeAttr(edge.id)}"`)
|
|
391
392
|
}
|
|
392
393
|
|
|
393
394
|
// Marching ants need a dash pattern to march; supply one only when the
|
|
@@ -395,19 +396,19 @@ function renderEdge(
|
|
|
395
396
|
const animatedDash = animate && !dashArray ? ' stroke-dasharray="8 6"' : ''
|
|
396
397
|
|
|
397
398
|
const geometry = curved
|
|
398
|
-
? `d="${pointsToPath(edge.points, curve)}"`
|
|
399
|
-
: `points="${pointsToPolylinePath(edge.points)}"`
|
|
399
|
+
? f`d="${pointsToPath(edge.points, curve)}"`
|
|
400
|
+
: f`points="${pointsToPolylinePath(edge.points)}"`
|
|
400
401
|
|
|
401
402
|
return (
|
|
402
|
-
`<${curved ? 'path' : 'polyline'} ${dataAttrs.join(' ')} ${geometry} ` +
|
|
403
|
-
`fill="none" stroke="${strokeColor}" ` +
|
|
404
|
-
`stroke-width="${strokeWidth}"${dashArray}${animatedDash}${markers} />`
|
|
403
|
+
f`<${curved ? 'path' : 'polyline'} ${dataAttrs.join(' ')} ${geometry} ` +
|
|
404
|
+
f`fill="none" stroke="${strokeColor}" ` +
|
|
405
|
+
f`stroke-width="${strokeWidth}"${dashArray}${animatedDash}${markers} />`
|
|
405
406
|
)
|
|
406
407
|
}
|
|
407
408
|
|
|
408
409
|
/** Convert points to SVG polyline points attribute: "x1,y1 x2,y2 ..." */
|
|
409
410
|
function pointsToPolylinePath(points: Point[]): string {
|
|
410
|
-
return points.map((p) => `${p.x},${p.y}`).join(' ')
|
|
411
|
+
return points.map((p) => f`${p.x},${p.y}`).join(' ')
|
|
411
412
|
}
|
|
412
413
|
|
|
413
414
|
// `_font` isn't read here but is kept to match the `(entity, font)` signature
|
|
@@ -444,15 +445,15 @@ function renderEdgeLabel(
|
|
|
444
445
|
fontSizes.edgeLabel,
|
|
445
446
|
padding,
|
|
446
447
|
// Use --_text-sec for better contrast (was --_text-muted)
|
|
447
|
-
`text-anchor="middle" font-size="${fontSizes.edgeLabel}" font-weight="${FONT_WEIGHTS.edgeLabel}" fill="var(--_text-sec)"`,
|
|
448
|
+
f`text-anchor="middle" font-size="${fontSizes.edgeLabel}" font-weight="${FONT_WEIGHTS.edgeLabel}" fill="var(--_text-sec)"`,
|
|
448
449
|
// Increased stroke width from 0.5 to 1 for better label separation from edges
|
|
449
450
|
`rx="2" ry="2" fill="var(--bg)" stroke="var(--_inner-stroke)" stroke-width="1"`,
|
|
450
451
|
)
|
|
451
452
|
|
|
452
453
|
// Semantic wrapper: links label to its edge via data-from/data-to
|
|
453
454
|
return (
|
|
454
|
-
`<g class="edge-label" data-from="${escapeAttr(edge.source)}" data-to="${escapeAttr(edge.target)}" data-label="${escapeAttr(label)}">\n` +
|
|
455
|
-
` ${content.replace(/\n/g, '\n ')}\n` +
|
|
455
|
+
f`<g class="edge-label" data-from="${escapeAttr(edge.source)}" data-to="${escapeAttr(edge.target)}" data-label="${escapeAttr(label)}">\n` +
|
|
456
|
+
f` ${content.replace(/\n/g, '\n ')}\n` +
|
|
456
457
|
`</g>`
|
|
457
458
|
)
|
|
458
459
|
}
|
|
@@ -471,7 +472,7 @@ function requirePoint(points: Point[], index: number): Point {
|
|
|
471
472
|
// Unreachable — both call sites in edgeMidpoint only ever pass indices
|
|
472
473
|
// in [0, points.length - 1].
|
|
473
474
|
/* v8 ignore next */
|
|
474
|
-
throw new Error(`edgeMidpoint: missing point at index ${index}`)
|
|
475
|
+
throw new Error(f`edgeMidpoint: missing point at index ${index}`)
|
|
475
476
|
}
|
|
476
477
|
return point
|
|
477
478
|
}
|
|
@@ -539,14 +540,14 @@ function renderNode(
|
|
|
539
540
|
// This enables reliable node identification without heuristics
|
|
540
541
|
const parts: string[] = []
|
|
541
542
|
const safeClassName = sanitizeClassName(node.className)
|
|
542
|
-
const classAttr = safeClassName ? `node ${safeClassName}` : 'node'
|
|
543
|
+
const classAttr = safeClassName ? f`node ${safeClassName}` : 'node'
|
|
543
544
|
|
|
544
545
|
const interaction = node.interaction
|
|
545
546
|
const groupAttrs = [
|
|
546
|
-
`class="${classAttr}"`,
|
|
547
|
-
`data-id="${escapeAttr(node.id)}"`,
|
|
548
|
-
`data-label="${escapeAttr(node.label)}"`,
|
|
549
|
-
`data-shape="${node.shape}"`,
|
|
547
|
+
f`class="${classAttr}"`,
|
|
548
|
+
f`data-id="${escapeAttr(node.id)}"`,
|
|
549
|
+
f`data-label="${escapeAttr(node.label)}"`,
|
|
550
|
+
f`data-shape="${node.shape}"`,
|
|
550
551
|
]
|
|
551
552
|
/*
|
|
552
553
|
* `click A call fn()` is deliberately absent from the markup. This renderer
|
|
@@ -558,7 +559,7 @@ function renderNode(
|
|
|
558
559
|
* attribute once emitted here was removed in #216 — see
|
|
559
560
|
* docs/decisions/no-script-interactivity.md.
|
|
560
561
|
*/
|
|
561
|
-
parts.push(`<g ${groupAttrs.join(' ')}>`)
|
|
562
|
+
parts.push(f`<g ${groupAttrs.join(' ')}>`)
|
|
562
563
|
|
|
563
564
|
// An href becomes a real SVG link, which needs no script to work.
|
|
564
565
|
// `interactivity: 'none'` strips it — a link is meaningless in
|
|
@@ -567,21 +568,21 @@ function renderNode(
|
|
|
567
568
|
const indent = href ? ' ' : ' '
|
|
568
569
|
if (href) {
|
|
569
570
|
const targetAttr = interaction?.target
|
|
570
|
-
? ` target="${escapeAttr(interaction.target)}"`
|
|
571
|
+
? f` target="${escapeAttr(interaction.target)}"`
|
|
571
572
|
: ''
|
|
572
|
-
parts.push(` <a href="${escapeAttr(href)}"${targetAttr}>`)
|
|
573
|
+
parts.push(f` <a href="${escapeAttr(href)}"${targetAttr}>`)
|
|
573
574
|
}
|
|
574
575
|
|
|
575
576
|
if (linksEnabled && interaction?.tooltip) {
|
|
576
577
|
// <title> is SVG's native tooltip — no script, no CSS. Gated by
|
|
577
578
|
// linksEnabled alongside href above: both come from the same `click`
|
|
578
579
|
// statement, and 'none' strips both.
|
|
579
|
-
parts.push(`${indent}<title>${escapeXml(interaction.tooltip)}</title>`)
|
|
580
|
+
parts.push(f`${indent}<title>${escapeXml(interaction.tooltip)}</title>`)
|
|
580
581
|
}
|
|
581
582
|
|
|
582
|
-
parts.push(`${indent}${shape.replace(/\n/g, `\n${indent}`)}`)
|
|
583
|
+
parts.push(f`${indent}${shape.replace(/\n/g, f`\n${indent}`)}`)
|
|
583
584
|
if (label) {
|
|
584
|
-
parts.push(`${indent}${label.replace(/\n/g, `\n${indent}`)}`)
|
|
585
|
+
parts.push(f`${indent}${label.replace(/\n/g, f`\n${indent}`)}`)
|
|
585
586
|
}
|
|
586
587
|
|
|
587
588
|
if (href) parts.push(' </a>')
|
|
@@ -700,8 +701,8 @@ function renderRect(
|
|
|
700
701
|
sw: string,
|
|
701
702
|
): string {
|
|
702
703
|
return (
|
|
703
|
-
`<rect x="${x}" y="${y}" width="${w}" height="${h}" ` +
|
|
704
|
-
`rx="0" ry="0" fill="${fill}" stroke="${stroke}" stroke-width="${sw}" />`
|
|
704
|
+
f`<rect x="${x}" y="${y}" width="${w}" height="${h}" ` +
|
|
705
|
+
f`rx="0" ry="0" fill="${fill}" stroke="${stroke}" stroke-width="${sw}" />`
|
|
705
706
|
)
|
|
706
707
|
}
|
|
707
708
|
|
|
@@ -715,8 +716,8 @@ function renderRoundedRect(
|
|
|
715
716
|
sw: string,
|
|
716
717
|
): string {
|
|
717
718
|
return (
|
|
718
|
-
`<rect x="${x}" y="${y}" width="${w}" height="${h}" ` +
|
|
719
|
-
`rx="6" ry="6" fill="${fill}" stroke="${stroke}" stroke-width="${sw}" />`
|
|
719
|
+
f`<rect x="${x}" y="${y}" width="${w}" height="${h}" ` +
|
|
720
|
+
f`rx="6" ry="6" fill="${fill}" stroke="${stroke}" stroke-width="${sw}" />`
|
|
720
721
|
)
|
|
721
722
|
}
|
|
722
723
|
|
|
@@ -731,8 +732,8 @@ function renderStadium(
|
|
|
731
732
|
): string {
|
|
732
733
|
const r = h / 2
|
|
733
734
|
return (
|
|
734
|
-
`<rect x="${x}" y="${y}" width="${w}" height="${h}" ` +
|
|
735
|
-
`rx="${r}" ry="${r}" fill="${fill}" stroke="${stroke}" stroke-width="${sw}" />`
|
|
735
|
+
f`<rect x="${x}" y="${y}" width="${w}" height="${h}" ` +
|
|
736
|
+
f`rx="${r}" ry="${r}" fill="${fill}" stroke="${stroke}" stroke-width="${sw}" />`
|
|
736
737
|
)
|
|
737
738
|
}
|
|
738
739
|
|
|
@@ -749,8 +750,8 @@ function renderCircle(
|
|
|
749
750
|
const cy = y + h / 2
|
|
750
751
|
const r = Math.min(w, h) / 2
|
|
751
752
|
return (
|
|
752
|
-
`<circle cx="${cx}" cy="${cy}" r="${r}" ` +
|
|
753
|
-
`fill="${fill}" stroke="${stroke}" stroke-width="${sw}" />`
|
|
753
|
+
f`<circle cx="${cx}" cy="${cy}" r="${r}" ` +
|
|
754
|
+
f`fill="${fill}" stroke="${stroke}" stroke-width="${sw}" />`
|
|
754
755
|
)
|
|
755
756
|
}
|
|
756
757
|
|
|
@@ -768,13 +769,13 @@ function renderDiamond(
|
|
|
768
769
|
const hw = w / 2
|
|
769
770
|
const hh = h / 2
|
|
770
771
|
const points = [
|
|
771
|
-
`${cx},${cy - hh}`, // top
|
|
772
|
-
`${cx + hw},${cy}`, // right
|
|
773
|
-
`${cx},${cy + hh}`, // bottom
|
|
774
|
-
`${cx - hw},${cy}`, // left
|
|
772
|
+
f`${cx},${cy - hh}`, // top
|
|
773
|
+
f`${cx + hw},${cy}`, // right
|
|
774
|
+
f`${cx},${cy + hh}`, // bottom
|
|
775
|
+
f`${cx - hw},${cy}`, // left
|
|
775
776
|
].join(' ')
|
|
776
777
|
|
|
777
|
-
return `<polygon points="${points}" fill="${fill}" stroke="${stroke}" stroke-width="${sw}" />`
|
|
778
|
+
return f`<polygon points="${points}" fill="${fill}" stroke="${stroke}" stroke-width="${sw}" />`
|
|
778
779
|
}
|
|
779
780
|
|
|
780
781
|
// --- Batch 1 shapes ---
|
|
@@ -791,12 +792,12 @@ function renderSubroutine(
|
|
|
791
792
|
): string {
|
|
792
793
|
const inset = 8 // distance from edge to inner vertical line
|
|
793
794
|
return (
|
|
794
|
-
`<rect x="${x}" y="${y}" width="${w}" height="${h}" ` +
|
|
795
|
-
`rx="0" ry="0" fill="${fill}" stroke="${stroke}" stroke-width="${sw}" />` +
|
|
796
|
-
`\n<line x1="${x + inset}" y1="${y}" x2="${x + inset}" y2="${y + h}" ` +
|
|
797
|
-
`stroke="${stroke}" stroke-width="${sw}" />` +
|
|
798
|
-
`\n<line x1="${x + w - inset}" y1="${y}" x2="${x + w - inset}" y2="${y + h}" ` +
|
|
799
|
-
`stroke="${stroke}" stroke-width="${sw}" />`
|
|
795
|
+
f`<rect x="${x}" y="${y}" width="${w}" height="${h}" ` +
|
|
796
|
+
f`rx="0" ry="0" fill="${fill}" stroke="${stroke}" stroke-width="${sw}" />` +
|
|
797
|
+
f`\n<line x1="${x + inset}" y1="${y}" x2="${x + inset}" y2="${y + h}" ` +
|
|
798
|
+
f`stroke="${stroke}" stroke-width="${sw}" />` +
|
|
799
|
+
f`\n<line x1="${x + w - inset}" y1="${y}" x2="${x + w - inset}" y2="${y + h}" ` +
|
|
800
|
+
f`stroke="${stroke}" stroke-width="${sw}" />`
|
|
800
801
|
)
|
|
801
802
|
}
|
|
802
803
|
|
|
@@ -815,10 +816,10 @@ function renderDoubleCircle(
|
|
|
815
816
|
const outerR = Math.min(w, h) / 2
|
|
816
817
|
const innerR = outerR - 5 // 5px gap between rings
|
|
817
818
|
return (
|
|
818
|
-
`<circle cx="${cx}" cy="${cy}" r="${outerR}" ` +
|
|
819
|
-
`fill="${fill}" stroke="${stroke}" stroke-width="${sw}" />` +
|
|
820
|
-
`\n<circle cx="${cx}" cy="${cy}" r="${innerR}" ` +
|
|
821
|
-
`fill="${fill}" stroke="${stroke}" stroke-width="${sw}" />`
|
|
819
|
+
f`<circle cx="${cx}" cy="${cy}" r="${outerR}" ` +
|
|
820
|
+
f`fill="${fill}" stroke="${stroke}" stroke-width="${sw}" />` +
|
|
821
|
+
f`\n<circle cx="${cx}" cy="${cy}" r="${innerR}" ` +
|
|
822
|
+
f`fill="${fill}" stroke="${stroke}" stroke-width="${sw}" />`
|
|
822
823
|
)
|
|
823
824
|
}
|
|
824
825
|
|
|
@@ -834,15 +835,15 @@ function renderHexagon(
|
|
|
834
835
|
): string {
|
|
835
836
|
const inset = h / 4 // horizontal inset for the angled sides
|
|
836
837
|
const points = [
|
|
837
|
-
`${x + inset},${y}`, // top-left
|
|
838
|
-
`${x + w - inset},${y}`, // top-right
|
|
839
|
-
`${x + w},${y + h / 2}`, // mid-right
|
|
840
|
-
`${x + w - inset},${y + h}`, // bottom-right
|
|
841
|
-
`${x + inset},${y + h}`, // bottom-left
|
|
842
|
-
`${x},${y + h / 2}`, // mid-left
|
|
838
|
+
f`${x + inset},${y}`, // top-left
|
|
839
|
+
f`${x + w - inset},${y}`, // top-right
|
|
840
|
+
f`${x + w},${y + h / 2}`, // mid-right
|
|
841
|
+
f`${x + w - inset},${y + h}`, // bottom-right
|
|
842
|
+
f`${x + inset},${y + h}`, // bottom-left
|
|
843
|
+
f`${x},${y + h / 2}`, // mid-left
|
|
843
844
|
].join(' ')
|
|
844
845
|
|
|
845
|
-
return `<polygon points="${points}" fill="${fill}" stroke="${stroke}" stroke-width="${sw}" />`
|
|
846
|
+
return f`<polygon points="${points}" fill="${fill}" stroke="${stroke}" stroke-width="${sw}" />`
|
|
846
847
|
}
|
|
847
848
|
|
|
848
849
|
// --- Batch 2 shapes ---
|
|
@@ -864,17 +865,17 @@ function renderCylinder(
|
|
|
864
865
|
|
|
865
866
|
return (
|
|
866
867
|
// Body rectangle (no top border — covered by top ellipse)
|
|
867
|
-
`<rect x="${x}" y="${bodyTop}" width="${w}" height="${bodyH}" ` +
|
|
868
|
-
`fill="${fill}" stroke="none" />` +
|
|
868
|
+
f`<rect x="${x}" y="${bodyTop}" width="${w}" height="${bodyH}" ` +
|
|
869
|
+
f`fill="${fill}" stroke="none" />` +
|
|
869
870
|
// Left and right body borders
|
|
870
|
-
`\n<line x1="${x}" y1="${bodyTop}" x2="${x}" y2="${bodyTop + bodyH}" stroke="${stroke}" stroke-width="${sw}" />` +
|
|
871
|
-
`\n<line x1="${x + w}" y1="${bodyTop}" x2="${x + w}" y2="${bodyTop + bodyH}" stroke="${stroke}" stroke-width="${sw}" />` +
|
|
871
|
+
f`\n<line x1="${x}" y1="${bodyTop}" x2="${x}" y2="${bodyTop + bodyH}" stroke="${stroke}" stroke-width="${sw}" />` +
|
|
872
|
+
f`\n<line x1="${x + w}" y1="${bodyTop}" x2="${x + w}" y2="${bodyTop + bodyH}" stroke="${stroke}" stroke-width="${sw}" />` +
|
|
872
873
|
// Bottom ellipse (half visible)
|
|
873
|
-
`\n<ellipse cx="${cx}" cy="${y + h - ry}" rx="${w / 2}" ry="${ry}" ` +
|
|
874
|
-
`fill="${fill}" stroke="${stroke}" stroke-width="${sw}" />` +
|
|
874
|
+
f`\n<ellipse cx="${cx}" cy="${y + h - ry}" rx="${w / 2}" ry="${ry}" ` +
|
|
875
|
+
f`fill="${fill}" stroke="${stroke}" stroke-width="${sw}" />` +
|
|
875
876
|
// Top ellipse (full, on top)
|
|
876
|
-
`\n<ellipse cx="${cx}" cy="${bodyTop}" rx="${w / 2}" ry="${ry}" ` +
|
|
877
|
-
`fill="${fill}" stroke="${stroke}" stroke-width="${sw}" />`
|
|
877
|
+
f`\n<ellipse cx="${cx}" cy="${bodyTop}" rx="${w / 2}" ry="${ry}" ` +
|
|
878
|
+
f`fill="${fill}" stroke="${stroke}" stroke-width="${sw}" />`
|
|
878
879
|
)
|
|
879
880
|
}
|
|
880
881
|
|
|
@@ -890,14 +891,14 @@ function renderAsymmetric(
|
|
|
890
891
|
): string {
|
|
891
892
|
const indent = 12 // how far the point indents
|
|
892
893
|
const points = [
|
|
893
|
-
`${x + indent},${y}`, // top-left (indented)
|
|
894
|
-
`${x + w},${y}`, // top-right
|
|
895
|
-
`${x + w},${y + h}`, // bottom-right
|
|
896
|
-
`${x + indent},${y + h}`, // bottom-left (indented)
|
|
897
|
-
`${x},${y + h / 2}`, // left point
|
|
894
|
+
f`${x + indent},${y}`, // top-left (indented)
|
|
895
|
+
f`${x + w},${y}`, // top-right
|
|
896
|
+
f`${x + w},${y + h}`, // bottom-right
|
|
897
|
+
f`${x + indent},${y + h}`, // bottom-left (indented)
|
|
898
|
+
f`${x},${y + h / 2}`, // left point
|
|
898
899
|
].join(' ')
|
|
899
900
|
|
|
900
|
-
return `<polygon points="${points}" fill="${fill}" stroke="${stroke}" stroke-width="${sw}" />`
|
|
901
|
+
return f`<polygon points="${points}" fill="${fill}" stroke="${stroke}" stroke-width="${sw}" />`
|
|
901
902
|
}
|
|
902
903
|
|
|
903
904
|
/** Trapezoid [/text\]: wider bottom, narrower top */
|
|
@@ -912,13 +913,13 @@ function renderTrapezoid(
|
|
|
912
913
|
): string {
|
|
913
914
|
const inset = w * 0.15 // top edge is narrower by this amount on each side
|
|
914
915
|
const points = [
|
|
915
|
-
`${x + inset},${y}`, // top-left (indented)
|
|
916
|
-
`${x + w - inset},${y}`, // top-right (indented)
|
|
917
|
-
`${x + w},${y + h}`, // bottom-right (full width)
|
|
918
|
-
`${x},${y + h}`, // bottom-left (full width)
|
|
916
|
+
f`${x + inset},${y}`, // top-left (indented)
|
|
917
|
+
f`${x + w - inset},${y}`, // top-right (indented)
|
|
918
|
+
f`${x + w},${y + h}`, // bottom-right (full width)
|
|
919
|
+
f`${x},${y + h}`, // bottom-left (full width)
|
|
919
920
|
].join(' ')
|
|
920
921
|
|
|
921
|
-
return `<polygon points="${points}" fill="${fill}" stroke="${stroke}" stroke-width="${sw}" />`
|
|
922
|
+
return f`<polygon points="${points}" fill="${fill}" stroke="${stroke}" stroke-width="${sw}" />`
|
|
922
923
|
}
|
|
923
924
|
|
|
924
925
|
/** Trapezoid-alt [\text/]: wider top, narrower bottom */
|
|
@@ -933,13 +934,13 @@ function renderTrapezoidAlt(
|
|
|
933
934
|
): string {
|
|
934
935
|
const inset = w * 0.15 // bottom edge is narrower
|
|
935
936
|
const points = [
|
|
936
|
-
`${x},${y}`, // top-left (full width)
|
|
937
|
-
`${x + w},${y}`, // top-right (full width)
|
|
938
|
-
`${x + w - inset},${y + h}`, // bottom-right (indented)
|
|
939
|
-
`${x + inset},${y + h}`, // bottom-left (indented)
|
|
937
|
+
f`${x},${y}`, // top-left (full width)
|
|
938
|
+
f`${x + w},${y}`, // top-right (full width)
|
|
939
|
+
f`${x + w - inset},${y + h}`, // bottom-right (indented)
|
|
940
|
+
f`${x + inset},${y + h}`, // bottom-left (indented)
|
|
940
941
|
].join(' ')
|
|
941
942
|
|
|
942
|
-
return `<polygon points="${points}" fill="${fill}" stroke="${stroke}" stroke-width="${sw}" />`
|
|
943
|
+
return f`<polygon points="${points}" fill="${fill}" stroke="${stroke}" stroke-width="${sw}" />`
|
|
943
944
|
}
|
|
944
945
|
|
|
945
946
|
/**
|
|
@@ -960,13 +961,13 @@ function renderParallelogram(
|
|
|
960
961
|
): string {
|
|
961
962
|
const inset = w * 0.15
|
|
962
963
|
const points = [
|
|
963
|
-
`${x + inset},${y}`, // top-left (shifted right)
|
|
964
|
-
`${x + w},${y}`, // top-right
|
|
965
|
-
`${x + w - inset},${y + h}`, // bottom-right (shifted left)
|
|
966
|
-
`${x},${y + h}`, // bottom-left
|
|
964
|
+
f`${x + inset},${y}`, // top-left (shifted right)
|
|
965
|
+
f`${x + w},${y}`, // top-right
|
|
966
|
+
f`${x + w - inset},${y + h}`, // bottom-right (shifted left)
|
|
967
|
+
f`${x},${y + h}`, // bottom-left
|
|
967
968
|
].join(' ')
|
|
968
969
|
|
|
969
|
-
return `<polygon points="${points}" fill="${fill}" stroke="${stroke}" stroke-width="${sw}" />`
|
|
970
|
+
return f`<polygon points="${points}" fill="${fill}" stroke="${stroke}" stroke-width="${sw}" />`
|
|
970
971
|
}
|
|
971
972
|
|
|
972
973
|
/** Parallelogram-alt [\text\]: leans left — the mirror of renderParallelogram. */
|
|
@@ -981,13 +982,13 @@ function renderParallelogramAlt(
|
|
|
981
982
|
): string {
|
|
982
983
|
const inset = w * 0.15
|
|
983
984
|
const points = [
|
|
984
|
-
`${x},${y}`, // top-left
|
|
985
|
-
`${x + w - inset},${y}`, // top-right (shifted left)
|
|
986
|
-
`${x + w},${y + h}`, // bottom-right
|
|
987
|
-
`${x + inset},${y + h}`, // bottom-left (shifted right)
|
|
985
|
+
f`${x},${y}`, // top-left
|
|
986
|
+
f`${x + w - inset},${y}`, // top-right (shifted left)
|
|
987
|
+
f`${x + w},${y + h}`, // bottom-right
|
|
988
|
+
f`${x + inset},${y + h}`, // bottom-left (shifted right)
|
|
988
989
|
].join(' ')
|
|
989
990
|
|
|
990
|
-
return `<polygon points="${points}" fill="${fill}" stroke="${stroke}" stroke-width="${sw}" />`
|
|
991
|
+
return f`<polygon points="${points}" fill="${fill}" stroke="${stroke}" stroke-width="${sw}" />`
|
|
991
992
|
}
|
|
992
993
|
|
|
993
994
|
// --- Expanded-syntax shapes (`A@{ shape: ... }`) ---
|
|
@@ -999,7 +1000,7 @@ function renderParallelogramAlt(
|
|
|
999
1000
|
|
|
1000
1001
|
/** Shared attribute string for a filled, stroked path. */
|
|
1001
1002
|
function shapeAttrs(fill: string, stroke: string, sw: string): string {
|
|
1002
|
-
return `fill="${fill}" stroke="${stroke}" stroke-width="${sw}"`
|
|
1003
|
+
return f`fill="${fill}" stroke="${stroke}" stroke-width="${sw}"`
|
|
1003
1004
|
}
|
|
1004
1005
|
|
|
1005
1006
|
/**
|
|
@@ -1024,9 +1025,9 @@ function renderDocument(
|
|
|
1024
1025
|
// height. Ending the curve anywhere else makes every document node
|
|
1025
1026
|
// visibly lopsided.
|
|
1026
1027
|
const d =
|
|
1027
|
-
`M ${x} ${y} L ${x + w} ${y} L ${x + w} ${base} ` +
|
|
1028
|
-
`C ${x + w * 0.75} ${base + waveH * 1.8} ${x + w * 0.25} ${base - waveH * 0.8} ${x} ${base} Z`
|
|
1029
|
-
return `<path d="${d}" ${shapeAttrs(fill, stroke, sw)} />`
|
|
1028
|
+
f`M ${x} ${y} L ${x + w} ${y} L ${x + w} ${base} ` +
|
|
1029
|
+
f`C ${x + w * 0.75} ${base + waveH * 1.8} ${x + w * 0.25} ${base - waveH * 0.8} ${x} ${base} Z`
|
|
1030
|
+
return f`<path d="${d}" ${shapeAttrs(fill, stroke, sw)} />`
|
|
1030
1031
|
}
|
|
1031
1032
|
|
|
1032
1033
|
/** Stacked document / process: offset copies behind the front shape. */
|
|
@@ -1048,15 +1049,15 @@ function renderStacked(
|
|
|
1048
1049
|
// Two offset copies behind, back to front.
|
|
1049
1050
|
for (let i = 2; i >= 1; i--) {
|
|
1050
1051
|
parts.push(
|
|
1051
|
-
`<rect x="${x + offset * i}" y="${y + offset * (2 - i)}" width="${frontW}" height="${frontH}" ` +
|
|
1052
|
-
`${shapeAttrs(fill, stroke, sw)} />`,
|
|
1052
|
+
f`<rect x="${x + offset * i}" y="${y + offset * (2 - i)}" width="${frontW}" height="${frontH}" ` +
|
|
1053
|
+
f`${shapeAttrs(fill, stroke, sw)} />`,
|
|
1053
1054
|
)
|
|
1054
1055
|
}
|
|
1055
1056
|
|
|
1056
1057
|
parts.push(
|
|
1057
1058
|
isDocument
|
|
1058
1059
|
? renderDocument(x, y + offset * 2, frontW, frontH, fill, stroke, sw)
|
|
1059
|
-
: `<rect x="${x}" y="${y + offset * 2}" width="${frontW}" height="${frontH}" ${shapeAttrs(fill, stroke, sw)} />`,
|
|
1060
|
+
: f`<rect x="${x}" y="${y + offset * 2}" width="${frontW}" height="${frontH}" ${shapeAttrs(fill, stroke, sw)} />`,
|
|
1060
1061
|
)
|
|
1061
1062
|
|
|
1062
1063
|
return parts.join('\n')
|
|
@@ -1074,13 +1075,13 @@ function renderCard(
|
|
|
1074
1075
|
): string {
|
|
1075
1076
|
const notch = Math.min(14, w * 0.12, h * 0.3)
|
|
1076
1077
|
const points = [
|
|
1077
|
-
`${x + notch},${y}`,
|
|
1078
|
-
`${x + w},${y}`,
|
|
1079
|
-
`${x + w},${y + h}`,
|
|
1080
|
-
`${x},${y + h}`,
|
|
1081
|
-
`${x},${y + notch}`,
|
|
1078
|
+
f`${x + notch},${y}`,
|
|
1079
|
+
f`${x + w},${y}`,
|
|
1080
|
+
f`${x + w},${y + h}`,
|
|
1081
|
+
f`${x},${y + h}`,
|
|
1082
|
+
f`${x},${y + notch}`,
|
|
1082
1083
|
].join(' ')
|
|
1083
|
-
return `<polygon points="${points}" ${shapeAttrs(fill, stroke, sw)} />`
|
|
1084
|
+
return f`<polygon points="${points}" ${shapeAttrs(fill, stroke, sw)} />`
|
|
1084
1085
|
}
|
|
1085
1086
|
|
|
1086
1087
|
/** Lined process: rectangle with a vertical rule inset from the left edge. */
|
|
@@ -1095,8 +1096,8 @@ function renderLinedProcess(
|
|
|
1095
1096
|
): string {
|
|
1096
1097
|
const inset = Math.min(12, w * 0.12)
|
|
1097
1098
|
return (
|
|
1098
|
-
`<rect x="${x}" y="${y}" width="${w}" height="${h}" ${shapeAttrs(fill, stroke, sw)} />\n` +
|
|
1099
|
-
`<line x1="${x + inset}" y1="${y}" x2="${x + inset}" y2="${y + h}" stroke="${stroke}" stroke-width="${sw}" />`
|
|
1099
|
+
f`<rect x="${x}" y="${y}" width="${w}" height="${h}" ${shapeAttrs(fill, stroke, sw)} />\n` +
|
|
1100
|
+
f`<line x1="${x + inset}" y1="${y}" x2="${x + inset}" y2="${y + h}" stroke="${stroke}" stroke-width="${sw}" />`
|
|
1100
1101
|
)
|
|
1101
1102
|
}
|
|
1102
1103
|
|
|
@@ -1112,8 +1113,8 @@ function renderDividedProcess(
|
|
|
1112
1113
|
): string {
|
|
1113
1114
|
const split = y + Math.min(16, h * 0.3)
|
|
1114
1115
|
return (
|
|
1115
|
-
`<rect x="${x}" y="${y}" width="${w}" height="${h}" ${shapeAttrs(fill, stroke, sw)} />\n` +
|
|
1116
|
-
`<line x1="${x}" y1="${split}" x2="${x + w}" y2="${split}" stroke="${stroke}" stroke-width="${sw}" />`
|
|
1116
|
+
f`<rect x="${x}" y="${y}" width="${w}" height="${h}" ${shapeAttrs(fill, stroke, sw)} />\n` +
|
|
1117
|
+
f`<line x1="${x}" y1="${split}" x2="${x + w}" y2="${split}" stroke="${stroke}" stroke-width="${sw}" />`
|
|
1117
1118
|
)
|
|
1118
1119
|
}
|
|
1119
1120
|
|
|
@@ -1130,9 +1131,9 @@ function renderWindowPane(
|
|
|
1130
1131
|
const vx = x + Math.min(16, w * 0.16)
|
|
1131
1132
|
const hy = y + Math.min(14, h * 0.28)
|
|
1132
1133
|
return (
|
|
1133
|
-
`<rect x="${x}" y="${y}" width="${w}" height="${h}" ${shapeAttrs(fill, stroke, sw)} />\n` +
|
|
1134
|
-
`<line x1="${vx}" y1="${y}" x2="${vx}" y2="${y + h}" stroke="${stroke}" stroke-width="${sw}" />\n` +
|
|
1135
|
-
`<line x1="${x}" y1="${hy}" x2="${x + w}" y2="${hy}" stroke="${stroke}" stroke-width="${sw}" />`
|
|
1134
|
+
f`<rect x="${x}" y="${y}" width="${w}" height="${h}" ${shapeAttrs(fill, stroke, sw)} />\n` +
|
|
1135
|
+
f`<line x1="${vx}" y1="${y}" x2="${vx}" y2="${y + h}" stroke="${stroke}" stroke-width="${sw}" />\n` +
|
|
1136
|
+
f`<line x1="${x}" y1="${hy}" x2="${x + w}" y2="${hy}" stroke="${stroke}" stroke-width="${sw}" />`
|
|
1136
1137
|
)
|
|
1137
1138
|
}
|
|
1138
1139
|
|
|
@@ -1148,9 +1149,9 @@ function renderTriangle(
|
|
|
1148
1149
|
flipped: boolean,
|
|
1149
1150
|
): string {
|
|
1150
1151
|
const points = flipped
|
|
1151
|
-
? [`${x},${y}`, `${x + w},${y}`, `${x + w / 2},${y + h}`]
|
|
1152
|
-
: [`${x + w / 2},${y}`, `${x + w},${y + h}`, `${x},${y + h}`]
|
|
1153
|
-
return `<polygon points="${points.join(' ')}" ${shapeAttrs(fill, stroke, sw)} />`
|
|
1152
|
+
? [f`${x},${y}`, f`${x + w},${y}`, f`${x + w / 2},${y + h}`]
|
|
1153
|
+
: [f`${x + w / 2},${y}`, f`${x + w},${y + h}`, f`${x},${y + h}`]
|
|
1154
|
+
return f`<polygon points="${points.join(' ')}" ${shapeAttrs(fill, stroke, sw)} />`
|
|
1154
1155
|
}
|
|
1155
1156
|
|
|
1156
1157
|
/** Filled circle (junction): a solid dot, so it takes the stroke color as fill. */
|
|
@@ -1162,7 +1163,7 @@ function renderFilledCircle(
|
|
|
1162
1163
|
stroke: string,
|
|
1163
1164
|
): string {
|
|
1164
1165
|
const r = Math.min(w, h) / 2 - 2
|
|
1165
|
-
return `<circle cx="${x + w / 2}" cy="${y + h / 2}" r="${r}" fill="${stroke}" stroke="${stroke}" />`
|
|
1166
|
+
return f`<circle cx="${x + w / 2}" cy="${y + h / 2}" r="${r}" fill="${stroke}" stroke="${stroke}" />`
|
|
1166
1167
|
}
|
|
1167
1168
|
|
|
1168
1169
|
/** Crossed circle (summary): circle with an X through it. */
|
|
@@ -1181,9 +1182,9 @@ function renderCrossedCircle(
|
|
|
1181
1182
|
// Cross arms meet the circumference at 45°, so offset by r/√2.
|
|
1182
1183
|
const d = r / Math.SQRT2
|
|
1183
1184
|
return (
|
|
1184
|
-
`<circle cx="${cx}" cy="${cy}" r="${r}" ${shapeAttrs(fill, stroke, sw)} />\n` +
|
|
1185
|
-
`<line x1="${cx - d}" y1="${cy - d}" x2="${cx + d}" y2="${cy + d}" stroke="${stroke}" stroke-width="${sw}" />\n` +
|
|
1186
|
-
`<line x1="${cx + d}" y1="${cy - d}" x2="${cx - d}" y2="${cy + d}" stroke="${stroke}" stroke-width="${sw}" />`
|
|
1185
|
+
f`<circle cx="${cx}" cy="${cy}" r="${r}" ${shapeAttrs(fill, stroke, sw)} />\n` +
|
|
1186
|
+
f`<line x1="${cx - d}" y1="${cy - d}" x2="${cx + d}" y2="${cy + d}" stroke="${stroke}" stroke-width="${sw}" />\n` +
|
|
1187
|
+
f`<line x1="${cx + d}" y1="${cy - d}" x2="${cx - d}" y2="${cy + d}" stroke="${stroke}" stroke-width="${sw}" />`
|
|
1187
1188
|
)
|
|
1188
1189
|
}
|
|
1189
1190
|
|
|
@@ -1196,7 +1197,7 @@ function renderForkJoin(
|
|
|
1196
1197
|
stroke: string,
|
|
1197
1198
|
): string {
|
|
1198
1199
|
const barH = Math.min(8, h)
|
|
1199
|
-
return `<rect x="${x}" y="${y + (h - barH) / 2}" width="${w}" height="${barH}" fill="${stroke}" stroke="${stroke}" />`
|
|
1200
|
+
return f`<rect x="${x}" y="${y + (h - barH) / 2}" width="${w}" height="${barH}" fill="${stroke}" stroke="${stroke}" />`
|
|
1200
1201
|
}
|
|
1201
1202
|
|
|
1202
1203
|
/** Notched pentagon (loop limit): both top corners clipped. */
|
|
@@ -1211,14 +1212,14 @@ function renderNotchedPentagon(
|
|
|
1211
1212
|
): string {
|
|
1212
1213
|
const notch = Math.min(14, w * 0.12, h * 0.3)
|
|
1213
1214
|
const points = [
|
|
1214
|
-
`${x + notch},${y}`,
|
|
1215
|
-
`${x + w - notch},${y}`,
|
|
1216
|
-
`${x + w},${y + notch}`,
|
|
1217
|
-
`${x + w},${y + h}`,
|
|
1218
|
-
`${x},${y + h}`,
|
|
1219
|
-
`${x},${y + notch}`,
|
|
1215
|
+
f`${x + notch},${y}`,
|
|
1216
|
+
f`${x + w - notch},${y}`,
|
|
1217
|
+
f`${x + w},${y + notch}`,
|
|
1218
|
+
f`${x + w},${y + h}`,
|
|
1219
|
+
f`${x},${y + h}`,
|
|
1220
|
+
f`${x},${y + notch}`,
|
|
1220
1221
|
].join(' ')
|
|
1221
|
-
return `<polygon points="${points}" ${shapeAttrs(fill, stroke, sw)} />`
|
|
1222
|
+
return f`<polygon points="${points}" ${shapeAttrs(fill, stroke, sw)} />`
|
|
1222
1223
|
}
|
|
1223
1224
|
|
|
1224
1225
|
/** Sloped rectangle (manual input): top edge slopes up to the right. */
|
|
@@ -1233,12 +1234,12 @@ function renderSlopedRectangle(
|
|
|
1233
1234
|
): string {
|
|
1234
1235
|
const slope = Math.min(12, h * 0.3)
|
|
1235
1236
|
const points = [
|
|
1236
|
-
`${x},${y + slope}`,
|
|
1237
|
-
`${x + w},${y}`,
|
|
1238
|
-
`${x + w},${y + h}`,
|
|
1239
|
-
`${x},${y + h}`,
|
|
1237
|
+
f`${x},${y + slope}`,
|
|
1238
|
+
f`${x + w},${y}`,
|
|
1239
|
+
f`${x + w},${y + h}`,
|
|
1240
|
+
f`${x},${y + h}`,
|
|
1240
1241
|
].join(' ')
|
|
1241
|
-
return `<polygon points="${points}" ${shapeAttrs(fill, stroke, sw)} />`
|
|
1242
|
+
return f`<polygon points="${points}" ${shapeAttrs(fill, stroke, sw)} />`
|
|
1242
1243
|
}
|
|
1243
1244
|
|
|
1244
1245
|
/** Flag / paper tape: wavy top and bottom edges. */
|
|
@@ -1253,11 +1254,11 @@ function renderFlag(
|
|
|
1253
1254
|
): string {
|
|
1254
1255
|
const waveH = Math.min(8, h * 0.14)
|
|
1255
1256
|
const d =
|
|
1256
|
-
`M ${x} ${y + waveH} ` +
|
|
1257
|
-
`C ${x + w * 0.25} ${y - waveH} ${x + w * 0.75} ${y + waveH * 2} ${x + w} ${y + waveH} ` +
|
|
1258
|
-
`L ${x + w} ${y + h - waveH} ` +
|
|
1259
|
-
`C ${x + w * 0.75} ${y + h + waveH} ${x + w * 0.25} ${y + h - waveH * 2} ${x} ${y + h - waveH} Z`
|
|
1260
|
-
return `<path d="${d}" ${shapeAttrs(fill, stroke, sw)} />`
|
|
1257
|
+
f`M ${x} ${y + waveH} ` +
|
|
1258
|
+
f`C ${x + w * 0.25} ${y - waveH} ${x + w * 0.75} ${y + waveH * 2} ${x + w} ${y + waveH} ` +
|
|
1259
|
+
f`L ${x + w} ${y + h - waveH} ` +
|
|
1260
|
+
f`C ${x + w * 0.75} ${y + h + waveH} ${x + w * 0.25} ${y + h - waveH * 2} ${x} ${y + h - waveH} Z`
|
|
1261
|
+
return f`<path d="${d}" ${shapeAttrs(fill, stroke, sw)} />`
|
|
1261
1262
|
}
|
|
1262
1263
|
|
|
1263
1264
|
/** Bow-tie rectangle (stored data): left and right edges curve inward. */
|
|
@@ -1272,11 +1273,11 @@ function renderBowTie(
|
|
|
1272
1273
|
): string {
|
|
1273
1274
|
const bow = Math.min(14, w * 0.12)
|
|
1274
1275
|
const d =
|
|
1275
|
-
`M ${x + bow} ${y} L ${x + w} ${y} ` +
|
|
1276
|
-
`Q ${x + w - bow * 1.4} ${y + h / 2} ${x + w} ${y + h} ` +
|
|
1277
|
-
`L ${x + bow} ${y + h} ` +
|
|
1278
|
-
`Q ${x + bow * 1.4} ${y + h / 2} ${x + bow} ${y} Z`
|
|
1279
|
-
return `<path d="${d}" ${shapeAttrs(fill, stroke, sw)} />`
|
|
1276
|
+
f`M ${x + bow} ${y} L ${x + w} ${y} ` +
|
|
1277
|
+
f`Q ${x + w - bow * 1.4} ${y + h / 2} ${x + w} ${y + h} ` +
|
|
1278
|
+
f`L ${x + bow} ${y + h} ` +
|
|
1279
|
+
f`Q ${x + bow * 1.4} ${y + h / 2} ${x + bow} ${y} Z`
|
|
1280
|
+
return f`<path d="${d}" ${shapeAttrs(fill, stroke, sw)} />`
|
|
1280
1281
|
}
|
|
1281
1282
|
|
|
1282
1283
|
/** Delay / half-rounded rectangle: the right end is a semicircle. */
|
|
@@ -1292,10 +1293,10 @@ function renderHalfRounded(
|
|
|
1292
1293
|
const r = h / 2
|
|
1293
1294
|
const straight = Math.max(0, w - r)
|
|
1294
1295
|
const d =
|
|
1295
|
-
`M ${x} ${y} L ${x + straight} ${y} ` +
|
|
1296
|
-
`A ${r} ${r} 0 0 1 ${x + straight} ${y + h} ` +
|
|
1297
|
-
`L ${x} ${y + h} Z`
|
|
1298
|
-
return `<path d="${d}" ${shapeAttrs(fill, stroke, sw)} />`
|
|
1296
|
+
f`M ${x} ${y} L ${x + straight} ${y} ` +
|
|
1297
|
+
f`A ${r} ${r} 0 0 1 ${x + straight} ${y + h} ` +
|
|
1298
|
+
f`L ${x} ${y + h} Z`
|
|
1299
|
+
return f`<path d="${d}" ${shapeAttrs(fill, stroke, sw)} />`
|
|
1299
1300
|
}
|
|
1300
1301
|
|
|
1301
1302
|
/**
|
|
@@ -1320,28 +1321,28 @@ function renderBraces(
|
|
|
1320
1321
|
// what a brace annotation means. The rect stays only to reserve the
|
|
1321
1322
|
// label area; `_fill` is deliberately unused.
|
|
1322
1323
|
const parts = [
|
|
1323
|
-
`<rect x="${x}" y="${y}" width="${w}" height="${h}" fill="none" stroke="none" />`,
|
|
1324
|
+
f`<rect x="${x}" y="${y}" width="${w}" height="${h}" fill="none" stroke="none" />`,
|
|
1324
1325
|
]
|
|
1325
1326
|
|
|
1326
1327
|
const leftBrace =
|
|
1327
|
-
`M ${x + armW} ${y} Q ${x} ${y} ${x} ${y + h * 0.25} ` +
|
|
1328
|
-
`Q ${x} ${y + h / 2} ${x - armW * 0.4} ${y + h / 2} ` +
|
|
1329
|
-
`Q ${x} ${y + h / 2} ${x} ${y + h * 0.75} ` +
|
|
1330
|
-
`Q ${x} ${y + h} ${x + armW} ${y + h}`
|
|
1328
|
+
f`M ${x + armW} ${y} Q ${x} ${y} ${x} ${y + h * 0.25} ` +
|
|
1329
|
+
f`Q ${x} ${y + h / 2} ${x - armW * 0.4} ${y + h / 2} ` +
|
|
1330
|
+
f`Q ${x} ${y + h / 2} ${x} ${y + h * 0.75} ` +
|
|
1331
|
+
f`Q ${x} ${y + h} ${x + armW} ${y + h}`
|
|
1331
1332
|
const rightBrace =
|
|
1332
|
-
`M ${x + w - armW} ${y} Q ${x + w} ${y} ${x + w} ${y + h * 0.25} ` +
|
|
1333
|
-
`Q ${x + w} ${y + h / 2} ${x + w + armW * 0.4} ${y + h / 2} ` +
|
|
1334
|
-
`Q ${x + w} ${y + h / 2} ${x + w} ${y + h * 0.75} ` +
|
|
1335
|
-
`Q ${x + w} ${y + h} ${x + w - armW} ${y + h}`
|
|
1333
|
+
f`M ${x + w - armW} ${y} Q ${x + w} ${y} ${x + w} ${y + h * 0.25} ` +
|
|
1334
|
+
f`Q ${x + w} ${y + h / 2} ${x + w + armW * 0.4} ${y + h / 2} ` +
|
|
1335
|
+
f`Q ${x + w} ${y + h / 2} ${x + w} ${y + h * 0.75} ` +
|
|
1336
|
+
f`Q ${x + w} ${y + h} ${x + w - armW} ${y + h}`
|
|
1336
1337
|
|
|
1337
1338
|
if (side === 'left' || side === 'both') {
|
|
1338
1339
|
parts.push(
|
|
1339
|
-
`<path d="${leftBrace}" fill="none" stroke="${stroke}" stroke-width="${sw}" />`,
|
|
1340
|
+
f`<path d="${leftBrace}" fill="none" stroke="${stroke}" stroke-width="${sw}" />`,
|
|
1340
1341
|
)
|
|
1341
1342
|
}
|
|
1342
1343
|
if (side === 'right' || side === 'both') {
|
|
1343
1344
|
parts.push(
|
|
1344
|
-
`<path d="${rightBrace}" fill="none" stroke="${stroke}" stroke-width="${sw}" />`,
|
|
1345
|
+
f`<path d="${rightBrace}" fill="none" stroke="${stroke}" stroke-width="${sw}" />`,
|
|
1345
1346
|
)
|
|
1346
1347
|
}
|
|
1347
1348
|
|
|
@@ -1359,15 +1360,15 @@ function renderBolt(
|
|
|
1359
1360
|
sw: string,
|
|
1360
1361
|
): string {
|
|
1361
1362
|
const points = [
|
|
1362
|
-
`${x + w * 0.42},${y}`,
|
|
1363
|
-
`${x + w},${y}`,
|
|
1364
|
-
`${x + w * 0.62},${y + h * 0.42}`,
|
|
1365
|
-
`${x + w},${y + h * 0.42}`,
|
|
1366
|
-
`${x + w * 0.3},${y + h}`,
|
|
1367
|
-
`${x + w * 0.5},${y + h * 0.55}`,
|
|
1368
|
-
`${x},${y + h * 0.55}`,
|
|
1363
|
+
f`${x + w * 0.42},${y}`,
|
|
1364
|
+
f`${x + w},${y}`,
|
|
1365
|
+
f`${x + w * 0.62},${y + h * 0.42}`,
|
|
1366
|
+
f`${x + w},${y + h * 0.42}`,
|
|
1367
|
+
f`${x + w * 0.3},${y + h}`,
|
|
1368
|
+
f`${x + w * 0.5},${y + h * 0.55}`,
|
|
1369
|
+
f`${x},${y + h * 0.55}`,
|
|
1369
1370
|
].join(' ')
|
|
1370
|
-
return `<polygon points="${points}" ${shapeAttrs(fill, stroke, sw)} />`
|
|
1371
|
+
return f`<polygon points="${points}" ${shapeAttrs(fill, stroke, sw)} />`
|
|
1371
1372
|
}
|
|
1372
1373
|
|
|
1373
1374
|
// --- Batch 3: State diagram pseudostates ---
|
|
@@ -1377,7 +1378,7 @@ function renderStateStart(x: number, y: number, w: number, h: number): string {
|
|
|
1377
1378
|
const cx = x + w / 2
|
|
1378
1379
|
const cy = y + h / 2
|
|
1379
1380
|
const r = Math.min(w, h) / 2 - 2
|
|
1380
|
-
return `<circle cx="${cx}" cy="${cy}" r="${r}" fill="var(--_text)" stroke="none" />`
|
|
1381
|
+
return f`<circle cx="${cx}" cy="${cy}" r="${r}" fill="var(--_text)" stroke="none" />`
|
|
1381
1382
|
}
|
|
1382
1383
|
|
|
1383
1384
|
/** State end: bullseye — outer ring + inner filled circle using primary text color */
|
|
@@ -1387,9 +1388,9 @@ function renderStateEnd(x: number, y: number, w: number, h: number): string {
|
|
|
1387
1388
|
const outerR = Math.min(w, h) / 2 - 2
|
|
1388
1389
|
const innerR = outerR - 4
|
|
1389
1390
|
return (
|
|
1390
|
-
`<circle cx="${cx}" cy="${cy}" r="${outerR}" ` +
|
|
1391
|
-
`fill="none" stroke="var(--_text)" stroke-width="${STROKE_WIDTHS.innerBox * 2}" />` +
|
|
1392
|
-
`\n<circle cx="${cx}" cy="${cy}" r="${innerR}" fill="var(--_text)" stroke="none" />`
|
|
1391
|
+
f`<circle cx="${cx}" cy="${cy}" r="${outerR}" ` +
|
|
1392
|
+
f`fill="none" stroke="var(--_text)" stroke-width="${STROKE_WIDTHS.innerBox * 2}" />` +
|
|
1393
|
+
f`\n<circle cx="${cx}" cy="${cy}" r="${innerR}" fill="var(--_text)" stroke="none" />`
|
|
1393
1394
|
)
|
|
1394
1395
|
}
|
|
1395
1396
|
|
|
@@ -1423,7 +1424,7 @@ function renderNodeLabel(
|
|
|
1423
1424
|
getReadableTextColor(node.inlineStyle?.fill, 'var(--_text)'),
|
|
1424
1425
|
)
|
|
1425
1426
|
|
|
1426
|
-
let attrs = `text-anchor="middle" font-size="${fontSizes.nodeLabel}" font-weight="${FONT_WEIGHTS.nodeLabel}" fill="${textColor}"`
|
|
1427
|
+
let attrs = f`text-anchor="middle" font-size="${fontSizes.nodeLabel}" font-weight="${FONT_WEIGHTS.nodeLabel}" fill="${textColor}"`
|
|
1427
1428
|
|
|
1428
1429
|
// Per-node font-family override (from `style A font-family:...` or
|
|
1429
1430
|
// classDef/class). Emitted as an inline `style` attribute rather than a
|
|
@@ -1436,7 +1437,7 @@ function renderNodeLabel(
|
|
|
1436
1437
|
// keeps falling back to the global font stack. See issue #57.
|
|
1437
1438
|
const fontFamily = node.inlineStyle?.['font-family']
|
|
1438
1439
|
if (fontFamily) {
|
|
1439
|
-
attrs += ` style="font-family: ${escapeAttr(fontFamily)};"`
|
|
1440
|
+
attrs += f` style="font-family: ${escapeAttr(fontFamily)};"`
|
|
1440
1441
|
}
|
|
1441
1442
|
|
|
1442
1443
|
return renderMultilineText(node.label, cx, cy, fontSizes.nodeLabel, attrs)
|
|
@@ -1480,6 +1481,6 @@ export function withDataSrc(
|
|
|
1480
1481
|
if (source === undefined) return svgTag
|
|
1481
1482
|
return svgTag.replace(
|
|
1482
1483
|
'<svg ',
|
|
1483
|
-
`<svg data-src="${escapeMultilineAttr(source)}" `,
|
|
1484
|
+
f`<svg data-src="${escapeMultilineAttr(source)}" `,
|
|
1484
1485
|
)
|
|
1485
1486
|
}
|