@polotno/pdf-export 0.1.18 → 0.1.19
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 +52 -0
- package/lib/figure.d.ts +10 -0
- package/lib/figure.js +53 -48
- package/lib/ghostscript.d.ts +21 -0
- package/lib/ghostscript.js +101 -128
- package/lib/group.d.ts +5 -0
- package/lib/group.js +4 -8
- package/lib/image.d.ts +19 -0
- package/lib/image.js +134 -89
- package/lib/index.d.ts +26 -0
- package/lib/index.js +130 -0
- package/lib/line.d.ts +10 -0
- package/lib/line.js +41 -57
- package/lib/spot-colors.d.ts +38 -0
- package/lib/spot-colors.js +141 -0
- package/lib/svg-render.d.ts +9 -0
- package/lib/svg-render.js +19 -28
- package/lib/svg.d.ts +11 -0
- package/lib/svg.js +203 -232
- package/lib/text.d.ts +28 -0
- package/lib/text.js +147 -174
- package/lib/utils.d.ts +15 -0
- package/lib/utils.js +88 -72
- package/package.json +22 -14
- package/index.js +0 -130
package/lib/image.js
CHANGED
|
@@ -1,92 +1,137 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
const imageRatio = availableWidth / availableHeight;
|
|
24
|
-
const allowScale = element.type === 'svg';
|
|
25
|
-
|
|
26
|
-
if (allowScale) {
|
|
27
|
-
cropAbsoluteWidth = availableWidth;
|
|
28
|
-
cropAbsoluteHeight = availableHeight;
|
|
29
|
-
} else if (aspectRatio >= imageRatio) {
|
|
30
|
-
cropAbsoluteWidth = availableWidth;
|
|
31
|
-
cropAbsoluteHeight = availableWidth / aspectRatio;
|
|
32
|
-
} else {
|
|
33
|
-
cropAbsoluteWidth = availableHeight * aspectRatio;
|
|
34
|
-
cropAbsoluteHeight = availableHeight;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
ctx.drawImage(
|
|
38
|
-
image,
|
|
39
|
-
cropX * image.width,
|
|
40
|
-
cropY * image.height,
|
|
41
|
-
cropAbsoluteWidth,
|
|
42
|
-
cropAbsoluteHeight,
|
|
43
|
-
0,
|
|
44
|
-
0,
|
|
45
|
-
canvas.width,
|
|
46
|
-
canvas.height
|
|
47
|
-
);
|
|
48
|
-
|
|
49
|
-
return canvas.toDataURL('image/png');
|
|
1
|
+
import { loadImage, PIXEL_RATIO, srcToBuffer } 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
|
+
async function applyFlip(image, element) {
|
|
8
|
+
const { flipX, flipY } = element;
|
|
9
|
+
if (!flipX && !flipY) {
|
|
10
|
+
return image;
|
|
11
|
+
}
|
|
12
|
+
if (!image || !image.width || !image.height) {
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
const canvas = new Canvas.Canvas(image.width, image.height);
|
|
16
|
+
const ctx = canvas.getContext('2d');
|
|
17
|
+
let x = flipX ? -canvas.width : 0;
|
|
18
|
+
let y = flipY ? -canvas.height : 0;
|
|
19
|
+
ctx.scale(flipX ? -1 : 1, flipY ? -1 : 1);
|
|
20
|
+
ctx.drawImage(image, x, y, canvas.width, canvas.height);
|
|
21
|
+
return canvas;
|
|
50
22
|
}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
const clipCtx = clipCanvas.getContext('2d');
|
|
63
|
-
|
|
64
|
-
clipCtx.drawImage(clipImage, 0, 0, element.width, element.height);
|
|
65
|
-
|
|
66
|
-
ctx.globalCompositeOperation = 'destination-in';
|
|
67
|
-
ctx.drawImage(clipCanvas, 0, 0);
|
|
68
|
-
|
|
69
|
-
ctx.globalCompositeOperation = 'source-over';
|
|
70
|
-
|
|
71
|
-
return canvas.toDataURL('image/png');
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
async function renderImage(doc, element) {
|
|
75
|
-
let src = await cropImage(element.src, element);
|
|
76
|
-
if (element.clipSrc) {
|
|
77
|
-
src = await clipImage(src, element);
|
|
78
|
-
}
|
|
79
|
-
if (src) {
|
|
80
|
-
doc.image(await srcToBuffer(src), 0, 0, {
|
|
81
|
-
width: element.width,
|
|
82
|
-
height: element.height,
|
|
83
|
-
opacity: element.opacity,
|
|
23
|
+
// Helper to create cache key for processed images
|
|
24
|
+
export function getProcessedImageKey(element) {
|
|
25
|
+
return JSON.stringify({
|
|
26
|
+
src: element.src,
|
|
27
|
+
width: element.width,
|
|
28
|
+
height: element.height,
|
|
29
|
+
cropX: element.cropX,
|
|
30
|
+
cropY: element.cropY,
|
|
31
|
+
cropWidth: element.cropWidth,
|
|
32
|
+
cropHeight: element.cropHeight,
|
|
33
|
+
clipSrc: element.clipSrc,
|
|
84
34
|
});
|
|
85
|
-
}
|
|
86
35
|
}
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
36
|
+
export async function cropImage(src, element, cache = null) {
|
|
37
|
+
let image = await loadImage(src);
|
|
38
|
+
// Apply flip transformations first
|
|
39
|
+
image = await applyFlip(image, element);
|
|
40
|
+
if (!image) {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
const canvas = Canvas.createCanvas(element.width * PIXEL_RATIO, element.height * PIXEL_RATIO);
|
|
44
|
+
const ctx = canvas.getContext('2d');
|
|
45
|
+
let { cropX, cropY } = element;
|
|
46
|
+
const availableWidth = image.width * element.cropWidth;
|
|
47
|
+
const availableHeight = image.height * element.cropHeight;
|
|
48
|
+
const aspectRatio = element.width / element.height;
|
|
49
|
+
let cropAbsoluteWidth;
|
|
50
|
+
let cropAbsoluteHeight;
|
|
51
|
+
const imageRatio = availableWidth / availableHeight;
|
|
52
|
+
const allowScale = element.type === 'svg';
|
|
53
|
+
if (allowScale) {
|
|
54
|
+
cropAbsoluteWidth = availableWidth;
|
|
55
|
+
cropAbsoluteHeight = availableHeight;
|
|
56
|
+
}
|
|
57
|
+
else if (aspectRatio >= imageRatio) {
|
|
58
|
+
cropAbsoluteWidth = availableWidth;
|
|
59
|
+
cropAbsoluteHeight = availableWidth / aspectRatio;
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
cropAbsoluteWidth = availableHeight * aspectRatio;
|
|
63
|
+
cropAbsoluteHeight = availableHeight;
|
|
64
|
+
}
|
|
65
|
+
ctx.drawImage(image, cropX * image.width, cropY * image.height, cropAbsoluteWidth, cropAbsoluteHeight, 0, 0, canvas.width, canvas.height);
|
|
66
|
+
return canvas.toDataURL('image/png');
|
|
67
|
+
}
|
|
68
|
+
export async function clipImage(src, element, cache = null) {
|
|
69
|
+
const image = await loadImage(src, cache);
|
|
70
|
+
const clipImage = await loadImage(element.clipSrc, cache);
|
|
71
|
+
const canvas = Canvas.createCanvas(element.width, element.height);
|
|
72
|
+
const ctx = canvas.getContext('2d');
|
|
73
|
+
ctx.drawImage(image, 0, 0, element.width, element.height);
|
|
74
|
+
const clipCanvas = Canvas.createCanvas(element.width, element.height);
|
|
75
|
+
const clipCtx = clipCanvas.getContext('2d');
|
|
76
|
+
clipCtx.drawImage(clipImage, 0, 0, element.width, element.height);
|
|
77
|
+
ctx.globalCompositeOperation = 'destination-in';
|
|
78
|
+
ctx.drawImage(clipCanvas, 0, 0);
|
|
79
|
+
ctx.globalCompositeOperation = 'source-over';
|
|
80
|
+
return canvas.toDataURL('image/png');
|
|
81
|
+
}
|
|
82
|
+
export async function renderImage(doc, element, cache = null) {
|
|
83
|
+
// Check if we have a cached processed version
|
|
84
|
+
const cacheKey = getProcessedImageKey(element);
|
|
85
|
+
// Check if we have a cached file path for this image
|
|
86
|
+
if (cache && cache.imageFiles && cache.imageFiles.has(cacheKey)) {
|
|
87
|
+
const filePath = cache.imageFiles.get(cacheKey);
|
|
88
|
+
console.log('✓ Using cached image file:', path.basename(filePath));
|
|
89
|
+
doc.image(filePath, 0, 0, {
|
|
90
|
+
width: element.width,
|
|
91
|
+
height: element.height,
|
|
92
|
+
opacity: element.opacity,
|
|
93
|
+
});
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
let src = null;
|
|
97
|
+
if (cache && cache.processedImages.has(cacheKey)) {
|
|
98
|
+
src = cache.processedImages.get(cacheKey);
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
src = await cropImage(element.src, element, cache);
|
|
102
|
+
if (element.clipSrc) {
|
|
103
|
+
src = await clipImage(src, element, cache);
|
|
104
|
+
}
|
|
105
|
+
// Cache the processed result
|
|
106
|
+
if (cache && src) {
|
|
107
|
+
cache.processedImages.set(cacheKey, src);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
if (src) {
|
|
111
|
+
const buffer = await srcToBuffer(src, cache);
|
|
112
|
+
// Save buffer to a temp file and cache the path
|
|
113
|
+
let filePath;
|
|
114
|
+
if (cache) {
|
|
115
|
+
if (!cache.imageFiles) {
|
|
116
|
+
cache.imageFiles = new Map();
|
|
117
|
+
}
|
|
118
|
+
if (!cache.tempDir) {
|
|
119
|
+
cache.tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'pdfkit-images-'));
|
|
120
|
+
}
|
|
121
|
+
// Create a unique filename based on cache key hash
|
|
122
|
+
const hash = crypto.createHash('md5').update(cacheKey).digest('hex');
|
|
123
|
+
filePath = path.join(cache.tempDir, `${hash}.png`);
|
|
124
|
+
// Write buffer to file
|
|
125
|
+
fs.writeFileSync(filePath, buffer);
|
|
126
|
+
cache.imageFiles.set(cacheKey, filePath);
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
filePath = buffer;
|
|
130
|
+
}
|
|
131
|
+
doc.image(filePath, 0, 0, {
|
|
132
|
+
width: element.width,
|
|
133
|
+
height: element.height,
|
|
134
|
+
opacity: element.opacity,
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
}
|
package/lib/index.d.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
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<void>;
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import PDFDocument from 'pdfkit';
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { srcToBuffer, parseColor } from './utils.js';
|
|
5
|
+
import { renderImage } from './image.js';
|
|
6
|
+
import { loadFontIfNeeded, renderText } from './text.js';
|
|
7
|
+
import { renderFigure } from './figure.js';
|
|
8
|
+
import { renderGroup } from './group.js';
|
|
9
|
+
import { lineToPDF } from './line.js';
|
|
10
|
+
import { renderSVG } from './svg-render.js';
|
|
11
|
+
import { convertToPDFX1a, validatePDFX1a } from './ghostscript.js';
|
|
12
|
+
import { enableSpotColorSupport } from './spot-colors.js';
|
|
13
|
+
import SVGtoPDF from 'svg-to-pdfkit';
|
|
14
|
+
// Extend PDFDocument prototype with addSVG method
|
|
15
|
+
PDFDocument.prototype.addSVG = function (svg, x, y, options) {
|
|
16
|
+
return SVGtoPDF(this, svg, x, y, options), this;
|
|
17
|
+
};
|
|
18
|
+
async function renderElement({ doc, element, fonts, attrs, cache, }) {
|
|
19
|
+
if ((element.visible !== undefined && !element.visible) ||
|
|
20
|
+
(element.showInExport !== undefined && !element.showInExport)) {
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
doc.save();
|
|
24
|
+
if (element.type !== 'group') {
|
|
25
|
+
doc.translate(element.x, element.y);
|
|
26
|
+
doc.rotate(element.rotation);
|
|
27
|
+
}
|
|
28
|
+
if (element.opacity !== undefined) {
|
|
29
|
+
doc.opacity(element.opacity);
|
|
30
|
+
}
|
|
31
|
+
if (element.type === 'group') {
|
|
32
|
+
await renderGroup(doc, element, renderElement, fonts, attrs, cache);
|
|
33
|
+
}
|
|
34
|
+
else if (element.type === 'text') {
|
|
35
|
+
await loadFontIfNeeded(doc, element, fonts);
|
|
36
|
+
renderText(doc, element, attrs);
|
|
37
|
+
}
|
|
38
|
+
else if (element.type === 'line') {
|
|
39
|
+
lineToPDF(doc, element);
|
|
40
|
+
}
|
|
41
|
+
else if (element.type === 'image') {
|
|
42
|
+
await renderImage(doc, element, cache);
|
|
43
|
+
}
|
|
44
|
+
else if (element.type === 'svg') {
|
|
45
|
+
await renderSVG(doc, element, cache);
|
|
46
|
+
}
|
|
47
|
+
else if (element.type === 'figure') {
|
|
48
|
+
renderFigure(doc, element);
|
|
49
|
+
}
|
|
50
|
+
doc.restore();
|
|
51
|
+
}
|
|
52
|
+
export async function jsonToPDF(json, pdfFileName, attrs = {}) {
|
|
53
|
+
const fonts = {};
|
|
54
|
+
// Create cache for images and processed results
|
|
55
|
+
const cache = {
|
|
56
|
+
images: new Map(), // Cache for loaded Canvas images
|
|
57
|
+
buffers: new Map(), // Cache for fetched buffers
|
|
58
|
+
processedImages: new Map(), // Cache for cropped/clipped image data URLs
|
|
59
|
+
imageFiles: new Map(), // Cache for image file paths
|
|
60
|
+
tempDir: null, // Temporary directory for image files
|
|
61
|
+
};
|
|
62
|
+
var doc = new PDFDocument({
|
|
63
|
+
size: [json.width, json.height],
|
|
64
|
+
autoFirstPage: false,
|
|
65
|
+
});
|
|
66
|
+
// Enable spot color support if configured
|
|
67
|
+
if (attrs.spotColors) {
|
|
68
|
+
enableSpotColorSupport(doc, attrs.spotColors);
|
|
69
|
+
}
|
|
70
|
+
for (const font of json.fonts) {
|
|
71
|
+
doc.registerFont(font.fontFamily, await srcToBuffer(font.url, cache));
|
|
72
|
+
fonts[font.fontFamily] = true;
|
|
73
|
+
}
|
|
74
|
+
for (const page of json.pages) {
|
|
75
|
+
doc.addPage();
|
|
76
|
+
if (page.background) {
|
|
77
|
+
const isURL = page.background.indexOf('http') >= 0 ||
|
|
78
|
+
page.background.indexOf('.png') >= 0 ||
|
|
79
|
+
page.background.indexOf('.jpg') >= 0;
|
|
80
|
+
if (isURL) {
|
|
81
|
+
doc.image(await srcToBuffer(page.background, cache), 0, 0);
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
doc.rect(0, 0, json.width, json.height);
|
|
85
|
+
doc.fill(parseColor(page.background).hex);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
for (const element of page.children) {
|
|
89
|
+
await renderElement({ doc, element, fonts, attrs, cache });
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
doc.end();
|
|
93
|
+
await new Promise((r) => doc.pipe(fs.createWriteStream(pdfFileName)).on('finish', r));
|
|
94
|
+
// Clean up temporary image files
|
|
95
|
+
if (cache.tempDir && fs.existsSync(cache.tempDir)) {
|
|
96
|
+
const files = fs.readdirSync(cache.tempDir);
|
|
97
|
+
for (const file of files) {
|
|
98
|
+
fs.unlinkSync(path.join(cache.tempDir, file));
|
|
99
|
+
}
|
|
100
|
+
fs.rmdirSync(cache.tempDir);
|
|
101
|
+
}
|
|
102
|
+
// Optional PDF/X-1a conversion
|
|
103
|
+
if (attrs.pdfx1a) {
|
|
104
|
+
const tempFileName = pdfFileName.replace('.pdf', '-temp.pdf');
|
|
105
|
+
// Rename current file to temp
|
|
106
|
+
fs.renameSync(pdfFileName, tempFileName);
|
|
107
|
+
try {
|
|
108
|
+
// Convert temp file to PDF/X-1a
|
|
109
|
+
await convertToPDFX1a(tempFileName, pdfFileName, {
|
|
110
|
+
metadata: attrs.metadata || {},
|
|
111
|
+
});
|
|
112
|
+
// Clean up temp file
|
|
113
|
+
fs.unlinkSync(tempFileName);
|
|
114
|
+
// Optional validation
|
|
115
|
+
if (attrs.validate) {
|
|
116
|
+
const isValid = await validatePDFX1a(pdfFileName);
|
|
117
|
+
if (!isValid) {
|
|
118
|
+
console.warn('Warning: Generated PDF may not be fully PDF/X-1a compliant');
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
catch (error) {
|
|
123
|
+
// Restore original file if conversion fails
|
|
124
|
+
if (fs.existsSync(tempFileName)) {
|
|
125
|
+
fs.renameSync(tempFileName, pdfFileName);
|
|
126
|
+
}
|
|
127
|
+
throw new Error(`PDF/X-1a conversion failed: ${error.message}`);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
package/lib/line.d.ts
ADDED
package/lib/line.js
CHANGED
|
@@ -1,82 +1,66 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
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 }) {
|
|
7
15
|
doc.lineWidth(element.height);
|
|
8
16
|
doc.lineCap('round');
|
|
9
17
|
doc.lineJoin('round');
|
|
10
18
|
doc.opacity(element.opacity);
|
|
11
|
-
|
|
12
19
|
const rgba = Util.colorToRGBA(element.color);
|
|
13
20
|
const fillColor = rgbToHex(rgba);
|
|
14
|
-
|
|
15
21
|
if (type === 'arrow') {
|
|
16
|
-
doc
|
|
22
|
+
doc
|
|
23
|
+
.moveTo(element.height * 3, -element.height * 2)
|
|
17
24
|
.lineTo(0, 0)
|
|
18
25
|
.lineTo(element.height * 3, element.height * 2);
|
|
19
|
-
doc.stroke()
|
|
20
|
-
return
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
doc.polygon([element.height * 3, -element.height * 2],
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
[0, 0],
|
|
30
|
-
[0, element.height * 2]);
|
|
31
|
-
|
|
32
|
-
} else if (type === 'circle') {
|
|
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') {
|
|
33
36
|
doc.circle(element.height * 2, 0, element.height * 2);
|
|
34
|
-
|
|
35
|
-
} else if (type === 'square') {
|
|
36
|
-
doc.rect(0, -element.height * 2,
|
|
37
|
-
element.height * 4,
|
|
38
|
-
element.height * 4);
|
|
39
|
-
|
|
40
|
-
} else {
|
|
41
|
-
return null;
|
|
42
37
|
}
|
|
43
|
-
|
|
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
44
|
doc.fillAndStroke(fillColor, fillColor);
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
module.exports.lineToPDF = (doc, element) => {
|
|
45
|
+
}
|
|
46
|
+
export function lineToPDF(doc, element) {
|
|
48
47
|
doc.translate(0, element.height / 2);
|
|
49
48
|
doc.lineWidth(element.height);
|
|
50
49
|
doc.moveTo(0, 0);
|
|
51
50
|
doc.lineTo(element.width, 0);
|
|
52
|
-
|
|
53
51
|
if (element.dash && element.dash.length > 0) {
|
|
54
|
-
doc.dash(element.dash.map(dash => dash * element.height));
|
|
52
|
+
doc.dash(element.dash.map((dash) => dash * element.height));
|
|
55
53
|
}
|
|
56
|
-
|
|
57
54
|
const rgba = Util.colorToRGBA(element.color);
|
|
58
55
|
doc.strokeColor(rgbToHex(rgba));
|
|
59
56
|
doc.stroke();
|
|
60
|
-
|
|
61
57
|
if (element.dash && element.dash.length > 0) {
|
|
62
58
|
doc.undash();
|
|
63
59
|
}
|
|
64
|
-
|
|
65
|
-
getLineHead({
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
// Ensure each value is within the valid range
|
|
71
|
-
r = Math.max(0, Math.min(255, r));
|
|
72
|
-
g = Math.max(0, Math.min(255, g));
|
|
73
|
-
b = Math.max(0, Math.min(255, b));
|
|
74
|
-
|
|
75
|
-
// Convert each value to a 2-digit hexadecimal string
|
|
76
|
-
const hexR = r.toString(16).padStart(2, '0');
|
|
77
|
-
const hexG = g.toString(16).padStart(2, '0');
|
|
78
|
-
const hexB = b.toString(16).padStart(2, '0');
|
|
79
|
-
|
|
80
|
-
// Return the concatenated hex string
|
|
81
|
-
return `#${hexR}${hexG}${hexB}`;
|
|
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
|
+
});
|
|
82
66
|
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
export interface SpotColorDefinition {
|
|
2
|
+
name?: string;
|
|
3
|
+
cmyk?: number[];
|
|
4
|
+
}
|
|
5
|
+
export interface SpotColorConfig {
|
|
6
|
+
[color: string]: SpotColorDefinition;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Normalize color to a consistent format for matching using Konva's color parser
|
|
10
|
+
* This ensures consistency with how Polotno/Konva handles colors
|
|
11
|
+
* @param color - Color in any format (string or [r, g, b, a] array)
|
|
12
|
+
* @returns Normalized color string in rgba format
|
|
13
|
+
*/
|
|
14
|
+
export declare function normalizeColor(color: string | number[]): string | null;
|
|
15
|
+
/**
|
|
16
|
+
* Get spot color definition for a given color
|
|
17
|
+
* @param color - Color to check
|
|
18
|
+
* @param spotColorConfig - Spot color configuration
|
|
19
|
+
* @returns Spot color definition or null
|
|
20
|
+
*/
|
|
21
|
+
export declare function getSpotColorForColor(color: string, spotColorConfig: SpotColorConfig): SpotColorDefinition | null;
|
|
22
|
+
/**
|
|
23
|
+
* Register a spot color using PDFKit's built-in addSpotColor method
|
|
24
|
+
* @param doc - PDFKit document
|
|
25
|
+
* @param spotName - Name of the spot color
|
|
26
|
+
* @param spotColorDef - Spot color definition with CMYK fallback
|
|
27
|
+
* @returns Reference to the registered spot color
|
|
28
|
+
*/
|
|
29
|
+
export declare function registerSpotColor(doc: any, spotName: string, spotColorDef: SpotColorDefinition): {
|
|
30
|
+
name: string;
|
|
31
|
+
cmyk: number[];
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* Enable spot color support on a PDFDocument by intercepting color methods
|
|
35
|
+
* @param doc - PDFKit document
|
|
36
|
+
* @param spotColorConfig - Spot color configuration mapping
|
|
37
|
+
*/
|
|
38
|
+
export declare function enableSpotColorSupport(doc: any, spotColorConfig: SpotColorConfig): void;
|