@polotno/pdf-export 0.1.35 → 0.1.36

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
@@ -94,6 +94,41 @@ await jsonToPDF(json, './output.pdf', {
94
94
  - Spot colors work best with PDF/X-1a export enabled
95
95
  - You can verify spot colors in Adobe Acrobat by checking Output Preview > Separations
96
96
 
97
+ ## DPI Handling
98
+
99
+ The library automatically handles DPI conversion to ensure correct physical dimensions in the output PDF. By default, it uses the `dpi` value from your JSON file (or 72 DPI if not specified).
100
+
101
+ ```js
102
+ // JSON with dpi specified
103
+ const json = {
104
+ width: 1920,
105
+ height: 1080,
106
+ dpi: 300, // 300 DPI input
107
+ // ... rest of JSON
108
+ };
109
+
110
+ // Use JSON dpi automatically
111
+ await jsonToPDF(json, './output.pdf');
112
+
113
+ // Override DPI via attrs (takes precedence over JSON dpi)
114
+ await jsonToPDF(json, './output.pdf', {
115
+ dpi: 150, // Override to 150 DPI
116
+ });
117
+ ```
118
+
119
+ **How it works:**
120
+
121
+ - Input JSON coordinates are in **pixels** at the specified DPI
122
+ - PDF uses **points** (1 point = 1/72 inch)
123
+ - The library converts: `points = pixels × (72 / dpi)`
124
+ - This ensures the PDF has correct physical dimensions for printing
125
+ - All element positions, sizes, and coordinates are automatically scaled
126
+
127
+ **Example:**
128
+
129
+ - A 1920×1080 pixel canvas at 300 DPI = 6.4" × 3.6" in the PDF
130
+ - The same canvas at 72 DPI = 26.67" × 15" in the PDF
131
+
97
132
  ## Requirements
98
133
 
99
134
  - **GhostScript** must be installed for PDF/X-1a conversion
package/lib/index.d.ts CHANGED
@@ -10,6 +10,7 @@ export interface PolotnoJSON {
10
10
  background?: string;
11
11
  children: any[];
12
12
  }>;
13
+ dpi?: number;
13
14
  }
14
15
  export interface RenderAttrs {
15
16
  pdfx1a?: boolean;
@@ -22,5 +23,6 @@ export interface RenderAttrs {
22
23
  };
23
24
  spotColors?: SpotColorConfig;
24
25
  textVerticalResizeEnabled?: boolean;
26
+ dpi?: number;
25
27
  }
26
28
  export declare function jsonToPDF(json: PolotnoJSON, pdfFileName: string, attrs?: RenderAttrs): Promise<void>;
package/lib/index.js CHANGED
@@ -51,6 +51,13 @@ async function renderElement({ doc, element, fonts, attrs, cache, }) {
51
51
  }
52
52
  export async function jsonToPDF(json, pdfFileName, attrs = {}) {
53
53
  const fonts = {};
54
+ // Compute DPI and scale factor
55
+ // Priority: attrs.dpi (override) > json.dpi > 72 (default)
56
+ const inputDpi = attrs.dpi ?? json.dpi ?? 72;
57
+ // Validate DPI: must be finite and positive
58
+ const validDpi = Number.isFinite(inputDpi) && inputDpi > 0 ? inputDpi : 72;
59
+ // Convert pixels to PDF points: 1 point = 1/72 inch, so points per pixel = 72 / dpi
60
+ const ptPerPx = 72 / validDpi;
54
61
  // Create cache for images and processed results
55
62
  const cache = {
56
63
  images: new Map(), // Cache for loaded Canvas images
@@ -60,7 +67,7 @@ export async function jsonToPDF(json, pdfFileName, attrs = {}) {
60
67
  tempDir: null, // Temporary directory for image files
61
68
  };
62
69
  var doc = new PDFDocument({
63
- size: [json.width, json.height],
70
+ size: [json.width * ptPerPx, json.height * ptPerPx],
64
71
  autoFirstPage: false,
65
72
  });
66
73
  // Enable spot color support if configured
@@ -73,6 +80,10 @@ export async function jsonToPDF(json, pdfFileName, attrs = {}) {
73
80
  }
74
81
  for (const page of json.pages) {
75
82
  doc.addPage();
83
+ // Apply scale transform so all pixel-based coordinates work correctly
84
+ // The page size is already in points, but we render in pixel coordinates
85
+ doc.save();
86
+ doc.scale(ptPerPx);
76
87
  if (page.background) {
77
88
  const isURL = page.background.indexOf('http') >= 0 ||
78
89
  page.background.indexOf('.png') >= 0 ||
@@ -91,6 +102,7 @@ export async function jsonToPDF(json, pdfFileName, attrs = {}) {
91
102
  for (const element of page.children) {
92
103
  await renderElement({ doc, element, fonts, attrs, cache });
93
104
  }
105
+ doc.restore();
94
106
  }
95
107
  doc.end();
96
108
  await new Promise((r) => doc.pipe(fs.createWriteStream(pdfFileName)).on('finish', r));
@@ -186,13 +186,14 @@ export async function renderTextFill(doc, element, textLines, yOffset, lineHeigh
186
186
  const renderSegments = await buildRenderSegmentsForLine(doc, element, line.text, textOptions, fonts);
187
187
  // Apply segment-specific colors for rich text
188
188
  const applySegmentColor = (segment) => {
189
- const segmentColor = segment.color
190
- ? parseColor(segment.color).hex
191
- : parseColor(element.fill).hex;
192
189
  const segmentParsedColor = segment.color
193
190
  ? parseColor(segment.color)
194
191
  : parseColor(element.fill);
195
- const segmentOpacity = Math.min(segmentParsedColor.rgba[3] ?? 1, element.opacity, 1);
192
+ // Fallback to element fill if segment color parsing fails
193
+ const segmentColor = segmentParsedColor?.hex || parseColor(element.fill).hex || '#000000';
194
+ // Segment alpha can be NaN (e.g. rgba(..., var(--x, 1))) -> fallback to 1
195
+ const a = segmentParsedColor?.rgba?.[3];
196
+ const segmentOpacity = Math.min(typeof a === 'number' && a >= 0 && a <= 1 ? a : 1, element.opacity, 1);
196
197
  doc.fillColor(segmentColor, segmentOpacity);
197
198
  };
198
199
  await renderSegmentsForLine(doc, element, line, renderSegments, context, textOptions, {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@polotno/pdf-export",
3
- "version": "0.1.35",
3
+ "version": "0.1.36",
4
4
  "description": "Convert Polotno JSON into vector PDF",
5
5
  "type": "module",
6
6
  "main": "./lib/index.js",