@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/text.js DELETED
@@ -1,576 +0,0 @@
1
- import { parseColor, srcToBuffer, fetchWithTimeout } from './utils.js';
2
- import getUrls from 'get-urls';
3
- import { stripHtml } from "string-strip-html";
4
- /**
5
- * Check if text contains HTML tags
6
- */
7
- function containsHTML(text) {
8
- const htmlTagRegex = /<\/?(?:strong|b|em|i|u|span)[^>]*>/i;
9
- return htmlTagRegex.test(text);
10
- }
11
- /**
12
- * Parse HTML text into styled segments
13
- */
14
- function parseHTMLToSegments(html, baseElement) {
15
- const segments = [];
16
- const tagStack = [];
17
- // Regex to match tags and text content
18
- const regex = /<(\/?)(strong|b|em|i|u|span)([^>]*)>|([^<]+)/gi;
19
- let match;
20
- while ((match = regex.exec(html)) !== null) {
21
- if (match[4]) {
22
- // Text content
23
- const text = match[4];
24
- // Calculate current styles from tag stack
25
- let bold = false;
26
- let italic = false;
27
- let underline = false;
28
- let color = undefined;
29
- for (const tag of tagStack) {
30
- if (tag.tag === 'strong' || tag.tag === 'b')
31
- bold = true;
32
- if (tag.tag === 'em' || tag.tag === 'i')
33
- italic = true;
34
- if (tag.tag === 'u')
35
- underline = true;
36
- if (tag.color)
37
- color = tag.color;
38
- }
39
- segments.push({
40
- text,
41
- bold,
42
- italic,
43
- underline,
44
- color
45
- });
46
- }
47
- else {
48
- // Tag
49
- const isClosing = match[1] === '/';
50
- const tagName = match[2].toLowerCase();
51
- const attributes = match[3];
52
- if (isClosing) {
53
- // Remove from stack
54
- const index = tagStack.findIndex(t => t.tag === tagName);
55
- if (index !== -1) {
56
- tagStack.splice(index, 1);
57
- }
58
- }
59
- else {
60
- // Add to stack
61
- const tagData = { tag: tagName };
62
- // Parse color from span style attribute
63
- if (attributes) {
64
- const colorMatch = /style=["'](?:[^"']*)?color:\s*([^;"']+)/i.exec(attributes);
65
- if (colorMatch) {
66
- tagData.color = colorMatch[1].trim();
67
- }
68
- }
69
- tagStack.push(tagData);
70
- }
71
- }
72
- }
73
- return segments;
74
- }
75
- /**
76
- * Get font weight string based on bold/italic state
77
- */
78
- function getFontWeight(bold, italic, baseFontWeight) {
79
- if (bold) {
80
- return 'bold';
81
- }
82
- return baseFontWeight || 'normal';
83
- }
84
- /**
85
- * Get font key for caching
86
- */
87
- function getFontKey(fontFamily, bold, italic, baseFontWeight) {
88
- const weight = getFontWeight(bold, italic, baseFontWeight);
89
- const style = italic ? 'italic' : 'normal';
90
- return `${fontFamily}-${weight}-${style}`;
91
- }
92
- export async function getGoogleFontPath(fontFamily, fontWeight = 'normal', italic = false) {
93
- const weight = fontWeight === 'bold' ? '700' : '400';
94
- const italicParam = italic ? 'italic' : '';
95
- const url = `https://fonts.googleapis.com/css?family=${fontFamily}:${italicParam}${weight}`;
96
- const req = await fetchWithTimeout(url);
97
- if (!req.ok) {
98
- if (weight !== '400' || italic) {
99
- // Fallback: try normal weight without italic
100
- return getGoogleFontPath(fontFamily, 'normal', false);
101
- }
102
- throw new Error(`Failed to fetch Google font: ${fontFamily}`);
103
- }
104
- const text = await req.text();
105
- const urls = getUrls(text);
106
- return urls.values().next().value;
107
- }
108
- export async function loadFontIfNeeded(doc, element, fonts) {
109
- // check if universal font is already defined
110
- if (fonts[element.fontFamily]) {
111
- doc.font(element.fontFamily);
112
- return element.fontFamily;
113
- }
114
- const isItalic = element.fontStyle?.indexOf('italic') >= 0;
115
- const isBold = element.fontWeight == 'bold';
116
- const fontKey = getFontKey(element.fontFamily, isBold, isItalic, element.fontWeight);
117
- if (!fonts[fontKey]) {
118
- const src = await getGoogleFontPath(element.fontFamily, element.fontWeight, isItalic);
119
- doc.registerFont(fontKey, await srcToBuffer(src));
120
- fonts[fontKey] = true;
121
- }
122
- doc.font(fontKey);
123
- return fontKey;
124
- }
125
- /**
126
- * Load font for a rich text segment
127
- */
128
- async function loadFontForSegment(doc, segment, element, fonts) {
129
- const fontFamily = element.fontFamily;
130
- const bold = segment.bold || element.fontWeight == 'bold' || false;
131
- const italic = segment.italic || element.fontStyle?.indexOf('italic') >= 0 || false;
132
- // Check if universal font is already defined
133
- if (fonts[fontFamily]) {
134
- doc.font(fontFamily);
135
- return fontFamily;
136
- }
137
- const fontKey = getFontKey(fontFamily, bold, italic, element.fontWeight);
138
- if (!fonts[fontKey]) {
139
- const weight = getFontWeight(bold, italic, element.fontWeight);
140
- const src = await getGoogleFontPath(fontFamily, weight, italic);
141
- doc.registerFont(fontKey, await srcToBuffer(src));
142
- fonts[fontKey] = true;
143
- }
144
- doc.font(fontKey);
145
- return fontKey;
146
- }
147
- /**
148
- * Parse HTML into tokens (text and tags)
149
- */
150
- function tokenizeHTML(html) {
151
- const tokens = [];
152
- const regex = /<(\/?)(strong|b|em|i|u|span)([^>]*)>|([^<]+)/gi;
153
- let match;
154
- while ((match = regex.exec(html)) !== null) {
155
- if (match[4]) {
156
- // Text content
157
- tokens.push({
158
- type: 'text',
159
- content: match[4]
160
- });
161
- }
162
- else {
163
- // Tag
164
- const isClosing = match[1] === '/';
165
- const tagName = match[2].toLowerCase();
166
- tokens.push({
167
- type: 'tag',
168
- content: match[0],
169
- tagName: tagName,
170
- isClosing: isClosing
171
- });
172
- }
173
- }
174
- return tokens;
175
- }
176
- /**
177
- * Reconstruct HTML from tokens while maintaining proper tag nesting across line breaks
178
- * @param tokens - Array of parsed HTML tokens
179
- * @param openTags - Tags that were opened in previous lines and should be carried forward
180
- * @returns Reconstructed HTML string and the updated list of open tags
181
- */
182
- function tokensToHTML(tokens, openTags) {
183
- let html = '';
184
- const tagStack = [...openTags]; // Clone the open tags
185
- // Prepend any open tags
186
- for (const tag of openTags) {
187
- html += tag.fullTag;
188
- }
189
- // Process tokens
190
- for (const token of tokens) {
191
- if (token.type === 'text') {
192
- html += token.content;
193
- }
194
- else if (token.type === 'tag') {
195
- html += token.content;
196
- if (token.isClosing) {
197
- // Remove from stack
198
- const idx = tagStack.findIndex(t => t.name === token.tagName);
199
- if (idx !== -1) {
200
- tagStack.splice(idx, 1);
201
- }
202
- }
203
- else {
204
- // Add to stack
205
- tagStack.push({
206
- name: token.tagName,
207
- fullTag: token.content
208
- });
209
- }
210
- }
211
- }
212
- // Close any remaining open tags for this line
213
- for (let i = tagStack.length - 1; i >= 0; i--) {
214
- html += `</${tagStack[i].name}>`;
215
- }
216
- return { html, openTags: tagStack };
217
- }
218
- /**
219
- * Split text into lines that fit within the element width while preserving HTML formatting
220
- * Handles word wrapping and ensures HTML tags are properly opened/closed across line breaks
221
- */
222
- function splitTextIntoLines(doc, element, props) {
223
- const lines = [];
224
- const paragraphs = element.text.split('\n');
225
- for (const paragraph of paragraphs) {
226
- // Tokenize the paragraph
227
- const tokens = tokenizeHTML(paragraph);
228
- // Extract plain text for width calculation
229
- const plainText = tokens
230
- .filter(t => t.type === 'text')
231
- .map(t => t.content)
232
- .join('');
233
- const paragraphWidth = doc.widthOfString(plainText, props);
234
- // Justify alignment using native pdfkit instruments
235
- if (paragraphWidth <= element.width || element.align === 'justify') {
236
- // Paragraph fits on one line
237
- lines.push({ text: paragraph, width: paragraphWidth });
238
- }
239
- else {
240
- // Need to split paragraph into multiple lines
241
- let currentLine = '';
242
- let currentWidth = 0;
243
- let currentTokens = [];
244
- let openTags = [];
245
- for (const token of tokens) {
246
- if (token.type === 'tag') {
247
- currentTokens.push(token);
248
- continue;
249
- }
250
- // Text token - split by words
251
- const textWords = token.content.split(' ');
252
- for (let i = 0; i < textWords.length; i++) {
253
- const word = textWords[i];
254
- const testLine = currentLine ? `${currentLine}${i > 0 ? ' ' : ''}${word}` : word;
255
- const testWidth = doc.widthOfString(testLine, props);
256
- if (testWidth <= element.width) {
257
- currentLine = testLine;
258
- currentWidth = testWidth;
259
- // Add text token (with space if not first word in token)
260
- if (i > 0 || currentTokens.length > 0) {
261
- let content = (i > 0 ? ' ' : '') + word;
262
- currentTokens.push({
263
- type: 'text',
264
- content: content
265
- });
266
- }
267
- else {
268
- currentTokens.push({
269
- type: 'text',
270
- content: word
271
- });
272
- }
273
- }
274
- else {
275
- // Line is too long, save current line and start new one
276
- if (currentLine) {
277
- const result = tokensToHTML(currentTokens, openTags);
278
- lines.push({ text: result.html, width: currentWidth });
279
- openTags = result.openTags;
280
- currentTokens = [];
281
- }
282
- currentLine = word;
283
- currentWidth = doc.widthOfString(word, props);
284
- currentTokens.push({
285
- type: 'text',
286
- content: word
287
- });
288
- }
289
- }
290
- }
291
- // Add the last line
292
- if (currentLine) {
293
- const result = tokensToHTML(currentTokens, openTags);
294
- lines.push({ text: result.html, width: currentWidth });
295
- }
296
- }
297
- }
298
- return lines;
299
- }
300
- /**
301
- * Calculate horizontal offset for a line of text based on alignment
302
- * @param element - Text element with alignment settings
303
- * @param lineWidth - Width of the current line
304
- * @returns X offset for positioning the line
305
- */
306
- function calculateLineXOffset(element, lineWidth) {
307
- const align = element.align;
308
- if (align === 'right') {
309
- return element.width - lineWidth;
310
- }
311
- else if (align === 'center') {
312
- return (element.width - lineWidth) / 2;
313
- }
314
- else if (align === 'justify') {
315
- // Justify alignment is handled by PDFKit's align property
316
- return 0;
317
- }
318
- // Default: left alignment
319
- return 0;
320
- }
321
- /**
322
- * Calculate text rendering metrics including line height and baseline offset
323
- */
324
- function calculateTextMetrics(doc, element) {
325
- const textOptions = {
326
- align: element.align === 'justify' ? 'justify' : 'left',
327
- baseline: 'alphabetic',
328
- lineGap: 1,
329
- width: element.width,
330
- underline: element.textDecoration.indexOf('underline') >= 0,
331
- characterSpacing: element.letterSpacing
332
- ? element.letterSpacing * element.fontSize
333
- : 0,
334
- lineBreak: false,
335
- stroke: false,
336
- fill: false
337
- };
338
- const currentLineHeight = doc.heightOfString('A', textOptions);
339
- const lineHeight = element.lineHeight * element.fontSize;
340
- const fontBoundingBoxAscent = (doc._font.ascender / 1000) * element.fontSize;
341
- const fontBoundingBoxDescent = (doc._font.descender / 1000) * element.fontSize;
342
- // Calculate baseline offset based on font metrics (similar to Konva rendering)
343
- const baselineOffset = (fontBoundingBoxAscent - Math.abs(fontBoundingBoxDescent)) / 2 + lineHeight / 2;
344
- // Adjust line gap to match desired line height
345
- const lineHeightDiff = currentLineHeight - lineHeight;
346
- textOptions.lineGap = textOptions.lineGap - lineHeightDiff;
347
- const textLines = splitTextIntoLines(doc, element, textOptions);
348
- return {
349
- textOptions,
350
- lineHeightPx: lineHeight,
351
- baselineOffset,
352
- textLines
353
- };
354
- }
355
- /**
356
- * Calculate vertical alignment offset for text
357
- */
358
- function calculateVerticalAlignment(doc, element, textOptions) {
359
- if (!element.verticalAlign || element.verticalAlign === 'top') {
360
- return 0;
361
- }
362
- const strippedContent = stripHtml(element.text).result;
363
- const textHeight = doc.heightOfString(strippedContent, textOptions);
364
- if (element.verticalAlign === 'middle') {
365
- return (element.height - textHeight) / 2;
366
- }
367
- else if (element.verticalAlign === 'bottom') {
368
- return element.height - textHeight;
369
- }
370
- return 0;
371
- }
372
- /**
373
- * Reduce font size to fit text within element height
374
- */
375
- function fitTextToHeight(doc, element, textOptions) {
376
- const strippedContent = stripHtml(element.text).result;
377
- for (let size = element.fontSize; size > 0; size -= 1) {
378
- doc.fontSize(size);
379
- const height = doc.heightOfString(strippedContent, textOptions);
380
- if (height <= element.height) {
381
- break;
382
- }
383
- }
384
- }
385
- /**
386
- * Render text background box
387
- */
388
- function renderTextBackground(doc, element, verticalAlignmentOffset, textOptions) {
389
- if (!element.backgroundEnabled) {
390
- return;
391
- }
392
- const strippedContent = stripHtml(element.text).result;
393
- const padding = element.backgroundPadding * (element.fontSize * element.lineHeight);
394
- const cornerRadius = element.backgroundCornerRadius * (element.fontSize * element.lineHeight * 0.5);
395
- const textWidth = doc.widthOfString(strippedContent, {
396
- ...textOptions,
397
- width: element.width,
398
- });
399
- const textHeight = doc.heightOfString(strippedContent, {
400
- ...textOptions,
401
- width: element.width,
402
- });
403
- let bgX = -padding / 2;
404
- let bgY = verticalAlignmentOffset - padding / 2;
405
- const bgWidth = textWidth + padding;
406
- const bgHeight = textHeight + padding;
407
- // Adjust horizontal position based on text alignment
408
- if (element.align === 'center') {
409
- bgX = (element.width - textWidth) / 2 - padding / 2;
410
- }
411
- else if (element.align === 'right') {
412
- bgX = element.width - textWidth - padding / 2;
413
- }
414
- doc.roundedRect(bgX, bgY, bgWidth, bgHeight, cornerRadius);
415
- doc.fillColor(parseColor(element.backgroundColor).hex);
416
- doc.fill();
417
- doc.fillColor(parseColor(element.fill).hex, element.opacity);
418
- }
419
- /**
420
- * Render text stroke using PDF/X-1a compatible method (multiple offset fills)
421
- */
422
- function renderPDFX1aStroke(doc, element, textLines, yOffset, lineHeightPx, textOptions) {
423
- const strokeColor = parseColor(element.stroke).hex;
424
- const strokeWidth = element.strokeWidth;
425
- const isJustify = element.align === 'justify';
426
- // Generate stroke offsets in a circle pattern
427
- const offsets = [];
428
- for (let angle = 0; angle < 360; angle += 45) {
429
- const radian = (angle * Math.PI) / 180;
430
- offsets.push({
431
- x: Math.cos(radian) * strokeWidth,
432
- y: Math.sin(radian) * strokeWidth,
433
- });
434
- }
435
- // Render stroke layer by drawing text multiple times with offsets
436
- doc.save();
437
- doc.fillColor(strokeColor, element.opacity);
438
- for (let i = 0; i < textLines.length; i++) {
439
- const line = textLines[i];
440
- const lineXOffset = calculateLineXOffset(element, line.width);
441
- const lineYOffset = yOffset + (i * lineHeightPx);
442
- for (const offset of offsets) {
443
- doc.text(line.text, lineXOffset + offset.x, lineYOffset + offset.y, {
444
- ...textOptions,
445
- width: isJustify ? element.width : undefined,
446
- stroke: false,
447
- });
448
- }
449
- }
450
- doc.restore();
451
- // Render fill layer on top
452
- doc.fillColor(parseColor(element.fill).hex, element.opacity);
453
- for (let i = 0; i < textLines.length; i++) {
454
- const line = textLines[i];
455
- const lineXOffset = calculateLineXOffset(element, line.width);
456
- const lineYOffset = yOffset + (i * lineHeightPx);
457
- doc.text(line.text, lineXOffset, lineYOffset, {
458
- ...textOptions,
459
- width: isJustify ? element.width : undefined,
460
- stroke: false,
461
- });
462
- }
463
- }
464
- /**
465
- * Render text stroke using standard PDF stroke
466
- */
467
- function renderStandardStroke(doc, element, textLines, yOffset, lineHeightPx, textOptions) {
468
- const isJustify = element.align === 'justify';
469
- doc.save();
470
- doc.lineWidth(element.strokeWidth);
471
- doc.lineCap('round').lineJoin('round');
472
- doc.strokeColor(parseColor(element.stroke).hex, element.opacity);
473
- let cumulativeYOffset = 0;
474
- for (let i = 0; i < textLines.length; i++) {
475
- const line = textLines[i];
476
- const lineXOffset = calculateLineXOffset(element, line.width);
477
- const lineYOffset = yOffset + cumulativeYOffset;
478
- const strippedLineText = stripHtml(line.text).result;
479
- const heightOfLine = line.text === ''
480
- ? lineHeightPx
481
- : doc.heightOfString(strippedLineText, textOptions);
482
- cumulativeYOffset += heightOfLine;
483
- doc.text(line.text, lineXOffset, lineYOffset, {
484
- ...textOptions,
485
- width: isJustify ? element.width : undefined,
486
- height: heightOfLine,
487
- stroke: true,
488
- fill: false
489
- });
490
- }
491
- doc.restore();
492
- }
493
- /**
494
- * Render text fill with rich text support (HTML segments)
495
- */
496
- async function renderTextFill(doc, element, textLines, yOffset, lineHeightPx, textOptions, fonts) {
497
- if (!element.fill) {
498
- return;
499
- }
500
- const baseParsedColor = parseColor(element.fill);
501
- const baseOpacity = Math.min(baseParsedColor.rgba[3] ?? 1, element.opacity, 1);
502
- doc.fillColor(baseParsedColor.hex, baseOpacity);
503
- const isJustify = element.align === 'justify';
504
- let cumulativeYOffset = 0;
505
- for (let i = 0; i < textLines.length; i++) {
506
- const line = textLines[i];
507
- const lineXOffset = calculateLineXOffset(element, line.width);
508
- const lineYOffset = yOffset + cumulativeYOffset;
509
- const strippedLineText = stripHtml(line.text).result;
510
- const heightOfLine = line.text === ''
511
- ? lineHeightPx
512
- : doc.heightOfString(strippedLineText, textOptions);
513
- cumulativeYOffset += heightOfLine;
514
- // Position cursor at line start
515
- doc.text('', lineXOffset, lineYOffset, { height: 0, width: 0 });
516
- // Parse line into styled segments
517
- const segments = parseHTMLToSegments(line.text, element);
518
- // Render each segment with its own styling
519
- for (let segmentIndex = 0; segmentIndex < segments.length; segmentIndex++) {
520
- const segment = segments[segmentIndex];
521
- const isLastSegment = segmentIndex === segments.length - 1;
522
- // Load appropriate font for this segment
523
- await loadFontForSegment(doc, segment, element, fonts);
524
- doc.fontSize(element.fontSize);
525
- // Apply segment color
526
- const segmentColor = segment.color
527
- ? parseColor(segment.color).hex
528
- : parseColor(element.fill).hex;
529
- const segmentParsedColor = segment.color
530
- ? parseColor(segment.color)
531
- : parseColor(element.fill);
532
- const segmentOpacity = Math.min(segmentParsedColor.rgba[3] ?? 1, element.opacity, 1);
533
- doc.fillColor(segmentColor, segmentOpacity);
534
- // Render segment text
535
- doc.text(segment.text, {
536
- ...textOptions,
537
- width: isJustify ? element.width : undefined,
538
- height: heightOfLine,
539
- continued: !isLastSegment,
540
- underline: segment.underline || textOptions.underline || false,
541
- lineBreak: !!segment.underline, // Workaround for pdfkit bug
542
- stroke: false,
543
- fill: true
544
- });
545
- }
546
- }
547
- }
548
- /**
549
- * Main text rendering function
550
- */
551
- export async function renderText(doc, element, fonts, attrs = {}) {
552
- doc.fontSize(element.fontSize);
553
- const hasStroke = element.strokeWidth > 0;
554
- const isPDFX1a = attrs.pdfx1a;
555
- // Calculate text metrics and line positioning
556
- const metrics = calculateTextMetrics(doc, element);
557
- const verticalAlignmentOffset = calculateVerticalAlignment(doc, element, metrics.textOptions);
558
- // Fit text to element height if needed
559
- fitTextToHeight(doc, element, metrics.textOptions);
560
- // Calculate final vertical offset
561
- const finalYOffset = verticalAlignmentOffset + metrics.baselineOffset;
562
- // Render background if enabled
563
- renderTextBackground(doc, element, verticalAlignmentOffset, metrics.textOptions);
564
- // Render text based on stroke and PDF/X-1a requirements
565
- if (hasStroke && isPDFX1a) {
566
- // PDF/X-1a mode: simulate stroke with offset fills
567
- renderPDFX1aStroke(doc, element, metrics.textLines, finalYOffset, metrics.lineHeightPx, metrics.textOptions);
568
- }
569
- else {
570
- // Standard rendering: stroke first, then fill
571
- if (hasStroke) {
572
- renderStandardStroke(doc, element, metrics.textLines, finalYOffset, metrics.lineHeightPx, metrics.textOptions);
573
- }
574
- await renderTextFill(doc, element, metrics.textLines, finalYOffset, metrics.lineHeightPx, metrics.textOptions, fonts);
575
- }
576
- }
package/lib/utils.d.ts DELETED
@@ -1,16 +0,0 @@
1
- import parseColor from 'parse-color';
2
- export declare const DPI = 75;
3
- export declare const PIXEL_RATIO = 2;
4
- export declare function fetchWithTimeout(url: string, timeout?: number, retries?: number): Promise<any>;
5
- export declare function pxToPt(px: number): number;
6
- export interface ImageCache {
7
- images: Map<string, any>;
8
- buffers: Map<string, any>;
9
- processedImages: Map<string, string>;
10
- imageFiles: Map<string, string>;
11
- tempDir: string | null;
12
- }
13
- export declare function loadImage(src: string, cache?: ImageCache | null): Promise<any>;
14
- export declare function srcToBase64(src: string, cache?: ImageCache | null): Promise<string>;
15
- export declare function srcToBuffer(src: string, cache?: ImageCache | null): Promise<Buffer>;
16
- export { parseColor };