@nowline/renderer 0.8.6 → 0.9.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.
Files changed (86) hide show
  1. package/dist/image-size.d.ts +9 -0
  2. package/dist/image-size.d.ts.map +1 -0
  3. package/dist/image-size.js +104 -0
  4. package/dist/image-size.js.map +1 -0
  5. package/dist/index.d.ts +4 -1
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/index.js +4 -1
  8. package/dist/index.js.map +1 -1
  9. package/dist/logo.d.ts +24 -0
  10. package/dist/logo.d.ts.map +1 -0
  11. package/dist/logo.js +60 -0
  12. package/dist/logo.js.map +1 -0
  13. package/dist/stage.d.ts +78 -0
  14. package/dist/stage.d.ts.map +1 -0
  15. package/dist/stage.js +88 -0
  16. package/dist/stage.js.map +1 -0
  17. package/dist/svg/edges.d.ts +3 -0
  18. package/dist/svg/edges.d.ts.map +1 -0
  19. package/dist/svg/edges.js +144 -0
  20. package/dist/svg/edges.js.map +1 -0
  21. package/dist/svg/footnotes.d.ts +4 -0
  22. package/dist/svg/footnotes.d.ts.map +1 -0
  23. package/dist/svg/footnotes.js +81 -0
  24. package/dist/svg/footnotes.js.map +1 -0
  25. package/dist/svg/header.d.ts +6 -0
  26. package/dist/svg/header.d.ts.map +1 -0
  27. package/dist/svg/header.js +215 -0
  28. package/dist/svg/header.js.map +1 -0
  29. package/dist/svg/include-region.d.ts +4 -0
  30. package/dist/svg/include-region.d.ts.map +1 -0
  31. package/dist/svg/include-region.js +157 -0
  32. package/dist/svg/include-region.js.map +1 -0
  33. package/dist/svg/item.d.ts +36 -0
  34. package/dist/svg/item.d.ts.map +1 -0
  35. package/dist/svg/item.js +549 -0
  36. package/dist/svg/item.js.map +1 -0
  37. package/dist/svg/markers.d.ts +7 -0
  38. package/dist/svg/markers.d.ts.map +1 -0
  39. package/dist/svg/markers.js +151 -0
  40. package/dist/svg/markers.js.map +1 -0
  41. package/dist/svg/nowline.d.ts +4 -0
  42. package/dist/svg/nowline.d.ts.map +1 -0
  43. package/dist/svg/nowline.js +133 -0
  44. package/dist/svg/nowline.js.map +1 -0
  45. package/dist/svg/primitives.d.ts +7 -0
  46. package/dist/svg/primitives.d.ts.map +1 -0
  47. package/dist/svg/primitives.js +70 -0
  48. package/dist/svg/primitives.js.map +1 -0
  49. package/dist/svg/render.d.ts +13 -7
  50. package/dist/svg/render.d.ts.map +1 -1
  51. package/dist/svg/render.js +64 -1701
  52. package/dist/svg/render.js.map +1 -1
  53. package/dist/svg/sanitize.d.ts +10 -0
  54. package/dist/svg/sanitize.d.ts.map +1 -1
  55. package/dist/svg/sanitize.js +264 -16
  56. package/dist/svg/sanitize.js.map +1 -1
  57. package/dist/svg/swimlanes.d.ts +5 -0
  58. package/dist/svg/swimlanes.d.ts.map +1 -0
  59. package/dist/svg/swimlanes.js +228 -0
  60. package/dist/svg/swimlanes.js.map +1 -0
  61. package/dist/svg/timeline.d.ts +24 -0
  62. package/dist/svg/timeline.d.ts.map +1 -0
  63. package/dist/svg/timeline.js +284 -0
  64. package/dist/svg/timeline.js.map +1 -0
  65. package/dist/svg/waves.d.ts +62 -0
  66. package/dist/svg/waves.d.ts.map +1 -0
  67. package/dist/svg/waves.js +397 -0
  68. package/dist/svg/waves.js.map +1 -0
  69. package/package.json +6 -6
  70. package/src/image-size.ts +113 -0
  71. package/src/index.ts +10 -1
  72. package/src/logo.ts +82 -0
  73. package/src/stage.ts +191 -0
  74. package/src/svg/edges.ts +154 -0
  75. package/src/svg/footnotes.ts +115 -0
  76. package/src/svg/header.ts +285 -0
  77. package/src/svg/include-region.ts +201 -0
  78. package/src/svg/item.ts +672 -0
  79. package/src/svg/markers.ts +178 -0
  80. package/src/svg/nowline.ts +152 -0
  81. package/src/svg/primitives.ts +86 -0
  82. package/src/svg/render.ts +94 -2110
  83. package/src/svg/sanitize.ts +286 -16
  84. package/src/svg/swimlanes.ts +317 -0
  85. package/src/svg/timeline.ts +343 -0
  86. package/src/svg/waves.ts +502 -0
@@ -0,0 +1,397 @@
1
+ // The waves layer (specs/waves.md section 9): the hatch patterns that mark
2
+ // background work, the wave strip, tints, boundaries, labels, crossings and the
3
+ // legend. Every wave layer is emitted only when it has content, so a roadmap
4
+ // without waves renders byte-identically.
5
+ import { CORNER_RADIUS_PX, ITEM_FOOTNOTE_INDICATOR_STEP_PX, WAVE_BOUNDARY_WIDTH_PX, WAVE_CELL_INSET_PX, WAVE_CROSS_DASH, WAVE_HATCH_OPACITY, WAVE_HATCH_STROKE_PX, WAVE_HATCH_TILE_PX, WAVE_STRIP_LABEL_FONT_SIZE_PX, WAVE_STYLED_STRIP_MIX_OPACITY, WAVE_STYLED_TINT_OPACITY, } from '@nowline/layout';
6
+ import { renderInlineDatePins } from './item.js';
7
+ import { intersectBox } from './primitives.js';
8
+ import { escText, num, tag, textTag } from './xml.js';
9
+ // --- Waves (specs/waves.md §9) ---------------------------------------------
10
+ //
11
+ // Every wave layer is emitted only when it has content, so a roadmap without
12
+ // waves renders byte-identically: no `wave-*` layers and no hatch `<defs>`.
13
+ /** Strip label baseline below the strip's vertical centre (10 px text). */
14
+ const WAVE_LABEL_BASELINE_BELOW_CENTER_PX = 4;
15
+ /** Footnote superscripts in the strip: size and baseline below the strip top. */
16
+ const WAVE_SUPERSCRIPT_FONT_SIZE_PX = 8;
17
+ const WAVE_SUPERSCRIPT_BASELINE_OFFSET_PX = 10;
18
+ function waveHatchUrl(idPrefix, kind) {
19
+ return `url(#${idPrefix}-wave-hatch-${kind})`;
20
+ }
21
+ /**
22
+ * A hatch `<pattern>`: a `WAVE_HATCH_TILE_PX` tile rotated 45°, one
23
+ * `WAVE_HATCH_STROKE_PX` stroke down its middle (so the tile never clips
24
+ * it) at `WAVE_HATCH_OPACITY`. The id carries the SVG's prefix, like the
25
+ * shadow filters, so two SVGs on one page never share it.
26
+ *
27
+ * The line carries `opacity`, not `stroke-opacity`. Browsers and resvg draw
28
+ * the two the same (one stroke, nothing overlaps), but svg-to-pdfkit turns
29
+ * `stroke-opacity` into a stroke-only `CA` inside the pattern cell, which
30
+ * poppler ignores, so the PDF hatch drew opaque. With `opacity` it sets the
31
+ * fill alpha `ca` as well, which poppler and cairo both honour.
32
+ */
33
+ export function waveHatchPatternDef(idPrefix, kind, palette) {
34
+ const tile = WAVE_HATCH_TILE_PX;
35
+ return tag('pattern', {
36
+ id: `${idPrefix}-wave-hatch-${kind}`,
37
+ width: tile,
38
+ height: tile,
39
+ patternUnits: 'userSpaceOnUse',
40
+ patternTransform: 'rotate(45)',
41
+ }, tag('line', {
42
+ x1: num(tile / 2),
43
+ y1: 0,
44
+ x2: num(tile / 2),
45
+ y2: tile,
46
+ stroke: kind === 'dark' ? palette.wave.hatch : palette.wave.hatchOnDark,
47
+ 'stroke-width': WAVE_HATCH_STROKE_PX,
48
+ opacity: WAVE_HATCH_OPACITY,
49
+ }));
50
+ }
51
+ /**
52
+ * The hatch patterns the SVG uses, in a fixed order: one per kind that a
53
+ * background bar (main lanes or isolated regions) or the legend's hatch
54
+ * swatch needs, as layout resolved them (`waveHatch`, `hatch`). Empty
55
+ * without waves.
56
+ */
57
+ export function usedWaveHatchKinds(model) {
58
+ if (!model.waves)
59
+ return [];
60
+ const used = new Set();
61
+ const walk = (children) => {
62
+ for (const c of children) {
63
+ if (c.kind === 'item') {
64
+ if (c.waveRole === 'background' && c.waveHatch)
65
+ used.add(c.waveHatch);
66
+ }
67
+ else {
68
+ walk(c.children);
69
+ }
70
+ }
71
+ };
72
+ const walkLanes = (lanes) => {
73
+ for (const lane of lanes) {
74
+ walk(lane.children);
75
+ walkLanes(lane.nested);
76
+ }
77
+ };
78
+ walkLanes(model.swimlanes);
79
+ for (const r of model.includes)
80
+ walkLanes(r.nestedSwimlanes);
81
+ for (const e of model.waveLegend?.entries ?? []) {
82
+ used.add(e.hatch);
83
+ }
84
+ return ['dark', 'light'].filter((k) => used.has(k));
85
+ }
86
+ /**
87
+ * The hatch overlay over a background bar: the bar's rect inset by half its
88
+ * 1 px stroke, with the same corner radius, so it never covers the stroke.
89
+ * `kind` is the bar's hatch kind as layout resolved it.
90
+ */
91
+ export function hatchOverlay(box, style, kind, idPrefix) {
92
+ const inset = 0.5;
93
+ const rx = Math.min(CORNER_RADIUS_PX[style.cornerRadius] ?? 4, box.height / 2);
94
+ return tag('rect', {
95
+ x: num(box.x + inset),
96
+ y: num(box.y + inset),
97
+ width: num(Math.max(0, box.width - 2 * inset)),
98
+ height: num(Math.max(0, box.height - 2 * inset)),
99
+ rx: num(rx),
100
+ ry: num(rx),
101
+ fill: waveHatchUrl(idPrefix, kind),
102
+ });
103
+ }
104
+ /** The strip's backing panel corner radius (matches the tick panel's). */
105
+ const WAVE_STRIP_PANEL_RADIUS_PX = 4;
106
+ function waveCellCorners(cell, t) {
107
+ const r = Math.min(WAVE_STRIP_PANEL_RADIUS_PX - WAVE_CELL_INSET_PX, cell.width / 2, cell.height / 2);
108
+ const eps = 1e-6;
109
+ return {
110
+ left: cell.x <= t.box.x + WAVE_CELL_INSET_PX + eps ? r : 0,
111
+ right: cell.x + cell.width >= t.box.x + t.box.width - WAVE_CELL_INSET_PX - eps ? r : 0,
112
+ };
113
+ }
114
+ /** A box as a path with the given left / right corner radii. */
115
+ function roundedEndsPath(box, c) {
116
+ const { x, y, width: w, height: h } = box;
117
+ const arc = (r, ex, ey) => r > 0 ? ` A${num(r)} ${num(r)} 0 0 1 ${num(ex)} ${num(ey)}` : '';
118
+ return (`M${num(x + c.left)} ${num(y)}` +
119
+ ` H${num(x + w - c.right)}` +
120
+ arc(c.right, x + w, y + c.right) +
121
+ ` V${num(y + h - c.right)}` +
122
+ arc(c.right, x + w - c.right, y + h) +
123
+ ` H${num(x + c.left)}` +
124
+ arc(c.left, x, y + h - c.left) +
125
+ ` V${num(y + c.left)}` +
126
+ arc(c.left, x + c.left, y) +
127
+ ' Z');
128
+ }
129
+ /**
130
+ * A cell's fill: the strip fill, then the styled `bg` overlay when set.
131
+ * Square rects, unless `corners` rounds an end that meets the panel's.
132
+ */
133
+ function waveCellFill(w, box, corners) {
134
+ const shape = (fill) => corners && (corners.left > 0 || corners.right > 0)
135
+ ? tag('path', { d: roundedEndsPath(box, corners), ...fill })
136
+ : tag('rect', {
137
+ x: num(box.x),
138
+ y: num(box.y),
139
+ width: num(box.width),
140
+ height: num(box.height),
141
+ ...fill,
142
+ });
143
+ const base = shape({ fill: w.style.stripFill });
144
+ if (!w.style.tint)
145
+ return base;
146
+ return (base +
147
+ shape({
148
+ fill: w.style.tint,
149
+ 'fill-opacity': WAVE_STYLED_STRIP_MIX_OPACITY,
150
+ }));
151
+ }
152
+ /**
153
+ * `wave-strip` (§9.1): the backing panel and one cell per non-empty wave,
154
+ * each with its tooltip. Drawn right after the timeline panels, so the
155
+ * major grid lines cross the cells as they cross the tick panel.
156
+ */
157
+ export function renderWaveStrip(model, palette) {
158
+ const t = model.timeline;
159
+ const strip = t.waveStrip;
160
+ if (!model.waves || !strip)
161
+ return '';
162
+ const parts = [
163
+ tag('rect', {
164
+ x: num(t.box.x),
165
+ y: num(strip.y),
166
+ width: num(t.box.width),
167
+ height: num(strip.height),
168
+ rx: WAVE_STRIP_PANEL_RADIUS_PX,
169
+ ry: WAVE_STRIP_PANEL_RADIUS_PX,
170
+ fill: palette.timeline.panelFill,
171
+ stroke: palette.timeline.border,
172
+ 'stroke-width': 1,
173
+ }),
174
+ ];
175
+ for (const w of model.waves) {
176
+ // The cell layout clipped to the panel; none for an empty or
177
+ // off-screen wave.
178
+ const box = w.strip.cell;
179
+ if (!box)
180
+ continue;
181
+ parts.push(tag('g', { 'data-id': w.id }, `<title>${escText(w.strip.tooltip)}</title>${waveCellFill(w, box, waveCellCorners(box, t))}`));
182
+ }
183
+ return tag('g', { 'data-layer': 'wave-strip' }, parts.join(''));
184
+ }
185
+ /**
186
+ * `wave-bg` (§9.4): a column tint over each styled wave's `columnBox`,
187
+ * clipped to `clip` when given (an include region's painted rect).
188
+ */
189
+ export function renderWaveTints(waves, clip) {
190
+ const parts = [];
191
+ for (const w of waves) {
192
+ if (!w.style.tint || w.empty)
193
+ continue;
194
+ const box = clip ? intersectBox(w.columnBox, clip) : w.columnBox;
195
+ if (!box || box.width <= 0 || box.height <= 0)
196
+ continue;
197
+ parts.push(tag('rect', {
198
+ x: num(box.x),
199
+ y: num(box.y),
200
+ width: num(box.width),
201
+ height: num(box.height),
202
+ fill: w.style.tint,
203
+ 'fill-opacity': WAVE_STYLED_TINT_OPACITY,
204
+ }));
205
+ }
206
+ if (parts.length === 0)
207
+ return '';
208
+ return tag('g', { 'data-layer': 'wave-bg' }, parts.join(''));
209
+ }
210
+ /**
211
+ * `wave-boundary` (§9.2): the 2 px boundary lines, from the strip top to the
212
+ * last lane. With `clip`, only the lines strictly inside it, cut to its
213
+ * vertical extent.
214
+ */
215
+ export function renderWaveBoundaries(boundaries, clip) {
216
+ const parts = [];
217
+ for (const b of boundaries ?? []) {
218
+ let topY = b.topY;
219
+ let bottomY = b.bottomY;
220
+ if (clip) {
221
+ if (b.x <= clip.x || b.x >= clip.x + clip.width)
222
+ continue;
223
+ topY = Math.max(topY, clip.y);
224
+ bottomY = Math.min(bottomY, clip.y + clip.height);
225
+ if (bottomY <= topY)
226
+ continue;
227
+ }
228
+ parts.push(tag('line', {
229
+ x1: num(b.x),
230
+ y1: num(topY),
231
+ x2: num(b.x),
232
+ y2: num(bottomY),
233
+ stroke: b.stroke,
234
+ 'stroke-width': WAVE_BOUNDARY_WIDTH_PX,
235
+ 'stroke-dasharray': b.dash,
236
+ }));
237
+ }
238
+ if (parts.length === 0)
239
+ return '';
240
+ return tag('g', { 'data-layer': 'wave-boundary' }, parts.join(''));
241
+ }
242
+ /**
243
+ * `wave-labels` (§9.1): strip labels on halos in their cell's fill,
244
+ * right-aligned footnote superscripts, empty-wave diamonds, gap floor
245
+ * labels, and the all-empty placeholder. Drawn after the boundaries.
246
+ */
247
+ export function renderWaveLabels(model, palette, fonts) {
248
+ const t = model.timeline;
249
+ const strip = t.waveStrip;
250
+ if (!model.waves || !strip)
251
+ return '';
252
+ const baselineY = strip.y + strip.height / 2 + WAVE_LABEL_BASELINE_BELOW_CENTER_PX;
253
+ const parts = [];
254
+ // Muted italics on a halo in the backing panel's fill, so the major
255
+ // grid lines drawn later never cut through the text (§9.1). Layout
256
+ // placed the halo.
257
+ const mutedText = (x, text, halo) => {
258
+ return (tag('rect', {
259
+ x: num(halo.x),
260
+ y: num(halo.y),
261
+ width: num(halo.width),
262
+ height: num(halo.height),
263
+ fill: palette.timeline.panelFill,
264
+ }) +
265
+ textTag({
266
+ x: num(x),
267
+ y: num(baselineY),
268
+ 'font-family': fonts.sans,
269
+ 'font-size': WAVE_STRIP_LABEL_FONT_SIZE_PX,
270
+ 'font-style': 'italic',
271
+ fill: palette.wave.labelMuted,
272
+ 'text-anchor': 'middle',
273
+ }, text));
274
+ };
275
+ for (const w of model.waves) {
276
+ // A gap opened by a start floor: no cell, the floor reference in
277
+ // muted italics on a halo in the backing panel's fill.
278
+ if (w.strip.gapLabel) {
279
+ const { text, x, halo } = w.strip.gapLabel;
280
+ parts.push(mutedText(x, text, halo));
281
+ }
282
+ // Layout placed the label and superscript halos in the painted
283
+ // cell; a wave without one draws neither.
284
+ if (!w.strip.cell)
285
+ continue;
286
+ const { labelDraw, superscripts, inlineDatePin } = w.strip;
287
+ if (inlineDatePin)
288
+ parts.push(renderInlineDatePins([inlineDatePin], w.style.text));
289
+ if (superscripts) {
290
+ // The superscripts' own halo in the cell's fill, first so the
291
+ // label's halo never covers them.
292
+ parts.push(waveCellFill(w, superscripts.halo));
293
+ }
294
+ if (w.strip.label !== undefined && labelDraw) {
295
+ // Centred on the visible span, but never under the superscripts.
296
+ parts.push(waveCellFill(w, labelDraw.halo));
297
+ parts.push(textTag({
298
+ x: num(labelDraw.x),
299
+ y: num(baselineY),
300
+ 'font-family': fonts.sans,
301
+ 'font-size': WAVE_STRIP_LABEL_FONT_SIZE_PX,
302
+ 'font-weight': 600,
303
+ fill: w.style.text,
304
+ 'text-anchor': 'middle',
305
+ }, w.strip.label));
306
+ }
307
+ if (superscripts) {
308
+ // Right-aligned in the cell, walking left like an item's.
309
+ let fx = superscripts.rightX;
310
+ for (let k = w.footnoteIndicators.length - 1; k >= 0; k--) {
311
+ parts.push(textTag({
312
+ x: num(fx),
313
+ y: num(strip.y + WAVE_SUPERSCRIPT_BASELINE_OFFSET_PX),
314
+ 'font-family': fonts.sans,
315
+ 'font-size': WAVE_SUPERSCRIPT_FONT_SIZE_PX,
316
+ 'font-weight': 700,
317
+ fill: w.style.text,
318
+ 'text-anchor': 'end',
319
+ }, String(w.footnoteIndicators[k])));
320
+ fx -= ITEM_FOOTNOTE_INDICATOR_STEP_PX;
321
+ }
322
+ }
323
+ }
324
+ if (parts.length === 0)
325
+ return '';
326
+ return tag('g', { 'data-layer': 'wave-labels' }, parts.join(''));
327
+ }
328
+ /**
329
+ * `wave-cross` (§9.3): a 1 px dashed boundary over each background bar a
330
+ * boundary runs through, showing the barrier does not hold that work.
331
+ */
332
+ export function renderWaveCrossings(crossings) {
333
+ if (!crossings || crossings.length === 0)
334
+ return '';
335
+ const parts = crossings.map((c) => tag('line', {
336
+ x1: num(c.x),
337
+ y1: num(c.topY),
338
+ x2: num(c.x),
339
+ y2: num(c.bottomY),
340
+ stroke: c.stroke,
341
+ 'stroke-width': 1,
342
+ 'stroke-dasharray': WAVE_CROSS_DASH,
343
+ }));
344
+ return tag('g', { 'data-layer': 'wave-cross' }, parts.join(''));
345
+ }
346
+ /** The legend's hatch swatch fill: a default item bar. */
347
+ function legendSwatchFill(palette) {
348
+ const bg = palette.entities.item.bg;
349
+ return bg === 'none' ? 'transparent' : bg;
350
+ }
351
+ /**
352
+ * `wave-legend` (§9.5): the hatched bar swatch for background work and its
353
+ * text, as the layout placed them.
354
+ */
355
+ export function renderWaveLegend(legend, idPrefix, palette, fonts) {
356
+ if (!legend)
357
+ return '';
358
+ const parts = [];
359
+ for (const entry of legend.entries) {
360
+ const s = entry.swatch;
361
+ const fill = legendSwatchFill(palette);
362
+ const geometry = {
363
+ x: num(s.x),
364
+ y: num(s.y),
365
+ width: num(s.width),
366
+ height: num(s.height),
367
+ };
368
+ parts.push(tag('rect', {
369
+ ...geometry,
370
+ rx: 2,
371
+ ry: 2,
372
+ fill,
373
+ stroke: palette.entities.item.fg,
374
+ 'stroke-width': 1,
375
+ }));
376
+ parts.push(tag('rect', {
377
+ x: num(s.x + 0.5),
378
+ y: num(s.y + 0.5),
379
+ width: num(s.width - 1),
380
+ height: num(s.height - 1),
381
+ rx: 2,
382
+ ry: 2,
383
+ fill: waveHatchUrl(idPrefix, entry.hatch),
384
+ }));
385
+ for (const run of entry.runs) {
386
+ parts.push(textTag({
387
+ x: num(run.x),
388
+ y: num(run.y),
389
+ 'font-family': fonts.sans,
390
+ 'font-size': WAVE_STRIP_LABEL_FONT_SIZE_PX,
391
+ fill: palette.wave.labelText,
392
+ }, run.text));
393
+ }
394
+ }
395
+ return tag('g', { 'data-layer': 'wave-legend' }, parts.join(''));
396
+ }
397
+ //# sourceMappingURL=waves.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"waves.js","sourceRoot":"","sources":["../../src/svg/waves.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nowline/renderer",
3
- "version": "0.8.6",
3
+ "version": "0.9.0",
4
4
  "description": "Nowline renderer — draws a laid-out roadmap as an SVG image.",
5
5
  "license": "Apache-2.0",
6
6
  "engines": {
@@ -22,14 +22,14 @@
22
22
  "src/"
23
23
  ],
24
24
  "dependencies": {
25
- "@nowline/layout": "0.8.6"
25
+ "@nowline/core": "0.9.0",
26
+ "@nowline/layout": "0.9.0",
27
+ "langium": "~4.4.0"
26
28
  },
27
29
  "devDependencies": {
28
- "@nowline/core": "0.8.6",
29
- "@types/node": "^26.6.3",
30
- "langium": "~4.4.0",
30
+ "@types/node": "^26.6.5",
31
31
  "typescript": "^7.0.2",
32
- "vitest": "^5.0.2"
32
+ "vitest": "^5.0.5"
33
33
  },
34
34
  "scripts": {
35
35
  "build": "tsc -b tsconfig.json",
@@ -0,0 +1,113 @@
1
+ // Intrinsic pixel size of a raster logo, read from the file header so layout
2
+ // can size the logo slot before render. Browser-safe: no Buffer, no fs.
3
+
4
+ export interface ImageSize {
5
+ width: number;
6
+ height: number;
7
+ }
8
+
9
+ /** The raster formats `logo:` accepts. */
10
+ export type RasterLogoMime = 'image/png' | 'image/jpeg' | 'image/webp';
11
+
12
+ /** Width and height from a PNG, JPEG or WebP header; undefined when unreadable. */
13
+ export function rasterSize(bytes: Uint8Array, mime: RasterLogoMime): ImageSize | undefined {
14
+ const size =
15
+ mime === 'image/png'
16
+ ? pngSize(bytes)
17
+ : mime === 'image/jpeg'
18
+ ? jpegSize(bytes)
19
+ : webpSize(bytes);
20
+ return size && size.width > 0 && size.height > 0 ? size : undefined;
21
+ }
22
+
23
+ const PNG_SIGNATURE = [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a];
24
+
25
+ function u16be(b: Uint8Array, i: number): number {
26
+ return (b[i] << 8) | b[i + 1];
27
+ }
28
+
29
+ function u32be(b: Uint8Array, i: number): number {
30
+ return ((b[i] << 24) | (b[i + 1] << 16) | (b[i + 2] << 8) | b[i + 3]) >>> 0;
31
+ }
32
+
33
+ function u16le(b: Uint8Array, i: number): number {
34
+ return b[i] | (b[i + 1] << 8);
35
+ }
36
+
37
+ function u24le(b: Uint8Array, i: number): number {
38
+ return b[i] | (b[i + 1] << 8) | (b[i + 2] << 16);
39
+ }
40
+
41
+ function ascii(b: Uint8Array, i: number, n: number): string {
42
+ return String.fromCharCode(...b.subarray(i, i + n));
43
+ }
44
+
45
+ // The first chunk after the signature is always IHDR: width, height (u32 BE).
46
+ function pngSize(b: Uint8Array): ImageSize | undefined {
47
+ if (b.length < 24 || PNG_SIGNATURE.some((v, i) => b[i] !== v)) return undefined;
48
+ if (ascii(b, 12, 4) !== 'IHDR') return undefined;
49
+ return { width: u32be(b, 16), height: u32be(b, 20) };
50
+ }
51
+
52
+ // Walk the marker segments to the first start-of-frame (SOF0..SOF15 minus
53
+ // DHT C4, JPG C8 and DAC CC), which carries height then width.
54
+ function jpegSize(b: Uint8Array): ImageSize | undefined {
55
+ if (b.length < 4 || b[0] !== 0xff || b[1] !== 0xd8) return undefined;
56
+ let i = 2;
57
+ while (i + 3 < b.length) {
58
+ if (b[i] !== 0xff) return undefined;
59
+ const marker = b[i + 1];
60
+ if (marker === 0xff) {
61
+ i += 1; // fill byte
62
+ continue;
63
+ }
64
+ // Standalone markers carry no length.
65
+ if (marker === 0x01 || (marker >= 0xd0 && marker <= 0xd9)) {
66
+ i += 2;
67
+ continue;
68
+ }
69
+ const length = u16be(b, i + 2);
70
+ const isSof =
71
+ marker >= 0xc0 &&
72
+ marker <= 0xcf &&
73
+ marker !== 0xc4 &&
74
+ marker !== 0xc8 &&
75
+ marker !== 0xcc;
76
+ if (isSof) {
77
+ if (i + 8 >= b.length) return undefined;
78
+ return { width: u16be(b, i + 7), height: u16be(b, i + 5) };
79
+ }
80
+ if (length < 2) return undefined;
81
+ i += 2 + length;
82
+ }
83
+ return undefined;
84
+ }
85
+
86
+ // RIFF container; the first chunk says lossy (VP8), lossless (VP8L) or
87
+ // extended (VP8X, which records the canvas size directly).
88
+ function webpSize(b: Uint8Array): ImageSize | undefined {
89
+ if (b.length < 16 || ascii(b, 0, 4) !== 'RIFF' || ascii(b, 8, 4) !== 'WEBP') return undefined;
90
+ const chunk = ascii(b, 12, 4);
91
+ // Bytes each chunk kind needs to reach its size fields.
92
+ const needed = chunk === 'VP8L' ? 25 : 30;
93
+ if (b.length < needed) return undefined;
94
+ if (chunk === 'VP8 ') {
95
+ if (b[23] !== 0x9d || b[24] !== 0x01 || b[25] !== 0x2a) return undefined;
96
+ return { width: u16le(b, 26) & 0x3fff, height: u16le(b, 28) & 0x3fff };
97
+ }
98
+ if (chunk === 'VP8L') {
99
+ if (b[20] !== 0x2f) return undefined;
100
+ const b1 = b[21];
101
+ const b2 = b[22];
102
+ const b3 = b[23];
103
+ const b4 = b[24];
104
+ return {
105
+ width: 1 + (b1 | ((b2 & 0x3f) << 8)),
106
+ height: 1 + ((b2 >> 6) | (b3 << 2) | ((b4 & 0x0f) << 10)),
107
+ };
108
+ }
109
+ if (chunk === 'VP8X') {
110
+ return { width: 1 + u24le(b, 24), height: 1 + u24le(b, 27) };
111
+ }
112
+ return undefined;
113
+ }
package/src/index.ts CHANGED
@@ -1,3 +1,12 @@
1
+ export { type ImageSize, type RasterLogoMime, rasterSize } from './image-size.js';
2
+ export { type LoadedLogo, loadLogo } from './logo.js';
3
+ export {
4
+ type StagedRoadmap,
5
+ type StageInputs,
6
+ type StageLayout,
7
+ type StageResult,
8
+ stageRoadmap,
9
+ } from './stage.js';
1
10
  export type {
2
11
  AssetBytes,
3
12
  AssetResolver,
@@ -5,4 +14,4 @@ export type {
5
14
  RenderOptions,
6
15
  } from './svg/render.js';
7
16
  export { renderSvg } from './svg/render.js';
8
- export { sanitizeSvg } from './svg/sanitize.js';
17
+ export { sanitizeSvg, svgIntrinsicSize } from './svg/sanitize.js';
package/src/logo.ts ADDED
@@ -0,0 +1,82 @@
1
+ // Pre-layout logo loading (specs/rendering.md § Company Logo Error Handling).
2
+ //
3
+ // Pipelines call `loadLogo` before layout so layout can size the logo slot
4
+ // from the asset's intrinsic aspect ratio. Every failure is a warning, never
5
+ // a throw: the caller decides whether `--strict` turns it into an error, and
6
+ // a failed load lays out as if no logo were declared.
7
+
8
+ import type { LogoDimensions } from '@nowline/layout';
9
+ import { type RasterLogoMime, rasterSize } from './image-size.js';
10
+ import type { AssetBytes, AssetResolver } from './svg/render.js';
11
+ import { svgIntrinsicSize } from './svg/sanitize.js';
12
+
13
+ const LOGO_MIME_BY_EXTENSION: Record<string, 'image/svg+xml' | RasterLogoMime> = {
14
+ '.svg': 'image/svg+xml',
15
+ '.png': 'image/png',
16
+ '.jpg': 'image/jpeg',
17
+ '.jpeg': 'image/jpeg',
18
+ '.webp': 'image/webp',
19
+ };
20
+
21
+ function extensionOf(ref: string): string {
22
+ const base = ref.slice(ref.lastIndexOf('/') + 1);
23
+ const dot = base.lastIndexOf('.');
24
+ return dot > 0 ? base.slice(dot).toLowerCase() : '';
25
+ }
26
+
27
+ /**
28
+ * A logo read once: its size for layout and the bytes for the renderer, so no
29
+ * later step resolves the asset again.
30
+ */
31
+ export interface LoadedLogo {
32
+ /** The `logo:` reference, as the roadmap declared it. */
33
+ ref: string;
34
+ /** The intrinsic size, passed to layout as `LayoutOptions.logo`. */
35
+ dimensions: LogoDimensions;
36
+ /** The bytes and MIME type the resolver returned, which `renderSvg` embeds. */
37
+ asset: AssetBytes;
38
+ }
39
+
40
+ /**
41
+ * Load the `logo:` asset and read its intrinsic size, to pass to layout as
42
+ * `LayoutOptions.logo` (`.dimensions`), keeping the bytes for
43
+ * `RenderOptions.logo`. Returns undefined, after a `warn` call, when the
44
+ * extension is unsupported or the file is missing or unreadable. Returns
45
+ * undefined silently when there is no `ref` or no resolver (a surface with
46
+ * no asset access, such as the embed).
47
+ */
48
+ export async function loadLogo(
49
+ ref: string | undefined,
50
+ resolver: AssetResolver | undefined,
51
+ warn: (message: string) => void,
52
+ ): Promise<LoadedLogo | undefined> {
53
+ if (!ref || !resolver) return undefined;
54
+ const ext = extensionOf(ref);
55
+ const mime = LOGO_MIME_BY_EXTENSION[ext];
56
+ if (!mime) {
57
+ warn(`unsupported logo format: ${ext || '(none)'} (${ref})`);
58
+ return undefined;
59
+ }
60
+ let asset: AssetBytes;
61
+ try {
62
+ asset = await resolver(ref);
63
+ } catch (err) {
64
+ const reason = err instanceof Error ? err.message : String(err);
65
+ warn(`logo file not found: ${ref} (${reason})`);
66
+ return undefined;
67
+ }
68
+ const bytes = asset.bytes;
69
+ const size =
70
+ mime === 'image/svg+xml'
71
+ ? svgIntrinsicSize(new TextDecoder().decode(bytes))
72
+ : rasterSize(bytes, mime);
73
+ if (!size) {
74
+ const why =
75
+ mime === 'image/svg+xml'
76
+ ? 'no viewBox and no pixel width/height on the root <svg>'
77
+ : `not a readable ${ext.slice(1).toUpperCase()} header`;
78
+ warn(`logo could not be parsed: ${ref} (${why})`);
79
+ return undefined;
80
+ }
81
+ return { ref, dimensions: size, asset };
82
+ }