@polotno/pdf-export 0.1.37 → 0.1.38

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@polotno/pdf-export",
3
- "version": "0.1.37",
3
+ "version": "0.1.38",
4
4
  "description": "Convert Polotno JSON into vector PDF",
5
5
  "type": "module",
6
6
  "main": "./lib/index.js",
@@ -1,7 +0,0 @@
1
- import type { PolotnoJSON, RenderAttrs } from './core/index.js';
2
- export type { PolotnoJSON, RenderAttrs } from './core/index.js';
3
- export interface BrowserExportOptions {
4
- fileName?: string;
5
- attrs?: RenderAttrs;
6
- }
7
- export declare function jsonToPDF(json: PolotnoJSON, options?: BrowserExportOptions): Promise<Blob>;
@@ -1,11 +0,0 @@
1
- import { setPlatformAdapter, hasPlatformAdapter } from './platform/adapter.js';
2
- import { createBrowserAdapter } from './platform/browser.js';
3
- import { jsonToPDF as coreJsonToPDF } from './core/index.js';
4
- if (!hasPlatformAdapter()) {
5
- setPlatformAdapter(createBrowserAdapter());
6
- }
7
- export async function jsonToPDF(json, options = {}) {
8
- const { fileName, attrs } = options;
9
- const result = await coreJsonToPDF(json, fileName, attrs ?? {});
10
- return result;
11
- }
@@ -1,26 +0,0 @@
1
- import { SpotColorConfig } from '../spot-colors.js';
2
- export interface PolotnoJSON {
3
- width: number;
4
- height: number;
5
- fonts: Array<{
6
- fontFamily: string;
7
- url: string;
8
- }>;
9
- pages: Array<{
10
- background?: string;
11
- children: any[];
12
- }>;
13
- }
14
- export interface RenderAttrs {
15
- pdfx1a?: boolean;
16
- validate?: boolean;
17
- metadata?: {
18
- title?: string;
19
- author?: string;
20
- application?: string;
21
- producer?: string;
22
- };
23
- spotColors?: SpotColorConfig;
24
- textVerticalResizeEnabled?: boolean;
25
- }
26
- export declare function jsonToPDF(json: PolotnoJSON, pdfFileName?: string, attrs?: RenderAttrs): Promise<Blob | Uint8Array | undefined>;
package/lib/core/index.js DELETED
@@ -1,87 +0,0 @@
1
- import { srcToBuffer, parseColor } from '../utils.js';
2
- import { renderImage } from '../image.js';
3
- import { loadFontIfNeeded, renderText } from '../text.js';
4
- import { renderFigure } from '../figure.js';
5
- import { renderGroup } from '../group.js';
6
- import { lineToPDF } from '../line.js';
7
- import { renderSVG } from '../svg-render.js';
8
- import { enableSpotColorSupport } from '../spot-colors.js';
9
- import { getPlatformAdapter } from '../platform/adapter.js';
10
- async function renderElement({ doc, element, fonts, attrs, cache, }) {
11
- if ((element.visible !== undefined && !element.visible) ||
12
- (element.showInExport !== undefined && !element.showInExport)) {
13
- return;
14
- }
15
- doc.save();
16
- if (element.type !== 'group') {
17
- doc.translate(element.x, element.y);
18
- doc.rotate(element.rotation);
19
- }
20
- if (element.opacity !== undefined) {
21
- doc.opacity(element.opacity);
22
- }
23
- if (element.type === 'group') {
24
- await renderGroup(doc, element, renderElement, fonts, attrs, cache);
25
- }
26
- else if (element.type === 'text') {
27
- await loadFontIfNeeded(doc, element, fonts);
28
- await renderText(doc, element, fonts, attrs);
29
- }
30
- else if (element.type === 'line') {
31
- lineToPDF(doc, element);
32
- }
33
- else if (element.type === 'image') {
34
- await renderImage(doc, element, cache);
35
- }
36
- else if (element.type === 'svg') {
37
- await renderSVG(doc, element, cache);
38
- }
39
- else if (element.type === 'figure') {
40
- renderFigure(doc, element);
41
- }
42
- doc.restore();
43
- }
44
- export async function jsonToPDF(json, pdfFileName, attrs = {}) {
45
- const fonts = {};
46
- const cache = {
47
- images: new Map(),
48
- buffers: new Map(),
49
- processedImages: new Map(),
50
- };
51
- const adapter = getPlatformAdapter();
52
- const { doc, finalize } = adapter.createPDFContext({
53
- size: [json.width, json.height],
54
- autoFirstPage: false,
55
- });
56
- if (attrs.spotColors) {
57
- enableSpotColorSupport(doc, attrs.spotColors);
58
- }
59
- for (const font of json.fonts) {
60
- await adapter.registerFont(doc, font.fontFamily, await srcToBuffer(font.url, cache));
61
- fonts[font.fontFamily] = true;
62
- }
63
- for (const page of json.pages) {
64
- doc.addPage();
65
- if (page.background) {
66
- const isURL = page.background.indexOf('http') >= 0 ||
67
- page.background.indexOf('.png') >= 0 ||
68
- page.background.indexOf('.jpg') >= 0;
69
- if (isURL) {
70
- const mime = cache.buffers.get(`mime:${page.background}`);
71
- await adapter.embedImage(doc, await srcToBuffer(page.background, cache), 0, 0, {
72
- cover: [json.width, json.height],
73
- align: 'center',
74
- }, mime);
75
- }
76
- else {
77
- doc.rect(0, 0, json.width, json.height);
78
- doc.fill(parseColor(page.background).hex);
79
- }
80
- }
81
- for (const element of page.children) {
82
- await renderElement({ doc, element, fonts, attrs, cache });
83
- }
84
- }
85
- doc.end();
86
- return finalize({ fileName: pdfFileName, attrs });
87
- }
@@ -1,37 +0,0 @@
1
- export interface FetchResponse {
2
- ok: boolean;
3
- status: number;
4
- statusText: string;
5
- headers: Headers;
6
- arrayBuffer(): Promise<ArrayBuffer>;
7
- text(): Promise<string>;
8
- json(): Promise<any>;
9
- }
10
- export interface CanvasLike {
11
- width: number;
12
- height: number;
13
- getContext(type: '2d'): any;
14
- toDataURL(type?: string, quality?: any): string;
15
- }
16
- export interface ImageLike {
17
- width: number;
18
- height: number;
19
- [key: string]: any;
20
- }
21
- export interface PDFContext<TOutput = unknown, TFinalizeOptions = unknown> {
22
- doc: any;
23
- finalize(options?: TFinalizeOptions): Promise<TOutput>;
24
- }
25
- export interface PlatformAdapter<TOutput = unknown, TFinalizeOptions = unknown> {
26
- fetch(input: string, init?: RequestInit): Promise<FetchResponse>;
27
- loadImage(src: string): Promise<ImageLike>;
28
- createCanvas(width: number, height: number): CanvasLike;
29
- createPDFContext(options: any): PDFContext<TOutput, TFinalizeOptions>;
30
- registerFont(doc: any, name: string, data: Uint8Array): void | Promise<void>;
31
- encodeBase64(data: Uint8Array): string;
32
- decodeBase64(value: string): Uint8Array;
33
- embedImage(doc: any, data: Uint8Array | string, x: number, y: number, options: any, mimeType?: string): void | Promise<void>;
34
- }
35
- export declare function setPlatformAdapter(adapter: PlatformAdapter): void;
36
- export declare function hasPlatformAdapter(): boolean;
37
- export declare function getPlatformAdapter(): PlatformAdapter;
@@ -1,13 +0,0 @@
1
- let currentAdapter = null;
2
- export function setPlatformAdapter(adapter) {
3
- currentAdapter = adapter;
4
- }
5
- export function hasPlatformAdapter() {
6
- return currentAdapter !== null;
7
- }
8
- export function getPlatformAdapter() {
9
- if (!currentAdapter) {
10
- throw new Error('Platform adapter is not configured.');
11
- }
12
- return currentAdapter;
13
- }
@@ -1,5 +0,0 @@
1
- const globalScope = globalThis;
2
- if (typeof globalScope.global === 'undefined') {
3
- globalScope.global = globalScope;
4
- }
5
- export {};
@@ -1,7 +0,0 @@
1
- import type { PlatformAdapter } from './adapter.js';
2
- interface FinalizeOptions {
3
- fileName?: string;
4
- attrs?: any;
5
- }
6
- export declare function createBrowserAdapter(): PlatformAdapter<Blob, FinalizeOptions>;
7
- export {};
@@ -1,145 +0,0 @@
1
- import PDFDocument from 'pdfkit/js/pdfkit.standalone.js';
2
- import SVGtoPDF from 'svg-to-pdfkit';
3
- import { Buffer } from 'buffer';
4
- function attachAddSVG(doc) {
5
- const existing = doc.addSVG;
6
- if (!existing) {
7
- doc.addSVG = function (svg, x, y, options) {
8
- SVGtoPDF(this, svg, x, y, options);
9
- return this;
10
- };
11
- }
12
- }
13
- function arrayBufferToBase64(buffer) {
14
- let binary = '';
15
- const chunkSize = 8192;
16
- for (let i = 0; i < buffer.length; i += chunkSize) {
17
- const chunk = buffer.subarray(i, i + chunkSize);
18
- binary += String.fromCharCode(...chunk);
19
- }
20
- return btoa(binary);
21
- }
22
- function base64ToUint8Array(value) {
23
- const binary = atob(value);
24
- const bytes = new Uint8Array(binary.length);
25
- for (let i = 0; i < binary.length; i++) {
26
- bytes[i] = binary.charCodeAt(i);
27
- }
28
- return bytes;
29
- }
30
- function loadImageElement(src) {
31
- return new Promise((resolve, reject) => {
32
- const img = new Image();
33
- if (!src.startsWith('data:')) {
34
- img.crossOrigin = 'anonymous';
35
- }
36
- img.onload = () => resolve(img);
37
- img.onerror = (error) => reject(error);
38
- img.src = src;
39
- });
40
- }
41
- async function loadImageFromSource(src) {
42
- if (src.startsWith('data:')) {
43
- return loadImageElement(src);
44
- }
45
- const fetchImpl = globalThis.fetch?.bind(globalThis);
46
- if (!fetchImpl) {
47
- throw new Error('Global fetch is not available in this environment.');
48
- }
49
- const response = await fetchImpl(src, { mode: 'cors' });
50
- if (!response.ok) {
51
- throw new Error(`HTTP ${response.status}: ${response.statusText}`);
52
- }
53
- const blob = await response.blob();
54
- const objectUrl = URL.createObjectURL(blob);
55
- try {
56
- return await loadImageElement(objectUrl);
57
- }
58
- finally {
59
- URL.revokeObjectURL(objectUrl);
60
- }
61
- }
62
- export function createBrowserAdapter() {
63
- return {
64
- async fetch(input, init) {
65
- const fetchImpl = globalThis.fetch?.bind(globalThis);
66
- if (!fetchImpl) {
67
- throw new Error('Global fetch is not available in this environment.');
68
- }
69
- return (await fetchImpl(input, init));
70
- },
71
- async loadImage(src) {
72
- return loadImageFromSource(src);
73
- },
74
- createCanvas(width, height) {
75
- const canvas = document.createElement('canvas');
76
- canvas.width = width;
77
- canvas.height = height;
78
- return canvas;
79
- },
80
- createPDFContext(options) {
81
- const doc = new PDFDocument(options);
82
- attachAddSVG(doc);
83
- const blobParts = [];
84
- const appendTypedArray = (view) => {
85
- blobParts.push(view.slice());
86
- };
87
- doc.on('data', (chunk) => {
88
- if (chunk instanceof Uint8Array) {
89
- appendTypedArray(chunk);
90
- }
91
- else if (Array.isArray(chunk)) {
92
- appendTypedArray(new Uint8Array(chunk));
93
- }
94
- else if (typeof chunk === 'string') {
95
- const encoder = new TextEncoder();
96
- appendTypedArray(encoder.encode(chunk));
97
- }
98
- else if (chunk?.buffer instanceof ArrayBuffer) {
99
- appendTypedArray(new Uint8Array(chunk.buffer));
100
- }
101
- });
102
- const resultPromise = new Promise((resolve, reject) => {
103
- doc.on('end', () => {
104
- try {
105
- resolve(new Blob(blobParts, { type: 'application/pdf' }));
106
- }
107
- catch (error) {
108
- reject(error);
109
- }
110
- });
111
- doc.on('error', reject);
112
- });
113
- const finalize = async (options) => {
114
- if (options?.attrs?.pdfx1a) {
115
- console.warn('PDF/X-1a conversion is not supported in the browser runtime.');
116
- }
117
- return resultPromise;
118
- };
119
- return {
120
- doc,
121
- finalize,
122
- };
123
- },
124
- registerFont(doc, name, data) {
125
- const buffer = Buffer.from(data.buffer, data.byteOffset, data.byteLength);
126
- doc.registerFont(name, buffer);
127
- },
128
- encodeBase64(data) {
129
- return arrayBufferToBase64(data);
130
- },
131
- decodeBase64(value) {
132
- return base64ToUint8Array(value);
133
- },
134
- embedImage(doc, data, x, y, options, mimeType) {
135
- if (typeof data === 'string') {
136
- doc.image(data, x, y, options);
137
- return;
138
- }
139
- const mime = mimeType || 'image/png';
140
- const base64 = arrayBufferToBase64(data);
141
- const dataUrl = `data:${mime};base64,${base64}`;
142
- doc.image(dataUrl, x, y, options);
143
- },
144
- };
145
- }
@@ -1,7 +0,0 @@
1
- import type { PlatformAdapter } from './adapter.js';
2
- interface FinalizeOptions {
3
- fileName?: string;
4
- attrs?: any;
5
- }
6
- export declare function createNodeAdapter(): PlatformAdapter<Uint8Array, FinalizeOptions>;
7
- export {};
@@ -1,142 +0,0 @@
1
- import fs from 'fs';
2
- import { Buffer } from 'buffer';
3
- import { PassThrough } from 'stream';
4
- import PDFDocument from 'pdfkit';
5
- import SVGtoPDF from 'svg-to-pdfkit';
6
- import Canvas from 'canvas';
7
- import sharp from 'sharp';
8
- import nodeFetch from 'node-fetch';
9
- import { fileTypeFromBuffer } from 'file-type';
10
- import { convertToPDFX1a, validatePDFX1a } from '../ghostscript.js';
11
- function ensureBuffer(data) {
12
- return Buffer.isBuffer(data) ? data : Buffer.from(data);
13
- }
14
- function getFetch() {
15
- if (typeof globalThis.fetch === 'function') {
16
- return globalThis.fetch.bind(globalThis);
17
- }
18
- return nodeFetch;
19
- }
20
- async function convertToPNG(buffer, mime) {
21
- if (mime === 'image/png' || mime === 'image/jpeg') {
22
- return buffer;
23
- }
24
- // Fall back to PNG conversion for other formats
25
- return sharp(buffer).toFormat('png').toBuffer();
26
- }
27
- function attachAddSVG(doc) {
28
- const existing = doc.addSVG;
29
- if (!existing) {
30
- doc.addSVG = function (svg, x, y, options) {
31
- SVGtoPDF(this, svg, x, y, options);
32
- return this;
33
- };
34
- }
35
- }
36
- async function writeBufferToFile(fileName, buffer) {
37
- await fs.promises.writeFile(fileName, buffer);
38
- }
39
- export function createNodeAdapter() {
40
- const fetchImpl = getFetch();
41
- return {
42
- async fetch(input, init) {
43
- return (await fetchImpl(input, init));
44
- },
45
- async loadImage(src) {
46
- let buffer;
47
- let mime;
48
- if (src.startsWith('data:')) {
49
- const matches = src.match(/^data:(.+);base64,(.*)$/);
50
- if (!matches) {
51
- throw new Error('Invalid data URL');
52
- }
53
- mime = matches[1];
54
- buffer = Buffer.from(matches[2], 'base64');
55
- }
56
- else {
57
- const response = await fetchImpl(src);
58
- if (!response.ok) {
59
- throw new Error(`HTTP ${response.status}: ${response.statusText}`);
60
- }
61
- const arrayBuffer = await response.arrayBuffer();
62
- buffer = Buffer.from(arrayBuffer);
63
- const typeInfo = await fileTypeFromBuffer(buffer);
64
- if (typeInfo) {
65
- mime = typeInfo.mime;
66
- }
67
- }
68
- const normalizedBuffer = await convertToPNG(buffer, mime);
69
- return Canvas.loadImage(normalizedBuffer);
70
- },
71
- createCanvas(width, height) {
72
- return Canvas.createCanvas(width, height);
73
- },
74
- createPDFContext(options) {
75
- const doc = new PDFDocument(options);
76
- attachAddSVG(doc);
77
- const stream = doc.pipe(new PassThrough());
78
- const chunks = [];
79
- stream.on('data', (chunk) => {
80
- chunks.push(Buffer.from(chunk));
81
- });
82
- const resultPromise = new Promise((resolve, reject) => {
83
- stream.on('end', () => resolve(Buffer.concat(chunks)));
84
- stream.on('error', reject);
85
- doc.on('error', reject);
86
- });
87
- const finalize = async (options) => {
88
- const buffer = await resultPromise;
89
- const outputBuffer = Buffer.from(buffer);
90
- if (options?.fileName) {
91
- const targetFile = options.fileName;
92
- await writeBufferToFile(targetFile, outputBuffer);
93
- if (options.attrs?.pdfx1a) {
94
- const tempFile = targetFile.replace('.pdf', '-temp.pdf');
95
- await fs.promises.rename(targetFile, tempFile);
96
- try {
97
- await convertToPDFX1a(tempFile, targetFile, {
98
- metadata: options.attrs.metadata || {},
99
- });
100
- await fs.promises.unlink(tempFile);
101
- if (options.attrs.validate) {
102
- const isValid = await validatePDFX1a(targetFile);
103
- if (!isValid) {
104
- console.warn('Warning: Generated PDF may not be fully PDF/X-1a compliant');
105
- }
106
- }
107
- }
108
- catch (error) {
109
- if (fs.existsSync(tempFile)) {
110
- await fs.promises.rename(tempFile, targetFile);
111
- }
112
- throw new Error(`PDF/X-1a conversion failed: ${error.message}`);
113
- }
114
- }
115
- }
116
- return new Uint8Array(outputBuffer);
117
- };
118
- return {
119
- doc,
120
- finalize,
121
- };
122
- },
123
- registerFont(doc, name, data) {
124
- const buffer = ensureBuffer(data);
125
- doc.registerFont(name, buffer);
126
- },
127
- encodeBase64(data) {
128
- return Buffer.from(data).toString('base64');
129
- },
130
- decodeBase64(value) {
131
- return new Uint8Array(Buffer.from(value, 'base64'));
132
- },
133
- embedImage(doc, data, x, y, options, _mimeType) {
134
- if (typeof data === 'string') {
135
- doc.image(data, x, y, options);
136
- return;
137
- }
138
- const buffer = ensureBuffer(data);
139
- doc.image(buffer, x, y, options);
140
- },
141
- };
142
- }