takumi-pdf 0.0.0 → 0.1.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
@@ -1,7 +1,144 @@
1
1
  # takumi-pdf
2
2
 
3
- HTML and JSX in, paginated vector PDF out. No headless browser.
3
+ Render JSX to paged PDF in WebAssembly. No Chromium or native binary.
4
4
 
5
- Built on the [takumi](https://github.com/kane50613/takumi) rendering engine: real CSS layout, embedded subset fonts with selectable text, page breaks (`break-before`, `break-inside: avoid`, `box-decoration-break`), and repeating headers/footers with page counters.
5
+ ## Install
6
6
 
7
- This version reserves the package name while the first release is being finished. Follow progress at [github.com/kane50613/takumi](https://github.com/kane50613/takumi).
7
+ ```bash
8
+ npm install takumi-pdf
9
+ # or
10
+ bun add takumi-pdf
11
+ ```
12
+
13
+ ## Quick start
14
+
15
+ ```tsx
16
+ import { render } from "takumi-pdf";
17
+ import { writeFile } from "node:fs/promises";
18
+
19
+ const pdf = await render(<h1>Invoice #1042</h1>);
20
+
21
+ await writeFile("invoice.pdf", pdf);
22
+ ```
23
+
24
+ `render()` returns `Uint8Array` PDF bytes. Output is paged A4 with a 48px margin by default; content flows onto as many pages as it needs. Text stays selectable and searchable, with fonts subset and embedded.
25
+
26
+ See the [Takumi documentation](https://takumi.kane.tw/docs/) for the shared node and JSX model.
27
+
28
+ ## Page setup
29
+
30
+ ```tsx
31
+ const pdf = await render(report, {
32
+ size: "letter",
33
+ landscape: true,
34
+ margin: { top: 48, right: 32, bottom: 48, left: 32 },
35
+ });
36
+ ```
37
+
38
+ | Option | Type | Default | Description |
39
+ | ----------- | ---------------------------------------------- | ------- | ------------------------------------------------------------ |
40
+ | `size` | `"a4"`, `"letter"`, or `{ width, height }` | `"a4"` | Page size in CSS px at 96 dpi. Presets ignore case. |
41
+ | `landscape` | `boolean` | `false` | Swaps page width and height, including explicit sizes. |
42
+ | `margin` | `number` or `{ top?, right?, bottom?, left? }` | `48` | A number applies to all sides. Missing object sides are `0`. |
43
+
44
+ ## Headers and footers
45
+
46
+ Headers and footers repeat on every page. Elements with `pageNumber` or `totalPages` in their class list receive the counter as text, the same contract as Chromium print templates.
47
+
48
+ ```tsx
49
+ const pdf = await render(report, {
50
+ footer: (
51
+ <div style={{ fontSize: 12 }}>
52
+ Page <span className="pageNumber" /> of <span className="totalPages" />
53
+ </div>
54
+ ),
55
+ });
56
+ ```
57
+
58
+ Add a CSS counter-style name to the class list to format the number:
59
+
60
+ ```tsx
61
+ footer: (
62
+ <div style={{ fontSize: 12 }}>
63
+ 第 <span className="pageNumber trad-chinese-informal" /> 頁,共{" "}
64
+ <span className="totalPages trad-chinese-informal" /> 頁
65
+ </div>
66
+ ),
67
+ ```
68
+
69
+ | Counter style | Example |
70
+ | ------------------------------------------- | ------------- |
71
+ | `decimal` (default) | `12` |
72
+ | `decimal-leading-zero` | `07` |
73
+ | `lower-roman` / `upper-roman` | `xii` / `XII` |
74
+ | `cjk-decimal` | `一二` |
75
+ | `trad-chinese-informal` / `cjk-ideographic` | `十二` |
76
+
77
+ ## Single-page viewport
78
+
79
+ 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.
80
+
81
+ ```tsx
82
+ const pdf = await render(<div style={{ width: "100%", height: "100%" }}>Certificate</div>, {
83
+ viewport: { width: 1123, height: 794 },
84
+ });
85
+ ```
86
+
87
+ `viewport` cannot be combined with `size`, `landscape`, `margin`, `header`, or `footer`.
88
+
89
+ ## Fonts
90
+
91
+ Pass a font URL or font bytes. Registered fonts are deduplicated across calls.
92
+
93
+ ```tsx
94
+ const pdf = await render(doc, {
95
+ fonts: [
96
+ "https://example.com/Inter-Regular.woff2",
97
+ { name: "Brand Sans", weight: 700, data: fontBytes },
98
+ ],
99
+ fontFamilies: ["Brand Sans", "sans-serif"],
100
+ });
101
+ ```
102
+
103
+ Reuse a `PdfRenderer` when an application renders many documents:
104
+
105
+ ```tsx
106
+ import { PdfRenderer } from "takumi-pdf";
107
+
108
+ const renderer = new PdfRenderer();
109
+ await renderer.registerFont("https://example.com/Inter-Regular.woff2");
110
+
111
+ const pdf = await renderer.render(doc);
112
+ ```
113
+
114
+ ## Pagination CSS
115
+
116
+ ```tsx
117
+ const pdf = await render(
118
+ <article>
119
+ <h1 style={{ breakBefore: "page" }}>Chapter two</h1>
120
+ <section style={{ breakInside: "avoid" }}>Keep this together.</section>
121
+ </article>,
122
+ );
123
+ ```
124
+
125
+ | Property | Effect |
126
+ | ----------------------------- | ------------------------------------------------------- |
127
+ | `break-before: page` | Starts the element on a new page. |
128
+ | `break-after: page` | Starts the following content on a new page. |
129
+ | `break-inside: avoid` | Keeps the element on one page when it fits. |
130
+ | `box-decoration-break: clone` | Repeats borders and backgrounds on every page fragment. |
131
+
132
+ ## Images and runtimes
133
+
134
+ `takumi-pdf` runs on Node.js, Bun, and Cloudflare Workers.
135
+
136
+ The renderer does not fetch remote images. Pass pre-fetched bytes for image URLs in the document:
137
+
138
+ ```tsx
139
+ const pdf = await render(doc, {
140
+ images: [{ src: "https://example.com/logo.png", data: logoBytes }],
141
+ });
142
+ ```
143
+
144
+ `@page` CSS rules are not supported. Set page geometry with `size`, `landscape`, and `margin`.
@@ -0,0 +1,10 @@
1
+ const wasm = require("../dist/export.cjs");
2
+ const { readFileSync } = require("node:fs");
3
+ const { join } = require("node:path");
4
+
5
+ const wasmPath = join(__dirname, "../pkg/takumi_pdf_wasm_bg.wasm");
6
+ const wasmBytes = readFileSync(wasmPath);
7
+
8
+ wasm.initSync({ module: wasmBytes });
9
+
10
+ module.exports = wasm;
@@ -0,0 +1 @@
1
+ export * from "../dist/export.mjs";
@@ -0,0 +1 @@
1
+ export * from "../dist/export.mjs";
@@ -0,0 +1,9 @@
1
+ import { readFileSync } from "node:fs";
2
+ import * as wasm from "../dist/export.mjs";
3
+
4
+ const wasmBytes = readFileSync(new URL("../pkg/takumi_pdf_wasm_bg.wasm", import.meta.url));
5
+
6
+ wasm.initSync({ module: wasmBytes });
7
+
8
+ export * from "../dist/export.mjs";
9
+ export default wasm.default;
@@ -0,0 +1,7 @@
1
+ import module from "../pkg/takumi_pdf_wasm_bg.wasm";
2
+ import * as wasm from "../dist/export.mjs";
3
+
4
+ wasm.initSync({ module });
5
+
6
+ export * from "../dist/export.mjs";
7
+ export default wasm.default;