@polotno/pdf-export 0.1.42 → 0.2.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 +76 -112
- package/lib/browser.d.ts +102 -0
- package/lib/browser.js +3264 -0
- package/lib/data/Courier-Bold.afm +342 -0
- package/lib/data/Courier-BoldOblique.afm +342 -0
- package/lib/data/Courier-Oblique.afm +342 -0
- package/lib/data/Courier.afm +342 -0
- package/lib/data/Helvetica-Bold.afm +2827 -0
- package/lib/data/Helvetica-BoldOblique.afm +2827 -0
- package/lib/data/Helvetica-Oblique.afm +3051 -0
- package/lib/data/Helvetica.afm +3051 -0
- package/lib/data/Symbol.afm +213 -0
- package/lib/data/Times-Bold.afm +2588 -0
- package/lib/data/Times-BoldItalic.afm +2384 -0
- package/lib/data/Times-Italic.afm +2667 -0
- package/lib/data/Times-Roman.afm +2419 -0
- package/lib/data/ZapfDingbats.afm +225 -0
- package/lib/data/sRGB_IEC61966_2_1.icc +0 -0
- package/lib/index.d.ts +13 -1
- package/lib/index.js +196 -20
- package/package.json +49 -28
- package/patches/pdfkit+0.17.2.patch +0 -19
package/README.md
CHANGED
|
@@ -1,46 +1,63 @@
|
|
|
1
1
|
# Polotno to Vector PDF
|
|
2
2
|
|
|
3
|
-
Convert
|
|
3
|
+
Convert Polotno JSON into a vector PDF, in **Node.js** or directly in the **browser**. Optional PDF/X-1a print-ready export (Node only).
|
|
4
4
|
|
|
5
5
|
```bash
|
|
6
6
|
npm install @polotno/pdf-export
|
|
7
7
|
```
|
|
8
8
|
|
|
9
|
-
##
|
|
9
|
+
## Node usage
|
|
10
10
|
|
|
11
11
|
```js
|
|
12
|
-
import fs from 'fs';
|
|
12
|
+
import fs from 'node:fs';
|
|
13
13
|
import { jsonToPDF } from '@polotno/pdf-export';
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
const json = JSON.parse(fs.readFileSync('./polotno.json', 'utf8'));
|
|
16
|
+
await jsonToPDF(json, './output.pdf');
|
|
17
|
+
```
|
|
17
18
|
|
|
18
|
-
|
|
19
|
-
await jsonToPDF(json, './output.pdf');
|
|
20
|
-
}
|
|
19
|
+
`jsonToPDFBytes(json, attrs)` is also exported on the Node entry — useful when you want raw bytes (to upload, attach, etc.) without writing to disk.
|
|
21
20
|
|
|
22
|
-
|
|
21
|
+
## Browser usage
|
|
22
|
+
|
|
23
|
+
```js
|
|
24
|
+
import { jsonToPDFBlob } from '@polotno/pdf-export/browser';
|
|
25
|
+
|
|
26
|
+
async function exportToPDF(store) {
|
|
27
|
+
const blob = await jsonToPDFBlob(store.toJSON());
|
|
28
|
+
const url = URL.createObjectURL(blob);
|
|
29
|
+
const a = document.createElement('a');
|
|
30
|
+
a.href = url;
|
|
31
|
+
a.download = 'design.pdf';
|
|
32
|
+
document.body.appendChild(a);
|
|
33
|
+
a.click();
|
|
34
|
+
a.remove();
|
|
35
|
+
URL.revokeObjectURL(url);
|
|
36
|
+
}
|
|
23
37
|
```
|
|
24
38
|
|
|
25
|
-
|
|
39
|
+
Also exported: `jsonToPDFBytes(json, attrs): Promise<Uint8Array>` — for cases where you want raw bytes (to push to IndexedDB, send via WebTransport, share via `navigator.share`, etc.).
|
|
40
|
+
|
|
41
|
+
The browser entry **does not** support `attrs.pdfx1a` / `attrs.validate` — those rely on Ghostscript, which has no browser-side equivalent. Pass them and you'll get a clear runtime error pointing at the Node entry.
|
|
42
|
+
|
|
43
|
+
### CORS
|
|
44
|
+
|
|
45
|
+
When polotno designs reference remote images, fonts, or SVG sources, the browser fetches those assets at export time. They must be reachable with CORS:
|
|
26
46
|
|
|
27
|
-
|
|
47
|
+
- **Google Fonts** (`fonts.googleapis.com` and `fonts.gstatic.com`) work out of the box.
|
|
48
|
+
- **Custom asset URLs** must be served with `Access-Control-Allow-Origin: *` (or your origin), or pre-fetched and passed as `data:` URLs in the JSON.
|
|
28
49
|
|
|
29
|
-
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
- Print industry compliance
|
|
50
|
+
## PDF/X-1a print-ready export (Node only)
|
|
51
|
+
|
|
52
|
+
For professional printing:
|
|
33
53
|
|
|
34
54
|
```js
|
|
35
|
-
|
|
36
|
-
await jsonToPDF(json, './print-ready.pdf', {
|
|
37
|
-
pdfx1a: true,
|
|
38
|
-
});
|
|
55
|
+
await jsonToPDF(json, './print-ready.pdf', { pdfx1a: true });
|
|
39
56
|
|
|
40
|
-
//
|
|
57
|
+
// With custom metadata and validation
|
|
41
58
|
await jsonToPDF(json, './book-cover.pdf', {
|
|
42
59
|
pdfx1a: true,
|
|
43
|
-
validate: true,
|
|
60
|
+
validate: true,
|
|
44
61
|
metadata: {
|
|
45
62
|
title: 'My Book Cover',
|
|
46
63
|
author: 'Author Name',
|
|
@@ -49,20 +66,25 @@ await jsonToPDF(json, './book-cover.pdf', {
|
|
|
49
66
|
});
|
|
50
67
|
```
|
|
51
68
|
|
|
52
|
-
|
|
69
|
+
**Requires Ghostscript:**
|
|
70
|
+
|
|
71
|
+
- macOS: `brew install ghostscript`
|
|
72
|
+
- Ubuntu: `apt-get install ghostscript`
|
|
73
|
+
- Windows: download from [ghostscript.com](https://www.ghostscript.com/)
|
|
74
|
+
|
|
75
|
+
## Spot color / foil support
|
|
53
76
|
|
|
54
|
-
For professional printing with special inks like metallic foils
|
|
77
|
+
For professional printing with special inks like metallic foils or Pantone colors:
|
|
55
78
|
|
|
56
79
|
```js
|
|
57
80
|
await jsonToPDF(json, './output.pdf', {
|
|
58
81
|
pdfx1a: true,
|
|
59
82
|
spotColors: {
|
|
60
|
-
// Map any color to a spot color
|
|
61
83
|
'rgba(255,215,0,1)': {
|
|
62
84
|
name: 'Gold Foil',
|
|
63
|
-
pantoneCode: 'Pantone 871 C', //
|
|
64
|
-
cmyk: [0, 0.15, 0.5, 0],
|
|
65
|
-
overprint: true,
|
|
85
|
+
pantoneCode: 'Pantone 871 C', // optional reference
|
|
86
|
+
cmyk: [0, 0.15, 0.5, 0], // CMYK fallback (0–1 range)
|
|
87
|
+
overprint: true, // recommended for foils — see below
|
|
66
88
|
},
|
|
67
89
|
'#C0C0C0': {
|
|
68
90
|
name: 'Silver Foil',
|
|
@@ -73,44 +95,24 @@ await jsonToPDF(json, './output.pdf', {
|
|
|
73
95
|
});
|
|
74
96
|
```
|
|
75
97
|
|
|
76
|
-
|
|
98
|
+
Any element with a matching fill or stroke color is automatically converted. Color matching is flexible — `'#FFD700'`, `'#ffd700'`, `'rgb(255,215,0)'`, and `'rgba(255,215,0,1)'` all match. CMYK fallback values are used by viewers that don't support spot colors.
|
|
77
99
|
|
|
78
|
-
|
|
79
|
-
- Colors are matched flexibly — `'#FFD700'`, `'#ffd700'`, and `'rgba(255,215,0,1)'` all match
|
|
80
|
-
- Spot colors are preserved as Separation color spaces in both standard and PDF/X-1a output
|
|
81
|
-
- CMYK fallback values are used when viewers don't support spot colors
|
|
82
|
-
- Supported on text, lines, figures (rect, circle, star, blobs, and other subTypes), and SVG elements
|
|
83
|
-
|
|
84
|
-
**Color Format Support:**
|
|
85
|
-
|
|
86
|
-
- Hex: `'#FFD700'` or `'#ffd700'`
|
|
87
|
-
- RGB: `'rgb(255,215,0)'`
|
|
88
|
-
- RGBA: `'rgba(255,215,0,1)'`
|
|
100
|
+
Supported on text, lines, figures, and SVG elements.
|
|
89
101
|
|
|
90
102
|
### Overprint
|
|
91
103
|
|
|
92
|
-
`overprint: true` emits the PDF graphics state `/OP true /op true /OPM 1` whenever the spot color is painted
|
|
93
|
-
|
|
94
|
-
- **Without overprint** (default): the spot region knocks out CMYK underneath. If the foil die is misaligned by even a fraction of a millimetre, you get a visible white halo at the edge.
|
|
95
|
-
- **With overprint**: CMYK prints continuously through the spot region; the foil is stamped over it. No halo at edges, and the underlying artwork stays intact.
|
|
96
|
-
|
|
97
|
-
Overprint is the standard choice for foil stamps and varnishes. Acrobat's *Output Preview → Simulate Overprinting* shows how the press will composite the layers.
|
|
98
|
-
|
|
99
|
-
**Tips:**
|
|
104
|
+
`overprint: true` emits the PDF graphics state `/OP true /op true /OPM 1` whenever the spot color is painted, telling the press to **add** the spot ink on top of any underlying CMYK rather than knocking it out:
|
|
100
105
|
|
|
101
|
-
-
|
|
102
|
-
-
|
|
103
|
-
- Verify spot colors in Adobe Acrobat Pro: **Tools → Print Production → Output Preview → Separations**
|
|
106
|
+
- **Without overprint** (default): the spot region knocks out CMYK underneath. A misaligned foil die produces a visible white halo at the edge.
|
|
107
|
+
- **With overprint**: CMYK prints continuously through the spot region; the foil is stamped over it. No halo, underlying artwork stays intact.
|
|
104
108
|
|
|
105
|
-
**
|
|
109
|
+
Acrobat's *Output Preview → Simulate Overprinting* shows how the press will composite the layers. Verify spot colors in **Tools → Print Production → Output Preview → Separations**.
|
|
106
110
|
|
|
107
|
-
|
|
111
|
+
For foil regions, prefer plain shapes or simple SVG paths — SVGs that internally use `<clipPath>` or `<mask>` may have their spot color flattened to CMYK during PDF/X-1a conversion.
|
|
108
112
|
|
|
109
|
-
## Bleed and
|
|
113
|
+
## Bleed and crop marks
|
|
110
114
|
|
|
111
|
-
For print production you usually want extra paper around the trim edge so
|
|
112
|
-
that ink covers the cut line, plus crop marks indicating where the print
|
|
113
|
-
shop should trim:
|
|
115
|
+
For print production you usually want extra paper around the trim edge so that ink covers the cut line, plus crop marks for the print shop:
|
|
114
116
|
|
|
115
117
|
```js
|
|
116
118
|
await jsonToPDF(json, './print-ready.pdf', {
|
|
@@ -120,78 +122,40 @@ await jsonToPDF(json, './print-ready.pdf', {
|
|
|
120
122
|
});
|
|
121
123
|
```
|
|
122
124
|
|
|
123
|
-
|
|
125
|
+
Set `bleed` in pixels on each page in the JSON:
|
|
124
126
|
|
|
125
127
|
```js
|
|
126
|
-
{
|
|
127
|
-
width: 1080,
|
|
128
|
-
height: 1080,
|
|
129
|
-
pages: [{ background: '...', bleed: 36, children: [...] }]
|
|
130
|
-
}
|
|
128
|
+
{ width: 1080, height: 1080, pages: [{ background: '...', bleed: 36, children: [...] }] }
|
|
131
129
|
```
|
|
132
130
|
|
|
133
|
-
**
|
|
134
|
-
generated PDF has correct PDF/X page boxes:
|
|
135
|
-
|
|
136
|
-
- `MediaBox` — full sheet (trim + bleed + crop-mark margin)
|
|
137
|
-
- `BleedBox` — trim + bleed (where ink may cover)
|
|
138
|
-
- `TrimBox` — final cut size
|
|
139
|
-
- `ArtBox` — same as `TrimBox`
|
|
131
|
+
Element coordinates stay **relative to the trim corner** — `(0, 0)` still lands at the top-left of the trim. Backgrounds automatically extend into the bleed strip.
|
|
140
132
|
|
|
141
|
-
|
|
142
|
-
identify the live area and the bleed strip without guessing.
|
|
133
|
+
The output PDF gets correct PDF/X page boxes (`MediaBox` ⊇ `BleedBox` ⊇ `TrimBox` = `ArtBox`) so RIPs and proofers can identify the live area without guessing.
|
|
143
134
|
|
|
144
|
-
|
|
145
|
-
**relative to the trim corner** — the renderer translates origin so a
|
|
146
|
-
`(0, 0)` element still lands at the top-left of the trim. Backgrounds
|
|
147
|
-
automatically extend into the bleed strip; the crop-mark margin remains
|
|
148
|
-
unprinted.
|
|
135
|
+
Works on both Node and browser entries — no Ghostscript required if you skip `pdfx1a`.
|
|
149
136
|
|
|
150
|
-
## DPI
|
|
137
|
+
## DPI handling
|
|
151
138
|
|
|
152
|
-
The library
|
|
139
|
+
The library converts pixel coordinates to PDF points using the `dpi` value from your JSON (default 72):
|
|
153
140
|
|
|
154
141
|
```js
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
height: 1080,
|
|
159
|
-
dpi: 300, // 300 DPI input
|
|
160
|
-
// ... rest of JSON
|
|
161
|
-
};
|
|
162
|
-
|
|
163
|
-
// Use JSON dpi automatically
|
|
142
|
+
const json = { width: 1920, height: 1080, dpi: 300, /* ... */ };
|
|
143
|
+
|
|
144
|
+
// Uses JSON dpi automatically
|
|
164
145
|
await jsonToPDF(json, './output.pdf');
|
|
165
146
|
|
|
166
|
-
//
|
|
167
|
-
await jsonToPDF(json, './output.pdf', {
|
|
168
|
-
dpi: 150, // Override to 150 DPI
|
|
169
|
-
});
|
|
147
|
+
// Or override
|
|
148
|
+
await jsonToPDF(json, './output.pdf', { dpi: 150 });
|
|
170
149
|
```
|
|
171
150
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
- Input JSON coordinates are in **pixels** at the specified DPI
|
|
175
|
-
- PDF uses **points** (1 point = 1/72 inch)
|
|
176
|
-
- The library converts: `points = pixels × (72 / dpi)`
|
|
177
|
-
- This ensures the PDF has correct physical dimensions for printing
|
|
178
|
-
- All element positions, sizes, and coordinates are automatically scaled
|
|
179
|
-
|
|
180
|
-
**Example:**
|
|
181
|
-
|
|
182
|
-
- A 1920×1080 pixel canvas at 300 DPI = 6.4" × 3.6" in the PDF
|
|
183
|
-
- The same canvas at 72 DPI = 26.67" × 15" in the PDF
|
|
184
|
-
|
|
185
|
-
## Requirements
|
|
186
|
-
|
|
187
|
-
- **GhostScript** must be installed for PDF/X-1a conversion
|
|
188
|
-
- macOS: `brew install ghostscript`
|
|
189
|
-
- Ubuntu: `apt-get install ghostscript`
|
|
190
|
-
- Windows: Download from [ghostscript.com](https://www.ghostscript.com/)
|
|
151
|
+
A 1920×1080 canvas at 300 DPI is 6.4″ × 3.6″ in the PDF; at 72 DPI it's 26.67″ × 15″.
|
|
191
152
|
|
|
192
153
|
## Development
|
|
193
154
|
|
|
194
155
|
```bash
|
|
195
|
-
npm
|
|
196
|
-
npm
|
|
156
|
+
npm install # postinstall applies patches/ to node_modules
|
|
157
|
+
npm run build # builds the Node and browser bundles
|
|
158
|
+
npm test # Node-side fixture tests
|
|
159
|
+
npm run test:browser # real-Chromium smoke suite (Vitest browser mode)
|
|
160
|
+
npm run dev # demo Polotno editor for manual export testing
|
|
197
161
|
```
|
package/lib/browser.d.ts
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
interface SpotColorDefinition {
|
|
2
|
+
name?: string;
|
|
3
|
+
cmyk?: number[];
|
|
4
|
+
/**
|
|
5
|
+
* If true, the spot color is painted with overprint enabled
|
|
6
|
+
* (PDF graphics state /OP true /op true /OPM 1). This prevents
|
|
7
|
+
* underlying inks from being knocked out, so the spot ink mixes
|
|
8
|
+
* with the colors beneath it on press. Typically used for
|
|
9
|
+
* varnishes, foils and Pantone overlays.
|
|
10
|
+
*/
|
|
11
|
+
overprint?: boolean;
|
|
12
|
+
}
|
|
13
|
+
interface SpotColorConfig {
|
|
14
|
+
[color: string]: SpotColorDefinition;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* One CSS @font-face style variant for a custom font. Mirrors Polotno's
|
|
19
|
+
* `FontStyleVariant` (`node_modules/polotno/model/schema.d.ts`): `src` is
|
|
20
|
+
* the CSS-style source string (e.g. `url("https://example.com/foo.woff2")`),
|
|
21
|
+
* and `fontStyle`/`fontWeight` qualify which (style, weight) the variant
|
|
22
|
+
* is for. Missing means normal/400.
|
|
23
|
+
*/
|
|
24
|
+
interface FontStyleVariant {
|
|
25
|
+
src: string;
|
|
26
|
+
fontStyle?: string;
|
|
27
|
+
fontWeight?: string;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Custom font definition. Three valid shapes (matching polotno):
|
|
31
|
+
* 1. `{ fontFamily }` alone → fetched from Google Fonts per variant
|
|
32
|
+
* 2. `{ fontFamily, url }` → one URL applied to all variants (regular
|
|
33
|
+
* file used for bold/italic; not ideal but matches polotno fallback)
|
|
34
|
+
* 3. `{ fontFamily, styles: [...] }` → per-variant URLs (preferred)
|
|
35
|
+
*/
|
|
36
|
+
interface FontDefinition {
|
|
37
|
+
fontFamily: string;
|
|
38
|
+
url?: string;
|
|
39
|
+
styles?: FontStyleVariant[];
|
|
40
|
+
}
|
|
41
|
+
interface PolotnoJSON {
|
|
42
|
+
width: number;
|
|
43
|
+
height: number;
|
|
44
|
+
fonts: FontDefinition[];
|
|
45
|
+
pages: Array<{
|
|
46
|
+
background?: string;
|
|
47
|
+
children: any[];
|
|
48
|
+
}>;
|
|
49
|
+
dpi?: number;
|
|
50
|
+
}
|
|
51
|
+
interface RenderAttrs {
|
|
52
|
+
pdfx1a?: boolean;
|
|
53
|
+
validate?: boolean;
|
|
54
|
+
metadata?: {
|
|
55
|
+
title?: string;
|
|
56
|
+
author?: string;
|
|
57
|
+
application?: string;
|
|
58
|
+
producer?: string;
|
|
59
|
+
};
|
|
60
|
+
spotColors?: SpotColorConfig;
|
|
61
|
+
/**
|
|
62
|
+
* @deprecated No-op. Text always shrinks to fit `element.height` and
|
|
63
|
+
* `verticalAlign` is always honored — both are properties of the polotno
|
|
64
|
+
* element model, not render-time options. This field is kept for
|
|
65
|
+
* backwards compatibility with callers that used to pass it.
|
|
66
|
+
*/
|
|
67
|
+
textVerticalResizeEnabled?: boolean;
|
|
68
|
+
dpi?: number;
|
|
69
|
+
/**
|
|
70
|
+
* When true, expand each page's PDF dimensions to include the per-page
|
|
71
|
+
* `bleed` value (read from `page.bleed` in the JSON, in pixels). Background
|
|
72
|
+
* and content drawn outside the trim area are preserved into the bleed
|
|
73
|
+
* strip — exactly matching what print shops expect.
|
|
74
|
+
*/
|
|
75
|
+
includeBleed?: boolean;
|
|
76
|
+
/**
|
|
77
|
+
* Reserve `cropMarkSize` pixels of additional margin around each page and
|
|
78
|
+
* draw 8 corner crop marks at the trim line (matching polotno's bitmap
|
|
79
|
+
* PDF export). 0 (default) = no crop marks. Independent of `includeBleed`
|
|
80
|
+
* — a print-ready PDF typically wants both.
|
|
81
|
+
*/
|
|
82
|
+
cropMarkSize?: number;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Render `json` and return the resulting PDF as raw bytes. Works on both
|
|
86
|
+
* Node and browser (the browser entry re-exports this with the browser
|
|
87
|
+
* platform shim swapped in by the bundler). PDF/X-1a is rejected — that
|
|
88
|
+
* conversion needs Ghostscript and a real file path.
|
|
89
|
+
*/
|
|
90
|
+
declare function jsonToPDFBytes(json: PolotnoJSON, attrs?: RenderAttrs): Promise<Uint8Array>;
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Render `json` into a PDF and return it as a `Blob` ready for download
|
|
94
|
+
* (`URL.createObjectURL`), upload, or `navigator.share`.
|
|
95
|
+
*
|
|
96
|
+
* The `Blob`'s MIME type is `application/pdf`. PDF/X-1a (`attrs.pdfx1a`)
|
|
97
|
+
* is unsupported in browsers — pass it and you'll get a clear runtime
|
|
98
|
+
* error pointing you at the Node entry.
|
|
99
|
+
*/
|
|
100
|
+
declare function jsonToPDFBlob(json: PolotnoJSON, attrs?: RenderAttrs): Promise<Blob>;
|
|
101
|
+
|
|
102
|
+
export { type FontDefinition, type FontStyleVariant, type PolotnoJSON, type RenderAttrs, type SpotColorConfig, jsonToPDFBlob, jsonToPDFBytes };
|