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 +140 -3
- package/bundlers/node.cjs +10 -0
- package/bundlers/node.d.cts +1 -0
- package/bundlers/node.d.ts +1 -0
- package/bundlers/node.mjs +9 -0
- package/bundlers/workerd.mjs +7 -0
- package/dist/export.cjs +535 -0
- package/dist/export.d.cts +124 -0
- package/dist/export.d.mts +124 -0
- package/dist/export.mjs +528 -0
- package/package.json +100 -14
- package/pkg/takumi_pdf_wasm_bg.wasm +0 -0
- package/pkg/takumi_pdf_wasm_bg.wasm.d.ts +12 -0
- package/index.js +0 -3
package/README.md
CHANGED
|
@@ -1,7 +1,144 @@
|
|
|
1
1
|
# takumi-pdf
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Render JSX to paged PDF in WebAssembly. No Chromium or native binary.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
## Install
|
|
6
6
|
|
|
7
|
-
|
|
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;
|