@vyaz/renderer 0.1.0 → 0.4.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/README.md ADDED
@@ -0,0 +1,121 @@
1
+ # @vyaz/renderer
2
+
3
+ SVG renderer for [`@vyaz/core`](https://www.npmjs.com/package/@vyaz/core) layout
4
+ output — both a plain `TextFrame` and a `TableFrame` grid.
5
+
6
+ Part of the [Vyaz](https://github.com/sedrew/vyaz) project.
7
+
8
+ ```bash
9
+ bun add @vyaz/core @vyaz/renderer
10
+ # or
11
+ npm install @vyaz/core @vyaz/renderer
12
+ ```
13
+
14
+ ## Quick start — text
15
+
16
+ ```ts
17
+ import { layoutTextFrame } from '@vyaz/core'
18
+ import { renderToSVG } from '@vyaz/renderer'
19
+ import type { TextFrame } from '@vyaz/core'
20
+
21
+ const frame: TextFrame = {
22
+ width: 400,
23
+ wrap: true,
24
+ paragraphs: [
25
+ {
26
+ style: { alignment: 'left', lineHeight: 1.4, spaceBefore: 0, spaceAfter: 0 },
27
+ children: [
28
+ { type: 'text', text: 'Hello, ', fontFamily: 'Inter', fontSize: 16, fontWeight: 'normal', fontStyle: 'normal', color: '#111' },
29
+ { type: 'text', text: 'Vyaz!', fontFamily: 'Inter', fontSize: 16, fontWeight: 'bold', fontStyle: 'normal', color: '#111' },
30
+ ],
31
+ },
32
+ ],
33
+ }
34
+
35
+ const svg = renderToSVG(layoutTextFrame(frame), { preset: 'browser' })
36
+ ```
37
+
38
+ ## Quick start — tables
39
+
40
+ ```ts
41
+ import { layoutTableFrame } from '@vyaz/core'
42
+ import { renderTableToSVG } from '@vyaz/renderer'
43
+ import type { TableFrame } from '@vyaz/core'
44
+
45
+ const cell = (text: string) => ({
46
+ content: {
47
+ wrap: true,
48
+ paragraphs: [{
49
+ style: { alignment: 'left', lineHeight: 1.3, spaceBefore: 0, spaceAfter: 0 },
50
+ children: [{ type: 'text', text, fontFamily: 'Inter', fontSize: 14, fontWeight: 'normal', fontStyle: 'normal', color: '#111' }],
51
+ }],
52
+ },
53
+ })
54
+
55
+ const table: TableFrame = {
56
+ defaultCellStyle: { paddings: 8, borderWidths: 1, borderColors: '#ccc' },
57
+ rows: [
58
+ { style: { bgColor: '#eee' }, cells: [cell('Name'), cell('Qty')] },
59
+ { cells: [cell('Widget'), cell('3')] },
60
+ ],
61
+ }
62
+
63
+ const svg = renderTableToSVG(layoutTableFrame(table), { preset: 'browser' })
64
+ ```
65
+
66
+ `renderTableToSVG` paints table/row/cell backgrounds and borders (solid,
67
+ dashed, rounded corners), each cell's text content, `colSpan`/`rowSpan`,
68
+ `before`/`after` decorative slots, and tables nested inside a cell — see the
69
+ [Tables guide](https://sedrew.github.io/vyaz/guide/tables) for the full style
70
+ surface.
71
+
72
+ ## Text presets
73
+
74
+ | Preset | Structure | Use case |
75
+ |--------|-----------|----------|
76
+ | `flat` | Single `<text>` with concatenated text | PowerPoint / OOXML export |
77
+ | `browser` | `<text>` + `<tspan>` per run | Web / browser display |
78
+ | `preserve` | `<text>` + `<tspan>` + `textLength` | Pixel-perfect rendering |
79
+ | `glyph` | `<tspan x="x0 x1 ...">` per glyph | Selection / cursor positioning |
80
+
81
+ ```ts
82
+ renderToSVG(result, { preset: 'preserve', style: 'css' })
83
+
84
+ // low-level — supply width/height/sizing yourself instead of deriving from the result
85
+ renderToSVG(result.lines, { sizing: { horizontal: 'frame', vertical: 'content' }, width: 400, preset: 'flat' })
86
+ ```
87
+
88
+ Pass `{ debug: { frameBox, contentBox, baseline, … } }` for overlay boxes.
89
+
90
+ ## `registerFont(family, source, opts?)`
91
+
92
+ Registers a font with **both** `@vyaz/core`'s measurement engine and the
93
+ browser's `document.fonts` in one call — the single most common source of
94
+ drift between measured and painted text is registering only one of the two.
95
+
96
+ ```ts
97
+ import { registerFont } from '@vyaz/renderer'
98
+
99
+ await registerFont('Inter', 'https://example.com/Inter.woff2')
100
+ // or a local buffer (Node.js)
101
+ import { readFileSync } from 'node:fs'
102
+ await registerFont('Inter', readFileSync('./Inter.woff2'), { weight: 'normal', style: 'normal' })
103
+ ```
104
+
105
+ ## `TableRenderOptions`
106
+
107
+ | option | default | |
108
+ |---|---|---|
109
+ | `preset` | `'browser'` | forwarded to every cell's own text render |
110
+ | `style` | `'xml'` | how cell text style properties are expressed |
111
+ | `className` | – | CSS class on the root `<svg>`/`<g>` |
112
+ | `fragment` | `false` | emit a bare `<g>` instead of `<svg xmlns… viewBox…>`, for splicing into a document that already has one |
113
+
114
+ ## Requirements
115
+
116
+ - **Runtime**: Bun 1.x, Node.js 18+, or a modern browser
117
+ - **Peer**: `@vyaz/core` (matching major/minor)
118
+
119
+ ## License
120
+
121
+ MIT
@@ -17,7 +17,7 @@
17
17
  * const svg = renderToSVG(lines, { preset: 'browser' })
18
18
  * const svg = renderToSVG(lines, { preset: 'preserve', style: 'css', fit: 'frag' })
19
19
  */
20
- import type { Line, ParagraphLayoutResult, TextFrameLayoutResult, MultiColumnConfig } from '@vyaz/core';
20
+ import type { Line, ParagraphLayoutResult, TextFrameLayoutResult, MultiColumnConfig, FrameTransform } from '@vyaz/core';
21
21
  import type { DebugFlags } from './types.js';
22
22
  export type SvgPreset = 'flat' | 'browser' | 'preserve' | 'glyph';
23
23
  export type SvgStyle = 'css' | 'xml';
@@ -63,6 +63,17 @@ export interface SVGRenderOptions {
63
63
  columns?: MultiColumnConfig;
64
64
  /** Left padding from frame (needed for column debug rendering). */
65
65
  paddingLeft?: number;
66
+ /**
67
+ * Post-layout rigid transform (writing-mode `sideways-*` / frame `rotation`).
68
+ * When the input is a {@link TextFrameLayoutResult} its own `transform` is used
69
+ * automatically; pass this to supply one for the bare `Line[]` form or to
70
+ * override. A net rotation that is a multiple of 360° is ignored.
71
+ *
72
+ * The lines are rendered into `layoutBox` (pre-rotation space) and the whole
73
+ * output is wrapped in one `<g transform>`; the `<svg>` canvas becomes the
74
+ * rotated visual box.
75
+ */
76
+ transform?: FrameTransform;
66
77
  /**
67
78
  * glyph preset only: draw `underline` / `strikethrough` as explicit `<line>`
68
79
  * geometry. The glyph path positions each character with its own `x`, so it
@@ -70,6 +81,14 @@ export interface SVGRenderOptions {
70
81
  * Ignored by every other preset. Default `true`.
71
82
  */
72
83
  glyphDecorations?: boolean;
84
+ /**
85
+ * Content for inline-box spans, keyed by `span.inlineWidget.id`. Each value is
86
+ * an SVG fragment already sized to the widget's `width` × `height`; it is
87
+ * spliced into a `<g>` translated to the box the layout reserved. A span whose
88
+ * id has no entry gets a light placeholder `<rect>`. Produced by `@vyaz/converters`
89
+ * for `<img>` / `<svg>` / `<progress>` / … ; irrelevant without inline boxes.
90
+ */
91
+ inlineBoxes?: Record<string, string>;
73
92
  }
74
93
  /**
75
94
  * Render a layout to an SVG string.
@@ -1 +1 @@
1
- {"version":3,"file":"SVGRenderer.d.ts","sourceRoot":"","sources":["../src/SVGRenderer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,KAAK,EAAE,IAAI,EAAQ,qBAAqB,EAAE,qBAAqB,EAAkB,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAE9H,OAAO,KAAK,EAAE,UAAU,EAAuB,MAAM,YAAY,CAAC;AAKlE,MAAM,MAAM,SAAS,GAAG,MAAM,GAAG,SAAS,GAAG,UAAU,GAAG,OAAO,CAAC;AAElE,MAAM,MAAM,QAAQ,GAAG,KAAK,GAAG,KAAK,CAAC;AAErC,MAAM,MAAM,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,CAAC;AAE9C,MAAM,MAAM,SAAS,GAAG,OAAO,GAAG,SAAS,CAAC;AAE5C,MAAM,MAAM,aAAa,GAAG;IAAE,UAAU,EAAE,SAAS,CAAC;IAAC,QAAQ,EAAE,SAAS,CAAA;CAAE,CAAC;AAE3E,MAAM,WAAW,gBAAgB;IAC/B,uDAAuD;IACvD,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB,sGAAsG;IACtG,KAAK,CAAC,EAAE,QAAQ,CAAC;IACjB,mCAAmC;IACnC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;;;;;OAKG;IACH,MAAM,CAAC,EAAE,SAAS,GAAG,aAAa,CAAC;IACnC,iFAAiF;IACjF,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,gFAAgF;IAChF,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,6BAA6B;IAC7B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;;OAKG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,sBAAsB;IACtB,KAAK,CAAC,EAAE,UAAU,CAAC;IACnB;;;OAGG;IACH,OAAO,CAAC,EAAE,iBAAiB,CAAC;IAC5B,mEAAmE;IACnE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;;OAKG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B;AA8zBD;;;;;;;;;;;;GAYG;AACH,wBAAgB,WAAW,CACzB,KAAK,EAAE,IAAI,EAAE,GAAG,qBAAqB,EACrC,OAAO,GAAE,gBAAqB,GAC7B,MAAM,CA6QR;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,IAAI,EAAE,EACb,cAAc,EAAE,MAAM,EACtB,eAAe,EAAE,MAAM,EACvB,OAAO,CAAC,EAAE,gBAAgB,GACzB,MAAM,CAMR;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,qBAAqB,EAC7B,OAAO,CAAC,EAAE,gBAAgB,GACzB,MAAM,CAMR"}
1
+ {"version":3,"file":"SVGRenderer.d.ts","sourceRoot":"","sources":["../src/SVGRenderer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,KAAK,EAAE,IAAI,EAAQ,qBAAqB,EAAE,qBAAqB,EAAkB,iBAAiB,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAE9I,OAAO,KAAK,EAAE,UAAU,EAAuB,MAAM,YAAY,CAAC;AAKlE,MAAM,MAAM,SAAS,GAAG,MAAM,GAAG,SAAS,GAAG,UAAU,GAAG,OAAO,CAAC;AAElE,MAAM,MAAM,QAAQ,GAAG,KAAK,GAAG,KAAK,CAAC;AAErC,MAAM,MAAM,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,CAAC;AAE9C,MAAM,MAAM,SAAS,GAAG,OAAO,GAAG,SAAS,CAAC;AAE5C,MAAM,MAAM,aAAa,GAAG;IAAE,UAAU,EAAE,SAAS,CAAC;IAAC,QAAQ,EAAE,SAAS,CAAA;CAAE,CAAC;AAE3E,MAAM,WAAW,gBAAgB;IAC/B,uDAAuD;IACvD,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB,sGAAsG;IACtG,KAAK,CAAC,EAAE,QAAQ,CAAC;IACjB,mCAAmC;IACnC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;;;;;OAKG;IACH,MAAM,CAAC,EAAE,SAAS,GAAG,aAAa,CAAC;IACnC,iFAAiF;IACjF,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,gFAAgF;IAChF,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,6BAA6B;IAC7B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;;OAKG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,sBAAsB;IACtB,KAAK,CAAC,EAAE,UAAU,CAAC;IACnB;;;OAGG;IACH,OAAO,CAAC,EAAE,iBAAiB,CAAC;IAC5B,mEAAmE;IACnE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;;;;;;OASG;IACH,SAAS,CAAC,EAAE,cAAc,CAAC;IAC3B;;;;;OAKG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B;;;;;;OAMG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACtC;AA+3BD;;;;;;;;;;;;GAYG;AACH,wBAAgB,WAAW,CACzB,KAAK,EAAE,IAAI,EAAE,GAAG,qBAAqB,EACrC,OAAO,GAAE,gBAAqB,GAC7B,MAAM,CA2TR;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,IAAI,EAAE,EACb,cAAc,EAAE,MAAM,EACtB,eAAe,EAAE,MAAM,EACvB,OAAO,CAAC,EAAE,gBAAgB,GACzB,MAAM,CAMR;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,qBAAqB,EAC7B,OAAO,CAAC,EAAE,gBAAgB,GACzB,MAAM,CAMR"}
@@ -0,0 +1,59 @@
1
+ /**
2
+ * TableRenderer.ts — TableLayoutResult → SVG string.
3
+ *
4
+ * Paint order (back to front): table bg → row bg → cell bg → row borders →
5
+ * cell borders → table border → cell content (before slot → main content →
6
+ * after slot, per cell). Each of those three is a complete nested `<svg>`
7
+ * (from `renderToSVG`), positioned with a `<g transform="translate(…)">` —
8
+ * the same "paint a self-contained SVG fragment into a box" pattern
9
+ * `SVGRenderOptions.inlineBoxes` uses for `<img>`/`<svg>` boxes from
10
+ * `@vyaz/converters`. `before`/`after` (`paintSlot`) paint at the absolute
11
+ * position `TableLayoutEngine` already resolved for them — no extra
12
+ * padding/border math here, unlike main content.
13
+ *
14
+ * A cell's content is rendered at `sizing: 'frame'` with the *exact* width
15
+ * TableLayoutEngine measured it at (not `sizing: 'content'`): trimming to the
16
+ * ink bounding box would discard the alignment offset for centered/
17
+ * right-aligned paragraphs — the nested `<svg>`'s viewBox has to start at
18
+ * (0, 0) so `line.x` stays the true position within the cell.
19
+ *
20
+ * `cell.cx`/`cy` are an extra px nudge added straight onto the content
21
+ * origin. `cell.allowOverflow` forces `overflow: visible` on the cell's own
22
+ * nested `<svg>` (default clips at the padding box, same as `svg-table-core`
23
+ * — see `withOverflowVisible`). `opts.fragment` swaps the outer `<svg
24
+ * xmlns… viewBox…>` for a bare `<g>`, for splicing this table into a
25
+ * document that already has one (composing tables, nested-table cells).
26
+ *
27
+ * `cell.nestedTable` (`TableCell.content` was itself a `TableFrame`) paints
28
+ * via a *recursive* `renderTableToSVG(cell.nestedTable, { fragment: true })`
29
+ * call at the cell's content origin instead of the normal `renderToSVG` text
30
+ * path — `fragment: true` is exactly what makes this splice in as a `<g>`
31
+ * rather than nesting `<svg>` inside `<svg>` inside `<svg>`.
32
+ *
33
+ * Border dash patterns (`stroke-dasharray`) and per-side `stroke-linecap`
34
+ * (T3, `dashAttrs`/`dashUniform`) paint on both border shapes: a uniform
35
+ * `<rect stroke>` needs its pattern+linecap to also match on all four sides
36
+ * to stay a single rect (SVG has one dasharray per shape); otherwise (or
37
+ * when width/color already differ) it falls back to four `<line>`s, each
38
+ * with its own dasharray/linecap. Asymmetric corner radii remain a possible
39
+ * future addition — only a uniform `rx`/`ry` today.
40
+ */
41
+ import type { TableLayoutResult } from '@vyaz/core';
42
+ import type { SvgPreset, SvgStyle } from './SVGRenderer.js';
43
+ export interface TableRenderOptions {
44
+ /** Preset used for every cell's own text render. Default `'browser'`. */
45
+ preset?: SvgPreset;
46
+ /** How cell text style properties are expressed. Default `'xml'`. */
47
+ style?: SvgStyle;
48
+ /** CSS class for the root `<svg>`/`<g>`. */
49
+ className?: string;
50
+ /**
51
+ * Emit a bare `<g>` (no `<svg>`/`viewBox`/`xmlns` wrapper) for splicing
52
+ * into a document that already has an outer `<svg>` — e.g. composing
53
+ * several tables, or embedding as a nested-table cell's content. Default
54
+ * `false`.
55
+ */
56
+ fragment?: boolean;
57
+ }
58
+ export declare function renderTableToSVG(result: TableLayoutResult, opts?: TableRenderOptions): string;
59
+ //# sourceMappingURL=TableRenderer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TableRenderer.d.ts","sourceRoot":"","sources":["../src/TableRenderer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AACH,OAAO,KAAK,EAAE,iBAAiB,EAAyC,MAAM,YAAY,CAAC;AAE3F,OAAO,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAG5D,MAAM,WAAW,kBAAkB;IACjC,yEAAyE;IACzE,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB,qEAAqE;IACrE,KAAK,CAAC,EAAE,QAAQ,CAAC;IACjB,4CAA4C;IAC5C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAiFD,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,iBAAiB,EAAE,IAAI,GAAE,kBAAuB,GAAG,MAAM,CAgEjG"}
@@ -9,9 +9,13 @@
9
9
  */
10
10
  export { renderToSVG, renderParagraphToSVG, renderResultToSVG } from './SVGRenderer.js';
11
11
  export type { SVGRenderOptions, SvgPreset, SvgStyle, SvgFit, SvgSizing } from './SVGRenderer.js';
12
+ export { renderTableToSVG } from './TableRenderer.js';
13
+ export type { TableRenderOptions } from './TableRenderer.js';
12
14
  export { renderToCanvas, renderDebugToCanvas, renderSelection, renderCursor } from './CanvasRenderer.js';
13
15
  export type { CanvasRenderOptions, CursorOptions } from './CanvasRenderer.js';
14
16
  export { charAtPoint, charIndexToPos, posToCharIndex } from './interactive.js';
15
17
  export type { CharPos } from './interactive.js';
18
+ export { registerFont } from './register-font.js';
19
+ export type { RegisterFontOptions, RegisterFontResult } from './register-font.js';
16
20
  export type { DebugFlags } from './types.js';
17
21
  //# sourceMappingURL=index.browser.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.browser.d.ts","sourceRoot":"","sources":["../src/index.browser.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,WAAW,EAAE,oBAAoB,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AACxF,YAAY,EAAE,gBAAgB,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAEjG,OAAO,EAAE,cAAc,EAAE,mBAAmB,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACzG,YAAY,EAAE,mBAAmB,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAE9E,OAAO,EAAE,WAAW,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAC/E,YAAY,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAEhD,YAAY,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC"}
1
+ {"version":3,"file":"index.browser.d.ts","sourceRoot":"","sources":["../src/index.browser.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,WAAW,EAAE,oBAAoB,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AACxF,YAAY,EAAE,gBAAgB,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAEjG,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACtD,YAAY,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAE7D,OAAO,EAAE,cAAc,EAAE,mBAAmB,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACzG,YAAY,EAAE,mBAAmB,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAE9E,OAAO,EAAE,WAAW,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAC/E,YAAY,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAEhD,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,YAAY,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAElF,YAAY,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC"}