@polotno/pdf-export 0.1.38 → 0.1.39

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/svg.d.ts DELETED
@@ -1,12 +0,0 @@
1
- import { ImageCache } from './utils.js';
2
- export declare function urlToBase64(url: string, cache?: ImageCache | null): Promise<string>;
3
- export declare function urlToString(url: string, cache?: ImageCache | null): Promise<string>;
4
- export declare function getColors(svgString: string): string[];
5
- export declare function svgToURL(s: string): string;
6
- export declare function getSvgSize(url: string): Promise<{
7
- width: number;
8
- height: number;
9
- }>;
10
- export declare function fixSize(svgString: string): string;
11
- export declare const sameColors: (color1: any, color2: any) => boolean;
12
- export declare function replaceColors(svgString: string, replaceMap: Map<string, string>): string;
package/lib/svg.js DELETED
@@ -1,224 +0,0 @@
1
- import { Util } from 'konva/lib/Util.js';
2
- import { DOMParser, XMLSerializer } from 'xmldom';
3
- import { fetchWithTimeout } from './utils.js';
4
- function isInsideDef(element) {
5
- while (element.parentNode) {
6
- if (element.nodeName === 'defs') {
7
- return true;
8
- }
9
- element = element.parentNode;
10
- }
11
- return false;
12
- }
13
- function parseStyleAttribute(style) {
14
- if (!style) {
15
- return {};
16
- }
17
- const styles = style.split(';');
18
- const result = {};
19
- styles.forEach((style) => {
20
- const [key, value] = style.split(':');
21
- if (key && value) {
22
- result[key] = value;
23
- }
24
- });
25
- return result;
26
- }
27
- function buildStyleAttribute(styleObject) {
28
- return Object.keys(styleObject)
29
- .map((key) => `${key}:${styleObject[key]}`)
30
- .join(';');
31
- }
32
- function getElementColors(e) {
33
- const style = parseStyleAttribute(e.getAttribute('style'));
34
- const colors = {
35
- fill: '',
36
- stroke: '',
37
- };
38
- if (e.getAttribute('fill') && e.getAttribute('fill') !== 'none') {
39
- colors.fill = e.getAttribute('fill');
40
- }
41
- if (!colors.fill && style && style.fill && style.fill !== 'none') {
42
- colors.fill = style.fill;
43
- }
44
- if (e.getAttribute('stroke')) {
45
- colors.stroke = e.getAttribute('stroke');
46
- }
47
- if (!colors.stroke && style && style.stroke) {
48
- colors.stroke = style.stroke;
49
- }
50
- if (!colors.stroke && !colors.fill) {
51
- colors.fill = 'black';
52
- }
53
- return colors;
54
- }
55
- const SVG_SHAPES = ['path', 'rect', 'circle'];
56
- function getAllElementsWithColor(doc) {
57
- var matchingElements = [];
58
- var allElements = doc.getElementsByTagName('*');
59
- for (var i = 0, n = allElements.length; i < n; i++) {
60
- const element = allElements[i];
61
- if (isInsideDef(element)) {
62
- continue;
63
- }
64
- if (element.getAttribute('fill') !== null) {
65
- matchingElements.push(element);
66
- }
67
- const style = element.getAttribute('style');
68
- if (style != null && style.indexOf('fill') >= 0) {
69
- matchingElements.push(element);
70
- }
71
- if (element.getAttribute('stroke') !== null) {
72
- matchingElements.push(element);
73
- }
74
- else if (element.style && element.style['fill']) {
75
- matchingElements.push(element);
76
- }
77
- else if (SVG_SHAPES.indexOf(element.nodeName) >= 0) {
78
- matchingElements.push(element);
79
- }
80
- }
81
- return matchingElements;
82
- }
83
- export async function urlToBase64(url, cache = null) {
84
- // Check cache first
85
- if (cache && cache.buffers.has(url)) {
86
- return cache.buffers.get(url);
87
- }
88
- const req = await fetchWithTimeout(url);
89
- let result;
90
- if (req.arrayBuffer) {
91
- const buffer = Buffer.from(await req.arrayBuffer());
92
- result = `data:image/svg+xml;base64,${buffer.toString('base64')}`;
93
- }
94
- else {
95
- const svgString = await req.text();
96
- result = svgToURL(svgString);
97
- }
98
- // Store in cache
99
- if (cache) {
100
- cache.buffers.set(url, result);
101
- }
102
- return result;
103
- }
104
- export async function urlToString(url, cache = null) {
105
- // Check cache first
106
- if (cache && cache.buffers.has(url)) {
107
- const cached = cache.buffers.get(url);
108
- // If cached value is a base64 data URL, decode it
109
- if (cached.startsWith('data:')) {
110
- return Buffer.from(cached.split('base64,')[1], 'base64').toString();
111
- }
112
- return cached;
113
- }
114
- let svgString;
115
- if (url.startsWith('data:')) {
116
- svgString = Buffer.from(url.split('base64,')[1], 'base64').toString();
117
- }
118
- else {
119
- const req = await fetchWithTimeout(url);
120
- svgString = await req.text();
121
- }
122
- // Store in cache
123
- if (cache) {
124
- cache.buffers.set(url, svgString);
125
- }
126
- return svgString;
127
- }
128
- export function getColors(svgString) {
129
- var parser = new DOMParser();
130
- var doc = parser.parseFromString(svgString, 'text/xml');
131
- const elements = getAllElementsWithColor(doc);
132
- const colors = [];
133
- elements.forEach((e) => {
134
- const { fill, stroke } = getElementColors(e);
135
- const results = [fill, stroke];
136
- results.forEach((color) => {
137
- if (!color) {
138
- return;
139
- }
140
- const rgba = Util.colorToRGBA(color);
141
- if (!rgba) {
142
- return;
143
- }
144
- if (colors.indexOf(color) === -1) {
145
- colors.push(color);
146
- }
147
- });
148
- });
149
- return colors;
150
- }
151
- export function svgToURL(s) {
152
- const uri = Buffer.from(unescape(encodeURIComponent(s))).toString('base64');
153
- return 'data:image/svg+xml;base64,' + uri;
154
- }
155
- export async function getSvgSize(url) {
156
- const svgString = await urlToString(url);
157
- var parser = new DOMParser();
158
- var doc = parser.parseFromString(svgString, 'image/svg+xml');
159
- const viewBox = doc.documentElement.getAttribute('viewBox');
160
- const [x, y, width, height] = viewBox?.split(' ') || [];
161
- return { width: parseFloat(width), height: parseFloat(height) };
162
- }
163
- export function fixSize(svgString) {
164
- var parser = new DOMParser();
165
- var doc = parser.parseFromString(svgString, 'image/svg+xml');
166
- const viewBox = doc.documentElement.getAttribute('viewBox');
167
- const [x, y, width, height] = viewBox?.split(' ') || [];
168
- if (!doc.documentElement.getAttribute('width')) {
169
- doc.documentElement.setAttribute('width', width + 'px');
170
- }
171
- if (!doc.documentElement.getAttribute('height')) {
172
- doc.documentElement.setAttribute('height', height + 'px');
173
- }
174
- var xmlSerializer = new XMLSerializer();
175
- const str = xmlSerializer.serializeToString(doc);
176
- return str;
177
- }
178
- export const sameColors = (color1, color2) => {
179
- if (!color1 || !color2) {
180
- return false;
181
- }
182
- if (color2 === 'currentColor' && color1 === 'black') {
183
- return true;
184
- }
185
- const c1 = Util.colorToRGBA(color1);
186
- const c2 = Util.colorToRGBA(color2);
187
- if (!c1 || !c2) {
188
- return;
189
- }
190
- return c1.r === c2.r && c1.g === c2.g && c1.b === c2.b && c1.a === c2.a;
191
- };
192
- export function replaceColors(svgString, replaceMap) {
193
- var parser = new DOMParser();
194
- var doc = parser.parseFromString(svgString, 'text/xml');
195
- const elements = getAllElementsWithColor(doc);
196
- const oldColors = Array.from(replaceMap.keys());
197
- elements.forEach((el) => {
198
- const { fill, stroke } = getElementColors(el);
199
- const colors = [
200
- { prop: 'fill', color: fill },
201
- { prop: 'stroke', color: stroke },
202
- ];
203
- colors.forEach(({ prop, color }) => {
204
- // find matched oldColor
205
- const marchedOldValue = oldColors.find((oldColor) => {
206
- return sameColors(oldColor, color);
207
- });
208
- if (!marchedOldValue) {
209
- return;
210
- }
211
- else {
212
- el.setAttribute(prop, replaceMap.get(marchedOldValue));
213
- const style = parseStyleAttribute(el.getAttribute('style'));
214
- if (style && style[prop]) {
215
- style[prop] = replaceMap.get(marchedOldValue);
216
- el.setAttribute('style', buildStyleAttribute(style));
217
- }
218
- }
219
- });
220
- });
221
- var xmlSerializer = new XMLSerializer();
222
- const str = xmlSerializer.serializeToString(doc);
223
- return svgToURL(str);
224
- }
@@ -1,16 +0,0 @@
1
- import { TextElement, TextSegment } from './types.js';
2
- export declare function registerFontUrl(fontFamily: string, url: string): void;
3
- /**
4
- * Get font weight string based on bold/italic state
5
- */
6
- export declare function getFontWeight(bold: boolean, italic: boolean, baseFontWeight?: string): string;
7
- /**
8
- * Get font key for caching
9
- */
10
- export declare function getFontKey(fontFamily: string, bold: boolean, italic: boolean, baseFontWeight?: string): string;
11
- export declare function getGoogleFontPath(fontFamily: string, fontWeight?: string, italic?: boolean): Promise<string>;
12
- /**
13
- * Load font for a text element or segment
14
- */
15
- export declare function loadFontForSegment(doc: any, segment: TextSegment | null, element: TextElement, fonts: Record<string, boolean>): Promise<string>;
16
- export declare function loadFontIfNeeded(doc: any, element: TextElement, fonts: Record<string, boolean>): Promise<string>;
package/lib/text/fonts.js DELETED
@@ -1,113 +0,0 @@
1
- import { srcToBuffer } from '../utils.js';
2
- import getUrls from 'get-urls';
3
- import fetch from 'node-fetch';
4
- const fontUrlRegistry = {};
5
- const patchedPrototypes = new WeakSet();
6
- /**
7
- * Patch fontkit's TTFGlyph._getCBox to handle zero-length glyphs.
8
- * Some fonts (e.g. "Cute Font") have glyphs with equal start/end offsets
9
- * in the loca table (no outline data), but fontkit still tries to read
10
- * from the glyf table at that offset, causing a DataView out-of-bounds error.
11
- */
12
- function patchFontkitGlyphs(doc) {
13
- const fkFont = doc._font?.font;
14
- if (!fkFont || typeof fkFont.getGlyph !== 'function')
15
- return;
16
- const glyph = fkFont.getGlyph(0);
17
- const proto = Object.getPrototypeOf(glyph);
18
- if (!proto._getCBox || patchedPrototypes.has(proto))
19
- return;
20
- const origGetCBox = proto._getCBox;
21
- proto._getCBox = function (internal) {
22
- const loca = this._font.loca;
23
- if (loca) {
24
- const start = loca.offsets[this.id];
25
- const end = loca.offsets[this.id + 1];
26
- if (start === end) {
27
- return Object.freeze({ minX: 0, minY: 0, maxX: 0, maxY: 0 });
28
- }
29
- }
30
- return origGetCBox.call(this, internal);
31
- };
32
- patchedPrototypes.add(proto);
33
- }
34
- export function registerFontUrl(fontFamily, url) {
35
- fontUrlRegistry[fontFamily] = url;
36
- }
37
- /**
38
- * Get font weight string based on bold/italic state
39
- */
40
- export function getFontWeight(bold, italic, baseFontWeight) {
41
- if (bold) {
42
- return 'bold';
43
- }
44
- return baseFontWeight || 'normal';
45
- }
46
- /**
47
- * Get font key for caching
48
- */
49
- export function getFontKey(fontFamily, bold, italic, baseFontWeight) {
50
- const weight = getFontWeight(bold, italic, baseFontWeight);
51
- const style = italic ? 'italic' : 'normal';
52
- return `${fontFamily}-${weight}-${style}`;
53
- }
54
- export async function getGoogleFontPath(fontFamily, fontWeight = 'normal', italic = false) {
55
- const weight = fontWeight === 'bold' ? '700' : '400';
56
- const italicParam = italic ? 'italic' : '';
57
- const url = `https://fonts.googleapis.com/css?family=${fontFamily}:${italicParam}${weight}`;
58
- const req = await fetch(url);
59
- if (!req.ok) {
60
- if (weight !== '400' || italic) {
61
- // Fallback: try normal weight without italic
62
- return getGoogleFontPath(fontFamily, 'normal', false);
63
- }
64
- throw new Error(`Failed to fetch Google font: ${fontFamily}`);
65
- }
66
- const text = await req.text();
67
- const urls = getUrls(text);
68
- return urls.values().next().value;
69
- }
70
- /**
71
- * Load font for a text element or segment
72
- */
73
- export async function loadFontForSegment(doc, segment, element, fonts) {
74
- const fontFamily = element.fontFamily;
75
- // Determine bold/italic from segment or element
76
- const bold = segment
77
- ? segment.bold || element.fontWeight == 'bold' || false
78
- : element.fontWeight == 'bold';
79
- const italic = segment
80
- ? segment.italic || element.fontStyle?.indexOf('italic') >= 0 || false
81
- : element.fontStyle?.indexOf('italic') >= 0 || false;
82
- // Check if universal font is already defined
83
- if (fonts[fontFamily]) {
84
- doc.font(fontFamily);
85
- patchFontkitGlyphs(doc);
86
- return fontFamily;
87
- }
88
- const fontKey = getFontKey(fontFamily, bold, italic, element.fontWeight);
89
- if (!fonts[fontKey]) {
90
- let src;
91
- if (fontUrlRegistry[fontFamily]) {
92
- src = fontUrlRegistry[fontFamily];
93
- }
94
- else {
95
- const weight = getFontWeight(bold, italic, element.fontWeight);
96
- src = await getGoogleFontPath(fontFamily, weight, italic);
97
- }
98
- try {
99
- doc.registerFont(fontKey, await srcToBuffer(src));
100
- }
101
- catch (error) {
102
- throw new Error(`Failed to load font "${fontFamily}" from ${src}: ${error.message}`);
103
- }
104
- fonts[fontKey] = true;
105
- }
106
- doc.font(fontKey);
107
- patchFontkitGlyphs(doc);
108
- return fontKey;
109
- }
110
- // Alias for backward compatibility
111
- export async function loadFontIfNeeded(doc, element, fonts) {
112
- return loadFontForSegment(doc, null, element, fonts);
113
- }
@@ -1,8 +0,0 @@
1
- import { TextElement, RenderAttrs } from './types.js';
2
- export * from './types.js';
3
- export { loadFontIfNeeded } from './fonts.js';
4
- export { normalizeRichText, parseHTMLToSegments } from './parser.js';
5
- /**
6
- * Main text rendering function
7
- */
8
- export declare function renderText(doc: PDFKit.PDFDocument, element: TextElement, fonts: Record<string, boolean>, attrs?: RenderAttrs): Promise<void>;
package/lib/text/index.js DELETED
@@ -1,42 +0,0 @@
1
- import { normalizeRichText } from './parser.js';
2
- import { calculateTextMetrics, calculateVerticalAlignment, fitTextToHeight, } from './layout.js';
3
- import { renderTextBackground, renderPDFX1aStroke, renderStandardStroke, renderTextFill, } from './render.js';
4
- export * from './types.js';
5
- export { loadFontIfNeeded } from './fonts.js';
6
- export { normalizeRichText, parseHTMLToSegments } from './parser.js';
7
- /**
8
- * Main text rendering function
9
- */
10
- export async function renderText(doc, element, fonts, attrs = {}) {
11
- let elementToRender = element;
12
- if (typeof element.text === 'string') {
13
- const normalizedText = normalizeRichText(element.text);
14
- if (normalizedText !== element.text) {
15
- elementToRender = { ...element, text: normalizedText };
16
- }
17
- }
18
- doc.fontSize(elementToRender.fontSize);
19
- const hasStroke = elementToRender.strokeWidth > 0;
20
- const isPDFX1a = attrs.pdfx1a;
21
- // Calculate text metrics and line positioning
22
- const metrics = calculateTextMetrics(doc, elementToRender);
23
- const verticalAlignmentOffset = calculateVerticalAlignment(doc, elementToRender, metrics.textOptions);
24
- // Fit text to element height if needed
25
- fitTextToHeight(doc, elementToRender, metrics.textOptions);
26
- // Calculate final vertical offset
27
- const finalYOffset = verticalAlignmentOffset + metrics.baselineOffset;
28
- // Render background if enabled
29
- renderTextBackground(doc, elementToRender, verticalAlignmentOffset, metrics.textOptions);
30
- // Render text based on stroke and PDF/X-1a requirements
31
- if (hasStroke && isPDFX1a) {
32
- // PDF/X-1a mode: simulate stroke with offset fills
33
- await renderPDFX1aStroke(doc, elementToRender, metrics.textLines, finalYOffset, metrics.lineHeightPx, metrics.textOptions, fonts);
34
- }
35
- else {
36
- // Standard rendering: stroke first, then fill
37
- if (hasStroke) {
38
- await renderStandardStroke(doc, elementToRender, metrics.textLines, finalYOffset, metrics.lineHeightPx, metrics.textOptions, fonts);
39
- }
40
- await renderTextFill(doc, elementToRender, metrics.textLines, finalYOffset, metrics.lineHeightPx, metrics.textOptions, fonts);
41
- }
42
- }
@@ -1,22 +0,0 @@
1
- import { TextElement, TextLine, TextMetrics, RenderSegment, TabExpansionSegment } from './types.js';
2
- /**
3
- * Expand tabs in text with word spacing adjustment for accurate visual alignment.
4
- */
5
- export declare function expandTabsWithWordSpacing(text: string, doc: PDFKit.PDFDocument, textOptions: PDFKit.Mixins.TextOptions, tabSizeInSpaces?: number, currentWidth?: number): {
6
- segments: TabExpansionSegment[];
7
- finalWidth: number;
8
- };
9
- export declare function buildRenderSegmentsForLine(doc: PDFKit.PDFDocument, element: TextElement, lineText: string, textOptions: PDFKit.Mixins.TextOptions, fonts: Record<string, boolean>): Promise<RenderSegment[]>;
10
- export declare function splitTextIntoLines(doc: PDFKit.PDFDocument, element: TextElement, props: PDFKit.Mixins.TextOptions): TextLine[];
11
- export declare function calculateTextMetrics(doc: PDFKit.PDFDocument, element: TextElement): TextMetrics;
12
- export declare function calculateVerticalAlignment(doc: PDFKit.PDFDocument, element: TextElement, textOptions: PDFKit.Mixins.TextOptions): number;
13
- export declare function fitTextToHeight(doc: PDFKit.PDFDocument, element: TextElement, textOptions: PDFKit.Mixins.TextOptions): void;
14
- /**
15
- * Calculate X offset for list markers (not used for text content positioning)
16
- */
17
- export declare function calculateLineXOffset(element: TextElement, line: TextLine): number;
18
- export declare function calculateTextContentXOffset(element: TextElement, line: TextLine): number;
19
- /**
20
- * Calculate effective width for text rendering, considering justify and underline constraints
21
- */
22
- export declare function calculateEffectiveWidth(element: TextElement, line: TextLine, widthOption: number | undefined, hasUnderline: boolean): number | undefined;