takumi-pdf 0.14.3 → 0.15.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
@@ -7,22 +7,22 @@
7
7
 
8
8
  Build invoices and reports with CSS or Tailwind. The renderer writes vector PDF with selectable text.
9
9
 
10
- [Documentation](https://takumi.kane.tw/docs/) · [Playground](https://takumi.kane.tw/playground)
10
+ [PDF documentation](https://takumi.kane.tw/docs/pdf) · [Playground](https://takumi.kane.tw/playground)
11
11
 
12
12
  </div>
13
13
 
14
- ## Why
14
+ ## How it works
15
15
 
16
- Browser-based PDF generation starts a browser process and ships a Chrome installation. `takumi-pdf` compiles Takumi's layout and PDF code to WebAssembly.
16
+ `takumi-pdf` runs Takumi's layout and PDF engine in WebAssembly on Node.js, Bun, and Cloudflare Workers. It does not launch a browser process.
17
17
 
18
- Pass JSX or a Takumi node tree with CSS. The renderer returns vector PDF bytes with searchable text and embedded subset fonts.
18
+ Pass JSX, an HTML string, or a Takumi node tree with CSS. The renderer returns vector PDF bytes with searchable text and embedded subset fonts.
19
19
 
20
20
  ## Install
21
21
 
22
22
  ```bash
23
- npm install takumi-pdf
23
+ npm install takumi-pdf @takumi-rs/helpers
24
24
  # or
25
- bun add takumi-pdf
25
+ bun add takumi-pdf @takumi-rs/helpers
26
26
  ```
27
27
 
28
28
  ## Quick start
@@ -55,224 +55,31 @@ const pdf = await render(
55
55
  await writeFile("invoice.pdf", pdf);
56
56
  ```
57
57
 
58
- `render()` returns `Promise<Uint8Array>`. Paged output defaults to A4 with a uniform 48px margin; content flows across as many pages as it needs.
58
+ `render()` returns `Promise<Uint8Array>`. Paged output defaults to A4, and `margin` defaults to `"auto"` on all sides. Without headers or footers, margins are 37.8px. Top and bottom margins can expand to fit header or footer bands. Content flows across pages automatically.
59
59
 
60
- ## Page setup
60
+ ## Choose the next guide
61
61
 
62
- ```tsx
63
- const pdf = await render(report, {
64
- size: "letter",
65
- landscape: true,
66
- margin: { top: 48, right: 32, bottom: 48, left: 32 },
67
- });
68
- ```
69
-
70
- | Option | Type | Default | Description |
71
- | ----------- | ---------------------------------------------- | ------- | ------------------------------------------------------------ |
72
- | `size` | `"a4"`, `"letter"`, or `{ width, height }` | `"a4"` | Page size in CSS px at 96 dpi. Presets ignore case. |
73
- | `landscape` | `boolean` | `false` | Swaps page width and height, including explicit sizes. |
74
- | `margin` | `number` or `{ top?, right?, bottom?, left? }` | `48` | A number applies to all sides. Missing object sides are `0`. |
75
-
76
- ## Headers and footers
77
-
78
- Headers and footers repeat on every page. `<PageNumber />` and `<TotalPages />` place the counters; the `format` prop picks a CSS counter style.
79
-
80
- ```tsx
81
- import { render } from "takumi-pdf";
82
- import { PageNumber, TotalPages } from "takumi-pdf/primitives";
83
-
84
- const pdf = await render(report, {
85
- footer: (
86
- <div style={{ fontSize: 12 }}>
87
- 第 <PageNumber format="trad-chinese-informal" /> 頁,共{" "}
88
- <TotalPages format="trad-chinese-informal" /> 頁
89
- </div>
90
- ),
91
- });
92
- ```
93
-
94
- The primitives render class hooks, the same `pageNumber` / `totalPages` names Chromium's print templates use, so HTML input writes `<span class="pageNumber"></span>` directly.
95
-
96
- | Counter style | Example |
97
- | ------------------------------------------- | ------------- |
98
- | `decimal` (default) | `12` |
99
- | `decimal-leading-zero` | `07` |
100
- | `lower-roman` / `upper-roman` | `xii` / `XII` |
101
- | `cjk-decimal` | `一二` |
102
- | `trad-chinese-informal` / `cjk-ideographic` | `十二` |
62
+ | Task | Guide |
63
+ | --------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
64
+ | Set page size, margins, and runtime imports | [PDF getting started](https://takumi.kane.tw/docs/pdf) |
65
+ | Control page breaks and keep content together | [Pagination](https://takumi.kane.tw/docs/pdf/pagination) |
66
+ | Reserve space for repeated headers and page numbers | [Headers and footers](https://takumi.kane.tw/docs/pdf/headers-and-footers) |
67
+ | Create a certificate, ticket, or receipt | [Single-page output](https://takumi.kane.tw/docs/pdf/single-page) |
68
+ | Load fonts and remote image bytes | [Fonts and images](https://takumi.kane.tw/docs/pdf/fonts-and-images) |
69
+ | Add hyperlinks, bookmarks, and a table of contents | [Links and metadata](https://takumi.kane.tw/docs/pdf/links-and-outline) |
70
+ | Configure archival or accessible output | [PDF/A and PDF/UA](https://takumi.kane.tw/docs/pdf/pdf-a) |
71
+ | Embed invoice XML or another file | [Attachments](https://takumi.kane.tw/docs/pdf/attachments) |
72
+ | Replace a browser or document renderer | [From Puppeteer](https://takumi.kane.tw/docs/pdf/from-puppeteer) · [From react-pdf](https://takumi.kane.tw/docs/pdf/from-react-pdf) |
103
73
 
104
- ## Single-page viewport
105
-
106
- Use `viewport` for a fixed one-page PDF, such as a certificate or card. Percentage heights resolve against the viewport and overflow is clipped, like an image render.
107
-
108
- ```tsx
109
- const pdf = await render(<div style={{ width: "100%", height: "100%" }}>Certificate</div>, {
110
- viewport: { width: 1123, height: 794 },
111
- });
112
- ```
113
-
114
- Omit `height` to size the single page to its content, like a thermal receipt. Percentage heights do not resolve there.
115
-
116
- ```tsx
117
- const pdf = await render(receipt, { viewport: { width: 302 } });
118
- ```
119
-
120
- `viewport` cannot be combined with `size`, `landscape`, `margin`, `header`, or `footer`.
121
-
122
- ## Fonts
123
-
124
- Pass a font URL, font bytes, or the `googleFonts` helper. Registered fonts are deduplicated across calls.
125
-
126
- ```tsx
127
- import { googleFonts } from "@takumi-rs/helpers";
128
- import { render } from "takumi-pdf";
129
-
130
- const pdf = await render(doc, {
131
- fonts: [...(await googleFonts(["Inter"])), { name: "Brand Sans", weight: 700, data: fontBytes }],
132
- fontFamilies: ["Brand Sans", "Inter", "sans-serif"],
133
- });
134
- ```
135
-
136
- Reuse a `PdfRenderer` when an application renders many documents:
137
-
138
- ```tsx
139
- import { PdfRenderer } from "takumi-pdf";
140
-
141
- const renderer = new PdfRenderer();
142
- await renderer.registerFont("https://example.com/Inter-Regular.woff2");
143
-
144
- const pdf = await renderer.render(doc);
145
- renderer.free();
146
- ```
147
-
148
- ## Pagination CSS
149
-
150
- ```tsx
151
- const pdf = await render(
152
- <article>
153
- <h1 style={{ breakBefore: "page" }}>Chapter two</h1>
154
- <section style={{ breakInside: "avoid" }}>Keep this together.</section>
155
- </article>,
156
- );
157
- ```
74
+ ## Before using an existing template
158
75
 
159
- | Property | Effect |
160
- | ----------------------------- | ------------------------------------------------------- |
161
- | `break-before: page` | Starts the element on a new page. |
162
- | `break-after: page` | Starts the following content on a new page. |
163
- | `break-inside: avoid` | Keeps the element on one page when it fits. |
164
- | `box-decoration-break: clone` | Repeats borders and backgrounds on every page fragment. |
165
-
166
- ## Tables
167
-
168
- `<table>` markup lays out on shared column tracks, so column x positions stay identical across pages. A `<thead>` paints again at the top of every page its table continues onto, when it is at most a quarter of the page tall and no header cell spans into the body.
169
-
170
- ```tsx
171
- const pdf = await render(
172
- <table>
173
- <thead>
174
- <tr>
175
- <th>Name</th>
176
- <th>Qty</th>
177
- </tr>
178
- </thead>
179
- <tbody>{rows}</tbody>
180
- </table>,
181
- );
182
- ```
183
-
184
- ## Links, outline, and metadata
185
-
186
- Anchors with an `href` become clickable link annotations. `<TargetPageNumber />` prints the page a link's target lands on, which is what a table of contents needs. `outline: true` builds PDF bookmarks from `h1` through `h6` headings. `metadata` fills the document properties:
187
-
188
- ```tsx
189
- const pdf = await render(report, {
190
- outline: true,
191
- lang: "en",
192
- metadata: {
193
- title: "Annual report 2026",
194
- authors: ["Acme Inc."],
195
- creationDate: "2026-08-06",
196
- },
197
- });
198
- ```
199
-
200
- Omit `metadata` to keep output byte-identical across runs.
201
-
202
- ## Tagged output and PDF/A
203
-
204
- Output is **tagged by default**: HTML semantics (`h1` through `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.
205
-
206
- `<table>` markup lays out and paints, but carries no `Table` structure elements yet.
207
-
208
- `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).
209
-
210
- ```tsx
211
- const pdf = await render(report, {
212
- pdfa: "2a",
213
- tagged: "ua1",
214
- lang: "en",
215
- metadata: { title: "Annual report", creationDate: "2026-08-06" },
216
- });
217
- ```
218
-
219
- | Level | What it adds |
220
- | ------------------------ | ------------------------------------- |
221
- | `"2b"` / `"2u"` | Basic conformance / Unicode mapping. |
222
- | `"2a"` / `"3a"` | A tagged structure tree. |
223
- | `"3b"` / `"3u"` / `"3a"` | Arbitrary file attachments. |
224
- | `"4"` | The PDF 2.0 revision of the standard. |
225
- | `"4f"` | PDF 2.0 with file attachments. |
226
-
227
- 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.
228
-
229
- ## Attachments
230
-
231
- Attach files with `attachments`. They appear in the viewer's attachment panel. Combine with `pdfa: "3b"` for ZUGFeRD and Factur-X electronic invoices:
232
-
233
- ```tsx
234
- const pdf = await render(invoice, {
235
- pdfa: "3b",
236
- metadata: { title: "Invoice 1042", creationDate: "2026-08-06" },
237
- attachments: [
238
- {
239
- name: "factur-x.xml",
240
- data: xml,
241
- mimeType: "application/xml",
242
- description: "Factur-X invoice data",
243
- relationship: "alternative",
244
- },
245
- ],
246
- });
247
- ```
248
-
249
- The PDF/A-3 levels require `mimeType`, `description`, and a modification date on each attachment. `metadata.creationDate` serves as the date fallback.
250
-
251
- ## Measuring
252
-
253
- `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`:
254
-
255
- ```tsx
256
- import { measure } from "takumi-pdf";
257
-
258
- const { height } = await measure(footer, { size: "a4" });
259
- ```
260
-
261
- ## Images and runtimes
262
-
263
- `takumi-pdf` runs on Node.js, Bun, and Cloudflare Workers.
264
-
265
- SVG images embed as vectors, not rasterized bitmaps.
266
-
267
- The renderer does not fetch remote images. Pass pre-fetched bytes for image URLs in the document:
268
-
269
- ```tsx
270
- const pdf = await render(doc, {
271
- images: [{ src: "https://example.com/logo.png", data: logoBytes }],
272
- });
273
- ```
76
+ - **Fonts:** Takumi does not read system fonts. Register fonts that cover your text.
77
+ - **Images:** Fetch remote document images yourself and pass their bytes through `images`.
78
+ - **Page geometry:** Use `size`, `landscape`, and `margin`. CSS `@page` rules are not supported.
79
+ - **Effects:** PDF output rejects CSS `filter: blur()`, `drop-shadow()`, and `backdrop-filter`. Prepare those effects as images before rendering.
80
+ - **Validation:** Tagged PDF is enabled by default. Choose the conformance options your document needs and validate the result.
274
81
 
275
- `@page` CSS rules are not supported. Set page geometry with `size`, `landscape`, and `margin`.
82
+ See the [PDF renderer comparison](https://takumi.kane.tw/docs/pdf/comparison) for recorded benchmarks and rendering differences. Runnable examples cover [invoices and receipts](https://github.com/kane50613/takumi/tree/master/example/generate-invoice) and [Factur-X invoices](https://github.com/kane50613/takumi/tree/master/example/e-invoice).
276
83
 
277
84
  ## License
278
85
 
package/dist/export.cjs CHANGED
@@ -103,10 +103,10 @@ function __wbg_get_imports() {
103
103
  __proto__: null,
104
104
  "./takumi_pdf_wasm_bg.js": {
105
105
  __proto__: null,
106
- __wbg_Error_408e67f47ca7b58b: function(arg0, arg1) {
106
+ __wbg_Error_67e7344beaa85059: function(arg0, arg1) {
107
107
  return Error(getStringFromWasm0(arg0, arg1));
108
108
  },
109
- __wbg_Number_3890faa6d3ff057d: function(arg0) {
109
+ __wbg_Number_c54e7112a3fa7e3e: function(arg0) {
110
110
  return Number(arg0);
111
111
  },
112
112
  __wbg_String_8564e559799eccda: function(arg0, arg1) {
@@ -115,55 +115,55 @@ function __wbg_get_imports() {
115
115
  getDataViewMemory0().setInt32(arg0 + 4, len1, true);
116
116
  getDataViewMemory0().setInt32(arg0 + 0, ptr1, true);
117
117
  },
118
- __wbg___wbindgen_bigint_get_as_i64_c4ecf48528083721: function(arg0, arg1) {
118
+ __wbg___wbindgen_bigint_get_as_i64_b482365c149396c8: function(arg0, arg1) {
119
119
  const v = arg1;
120
120
  const ret = typeof v === "bigint" ? v : void 0;
121
121
  getDataViewMemory0().setBigInt64(arg0 + 8, isLikeNone(ret) ? BigInt(0) : ret, true);
122
122
  getDataViewMemory0().setInt32(arg0 + 0, !isLikeNone(ret), true);
123
123
  },
124
- __wbg___wbindgen_boolean_get_c9c83ebd41b34df3: function(arg0) {
124
+ __wbg___wbindgen_boolean_get_7a12af2b3f899c5a: function(arg0) {
125
125
  const v = arg0;
126
126
  const ret = typeof v === "boolean" ? v : void 0;
127
127
  return isLikeNone(ret) ? 16777215 : ret ? 1 : 0;
128
128
  },
129
- __wbg___wbindgen_debug_string_a57024b9c6e4a48b: function(arg0, arg1) {
129
+ __wbg___wbindgen_debug_string_0e68cf47c9cbd9b0: function(arg0, arg1) {
130
130
  const ptr1 = passStringToWasm0(debugString(arg1), wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
131
131
  const len1 = WASM_VECTOR_LEN;
132
132
  getDataViewMemory0().setInt32(arg0 + 4, len1, true);
133
133
  getDataViewMemory0().setInt32(arg0 + 0, ptr1, true);
134
134
  },
135
- __wbg___wbindgen_in_ac983077f137f2e6: function(arg0, arg1) {
135
+ __wbg___wbindgen_in_50072d4d6e45c193: function(arg0, arg1) {
136
136
  return arg0 in arg1;
137
137
  },
138
- __wbg___wbindgen_is_bigint_8ffbbef442139384: function(arg0) {
138
+ __wbg___wbindgen_is_bigint_60fc0336cb14f5d7: function(arg0) {
139
139
  return typeof arg0 === "bigint";
140
140
  },
141
- __wbg___wbindgen_is_function_5e4570eb24ffa122: function(arg0) {
141
+ __wbg___wbindgen_is_function_fcda5e3902d732fe: function(arg0) {
142
142
  return typeof arg0 === "function";
143
143
  },
144
- __wbg___wbindgen_is_object_a2790eb24c211ea0: function(arg0) {
144
+ __wbg___wbindgen_is_object_edb6b15aa3afe12e: function(arg0) {
145
145
  const val = arg0;
146
146
  return typeof val === "object" && val !== null;
147
147
  },
148
- __wbg___wbindgen_is_string_e6f02f0ea5f20a32: function(arg0) {
148
+ __wbg___wbindgen_is_string_c4f7cb494a2a21f1: function(arg0) {
149
149
  return typeof arg0 === "string";
150
150
  },
151
- __wbg___wbindgen_is_undefined_6cff064c44e0d823: function(arg0) {
151
+ __wbg___wbindgen_is_undefined_8c687d0b90d5b524: function(arg0) {
152
152
  return arg0 === void 0;
153
153
  },
154
- __wbg___wbindgen_jsval_eq_0a18949a61670320: function(arg0, arg1) {
154
+ __wbg___wbindgen_jsval_eq_9fdcd3c0a860dd3b: function(arg0, arg1) {
155
155
  return arg0 === arg1;
156
156
  },
157
- __wbg___wbindgen_jsval_loose_eq_acf2776254a8d832: function(arg0, arg1) {
157
+ __wbg___wbindgen_jsval_loose_eq_3c30021c243b64cd: function(arg0, arg1) {
158
158
  return arg0 == arg1;
159
159
  },
160
- __wbg___wbindgen_number_get_136b9679cab35cfb: function(arg0, arg1) {
160
+ __wbg___wbindgen_number_get_1dc732b810cb937c: function(arg0, arg1) {
161
161
  const obj = arg1;
162
162
  const ret = typeof obj === "number" ? obj : void 0;
163
163
  getDataViewMemory0().setFloat64(arg0 + 8, isLikeNone(ret) ? 0 : ret, true);
164
164
  getDataViewMemory0().setInt32(arg0 + 0, !isLikeNone(ret), true);
165
165
  },
166
- __wbg___wbindgen_string_get_d154f1e671052120: function(arg0, arg1) {
166
+ __wbg___wbindgen_string_get_92ab86bb19cbc12f: function(arg0, arg1) {
167
167
  const obj = arg1;
168
168
  const ret = typeof obj === "string" ? obj : void 0;
169
169
  var ptr1 = isLikeNone(ret) ? 0 : passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
@@ -171,35 +171,35 @@ function __wbg_get_imports() {
171
171
  getDataViewMemory0().setInt32(arg0 + 4, len1, true);
172
172
  getDataViewMemory0().setInt32(arg0 + 0, ptr1, true);
173
173
  },
174
- __wbg___wbindgen_throw_bb96b2010945f0bc: function(arg0, arg1) {
174
+ __wbg___wbindgen_throw_5d9e815e6fdf150f: function(arg0, arg1) {
175
175
  throw new Error(getStringFromWasm0(arg0, arg1));
176
176
  },
177
- __wbg_call_1c5886ab9c57d1c7: function() {
177
+ __wbg_call_269c5566fbede3eb: function() {
178
178
  return handleError(function(arg0, arg1) {
179
179
  return arg0.call(arg1);
180
180
  }, arguments);
181
181
  },
182
- __wbg_done_669171204c3dcae2: function(arg0) {
182
+ __wbg_done_cffed884d87aa22e: function(arg0) {
183
183
  return arg0.done;
184
184
  },
185
- __wbg_entries_7774d489e1da5f4f: function(arg0) {
185
+ __wbg_entries_972a87586902cf87: function(arg0) {
186
186
  return Object.entries(arg0);
187
187
  },
188
- __wbg_get_c0c8f8d7da0c03dd: function(arg0, arg1) {
189
- return arg0[arg1 >>> 0];
190
- },
191
- __wbg_get_d173c0308df22d37: function() {
188
+ __wbg_get_6cf5a4d4d8ad3c5a: function() {
192
189
  return handleError(function(arg0, arg1) {
193
190
  return Reflect.get(arg0, arg1);
194
191
  }, arguments);
195
192
  },
196
- __wbg_get_unchecked_e20b893aeafc3fca: function(arg0, arg1) {
193
+ __wbg_get_b1f0ab13c737f856: function(arg0, arg1) {
194
+ return arg0[arg1 >>> 0];
195
+ },
196
+ __wbg_get_unchecked_363572bdd397d473: function(arg0, arg1) {
197
197
  return arg0[arg1 >>> 0];
198
198
  },
199
199
  __wbg_get_with_ref_key_6412cf3094599694: function(arg0, arg1) {
200
200
  return arg0[arg1];
201
201
  },
202
- __wbg_instanceof_ArrayBuffer_993d02d2d254cad1: function(arg0) {
202
+ __wbg_instanceof_ArrayBuffer_d4ff01f8247925ae: function(arg0) {
203
203
  let result;
204
204
  try {
205
205
  result = arg0 instanceof ArrayBuffer;
@@ -208,7 +208,7 @@ function __wbg_get_imports() {
208
208
  }
209
209
  return result;
210
210
  },
211
- __wbg_instanceof_Map_9a4d6ead180ae3a9: function(arg0) {
211
+ __wbg_instanceof_Map_1ff6a2b54c899f0d: function(arg0) {
212
212
  let result;
213
213
  try {
214
214
  result = arg0 instanceof Map;
@@ -217,7 +217,7 @@ function __wbg_get_imports() {
217
217
  }
218
218
  return result;
219
219
  },
220
- __wbg_instanceof_Uint8Array_f935dbb0aa7cdeed: function(arg0) {
220
+ __wbg_instanceof_Uint8Array_598adc0fef426aa8: function(arg0) {
221
221
  let result;
222
222
  try {
223
223
  result = arg0 instanceof Uint8Array;
@@ -226,63 +226,63 @@ function __wbg_get_imports() {
226
226
  }
227
227
  return result;
228
228
  },
229
- __wbg_isArray_6339f732981044bf: function(arg0) {
229
+ __wbg_isArray_5674713bb7b79043: function(arg0) {
230
230
  return Array.isArray(arg0);
231
231
  },
232
- __wbg_isSafeInteger_f3d6cd19ccfe4512: function(arg0) {
232
+ __wbg_isSafeInteger_8f51c743827d1ec5: function(arg0) {
233
233
  return Number.isSafeInteger(arg0);
234
234
  },
235
- __wbg_iterator_5cebbb86e33c6dd6: function() {
235
+ __wbg_iterator_22ddeb808cf55a6f: function() {
236
236
  return Symbol.iterator;
237
237
  },
238
- __wbg_length_36bd29c6848c2144: function(arg0) {
238
+ __wbg_length_31bdaf014f5fbde2: function(arg0) {
239
239
  return arg0.length;
240
240
  },
241
- __wbg_length_ecfa2c63d3d0d82c: function(arg0) {
241
+ __wbg_length_4e1adc0d42e23620: function(arg0) {
242
242
  return arg0.length;
243
243
  },
244
- __wbg_new_116be93542d39019: function() {
245
- return new Array();
244
+ __wbg_new_1da3429bc3c4541c: function(arg0) {
245
+ return new Uint8Array(arg0);
246
246
  },
247
- __wbg_new_358857d90afd5a2d: function(arg0, arg1) {
247
+ __wbg_new_a32a1ab6c6655abe: function(arg0, arg1) {
248
248
  return new Error(getStringFromWasm0(arg0, arg1));
249
249
  },
250
- __wbg_new_77cc4f4f472aeb81: function(arg0) {
251
- return new Uint8Array(arg0);
252
- },
253
- __wbg_new_ebe3e0f6837f0879: function() {
250
+ __wbg_new_bebc3f4757acf305: function() {
254
251
  return /* @__PURE__ */ new Object();
255
252
  },
256
- __wbg_next_42cf16ee0dafc9e2: function() {
253
+ __wbg_new_ffa92086ea89f79c: function() {
254
+ return new Array();
255
+ },
256
+ __wbg_next_95053e306b1c3aed: function(arg0) {
257
+ return arg0.next;
258
+ },
259
+ __wbg_next_f31ecb8646d2c605: function() {
257
260
  return handleError(function(arg0) {
258
261
  return arg0.next();
259
262
  }, arguments);
260
263
  },
261
- __wbg_next_8f26b64fa5e9f64b: function(arg0) {
262
- return arg0.next;
263
- },
264
- __wbg_prototypesetcall_de8e0d9553586985: function(arg0, arg1, arg2) {
264
+ __wbg_prototypesetcall_ae9f5e7459250748: function(arg0, arg1, arg2) {
265
265
  Uint8Array.prototype.set.call(getArrayU8FromWasm0(arg0, arg1), arg2);
266
266
  },
267
+ __wbg_set_13d25b81ab403f5e: function(arg0, arg1, arg2) {
268
+ arg0[arg1 >>> 0] = arg2;
269
+ },
267
270
  __wbg_set_6be42768c690e380: function(arg0, arg1, arg2) {
268
271
  arg0[arg1] = arg2;
269
272
  },
270
- __wbg_set_a80955eb93b145c6: function(arg0, arg1, arg2) {
271
- arg0[arg1 >>> 0] = arg2;
272
- },
273
- __wbg_value_1e2369fab29b420e: function(arg0) {
273
+ __wbg_value_c227f843d21da141: function(arg0) {
274
274
  return arg0.value;
275
275
  },
276
- __wbindgen_cast_0000000000000001: function(arg0) {
276
+ __wbindgen_generic_0000000000000001: function(arg0) {
277
277
  return arg0;
278
278
  },
279
- __wbindgen_cast_0000000000000002: function(arg0) {
279
+ __wbindgen_generic_0000000000000002: function(arg0) {
280
280
  return arg0;
281
281
  },
282
- __wbindgen_cast_0000000000000003: function(arg0, arg1) {
282
+ __wbindgen_generic_0000000000000003: function(arg0, arg1) {
283
283
  return getStringFromWasm0(arg0, arg1);
284
284
  },
285
- __wbindgen_cast_0000000000000004: function(arg0) {
285
+ __wbindgen_generic_0000000000000004: function(arg0) {
286
286
  return BigInt.asUintN(64, arg0);
287
287
  },
288
288
  __wbindgen_init_externref_table: function() {
@@ -529,6 +529,50 @@ function ownCss(css, stylesheets) {
529
529
  if (stylesheets !== void 0) warnStylesheetsDeprecated();
530
530
  return stylesheets ?? [];
531
531
  }
532
+ const PAGE_RULES = [
533
+ "first",
534
+ "last",
535
+ "odd",
536
+ "even"
537
+ ];
538
+ const PAGE_OVERRIDE_FIELDS = ["header", "footer"];
539
+ function rejectNonObject(value, what) {
540
+ if (typeof value !== "object" || value === null || Array.isArray(value)) throw new TypeError(`${what} takes an object`);
541
+ }
542
+ function rejectUnknownKeys(object, known, what) {
543
+ const unknown = Object.keys(object).filter((key) => !known.includes(key));
544
+ if (unknown.length > 0) throw new TypeError(`unknown ${what}: ${unknown.join(", ")}`);
545
+ }
546
+ async function resolveRules(input) {
547
+ const resolved = {
548
+ pages: {},
549
+ nodes: [],
550
+ css: []
551
+ };
552
+ rejectNonObject(input, "pages");
553
+ rejectUnknownKeys(input, PAGE_RULES, "page rule");
554
+ for (const rule of PAGE_RULES) {
555
+ const override = input[rule];
556
+ if (override == null) continue;
557
+ rejectNonObject(override, `pages.${rule}`);
558
+ rejectUnknownKeys(override, PAGE_OVERRIDE_FIELDS, `field of pages.${rule}`);
559
+ const page = {};
560
+ for (const band of PAGE_OVERRIDE_FIELDS) {
561
+ const value = override[band];
562
+ if (value == null) continue;
563
+ if (value === false) {
564
+ page[band] = false;
565
+ continue;
566
+ }
567
+ const { node, css } = await resolveNode(value);
568
+ page[band] = node;
569
+ resolved.nodes.push(node);
570
+ resolved.css.push(...css);
571
+ }
572
+ resolved.pages[rule] = page;
573
+ }
574
+ return resolved;
575
+ }
532
576
  async function resolveNode(input) {
533
577
  if (isNode(input)) return {
534
578
  node: input,
@@ -543,13 +587,18 @@ var PdfRenderer = class {
543
587
  fonts = new _takumi_rs_helpers_renderer.FontRegistry((font) => this.inner.registerFont(font));
544
588
  /** Renders a node tree, JSX, or an HTML string to PDF bytes. See {@link RenderOptions}. */
545
589
  async render(node, options = {}) {
546
- const { fonts, images, header, footer, css, stylesheets, fontFamilies, ...rest } = options;
547
- const [main, headerResult, footerResult] = await Promise.all([
590
+ const { fonts, images, header, footer, pages, css, stylesheets, fontFamilies, ...rest } = options;
591
+ const [main, headerResult, footerResult, pagesResult] = await Promise.all([
548
592
  resolveNode(node),
549
- header === void 0 ? void 0 : resolveNode(header),
550
- footer === void 0 ? void 0 : resolveNode(footer)
593
+ header == null || header === false ? void 0 : resolveNode(header),
594
+ footer == null || footer === false ? void 0 : resolveNode(footer),
595
+ pages == null ? void 0 : resolveRules(pages)
551
596
  ]);
552
- const bands = [headerResult?.node, footerResult?.node].filter((band) => band !== void 0);
597
+ const bands = [
598
+ headerResult?.node,
599
+ footerResult?.node,
600
+ ...pagesResult?.nodes ?? []
601
+ ].filter((band) => band !== void 0);
553
602
  const resources = await this.fonts.resolveResources(fonts && (0, _takumi_rs_helpers.subsetFonts)({
554
603
  fonts,
555
604
  source: [
@@ -563,12 +612,14 @@ var PdfRenderer = class {
563
612
  ...ownCss(css, stylesheets),
564
613
  ...main.css,
565
614
  ...headerResult?.css ?? [],
566
- ...footerResult?.css ?? []
615
+ ...footerResult?.css ?? [],
616
+ ...pagesResult?.css ?? []
567
617
  ];
568
618
  return this.inner.render(main.node, {
569
619
  ...rest,
570
620
  header: headerResult?.node,
571
621
  footer: footerResult?.node,
622
+ pages: pagesResult?.pages,
572
623
  css: sheets.length > 0 ? sheets : void 0,
573
624
  images: resources.images,
574
625
  fontFamilies: resources.fontFamilies
package/dist/export.d.cts CHANGED
@@ -48,6 +48,20 @@ declare function __wbg_init(module_or_path?: {
48
48
  //#region src/export.d.ts
49
49
  /** A document input: a takumi node tree, JSX, or an HTML string. */
50
50
  type NodeInput = Node | ReactNode | ReactElementLike | string;
51
+ /** A band on a page: a document input, or `false` for none. */
52
+ type BandInput = NodeInput | false;
53
+ /** What some pages draw instead of the document's own bands. A field left out falls through. */
54
+ type PageOverrideInput = {
55
+ header?: BandInput;
56
+ footer?: BandInput;
57
+ };
58
+ /** Overrides keyed by the pages they cover. `first` and `last` win over `odd` and `even`. */
59
+ type PageRulesInput = {
60
+ first?: PageOverrideInput;
61
+ last?: PageOverrideInput;
62
+ odd?: PageOverrideInput;
63
+ even?: PageOverrideInput;
64
+ };
51
65
  /** Explicit dimensions in CSS px (96 dpi). */
52
66
  type Dimensions = {
53
67
  width: number;
@@ -83,6 +97,8 @@ type PageRange = number | {
83
97
  from?: number;
84
98
  to?: number;
85
99
  };
100
+ /** What a character no registered font covers turns into on the page. */
101
+ type UncoveredText = "error" | "placeholder" | "blank";
86
102
  /**
87
103
  * Paged output (the default): content flows across pages of `size`, like
88
104
  * Puppeteer's `page.pdf()`. The layout canvas has unbounded height, so
@@ -103,9 +119,11 @@ type PagedOptions = {
103
119
  * templates; add a CSS `@counter-style` name (e.g. `cjk-decimal`,
104
120
  * `lower-roman`) to the class list to format it.
105
121
  */
106
- header?: NodeInput;
122
+ header?: BandInput;
107
123
  /** Band repeated at the bottom of every page; same class hooks as `header`. */
108
- footer?: NodeInput;
124
+ footer?: BandInput;
125
+ /** What some pages draw differently from the rest. */
126
+ pages?: PageRulesInput;
109
127
  /**
110
128
  * The pages the output keeps, e.g. `[1, { from: 4, to: 8 }]`, like a print
111
129
  * dialog's page ranges. Layout and page counters still run over the whole
@@ -127,6 +145,7 @@ type ViewportOptions = {
127
145
  margin?: never;
128
146
  header?: never;
129
147
  footer?: never;
148
+ pages?: never;
130
149
  pageRanges?: never;
131
150
  };
132
151
  /**
@@ -306,6 +325,15 @@ type RenderOptions = (PagedOptions | ViewportOptions) & ConformanceOptions & {
306
325
  * Unset leaves the page empty, so a viewer shows its own white.
307
326
  */
308
327
  backgroundColor?: string;
328
+ /**
329
+ * What a character no registered font covers turns into: `"error"` (default)
330
+ * fails the render naming it, `"placeholder"` draws the font's glyph 0, and
331
+ * `"blank"` draws nothing. Neither of the last two reflows the line.
332
+ *
333
+ * `"placeholder"` is rejected with any `pdfa` level or `tagged: "ua1"` /
334
+ * `"ua2"`, which forbid glyph 0.
335
+ */
336
+ uncoveredText?: UncoveredText;
309
337
  };
310
338
  /** A PDF renderer holding registered fonts. Reuse one instance across renders. */
311
339
  declare class PdfRenderer {
@@ -335,4 +363,4 @@ declare function render(node: NodeInput, options?: RenderOptions): Promise<Uint8
335
363
  /** Measures with a lazily created shared {@link PdfRenderer}. */
336
364
  declare function measure(node: NodeInput, options?: MeasureOptions): Promise<MeasuredSize>;
337
365
  //#endregion
338
- export { ArchivalAttachment, Attachment, type CounterProps, type CounterStyle, Dimensions, type FontLoader, type ImagesInput, MeasureOptions, MeasuredSize, NodeInput, PageMargin, PageMarginSide, PageNumber, PageRange, PageSize, PageSizeName, PdfMetadata, PdfRenderer, RenderOptions, TargetPageNumber, TotalPages, ViewportInput, XmpProperty, XmpSchema, __wbg_init as default, initSync, measure, render };
366
+ export { ArchivalAttachment, Attachment, BandInput, type CounterProps, type CounterStyle, Dimensions, type FontLoader, type ImagesInput, MeasureOptions, MeasuredSize, NodeInput, PageMargin, PageMarginSide, PageNumber, PageOverrideInput, PageRange, PageRulesInput, PageSize, PageSizeName, PdfMetadata, PdfRenderer, RenderOptions, TargetPageNumber, TotalPages, UncoveredText, ViewportInput, XmpProperty, XmpSchema, __wbg_init as default, initSync, measure, render };
package/dist/export.d.mts CHANGED
@@ -48,6 +48,20 @@ declare function __wbg_init(module_or_path?: {
48
48
  //#region src/export.d.ts
49
49
  /** A document input: a takumi node tree, JSX, or an HTML string. */
50
50
  type NodeInput = Node | ReactNode | ReactElementLike | string;
51
+ /** A band on a page: a document input, or `false` for none. */
52
+ type BandInput = NodeInput | false;
53
+ /** What some pages draw instead of the document's own bands. A field left out falls through. */
54
+ type PageOverrideInput = {
55
+ header?: BandInput;
56
+ footer?: BandInput;
57
+ };
58
+ /** Overrides keyed by the pages they cover. `first` and `last` win over `odd` and `even`. */
59
+ type PageRulesInput = {
60
+ first?: PageOverrideInput;
61
+ last?: PageOverrideInput;
62
+ odd?: PageOverrideInput;
63
+ even?: PageOverrideInput;
64
+ };
51
65
  /** Explicit dimensions in CSS px (96 dpi). */
52
66
  type Dimensions = {
53
67
  width: number;
@@ -83,6 +97,8 @@ type PageRange = number | {
83
97
  from?: number;
84
98
  to?: number;
85
99
  };
100
+ /** What a character no registered font covers turns into on the page. */
101
+ type UncoveredText = "error" | "placeholder" | "blank";
86
102
  /**
87
103
  * Paged output (the default): content flows across pages of `size`, like
88
104
  * Puppeteer's `page.pdf()`. The layout canvas has unbounded height, so
@@ -103,9 +119,11 @@ type PagedOptions = {
103
119
  * templates; add a CSS `@counter-style` name (e.g. `cjk-decimal`,
104
120
  * `lower-roman`) to the class list to format it.
105
121
  */
106
- header?: NodeInput;
122
+ header?: BandInput;
107
123
  /** Band repeated at the bottom of every page; same class hooks as `header`. */
108
- footer?: NodeInput;
124
+ footer?: BandInput;
125
+ /** What some pages draw differently from the rest. */
126
+ pages?: PageRulesInput;
109
127
  /**
110
128
  * The pages the output keeps, e.g. `[1, { from: 4, to: 8 }]`, like a print
111
129
  * dialog's page ranges. Layout and page counters still run over the whole
@@ -127,6 +145,7 @@ type ViewportOptions = {
127
145
  margin?: never;
128
146
  header?: never;
129
147
  footer?: never;
148
+ pages?: never;
130
149
  pageRanges?: never;
131
150
  };
132
151
  /**
@@ -306,6 +325,15 @@ type RenderOptions = (PagedOptions | ViewportOptions) & ConformanceOptions & {
306
325
  * Unset leaves the page empty, so a viewer shows its own white.
307
326
  */
308
327
  backgroundColor?: string;
328
+ /**
329
+ * What a character no registered font covers turns into: `"error"` (default)
330
+ * fails the render naming it, `"placeholder"` draws the font's glyph 0, and
331
+ * `"blank"` draws nothing. Neither of the last two reflows the line.
332
+ *
333
+ * `"placeholder"` is rejected with any `pdfa` level or `tagged: "ua1"` /
334
+ * `"ua2"`, which forbid glyph 0.
335
+ */
336
+ uncoveredText?: UncoveredText;
309
337
  };
310
338
  /** A PDF renderer holding registered fonts. Reuse one instance across renders. */
311
339
  declare class PdfRenderer {
@@ -335,4 +363,4 @@ declare function render(node: NodeInput, options?: RenderOptions): Promise<Uint8
335
363
  /** Measures with a lazily created shared {@link PdfRenderer}. */
336
364
  declare function measure(node: NodeInput, options?: MeasureOptions): Promise<MeasuredSize>;
337
365
  //#endregion
338
- export { ArchivalAttachment, Attachment, type CounterProps, type CounterStyle, Dimensions, type FontLoader, type ImagesInput, MeasureOptions, MeasuredSize, NodeInput, PageMargin, PageMarginSide, PageNumber, PageRange, PageSize, PageSizeName, PdfMetadata, PdfRenderer, RenderOptions, TargetPageNumber, TotalPages, ViewportInput, XmpProperty, XmpSchema, __wbg_init as default, initSync, measure, render };
366
+ export { ArchivalAttachment, Attachment, BandInput, type CounterProps, type CounterStyle, Dimensions, type FontLoader, type ImagesInput, MeasureOptions, MeasuredSize, NodeInput, PageMargin, PageMarginSide, PageNumber, PageOverrideInput, PageRange, PageRulesInput, PageSize, PageSizeName, PdfMetadata, PdfRenderer, RenderOptions, TargetPageNumber, TotalPages, UncoveredText, ViewportInput, XmpProperty, XmpSchema, __wbg_init as default, initSync, measure, render };
package/dist/export.mjs CHANGED
@@ -99,10 +99,10 @@ function __wbg_get_imports() {
99
99
  __proto__: null,
100
100
  "./takumi_pdf_wasm_bg.js": {
101
101
  __proto__: null,
102
- __wbg_Error_408e67f47ca7b58b: function(arg0, arg1) {
102
+ __wbg_Error_67e7344beaa85059: function(arg0, arg1) {
103
103
  return Error(getStringFromWasm0(arg0, arg1));
104
104
  },
105
- __wbg_Number_3890faa6d3ff057d: function(arg0) {
105
+ __wbg_Number_c54e7112a3fa7e3e: function(arg0) {
106
106
  return Number(arg0);
107
107
  },
108
108
  __wbg_String_8564e559799eccda: function(arg0, arg1) {
@@ -111,55 +111,55 @@ function __wbg_get_imports() {
111
111
  getDataViewMemory0().setInt32(arg0 + 4, len1, true);
112
112
  getDataViewMemory0().setInt32(arg0 + 0, ptr1, true);
113
113
  },
114
- __wbg___wbindgen_bigint_get_as_i64_c4ecf48528083721: function(arg0, arg1) {
114
+ __wbg___wbindgen_bigint_get_as_i64_b482365c149396c8: function(arg0, arg1) {
115
115
  const v = arg1;
116
116
  const ret = typeof v === "bigint" ? v : void 0;
117
117
  getDataViewMemory0().setBigInt64(arg0 + 8, isLikeNone(ret) ? BigInt(0) : ret, true);
118
118
  getDataViewMemory0().setInt32(arg0 + 0, !isLikeNone(ret), true);
119
119
  },
120
- __wbg___wbindgen_boolean_get_c9c83ebd41b34df3: function(arg0) {
120
+ __wbg___wbindgen_boolean_get_7a12af2b3f899c5a: function(arg0) {
121
121
  const v = arg0;
122
122
  const ret = typeof v === "boolean" ? v : void 0;
123
123
  return isLikeNone(ret) ? 16777215 : ret ? 1 : 0;
124
124
  },
125
- __wbg___wbindgen_debug_string_a57024b9c6e4a48b: function(arg0, arg1) {
125
+ __wbg___wbindgen_debug_string_0e68cf47c9cbd9b0: function(arg0, arg1) {
126
126
  const ptr1 = passStringToWasm0(debugString(arg1), wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
127
127
  const len1 = WASM_VECTOR_LEN;
128
128
  getDataViewMemory0().setInt32(arg0 + 4, len1, true);
129
129
  getDataViewMemory0().setInt32(arg0 + 0, ptr1, true);
130
130
  },
131
- __wbg___wbindgen_in_ac983077f137f2e6: function(arg0, arg1) {
131
+ __wbg___wbindgen_in_50072d4d6e45c193: function(arg0, arg1) {
132
132
  return arg0 in arg1;
133
133
  },
134
- __wbg___wbindgen_is_bigint_8ffbbef442139384: function(arg0) {
134
+ __wbg___wbindgen_is_bigint_60fc0336cb14f5d7: function(arg0) {
135
135
  return typeof arg0 === "bigint";
136
136
  },
137
- __wbg___wbindgen_is_function_5e4570eb24ffa122: function(arg0) {
137
+ __wbg___wbindgen_is_function_fcda5e3902d732fe: function(arg0) {
138
138
  return typeof arg0 === "function";
139
139
  },
140
- __wbg___wbindgen_is_object_a2790eb24c211ea0: function(arg0) {
140
+ __wbg___wbindgen_is_object_edb6b15aa3afe12e: function(arg0) {
141
141
  const val = arg0;
142
142
  return typeof val === "object" && val !== null;
143
143
  },
144
- __wbg___wbindgen_is_string_e6f02f0ea5f20a32: function(arg0) {
144
+ __wbg___wbindgen_is_string_c4f7cb494a2a21f1: function(arg0) {
145
145
  return typeof arg0 === "string";
146
146
  },
147
- __wbg___wbindgen_is_undefined_6cff064c44e0d823: function(arg0) {
147
+ __wbg___wbindgen_is_undefined_8c687d0b90d5b524: function(arg0) {
148
148
  return arg0 === void 0;
149
149
  },
150
- __wbg___wbindgen_jsval_eq_0a18949a61670320: function(arg0, arg1) {
150
+ __wbg___wbindgen_jsval_eq_9fdcd3c0a860dd3b: function(arg0, arg1) {
151
151
  return arg0 === arg1;
152
152
  },
153
- __wbg___wbindgen_jsval_loose_eq_acf2776254a8d832: function(arg0, arg1) {
153
+ __wbg___wbindgen_jsval_loose_eq_3c30021c243b64cd: function(arg0, arg1) {
154
154
  return arg0 == arg1;
155
155
  },
156
- __wbg___wbindgen_number_get_136b9679cab35cfb: function(arg0, arg1) {
156
+ __wbg___wbindgen_number_get_1dc732b810cb937c: function(arg0, arg1) {
157
157
  const obj = arg1;
158
158
  const ret = typeof obj === "number" ? obj : void 0;
159
159
  getDataViewMemory0().setFloat64(arg0 + 8, isLikeNone(ret) ? 0 : ret, true);
160
160
  getDataViewMemory0().setInt32(arg0 + 0, !isLikeNone(ret), true);
161
161
  },
162
- __wbg___wbindgen_string_get_d154f1e671052120: function(arg0, arg1) {
162
+ __wbg___wbindgen_string_get_92ab86bb19cbc12f: function(arg0, arg1) {
163
163
  const obj = arg1;
164
164
  const ret = typeof obj === "string" ? obj : void 0;
165
165
  var ptr1 = isLikeNone(ret) ? 0 : passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
@@ -167,35 +167,35 @@ function __wbg_get_imports() {
167
167
  getDataViewMemory0().setInt32(arg0 + 4, len1, true);
168
168
  getDataViewMemory0().setInt32(arg0 + 0, ptr1, true);
169
169
  },
170
- __wbg___wbindgen_throw_bb96b2010945f0bc: function(arg0, arg1) {
170
+ __wbg___wbindgen_throw_5d9e815e6fdf150f: function(arg0, arg1) {
171
171
  throw new Error(getStringFromWasm0(arg0, arg1));
172
172
  },
173
- __wbg_call_1c5886ab9c57d1c7: function() {
173
+ __wbg_call_269c5566fbede3eb: function() {
174
174
  return handleError(function(arg0, arg1) {
175
175
  return arg0.call(arg1);
176
176
  }, arguments);
177
177
  },
178
- __wbg_done_669171204c3dcae2: function(arg0) {
178
+ __wbg_done_cffed884d87aa22e: function(arg0) {
179
179
  return arg0.done;
180
180
  },
181
- __wbg_entries_7774d489e1da5f4f: function(arg0) {
181
+ __wbg_entries_972a87586902cf87: function(arg0) {
182
182
  return Object.entries(arg0);
183
183
  },
184
- __wbg_get_c0c8f8d7da0c03dd: function(arg0, arg1) {
185
- return arg0[arg1 >>> 0];
186
- },
187
- __wbg_get_d173c0308df22d37: function() {
184
+ __wbg_get_6cf5a4d4d8ad3c5a: function() {
188
185
  return handleError(function(arg0, arg1) {
189
186
  return Reflect.get(arg0, arg1);
190
187
  }, arguments);
191
188
  },
192
- __wbg_get_unchecked_e20b893aeafc3fca: function(arg0, arg1) {
189
+ __wbg_get_b1f0ab13c737f856: function(arg0, arg1) {
190
+ return arg0[arg1 >>> 0];
191
+ },
192
+ __wbg_get_unchecked_363572bdd397d473: function(arg0, arg1) {
193
193
  return arg0[arg1 >>> 0];
194
194
  },
195
195
  __wbg_get_with_ref_key_6412cf3094599694: function(arg0, arg1) {
196
196
  return arg0[arg1];
197
197
  },
198
- __wbg_instanceof_ArrayBuffer_993d02d2d254cad1: function(arg0) {
198
+ __wbg_instanceof_ArrayBuffer_d4ff01f8247925ae: function(arg0) {
199
199
  let result;
200
200
  try {
201
201
  result = arg0 instanceof ArrayBuffer;
@@ -204,7 +204,7 @@ function __wbg_get_imports() {
204
204
  }
205
205
  return result;
206
206
  },
207
- __wbg_instanceof_Map_9a4d6ead180ae3a9: function(arg0) {
207
+ __wbg_instanceof_Map_1ff6a2b54c899f0d: function(arg0) {
208
208
  let result;
209
209
  try {
210
210
  result = arg0 instanceof Map;
@@ -213,7 +213,7 @@ function __wbg_get_imports() {
213
213
  }
214
214
  return result;
215
215
  },
216
- __wbg_instanceof_Uint8Array_f935dbb0aa7cdeed: function(arg0) {
216
+ __wbg_instanceof_Uint8Array_598adc0fef426aa8: function(arg0) {
217
217
  let result;
218
218
  try {
219
219
  result = arg0 instanceof Uint8Array;
@@ -222,63 +222,63 @@ function __wbg_get_imports() {
222
222
  }
223
223
  return result;
224
224
  },
225
- __wbg_isArray_6339f732981044bf: function(arg0) {
225
+ __wbg_isArray_5674713bb7b79043: function(arg0) {
226
226
  return Array.isArray(arg0);
227
227
  },
228
- __wbg_isSafeInteger_f3d6cd19ccfe4512: function(arg0) {
228
+ __wbg_isSafeInteger_8f51c743827d1ec5: function(arg0) {
229
229
  return Number.isSafeInteger(arg0);
230
230
  },
231
- __wbg_iterator_5cebbb86e33c6dd6: function() {
231
+ __wbg_iterator_22ddeb808cf55a6f: function() {
232
232
  return Symbol.iterator;
233
233
  },
234
- __wbg_length_36bd29c6848c2144: function(arg0) {
234
+ __wbg_length_31bdaf014f5fbde2: function(arg0) {
235
235
  return arg0.length;
236
236
  },
237
- __wbg_length_ecfa2c63d3d0d82c: function(arg0) {
237
+ __wbg_length_4e1adc0d42e23620: function(arg0) {
238
238
  return arg0.length;
239
239
  },
240
- __wbg_new_116be93542d39019: function() {
241
- return new Array();
240
+ __wbg_new_1da3429bc3c4541c: function(arg0) {
241
+ return new Uint8Array(arg0);
242
242
  },
243
- __wbg_new_358857d90afd5a2d: function(arg0, arg1) {
243
+ __wbg_new_a32a1ab6c6655abe: function(arg0, arg1) {
244
244
  return new Error(getStringFromWasm0(arg0, arg1));
245
245
  },
246
- __wbg_new_77cc4f4f472aeb81: function(arg0) {
247
- return new Uint8Array(arg0);
248
- },
249
- __wbg_new_ebe3e0f6837f0879: function() {
246
+ __wbg_new_bebc3f4757acf305: function() {
250
247
  return /* @__PURE__ */ new Object();
251
248
  },
252
- __wbg_next_42cf16ee0dafc9e2: function() {
249
+ __wbg_new_ffa92086ea89f79c: function() {
250
+ return new Array();
251
+ },
252
+ __wbg_next_95053e306b1c3aed: function(arg0) {
253
+ return arg0.next;
254
+ },
255
+ __wbg_next_f31ecb8646d2c605: function() {
253
256
  return handleError(function(arg0) {
254
257
  return arg0.next();
255
258
  }, arguments);
256
259
  },
257
- __wbg_next_8f26b64fa5e9f64b: function(arg0) {
258
- return arg0.next;
259
- },
260
- __wbg_prototypesetcall_de8e0d9553586985: function(arg0, arg1, arg2) {
260
+ __wbg_prototypesetcall_ae9f5e7459250748: function(arg0, arg1, arg2) {
261
261
  Uint8Array.prototype.set.call(getArrayU8FromWasm0(arg0, arg1), arg2);
262
262
  },
263
+ __wbg_set_13d25b81ab403f5e: function(arg0, arg1, arg2) {
264
+ arg0[arg1 >>> 0] = arg2;
265
+ },
263
266
  __wbg_set_6be42768c690e380: function(arg0, arg1, arg2) {
264
267
  arg0[arg1] = arg2;
265
268
  },
266
- __wbg_set_a80955eb93b145c6: function(arg0, arg1, arg2) {
267
- arg0[arg1 >>> 0] = arg2;
268
- },
269
- __wbg_value_1e2369fab29b420e: function(arg0) {
269
+ __wbg_value_c227f843d21da141: function(arg0) {
270
270
  return arg0.value;
271
271
  },
272
- __wbindgen_cast_0000000000000001: function(arg0) {
272
+ __wbindgen_generic_0000000000000001: function(arg0) {
273
273
  return arg0;
274
274
  },
275
- __wbindgen_cast_0000000000000002: function(arg0) {
275
+ __wbindgen_generic_0000000000000002: function(arg0) {
276
276
  return arg0;
277
277
  },
278
- __wbindgen_cast_0000000000000003: function(arg0, arg1) {
278
+ __wbindgen_generic_0000000000000003: function(arg0, arg1) {
279
279
  return getStringFromWasm0(arg0, arg1);
280
280
  },
281
- __wbindgen_cast_0000000000000004: function(arg0) {
281
+ __wbindgen_generic_0000000000000004: function(arg0) {
282
282
  return BigInt.asUintN(64, arg0);
283
283
  },
284
284
  __wbindgen_init_externref_table: function() {
@@ -525,6 +525,50 @@ function ownCss(css, stylesheets) {
525
525
  if (stylesheets !== void 0) warnStylesheetsDeprecated();
526
526
  return stylesheets ?? [];
527
527
  }
528
+ const PAGE_RULES = [
529
+ "first",
530
+ "last",
531
+ "odd",
532
+ "even"
533
+ ];
534
+ const PAGE_OVERRIDE_FIELDS = ["header", "footer"];
535
+ function rejectNonObject(value, what) {
536
+ if (typeof value !== "object" || value === null || Array.isArray(value)) throw new TypeError(`${what} takes an object`);
537
+ }
538
+ function rejectUnknownKeys(object, known, what) {
539
+ const unknown = Object.keys(object).filter((key) => !known.includes(key));
540
+ if (unknown.length > 0) throw new TypeError(`unknown ${what}: ${unknown.join(", ")}`);
541
+ }
542
+ async function resolveRules(input) {
543
+ const resolved = {
544
+ pages: {},
545
+ nodes: [],
546
+ css: []
547
+ };
548
+ rejectNonObject(input, "pages");
549
+ rejectUnknownKeys(input, PAGE_RULES, "page rule");
550
+ for (const rule of PAGE_RULES) {
551
+ const override = input[rule];
552
+ if (override == null) continue;
553
+ rejectNonObject(override, `pages.${rule}`);
554
+ rejectUnknownKeys(override, PAGE_OVERRIDE_FIELDS, `field of pages.${rule}`);
555
+ const page = {};
556
+ for (const band of PAGE_OVERRIDE_FIELDS) {
557
+ const value = override[band];
558
+ if (value == null) continue;
559
+ if (value === false) {
560
+ page[band] = false;
561
+ continue;
562
+ }
563
+ const { node, css } = await resolveNode(value);
564
+ page[band] = node;
565
+ resolved.nodes.push(node);
566
+ resolved.css.push(...css);
567
+ }
568
+ resolved.pages[rule] = page;
569
+ }
570
+ return resolved;
571
+ }
528
572
  async function resolveNode(input) {
529
573
  if (isNode(input)) return {
530
574
  node: input,
@@ -539,13 +583,18 @@ var PdfRenderer = class {
539
583
  fonts = new FontRegistry((font) => this.inner.registerFont(font));
540
584
  /** Renders a node tree, JSX, or an HTML string to PDF bytes. See {@link RenderOptions}. */
541
585
  async render(node, options = {}) {
542
- const { fonts, images, header, footer, css, stylesheets, fontFamilies, ...rest } = options;
543
- const [main, headerResult, footerResult] = await Promise.all([
586
+ const { fonts, images, header, footer, pages, css, stylesheets, fontFamilies, ...rest } = options;
587
+ const [main, headerResult, footerResult, pagesResult] = await Promise.all([
544
588
  resolveNode(node),
545
- header === void 0 ? void 0 : resolveNode(header),
546
- footer === void 0 ? void 0 : resolveNode(footer)
589
+ header == null || header === false ? void 0 : resolveNode(header),
590
+ footer == null || footer === false ? void 0 : resolveNode(footer),
591
+ pages == null ? void 0 : resolveRules(pages)
547
592
  ]);
548
- const bands = [headerResult?.node, footerResult?.node].filter((band) => band !== void 0);
593
+ const bands = [
594
+ headerResult?.node,
595
+ footerResult?.node,
596
+ ...pagesResult?.nodes ?? []
597
+ ].filter((band) => band !== void 0);
549
598
  const resources = await this.fonts.resolveResources(fonts && subsetFonts({
550
599
  fonts,
551
600
  source: [
@@ -559,12 +608,14 @@ var PdfRenderer = class {
559
608
  ...ownCss(css, stylesheets),
560
609
  ...main.css,
561
610
  ...headerResult?.css ?? [],
562
- ...footerResult?.css ?? []
611
+ ...footerResult?.css ?? [],
612
+ ...pagesResult?.css ?? []
563
613
  ];
564
614
  return this.inner.render(main.node, {
565
615
  ...rest,
566
616
  header: headerResult?.node,
567
617
  footer: footerResult?.node,
618
+ pages: pagesResult?.pages,
568
619
  css: sheets.length > 0 ? sheets : void 0,
569
620
  images: resources.images,
570
621
  fontFamilies: resources.fontFamilies
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "takumi-pdf",
3
- "version": "0.14.3",
3
+ "version": "0.15.0",
4
4
  "description": "Render paged PDFs from JSX, HTML, and CSS. No headless browser.",
5
5
  "keywords": [
6
6
  "chromium-free",
@@ -143,12 +143,12 @@
143
143
  "publish-lint": "attw --pack . && publint --strict ."
144
144
  },
145
145
  "dependencies": {
146
- "@takumi-rs/helpers": "2.13.7"
146
+ "@takumi-rs/helpers": "2.14.0"
147
147
  },
148
148
  "devDependencies": {
149
- "@types/bun": "^1.4.0",
149
+ "@types/bun": "^1.4.1",
150
150
  "@types/react": "19.2.18",
151
- "tsdown": "0.22.14"
151
+ "tsdown": "0.23.0"
152
152
  },
153
153
  "peerDependencies": {
154
154
  "react": "^18.0.0 || ^19.0.0"
Binary file