@polotno/pdf-export 0.1.38 → 0.1.40

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.
Files changed (56) hide show
  1. package/README.md +61 -8
  2. package/lib/index.d.ts +66 -8
  3. package/lib/index.js +25 -145
  4. package/package.json +17 -18
  5. package/lib/compare-render.d.ts +0 -1
  6. package/lib/compare-render.js +0 -185
  7. package/lib/figure.d.ts +0 -10
  8. package/lib/figure.js +0 -54
  9. package/lib/filters.d.ts +0 -2
  10. package/lib/filters.js +0 -163
  11. package/lib/ghostscript.d.ts +0 -21
  12. package/lib/ghostscript.js +0 -132
  13. package/lib/group.d.ts +0 -5
  14. package/lib/group.js +0 -5
  15. package/lib/image.d.ts +0 -38
  16. package/lib/image.js +0 -279
  17. package/lib/line.d.ts +0 -10
  18. package/lib/line.js +0 -66
  19. package/lib/pdf-import/coordinate-transform.d.ts +0 -51
  20. package/lib/pdf-import/coordinate-transform.js +0 -99
  21. package/lib/pdf-import/element-builder.d.ts +0 -21
  22. package/lib/pdf-import/element-builder.js +0 -163
  23. package/lib/pdf-import/font-mapper.d.ts +0 -17
  24. package/lib/pdf-import/font-mapper.js +0 -142
  25. package/lib/pdf-import/index.d.ts +0 -35
  26. package/lib/pdf-import/index.js +0 -105
  27. package/lib/pdf-import/parser.d.ts +0 -29
  28. package/lib/pdf-import/parser.js +0 -285
  29. package/lib/pdf-import/text-analysis.d.ts +0 -17
  30. package/lib/pdf-import/text-analysis.js +0 -186
  31. package/lib/pdf-import/types.d.ts +0 -101
  32. package/lib/pdf-import/types.js +0 -1
  33. package/lib/scripts/compare-json.d.ts +0 -1
  34. package/lib/scripts/compare-json.js +0 -141
  35. package/lib/spot-colors.d.ts +0 -38
  36. package/lib/spot-colors.js +0 -141
  37. package/lib/svg-render.d.ts +0 -9
  38. package/lib/svg-render.js +0 -63
  39. package/lib/svg.d.ts +0 -12
  40. package/lib/svg.js +0 -224
  41. package/lib/text/fonts.d.ts +0 -16
  42. package/lib/text/fonts.js +0 -113
  43. package/lib/text/index.d.ts +0 -8
  44. package/lib/text/index.js +0 -42
  45. package/lib/text/layout.d.ts +0 -22
  46. package/lib/text/layout.js +0 -522
  47. package/lib/text/parser.d.ts +0 -46
  48. package/lib/text/parser.js +0 -415
  49. package/lib/text/render.d.ts +0 -8
  50. package/lib/text/render.js +0 -237
  51. package/lib/text/types.d.ts +0 -91
  52. package/lib/text/types.js +0 -1
  53. package/lib/text.d.ts +0 -39
  54. package/lib/text.js +0 -576
  55. package/lib/utils.d.ts +0 -16
  56. package/lib/utils.js +0 -124
package/lib/image.js DELETED
@@ -1,279 +0,0 @@
1
- import { loadImage, PIXEL_RATIO, srcToBuffer, parseColor, } from './utils.js';
2
- import Canvas from 'canvas';
3
- import fs from 'fs';
4
- import path from 'path';
5
- import os from 'os';
6
- import crypto from 'crypto';
7
- import Konva from 'konva';
8
- import { elementFilterToKonva } from './filters.js';
9
- async function applyFlip(image, element) {
10
- const { flipX, flipY } = element;
11
- if (!flipX && !flipY) {
12
- return image;
13
- }
14
- if (!image || !image.width || !image.height) {
15
- return null;
16
- }
17
- const canvas = new Canvas.Canvas(image.width, image.height);
18
- const ctx = canvas.getContext('2d');
19
- let x = flipX ? -canvas.width : 0;
20
- let y = flipY ? -canvas.height : 0;
21
- ctx.scale(flipX ? -1 : 1, flipY ? -1 : 1);
22
- ctx.drawImage(image, x, y, canvas.width, canvas.height);
23
- return canvas;
24
- }
25
- // Helper to create cache key for processed images
26
- export function getProcessedImageKey(element) {
27
- return JSON.stringify({
28
- src: element.src,
29
- width: element.width,
30
- height: element.height,
31
- cropX: element.cropX,
32
- cropY: element.cropY,
33
- cropWidth: element.cropWidth,
34
- cropHeight: element.cropHeight,
35
- clipSrc: element.clipSrc,
36
- brightnessEnabled: element.brightnessEnabled,
37
- brightness: element.brightness,
38
- grayscaleEnabled: element.grayscaleEnabled,
39
- sepiaEnabled: element.sepiaEnabled,
40
- blurEnabled: element.blurEnabled,
41
- blurRadius: element.blurRadius,
42
- filters: element.filters,
43
- });
44
- }
45
- export async function cropImage(src, element, cache = null) {
46
- let image = await loadImage(src);
47
- // Apply flip transformations first
48
- image = await applyFlip(image, element);
49
- if (!image) {
50
- return null;
51
- }
52
- const canvas = Canvas.createCanvas(element.width * PIXEL_RATIO, element.height * PIXEL_RATIO);
53
- const ctx = canvas.getContext('2d');
54
- let { cropX, cropY } = element;
55
- const availableWidth = image.width * element.cropWidth;
56
- const availableHeight = image.height * element.cropHeight;
57
- const aspectRatio = element.width / element.height;
58
- let cropAbsoluteWidth;
59
- let cropAbsoluteHeight;
60
- const imageRatio = availableWidth / availableHeight;
61
- const allowScale = element.type === 'svg';
62
- if (allowScale) {
63
- cropAbsoluteWidth = availableWidth;
64
- cropAbsoluteHeight = availableHeight;
65
- }
66
- else if (aspectRatio >= imageRatio) {
67
- cropAbsoluteWidth = availableWidth;
68
- cropAbsoluteHeight = availableWidth / aspectRatio;
69
- }
70
- else {
71
- cropAbsoluteWidth = availableHeight * aspectRatio;
72
- cropAbsoluteHeight = availableHeight;
73
- }
74
- ctx.drawImage(image, cropX * image.width, cropY * image.height, cropAbsoluteWidth, cropAbsoluteHeight, 0, 0, canvas.width, canvas.height);
75
- return canvas.toDataURL('image/png');
76
- }
77
- export async function clipImage(src, element, cache = null) {
78
- const image = await loadImage(src, cache);
79
- const clipImage = await loadImage(element.clipSrc, cache);
80
- const canvas = Canvas.createCanvas(element.width, element.height);
81
- const ctx = canvas.getContext('2d');
82
- ctx.drawImage(image, 0, 0, element.width, element.height);
83
- const clipCanvas = Canvas.createCanvas(element.width, element.height);
84
- const clipCtx = clipCanvas.getContext('2d');
85
- clipCtx.drawImage(clipImage, 0, 0, element.width, element.height);
86
- ctx.globalCompositeOperation = 'destination-in';
87
- ctx.drawImage(clipCanvas, 0, 0);
88
- ctx.globalCompositeOperation = 'source-over';
89
- return canvas.toDataURL('image/png');
90
- }
91
- async function applyFilter(src, element, cache = null) {
92
- const image = await loadImage(src, cache);
93
- const canvas = Canvas.createCanvas(element.width * PIXEL_RATIO, element.height * PIXEL_RATIO);
94
- const ctx = canvas.getContext('2d');
95
- ctx.drawImage(image, 0, 0, canvas.width, canvas.height);
96
- const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
97
- const data = imageData.data;
98
- if (element.brightnessEnabled) {
99
- Konva.Filters.Brighten.bind({
100
- brightness: () => element.brightness,
101
- })({ data });
102
- }
103
- // grayscale
104
- if (element.grayscaleEnabled) {
105
- Konva.Filters.Grayscale.bind({})({ data });
106
- }
107
- // sepia
108
- if (element.sepiaEnabled) {
109
- Konva.Filters.Sepia.bind({})({ data });
110
- }
111
- // blur
112
- if (element.blurEnabled) {
113
- Konva.Filters.Blur.bind({
114
- blurRadius: () => element.blurRadius,
115
- })({ data });
116
- }
117
- // filters
118
- if (element.filters) {
119
- Object.entries(element.filters).forEach(([type, effect]) => {
120
- const filter = elementFilterToKonva[type];
121
- if (filter) {
122
- filter(effect.intensity).bind({})({ data });
123
- }
124
- });
125
- }
126
- ctx.putImageData(imageData, 0, 0);
127
- return canvas.toDataURL('image/png');
128
- }
129
- function applyBorder(doc, element) {
130
- if (element.borderSize > 0) {
131
- const borderColor = parseColor(element.borderColor).keyword || 'black';
132
- doc
133
- .rect(element.borderSize / 2, element.borderSize / 2, element.width - element.borderSize, element.height - element.borderSize)
134
- .lineWidth(element.borderSize)
135
- .strokeColor(borderColor)
136
- .stroke();
137
- }
138
- }
139
- function saveToTempFile(buffer, key, cache) {
140
- if (cache) {
141
- if (!cache.imageFiles) {
142
- cache.imageFiles = new Map();
143
- }
144
- if (!cache.tempDir) {
145
- cache.tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'pdfkit-images-'));
146
- }
147
- // Create a unique filename based on cache key hash
148
- const hash = crypto.createHash('md5').update(key).digest('hex');
149
- const filePath = path.join(cache.tempDir, `${hash}.png`);
150
- // Write buffer to file
151
- fs.writeFileSync(filePath, buffer);
152
- cache.imageFiles.set(key, filePath);
153
- return filePath;
154
- }
155
- else {
156
- return buffer;
157
- }
158
- }
159
- async function getShadowImage(src, element, cache) {
160
- const image = await loadImage(src, cache);
161
- const { shadowBlur, shadowColor, shadowOpacity } = element;
162
- // Shadow blur in Konva is standard. In Canvas it is standard.
163
- // We need to scale it by PIXEL_RATIO as our canvas is scaled.
164
- const ratio = PIXEL_RATIO;
165
- const blur = (shadowBlur || 0) * ratio;
166
- // Padding.
167
- const padding = blur * 4 + 20; // Sufficient padding
168
- const width = image.width + padding * 2;
169
- const height = image.height + padding * 2;
170
- const canvas = Canvas.createCanvas(width, height);
171
- const ctx = canvas.getContext('2d');
172
- // Parse color
173
- const parsed = parseColor(shadowColor || 'black');
174
- const opacity = shadowOpacity !== undefined ? shadowOpacity : 1;
175
- const r = parsed.rgb[0];
176
- const g = parsed.rgb[1];
177
- const b = parsed.rgb[2];
178
- const a = opacity;
179
- const colorString = `rgba(${r},${g},${b},${a})`;
180
- ctx.shadowColor = colorString;
181
- ctx.shadowBlur = blur;
182
- // We want the shadow to appear at (padding, padding) relative to canvas.
183
- // We draw the image at (padding - OFFSET, padding - OFFSET)
184
- // And set shadowOffset to (OFFSET, OFFSET).
185
- // OFFSET should be large enough to move image out of view.
186
- const OFFSET = 10000;
187
- ctx.shadowOffsetX = OFFSET;
188
- ctx.shadowOffsetY = OFFSET;
189
- // Draw image
190
- ctx.drawImage(image, padding - OFFSET, padding - OFFSET, image.width, image.height);
191
- return {
192
- src: canvas.toDataURL('image/png'),
193
- padding: padding / ratio, // return padding in original units (points)
194
- width: width / ratio,
195
- height: height / ratio,
196
- };
197
- }
198
- export async function renderImage(doc, element, cache = null) {
199
- // Check if we have a cached processed version
200
- const cacheKey = getProcessedImageKey(element);
201
- let src = null;
202
- const hasCachedFile = cache && cache.imageFiles && cache.imageFiles.has(cacheKey);
203
- if (hasCachedFile) {
204
- src = cache.imageFiles.get(cacheKey);
205
- }
206
- else if (cache && cache.processedImages.has(cacheKey)) {
207
- src = cache.processedImages.get(cacheKey);
208
- }
209
- else {
210
- src = await cropImage(element.src, element, cache);
211
- if (element.clipSrc) {
212
- src = await clipImage(src, element, cache);
213
- }
214
- src = await applyFilter(src, element, cache);
215
- // Cache the processed result
216
- if (cache && src) {
217
- cache.processedImages.set(cacheKey, src);
218
- }
219
- }
220
- if (element.shadowEnabled && src) {
221
- const shadowKey = cacheKey +
222
- '_shadow_' +
223
- JSON.stringify({
224
- blur: element.shadowBlur,
225
- color: element.shadowColor,
226
- opacity: element.shadowOpacity,
227
- });
228
- let shadowPadding = 0;
229
- let shadowW = 0;
230
- let shadowH = 0;
231
- if (cache && cache.imageFiles && cache.imageFiles.has(shadowKey)) {
232
- const filePath = cache.imageFiles.get(shadowKey);
233
- // Recalculate padding/dimensions as we don't cache them
234
- const ratio = PIXEL_RATIO;
235
- const blur = (element.shadowBlur || 0) * ratio;
236
- shadowPadding = (blur * 4 + 20) / ratio;
237
- shadowW = element.width + shadowPadding * 2;
238
- shadowH = element.height + shadowPadding * 2;
239
- console.log('✓ Using cached shadow file:', path.basename(filePath));
240
- doc.image(filePath, (element.shadowOffsetX || 0) - shadowPadding, (element.shadowOffsetY || 0) - shadowPadding, {
241
- width: shadowW,
242
- height: shadowH,
243
- });
244
- }
245
- else {
246
- const shadowResult = await getShadowImage(src, element, cache);
247
- const shadowSrc = shadowResult.src;
248
- shadowPadding = shadowResult.padding;
249
- shadowW = shadowResult.width;
250
- shadowH = shadowResult.height;
251
- if (shadowSrc) {
252
- const buffer = await srcToBuffer(shadowSrc, cache);
253
- const filePath = saveToTempFile(buffer, shadowKey, cache);
254
- doc.image(filePath, (element.shadowOffsetX || 0) - shadowPadding, (element.shadowOffsetY || 0) - shadowPadding, {
255
- width: shadowW,
256
- height: shadowH,
257
- });
258
- }
259
- }
260
- }
261
- if (src) {
262
- if (hasCachedFile) {
263
- console.log('✓ Using cached image file:', path.basename(src));
264
- doc.image(src, 0, 0, {
265
- width: element.width,
266
- height: element.height,
267
- });
268
- }
269
- else {
270
- const buffer = await srcToBuffer(src, cache);
271
- const filePath = saveToTempFile(buffer, cacheKey, cache);
272
- doc.image(filePath, 0, 0, {
273
- width: element.width,
274
- height: element.height,
275
- });
276
- }
277
- applyBorder(doc, element);
278
- }
279
- }
package/lib/line.d.ts DELETED
@@ -1,10 +0,0 @@
1
- export interface LineElement {
2
- height: number;
3
- width: number;
4
- color: string;
5
- opacity: number;
6
- dash?: number[];
7
- startHead?: string;
8
- endHead?: string;
9
- }
10
- export declare function lineToPDF(doc: any, element: LineElement): void;
package/lib/line.js DELETED
@@ -1,66 +0,0 @@
1
- import { Util } from 'konva/lib/Util.js';
2
- function rgbToHex({ r, g, b }) {
3
- // Ensure each value is within the valid range
4
- r = Math.max(0, Math.min(255, r));
5
- g = Math.max(0, Math.min(255, g));
6
- b = Math.max(0, Math.min(255, b));
7
- // Convert each value to a 2-digit hexadecimal string
8
- const hexR = r.toString(16).padStart(2, '0');
9
- const hexG = g.toString(16).padStart(2, '0');
10
- const hexB = b.toString(16).padStart(2, '0');
11
- // Return the concatenated hex string
12
- return `#${hexR}${hexG}${hexB}`;
13
- }
14
- function getLineHead({ element, type, doc }) {
15
- doc.lineWidth(element.height);
16
- doc.lineCap('round');
17
- doc.lineJoin('round');
18
- doc.opacity(element.opacity);
19
- const rgba = Util.colorToRGBA(element.color);
20
- const fillColor = rgbToHex(rgba);
21
- if (type === 'arrow') {
22
- doc
23
- .moveTo(element.height * 3, -element.height * 2)
24
- .lineTo(0, 0)
25
- .lineTo(element.height * 3, element.height * 2);
26
- doc.stroke();
27
- return;
28
- }
29
- else if (type === 'triangle') {
30
- doc.polygon([element.height * 3, -element.height * 2], [0, 0], [element.height * 3, element.height * 2]);
31
- }
32
- else if (type === 'bar') {
33
- doc.polygon([0, -element.height * 2], [0, 0], [0, element.height * 2]);
34
- }
35
- else if (type === 'circle') {
36
- doc.circle(element.height * 2, 0, element.height * 2);
37
- }
38
- else if (type === 'square') {
39
- doc.rect(0, -element.height * 2, element.height * 4, element.height * 4);
40
- }
41
- else {
42
- return;
43
- }
44
- doc.fillAndStroke(fillColor, fillColor);
45
- }
46
- export function lineToPDF(doc, element) {
47
- doc.translate(0, element.height / 2);
48
- doc.lineWidth(element.height);
49
- doc.moveTo(0, 0);
50
- doc.lineTo(element.width, 0);
51
- if (element.dash && element.dash.length > 0) {
52
- doc.dash(element.dash.map((dash) => dash * element.height));
53
- }
54
- const rgba = Util.colorToRGBA(element.color);
55
- doc.strokeColor(rgbToHex(rgba));
56
- doc.stroke();
57
- if (element.dash && element.dash.length > 0) {
58
- doc.undash();
59
- }
60
- getLineHead({ element, doc: doc, type: element.startHead });
61
- getLineHead({
62
- element,
63
- doc: doc.translate(element.width, 0).rotate(180),
64
- type: element.endHead,
65
- });
66
- }
@@ -1,51 +0,0 @@
1
- /**
2
- * Transform PDF coordinates (bottom-left origin) to Polotno coordinates (top-left origin)
3
- */
4
- export declare function pdfToPolotnoY(pdfY: number, elementHeight: number, pageHeight: number): number;
5
- /**
6
- * X coordinate remains the same between PDF and Polotno
7
- */
8
- export declare function pdfToPolotnoX(pdfX: number): number;
9
- /**
10
- * Extract rotation angle from PDF transformation matrix
11
- * PDF transformation matrix is [a, b, c, d, e, f]
12
- * where rotation is encoded in a, b, c, d components
13
- */
14
- export declare function extractRotation(transform: number[]): number;
15
- /**
16
- * Extract scale from PDF transformation matrix
17
- */
18
- export declare function extractScale(transform: number[]): {
19
- scaleX: number;
20
- scaleY: number;
21
- };
22
- /**
23
- * Extract position from PDF transformation matrix
24
- * Returns position in PDF coordinates (will need to be converted to Polotno)
25
- */
26
- export declare function extractPosition(transform: number[]): {
27
- x: number;
28
- y: number;
29
- };
30
- /**
31
- * Calculate font size from PDF transformation matrix
32
- * Font size is encoded in the scale component of the matrix
33
- */
34
- export declare function calculateFontSize(transform: number[]): number;
35
- /**
36
- * Transform a complete bounding box from PDF to Polotno coordinates
37
- */
38
- export declare function transformBoundingBox(pdfX: number, pdfY: number, width: number, height: number, pageHeight: number): {
39
- x: number;
40
- y: number;
41
- width: number;
42
- height: number;
43
- };
44
- /**
45
- * Convert PDF color array [r, g, b] (0-1 range) to hex color string
46
- */
47
- export declare function pdfColorToHex(color: number[]): string;
48
- /**
49
- * Apply unit conversion based on output unit and DPI
50
- */
51
- export declare function convertUnits(valueInPx: number, targetUnit: 'px' | 'cm' | 'in', dpi?: number): number;
@@ -1,99 +0,0 @@
1
- /**
2
- * Transform PDF coordinates (bottom-left origin) to Polotno coordinates (top-left origin)
3
- */
4
- export function pdfToPolotnoY(pdfY, elementHeight, pageHeight) {
5
- // PDF uses bottom-left origin, Polotno uses top-left
6
- // PDFy is distance from bottom, we need distance from top
7
- return pageHeight - pdfY - elementHeight;
8
- }
9
- /**
10
- * X coordinate remains the same between PDF and Polotno
11
- */
12
- export function pdfToPolotnoX(pdfX) {
13
- return pdfX;
14
- }
15
- /**
16
- * Extract rotation angle from PDF transformation matrix
17
- * PDF transformation matrix is [a, b, c, d, e, f]
18
- * where rotation is encoded in a, b, c, d components
19
- */
20
- export function extractRotation(transform) {
21
- const [a, b, c, d] = transform;
22
- // Calculate rotation angle from matrix
23
- // For a rotation matrix: a = cos(θ), b = sin(θ), c = -sin(θ), d = cos(θ)
24
- const rotation = Math.atan2(b, a) * (180 / Math.PI);
25
- return rotation;
26
- }
27
- /**
28
- * Extract scale from PDF transformation matrix
29
- */
30
- export function extractScale(transform) {
31
- const [a, b, c, d] = transform;
32
- // Scale is the magnitude of the transformation vectors
33
- const scaleX = Math.sqrt(a * a + b * b);
34
- const scaleY = Math.sqrt(c * c + d * d);
35
- return { scaleX, scaleY };
36
- }
37
- /**
38
- * Extract position from PDF transformation matrix
39
- * Returns position in PDF coordinates (will need to be converted to Polotno)
40
- */
41
- export function extractPosition(transform) {
42
- return {
43
- x: transform[4],
44
- y: transform[5],
45
- };
46
- }
47
- /**
48
- * Calculate font size from PDF transformation matrix
49
- * Font size is encoded in the scale component of the matrix
50
- */
51
- export function calculateFontSize(transform) {
52
- const { scaleY } = extractScale(transform);
53
- return Math.abs(scaleY);
54
- }
55
- /**
56
- * Transform a complete bounding box from PDF to Polotno coordinates
57
- */
58
- export function transformBoundingBox(pdfX, pdfY, width, height, pageHeight) {
59
- return {
60
- x: pdfToPolotnoX(pdfX),
61
- y: pdfToPolotnoY(pdfY, height, pageHeight),
62
- width,
63
- height,
64
- };
65
- }
66
- /**
67
- * Convert PDF color array [r, g, b] (0-1 range) to hex color string
68
- */
69
- export function pdfColorToHex(color) {
70
- if (!color || color.length < 3) {
71
- return '#000000';
72
- }
73
- // PDF colors are in 0-1 range, convert to 0-255
74
- const r = Math.round(color[0] * 255);
75
- const g = Math.round(color[1] * 255);
76
- const b = Math.round(color[2] * 255);
77
- // Convert to hex
78
- const hex = '#' + [r, g, b]
79
- .map(x => x.toString(16).padStart(2, '0'))
80
- .join('');
81
- return hex;
82
- }
83
- /**
84
- * Apply unit conversion based on output unit and DPI
85
- */
86
- export function convertUnits(valueInPx, targetUnit, dpi = 72) {
87
- if (targetUnit === 'px') {
88
- return valueInPx;
89
- }
90
- // Convert pixels to points first (assuming 72 DPI as PDF standard)
91
- const points = valueInPx;
92
- if (targetUnit === 'in') {
93
- return points / dpi;
94
- }
95
- if (targetUnit === 'cm') {
96
- return (points / dpi) * 2.54;
97
- }
98
- return valueInPx;
99
- }
@@ -1,21 +0,0 @@
1
- import type { TextBlock, ImageBlock, PDFImageObject, PDFImportOptions } from './types.js';
2
- /**
3
- * Build Polotno text element from text block
4
- */
5
- export declare function buildTextElement(block: TextBlock): any;
6
- /**
7
- * Build Polotno image element from image block
8
- */
9
- export declare function buildImageElement(imageBlock: ImageBlock): any;
10
- /**
11
- * Convert PDF image to data URL
12
- */
13
- export declare function imageToDataURL(buffer: Buffer, mimeType: string): Promise<string>;
14
- /**
15
- * Process PDF image and create image block
16
- */
17
- export declare function processImage(pdfImage: PDFImageObject, pageHeight: number, options: PDFImportOptions): Promise<ImageBlock | null>;
18
- /**
19
- * Process all images from a PDF page
20
- */
21
- export declare function processImages(pdfImages: PDFImageObject[], pageHeight: number, options: PDFImportOptions): Promise<any[]>;
@@ -1,163 +0,0 @@
1
- import { pdfToPolotnoX, pdfToPolotnoY, extractRotation } from './coordinate-transform.js';
2
- /**
3
- * Generate a random ID for Polotno elements
4
- */
5
- function randomId() {
6
- const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
7
- let result = '';
8
- for (let i = 0; i < 10; i++) {
9
- result += chars.charAt(Math.floor(Math.random() * chars.length));
10
- }
11
- return result;
12
- }
13
- /**
14
- * Build Polotno text element from text block
15
- */
16
- export function buildTextElement(block) {
17
- return {
18
- type: 'text',
19
- id: randomId(),
20
- name: '',
21
- x: block.x,
22
- y: block.y,
23
- width: block.width,
24
- height: block.height,
25
- rotation: block.rotation || 0,
26
- // Text content
27
- text: block.text,
28
- // Font properties
29
- fontSize: block.fontSize,
30
- fontFamily: block.fontName,
31
- fontWeight: block.fontWeight,
32
- fontStyle: block.fontStyle,
33
- // Color and styling
34
- fill: block.color,
35
- stroke: 'black',
36
- strokeWidth: 0,
37
- // Alignment
38
- align: block.align || 'left',
39
- verticalAlign: 'top',
40
- // Text properties
41
- lineHeight: 1.2,
42
- letterSpacing: 0,
43
- // Visibility
44
- opacity: 1,
45
- visible: true,
46
- selectable: true,
47
- draggable: true,
48
- resizable: true,
49
- contentEditable: true,
50
- removable: true,
51
- // Background (disabled by default)
52
- backgroundEnabled: false,
53
- // No effects by default
54
- shadowEnabled: false,
55
- blurEnabled: false,
56
- };
57
- }
58
- /**
59
- * Build Polotno image element from image block
60
- */
61
- export function buildImageElement(imageBlock) {
62
- return {
63
- type: 'image',
64
- id: randomId(),
65
- name: '',
66
- x: imageBlock.x,
67
- y: imageBlock.y,
68
- width: imageBlock.width,
69
- height: imageBlock.height,
70
- rotation: imageBlock.rotation || 0,
71
- // Image source
72
- src: imageBlock.src,
73
- // Cropping (no crop by default)
74
- cropX: 0,
75
- cropY: 0,
76
- cropWidth: 1,
77
- cropHeight: 1,
78
- // Transformations
79
- flipX: false,
80
- flipY: false,
81
- // Border
82
- borderColor: 'black',
83
- borderSize: 0,
84
- cornerRadius: 0,
85
- // Visibility
86
- opacity: 1,
87
- visible: true,
88
- selectable: true,
89
- draggable: true,
90
- resizable: true,
91
- removable: true,
92
- // No effects by default
93
- shadowEnabled: false,
94
- blurEnabled: false,
95
- filters: {},
96
- };
97
- }
98
- /**
99
- * Convert PDF image to data URL
100
- */
101
- export async function imageToDataURL(buffer, mimeType) {
102
- const base64 = buffer.toString('base64');
103
- return `data:${mimeType};base64,${base64}`;
104
- }
105
- /**
106
- * Process PDF image and create image block
107
- */
108
- export async function processImage(pdfImage, pageHeight, options) {
109
- try {
110
- // Extract components from transform matrix
111
- // Transform matrix: [a, b, c, d, e, f] where:
112
- // - a,d are scale X,Y (or combined with rotation)
113
- // - b,c are rotation/skew
114
- // - e,f are translation X,Y
115
- const [a, b, c, d, e, f] = pdfImage.transform;
116
- // Calculate dimensions from scale components
117
- const scaleX = Math.sqrt(a * a + b * b);
118
- const scaleY = Math.sqrt(c * c + d * d);
119
- const width = Math.abs(scaleX);
120
- const height = Math.abs(scaleY);
121
- // Extract rotation
122
- const rotation = extractRotation(pdfImage.transform);
123
- // Extract position (e,f are the translation components)
124
- const x = pdfToPolotnoX(e);
125
- // Check if Y is flipped (negative scaleY means coordinate system is already top-left)
126
- const isYFlipped = d < 0;
127
- const y = isYFlipped
128
- ? f // Already top-left origin, use directly
129
- : pdfToPolotnoY(f, height, pageHeight); // Bottom-left origin, needs conversion
130
- // Handle image data based on mode
131
- let src;
132
- if (options.imageMode === 'upload' && options.imageUploadFn) {
133
- // Upload image and get URL
134
- src = await options.imageUploadFn(pdfImage.buffer, pdfImage.mimeType);
135
- }
136
- else {
137
- // Convert to data URL (default)
138
- src = await imageToDataURL(pdfImage.buffer, pdfImage.mimeType);
139
- }
140
- return {
141
- src,
142
- x,
143
- y,
144
- width,
145
- height,
146
- rotation,
147
- };
148
- }
149
- catch (error) {
150
- console.warn('Failed to process image:', error);
151
- return null;
152
- }
153
- }
154
- /**
155
- * Process all images from a PDF page
156
- */
157
- export async function processImages(pdfImages, pageHeight, options) {
158
- const imageBlocks = await Promise.all(pdfImages.map(img => processImage(img, pageHeight, options)));
159
- // Filter out null results and build elements
160
- return imageBlocks
161
- .filter((block) => block !== null)
162
- .map(block => buildImageElement(block));
163
- }