takumi-pdf 0.2.0 → 0.3.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 CHANGED
@@ -166,10 +166,86 @@ const pdf = await render(
166
166
  | `break-inside: avoid` | Keeps the element on one page when it fits. |
167
167
  | `box-decoration-break: clone` | Repeats borders and backgrounds on every page fragment. |
168
168
 
169
+ ## Links, outline, and metadata
170
+
171
+ Anchors with an `href` become clickable link annotations. `outline: true` builds PDF bookmarks from `h1`–`h6` headings. `metadata` fills the document properties:
172
+
173
+ ```tsx
174
+ const pdf = await render(report, {
175
+ outline: true,
176
+ lang: "en",
177
+ metadata: {
178
+ title: "Annual report 2026",
179
+ authors: ["Acme Inc."],
180
+ creationDate: "2026-08-06",
181
+ },
182
+ });
183
+ ```
184
+
185
+ Omit `metadata` to keep output byte-identical across runs.
186
+
187
+ ## Tagged output and PDF/A
188
+
189
+ Output is **tagged by default**: HTML semantics (`h1`–`h6`, `p`, `img` with `alt`, `a`, lists) become a PDF structure tree, like Chromium's print-to-PDF. Set `tagged: "ua1"` to validate against PDF/UA-1, or `tagged: false` to drop the tree when file size matters more than accessibility.
190
+
191
+ `pdfa` renders archival output. Validation runs during rendering. A document that cannot conform fails with the violated rule instead of writing a broken file. Every level, and PDF/UA-1, passes [veraPDF](https://verapdf.org).
192
+
193
+ ```tsx
194
+ const pdf = await render(report, {
195
+ pdfa: "2a",
196
+ tagged: "ua1",
197
+ lang: "en",
198
+ metadata: { title: "Annual report", creationDate: "2026-08-06" },
199
+ });
200
+ ```
201
+
202
+ | Level | What it adds |
203
+ | ------------------------ | ------------------------------------- |
204
+ | `"2b"` / `"2u"` | Basic conformance / Unicode mapping. |
205
+ | `"2a"` / `"3a"` | A tagged structure tree. |
206
+ | `"3b"` / `"3u"` / `"3a"` | Arbitrary file attachments. |
207
+ | `"4"` | The PDF 2.0 revision of the standard. |
208
+
209
+ Invalid combinations are **TypeScript type errors**. See the [PDF/A docs](https://takumi.kane.tw/docs/pdf/pdf-a) for the structure-tree mapping and required metadata.
210
+
211
+ ## Attachments
212
+
213
+ Attach files with `attachments`. They appear in the viewer's attachment panel. Combine with `pdfa: "3b"` for ZUGFeRD and Factur-X electronic invoices:
214
+
215
+ ```tsx
216
+ const pdf = await render(invoice, {
217
+ pdfa: "3b",
218
+ metadata: { title: "Invoice 1042", creationDate: "2026-08-06" },
219
+ attachments: [
220
+ {
221
+ name: "factur-x.xml",
222
+ data: xml,
223
+ mimeType: "application/xml",
224
+ description: "Factur-X invoice data",
225
+ relationship: "alternative",
226
+ },
227
+ ],
228
+ });
229
+ ```
230
+
231
+ The PDF/A-3 levels require `mimeType`, `description`, and a modification date on each attachment. `metadata.creationDate` serves as the date fallback.
232
+
233
+ ## Measuring
234
+
235
+ `measure()` lays out a tree without rendering and returns its size in CSS px. Use it to size a header or footer band before setting `margin`:
236
+
237
+ ```tsx
238
+ import { measure } from "takumi-pdf";
239
+
240
+ const { height } = await measure(footer, { size: "a4" });
241
+ ```
242
+
169
243
  ## Images and runtimes
170
244
 
171
245
  `takumi-pdf` runs on Node.js, Bun, and Cloudflare Workers.
172
246
 
247
+ SVG images embed as vectors, not rasterized bitmaps.
248
+
173
249
  The renderer does not fetch remote images. Pass pre-fetched bytes for image URLs in the document:
174
250
 
175
251
  ```tsx
package/dist/export.cjs CHANGED
@@ -24,6 +24,26 @@ var PdfRenderer$1 = class {
24
24
  wasm.__wbg_pdfrenderer_free(ptr, 0);
25
25
  }
26
26
  /**
27
+ * Lays out a node tree without rendering and returns its size in CSS px.
28
+ * Page options lay out at the full page width, like a header/footer band;
29
+ * `pageNumber` / `totalPages` hooks are filled with three-digit counters.
30
+ * @param {Node} node
31
+ * @param {MeasureOptions | null} [options]
32
+ * @returns {MeasuredSize}
33
+ */
34
+ measure(node, options) {
35
+ try {
36
+ const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
37
+ wasm.pdfrenderer_measure(retptr, this.__wbg_ptr, addHeapObject(node), isLikeNone(options) ? 0 : addHeapObject(options));
38
+ var r0 = getDataViewMemory0().getInt32(retptr + 0, true);
39
+ var r1 = getDataViewMemory0().getInt32(retptr + 4, true);
40
+ if (getDataViewMemory0().getInt32(retptr + 8, true)) throw takeObject(r1);
41
+ return takeObject(r0);
42
+ } finally {
43
+ wasm.__wbindgen_add_to_stack_pointer(16);
44
+ }
45
+ }
46
+ /**
27
47
  * Creates a renderer with the bundled last-resort fonts.
28
48
  */
29
49
  constructor() {
@@ -43,8 +63,8 @@ var PdfRenderer$1 = class {
43
63
  /**
44
64
  * Registers a font (raw bytes or a details object), returning the families
45
65
  * it produced.
46
- * @param {any} font
47
- * @returns {any}
66
+ * @param {Font} font
67
+ * @returns {RegisteredFamily[]}
48
68
  */
49
69
  registerFont(font) {
50
70
  try {
@@ -61,9 +81,9 @@ var PdfRenderer$1 = class {
61
81
  /**
62
82
  * Renders a node tree to PDF bytes. Without options the output is paged A4;
63
83
  * `viewport` renders a single fixed page instead.
64
- * @param {any} node
65
- * @param {object | null} [options]
66
- * @returns {Uint8Array}
84
+ * @param {Node} node
85
+ * @param {PdfRenderOptions | null} [options]
86
+ * @returns {Uint8Array<ArrayBuffer>}
67
87
  */
68
88
  render(node, options) {
69
89
  try {
@@ -513,6 +533,25 @@ var PdfRenderer = class {
513
533
  fontFamilies: resources.fontFamilies
514
534
  });
515
535
  }
536
+ /**
537
+ * Lays out a node tree without rendering and returns its size in CSS px.
538
+ *
539
+ * With page options the tree lays out at the full page width with unbounded
540
+ * height, exactly how {@link render} measures a header or footer band
541
+ * (`pageNumber` / `totalPages` hooks are filled with three-digit counters),
542
+ * so the height tells you how much margin a band needs.
543
+ */
544
+ async measure(node, options = {}) {
545
+ const { fonts, images, stylesheets, fontFamilies, ...rest } = options;
546
+ const [main, resources] = await Promise.all([resolveNode(node), this.fonts.resolveResources(fonts, images, fontFamilies)]);
547
+ const sheets = [...stylesheets ?? [], ...main.stylesheets];
548
+ return this.inner.measure(main.node, {
549
+ ...rest,
550
+ stylesheets: sheets.length > 0 ? sheets : void 0,
551
+ images: resources.images,
552
+ fontFamilies: resources.fontFamilies
553
+ });
554
+ }
516
555
  /** Registers a font ahead of time, deduped against earlier registrations. */
517
556
  registerFont(font) {
518
557
  return this.fonts.register(font);
@@ -528,8 +567,14 @@ function render(node, options) {
528
567
  shared ??= new PdfRenderer();
529
568
  return shared.render(node, options);
530
569
  }
570
+ /** Measures with a lazily created shared {@link PdfRenderer}. */
571
+ function measure(node, options) {
572
+ shared ??= new PdfRenderer();
573
+ return shared.measure(node, options);
574
+ }
531
575
  //#endregion
532
576
  exports.PdfRenderer = PdfRenderer;
533
577
  exports.default = __wbg_init;
534
578
  exports.initSync = initSync;
579
+ exports.measure = measure;
535
580
  exports.render = render;
package/dist/export.d.cts CHANGED
@@ -6,6 +6,7 @@ type InitInput = RequestInfo | URL | Response | BufferSource | WebAssembly.Modul
6
6
  interface InitOutput {
7
7
  readonly memory: WebAssembly.Memory;
8
8
  readonly __wbg_pdfrenderer_free: (a: number, b: number) => void;
9
+ readonly pdfrenderer_measure: (a: number, b: number, c: number, d: number) => void;
9
10
  readonly pdfrenderer_new: (a: number) => void;
10
11
  readonly pdfrenderer_registerFont: (a: number, b: number, c: number) => void;
11
12
  readonly pdfrenderer_render: (a: number, b: number, c: number, d: number) => void;
@@ -104,6 +105,35 @@ type ViewportOptions = {
104
105
  header?: never;
105
106
  footer?: never;
106
107
  };
108
+ /**
109
+ * Options for {@link PdfRenderer.measure}: page geometry (or a viewport) plus
110
+ * layout resources. Margins do not affect the result.
111
+ */
112
+ type MeasureOptions = ({
113
+ size?: PageSize;
114
+ landscape?: boolean;
115
+ viewport?: never;
116
+ } | {
117
+ viewport: ViewportInput;
118
+ size?: never;
119
+ landscape?: never;
120
+ }) & {
121
+ /** Fonts to register before layout, deduped across calls. */
122
+ fonts?: FontLoader$1[];
123
+ /** Pre-fetched images for `src` URLs in the tree. */
124
+ images?: ImagesInput$1;
125
+ /** CSS stylesheets to apply before layout. */
126
+ stylesheets?: string[];
127
+ /** Per-render font stack: ordered family names used as the fallback chain. */
128
+ fontFamilies?: string[];
129
+ /** Default BCP-47 language tag applied to the root. */
130
+ lang?: string;
131
+ };
132
+ /** A node tree's laid-out size in CSS px. */
133
+ type MeasuredSize = {
134
+ width: number;
135
+ height: number;
136
+ };
107
137
  /** Document metadata written to the PDF's info dictionary. */
108
138
  type PdfMetadata = {
109
139
  /** The document title. */
@@ -116,8 +146,73 @@ type PdfMetadata = {
116
146
  keywords?: string[];
117
147
  /** The tool that created the source document. */
118
148
  creator?: string;
149
+ /**
150
+ * UTC creation date, `YYYY-MM-DD` or `YYYY-MM-DDTHH:MM:SS`. Tagged archival
151
+ * standards require one; supplying it keeps output deterministic.
152
+ */
153
+ creationDate?: string;
119
154
  };
120
- type RenderOptions = (PagedOptions | ViewportOptions) & {
155
+ /** A file attached to the PDF, shown in the viewer's attachment panel. */
156
+ type Attachment = {
157
+ /** File name in the PDF, e.g. "factur-x.xml". */
158
+ name: string;
159
+ /** The file's bytes, or a string encoded as UTF-8. */
160
+ data: Uint8Array | string;
161
+ /** IANA media type, e.g. "application/xml". The PDF/A-3 levels require one. */
162
+ mimeType?: string;
163
+ /** Human-readable description. The PDF/A-3 levels require one. */
164
+ description?: string;
165
+ /**
166
+ * How the file relates to the document (the PDF/A-3 AFRelationship).
167
+ * Defaults to "unspecified".
168
+ */
169
+ relationship?: "source" | "data" | "alternative" | "supplement" | "unspecified";
170
+ /**
171
+ * UTC modification date, `YYYY-MM-DD` or `YYYY-MM-DDTHH:MM:SS`; falls back
172
+ * to `metadata.creationDate`. The PDF/A-3 levels require one.
173
+ */
174
+ modificationDate?: string;
175
+ };
176
+ /** An attachment under the PDF/A-3 levels, which require the descriptive fields. */
177
+ type ArchivalAttachment = Attachment & {
178
+ mimeType: string;
179
+ description: string;
180
+ };
181
+ /**
182
+ * Standards conformance. Invalid combinations are type errors: the `a` levels
183
+ * imply a structure tree so `tagged: false` is rejected, PDF/A-4 is PDF 2.0
184
+ * while PDF/UA-1 is PDF 1.7-only so they cannot combine, and only the
185
+ * PDF/A-3 levels (or plain PDF) accept attachments.
186
+ */
187
+ type ConformanceOptions = {
188
+ pdfa?: never;
189
+ /** Structure tree: off, on (default), or validated against PDF/UA-1. */
190
+ tagged?: boolean | "ua1";
191
+ /** Files attached to the document. */
192
+ attachments?: Attachment[];
193
+ } | {
194
+ /** PDF/A conformance level. Validation failures reject the render. */
195
+ pdfa: "2b" | "2u";
196
+ tagged?: boolean | "ua1";
197
+ attachments?: never;
198
+ } | {
199
+ pdfa: "2a";
200
+ tagged?: true | "ua1";
201
+ attachments?: never;
202
+ } | {
203
+ pdfa: "3b" | "3u";
204
+ tagged?: boolean | "ua1";
205
+ attachments?: ArchivalAttachment[];
206
+ } | {
207
+ pdfa: "3a";
208
+ tagged?: true | "ua1";
209
+ attachments?: ArchivalAttachment[];
210
+ } | {
211
+ pdfa: "4";
212
+ tagged?: boolean;
213
+ attachments?: never;
214
+ };
215
+ type RenderOptions = (PagedOptions | ViewportOptions) & ConformanceOptions & {
121
216
  /** Fonts to register before rendering, deduped across calls. */
122
217
  fonts?: FontLoader$1[];
123
218
  /**
@@ -142,6 +237,15 @@ declare class PdfRenderer {
142
237
  private fonts;
143
238
  /** Renders a node tree or JSX to PDF bytes. See {@link RenderOptions}. */
144
239
  render(node: NodeInput, options?: RenderOptions): Promise<Uint8Array>;
240
+ /**
241
+ * Lays out a node tree without rendering and returns its size in CSS px.
242
+ *
243
+ * With page options the tree lays out at the full page width with unbounded
244
+ * height, exactly how {@link render} measures a header or footer band
245
+ * (`pageNumber` / `totalPages` hooks are filled with three-digit counters),
246
+ * so the height tells you how much margin a band needs.
247
+ */
248
+ measure(node: NodeInput, options?: MeasureOptions): Promise<MeasuredSize>;
145
249
  /** Registers a font ahead of time, deduped against earlier registrations. */
146
250
  registerFont(font: FontLoader$1): Promise<RegisteredFamilyLike[]>;
147
251
  /** Releases the underlying wasm renderer's memory. */
@@ -149,5 +253,7 @@ declare class PdfRenderer {
149
253
  }
150
254
  /** Renders with a lazily created shared {@link PdfRenderer}. */
151
255
  declare function render(node: NodeInput, options?: RenderOptions): Promise<Uint8Array>;
256
+ /** Measures with a lazily created shared {@link PdfRenderer}. */
257
+ declare function measure(node: NodeInput, options?: MeasureOptions): Promise<MeasuredSize>;
152
258
  //#endregion
153
- export { Dimensions, type FontLoader, type ImagesInput, NodeInput, PageMargin, PageSize, PdfMetadata, PdfRenderer, RenderOptions, ViewportInput, __wbg_init as default, initSync, render };
259
+ export { ArchivalAttachment, Attachment, Dimensions, type FontLoader, type ImagesInput, MeasureOptions, MeasuredSize, NodeInput, PageMargin, PageSize, PdfMetadata, PdfRenderer, RenderOptions, ViewportInput, __wbg_init as default, initSync, measure, render };
package/dist/export.d.mts CHANGED
@@ -6,6 +6,7 @@ type InitInput = RequestInfo | URL | Response | BufferSource | WebAssembly.Modul
6
6
  interface InitOutput {
7
7
  readonly memory: WebAssembly.Memory;
8
8
  readonly __wbg_pdfrenderer_free: (a: number, b: number) => void;
9
+ readonly pdfrenderer_measure: (a: number, b: number, c: number, d: number) => void;
9
10
  readonly pdfrenderer_new: (a: number) => void;
10
11
  readonly pdfrenderer_registerFont: (a: number, b: number, c: number) => void;
11
12
  readonly pdfrenderer_render: (a: number, b: number, c: number, d: number) => void;
@@ -104,6 +105,35 @@ type ViewportOptions = {
104
105
  header?: never;
105
106
  footer?: never;
106
107
  };
108
+ /**
109
+ * Options for {@link PdfRenderer.measure}: page geometry (or a viewport) plus
110
+ * layout resources. Margins do not affect the result.
111
+ */
112
+ type MeasureOptions = ({
113
+ size?: PageSize;
114
+ landscape?: boolean;
115
+ viewport?: never;
116
+ } | {
117
+ viewport: ViewportInput;
118
+ size?: never;
119
+ landscape?: never;
120
+ }) & {
121
+ /** Fonts to register before layout, deduped across calls. */
122
+ fonts?: FontLoader$1[];
123
+ /** Pre-fetched images for `src` URLs in the tree. */
124
+ images?: ImagesInput$1;
125
+ /** CSS stylesheets to apply before layout. */
126
+ stylesheets?: string[];
127
+ /** Per-render font stack: ordered family names used as the fallback chain. */
128
+ fontFamilies?: string[];
129
+ /** Default BCP-47 language tag applied to the root. */
130
+ lang?: string;
131
+ };
132
+ /** A node tree's laid-out size in CSS px. */
133
+ type MeasuredSize = {
134
+ width: number;
135
+ height: number;
136
+ };
107
137
  /** Document metadata written to the PDF's info dictionary. */
108
138
  type PdfMetadata = {
109
139
  /** The document title. */
@@ -116,8 +146,73 @@ type PdfMetadata = {
116
146
  keywords?: string[];
117
147
  /** The tool that created the source document. */
118
148
  creator?: string;
149
+ /**
150
+ * UTC creation date, `YYYY-MM-DD` or `YYYY-MM-DDTHH:MM:SS`. Tagged archival
151
+ * standards require one; supplying it keeps output deterministic.
152
+ */
153
+ creationDate?: string;
119
154
  };
120
- type RenderOptions = (PagedOptions | ViewportOptions) & {
155
+ /** A file attached to the PDF, shown in the viewer's attachment panel. */
156
+ type Attachment = {
157
+ /** File name in the PDF, e.g. "factur-x.xml". */
158
+ name: string;
159
+ /** The file's bytes, or a string encoded as UTF-8. */
160
+ data: Uint8Array | string;
161
+ /** IANA media type, e.g. "application/xml". The PDF/A-3 levels require one. */
162
+ mimeType?: string;
163
+ /** Human-readable description. The PDF/A-3 levels require one. */
164
+ description?: string;
165
+ /**
166
+ * How the file relates to the document (the PDF/A-3 AFRelationship).
167
+ * Defaults to "unspecified".
168
+ */
169
+ relationship?: "source" | "data" | "alternative" | "supplement" | "unspecified";
170
+ /**
171
+ * UTC modification date, `YYYY-MM-DD` or `YYYY-MM-DDTHH:MM:SS`; falls back
172
+ * to `metadata.creationDate`. The PDF/A-3 levels require one.
173
+ */
174
+ modificationDate?: string;
175
+ };
176
+ /** An attachment under the PDF/A-3 levels, which require the descriptive fields. */
177
+ type ArchivalAttachment = Attachment & {
178
+ mimeType: string;
179
+ description: string;
180
+ };
181
+ /**
182
+ * Standards conformance. Invalid combinations are type errors: the `a` levels
183
+ * imply a structure tree so `tagged: false` is rejected, PDF/A-4 is PDF 2.0
184
+ * while PDF/UA-1 is PDF 1.7-only so they cannot combine, and only the
185
+ * PDF/A-3 levels (or plain PDF) accept attachments.
186
+ */
187
+ type ConformanceOptions = {
188
+ pdfa?: never;
189
+ /** Structure tree: off, on (default), or validated against PDF/UA-1. */
190
+ tagged?: boolean | "ua1";
191
+ /** Files attached to the document. */
192
+ attachments?: Attachment[];
193
+ } | {
194
+ /** PDF/A conformance level. Validation failures reject the render. */
195
+ pdfa: "2b" | "2u";
196
+ tagged?: boolean | "ua1";
197
+ attachments?: never;
198
+ } | {
199
+ pdfa: "2a";
200
+ tagged?: true | "ua1";
201
+ attachments?: never;
202
+ } | {
203
+ pdfa: "3b" | "3u";
204
+ tagged?: boolean | "ua1";
205
+ attachments?: ArchivalAttachment[];
206
+ } | {
207
+ pdfa: "3a";
208
+ tagged?: true | "ua1";
209
+ attachments?: ArchivalAttachment[];
210
+ } | {
211
+ pdfa: "4";
212
+ tagged?: boolean;
213
+ attachments?: never;
214
+ };
215
+ type RenderOptions = (PagedOptions | ViewportOptions) & ConformanceOptions & {
121
216
  /** Fonts to register before rendering, deduped across calls. */
122
217
  fonts?: FontLoader$1[];
123
218
  /**
@@ -142,6 +237,15 @@ declare class PdfRenderer {
142
237
  private fonts;
143
238
  /** Renders a node tree or JSX to PDF bytes. See {@link RenderOptions}. */
144
239
  render(node: NodeInput, options?: RenderOptions): Promise<Uint8Array>;
240
+ /**
241
+ * Lays out a node tree without rendering and returns its size in CSS px.
242
+ *
243
+ * With page options the tree lays out at the full page width with unbounded
244
+ * height, exactly how {@link render} measures a header or footer band
245
+ * (`pageNumber` / `totalPages` hooks are filled with three-digit counters),
246
+ * so the height tells you how much margin a band needs.
247
+ */
248
+ measure(node: NodeInput, options?: MeasureOptions): Promise<MeasuredSize>;
145
249
  /** Registers a font ahead of time, deduped against earlier registrations. */
146
250
  registerFont(font: FontLoader$1): Promise<RegisteredFamilyLike[]>;
147
251
  /** Releases the underlying wasm renderer's memory. */
@@ -149,5 +253,7 @@ declare class PdfRenderer {
149
253
  }
150
254
  /** Renders with a lazily created shared {@link PdfRenderer}. */
151
255
  declare function render(node: NodeInput, options?: RenderOptions): Promise<Uint8Array>;
256
+ /** Measures with a lazily created shared {@link PdfRenderer}. */
257
+ declare function measure(node: NodeInput, options?: MeasureOptions): Promise<MeasuredSize>;
152
258
  //#endregion
153
- export { Dimensions, type FontLoader, type ImagesInput, NodeInput, PageMargin, PageSize, PdfMetadata, PdfRenderer, RenderOptions, ViewportInput, __wbg_init as default, initSync, render };
259
+ export { ArchivalAttachment, Attachment, Dimensions, type FontLoader, type ImagesInput, MeasureOptions, MeasuredSize, NodeInput, PageMargin, PageSize, PdfMetadata, PdfRenderer, RenderOptions, ViewportInput, __wbg_init as default, initSync, measure, render };
package/dist/export.mjs CHANGED
@@ -20,6 +20,26 @@ var PdfRenderer$1 = class {
20
20
  wasm.__wbg_pdfrenderer_free(ptr, 0);
21
21
  }
22
22
  /**
23
+ * Lays out a node tree without rendering and returns its size in CSS px.
24
+ * Page options lay out at the full page width, like a header/footer band;
25
+ * `pageNumber` / `totalPages` hooks are filled with three-digit counters.
26
+ * @param {Node} node
27
+ * @param {MeasureOptions | null} [options]
28
+ * @returns {MeasuredSize}
29
+ */
30
+ measure(node, options) {
31
+ try {
32
+ const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
33
+ wasm.pdfrenderer_measure(retptr, this.__wbg_ptr, addHeapObject(node), isLikeNone(options) ? 0 : addHeapObject(options));
34
+ var r0 = getDataViewMemory0().getInt32(retptr + 0, true);
35
+ var r1 = getDataViewMemory0().getInt32(retptr + 4, true);
36
+ if (getDataViewMemory0().getInt32(retptr + 8, true)) throw takeObject(r1);
37
+ return takeObject(r0);
38
+ } finally {
39
+ wasm.__wbindgen_add_to_stack_pointer(16);
40
+ }
41
+ }
42
+ /**
23
43
  * Creates a renderer with the bundled last-resort fonts.
24
44
  */
25
45
  constructor() {
@@ -39,8 +59,8 @@ var PdfRenderer$1 = class {
39
59
  /**
40
60
  * Registers a font (raw bytes or a details object), returning the families
41
61
  * it produced.
42
- * @param {any} font
43
- * @returns {any}
62
+ * @param {Font} font
63
+ * @returns {RegisteredFamily[]}
44
64
  */
45
65
  registerFont(font) {
46
66
  try {
@@ -57,9 +77,9 @@ var PdfRenderer$1 = class {
57
77
  /**
58
78
  * Renders a node tree to PDF bytes. Without options the output is paged A4;
59
79
  * `viewport` renders a single fixed page instead.
60
- * @param {any} node
61
- * @param {object | null} [options]
62
- * @returns {Uint8Array}
80
+ * @param {Node} node
81
+ * @param {PdfRenderOptions | null} [options]
82
+ * @returns {Uint8Array<ArrayBuffer>}
63
83
  */
64
84
  render(node, options) {
65
85
  try {
@@ -509,6 +529,25 @@ var PdfRenderer = class {
509
529
  fontFamilies: resources.fontFamilies
510
530
  });
511
531
  }
532
+ /**
533
+ * Lays out a node tree without rendering and returns its size in CSS px.
534
+ *
535
+ * With page options the tree lays out at the full page width with unbounded
536
+ * height, exactly how {@link render} measures a header or footer band
537
+ * (`pageNumber` / `totalPages` hooks are filled with three-digit counters),
538
+ * so the height tells you how much margin a band needs.
539
+ */
540
+ async measure(node, options = {}) {
541
+ const { fonts, images, stylesheets, fontFamilies, ...rest } = options;
542
+ const [main, resources] = await Promise.all([resolveNode(node), this.fonts.resolveResources(fonts, images, fontFamilies)]);
543
+ const sheets = [...stylesheets ?? [], ...main.stylesheets];
544
+ return this.inner.measure(main.node, {
545
+ ...rest,
546
+ stylesheets: sheets.length > 0 ? sheets : void 0,
547
+ images: resources.images,
548
+ fontFamilies: resources.fontFamilies
549
+ });
550
+ }
512
551
  /** Registers a font ahead of time, deduped against earlier registrations. */
513
552
  registerFont(font) {
514
553
  return this.fonts.register(font);
@@ -524,5 +563,10 @@ function render(node, options) {
524
563
  shared ??= new PdfRenderer();
525
564
  return shared.render(node, options);
526
565
  }
566
+ /** Measures with a lazily created shared {@link PdfRenderer}. */
567
+ function measure(node, options) {
568
+ shared ??= new PdfRenderer();
569
+ return shared.measure(node, options);
570
+ }
527
571
  //#endregion
528
- export { PdfRenderer, __wbg_init as default, initSync, render };
572
+ export { PdfRenderer, __wbg_init as default, initSync, measure, render };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "takumi-pdf",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "HTML/JSX to paged, selectable-text PDF, powered by takumi. WebAssembly, no Chromium.",
5
5
  "keywords": [
6
6
  "css",
@@ -83,11 +83,11 @@
83
83
  "publish-lint": "attw --pack . && publint --strict ."
84
84
  },
85
85
  "dependencies": {
86
- "@takumi-rs/helpers": "2.5.9"
86
+ "@takumi-rs/helpers": "2.5.10"
87
87
  },
88
88
  "devDependencies": {
89
89
  "@types/bun": "^1.3.14",
90
- "@types/react": "19.2.17",
90
+ "@types/react": "19.2.18",
91
91
  "tsdown": "0.22.14"
92
92
  },
93
93
  "peerDependencies": {
Binary file
@@ -2,6 +2,7 @@
2
2
  /* eslint-disable */
3
3
  export const memory: WebAssembly.Memory;
4
4
  export const __wbg_pdfrenderer_free: (a: number, b: number) => void;
5
+ export const pdfrenderer_measure: (a: number, b: number, c: number, d: number) => void;
5
6
  export const pdfrenderer_new: (a: number) => void;
6
7
  export const pdfrenderer_registerFont: (a: number, b: number, c: number) => void;
7
8
  export const pdfrenderer_render: (a: number, b: number, c: number, d: number) => void;