blitz-pdf 0.1.0 → 0.1.2

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 (114) hide show
  1. package/README.md +300 -10
  2. package/THIRD_PARTY_NOTICES.md +4 -2
  3. package/dist/browser.d.ts +1 -1
  4. package/dist/browser.d.ts.map +1 -1
  5. package/dist/document-options.d.ts +36 -0
  6. package/dist/document-options.d.ts.map +1 -0
  7. package/dist/document-options.js +236 -0
  8. package/dist/document-options.js.map +1 -0
  9. package/dist/node.d.ts +1 -1
  10. package/dist/node.d.ts.map +1 -1
  11. package/dist/options.d.ts +35 -1
  12. package/dist/options.d.ts.map +1 -1
  13. package/dist/options.js +21 -14
  14. package/dist/options.js.map +1 -1
  15. package/dist/types.d.ts +71 -1
  16. package/dist/types.d.ts.map +1 -1
  17. package/dist/validation.d.ts +2 -0
  18. package/dist/validation.d.ts.map +1 -0
  19. package/dist/validation.js +7 -0
  20. package/dist/validation.js.map +1 -0
  21. package/examples/zugferd.mjs +41 -0
  22. package/licenses/RUST_DEPENDENCIES.txt +27 -0
  23. package/package.json +3 -2
  24. package/src/browser.ts +1 -1
  25. package/src/document-options.ts +218 -0
  26. package/src/node.ts +1 -1
  27. package/src/options.ts +18 -13
  28. package/src/types.ts +76 -1
  29. package/src/validation.ts +6 -0
  30. package/vendor/README.md +27 -2
  31. package/vendor/krilla/.cargo-ok +1 -0
  32. package/vendor/krilla/.cargo_vcs_info.json +6 -0
  33. package/vendor/krilla/Cargo.lock +1357 -0
  34. package/vendor/krilla/Cargo.toml +165 -0
  35. package/vendor/krilla/LICENSE_APACHE +202 -0
  36. package/vendor/krilla/LICENSE_MIT +21 -0
  37. package/vendor/krilla/README.md +156 -0
  38. package/vendor/krilla/examples/basic.rs +117 -0
  39. package/vendor/krilla/examples/empty_document.rs +25 -0
  40. package/vendor/krilla/examples/parley.rs +174 -0
  41. package/vendor/krilla/examples/simple_text.rs +118 -0
  42. package/vendor/krilla/examples/stream_builder.rs +93 -0
  43. package/vendor/krilla/icc/LICENSE +116 -0
  44. package/vendor/krilla/icc/README.md +1 -0
  45. package/vendor/krilla/icc/sGrey-v2-magic.icc +0 -0
  46. package/vendor/krilla/icc/sGrey-v4.icc +0 -0
  47. package/vendor/krilla/icc/sRGB-v2-magic.icc +0 -0
  48. package/vendor/krilla/icc/sRGB-v4.icc +0 -0
  49. package/vendor/krilla/src/chunk_container.rs +503 -0
  50. package/vendor/krilla/src/configure/PDF_A1.md +227 -0
  51. package/vendor/krilla/src/configure/PDF_A2.md +269 -0
  52. package/vendor/krilla/src/configure/PDF_A3.md +31 -0
  53. package/vendor/krilla/src/configure/PDF_A4.md +211 -0
  54. package/vendor/krilla/src/configure/PDF_UA1.md +202 -0
  55. package/vendor/krilla/src/configure/README.md +23 -0
  56. package/vendor/krilla/src/configure/mod.rs +197 -0
  57. package/vendor/krilla/src/configure/validate.rs +1244 -0
  58. package/vendor/krilla/src/configure/version.rs +134 -0
  59. package/vendor/krilla/src/content.rs +1352 -0
  60. package/vendor/krilla/src/data.rs +49 -0
  61. package/vendor/krilla/src/document.rs +156 -0
  62. package/vendor/krilla/src/error.rs +170 -0
  63. package/vendor/krilla/src/geom.rs +341 -0
  64. package/vendor/krilla/src/graphics/blend.rs +46 -0
  65. package/vendor/krilla/src/graphics/color.rs +552 -0
  66. package/vendor/krilla/src/graphics/graphic.rs +30 -0
  67. package/vendor/krilla/src/graphics/graphics_state.rs +251 -0
  68. package/vendor/krilla/src/graphics/icc.rs +218 -0
  69. package/vendor/krilla/src/graphics/image.rs +835 -0
  70. package/vendor/krilla/src/graphics/mask.rs +128 -0
  71. package/vendor/krilla/src/graphics/mod.rs +14 -0
  72. package/vendor/krilla/src/graphics/paint.rs +434 -0
  73. package/vendor/krilla/src/graphics/separation.rs +86 -0
  74. package/vendor/krilla/src/graphics/shading_function.rs +865 -0
  75. package/vendor/krilla/src/graphics/shading_pattern.rs +60 -0
  76. package/vendor/krilla/src/graphics/tiling_pattern.rs +119 -0
  77. package/vendor/krilla/src/graphics/xobject.rs +150 -0
  78. package/vendor/krilla/src/interactive/action.rs +68 -0
  79. package/vendor/krilla/src/interactive/annotation.rs +283 -0
  80. package/vendor/krilla/src/interactive/destination.rs +289 -0
  81. package/vendor/krilla/src/interactive/mod.rs +3 -0
  82. package/vendor/krilla/src/interchange/embed.rs +240 -0
  83. package/vendor/krilla/src/interchange/metadata.rs +564 -0
  84. package/vendor/krilla/src/interchange/mod.rs +4 -0
  85. package/vendor/krilla/src/interchange/outline.rs +263 -0
  86. package/vendor/krilla/src/interchange/tagging/fmt.rs +733 -0
  87. package/vendor/krilla/src/interchange/tagging/generate.toml +474 -0
  88. package/vendor/krilla/src/interchange/tagging/generated.rs +4754 -0
  89. package/vendor/krilla/src/interchange/tagging/mod.rs +1324 -0
  90. package/vendor/krilla/src/interchange/tagging/tag.rs +686 -0
  91. package/vendor/krilla/src/lib.rs +179 -0
  92. package/vendor/krilla/src/num.rs +26 -0
  93. package/vendor/krilla/src/page.rs +612 -0
  94. package/vendor/krilla/src/pdf.rs +313 -0
  95. package/vendor/krilla/src/resource.rs +436 -0
  96. package/vendor/krilla/src/serialize.rs +1101 -0
  97. package/vendor/krilla/src/stream.rs +368 -0
  98. package/vendor/krilla/src/surface.rs +664 -0
  99. package/vendor/krilla/src/text/cid.rs +488 -0
  100. package/vendor/krilla/src/text/font.rs +351 -0
  101. package/vendor/krilla/src/text/glyph/bitmap.rs +81 -0
  102. package/vendor/krilla/src/text/glyph/colr.rs +581 -0
  103. package/vendor/krilla/src/text/glyph/mod.rs +205 -0
  104. package/vendor/krilla/src/text/glyph/outline.rs +72 -0
  105. package/vendor/krilla/src/text/glyph/svg.rs +34 -0
  106. package/vendor/krilla/src/text/group.rs +463 -0
  107. package/vendor/krilla/src/text/mod.rs +232 -0
  108. package/vendor/krilla/src/text/shape.rs +102 -0
  109. package/vendor/krilla/src/text/type3.rs +514 -0
  110. package/vendor/krilla/src/util.rs +286 -0
  111. package/vendor/stylo/properties/longhands.toml +0 -1
  112. package/vendor/stylo/stylesheets/rule_parser.rs +1 -1
  113. package/wasm/blitz_pdf.js +10 -5
  114. package/wasm/blitz_pdf_bg.wasm +0 -0
package/README.md CHANGED
@@ -80,13 +80,17 @@ cross-origin isolation are not required.
80
80
 
81
81
  | Option | Default | Meaning |
82
82
  | --- | --- | --- |
83
- | `format` | `"A4"` | `A3`, `A4`, `A5`, `Letter`, `Legal`, or `Tabloid` |
83
+ | `format` | `"A4"` | `A0`–`A6`, `Letter`, `Legal`, `Tabloid`, or `Ledger` |
84
84
  | `width`, `height` | From format | Supply both for custom dimensions; omit `format` |
85
85
  | `landscape` | `false` | Swap width and height |
86
+ | `preferCSSPageSize` | `false` | Let CSS `@page size` override an explicit format or dimensions |
86
87
  | `margin` | `0` | One length or `{ top, right, bottom, left }`; omitted sides are zero |
87
88
  | `media` | `"print"` | CSS media queries: `print` or `screen` |
88
- | `title` | Unset | PDF metadata title |
89
+ | `title` | Unset | PDF metadata title; `metadata.title` takes precedence |
90
+ | `metadata` | `{}` | Author, subject, keywords, dates, language, document ID, and custom XMP schemas |
91
+ | `attachments` | `[]` | Direct embedded files with MIME types and PDF/A relationships |
89
92
  | `pageNumbers` | `false` | `true` or `{ template, align, fontSize }` for an automatic text footer |
93
+ | `headerTemplate` / `footerTemplate` | Unset | HTML repeated in the top/bottom page margin, with `.pageNumber` and `.totalPages` placeholders |
90
94
  | `baseUrl` | `https://blitz-pdf.invalid/` | Resolve relative asset URLs |
91
95
  | `assets` | `{}` | Resource map or `(url) => data / Promise<data>`; data is a UTF-8 string, `Uint8Array`, or `ArrayBuffer` |
92
96
  | `fonts` | `[]` | Additional static TTF, OTF, WOFF, or WOFF2 font bytes |
@@ -97,6 +101,203 @@ Lengths accept numbers in CSS pixels (96 px = 1 inch), or strings with `px`,
97
101
  are limited to 19200 CSS pixels; margins must leave printable content.
98
102
  `getEngineVersion()` returns the package version and pinned Blitz revision.
99
103
 
104
+ ## CSS page settings
105
+
106
+ Unqualified `@page` rules support `size`, `margin`, and the four individual
107
+ margin properties. For example, this label uses a 105 mm by 148 mm page:
108
+
109
+ ```js
110
+ const pdf = await renderPdf(`
111
+ <style>
112
+ @page { size: 105mm 148mm; margin: 5mm; margin-bottom: 8mm; }
113
+ html, body { margin: 0; }
114
+ </style>
115
+ <h1>Shipping label</h1>
116
+ `);
117
+ ```
118
+
119
+ `size: A4 landscape`, `size: landscape`, square sizes such as `size: 100mm`,
120
+ and absolute length calculations are supported. Rules follow stylesheet and
121
+ `@import` order, honor `!important`, and use the selected `print`/`screen` media.
122
+ CSS page margins override the corresponding option margins; unspecified sides
123
+ keep their option values. Percentages resolve against the corresponding page
124
+ dimension. Margins must be non-negative and leave printable content.
125
+
126
+ CSS size is used automatically when neither `format` nor `width`/`height` is
127
+ provided. Explicit dimensions take precedence by default. Set
128
+ `preferCSSPageSize: true` to let CSS size override them. `size: auto` keeps the
129
+ option size, or A4 when no size was supplied. Content is laid out again at the
130
+ chosen dimensions; it is not scaled from an A4 layout.
131
+
132
+ Named formats are `A0`, `A1`, `A2`, `A3`, `A4`, `A5`, `A6`, `Letter`, `Legal`,
133
+ `Tabloid` (11 by 17 inches), and `Ledger` (17 by 11 inches). `landscape: true`
134
+ swaps the option dimensions. An explicit CSS orientation takes precedence when
135
+ CSS controls the size.
136
+
137
+ The page geometry is uniform throughout a document. Named pages, `:first`,
138
+ `:left`/`:right` page selectors, CSS page margin boxes, cascade layers, and
139
+ custom-property substitution inside `@page` are not implemented.
140
+
141
+ ## Document metadata
142
+
143
+ ```js
144
+ const pdf = await renderPdf(html, {
145
+ metadata: {
146
+ title: "Project report",
147
+ author: ["Ada Example", "Example GmbH"], // A single string also works.
148
+ subject: "Project delivery and supporting documents",
149
+ keywords: ["project", "delivery"],
150
+ creator: "My application",
151
+ language: "de-DE",
152
+ creationDate: "2026-10-13T10:00:00+02:00",
153
+ modificationDate: new Date("2026-10-14T08:00:00Z"),
154
+ documentId: "project-42-report", // Stable identity across revisions.
155
+ },
156
+ });
157
+ ```
158
+
159
+ Standard metadata is synchronized between the PDF information dictionary and
160
+ XMP. `language` also sets the document catalog's language. `producer` defaults
161
+ to `blitz-pdf` and its version and can be overridden. Creation time defaults to
162
+ the start of the render; modification time defaults to creation time and must
163
+ not precede it. Dates accept a valid `Date` or a timestamp with an explicit
164
+ timezone, with at most three fractional-second digits. They are stored in UTC
165
+ at whole-second precision. `documentId` is a stable input from which the engine
166
+ derives the PDF document ID; it is not copied literally into the ID field.
167
+
168
+ The original top-level `title` option remains supported. `metadata.title`
169
+ overrides it, including an empty string to omit the title. Metadata strings must
170
+ contain valid XML characters and fit in 16 KiB of UTF-8 per field. Author and
171
+ keyword lists allow up to 256 entries and 16 KiB when joined. CR/CRLF line endings
172
+ are normalized to LF to keep Info and XMP consistent.
173
+
174
+ ## Embedded files
175
+
176
+ ```js
177
+ const pdf = await renderPdf(html, {
178
+ attachments: [
179
+ {
180
+ name: "data.xml",
181
+ data: xmlString, // UTF-8 text, Uint8Array/Buffer, or ArrayBuffer.
182
+ mimeType: "text/xml",
183
+ description: "Structured document data",
184
+ relationship: "Alternative",
185
+ },
186
+ {
187
+ name: "supporting-data.csv",
188
+ data: "item,quantity\nExample,2\n",
189
+ mimeType: "text/csv",
190
+ relationship: "Supplement",
191
+ },
192
+ ],
193
+ });
194
+ ```
195
+
196
+ Files are embedded directly, without going through the HTML asset resolver.
197
+ `data` is the file's content, never a filename or URL to load. Use `readFile`,
198
+ `fetch(...).arrayBuffer()`, or `File.arrayBuffer()` before calling `renderPdf`.
199
+ The library snapshots supplied bytes and metadata before asynchronous work.
200
+
201
+ Each attachment has a Unicode filename, a MIME type, an uncompressed size and
202
+ modification date, and a document-level `/AFRelationship`. It appears both in
203
+ the catalog's `/AF` array and in the `/EmbeddedFiles` name tree used by PDF
204
+ viewers. `relationship` accepts `Source`, `Data`, `Alternative`, `Supplement`,
205
+ or `Unspecified` (the default). MIME type defaults to `application/octet-stream`,
206
+ description to the filename, and modification date to the document's modification
207
+ date. MIME parameters such as `; charset=utf-8` are not accepted. Set
208
+ `compress: true` or `false` to control compression; by default the engine chooses.
209
+
210
+ Up to 100 files and 64 MiB of combined attachment contents are allowed, separately
211
+ from HTML asset limits. Filenames must be at most 255 UTF-8 bytes, without
212
+ directories, colons, or control characters. Duplicate filenames, including
213
+ Unicode normalization equivalents, are rejected. Zero-byte files are supported.
214
+ Attachment contents are preserved without interpreting or validating their format.
215
+
216
+ ## Custom XMP schemas
217
+
218
+ `metadata.xmp` adds application-specific text properties and their PDF/A extension
219
+ schema descriptions to the existing XMP packet. Standard metadata and PDF/A
220
+ identification remain present.
221
+
222
+ ```js
223
+ const pdf = await renderPdf(html, {
224
+ metadata: {
225
+ xmp: [{
226
+ name: "Project metadata",
227
+ namespaceUri: "urn:example:project:1.0#",
228
+ prefix: "project",
229
+ properties: [{
230
+ name: "ProjectId",
231
+ value: "PR-0042",
232
+ description: "Identifier of the project this document belongs to",
233
+ category: "external", // Default; use internal for content-derived values.
234
+ }],
235
+ }],
236
+ },
237
+ });
238
+ ```
239
+
240
+ Every property is declared as XMP `Text`; values and descriptions are escaped
241
+ as XML. Schema names and property descriptions must be non-empty. Prefixes and
242
+ property names use ASCII XML names without colons or an `xml` prefix. Namespace
243
+ URIs must be absolute ASCII URIs with whitespace and XML delimiters percent-encoded.
244
+ Built-in namespaces/prefixes are reserved. Duplicate prefixes, namespace URIs,
245
+ or property names within one schema are rejected. Each schema requires at least
246
+ one property. Limits are 32 schemas, 256 properties in total, and 1 MiB of combined
247
+ schema text, in addition to the per-field limit. Structured XMP values and raw
248
+ XMP packet replacement are not currently exposed.
249
+
250
+ ### Factur-X / ZUGFeRD example
251
+
252
+ The generic attachment and XMP APIs can express Factur-X/ZUGFeRD packaging. For
253
+ example, with matching HTML and already-validated **EN 16931** CII invoice XML:
254
+
255
+ ```js
256
+ const pdf = await renderPdf(invoiceHtml, {
257
+ attachments: [{
258
+ name: "factur-x.xml", data: invoiceXml, mimeType: "text/xml",
259
+ description: "Invoice XML", relationship: "Alternative",
260
+ }],
261
+ metadata: {
262
+ title: "Invoice RE-0042", language: "de-DE",
263
+ xmp: [{
264
+ name: "Factur-X PDFA Extension Schema",
265
+ namespaceUri: "urn:factur-x:pdfa:CrossIndustryDocument:invoice:1p0#",
266
+ prefix: "fx",
267
+ properties: [
268
+ { name: "DocumentFileName", value: "factur-x.xml", description: "Embedded invoice filename" },
269
+ { name: "DocumentType", value: "INVOICE", description: "Document type" },
270
+ { name: "Version", value: "1.0", description: "Schema version" },
271
+ { name: "ConformanceLevel", value: "EN 16931", description: "Invoice profile" },
272
+ ],
273
+ }],
274
+ },
275
+ });
276
+ ```
277
+
278
+ These settings follow the [PDF embedding and XMP requirements](https://www.pdflib.com/pdf-knowledge-base/zugferd-and-factur-x/)
279
+ for Factur-X 1.x / ZUGFeRD 2.1+. The XMP `Version` is `1.0`, not the release number.
280
+ Choose the profile and relationship for your document: `Data` for MINIMUM/BASIC WL;
281
+ normally `Alternative` for BASIC, EN 16931, or EXTENDED. `Source` applies in the
282
+ specified cases where the PDF is derived from XML and the recipient is outside
283
+ Germany. The XML filename and XMP `DocumentFileName` must agree. See the
284
+ [FeRD specification and validation artifacts](https://www.ferd-net.de/publikationen-produkte/publikationen/detailseite/zugferd-252-english)
285
+ for the version/profile you use.
286
+
287
+ The renderer does not generate invoice XML, validate XSD/Schematron rules, infer
288
+ profiles, or check agreement between PDF and XML. Applications supply and validate
289
+ these inputs. PDF/A conformance alone does not establish invoice conformance.
290
+
291
+ The runnable example accepts your HTML, XML, and profile:
292
+
293
+ ```sh
294
+ npm run example:zugferd -- invoice.html invoice.xml "EN 16931" output/pdf/invoice.pdf
295
+ ```
296
+
297
+ `PdfAttachment`, `AttachmentRelationship`, `PdfMetadata`, `PdfDate`,
298
+ `PdfXmpSchema`, and `PdfXmpProperty` are exported TypeScript types in Node/Bun
299
+ and browser entry points.
300
+
100
301
  ## Resources and fonts
101
302
 
102
303
  The renderer requests resources through `assets` or decodes data URLs. Supply an
@@ -162,7 +363,7 @@ system font discovery. Fonts passed in `fonts` use their internal family names;
162
363
  use `@font-face` with an asset URL for a CSS alias. Supply fonts for scripts not
163
364
  covered by the bundled family.
164
365
 
165
- Input limits: 16 MiB HTML, 64 MiB supplied assets, 32 MiB supplied fonts, and 1024
366
+ Input limits: 16 MiB HTML, 64 MiB supplied assets, 64 MiB attachments, 32 MiB supplied fonts, and 1024
166
367
  resource requests. These are practical limits, not a complete sandbox; isolate
167
368
  untrusted documents in a terminable worker with application limits.
168
369
 
@@ -207,6 +408,7 @@ vertical flow include:
207
408
  `page-break-after`, and `page-break-inside` aliases.
208
409
  - Inherited `widows` and `orphans` (positive integers; default 2).
209
410
  - Automatic protection of text lines, table rows, images, and SVG boxes.
411
+ - Repeated table header and footer groups, with space reserved before splitting rows.
210
412
 
211
413
  ```html
212
414
  <style>
@@ -229,6 +431,18 @@ relaxed to make progress. A text line that cannot fit the printable height
229
431
  fails explicitly. Keep-with-next protects a heading even when the following
230
432
  paragraph spans multiple pages.
231
433
 
434
+ The first `<thead>` and `<tfoot>` (or elements styled as `table-header-group`
435
+ and `table-footer-group`) repeat on every fragment of their table. Their actual
436
+ layout, column widths, backgrounds, and selectable text are retained. Use
437
+ `display: table-row-group` to disable repetition. Headers stay with the first
438
+ body row and footers with the last where they fit. Repetition stops when the
439
+ table ends, and the extra space counts toward `maxPages`. Nested tables remain
440
+ part of their containing row rather than independently repeating their groups.
441
+
442
+ Pagination accounts for the subpixel difference between CSS dimensions and
443
+ Blitz's rounded box edges, avoiding an extra page for an exactly fitting
444
+ fractional-height label.
445
+
232
446
  ### Page numbers
233
447
 
234
448
  ```js
@@ -255,6 +469,42 @@ intentional blanks. The footer uses embedded DejaVu Sans and the same Unicode
255
469
  shaping as body text, preserving selectable text and PDF/A-3u. It works with
256
470
  async assets in every runtime. `PageNumberOptions` is an exported TypeScript type.
257
471
 
472
+ ### Repeated HTML headers and footers
473
+
474
+ ```js
475
+ const pdf = await renderPdf(reportHtml, {
476
+ margin: { top: "18mm", bottom: "15mm", left: "12mm", right: "12mm" },
477
+ headerTemplate: `
478
+ <div style="font: 10pt/14pt sans-serif; padding-top: 4mm">
479
+ <img src="logo.svg" width="24" height="24"> Quarterly report
480
+ </div>`,
481
+ footerTemplate: `
482
+ <div style="font: 9pt/12pt sans-serif; text-align: right">
483
+ Page <span class="pageNumber"></span> of <span class="totalPages"></span>
484
+ </div>`,
485
+ assets: { "logo.svg": logoSvg },
486
+ });
487
+ ```
488
+
489
+ Templates occupy the top/bottom margins between the left and right margins,
490
+ including on intentional blank pages. Supply enough margin height for each
491
+ template; overflow fails with an error. Both start at the top of their margin
492
+ band, so use CSS padding or flex layout to position their contents within it.
493
+ Body content does not overlap those bands. `pageNumbers` can also be used; its
494
+ automatic footer remains above the bottom margin.
495
+
496
+ Each template is a separate HTML document with zero default body margin and a
497
+ transparent background. Template-local inline styles, `<style>`, and linked
498
+ stylesheets are supported. Body CSS does not cascade into templates. They share
499
+ the document's asset resolver, loaded font collection, supplied fonts, base URL,
500
+ and media type. URLs are resolved and deduplicated across the whole render,
501
+ including async CSS, fonts, and images. Scripts are not run. Text stays selectable
502
+ and graphics use the same vector PDF pipeline as the body.
503
+
504
+ Elements with class `pageNumber` or `totalPages` have their contents replaced
505
+ with the final page number/count before layout. Templates are limited to 1 MiB
506
+ of HTML each. Omit a template or pass an empty string to disable it.
507
+
258
508
  ## German invoice example
259
509
 
260
510
  ```sh
@@ -273,7 +523,8 @@ alongside the PDF for inspection.
273
523
 
274
524
  The [example source and scope](examples/german-invoice/README.md) explain the
275
525
  fictitious data and the German invoice fields. This is a **PDF/A-3u example
276
- without embedded XML**, as opposed to a structured e-invoice. Its 2026 domestic
526
+ without embedded XML**. Use the generic [attachment and XMP APIs](#factur-x--zugferd-example)
527
+ with independently generated, matching invoice XML for a structured e-invoice. Its 2026 domestic
277
528
  B2B scenario assumes the recipient agrees to PDF delivery under the applicable
278
529
  transition rule. [§ 14 UStG](https://www.gesetze-im-internet.de/ustg_1980/__14.html),
279
530
  [§ 27(38) UStG](https://www.gesetze-im-internet.de/ustg_1980/__27.html).
@@ -284,9 +535,9 @@ transition rule. [§ 14 UStG](https://www.gesetze-im-internet.de/ustg_1980/__14.
284
535
  - Text retains shaped glyphs and Unicode cluster mappings, including ligatures.
285
536
  Unrecognized or ambiguous text runs are rejected rather than assigned guessed
286
537
  Unicode.
287
- - Pagination fragments one continuous vertical layout. CSS `@page`, repeated
288
- table/fixed headers, nested multicolumn fragmentation, and independent
289
- fragmentation of parallel flex/grid flows are not implemented. Oversized
538
+ - Pagination fragments one continuous vertical layout. Per-page geometry,
539
+ repeated fixed-position body elements, nested multicolumn fragmentation, and
540
+ independent fragmentation of parallel flex/grid flows are not implemented. Oversized
290
541
  boxes/images may span pages; box decorations are sliced rather than cloned.
291
542
  - CSS filters, blurred shadows, non-default variable font instances, and
292
543
  repeating radial gradients are rejected. Use static font faces. There is no
@@ -315,11 +566,12 @@ npm pack
315
566
  The wasm-bindgen CLI version must match `Cargo.lock`. Dependencies and upstream
316
567
  commits are locked. `npm pack` verifies artifacts and renders a PDF before making
317
568
  the tarball; it does not publish. Install locally using
318
- `npm install ./blitz-pdf-0.1.0.tgz`.
569
+ `npm install ./blitz-pdf-<version>.tgz`.
319
570
 
320
571
  The pinned Stylo source in `vendor/stylo` enables paged-media properties in the
321
- Servo build; `vendor/README.md` records the small patch. Its MPL-2.0 source is
322
- included in the npm archive.
572
+ Servo build. `vendor/krilla` adds custom XMP schema support and independent
573
+ modification dates. `vendor/README.md` records both patches; the sources and
574
+ licenses are included in the npm archive.
323
575
 
324
576
  `bun test` exercises the compiled module through its public package export. Tests
325
577
  extract Unicode with PDF.js and inspect embedded fonts, vector paths, absence
@@ -328,6 +580,44 @@ For browser verification, run `npm run test:browser` and open
328
580
  `http://127.0.0.1:4173`. CI builds WASM, runs Bun and Node tests, validates the
329
581
  captured PDFs with veraPDF, benchmarks, and packs an npm artifact.
330
582
 
583
+ ## Publishing releases
584
+
585
+ Push a tag matching the package version, such as `v0.1.1`, to start
586
+ [Publish to npm](https://github.com/iteufel/blitz-pdf/actions/workflows/publish.yml).
587
+ The workflow checks that the tag, npm manifests, and root Rust package versions
588
+ agree. It then builds WASM from source, runs the Bun and Node tests, validates
589
+ PDF/A-3u with veraPDF, runs benchmarks, and publishes the verified archive.
590
+ Release builds do not restore dependency or compiler caches.
591
+
592
+ Publishing uses [npm trusted publishing](https://docs.npmjs.com/trusted-publishers/)
593
+ with GitHub OIDC and provenance. The npm trust must permit direct publishing from
594
+ `iteufel/blitz-pdf`, workflow `publish.yml`, with no environment restriction.
595
+ No `NPM_TOKEN` repository secret is needed. Stable versions use the npm `latest`
596
+ tag; prereleases use `next`. After publishing, the workflow allows up to five
597
+ minutes for registry propagation and checks that npm serves the exact archive it
598
+ built, comparing SHA-512 integrity. Retrying an already-published version succeeds
599
+ only when its archive is identical; a different archive fails.
600
+
601
+ For the next patch release:
602
+
603
+ 1. Run `npm version patch --no-git-tag-version` to update both npm manifests.
604
+ 2. Set the same version in `Cargo.toml` and the `blitz-pdf-wasm` entry in
605
+ `Cargo.lock`.
606
+ 3. Commit the changes, create the matching Git tag, and push both:
607
+
608
+ ```sh
609
+ git add package.json package-lock.json Cargo.toml Cargo.lock
610
+ RELEASE_VERSION=$(node -p "require('./package.json').version")
611
+ git commit -m "chore: release $RELEASE_VERSION"
612
+ git tag "v$RELEASE_VERSION"
613
+ git push origin main "v$RELEASE_VERSION"
614
+ ```
615
+
616
+ To retry a failed release, rerun its GitHub Actions run or use
617
+ `gh workflow run publish.yml --ref v<version>`. Manual runs require a matching version
618
+ tag too. Published npm versions cannot be overwritten; subsequent releases need
619
+ a new version.
620
+
331
621
  ## Performance benchmarks
332
622
 
333
623
  ```sh
@@ -15,8 +15,10 @@ The resolved dependency inventory and upstream license notices are in
15
15
  Upstream sources are available at the repository links in that inventory and
16
16
  through crates.io. Stylo 0.22.0 is patched to expose paged-media CSS properties
17
17
  in its Servo build. Its full modified MPL-2.0 source and license are included in
18
- `vendor/stylo`; `vendor/README.md` lists the changes. Other dependencies are used
19
- without source modifications.
18
+ `vendor/stylo`. Krilla 0.8.2 is patched for custom XMP extension schemas and
19
+ independent modification dates; its source and MIT/Apache-2.0 licenses are in
20
+ `vendor/krilla`. `vendor/README.md` lists both patches. Other dependencies are
21
+ used without source modifications.
20
22
 
21
23
  Bundled DejaVu fonts use the Bitstream Vera / DejaVu license reproduced in
22
24
  `licenses/DejaVu.txt`. The regular WOFF2 comes from Blitz's
package/dist/browser.d.ts CHANGED
@@ -1,3 +1,3 @@
1
- export type { AssetData, AssetMap, AssetResolver, BinaryData, InitOptions, Length, PageFormat, PageNumberOptions, PdfOptions, WasmSource } from "./types.js";
1
+ export type { AssetData, AssetMap, AssetResolver, AttachmentRelationship, BinaryData, InitOptions, Length, PageFormat, PageNumberOptions, PdfAttachment, PdfDate, PdfMetadata, PdfOptions, PdfXmpProperty, PdfXmpSchema, WasmSource } from "./types.js";
2
2
  export declare const init: (options?: import("./types.js").InitOptions) => Promise<void>, renderPdf: (html: string, options?: import("./types.js").PdfOptions) => Promise<Uint8Array>, getEngineVersion: () => Promise<string>;
3
3
  //# sourceMappingURL=browser.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"browser.d.ts","sourceRoot":"","sources":["../src/browser.ts"],"names":[],"mappings":"AAEA,YAAY,EAAE,SAAS,EAAE,QAAQ,EAAE,aAAa,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,EAAE,UAAU,EAAE,iBAAiB,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAC7J,eAAO,MAAQ,IAAI,iEAAE,SAAS,oFAAE,gBAAgB,uBAE/C,CAAC"}
1
+ {"version":3,"file":"browser.d.ts","sourceRoot":"","sources":["../src/browser.ts"],"names":[],"mappings":"AAEA,YAAY,EAAE,SAAS,EAAE,QAAQ,EAAE,aAAa,EAAE,sBAAsB,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,EAAE,UAAU,EAAE,iBAAiB,EAAE,aAAa,EAAE,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,cAAc,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AACxP,eAAO,MAAQ,IAAI,iEAAE,SAAS,oFAAE,gBAAgB,uBAE/C,CAAC"}
@@ -0,0 +1,36 @@
1
+ import type { AttachmentRelationship, PdfOptions } from "./types.js";
2
+ export declare function normalizeDocumentOptions(options: PdfOptions): {
3
+ metadata: {
4
+ title: string | null;
5
+ authors: string[];
6
+ subject: string | null;
7
+ keywords: string[];
8
+ creator: string | null;
9
+ producer: string | null;
10
+ language: string | null;
11
+ creationDate: number;
12
+ modificationDate: number;
13
+ documentId: string | null;
14
+ xmp: {
15
+ name: string;
16
+ prefix: string;
17
+ namespaceUri: string;
18
+ properties: {
19
+ name: string;
20
+ value: string;
21
+ description: string;
22
+ category: any;
23
+ }[];
24
+ }[];
25
+ };
26
+ attachments: {
27
+ name: string;
28
+ data: Uint8Array<ArrayBufferLike>;
29
+ mimeType: string;
30
+ description: string;
31
+ relationship: AttachmentRelationship;
32
+ modificationDate: number;
33
+ compress: boolean | null;
34
+ }[];
35
+ };
36
+ //# sourceMappingURL=document-options.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"document-options.d.ts","sourceRoot":"","sources":["../src/document-options.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAa,sBAAsB,EAA0B,UAAU,EAAgB,MAAM,YAAY,CAAC;AAuJtH,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,UAAU;;;;;;;;;;;;;;;;;;;;;;;;;;;;;sBAyDxB,sBAAsB;;;;EAOzD"}