@nowline/renderer 0.5.1 → 0.6.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.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/svg/render.d.ts +18 -4
- package/dist/svg/render.d.ts.map +1 -1
- package/dist/svg/render.js +66 -62
- package/dist/svg/render.js.map +1 -1
- package/package.json +3 -3
- package/src/index.ts +1 -0
- package/src/svg/render.ts +101 -56
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type { AssetBytes, AssetResolver, RenderOptions, } from './svg/render.js';
|
|
1
|
+
export type { AssetBytes, AssetResolver, FontFamilies, RenderOptions, } from './svg/render.js';
|
|
2
2
|
export { renderSvg } from './svg/render.js';
|
|
3
3
|
export { sanitizeSvg } from './svg/sanitize.js';
|
|
4
4
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACR,UAAU,EACV,aAAa,EACb,aAAa,GAChB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACR,UAAU,EACV,aAAa,EACb,YAAY,EACZ,aAAa,GAChB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC"}
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC"}
|
package/dist/svg/render.d.ts
CHANGED
|
@@ -4,17 +4,31 @@ export interface AssetBytes {
|
|
|
4
4
|
mime: string;
|
|
5
5
|
}
|
|
6
6
|
export type AssetResolver = (ref: string) => Promise<AssetBytes>;
|
|
7
|
+
/**
|
|
8
|
+
* Per-role `font-family` strings the renderer stamps onto `<text>` elements.
|
|
9
|
+
* Defaults to the shared, portable `FONT_STACK` (generic CSS stacks). Raster
|
|
10
|
+
* and preview callers override this with a *pinned* family (e.g. the bundled
|
|
11
|
+
* `DejaVu Sans` / `DejaVu Sans Mono`) so the rendered SVG names exactly the
|
|
12
|
+
* font that resvg / the webview `@font-face` actually provide — the WYSIWYG
|
|
13
|
+
* contract. The `.svg` file export keeps the default portable stack.
|
|
14
|
+
*/
|
|
15
|
+
export type FontFamilies = Record<'sans' | 'serif' | 'mono', string>;
|
|
7
16
|
export interface RenderOptions {
|
|
8
17
|
assetResolver?: AssetResolver;
|
|
9
18
|
noLinks?: boolean;
|
|
10
19
|
strict?: boolean;
|
|
11
20
|
warn?: (message: string) => void;
|
|
12
21
|
idPrefix?: string;
|
|
22
|
+
/**
|
|
23
|
+
* Override per-role `font-family` strings. Defaults to the portable
|
|
24
|
+
* `FONT_STACK`. Set to a pinned family for raster/preview WYSIWYG.
|
|
25
|
+
*/
|
|
26
|
+
fontFamilies?: FontFamilies;
|
|
13
27
|
}
|
|
14
|
-
declare function renderHeader(h: PositionedHeader, idPrefix: string, palette: Theme): string;
|
|
15
|
-
declare function renderTimeline(t: PositionedTimelineScale, palette: Theme): string;
|
|
16
|
-
declare function renderItem(i: PositionedItem, options: RenderOptions, idPrefix: string, palette: Theme): string;
|
|
17
|
-
declare function renderSwimlane(s: PositionedSwimlane, options: RenderOptions, idPrefix: string, palette: Theme): string;
|
|
28
|
+
declare function renderHeader(h: PositionedHeader, idPrefix: string, palette: Theme, fonts: FontFamilies): string;
|
|
29
|
+
declare function renderTimeline(t: PositionedTimelineScale, palette: Theme, fonts: FontFamilies): string;
|
|
30
|
+
declare function renderItem(i: PositionedItem, options: RenderOptions, idPrefix: string, palette: Theme, fonts: FontFamilies): string;
|
|
31
|
+
declare function renderSwimlane(s: PositionedSwimlane, options: RenderOptions, idPrefix: string, palette: Theme, fonts: FontFamilies): string;
|
|
18
32
|
declare function renderEdge(e: PositionedDependencyEdge, palette: Theme): string;
|
|
19
33
|
export declare function renderSvg(model: PositionedRoadmap, options?: RenderOptions): Promise<string>;
|
|
20
34
|
export declare const __internal: {
|
package/dist/svg/render.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../../src/svg/render.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAKR,wBAAwB,EAGxB,gBAAgB,EAEhB,cAAc,EAId,iBAAiB,EACjB,kBAAkB,EAClB,uBAAuB,EAGvB,KAAK,EACR,MAAM,iBAAiB,CAAC;AAsEzB,MAAM,WAAW,UAAU;IACvB,KAAK,EAAE,UAAU,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,MAAM,aAAa,GAAG,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,UAAU,CAAC,CAAC;AAEjE,MAAM,WAAW,aAAa;IAC1B,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IAEjC,QAAQ,CAAC,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../../src/svg/render.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAKR,wBAAwB,EAGxB,gBAAgB,EAEhB,cAAc,EAId,iBAAiB,EACjB,kBAAkB,EAClB,uBAAuB,EAGvB,KAAK,EACR,MAAM,iBAAiB,CAAC;AAsEzB,MAAM,WAAW,UAAU;IACvB,KAAK,EAAE,UAAU,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,MAAM,aAAa,GAAG,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,UAAU,CAAC,CAAC;AAEjE;;;;;;;GAOG;AACH,MAAM,MAAM,YAAY,GAAG,MAAM,CAAC,MAAM,GAAG,OAAO,GAAG,MAAM,EAAE,MAAM,CAAC,CAAC;AAErE,MAAM,WAAW,aAAa;IAC1B,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IAEjC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;OAGG;IACH,YAAY,CAAC,EAAE,YAAY,CAAC;CAC/B;AAkWD,iBAAS,YAAY,CACjB,CAAC,EAAE,gBAAgB,EACnB,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,KAAK,EACd,KAAK,EAAE,YAAY,GACpB,MAAM,CAyER;AA6ED,iBAAS,cAAc,CAAC,CAAC,EAAE,uBAAuB,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,YAAY,GAAG,MAAM,CA2G/F;AAwID,iBAAS,UAAU,CACf,CAAC,EAAE,cAAc,EACjB,OAAO,EAAE,aAAa,EACtB,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,KAAK,EACd,KAAK,EAAE,YAAY,GACpB,MAAM,CA8SR;AAmRD,iBAAS,cAAc,CACnB,CAAC,EAAE,kBAAkB,EACrB,OAAO,EAAE,aAAa,EACtB,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,KAAK,EACd,KAAK,EAAE,YAAY,GACpB,MAAM,CAyHR;AAkHD,iBAAS,UAAU,CAAC,CAAC,EAAE,wBAAwB,EAAE,OAAO,EAAE,KAAK,GAAG,MAAM,CAmBvE;AA6YD,wBAAsB,SAAS,CAC3B,KAAK,EAAE,iBAAiB,EACxB,OAAO,GAAE,aAAkB,GAC5B,OAAO,CAAC,MAAM,CAAC,CAyHjB;AAGD,eAAO,MAAM,UAAU;;;;;;CAMtB,CAAC"}
|
package/dist/svg/render.js
CHANGED
|
@@ -23,9 +23,9 @@ const WEIGHT_NUM = {
|
|
|
23
23
|
function textSizePx(bucket) {
|
|
24
24
|
return TEXT_SIZE_PX[bucket] ?? 14;
|
|
25
25
|
}
|
|
26
|
-
function fontAttrs(style, overrideSize) {
|
|
26
|
+
function fontAttrs(style, fonts, overrideSize) {
|
|
27
27
|
return {
|
|
28
|
-
'font-family':
|
|
28
|
+
'font-family': fonts[style.font],
|
|
29
29
|
'font-size': overrideSize ?? textSizePx(style.textSize),
|
|
30
30
|
'font-weight': WEIGHT_NUM[style.weight] ?? 400,
|
|
31
31
|
'font-style': style.italic ? 'italic' : 'normal',
|
|
@@ -302,7 +302,7 @@ function rectFrame(x, y, w, h, style, extra = {}) {
|
|
|
302
302
|
...extra,
|
|
303
303
|
});
|
|
304
304
|
}
|
|
305
|
-
function renderHeader(h, idPrefix, palette) {
|
|
305
|
+
function renderHeader(h, idPrefix, palette, fonts) {
|
|
306
306
|
// The layout has already sized the card to its (wrapped) text content
|
|
307
307
|
// and stashed the bounds in `h.cardBox`, with `h.titleLines` /
|
|
308
308
|
// `h.authorLines` ready to render line-by-line. See sizeBesideHeader
|
|
@@ -333,7 +333,7 @@ function renderHeader(h, idPrefix, palette) {
|
|
|
333
333
|
titleParts.push(textTag({
|
|
334
334
|
x: num(cardX + HEADER_CARD_PADDING_X),
|
|
335
335
|
y: num(cardY + HEADER_CARD_PADDING_TOP + i * HEADER_TITLE_LINE_HEIGHT_PX),
|
|
336
|
-
'font-family':
|
|
336
|
+
'font-family': fonts[h.style.font],
|
|
337
337
|
'font-size': HEADER_TITLE_FONT_SIZE_PX,
|
|
338
338
|
'font-weight': 600,
|
|
339
339
|
fill: h.style.text,
|
|
@@ -351,7 +351,7 @@ function renderHeader(h, idPrefix, palette) {
|
|
|
351
351
|
y: num(lastTitleY +
|
|
352
352
|
HEADER_TITLE_TO_AUTHOR_GAP_PX +
|
|
353
353
|
j * HEADER_AUTHOR_LINE_HEIGHT_PX),
|
|
354
|
-
'font-family':
|
|
354
|
+
'font-family': fonts[h.style.font],
|
|
355
355
|
'font-size': HEADER_AUTHOR_FONT_SIZE_PX,
|
|
356
356
|
fill: authorColor,
|
|
357
357
|
}, line));
|
|
@@ -431,7 +431,7 @@ function renderGridLines(t, swimlaneTopY, palette) {
|
|
|
431
431
|
}
|
|
432
432
|
return tag('g', { 'data-layer': 'grid' }, parts.join(''));
|
|
433
433
|
}
|
|
434
|
-
function renderTimeline(t, palette) {
|
|
434
|
+
function renderTimeline(t, palette, fonts) {
|
|
435
435
|
const panelFill = palette.timeline.panelFill;
|
|
436
436
|
const borderColor = palette.timeline.border;
|
|
437
437
|
const labelColor = palette.timeline.labelText;
|
|
@@ -505,7 +505,7 @@ function renderTimeline(t, palette) {
|
|
|
505
505
|
parts.push(textTag({
|
|
506
506
|
x: num(tick.labelX),
|
|
507
507
|
y: num(tickPanelY + TIMELINE_TICK_LABEL_BASELINE_OFFSET_PX),
|
|
508
|
-
'font-family':
|
|
508
|
+
'font-family': fonts.sans,
|
|
509
509
|
'font-size': 10,
|
|
510
510
|
fill: labelColor,
|
|
511
511
|
'text-anchor': 'middle',
|
|
@@ -515,7 +515,7 @@ function renderTimeline(t, palette) {
|
|
|
515
515
|
parts.push(textTag({
|
|
516
516
|
x: num(tick.labelX),
|
|
517
517
|
y: num(bottomTickPanelY + TIMELINE_TICK_LABEL_BASELINE_OFFSET_PX),
|
|
518
|
-
'font-family':
|
|
518
|
+
'font-family': fonts.sans,
|
|
519
519
|
'font-size': 10,
|
|
520
520
|
fill: labelColor,
|
|
521
521
|
'text-anchor': 'middle',
|
|
@@ -524,7 +524,7 @@ function renderTimeline(t, palette) {
|
|
|
524
524
|
}
|
|
525
525
|
return tag('g', { 'data-layer': 'timeline' }, parts.join(''));
|
|
526
526
|
}
|
|
527
|
-
function renderNowline(n, palette) {
|
|
527
|
+
function renderNowline(n, palette, fonts) {
|
|
528
528
|
if (!n)
|
|
529
529
|
return '';
|
|
530
530
|
const color = palette.nowline.stroke;
|
|
@@ -549,7 +549,7 @@ function renderNowline(n, palette) {
|
|
|
549
549
|
// The squared edge IS the line; the rounded edge points into the
|
|
550
550
|
// chart, so the pill always hugs the line and never overflows.
|
|
551
551
|
const pillBg = renderNowPillBg(n, color);
|
|
552
|
-
const label = renderNowPillLabel(n, labelTextColor);
|
|
552
|
+
const label = renderNowPillLabel(n, labelTextColor, fonts);
|
|
553
553
|
return tag('g', { 'data-layer': 'nowline' }, line + pillBg + label);
|
|
554
554
|
}
|
|
555
555
|
/**
|
|
@@ -625,7 +625,7 @@ function renderNowPillBg(n, color) {
|
|
|
625
625
|
].join(' ');
|
|
626
626
|
return tag('path', { d, fill: color });
|
|
627
627
|
}
|
|
628
|
-
function renderNowPillLabel(n, labelTextColor) {
|
|
628
|
+
function renderNowPillLabel(n, labelTextColor, fonts) {
|
|
629
629
|
const baselineY = n.pillTopY + NOW_PILL_LABEL_BASELINE_OFFSET_PX;
|
|
630
630
|
const edgeX = squaredEdgeX(n);
|
|
631
631
|
let labelX;
|
|
@@ -645,14 +645,14 @@ function renderNowPillLabel(n, labelTextColor) {
|
|
|
645
645
|
return textTag({
|
|
646
646
|
x: num(labelX),
|
|
647
647
|
y: num(baselineY),
|
|
648
|
-
'font-family':
|
|
648
|
+
'font-family': fonts.sans,
|
|
649
649
|
'font-size': NOW_PILL_LABEL_FONT_SIZE_PX,
|
|
650
650
|
'font-weight': 700,
|
|
651
651
|
fill: labelTextColor,
|
|
652
652
|
'text-anchor': textAnchor,
|
|
653
653
|
}, n.label);
|
|
654
654
|
}
|
|
655
|
-
function renderItem(i, options, idPrefix, palette) {
|
|
655
|
+
function renderItem(i, options, idPrefix, palette, fonts) {
|
|
656
656
|
const parts = [];
|
|
657
657
|
const shadow = shadowFilterUrl(idPrefix, i.style.shadow);
|
|
658
658
|
parts.push(rectFrame(i.box.x, i.box.y, i.box.width, i.box.height, i.style, {
|
|
@@ -747,7 +747,7 @@ function renderItem(i, options, idPrefix, palette) {
|
|
|
747
747
|
parts.push(textTag({
|
|
748
748
|
x: num(captionX),
|
|
749
749
|
y: num(i.box.y + ITEM_CAPTION_TITLE_BASELINE_OFFSET_PX),
|
|
750
|
-
'font-family':
|
|
750
|
+
'font-family': fonts[i.style.font],
|
|
751
751
|
'font-size': ITEM_CAPTION_TITLE_FONT_SIZE_PX,
|
|
752
752
|
'font-weight': 600,
|
|
753
753
|
fill: titleColor,
|
|
@@ -767,7 +767,7 @@ function renderItem(i, options, idPrefix, palette) {
|
|
|
767
767
|
x: captionX,
|
|
768
768
|
baselineY: i.box.y + ITEM_CAPTION_META_BASELINE_OFFSET_PX,
|
|
769
769
|
fontSize: ITEM_CAPTION_META_FONT_SIZE_PX,
|
|
770
|
-
fontFamily:
|
|
770
|
+
fontFamily: fonts[i.style.font],
|
|
771
771
|
color: metaColor,
|
|
772
772
|
}));
|
|
773
773
|
}
|
|
@@ -795,7 +795,7 @@ function renderItem(i, options, idPrefix, palette) {
|
|
|
795
795
|
parts.push(textTag({
|
|
796
796
|
x: num(fx),
|
|
797
797
|
y: num(footnoteY),
|
|
798
|
-
'font-family':
|
|
798
|
+
'font-family': fonts.sans,
|
|
799
799
|
'font-size': 10,
|
|
800
800
|
'font-weight': 700,
|
|
801
801
|
fill: captionOutsideTextColor,
|
|
@@ -810,7 +810,7 @@ function renderItem(i, options, idPrefix, palette) {
|
|
|
810
810
|
parts.push(textTag({
|
|
811
811
|
x: num(fx),
|
|
812
812
|
y: num(footnoteY),
|
|
813
|
-
'font-family':
|
|
813
|
+
'font-family': fonts.sans,
|
|
814
814
|
'font-size': 10,
|
|
815
815
|
'font-weight': 700,
|
|
816
816
|
fill: i.style.text,
|
|
@@ -887,7 +887,7 @@ function renderItem(i, options, idPrefix, palette) {
|
|
|
887
887
|
parts.push(textTag({
|
|
888
888
|
x: num(i.overflowBox.x + i.overflowBox.width / 2),
|
|
889
889
|
y: num(i.overflowBox.y + i.overflowBox.height / 2 + 3),
|
|
890
|
-
'font-family':
|
|
890
|
+
'font-family': fonts.sans,
|
|
891
891
|
'font-size': 9,
|
|
892
892
|
'font-weight': 700,
|
|
893
893
|
fill: captionColor,
|
|
@@ -912,13 +912,13 @@ function renderItem(i, options, idPrefix, palette) {
|
|
|
912
912
|
parts.push(textTag({
|
|
913
913
|
x: num(chip.box.x + chip.box.width / 2),
|
|
914
914
|
y: num(chip.box.y + chip.box.height / 2 + 3),
|
|
915
|
-
...fontAttrs(chip.style, TEXT_SIZE_PX.xs),
|
|
915
|
+
...fontAttrs(chip.style, fonts, TEXT_SIZE_PX.xs),
|
|
916
916
|
'text-anchor': 'middle',
|
|
917
917
|
}, chip.text));
|
|
918
918
|
}
|
|
919
919
|
return tag('g', { 'data-layer': 'item', 'data-id': i.id ?? null }, parts.join(''));
|
|
920
920
|
}
|
|
921
|
-
function renderGroup(g, options, idPrefix, palette) {
|
|
921
|
+
function renderGroup(g, options, idPrefix, palette, fonts) {
|
|
922
922
|
const parts = [];
|
|
923
923
|
const hasFill = g.style.bg !== 'none' && g.style.bg !== '#ffffff';
|
|
924
924
|
if (hasFill) {
|
|
@@ -966,7 +966,7 @@ function renderGroup(g, options, idPrefix, palette) {
|
|
|
966
966
|
parts.push(textTag({
|
|
967
967
|
x: num(tabX + GROUP_TITLE_TAB_PAD_X_PX),
|
|
968
968
|
y: num(tabY + GROUP_TITLE_TAB_LABEL_BASELINE_OFFSET_PX),
|
|
969
|
-
'font-family':
|
|
969
|
+
'font-family': fonts[g.style.font],
|
|
970
970
|
'font-size': GROUP_TITLE_TAB_LABEL_FONT_SIZE_PX,
|
|
971
971
|
'font-weight': 600,
|
|
972
972
|
fill: '#ffffff',
|
|
@@ -1008,7 +1008,7 @@ function renderGroup(g, options, idPrefix, palette) {
|
|
|
1008
1008
|
parts.push(textTag({
|
|
1009
1009
|
x: num(g.box.x + 6),
|
|
1010
1010
|
y: num(g.box.y - 2),
|
|
1011
|
-
...fontAttrs(g.style, TEXT_SIZE_PX.xs),
|
|
1011
|
+
...fontAttrs(g.style, fonts, TEXT_SIZE_PX.xs),
|
|
1012
1012
|
'fill-opacity': 0.7,
|
|
1013
1013
|
}, g.title));
|
|
1014
1014
|
}
|
|
@@ -1019,12 +1019,12 @@ function renderGroup(g, options, idPrefix, palette) {
|
|
|
1019
1019
|
// bounding box.
|
|
1020
1020
|
parts.push(renderInlineDatePins(g.inlineDatePins, g.style.fg));
|
|
1021
1021
|
for (const c of g.children) {
|
|
1022
|
-
parts.push(renderTrackChild(c, options, idPrefix, palette));
|
|
1022
|
+
parts.push(renderTrackChild(c, options, idPrefix, palette, fonts));
|
|
1023
1023
|
}
|
|
1024
1024
|
void palette;
|
|
1025
1025
|
return tag('g', { 'data-layer': 'group', 'data-id': g.id ?? null }, parts.join(''));
|
|
1026
1026
|
}
|
|
1027
|
-
function renderParallel(p, options, idPrefix, palette) {
|
|
1027
|
+
function renderParallel(p, options, idPrefix, palette, fonts) {
|
|
1028
1028
|
const parts = [];
|
|
1029
1029
|
// `bracket: solid|dashed` parallels render explicit [ ] brackets framing
|
|
1030
1030
|
// the nested tracks with 12 px vertical padding above/below.
|
|
@@ -1057,7 +1057,7 @@ function renderParallel(p, options, idPrefix, palette) {
|
|
|
1057
1057
|
parts.push(textTag({
|
|
1058
1058
|
x: num(p.box.x + 4),
|
|
1059
1059
|
y: num(p.box.y - 2),
|
|
1060
|
-
...fontAttrs(p.style, TEXT_SIZE_PX.xs),
|
|
1060
|
+
...fontAttrs(p.style, fonts, TEXT_SIZE_PX.xs),
|
|
1061
1061
|
'fill-opacity': 0.7,
|
|
1062
1062
|
}, p.title));
|
|
1063
1063
|
}
|
|
@@ -1065,16 +1065,16 @@ function renderParallel(p, options, idPrefix, palette) {
|
|
|
1065
1065
|
// `before:DATE`). Painted before children so child bars sit on top.
|
|
1066
1066
|
parts.push(renderInlineDatePins(p.inlineDatePins, p.style.fg));
|
|
1067
1067
|
for (const c of p.children) {
|
|
1068
|
-
parts.push(renderTrackChild(c, options, idPrefix, palette));
|
|
1068
|
+
parts.push(renderTrackChild(c, options, idPrefix, palette, fonts));
|
|
1069
1069
|
}
|
|
1070
1070
|
return tag('g', { 'data-layer': 'parallel', 'data-id': p.id ?? null }, parts.join(''));
|
|
1071
1071
|
}
|
|
1072
|
-
function renderTrackChild(c, options, idPrefix, palette) {
|
|
1072
|
+
function renderTrackChild(c, options, idPrefix, palette, fonts) {
|
|
1073
1073
|
if (c.kind === 'item')
|
|
1074
|
-
return renderItem(c, options, idPrefix, palette);
|
|
1074
|
+
return renderItem(c, options, idPrefix, palette, fonts);
|
|
1075
1075
|
if (c.kind === 'group')
|
|
1076
|
-
return renderGroup(c, options, idPrefix, palette);
|
|
1077
|
-
return renderParallel(c, options, idPrefix, palette);
|
|
1076
|
+
return renderGroup(c, options, idPrefix, palette, fonts);
|
|
1077
|
+
return renderParallel(c, options, idPrefix, palette, fonts);
|
|
1078
1078
|
}
|
|
1079
1079
|
// Renders only the swimlane's background tint rect. Emitted before the
|
|
1080
1080
|
// chart-body grid lines so those lines visibly span the full chart width.
|
|
@@ -1136,7 +1136,7 @@ function renderSwimlaneBg(s, palette) {
|
|
|
1136
1136
|
'stroke-width': 1,
|
|
1137
1137
|
}));
|
|
1138
1138
|
}
|
|
1139
|
-
function renderSwimlane(s, options, idPrefix, palette) {
|
|
1139
|
+
function renderSwimlane(s, options, idPrefix, palette, fonts) {
|
|
1140
1140
|
const tabFill = palette.swimlane.tabFill;
|
|
1141
1141
|
const tabStroke = palette.swimlane.tabStroke;
|
|
1142
1142
|
const tabText = palette.swimlane.tabText;
|
|
@@ -1181,7 +1181,7 @@ function renderSwimlane(s, options, idPrefix, palette) {
|
|
|
1181
1181
|
parts.push(textTag({
|
|
1182
1182
|
x: num(tab.titleX),
|
|
1183
1183
|
y: num(labelY),
|
|
1184
|
-
'font-family':
|
|
1184
|
+
'font-family': fonts[s.style.font],
|
|
1185
1185
|
'font-size': 12,
|
|
1186
1186
|
'font-weight': 600,
|
|
1187
1187
|
fill: tabText,
|
|
@@ -1190,7 +1190,7 @@ function renderSwimlane(s, options, idPrefix, palette) {
|
|
|
1190
1190
|
parts.push(textTag({
|
|
1191
1191
|
x: num(tab.ownerX),
|
|
1192
1192
|
y: num(labelY),
|
|
1193
|
-
'font-family':
|
|
1193
|
+
'font-family': fonts[s.style.font],
|
|
1194
1194
|
'font-size': 10,
|
|
1195
1195
|
fill: ownerText,
|
|
1196
1196
|
}, `owner: ${s.owner}`));
|
|
@@ -1200,13 +1200,13 @@ function renderSwimlane(s, options, idPrefix, palette) {
|
|
|
1200
1200
|
// item-level suffixes (m6) so multiplier / built-in SVG /
|
|
1201
1201
|
// inline literal / dereferenced-custom-glyph paths stay
|
|
1202
1202
|
// consistent across both contexts.
|
|
1203
|
-
parts.push(renderCapacitySuffix(s.capacity, undefined, tab.badgeX, labelY, LANE_BADGE_FONT_SIZE_PX,
|
|
1203
|
+
parts.push(renderCapacitySuffix(s.capacity, undefined, tab.badgeX, labelY, LANE_BADGE_FONT_SIZE_PX, fonts[s.style.font], ownerText));
|
|
1204
1204
|
}
|
|
1205
1205
|
if (footnoteIndicatorText) {
|
|
1206
1206
|
parts.push(textTag({
|
|
1207
1207
|
x: num(tab.footnoteRightX),
|
|
1208
1208
|
y: num(tabY + 14),
|
|
1209
|
-
'font-family':
|
|
1209
|
+
'font-family': fonts.sans,
|
|
1210
1210
|
'font-size': LANE_BADGE_FONT_SIZE_PX,
|
|
1211
1211
|
'font-weight': 700,
|
|
1212
1212
|
fill: footnoteColor,
|
|
@@ -1215,7 +1215,7 @@ function renderSwimlane(s, options, idPrefix, palette) {
|
|
|
1215
1215
|
}
|
|
1216
1216
|
}
|
|
1217
1217
|
for (const c of s.children) {
|
|
1218
|
-
parts.push(renderTrackChild(c, options, idPrefix, palette));
|
|
1218
|
+
parts.push(renderTrackChild(c, options, idPrefix, palette, fonts));
|
|
1219
1219
|
}
|
|
1220
1220
|
// m13: tri-state utilization underline along the band's bottom edge.
|
|
1221
1221
|
// Painted after items so it overlays any item that happens to extend
|
|
@@ -1224,7 +1224,7 @@ function renderSwimlane(s, options, idPrefix, palette) {
|
|
|
1224
1224
|
parts.push(renderLaneUtilization(s, palette));
|
|
1225
1225
|
return tag('g', { 'data-layer': 'swimlane', 'data-id': s.id ?? null }, parts.join(''));
|
|
1226
1226
|
}
|
|
1227
|
-
function renderAnchor(a, palette) {
|
|
1227
|
+
function renderAnchor(a, palette, fonts) {
|
|
1228
1228
|
const size = a.radius;
|
|
1229
1229
|
const cx = a.center.x;
|
|
1230
1230
|
const cy = a.center.y;
|
|
@@ -1249,7 +1249,7 @@ function renderAnchor(a, palette) {
|
|
|
1249
1249
|
const labelAttrs = {
|
|
1250
1250
|
x: num(labelX),
|
|
1251
1251
|
y: num(cy + 4),
|
|
1252
|
-
'font-family':
|
|
1252
|
+
'font-family': fonts.sans,
|
|
1253
1253
|
'font-size': 10,
|
|
1254
1254
|
fill: labelColor,
|
|
1255
1255
|
};
|
|
@@ -1270,7 +1270,7 @@ function renderAnchorCutLine(a, palette) {
|
|
|
1270
1270
|
'stroke-dasharray': '1 3',
|
|
1271
1271
|
});
|
|
1272
1272
|
}
|
|
1273
|
-
function renderMilestone(m, palette) {
|
|
1273
|
+
function renderMilestone(m, palette, fonts) {
|
|
1274
1274
|
const cx = m.center.x;
|
|
1275
1275
|
const cy = m.center.y;
|
|
1276
1276
|
const r = m.radius;
|
|
@@ -1289,7 +1289,7 @@ function renderMilestone(m, palette) {
|
|
|
1289
1289
|
const labelAttrs = {
|
|
1290
1290
|
x: num(labelX),
|
|
1291
1291
|
y: num(cy + 4),
|
|
1292
|
-
'font-family':
|
|
1292
|
+
'font-family': fonts.sans,
|
|
1293
1293
|
'font-size': 10,
|
|
1294
1294
|
'font-weight': 600,
|
|
1295
1295
|
fill: labelColor,
|
|
@@ -1384,7 +1384,7 @@ function roundedOrthogonalPath(points, radius) {
|
|
|
1384
1384
|
}
|
|
1385
1385
|
return parts.join(' ');
|
|
1386
1386
|
}
|
|
1387
|
-
function renderFootnotes(f, idPrefix, palette) {
|
|
1387
|
+
function renderFootnotes(f, idPrefix, palette, fonts) {
|
|
1388
1388
|
if (f.entries.length === 0)
|
|
1389
1389
|
return '';
|
|
1390
1390
|
const panelFill = palette.footnotePanel.fill;
|
|
@@ -1409,7 +1409,7 @@ function renderFootnotes(f, idPrefix, palette) {
|
|
|
1409
1409
|
parts.push(textTag({
|
|
1410
1410
|
x: num(f.box.x + FOOTNOTE_PANEL_PADDING_PX),
|
|
1411
1411
|
y: num(f.box.y + FOOTNOTE_HEADER_BASELINE_OFFSET_PX),
|
|
1412
|
-
'font-family':
|
|
1412
|
+
'font-family': fonts.sans,
|
|
1413
1413
|
'font-size': 12,
|
|
1414
1414
|
'font-weight': 700,
|
|
1415
1415
|
fill: headerColor,
|
|
@@ -1424,7 +1424,7 @@ function renderFootnotes(f, idPrefix, palette) {
|
|
|
1424
1424
|
parts.push(textTag({
|
|
1425
1425
|
x: num(numberX),
|
|
1426
1426
|
y: num(y),
|
|
1427
|
-
'font-family':
|
|
1427
|
+
'font-family': fonts.sans,
|
|
1428
1428
|
'font-size': 10,
|
|
1429
1429
|
'font-weight': 700,
|
|
1430
1430
|
fill: numberColor,
|
|
@@ -1432,7 +1432,7 @@ function renderFootnotes(f, idPrefix, palette) {
|
|
|
1432
1432
|
parts.push(textTag({
|
|
1433
1433
|
x: num(titleX),
|
|
1434
1434
|
y: num(y),
|
|
1435
|
-
'font-family':
|
|
1435
|
+
'font-family': fonts.sans,
|
|
1436
1436
|
'font-size': 11,
|
|
1437
1437
|
'font-weight': 600,
|
|
1438
1438
|
fill: titleColor,
|
|
@@ -1441,7 +1441,7 @@ function renderFootnotes(f, idPrefix, palette) {
|
|
|
1441
1441
|
parts.push(textTag({
|
|
1442
1442
|
x: num(titleX + Math.max(120, e.title.length * 6)),
|
|
1443
1443
|
y: num(y),
|
|
1444
|
-
'font-family':
|
|
1444
|
+
'font-family': fonts.sans,
|
|
1445
1445
|
'font-size': 11,
|
|
1446
1446
|
fill: descColor,
|
|
1447
1447
|
}, `— ${e.description}`));
|
|
@@ -1449,7 +1449,7 @@ function renderFootnotes(f, idPrefix, palette) {
|
|
|
1449
1449
|
});
|
|
1450
1450
|
return tag('g', { 'data-layer': 'footnotes' }, parts.join(''));
|
|
1451
1451
|
}
|
|
1452
|
-
function renderIncludeRegion(r, options, idPrefix, palette) {
|
|
1452
|
+
function renderIncludeRegion(r, options, idPrefix, palette, fonts) {
|
|
1453
1453
|
const border = palette.includeRegion.border;
|
|
1454
1454
|
const fill = palette.includeRegion.fill;
|
|
1455
1455
|
const tabFill = palette.includeRegion.tabFill;
|
|
@@ -1495,7 +1495,7 @@ function renderIncludeRegion(r, options, idPrefix, palette) {
|
|
|
1495
1495
|
const tabLabel = textTag({
|
|
1496
1496
|
x: num(chrome.tabLabelX),
|
|
1497
1497
|
y: num(tabY + FRAME_TAB_LABEL_BASELINE_OFFSET_PX),
|
|
1498
|
-
'font-family':
|
|
1498
|
+
'font-family': fonts.sans,
|
|
1499
1499
|
'font-size': 11,
|
|
1500
1500
|
'font-weight': 600,
|
|
1501
1501
|
fill: tabText,
|
|
@@ -1548,13 +1548,13 @@ function renderIncludeRegion(r, options, idPrefix, palette) {
|
|
|
1548
1548
|
const sourceText = textTag({
|
|
1549
1549
|
x: num(chrome.sourceTextX),
|
|
1550
1550
|
y: num(sourceTextY),
|
|
1551
|
-
'font-family':
|
|
1551
|
+
'font-family': fonts.mono,
|
|
1552
1552
|
'font-size': sourceFontSize,
|
|
1553
1553
|
fill: badgeText,
|
|
1554
1554
|
}, r.sourcePath);
|
|
1555
1555
|
// Nested swimlanes (laid out by buildIncludeRegions against the parent's timeline).
|
|
1556
1556
|
const nested = r.nestedSwimlanes
|
|
1557
|
-
.map((s) => renderSwimlane(s, options, idPrefix, palette))
|
|
1557
|
+
.map((s) => renderSwimlane(s, options, idPrefix, palette, fonts))
|
|
1558
1558
|
.join('');
|
|
1559
1559
|
return tag('g', { 'data-layer': 'include' }, region + nested + tab + tabLabel + badge + glyph + sourceHalo + sourceText);
|
|
1560
1560
|
}
|
|
@@ -1564,7 +1564,7 @@ function renderIncludeRegion(r, options, idPrefix, palette) {
|
|
|
1564
1564
|
// entire string is clickable. Glyph anatomy (positions, widths, scale)
|
|
1565
1565
|
// lives in `themes/shared.ts` (`ATTRIBUTION_*`); the layout reserves a
|
|
1566
1566
|
// box of exactly that size at canvas-bottom-right.
|
|
1567
|
-
function renderAttributionMark(model) {
|
|
1567
|
+
function renderAttributionMark(model, fonts) {
|
|
1568
1568
|
const muted = model.palette.attribution.mark;
|
|
1569
1569
|
const accent = model.palette.attribution.link;
|
|
1570
1570
|
if (model.swimlanes.length === 0)
|
|
@@ -1578,7 +1578,7 @@ function renderAttributionMark(model) {
|
|
|
1578
1578
|
const inner = textTag({
|
|
1579
1579
|
x: '0',
|
|
1580
1580
|
y: baselineY,
|
|
1581
|
-
'font-family':
|
|
1581
|
+
'font-family': fonts.sans,
|
|
1582
1582
|
'font-size': ATTRIBUTION_PREFIX_FONT_SIZE,
|
|
1583
1583
|
'font-weight': 400,
|
|
1584
1584
|
fill: muted,
|
|
@@ -1586,7 +1586,7 @@ function renderAttributionMark(model) {
|
|
|
1586
1586
|
textTag({
|
|
1587
1587
|
x: ATTRIBUTION_NOW_LOGICAL_X,
|
|
1588
1588
|
y: baselineY,
|
|
1589
|
-
'font-family':
|
|
1589
|
+
'font-family': fonts.sans,
|
|
1590
1590
|
'font-size': ATTRIBUTION_WORDMARK_FONT_SIZE,
|
|
1591
1591
|
'font-weight': 700,
|
|
1592
1592
|
fill: muted,
|
|
@@ -1601,7 +1601,7 @@ function renderAttributionMark(model) {
|
|
|
1601
1601
|
textTag({
|
|
1602
1602
|
x: ATTRIBUTION_INE_LOGICAL_X,
|
|
1603
1603
|
y: baselineY,
|
|
1604
|
-
'font-family':
|
|
1604
|
+
'font-family': fonts.sans,
|
|
1605
1605
|
'font-size': ATTRIBUTION_WORDMARK_FONT_SIZE,
|
|
1606
1606
|
'font-weight': 400,
|
|
1607
1607
|
fill: muted,
|
|
@@ -1672,6 +1672,10 @@ export async function renderSvg(model, options = {}) {
|
|
|
1672
1672
|
const ids = new IdGenerator(options.idPrefix ?? 'nl');
|
|
1673
1673
|
const idPrefix = ids.next('root');
|
|
1674
1674
|
const palette = model.palette;
|
|
1675
|
+
// Per-role family strings stamped onto every <text>. Defaults to the
|
|
1676
|
+
// portable FONT_STACK; raster/preview callers pass a pinned bundled
|
|
1677
|
+
// family so the SVG names exactly the font the consumer provides.
|
|
1678
|
+
const fonts = options.fontFamilies ?? FONT_STACK;
|
|
1675
1679
|
const parts = [];
|
|
1676
1680
|
// <defs> — shadows + arrowhead markers (palette-driven fills baked in).
|
|
1677
1681
|
const arrowFillNeutral = palette.arrowhead.neutral;
|
|
@@ -1697,7 +1701,7 @@ export async function renderSvg(model, options = {}) {
|
|
|
1697
1701
|
// swimlane background rects emitted later — so the major dotted
|
|
1698
1702
|
// and minor grid lines never actually rendered in the chart body.
|
|
1699
1703
|
// They now ship as their own layer below.
|
|
1700
|
-
parts.push(renderTimeline(model.timeline, palette));
|
|
1704
|
+
parts.push(renderTimeline(model.timeline, palette, fonts));
|
|
1701
1705
|
// Swimlane backgrounds — emitted as their own pass so the grid
|
|
1702
1706
|
// lines can be drawn on top of them, then the swimlane content
|
|
1703
1707
|
// (frame tab + items) sits on top of the grid.
|
|
@@ -1722,11 +1726,11 @@ export async function renderSvg(model, options = {}) {
|
|
|
1722
1726
|
}
|
|
1723
1727
|
// Swimlane content (frame tabs + items) on top of the grid lines.
|
|
1724
1728
|
for (const s of model.swimlanes)
|
|
1725
|
-
parts.push(renderSwimlane(s, options, idPrefix, palette));
|
|
1729
|
+
parts.push(renderSwimlane(s, options, idPrefix, palette, fonts));
|
|
1726
1730
|
// Include regions (drawn after own swimlanes so the dashed border + tab
|
|
1727
1731
|
// overlay the chart, with their own nested swimlanes inside).
|
|
1728
1732
|
for (const r of model.includes)
|
|
1729
|
-
parts.push(renderIncludeRegion(r, options, idPrefix, palette));
|
|
1733
|
+
parts.push(renderIncludeRegion(r, options, idPrefix, palette, fonts));
|
|
1730
1734
|
// Normal / overflow dependency edges on top of items but below
|
|
1731
1735
|
// cut-lines / nowline. Under-bar edges already painted above.
|
|
1732
1736
|
for (const e of model.edges) {
|
|
@@ -1741,15 +1745,15 @@ export async function renderSvg(model, options = {}) {
|
|
|
1741
1745
|
parts.push(renderMilestoneCutLine(m, palette));
|
|
1742
1746
|
// Marker-row diamonds + labels.
|
|
1743
1747
|
for (const a of model.anchors)
|
|
1744
|
-
parts.push(renderAnchor(a, palette));
|
|
1748
|
+
parts.push(renderAnchor(a, palette, fonts));
|
|
1745
1749
|
for (const m of model.milestones)
|
|
1746
|
-
parts.push(renderMilestone(m, palette));
|
|
1750
|
+
parts.push(renderMilestone(m, palette, fonts));
|
|
1747
1751
|
// Now-line
|
|
1748
|
-
parts.push(renderNowline(model.nowline, palette));
|
|
1752
|
+
parts.push(renderNowline(model.nowline, palette, fonts));
|
|
1749
1753
|
// Footnotes + header last (always on top)
|
|
1750
|
-
parts.push(renderFootnotes(model.footnotes, idPrefix, palette));
|
|
1751
|
-
parts.push(renderHeader(model.header, idPrefix, palette));
|
|
1752
|
-
parts.push(renderAttributionMark(model));
|
|
1754
|
+
parts.push(renderFootnotes(model.footnotes, idPrefix, palette, fonts));
|
|
1755
|
+
parts.push(renderHeader(model.header, idPrefix, palette, fonts));
|
|
1756
|
+
parts.push(renderAttributionMark(model, fonts));
|
|
1753
1757
|
// Logo (if header carries one)
|
|
1754
1758
|
if (model.header.logo && options.assetResolver) {
|
|
1755
1759
|
const logoSvg = await embedLogo(model.header.logo.assetRef ?? '', options.assetResolver, idPrefix, options, model.header.logo.box.x, model.header.logo.box.y, Math.max(model.header.logo.box.width, model.header.logo.box.height));
|