takumi-pdf 0.5.0 → 0.6.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
@@ -3,9 +3,9 @@
3
3
 
4
4
  # takumi-pdf
5
5
 
6
- **Render JSX to paged, selectable-text PDF. WebAssembly, no Chromium.**
6
+ **Render paged PDFs from JSX, HTML, and CSS. No headless browser.**
7
7
 
8
- Invoices, reports, and receipts from the Takumi renderer, with CSS layout and vector PDF output.
8
+ Build invoices and reports with CSS or Tailwind. The renderer writes vector PDF with selectable text.
9
9
 
10
10
  [Documentation](https://takumi.kane.tw/docs/) · [Playground](https://takumi.kane.tw/playground)
11
11
 
@@ -13,9 +13,9 @@ Invoices, reports, and receipts from the Takumi renderer, with CSS layout and ve
13
13
 
14
14
  ## Why
15
15
 
16
- Browser-based PDF generation brings the Chromium serverless tax: browser cold starts, browser memory, a large browser binary, and headless-browser work before a document can render. It is a capable screenshot pipeline, but it is still a browser.
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.
17
17
 
18
- `takumi-pdf` uses Takumi's own layout and PDF rendering primitives, compiled to WebAssembly. There is no browser process. Render JSX or a Takumi node tree with CSS and Tailwind classes, then receive vector PDF bytes with selectable, searchable text and embedded subset fonts.
18
+ Pass JSX 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
 
@@ -168,7 +168,7 @@ const pdf = await render(
168
168
 
169
169
  ## Links, outline, and metadata
170
170
 
171
- Anchors with an `href` become clickable link annotations. `outline: true` builds PDF bookmarks from `h1`–`h6` headings. `metadata` fills the document properties:
171
+ Anchors with an `href` become clickable link annotations. `outline: true` builds PDF bookmarks from `h1` through `h6` headings. `metadata` fills the document properties:
172
172
 
173
173
  ```tsx
174
174
  const pdf = await render(report, {
@@ -186,7 +186,7 @@ Omit `metadata` to keep output byte-identical across runs.
186
186
 
187
187
  ## Tagged output and PDF/A
188
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.
189
+ 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.
190
190
 
191
191
  `<table>` markup is not supported, and a table built out of flex rows carries no `Table` structure elements.
192
192
 
package/dist/export.cjs CHANGED
@@ -104,6 +104,32 @@ var PdfRenderer$1 = class {
104
104
  }
105
105
  };
106
106
  if (Symbol.dispose) PdfRenderer$1.prototype[Symbol.dispose] = PdfRenderer$1.prototype.free;
107
+ /**
108
+ * The characters the page counters named by these class names can produce.
109
+ *
110
+ * A counter's characters appear nowhere in the document, so a caller choosing
111
+ * which faces to load has no other way to learn it needs, say, Thai digits.
112
+ * @param {string[]} classes
113
+ * @returns {string}
114
+ */
115
+ function counterCharacters(classes) {
116
+ let deferred2_0;
117
+ let deferred2_1;
118
+ try {
119
+ const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
120
+ const ptr0 = passArrayJsValueToWasm0(classes, wasm.__wbindgen_export);
121
+ const len0 = WASM_VECTOR_LEN;
122
+ wasm.counterCharacters(retptr, ptr0, len0);
123
+ var r0 = getDataViewMemory0().getInt32(retptr + 0, true);
124
+ var r1 = getDataViewMemory0().getInt32(retptr + 4, true);
125
+ deferred2_0 = r0;
126
+ deferred2_1 = r1;
127
+ return getStringFromWasm0(r0, r1);
128
+ } finally {
129
+ wasm.__wbindgen_add_to_stack_pointer(16);
130
+ wasm.__wbindgen_export4(deferred2_0, deferred2_1, 1);
131
+ }
132
+ }
107
133
  function __wbg_get_imports() {
108
134
  return {
109
135
  __proto__: null,
@@ -383,6 +409,13 @@ let heap_next = heap.length;
383
409
  function isLikeNone(x) {
384
410
  return x === void 0 || x === null;
385
411
  }
412
+ function passArrayJsValueToWasm0(array, malloc) {
413
+ const ptr = malloc(array.length * 4, 4) >>> 0;
414
+ const mem = getDataViewMemory0();
415
+ for (let i = 0; i < array.length; i++) mem.setUint32(ptr + 4 * i, addHeapObject(array[i]), true);
416
+ WASM_VECTOR_LEN = array.length;
417
+ return ptr;
418
+ }
386
419
  function passStringToWasm0(arg, malloc, realloc) {
387
420
  if (realloc === void 0) {
388
421
  const buf = cachedTextEncoder.encode(arg);
@@ -498,8 +531,18 @@ async function __wbg_init(module_or_path) {
498
531
  }
499
532
  //#endregion
500
533
  //#region src/export.ts
501
- /** Every character a page counter can produce, so subsetting keeps the fonts a band needs. */
502
- const COUNTER_DIGITS = "0123456789";
534
+ /** Every class name in a tree, so the renderer can say what its counters draw. */
535
+ function classNames(node) {
536
+ const into = [];
537
+ collectClassNames(node, into);
538
+ return into;
539
+ }
540
+ function collectClassNames(node, into) {
541
+ if (typeof node !== "object" || node === null) return;
542
+ const { className, children } = node;
543
+ if (typeof className === "string") into.push(...className.split(/\s+/).filter(Boolean));
544
+ if (Array.isArray(children)) for (const child of children) collectClassNames(child, into);
545
+ }
503
546
  function isNode(value) {
504
547
  return typeof value === "object" && value !== null && "type" in value && !("$$typeof" in value);
505
548
  }
@@ -526,11 +569,11 @@ var PdfRenderer = class {
526
569
  const bands = [headerResult?.node, footerResult?.node].filter((band) => band !== void 0);
527
570
  const resources = await this.fonts.resolveResources(fonts && (0, _takumi_rs_helpers.subsetFonts)({
528
571
  fonts,
529
- source: bands.length > 0 ? [
572
+ source: [
530
573
  main.node,
531
574
  ...bands,
532
- COUNTER_DIGITS
533
- ] : main.node
575
+ counterCharacters([main.node, ...bands].flatMap((tree) => classNames(tree)))
576
+ ]
534
577
  }), images, fontFamilies);
535
578
  const sheets = [
536
579
  ...stylesheets ?? [],
@@ -563,7 +606,7 @@ var PdfRenderer = class {
563
606
  const main = await resolveNode(node);
564
607
  const resources = await this.fonts.resolveResources(fonts && (0, _takumi_rs_helpers.subsetFonts)({
565
608
  fonts,
566
- source: [main.node, COUNTER_DIGITS]
609
+ source: [main.node, counterCharacters(classNames(main.node))]
567
610
  }), images, fontFamilies);
568
611
  const sheets = [...stylesheets ?? [], ...main.stylesheets];
569
612
  return this.inner.measure(main.node, {
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 counterCharacters: (a: number, b: number, c: number) => void;
9
10
  readonly pdfrenderer_measure: (a: number, b: number, c: number, d: number) => void;
10
11
  readonly pdfrenderer_new: (a: number) => void;
11
12
  readonly pdfrenderer_registerFont: (a: number, b: number, c: number) => void;
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 counterCharacters: (a: number, b: number, c: number) => void;
9
10
  readonly pdfrenderer_measure: (a: number, b: number, c: number, d: number) => void;
10
11
  readonly pdfrenderer_new: (a: number) => void;
11
12
  readonly pdfrenderer_registerFont: (a: number, b: number, c: number) => void;
package/dist/export.mjs CHANGED
@@ -100,6 +100,32 @@ var PdfRenderer$1 = class {
100
100
  }
101
101
  };
102
102
  if (Symbol.dispose) PdfRenderer$1.prototype[Symbol.dispose] = PdfRenderer$1.prototype.free;
103
+ /**
104
+ * The characters the page counters named by these class names can produce.
105
+ *
106
+ * A counter's characters appear nowhere in the document, so a caller choosing
107
+ * which faces to load has no other way to learn it needs, say, Thai digits.
108
+ * @param {string[]} classes
109
+ * @returns {string}
110
+ */
111
+ function counterCharacters(classes) {
112
+ let deferred2_0;
113
+ let deferred2_1;
114
+ try {
115
+ const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
116
+ const ptr0 = passArrayJsValueToWasm0(classes, wasm.__wbindgen_export);
117
+ const len0 = WASM_VECTOR_LEN;
118
+ wasm.counterCharacters(retptr, ptr0, len0);
119
+ var r0 = getDataViewMemory0().getInt32(retptr + 0, true);
120
+ var r1 = getDataViewMemory0().getInt32(retptr + 4, true);
121
+ deferred2_0 = r0;
122
+ deferred2_1 = r1;
123
+ return getStringFromWasm0(r0, r1);
124
+ } finally {
125
+ wasm.__wbindgen_add_to_stack_pointer(16);
126
+ wasm.__wbindgen_export4(deferred2_0, deferred2_1, 1);
127
+ }
128
+ }
103
129
  function __wbg_get_imports() {
104
130
  return {
105
131
  __proto__: null,
@@ -379,6 +405,13 @@ let heap_next = heap.length;
379
405
  function isLikeNone(x) {
380
406
  return x === void 0 || x === null;
381
407
  }
408
+ function passArrayJsValueToWasm0(array, malloc) {
409
+ const ptr = malloc(array.length * 4, 4) >>> 0;
410
+ const mem = getDataViewMemory0();
411
+ for (let i = 0; i < array.length; i++) mem.setUint32(ptr + 4 * i, addHeapObject(array[i]), true);
412
+ WASM_VECTOR_LEN = array.length;
413
+ return ptr;
414
+ }
382
415
  function passStringToWasm0(arg, malloc, realloc) {
383
416
  if (realloc === void 0) {
384
417
  const buf = cachedTextEncoder.encode(arg);
@@ -494,8 +527,18 @@ async function __wbg_init(module_or_path) {
494
527
  }
495
528
  //#endregion
496
529
  //#region src/export.ts
497
- /** Every character a page counter can produce, so subsetting keeps the fonts a band needs. */
498
- const COUNTER_DIGITS = "0123456789";
530
+ /** Every class name in a tree, so the renderer can say what its counters draw. */
531
+ function classNames(node) {
532
+ const into = [];
533
+ collectClassNames(node, into);
534
+ return into;
535
+ }
536
+ function collectClassNames(node, into) {
537
+ if (typeof node !== "object" || node === null) return;
538
+ const { className, children } = node;
539
+ if (typeof className === "string") into.push(...className.split(/\s+/).filter(Boolean));
540
+ if (Array.isArray(children)) for (const child of children) collectClassNames(child, into);
541
+ }
499
542
  function isNode(value) {
500
543
  return typeof value === "object" && value !== null && "type" in value && !("$$typeof" in value);
501
544
  }
@@ -522,11 +565,11 @@ var PdfRenderer = class {
522
565
  const bands = [headerResult?.node, footerResult?.node].filter((band) => band !== void 0);
523
566
  const resources = await this.fonts.resolveResources(fonts && subsetFonts({
524
567
  fonts,
525
- source: bands.length > 0 ? [
568
+ source: [
526
569
  main.node,
527
570
  ...bands,
528
- COUNTER_DIGITS
529
- ] : main.node
571
+ counterCharacters([main.node, ...bands].flatMap((tree) => classNames(tree)))
572
+ ]
530
573
  }), images, fontFamilies);
531
574
  const sheets = [
532
575
  ...stylesheets ?? [],
@@ -559,7 +602,7 @@ var PdfRenderer = class {
559
602
  const main = await resolveNode(node);
560
603
  const resources = await this.fonts.resolveResources(fonts && subsetFonts({
561
604
  fonts,
562
- source: [main.node, COUNTER_DIGITS]
605
+ source: [main.node, counterCharacters(classNames(main.node))]
563
606
  }), images, fontFamilies);
564
607
  const sheets = [...stylesheets ?? [], ...main.stylesheets];
565
608
  return this.inner.measure(main.node, {
package/package.json CHANGED
@@ -1,14 +1,19 @@
1
1
  {
2
2
  "name": "takumi-pdf",
3
- "version": "0.5.0",
4
- "description": "HTML/JSX to paged, selectable-text PDF, powered by takumi. WebAssembly, no Chromium.",
3
+ "version": "0.6.0",
4
+ "description": "Render paged PDFs from JSX, HTML, and CSS. No headless browser.",
5
5
  "keywords": [
6
+ "chromium-free",
7
+ "cjk",
6
8
  "css",
9
+ "html-to-pdf",
7
10
  "invoice",
8
11
  "jsx",
9
12
  "pdf",
13
+ "pdf-generation",
10
14
  "react",
11
- "rendering",
15
+ "receipt",
16
+ "tailwind",
12
17
  "webassembly"
13
18
  ],
14
19
  "homepage": "https://takumi.kane.tw/docs/",
@@ -83,7 +88,7 @@
83
88
  "publish-lint": "attw --pack . && publint --strict ."
84
89
  },
85
90
  "dependencies": {
86
- "@takumi-rs/helpers": "2.6.0"
91
+ "@takumi-rs/helpers": "2.6.1"
87
92
  },
88
93
  "devDependencies": {
89
94
  "@types/bun": "^1.3.14",
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 counterCharacters: (a: number, b: number, c: number) => void;
5
6
  export const pdfrenderer_measure: (a: number, b: number, c: number, d: number) => void;
6
7
  export const pdfrenderer_new: (a: number) => void;
7
8
  export const pdfrenderer_registerFont: (a: number, b: number, c: number) => void;