@flowaccount/pdfmake 1.0.5-staging.0 → 1.0.6-staging.2

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/src/printer.js CHANGED
@@ -1,1191 +1,1191 @@
1
- /*eslint no-unused-vars: ["error", {"args": "none"}]*/
2
- 'use strict';
3
-
4
- var PdfKitEngine = require('./pdfKitEngine');
5
- var FontProvider = require('./fontProvider');
6
- var LayoutBuilder = require('./layoutBuilder');
7
- var sizes = require('./standardPageSizes');
8
- var ImageMeasure = require('./imageMeasure');
9
- var SVGMeasure = require('./svgMeasure');
10
- var textDecorator = require('./textDecorator');
11
- var TextTools = require('./textTools');
12
- var isFunction = require('./helpers').isFunction;
13
- var isString = require('./helpers').isString;
14
- var isNumber = require('./helpers').isNumber;
15
- var isBoolean = require('./helpers').isBoolean;
16
- var isArray = require('./helpers').isArray;
17
- var isUndefined = require('./helpers').isUndefined;
18
- var isPattern = require('./helpers').isPattern;
19
- var getPattern = require('./helpers').getPattern;
20
- var SVGtoPDF = require('./3rd-party/svg-to-pdfkit');
21
-
22
- var REMOTE_RESOLVED_KEY = '__pdfMakeRemoteImagesResolved';
23
- var REMOTE_PROTOCOL_REGEX = /^https?:\/\//i;
24
- var DATA_URL_REGEX = /^data:/i;
25
- var TRANSPARENT_PNG_PLACEHOLDER = (typeof Buffer !== 'undefined') ? Buffer.from('iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQImWM8c+bMfQAI1gP2Ce279wAAAABJRU5ErkJggg==', 'base64') : null;
26
-
27
- var findFont = function (fonts, requiredFonts, defaultFont) {
28
- for (var i = 0; i < requiredFonts.length; i++) {
29
- var requiredFont = requiredFonts[i].toLowerCase();
30
-
31
- for (var font in fonts) {
32
- if (font.toLowerCase() === requiredFont) {
33
- return font;
34
- }
35
- }
36
- }
37
-
38
- return defaultFont;
39
- };
40
-
41
- ////////////////////////////////////////
42
- // PdfPrinter
43
-
44
- /**
45
- * @class Creates an instance of a PdfPrinter which turns document definition into a pdf
46
- *
47
- * @param {Object} fontDescriptors font definition dictionary
48
- * @param {Object} [options] optional configuration
49
- * @param {Number} [options.maxImageCacheSize] maximum number of images to cache (default: 100)
50
- * @param {Number} [options.imageCacheTTL] cache time-to-live in milliseconds (default: 3600000 = 1 hour)
51
- *
52
- * @example
53
- * var fontDescriptors = {
54
- * Roboto: {
55
- * normal: 'fonts/Roboto-Regular.ttf',
56
- * bold: 'fonts/Roboto-Medium.ttf',
57
- * italics: 'fonts/Roboto-Italic.ttf',
58
- * bolditalics: 'fonts/Roboto-MediumItalic.ttf'
59
- * }
60
- * };
61
- *
62
- * var printer = new PdfPrinter(fontDescriptors, {
63
- * maxImageCacheSize: 50,
64
- * imageCacheTTL: 30 * 60 * 1000 // 30 minutes
65
- * });
66
- */
67
- function PdfPrinter(fontDescriptors, options) {
68
- this.fontDescriptors = fontDescriptors;
69
- options = options || {};
70
-
71
- // Cache configuration with memory leak prevention
72
- this._cacheConfig = {
73
- maxSize: isNumber(options.maxImageCacheSize) ? options.maxImageCacheSize : 100,
74
- ttl: isNumber(options.imageCacheTTL) ? options.imageCacheTTL : 3600000 // 1 hour default
75
- };
76
-
77
- // LRU cache: Map maintains insertion order, so we can implement LRU easily
78
- this._remoteImageCache = new Map();
79
- }
80
-
81
- /**
82
- * Executes layout engine for the specified document and renders it into a pdfkit document
83
- * ready to be saved.
84
- *
85
- * @param {Object} docDefinition document definition
86
- * @param {Object} docDefinition.content an array describing the pdf structure (for more information take a look at the examples in the /examples folder)
87
- * @param {Object} [docDefinition.defaultStyle] default (implicit) style definition
88
- * @param {Object} [docDefinition.styles] dictionary defining all styles which can be used in the document
89
- * @param {Object} [docDefinition.pageSize] page size (pdfkit units, A4 dimensions by default)
90
- * @param {Number} docDefinition.pageSize.width width
91
- * @param {Number} docDefinition.pageSize.height height
92
- * @param {Object} [docDefinition.pageMargins] page margins (pdfkit units)
93
- * @param {Number} docDefinition.maxPagesNumber maximum number of pages to render
94
- *
95
- * @example
96
- *
97
- * var docDefinition = {
98
- * info: {
99
- * title: 'awesome Document',
100
- * author: 'john doe',
101
- * subject: 'subject of document',
102
- * keywords: 'keywords for document',
103
- * },
104
- * content: [
105
- * 'First paragraph',
106
- * 'Second paragraph, this time a little bit longer',
107
- * { text: 'Third paragraph, slightly bigger font size', fontSize: 20 },
108
- * { text: 'Another paragraph using a named style', style: 'header' },
109
- * { text: ['playing with ', 'inlines' ] },
110
- * { text: ['and ', { text: 'restyling ', bold: true }, 'them'] },
111
- * ],
112
- * styles: {
113
- * header: { fontSize: 30, bold: true }
114
- * },
115
- * patterns: {
116
- * stripe45d: {
117
- * boundingBox: [1, 1, 4, 4],
118
- * xStep: 3,
119
- * yStep: 3,
120
- * pattern: '1 w 0 1 m 4 5 l s 2 0 m 5 3 l s'
121
- * }
122
- * }
123
- * };
124
- *
125
- * var pdfKitDoc = printer.createPdfKitDocument(docDefinition);
126
- *
127
- * pdfKitDoc.pipe(fs.createWriteStream('sample.pdf'));
128
- * pdfKitDoc.end();
129
- *
130
- * @return {Object} a pdfKit document object which can be saved or encode to data-url
131
- */
132
- PdfPrinter.prototype.createPdfKitDocument = function (docDefinition, options) {
133
- if (!docDefinition || typeof docDefinition !== 'object') {
134
- throw new Error('docDefinition parameter is required and must be an object');
135
- }
136
-
137
- options = options || {};
138
-
139
- docDefinition.version = docDefinition.version || '1.3';
140
- docDefinition.subset = docDefinition.subset || undefined;
141
- docDefinition.tagged = typeof docDefinition.tagged === 'boolean' ? docDefinition.tagged : false;
142
- docDefinition.displayTitle = typeof docDefinition.displayTitle === 'boolean' ? docDefinition.displayTitle : false;
143
- docDefinition.compress = isBoolean(docDefinition.compress) ? docDefinition.compress : true;
144
- docDefinition.images = docDefinition.images || {};
145
- docDefinition.pageMargins = ((docDefinition.pageMargins !== undefined) && (docDefinition.pageMargins !== null)) ? docDefinition.pageMargins : 40;
146
-
147
- var pageSize = fixPageSize(docDefinition.pageSize, docDefinition.pageOrientation);
148
-
149
- var pdfOptions = {
150
- size: [pageSize.width, pageSize.height],
151
- pdfVersion: docDefinition.version,
152
- subset: docDefinition.subset,
153
- tagged: docDefinition.tagged,
154
- displayTitle: docDefinition.displayTitle,
155
- compress: docDefinition.compress,
156
- userPassword: docDefinition.userPassword,
157
- ownerPassword: docDefinition.ownerPassword,
158
- permissions: docDefinition.permissions,
159
- lang: docDefinition.language,
160
- fontLayoutCache: isBoolean(options.fontLayoutCache) ? options.fontLayoutCache : true,
161
- bufferPages: options.bufferPages || false,
162
- autoFirstPage: false,
163
- info: createMetadata(docDefinition),
164
- font: null
165
- };
166
-
167
- this.pdfKitDoc = PdfKitEngine.createPdfDocument(pdfOptions);
168
-
169
- this.fontProvider = new FontProvider(this.fontDescriptors, this.pdfKitDoc);
170
-
171
- var builder = new LayoutBuilder(pageSize, fixPageMargins(docDefinition.pageMargins), new ImageMeasure(this.pdfKitDoc, docDefinition.images), new SVGMeasure());
172
-
173
- if (docDefinition.footerGapOption !== undefined) {
174
- builder.applyFooterGapOption(docDefinition.footerGapOption);
175
- }
176
-
177
- registerDefaultTableLayouts(builder);
178
- if (options.tableLayouts) {
179
- builder.registerTableLayouts(options.tableLayouts);
180
- }
181
-
182
- var pages = builder.layoutDocument(docDefinition.content, this.fontProvider, docDefinition.styles || {}, docDefinition.defaultStyle || {
183
- fontSize: 12,
184
- font: 'Roboto'
185
- }, docDefinition.background, docDefinition.header, docDefinition.footer, docDefinition.images, docDefinition.watermark, docDefinition.pageBreakBefore);
186
- var maxNumberPages = docDefinition.maxPagesNumber || -1;
187
- if (isNumber(maxNumberPages) && maxNumberPages > -1) {
188
- pages = pages.slice(0, maxNumberPages);
189
- }
190
-
191
- // if pageSize.height is set to Infinity, calculate the actual height of the page that
192
- // was laid out using the height of each of the items in the page.
193
- if (pageSize.height === Infinity) {
194
- var pageHeight = calculatePageHeight(pages, docDefinition.pageMargins);
195
- this.pdfKitDoc.options.size = [pageSize.width, pageHeight];
196
- }
197
-
198
- var patterns = createPatterns(docDefinition.patterns || {}, this.pdfKitDoc);
199
-
200
- renderPages(pages, this.fontProvider, this.pdfKitDoc, patterns, options.progressCallback);
201
-
202
- if (options.autoPrint) {
203
- var printActionRef = this.pdfKitDoc.ref({
204
- Type: 'Action',
205
- S: 'Named',
206
- N: 'Print'
207
- });
208
- this.pdfKitDoc._root.data.OpenAction = printActionRef;
209
- printActionRef.end();
210
- }
211
- return this.pdfKitDoc;
212
- };
213
-
214
- PdfPrinter.prototype.resolveRemoteImages = function (docDefinition, timeoutMs) {
215
- return resolveRemoteImages.call(this, docDefinition, timeoutMs);
216
- };
217
-
218
- /**
219
- * Clears the remote image cache
220
- * Useful for freeing memory or forcing fresh image fetches
221
- */
222
- PdfPrinter.prototype.clearImageCache = function () {
223
- this._remoteImageCache.clear();
224
- };
225
-
226
- /**
227
- * Gets cache statistics for monitoring
228
- * @return {Object} Cache statistics
229
- */
230
- PdfPrinter.prototype.getImageCacheStats = function () {
231
- var now = Date.now();
232
- var cache = this._remoteImageCache;
233
- var expired = 0;
234
- var valid = 0;
235
-
236
- cache.forEach(function (entry) {
237
- if (now - entry.timestamp > this._cacheConfig.ttl) {
238
- expired++;
239
- } else {
240
- valid++;
241
- }
242
- }, this);
243
-
244
- return {
245
- size: cache.size,
246
- maxSize: this._cacheConfig.maxSize,
247
- ttl: this._cacheConfig.ttl,
248
- validEntries: valid,
249
- expiredEntries: expired
250
- };
251
- };
252
-
253
- /**
254
- * Removes expired entries from cache
255
- */
256
- PdfPrinter.prototype.cleanExpiredCache = function () {
257
- var now = Date.now();
258
- var cache = this._remoteImageCache;
259
- var keysToDelete = [];
260
-
261
- cache.forEach(function (entry, key) {
262
- if (now - entry.timestamp > this._cacheConfig.ttl) {
263
- keysToDelete.push(key);
264
- }
265
- }, this);
266
-
267
- keysToDelete.forEach(function (key) {
268
- cache.delete(key);
269
- });
270
-
271
- return keysToDelete.length;
272
- };
273
-
274
- PdfPrinter.prototype.createPdfKitDocumentAsync = function (docDefinition, options) {
275
- if (!docDefinition || typeof docDefinition !== 'object') {
276
- return Promise.reject(new Error('docDefinition parameter is required and must be an object'));
277
- }
278
-
279
- var createOptions = options ? Object.assign({}, options) : {};
280
- var timeout;
281
- if (Object.prototype.hasOwnProperty.call(createOptions, 'remoteImageTimeout')) {
282
- timeout = createOptions.remoteImageTimeout;
283
- delete createOptions.remoteImageTimeout;
284
- }
285
-
286
- var self = this;
287
- return resolveRemoteImages.call(this, docDefinition, timeout).then(function () {
288
- return self.createPdfKitDocument(docDefinition, createOptions);
289
- });
290
- };
291
-
292
- function createMetadata(docDefinition) {
293
- // PDF standard has these properties reserved: Title, Author, Subject, Keywords,
294
- // Creator, Producer, CreationDate, ModDate, Trapped.
295
- // To keep the pdfmake api consistent, the info field are defined lowercase.
296
- // Custom properties don't contain a space.
297
- function standardizePropertyKey(key) {
298
- var standardProperties = ['Title', 'Author', 'Subject', 'Keywords',
299
- 'Creator', 'Producer', 'CreationDate', 'ModDate', 'Trapped'];
300
- var standardizedKey = key.charAt(0).toUpperCase() + key.slice(1);
301
- if (standardProperties.indexOf(standardizedKey) !== -1) {
302
- return standardizedKey;
303
- }
304
-
305
- return key.replace(/\s+/g, '');
306
- }
307
-
308
- var info = {
309
- Producer: 'pdfmake',
310
- Creator: 'pdfmake'
311
- };
312
-
313
- if (docDefinition.info) {
314
- for (var key in docDefinition.info) {
315
- var value = docDefinition.info[key];
316
- if (value) {
317
- key = standardizePropertyKey(key);
318
- info[key] = value;
319
- }
320
- }
321
- }
322
- return info;
323
- }
324
-
325
-
326
- function calculatePageHeight(pages, margins) {
327
- function getItemHeight(item) {
328
- if (typeof item.item.getHeight === 'function') {
329
- return item.item.getHeight();
330
- } else if (item.item._height) {
331
- return item.item._height;
332
- } else {
333
- // TODO: add support for next item types
334
- return 0;
335
- }
336
- }
337
-
338
- var fixedMargins = fixPageMargins(margins || 40);
339
- var height = fixedMargins.top + fixedMargins.bottom;
340
- pages.forEach(function (page) {
341
- page.items.forEach(function (item) {
342
- height += getItemHeight(item);
343
- });
344
- });
345
- return height;
346
- }
347
-
348
- function fixPageSize(pageSize, pageOrientation) {
349
- function isNeedSwapPageSizes(pageOrientation) {
350
- if (isString(pageOrientation)) {
351
- pageOrientation = pageOrientation.toLowerCase();
352
- return ((pageOrientation === 'portrait') && (size.width > size.height)) ||
353
- ((pageOrientation === 'landscape') && (size.width < size.height));
354
- }
355
- return false;
356
- }
357
-
358
- // if pageSize.height is set to auto, set the height to infinity so there are no page breaks.
359
- if (pageSize && pageSize.height === 'auto') {
360
- pageSize.height = Infinity;
361
- }
362
-
363
- var size = pageSize2widthAndHeight(pageSize || 'A4');
364
- if (isNeedSwapPageSizes(pageOrientation)) { // swap page sizes
365
- size = { width: size.height, height: size.width };
366
- }
367
- size.orientation = size.width > size.height ? 'landscape' : 'portrait';
368
- return size;
369
- }
370
-
371
- function fixPageMargins(margin) {
372
- if (isNumber(margin)) {
373
- margin = { left: margin, right: margin, top: margin, bottom: margin };
374
- } else if (isArray(margin)) {
375
- if (margin.length === 2) {
376
- margin = { left: margin[0], top: margin[1], right: margin[0], bottom: margin[1] };
377
- } else if (margin.length === 4) {
378
- margin = { left: margin[0], top: margin[1], right: margin[2], bottom: margin[3] };
379
- } else {
380
- throw 'Invalid pageMargins definition';
381
- }
382
- }
383
-
384
- return margin;
385
- }
386
-
387
- function registerDefaultTableLayouts(layoutBuilder) {
388
- layoutBuilder.registerTableLayouts({
389
- noBorders: {
390
- hLineWidth: function (i) {
391
- return 0;
392
- },
393
- vLineWidth: function (i) {
394
- return 0;
395
- },
396
- paddingLeft: function (i) {
397
- return i && 4 || 0;
398
- },
399
- paddingRight: function (i, node) {
400
- return (i < node.table.widths.length - 1) ? 4 : 0;
401
- }
402
- },
403
- headerLineOnly: {
404
- hLineWidth: function (i, node) {
405
- if (i === 0 || i === node.table.body.length) {
406
- return 0;
407
- }
408
- return (i === node.table.headerRows) ? 2 : 0;
409
- },
410
- vLineWidth: function (i) {
411
- return 0;
412
- },
413
- paddingLeft: function (i) {
414
- return i === 0 ? 0 : 8;
415
- },
416
- paddingRight: function (i, node) {
417
- return (i === node.table.widths.length - 1) ? 0 : 8;
418
- }
419
- },
420
- lightHorizontalLines: {
421
- hLineWidth: function (i, node) {
422
- if (i === 0 || i === node.table.body.length) {
423
- return 0;
424
- }
425
- return (i === node.table.headerRows) ? 2 : 1;
426
- },
427
- vLineWidth: function (i) {
428
- return 0;
429
- },
430
- hLineColor: function (i) {
431
- return i === 1 ? 'black' : '#aaa';
432
- },
433
- paddingLeft: function (i) {
434
- return i === 0 ? 0 : 8;
435
- },
436
- paddingRight: function (i, node) {
437
- return (i === node.table.widths.length - 1) ? 0 : 8;
438
- }
439
- }
440
- });
441
- }
442
-
443
- function pageSize2widthAndHeight(pageSize) {
444
- if (isString(pageSize)) {
445
- var size = sizes[pageSize.toUpperCase()];
446
- if (!size) {
447
- throw 'Page size ' + pageSize + ' not recognized';
448
- }
449
- return { width: size[0], height: size[1] };
450
- }
451
-
452
- return pageSize;
453
- }
454
-
455
- function updatePageOrientationInOptions(currentPage, pdfKitDoc) {
456
- var previousPageOrientation = pdfKitDoc.options.size[0] > pdfKitDoc.options.size[1] ? 'landscape' : 'portrait';
457
-
458
- if (currentPage.pageSize.orientation !== previousPageOrientation) {
459
- var width = pdfKitDoc.options.size[0];
460
- var height = pdfKitDoc.options.size[1];
461
- pdfKitDoc.options.size = [height, width];
462
- }
463
- }
464
-
465
- function renderPages(pages, fontProvider, pdfKitDoc, patterns, progressCallback) {
466
- pdfKitDoc._pdfMakePages = pages;
467
- pdfKitDoc.addPage();
468
-
469
- var totalItems = 0;
470
- if (progressCallback) {
471
- pages.forEach(function (page) {
472
- totalItems += page.items.length;
473
- });
474
- }
475
-
476
- var renderedItems = 0;
477
- progressCallback = progressCallback || function () {
478
- };
479
-
480
- for (var i = 0; i < pages.length; i++) {
481
- if (i > 0) {
482
- updatePageOrientationInOptions(pages[i], pdfKitDoc);
483
- pdfKitDoc.addPage(pdfKitDoc.options);
484
- }
485
-
486
- var page = pages[i];
487
- for (var ii = 0, il = page.items.length; ii < il; ii++) {
488
- var item = page.items[ii];
489
- switch (item.type) {
490
- case 'vector':
491
- renderVector(item.item, patterns, pdfKitDoc);
492
- break;
493
- case 'line':
494
- renderLine(item.item, item.item.x, item.item.y, patterns, pdfKitDoc);
495
- break;
496
- case 'image':
497
- renderImage(item.item, item.item.x, item.item.y, pdfKitDoc);
498
- break;
499
- case 'svg':
500
- renderSVG(item.item, item.item.x, item.item.y, pdfKitDoc, fontProvider);
501
- break;
502
- case 'beginClip':
503
- beginClip(item.item, pdfKitDoc);
504
- break;
505
- case 'endClip':
506
- endClip(pdfKitDoc);
507
- break;
508
- case 'beginVerticalAlign':
509
- beginVerticalAlign(item.item, pdfKitDoc);
510
- break;
511
- case 'endVerticalAlign':
512
- endVerticalAlign(item.item, pdfKitDoc);
513
- break;
514
- }
515
- renderedItems++;
516
- progressCallback(renderedItems / totalItems);
517
- }
518
- if (page.watermark) {
519
- renderWatermark(page, pdfKitDoc);
520
- }
521
- }
522
- }
523
-
524
- /**
525
- * Shift the "y" height of the text baseline up or down (superscript or subscript,
526
- * respectively). The exact shift can / should be changed according to standard
527
- * conventions.
528
- *
529
- * @param {number} y
530
- * @param {any} inline
531
- */
532
- function offsetText(y, inline) {
533
- var newY = y;
534
- if (inline.sup) {
535
- newY -= inline.fontSize * 0.75;
536
- }
537
- if (inline.sub) {
538
- newY += inline.fontSize * 0.35;
539
- }
540
- return newY;
541
- }
542
-
543
- function renderLine(line, x, y, patterns, pdfKitDoc) {
544
- function preparePageNodeRefLine(_pageNodeRef, inline) {
545
- var newWidth;
546
- var diffWidth;
547
- var textTools = new TextTools(null);
548
-
549
- if (isUndefined(_pageNodeRef.positions)) {
550
- throw 'Page reference id not found';
551
- }
552
-
553
- var pageNumber = _pageNodeRef.positions[0].pageNumber.toString();
554
-
555
- inline.text = pageNumber;
556
- newWidth = textTools.widthOfString(inline.text, inline.font, inline.fontSize, inline.characterSpacing, inline.fontFeatures);
557
- diffWidth = inline.width - newWidth;
558
- inline.width = newWidth;
559
-
560
- switch (inline.alignment) {
561
- case 'right':
562
- inline.x += diffWidth;
563
- break;
564
- case 'center':
565
- inline.x += diffWidth / 2;
566
- break;
567
- }
568
- }
569
-
570
- if (line._pageNodeRef) {
571
- preparePageNodeRefLine(line._pageNodeRef, line.inlines[0]);
572
- }
573
-
574
- if (line._tocItemNode) {
575
- preparePageNodeRefLine(line._tocItemNode, line.inlines[0]);
576
- }
577
-
578
- x = x || 0;
579
- y = y || 0;
580
-
581
- var lineHeight = line.getHeight();
582
- var ascenderHeight = line.getAscenderHeight();
583
- var descent = lineHeight - ascenderHeight;
584
-
585
- textDecorator.drawBackground(line, x, y, patterns, pdfKitDoc);
586
-
587
- //TODO: line.optimizeInlines();
588
- for (var i = 0, l = line.inlines.length; i < l; i++) {
589
- var inline = line.inlines[i];
590
- var shiftToBaseline = lineHeight - ((inline.font.ascender / 1000) * inline.fontSize) - descent;
591
-
592
- if (inline._pageNodeRef) {
593
- preparePageNodeRefLine(inline._pageNodeRef, inline);
594
- }
595
-
596
- if (inline._tocItemNode) {
597
- preparePageNodeRefLine(inline._tocItemNode, inline);
598
- }
599
-
600
- var options = {
601
- lineBreak: false,
602
- textWidth: inline.width,
603
- characterSpacing: inline.characterSpacing,
604
- wordCount: 1,
605
- link: inline.link
606
- };
607
-
608
- if (inline.linkToDestination) {
609
- options.goTo = inline.linkToDestination;
610
- }
611
-
612
- if (line.id && i === 0) {
613
- options.destination = line.id;
614
- }
615
-
616
- if (inline.fontFeatures) {
617
- options.features = inline.fontFeatures;
618
- }
619
-
620
- var opacity = isNumber(inline.opacity) ? inline.opacity : 1;
621
- pdfKitDoc.opacity(opacity);
622
- pdfKitDoc.fill(inline.color || 'black');
623
-
624
- pdfKitDoc._font = inline.font;
625
- pdfKitDoc.fontSize(inline.fontSize);
626
-
627
- var shiftedY = offsetText(y + shiftToBaseline, inline);
628
- pdfKitDoc.text(inline.text, x + inline.x, shiftedY, options);
629
-
630
- if (inline.linkToPage) {
631
- pdfKitDoc.ref({ Type: 'Action', S: 'GoTo', D: [inline.linkToPage, 0, 0] }).end();
632
- pdfKitDoc.annotate(x + inline.x, shiftedY, inline.width, inline.height, {
633
- Subtype: 'Link',
634
- Dest: [inline.linkToPage - 1, 'XYZ', null, null, null]
635
- });
636
- }
637
-
638
- }
639
- // Decorations won't draw correctly for superscript
640
- textDecorator.drawDecorations(line, x, y, pdfKitDoc);
641
- }
642
-
643
- function renderWatermark(page, pdfKitDoc) {
644
- var watermark = page.watermark;
645
-
646
- pdfKitDoc.fill(watermark.color);
647
- pdfKitDoc.opacity(watermark.opacity);
648
-
649
- pdfKitDoc.save();
650
-
651
- pdfKitDoc.rotate(watermark.angle, { origin: [pdfKitDoc.page.width / 2, pdfKitDoc.page.height / 2] });
652
-
653
- var x = pdfKitDoc.page.width / 2 - watermark._size.size.width / 2;
654
- var y = pdfKitDoc.page.height / 2 - watermark._size.size.height / 2;
655
-
656
- pdfKitDoc._font = watermark.font;
657
- pdfKitDoc.fontSize(watermark.fontSize);
658
- pdfKitDoc.text(watermark.text, x, y, { lineBreak: false });
659
-
660
- pdfKitDoc.restore();
661
- }
662
-
663
- function renderVector(vector, patterns, pdfKitDoc) {
664
- //TODO: pdf optimization (there's no need to write all properties everytime)
665
- pdfKitDoc.lineWidth(vector.lineWidth || 1);
666
- if (vector.dash) {
667
- pdfKitDoc.dash(vector.dash.length, { space: vector.dash.space || vector.dash.length, phase: vector.dash.phase || 0 });
668
- } else {
669
- pdfKitDoc.undash();
670
- }
671
- pdfKitDoc.lineJoin(vector.lineJoin || 'miter');
672
- pdfKitDoc.lineCap(vector.lineCap || 'butt');
673
-
674
- //TODO: clipping
675
-
676
- var gradient = null;
677
-
678
- switch (vector.type) {
679
- case 'ellipse':
680
- pdfKitDoc.ellipse(vector.x, vector.y, vector.r1, vector.r2);
681
-
682
- if (vector.linearGradient) {
683
- gradient = pdfKitDoc.linearGradient(vector.x - vector.r1, vector.y, vector.x + vector.r1, vector.y);
684
- }
685
- break;
686
- case 'rect':
687
- if (vector.r) {
688
- pdfKitDoc.roundedRect(vector.x, vector.y, vector.w, vector.h, vector.r);
689
- } else {
690
- pdfKitDoc.rect(vector.x, vector.y, vector.w, vector.h);
691
- }
692
-
693
- if (vector.linearGradient) {
694
- gradient = pdfKitDoc.linearGradient(vector.x, vector.y, vector.x + vector.w, vector.y);
695
- }
696
- break;
697
- case 'line':
698
- pdfKitDoc.moveTo(vector.x1, vector.y1);
699
- pdfKitDoc.lineTo(vector.x2, vector.y2);
700
- break;
701
- case 'polyline':
702
- if (vector.points.length === 0) {
703
- break;
704
- }
705
-
706
- pdfKitDoc.moveTo(vector.points[0].x, vector.points[0].y);
707
- for (var i = 1, l = vector.points.length; i < l; i++) {
708
- pdfKitDoc.lineTo(vector.points[i].x, vector.points[i].y);
709
- }
710
-
711
- if (vector.points.length > 1) {
712
- var p1 = vector.points[0];
713
- var pn = vector.points[vector.points.length - 1];
714
-
715
- if (vector.closePath || p1.x === pn.x && p1.y === pn.y) {
716
- pdfKitDoc.closePath();
717
- }
718
- }
719
- break;
720
- case 'path':
721
- pdfKitDoc.path(vector.d);
722
- break;
723
- }
724
-
725
- if (vector.linearGradient && gradient) {
726
- var step = 1 / (vector.linearGradient.length - 1);
727
-
728
- for (var i = 0; i < vector.linearGradient.length; i++) {
729
- gradient.stop(i * step, vector.linearGradient[i]);
730
- }
731
-
732
- vector.color = gradient;
733
- }
734
-
735
- if (isPattern(vector.color)) {
736
- vector.color = getPattern(vector.color, patterns);
737
- }
738
-
739
- var fillOpacity = isNumber(vector.fillOpacity) ? vector.fillOpacity : 1;
740
- var strokeOpacity = isNumber(vector.strokeOpacity) ? vector.strokeOpacity : 1;
741
-
742
- if (vector.color && vector.lineColor) {
743
- pdfKitDoc.fillColor(vector.color, fillOpacity);
744
- pdfKitDoc.strokeColor(vector.lineColor, strokeOpacity);
745
- pdfKitDoc.fillAndStroke();
746
- } else if (vector.color) {
747
- pdfKitDoc.fillColor(vector.color, fillOpacity);
748
- pdfKitDoc.fill();
749
- } else {
750
- pdfKitDoc.strokeColor(vector.lineColor || 'black', strokeOpacity);
751
- pdfKitDoc.stroke();
752
- }
753
- }
754
-
755
- function renderImage(image, x, y, pdfKitDoc) {
756
- var opacity = isNumber(image.opacity) ? image.opacity : 1;
757
- pdfKitDoc.opacity(opacity);
758
- if (image.cover) {
759
- var align = image.cover.align || 'center';
760
- var valign = image.cover.valign || 'center';
761
- var width = image.cover.width ? image.cover.width : image.width;
762
- var height = image.cover.height ? image.cover.height : image.height;
763
- pdfKitDoc.save();
764
- pdfKitDoc.rect(image.x, image.y, width, height).clip();
765
- pdfKitDoc.image(image.image, image.x, image.y, { cover: [width, height], align: align, valign: valign });
766
- pdfKitDoc.restore();
767
- } else {
768
- pdfKitDoc.image(image.image, image.x, image.y, { width: image._width, height: image._height });
769
- }
770
- if (image.link) {
771
- pdfKitDoc.link(image.x, image.y, image._width, image._height, image.link);
772
- }
773
- if (image.linkToPage) {
774
- pdfKitDoc.ref({ Type: 'Action', S: 'GoTo', D: [image.linkToPage, 0, 0] }).end();
775
- pdfKitDoc.annotate(image.x, image.y, image._width, image._height, { Subtype: 'Link', Dest: [image.linkToPage - 1, 'XYZ', null, null, null] });
776
- }
777
- if (image.linkToDestination) {
778
- pdfKitDoc.goTo(image.x, image.y, image._width, image._height, image.linkToDestination);
779
- }
780
- }
781
-
782
- function beginVerticalAlign(item, pdfKitDoc) {
783
- switch (item.verticalAlign) {
784
- case 'center':
785
- pdfKitDoc.save();
786
- pdfKitDoc.translate(0, -(item.nodeHeight - item.viewHeight) / 2);
787
- break;
788
- case 'bottom':
789
- pdfKitDoc.save();
790
- pdfKitDoc.translate(0, -(item.nodeHeight - item.viewHeight));
791
- break;
792
- }
793
- }
794
-
795
- function endVerticalAlign(item, pdfKitDoc) {
796
- switch (item.verticalAlign) {
797
- case 'center':
798
- case 'bottom':
799
- pdfKitDoc.restore();
800
- break;
801
- }
802
- }
803
-
804
- function renderSVG(svg, x, y, pdfKitDoc, fontProvider) {
805
- var options = Object.assign({ width: svg._width, height: svg._height, assumePt: true }, svg.options);
806
- options.fontCallback = function (family, bold, italic) {
807
- var fontsFamily = family.split(',').map(function (f) { return f.trim().replace(/('|")/g, ''); });
808
- var font = findFont(fontProvider.fonts, fontsFamily, svg.font || 'Roboto');
809
-
810
- var fontFile = fontProvider.getFontFile(font, bold, italic);
811
- if (fontFile === null) {
812
- var type = fontProvider.getFontType(bold, italic);
813
- throw new Error('Font \'' + font + '\' in style \'' + type + '\' is not defined in the font section of the document definition.');
814
- }
815
-
816
- return fontFile;
817
- };
818
-
819
- SVGtoPDF(pdfKitDoc, svg.svg, svg.x, svg.y, options);
820
-
821
- if (svg.link) {
822
- pdfKitDoc.link(svg.x, svg.y, svg._width, svg._height, svg.link);
823
- }
824
- if (svg.linkToPage) {
825
- pdfKitDoc.ref({Type: 'Action', S: 'GoTo', D: [svg.linkToPage, 0, 0]}).end();
826
- pdfKitDoc.annotate(svg.x, svg.y, svg._width, svg._height, { Subtype: 'Link', Dest: [svg.linkToPage - 1, 'XYZ', null, null, null] });
827
- }
828
- if (svg.linkToDestination) {
829
- pdfKitDoc.goTo(svg.x, svg.y, svg._width, svg._height, svg.linkToDestination);
830
- }
831
- }
832
-
833
- function beginClip(rect, pdfKitDoc) {
834
- pdfKitDoc.save();
835
- pdfKitDoc.addContent('' + rect.x + ' ' + rect.y + ' ' + rect.width + ' ' + rect.height + ' re');
836
- pdfKitDoc.clip();
837
- }
838
-
839
- function endClip(pdfKitDoc) {
840
- pdfKitDoc.restore();
841
- }
842
-
843
- function createPatterns(patternDefinitions, pdfKitDoc) {
844
- var patterns = {};
845
- Object.keys(patternDefinitions).forEach(function (p) {
846
- var pattern = patternDefinitions[p];
847
- patterns[p] = pdfKitDoc.pattern(pattern.boundingBox, pattern.xStep, pattern.yStep, pattern.pattern, pattern.colored);
848
- });
849
- return patterns;
850
- }
851
-
852
- function resolveRemoteImages(docDefinition, timeoutMs) {
853
- if (!docDefinition || typeof docDefinition !== 'object') {
854
- return Promise.resolve();
855
- }
856
-
857
- if (docDefinition[REMOTE_RESOLVED_KEY]) {
858
- return Promise.resolve();
859
- }
860
-
861
- docDefinition.images = docDefinition.images || {};
862
- var images = docDefinition.images;
863
- var remoteTargets = new Map();
864
- var cache = this._remoteImageCache || (this._remoteImageCache = new Map());
865
-
866
- Object.keys(images).forEach(function (key) {
867
- var descriptor = parseRemoteDescriptor(images[key]);
868
- if (descriptor) {
869
- remoteTargets.set(key, descriptor);
870
- }
871
- });
872
-
873
- collectInlineImages(docDefinition.content, remoteTargets, images);
874
-
875
- if (remoteTargets.size === 0) {
876
- markRemoteResolved(docDefinition);
877
- return Promise.resolve();
878
- }
879
-
880
- var timeout = typeof timeoutMs === 'number' && timeoutMs > 0 ? timeoutMs : undefined;
881
- var tasks = [];
882
- var cacheConfig = this._cacheConfig;
883
-
884
- remoteTargets.forEach(function (descriptor, key) {
885
- var cacheKey = createCacheKey(descriptor.url, descriptor.headers);
886
- tasks.push(
887
- ensureRemoteBuffer(descriptor.url, descriptor.headers, cacheKey, cache, timeout, cacheConfig)
888
- .then(function (buffer) {
889
- images[key] = buffer;
890
- })
891
- .catch(function () {
892
- if (TRANSPARENT_PNG_PLACEHOLDER) {
893
- images[key] = Buffer.from(TRANSPARENT_PNG_PLACEHOLDER);
894
- } else {
895
- delete images[key];
896
- }
897
- })
898
- );
899
- });
900
-
901
- var self = this;
902
- return Promise.all(tasks).then(function () {
903
- markRemoteResolved(docDefinition);
904
- return self;
905
- });
906
- }
907
-
908
- function collectInlineImages(node, remoteTargets, images) {
909
- if (!node) {
910
- return;
911
- }
912
-
913
- if (Array.isArray(node)) {
914
- node.forEach(function (item) {
915
- collectInlineImages(item, remoteTargets, images);
916
- });
917
- return;
918
- }
919
-
920
- if (typeof node !== 'object') {
921
- return;
922
- }
923
-
924
- if (node.image) {
925
- var resolvedKey = registerInlineImage(node, images);
926
- if (resolvedKey) {
927
- var descriptor = parseRemoteDescriptor(images[resolvedKey]);
928
- if (descriptor) {
929
- remoteTargets.set(resolvedKey, descriptor);
930
- }
931
- }
932
- }
933
-
934
- Object.keys(node).forEach(function (prop) {
935
- if (prop === 'image' || prop.charAt(0) === '_') {
936
- return;
937
- }
938
- collectInlineImages(node[prop], remoteTargets, images);
939
- });
940
- }
941
-
942
- function registerInlineImage(node, images) {
943
- var value = node.image;
944
- if (typeof value === 'string') {
945
- if (isRemoteUrl(value)) {
946
- if (!images[value]) {
947
- images[value] = value;
948
- }
949
- node.image = value;
950
- return value;
951
- }
952
-
953
- var existing = images[value];
954
- if (existing) {
955
- var descriptor = parseRemoteDescriptor(existing);
956
- if (descriptor) {
957
- return value;
958
- }
959
- }
960
- } else if (value && typeof value === 'object') {
961
- if (typeof Buffer !== 'undefined' && Buffer.isBuffer && Buffer.isBuffer(value)) {
962
- return null;
963
- }
964
-
965
- if (typeof Uint8Array !== 'undefined' && value instanceof Uint8Array) {
966
- node.image = typeof Buffer !== 'undefined' ? Buffer.from(value) : value;
967
- return null;
968
- }
969
-
970
- if (value.type === 'Buffer' && Array.isArray(value.data)) {
971
- node.image = typeof Buffer !== 'undefined' ? Buffer.from(value.data) : value.data;
972
- return null;
973
- }
974
-
975
- var url = value.url;
976
- if (typeof url === 'string' && isRemoteUrl(url)) {
977
- var key = url;
978
- if (!images[key]) {
979
- images[key] = value;
980
- }
981
- node.image = key;
982
- return key;
983
- }
984
- }
985
-
986
- return null;
987
- }
988
-
989
- function ensureRemoteBuffer(url, headers, cacheKey, cache, timeout, cacheConfig) {
990
- var now = Date.now();
991
-
992
- // Check if image is in cache and not expired
993
- var cached = cache.get(cacheKey);
994
- if (cached) {
995
- var age = now - cached.timestamp;
996
- if (age < cacheConfig.ttl) {
997
- // Move to end (LRU: mark as recently used)
998
- cache.delete(cacheKey);
999
- cache.set(cacheKey, cached);
1000
- return Promise.resolve(cached.buffer);
1001
- } else {
1002
- // Expired - remove from cache
1003
- cache.delete(cacheKey);
1004
- }
1005
- }
1006
-
1007
- // Fetch remote image and cache the result
1008
- return fetchRemote(url, headers, timeout).then(function (buffer) {
1009
- // Implement LRU eviction if cache is full
1010
- if (cache.size >= cacheConfig.maxSize) {
1011
- // Remove oldest entry (first entry in Map)
1012
- var firstKey = cache.keys().next().value;
1013
- cache.delete(firstKey);
1014
- }
1015
-
1016
- // Store with timestamp for TTL
1017
- cache.set(cacheKey, {
1018
- buffer: buffer,
1019
- timestamp: now
1020
- });
1021
-
1022
- return buffer;
1023
- });
1024
- }
1025
-
1026
- function parseRemoteDescriptor(value) {
1027
- if (!value) {
1028
- return null;
1029
- }
1030
-
1031
- if (typeof Buffer !== 'undefined' && Buffer.isBuffer && Buffer.isBuffer(value)) {
1032
- return null;
1033
- }
1034
-
1035
- if (typeof Uint8Array !== 'undefined' && value instanceof Uint8Array) {
1036
- return null;
1037
- }
1038
-
1039
- if (typeof value === 'string') {
1040
- if (isRemoteUrl(value)) {
1041
- return { url: value, headers: {} };
1042
- }
1043
- return null;
1044
- }
1045
-
1046
- if (typeof value === 'object') {
1047
- if (typeof value.url === 'string' && isRemoteUrl(value.url)) {
1048
- return { url: value.url, headers: value.headers || {} };
1049
- }
1050
- }
1051
-
1052
- return null;
1053
- }
1054
-
1055
- function isRemoteUrl(value) {
1056
- return typeof value === 'string' && REMOTE_PROTOCOL_REGEX.test(value) && !DATA_URL_REGEX.test(value);
1057
- }
1058
-
1059
- function createCacheKey(url, headers) {
1060
- var normalizedHeaders = {};
1061
- if (headers && typeof headers === 'object') {
1062
- Object.keys(headers).sort().forEach(function (key) {
1063
- normalizedHeaders[key.toLowerCase()] = headers[key];
1064
- });
1065
- }
1066
-
1067
- return url + '::' + JSON.stringify(normalizedHeaders);
1068
- }
1069
-
1070
- function markRemoteResolved(docDefinition) {
1071
- try {
1072
- Object.defineProperty(docDefinition, REMOTE_RESOLVED_KEY, {
1073
- value: true,
1074
- enumerable: false,
1075
- configurable: true,
1076
- writable: true
1077
- });
1078
- } catch (error) {
1079
- void error;
1080
- docDefinition[REMOTE_RESOLVED_KEY] = true;
1081
- }
1082
- }
1083
-
1084
- function fetchRemote(url, headers, timeoutMs) {
1085
- if (typeof fetch === 'function') {
1086
- return fetchWithGlobal(url, headers, timeoutMs);
1087
- }
1088
-
1089
- return fetchWithNode(url, headers, timeoutMs);
1090
- }
1091
-
1092
- function fetchWithGlobal(url, headers, timeoutMs) {
1093
- var controller = (typeof AbortController !== 'undefined') ? new AbortController() : null;
1094
- var timer = null;
1095
- var options = { headers: headers || {} };
1096
- if (controller) {
1097
- options.signal = controller.signal;
1098
- }
1099
-
1100
- if (controller && timeoutMs) {
1101
- timer = setTimeout(function () {
1102
- controller.abort();
1103
- }, timeoutMs);
1104
- }
1105
-
1106
- return fetch(url, options).then(function (response) {
1107
- if (timer) {
1108
- clearTimeout(timer);
1109
- }
1110
-
1111
- if (!response.ok) {
1112
- var statusText = response.statusText || 'Unknown error';
1113
- throw new Error('Failed to fetch remote image (' + response.status + ' ' + statusText + ')');
1114
- }
1115
-
1116
- return response.arrayBuffer();
1117
- }).then(function (buffer) {
1118
- return typeof Buffer !== 'undefined' ? Buffer.from(buffer) : buffer;
1119
- });
1120
- }
1121
-
1122
- function fetchWithNode(url, headers, timeoutMs) {
1123
- return new Promise(function (resolve, reject) {
1124
- var parsedUrl;
1125
- try {
1126
- parsedUrl = new URL(url);
1127
- } catch (err) {
1128
- reject(err);
1129
- return;
1130
- }
1131
-
1132
- var transport = parsedUrl.protocol === 'https:' ? require('https') : require('http');
1133
- var requestOptions = {
1134
- protocol: parsedUrl.protocol,
1135
- hostname: parsedUrl.hostname,
1136
- port: parsedUrl.port,
1137
- path: parsedUrl.pathname + (parsedUrl.search || ''),
1138
- method: 'GET',
1139
- headers: headers || {}
1140
- };
1141
-
1142
- var timeoutTriggered = false;
1143
- var req = transport.request(requestOptions, function (res) {
1144
- if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
1145
- var redirectUrl = new URL(res.headers.location, parsedUrl);
1146
- res.resume();
1147
- fetchWithNode(redirectUrl.toString(), headers, timeoutMs).then(resolve, reject);
1148
- return;
1149
- }
1150
-
1151
- if (res.statusCode < 200 || res.statusCode >= 300) {
1152
- reject(new Error('Failed to fetch remote image (' + res.statusCode + ')'));
1153
- res.resume();
1154
- return;
1155
- }
1156
-
1157
- var chunks = [];
1158
- res.on('data', function (chunk) {
1159
- chunks.push(chunk);
1160
- });
1161
- res.on('end', function () {
1162
- if (timeoutTriggered) {
1163
- return;
1164
- }
1165
- resolve(Buffer.concat(chunks));
1166
- });
1167
- });
1168
-
1169
- req.on('error', function (err) {
1170
- if (timeoutTriggered) {
1171
- return;
1172
- }
1173
- reject(err);
1174
- });
1175
-
1176
- if (timeoutMs) {
1177
- req.setTimeout(timeoutMs, function () {
1178
- timeoutTriggered = true;
1179
- req.abort();
1180
- reject(new Error('Remote image request timed out'));
1181
- });
1182
- }
1183
-
1184
- req.end();
1185
- });
1186
- }
1187
-
1188
- module.exports = PdfPrinter;
1189
-
1190
- /* temporary browser extension */
1191
- PdfPrinter.prototype.fs = require('fs');
1
+ /*eslint no-unused-vars: ["error", {"args": "none"}]*/
2
+ 'use strict';
3
+
4
+ var PdfKitEngine = require('./pdfKitEngine');
5
+ var FontProvider = require('./fontProvider');
6
+ var LayoutBuilder = require('./layoutBuilder');
7
+ var sizes = require('./standardPageSizes');
8
+ var ImageMeasure = require('./imageMeasure');
9
+ var SVGMeasure = require('./svgMeasure');
10
+ var textDecorator = require('./textDecorator');
11
+ var TextTools = require('./textTools');
12
+ var isFunction = require('./helpers').isFunction;
13
+ var isString = require('./helpers').isString;
14
+ var isNumber = require('./helpers').isNumber;
15
+ var isBoolean = require('./helpers').isBoolean;
16
+ var isArray = require('./helpers').isArray;
17
+ var isUndefined = require('./helpers').isUndefined;
18
+ var isPattern = require('./helpers').isPattern;
19
+ var getPattern = require('./helpers').getPattern;
20
+ var SVGtoPDF = require('./3rd-party/svg-to-pdfkit');
21
+
22
+ var REMOTE_RESOLVED_KEY = '__pdfMakeRemoteImagesResolved';
23
+ var REMOTE_PROTOCOL_REGEX = /^https?:\/\//i;
24
+ var DATA_URL_REGEX = /^data:/i;
25
+ var TRANSPARENT_PNG_PLACEHOLDER = (typeof Buffer !== 'undefined') ? Buffer.from('iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQImWM8c+bMfQAI1gP2Ce279wAAAABJRU5ErkJggg==', 'base64') : null;
26
+
27
+ var findFont = function (fonts, requiredFonts, defaultFont) {
28
+ for (var i = 0; i < requiredFonts.length; i++) {
29
+ var requiredFont = requiredFonts[i].toLowerCase();
30
+
31
+ for (var font in fonts) {
32
+ if (font.toLowerCase() === requiredFont) {
33
+ return font;
34
+ }
35
+ }
36
+ }
37
+
38
+ return defaultFont;
39
+ };
40
+
41
+ ////////////////////////////////////////
42
+ // PdfPrinter
43
+
44
+ /**
45
+ * @class Creates an instance of a PdfPrinter which turns document definition into a pdf
46
+ *
47
+ * @param {Object} fontDescriptors font definition dictionary
48
+ * @param {Object} [options] optional configuration
49
+ * @param {Number} [options.maxImageCacheSize] maximum number of images to cache (default: 100)
50
+ * @param {Number} [options.imageCacheTTL] cache time-to-live in milliseconds (default: 3600000 = 1 hour)
51
+ *
52
+ * @example
53
+ * var fontDescriptors = {
54
+ * Roboto: {
55
+ * normal: 'fonts/Roboto-Regular.ttf',
56
+ * bold: 'fonts/Roboto-Medium.ttf',
57
+ * italics: 'fonts/Roboto-Italic.ttf',
58
+ * bolditalics: 'fonts/Roboto-MediumItalic.ttf'
59
+ * }
60
+ * };
61
+ *
62
+ * var printer = new PdfPrinter(fontDescriptors, {
63
+ * maxImageCacheSize: 50,
64
+ * imageCacheTTL: 30 * 60 * 1000 // 30 minutes
65
+ * });
66
+ */
67
+ function PdfPrinter(fontDescriptors, options) {
68
+ this.fontDescriptors = fontDescriptors;
69
+ options = options || {};
70
+
71
+ // Cache configuration with memory leak prevention
72
+ this._cacheConfig = {
73
+ maxSize: isNumber(options.maxImageCacheSize) ? options.maxImageCacheSize : 100,
74
+ ttl: isNumber(options.imageCacheTTL) ? options.imageCacheTTL : 3600000 // 1 hour default
75
+ };
76
+
77
+ // LRU cache: Map maintains insertion order, so we can implement LRU easily
78
+ this._remoteImageCache = new Map();
79
+ }
80
+
81
+ /**
82
+ * Executes layout engine for the specified document and renders it into a pdfkit document
83
+ * ready to be saved.
84
+ *
85
+ * @param {Object} docDefinition document definition
86
+ * @param {Object} docDefinition.content an array describing the pdf structure (for more information take a look at the examples in the /examples folder)
87
+ * @param {Object} [docDefinition.defaultStyle] default (implicit) style definition
88
+ * @param {Object} [docDefinition.styles] dictionary defining all styles which can be used in the document
89
+ * @param {Object} [docDefinition.pageSize] page size (pdfkit units, A4 dimensions by default)
90
+ * @param {Number} docDefinition.pageSize.width width
91
+ * @param {Number} docDefinition.pageSize.height height
92
+ * @param {Object} [docDefinition.pageMargins] page margins (pdfkit units)
93
+ * @param {Number} docDefinition.maxPagesNumber maximum number of pages to render
94
+ *
95
+ * @example
96
+ *
97
+ * var docDefinition = {
98
+ * info: {
99
+ * title: 'awesome Document',
100
+ * author: 'john doe',
101
+ * subject: 'subject of document',
102
+ * keywords: 'keywords for document',
103
+ * },
104
+ * content: [
105
+ * 'First paragraph',
106
+ * 'Second paragraph, this time a little bit longer',
107
+ * { text: 'Third paragraph, slightly bigger font size', fontSize: 20 },
108
+ * { text: 'Another paragraph using a named style', style: 'header' },
109
+ * { text: ['playing with ', 'inlines' ] },
110
+ * { text: ['and ', { text: 'restyling ', bold: true }, 'them'] },
111
+ * ],
112
+ * styles: {
113
+ * header: { fontSize: 30, bold: true }
114
+ * },
115
+ * patterns: {
116
+ * stripe45d: {
117
+ * boundingBox: [1, 1, 4, 4],
118
+ * xStep: 3,
119
+ * yStep: 3,
120
+ * pattern: '1 w 0 1 m 4 5 l s 2 0 m 5 3 l s'
121
+ * }
122
+ * }
123
+ * };
124
+ *
125
+ * var pdfKitDoc = printer.createPdfKitDocument(docDefinition);
126
+ *
127
+ * pdfKitDoc.pipe(fs.createWriteStream('sample.pdf'));
128
+ * pdfKitDoc.end();
129
+ *
130
+ * @return {Object} a pdfKit document object which can be saved or encode to data-url
131
+ */
132
+ PdfPrinter.prototype.createPdfKitDocument = function (docDefinition, options) {
133
+ if (!docDefinition || typeof docDefinition !== 'object') {
134
+ throw new Error('docDefinition parameter is required and must be an object');
135
+ }
136
+
137
+ options = options || {};
138
+
139
+ docDefinition.version = docDefinition.version || '1.3';
140
+ docDefinition.subset = docDefinition.subset || undefined;
141
+ docDefinition.tagged = typeof docDefinition.tagged === 'boolean' ? docDefinition.tagged : false;
142
+ docDefinition.displayTitle = typeof docDefinition.displayTitle === 'boolean' ? docDefinition.displayTitle : false;
143
+ docDefinition.compress = isBoolean(docDefinition.compress) ? docDefinition.compress : true;
144
+ docDefinition.images = docDefinition.images || {};
145
+ docDefinition.pageMargins = ((docDefinition.pageMargins !== undefined) && (docDefinition.pageMargins !== null)) ? docDefinition.pageMargins : 40;
146
+
147
+ var pageSize = fixPageSize(docDefinition.pageSize, docDefinition.pageOrientation);
148
+
149
+ var pdfOptions = {
150
+ size: [pageSize.width, pageSize.height],
151
+ pdfVersion: docDefinition.version,
152
+ subset: docDefinition.subset,
153
+ tagged: docDefinition.tagged,
154
+ displayTitle: docDefinition.displayTitle,
155
+ compress: docDefinition.compress,
156
+ userPassword: docDefinition.userPassword,
157
+ ownerPassword: docDefinition.ownerPassword,
158
+ permissions: docDefinition.permissions,
159
+ lang: docDefinition.language,
160
+ fontLayoutCache: isBoolean(options.fontLayoutCache) ? options.fontLayoutCache : true,
161
+ bufferPages: options.bufferPages || false,
162
+ autoFirstPage: false,
163
+ info: createMetadata(docDefinition),
164
+ font: null
165
+ };
166
+
167
+ this.pdfKitDoc = PdfKitEngine.createPdfDocument(pdfOptions);
168
+
169
+ this.fontProvider = new FontProvider(this.fontDescriptors, this.pdfKitDoc);
170
+
171
+ var builder = new LayoutBuilder(pageSize, fixPageMargins(docDefinition.pageMargins), new ImageMeasure(this.pdfKitDoc, docDefinition.images), new SVGMeasure());
172
+
173
+ if (docDefinition.footerGapOption !== undefined) {
174
+ builder.applyFooterGapOption(docDefinition.footerGapOption);
175
+ }
176
+
177
+ registerDefaultTableLayouts(builder);
178
+ if (options.tableLayouts) {
179
+ builder.registerTableLayouts(options.tableLayouts);
180
+ }
181
+
182
+ var pages = builder.layoutDocument(docDefinition.content, this.fontProvider, docDefinition.styles || {}, docDefinition.defaultStyle || {
183
+ fontSize: 12,
184
+ font: 'Roboto'
185
+ }, docDefinition.background, docDefinition.header, docDefinition.footer, docDefinition.images, docDefinition.watermark, docDefinition.pageBreakBefore);
186
+ var maxNumberPages = docDefinition.maxPagesNumber || -1;
187
+ if (isNumber(maxNumberPages) && maxNumberPages > -1) {
188
+ pages = pages.slice(0, maxNumberPages);
189
+ }
190
+
191
+ // if pageSize.height is set to Infinity, calculate the actual height of the page that
192
+ // was laid out using the height of each of the items in the page.
193
+ if (pageSize.height === Infinity) {
194
+ var pageHeight = calculatePageHeight(pages, docDefinition.pageMargins);
195
+ this.pdfKitDoc.options.size = [pageSize.width, pageHeight];
196
+ }
197
+
198
+ var patterns = createPatterns(docDefinition.patterns || {}, this.pdfKitDoc);
199
+
200
+ renderPages(pages, this.fontProvider, this.pdfKitDoc, patterns, options.progressCallback);
201
+
202
+ if (options.autoPrint) {
203
+ var printActionRef = this.pdfKitDoc.ref({
204
+ Type: 'Action',
205
+ S: 'Named',
206
+ N: 'Print'
207
+ });
208
+ this.pdfKitDoc._root.data.OpenAction = printActionRef;
209
+ printActionRef.end();
210
+ }
211
+ return this.pdfKitDoc;
212
+ };
213
+
214
+ PdfPrinter.prototype.resolveRemoteImages = function (docDefinition, timeoutMs) {
215
+ return resolveRemoteImages.call(this, docDefinition, timeoutMs);
216
+ };
217
+
218
+ /**
219
+ * Clears the remote image cache
220
+ * Useful for freeing memory or forcing fresh image fetches
221
+ */
222
+ PdfPrinter.prototype.clearImageCache = function () {
223
+ this._remoteImageCache.clear();
224
+ };
225
+
226
+ /**
227
+ * Gets cache statistics for monitoring
228
+ * @return {Object} Cache statistics
229
+ */
230
+ PdfPrinter.prototype.getImageCacheStats = function () {
231
+ var now = Date.now();
232
+ var cache = this._remoteImageCache;
233
+ var expired = 0;
234
+ var valid = 0;
235
+
236
+ cache.forEach(function (entry) {
237
+ if (now - entry.timestamp > this._cacheConfig.ttl) {
238
+ expired++;
239
+ } else {
240
+ valid++;
241
+ }
242
+ }, this);
243
+
244
+ return {
245
+ size: cache.size,
246
+ maxSize: this._cacheConfig.maxSize,
247
+ ttl: this._cacheConfig.ttl,
248
+ validEntries: valid,
249
+ expiredEntries: expired
250
+ };
251
+ };
252
+
253
+ /**
254
+ * Removes expired entries from cache
255
+ */
256
+ PdfPrinter.prototype.cleanExpiredCache = function () {
257
+ var now = Date.now();
258
+ var cache = this._remoteImageCache;
259
+ var keysToDelete = [];
260
+
261
+ cache.forEach(function (entry, key) {
262
+ if (now - entry.timestamp > this._cacheConfig.ttl) {
263
+ keysToDelete.push(key);
264
+ }
265
+ }, this);
266
+
267
+ keysToDelete.forEach(function (key) {
268
+ cache.delete(key);
269
+ });
270
+
271
+ return keysToDelete.length;
272
+ };
273
+
274
+ PdfPrinter.prototype.createPdfKitDocumentAsync = function (docDefinition, options) {
275
+ if (!docDefinition || typeof docDefinition !== 'object') {
276
+ return Promise.reject(new Error('docDefinition parameter is required and must be an object'));
277
+ }
278
+
279
+ var createOptions = options ? Object.assign({}, options) : {};
280
+ var timeout;
281
+ if (Object.prototype.hasOwnProperty.call(createOptions, 'remoteImageTimeout')) {
282
+ timeout = createOptions.remoteImageTimeout;
283
+ delete createOptions.remoteImageTimeout;
284
+ }
285
+
286
+ var self = this;
287
+ return resolveRemoteImages.call(this, docDefinition, timeout).then(function () {
288
+ return self.createPdfKitDocument(docDefinition, createOptions);
289
+ });
290
+ };
291
+
292
+ function createMetadata(docDefinition) {
293
+ // PDF standard has these properties reserved: Title, Author, Subject, Keywords,
294
+ // Creator, Producer, CreationDate, ModDate, Trapped.
295
+ // To keep the pdfmake api consistent, the info field are defined lowercase.
296
+ // Custom properties don't contain a space.
297
+ function standardizePropertyKey(key) {
298
+ var standardProperties = ['Title', 'Author', 'Subject', 'Keywords',
299
+ 'Creator', 'Producer', 'CreationDate', 'ModDate', 'Trapped'];
300
+ var standardizedKey = key.charAt(0).toUpperCase() + key.slice(1);
301
+ if (standardProperties.indexOf(standardizedKey) !== -1) {
302
+ return standardizedKey;
303
+ }
304
+
305
+ return key.replace(/\s+/g, '');
306
+ }
307
+
308
+ var info = {
309
+ Producer: 'pdfmake',
310
+ Creator: 'pdfmake'
311
+ };
312
+
313
+ if (docDefinition.info) {
314
+ for (var key in docDefinition.info) {
315
+ var value = docDefinition.info[key];
316
+ if (value) {
317
+ key = standardizePropertyKey(key);
318
+ info[key] = value;
319
+ }
320
+ }
321
+ }
322
+ return info;
323
+ }
324
+
325
+
326
+ function calculatePageHeight(pages, margins) {
327
+ function getItemHeight(item) {
328
+ if (typeof item.item.getHeight === 'function') {
329
+ return item.item.getHeight();
330
+ } else if (item.item._height) {
331
+ return item.item._height;
332
+ } else {
333
+ // TODO: add support for next item types
334
+ return 0;
335
+ }
336
+ }
337
+
338
+ var fixedMargins = fixPageMargins(margins || 40);
339
+ var height = fixedMargins.top + fixedMargins.bottom;
340
+ pages.forEach(function (page) {
341
+ page.items.forEach(function (item) {
342
+ height += getItemHeight(item);
343
+ });
344
+ });
345
+ return height;
346
+ }
347
+
348
+ function fixPageSize(pageSize, pageOrientation) {
349
+ function isNeedSwapPageSizes(pageOrientation) {
350
+ if (isString(pageOrientation)) {
351
+ pageOrientation = pageOrientation.toLowerCase();
352
+ return ((pageOrientation === 'portrait') && (size.width > size.height)) ||
353
+ ((pageOrientation === 'landscape') && (size.width < size.height));
354
+ }
355
+ return false;
356
+ }
357
+
358
+ // if pageSize.height is set to auto, set the height to infinity so there are no page breaks.
359
+ if (pageSize && pageSize.height === 'auto') {
360
+ pageSize.height = Infinity;
361
+ }
362
+
363
+ var size = pageSize2widthAndHeight(pageSize || 'A4');
364
+ if (isNeedSwapPageSizes(pageOrientation)) { // swap page sizes
365
+ size = { width: size.height, height: size.width };
366
+ }
367
+ size.orientation = size.width > size.height ? 'landscape' : 'portrait';
368
+ return size;
369
+ }
370
+
371
+ function fixPageMargins(margin) {
372
+ if (isNumber(margin)) {
373
+ margin = { left: margin, right: margin, top: margin, bottom: margin };
374
+ } else if (isArray(margin)) {
375
+ if (margin.length === 2) {
376
+ margin = { left: margin[0], top: margin[1], right: margin[0], bottom: margin[1] };
377
+ } else if (margin.length === 4) {
378
+ margin = { left: margin[0], top: margin[1], right: margin[2], bottom: margin[3] };
379
+ } else {
380
+ throw 'Invalid pageMargins definition';
381
+ }
382
+ }
383
+
384
+ return margin;
385
+ }
386
+
387
+ function registerDefaultTableLayouts(layoutBuilder) {
388
+ layoutBuilder.registerTableLayouts({
389
+ noBorders: {
390
+ hLineWidth: function (i) {
391
+ return 0;
392
+ },
393
+ vLineWidth: function (i) {
394
+ return 0;
395
+ },
396
+ paddingLeft: function (i) {
397
+ return i && 4 || 0;
398
+ },
399
+ paddingRight: function (i, node) {
400
+ return (i < node.table.widths.length - 1) ? 4 : 0;
401
+ }
402
+ },
403
+ headerLineOnly: {
404
+ hLineWidth: function (i, node) {
405
+ if (i === 0 || i === node.table.body.length) {
406
+ return 0;
407
+ }
408
+ return (i === node.table.headerRows) ? 2 : 0;
409
+ },
410
+ vLineWidth: function (i) {
411
+ return 0;
412
+ },
413
+ paddingLeft: function (i) {
414
+ return i === 0 ? 0 : 8;
415
+ },
416
+ paddingRight: function (i, node) {
417
+ return (i === node.table.widths.length - 1) ? 0 : 8;
418
+ }
419
+ },
420
+ lightHorizontalLines: {
421
+ hLineWidth: function (i, node) {
422
+ if (i === 0 || i === node.table.body.length) {
423
+ return 0;
424
+ }
425
+ return (i === node.table.headerRows) ? 2 : 1;
426
+ },
427
+ vLineWidth: function (i) {
428
+ return 0;
429
+ },
430
+ hLineColor: function (i) {
431
+ return i === 1 ? 'black' : '#aaa';
432
+ },
433
+ paddingLeft: function (i) {
434
+ return i === 0 ? 0 : 8;
435
+ },
436
+ paddingRight: function (i, node) {
437
+ return (i === node.table.widths.length - 1) ? 0 : 8;
438
+ }
439
+ }
440
+ });
441
+ }
442
+
443
+ function pageSize2widthAndHeight(pageSize) {
444
+ if (isString(pageSize)) {
445
+ var size = sizes[pageSize.toUpperCase()];
446
+ if (!size) {
447
+ throw 'Page size ' + pageSize + ' not recognized';
448
+ }
449
+ return { width: size[0], height: size[1] };
450
+ }
451
+
452
+ return pageSize;
453
+ }
454
+
455
+ function updatePageOrientationInOptions(currentPage, pdfKitDoc) {
456
+ var previousPageOrientation = pdfKitDoc.options.size[0] > pdfKitDoc.options.size[1] ? 'landscape' : 'portrait';
457
+
458
+ if (currentPage.pageSize.orientation !== previousPageOrientation) {
459
+ var width = pdfKitDoc.options.size[0];
460
+ var height = pdfKitDoc.options.size[1];
461
+ pdfKitDoc.options.size = [height, width];
462
+ }
463
+ }
464
+
465
+ function renderPages(pages, fontProvider, pdfKitDoc, patterns, progressCallback) {
466
+ pdfKitDoc._pdfMakePages = pages;
467
+ pdfKitDoc.addPage();
468
+
469
+ var totalItems = 0;
470
+ if (progressCallback) {
471
+ pages.forEach(function (page) {
472
+ totalItems += page.items.length;
473
+ });
474
+ }
475
+
476
+ var renderedItems = 0;
477
+ progressCallback = progressCallback || function () {
478
+ };
479
+
480
+ for (var i = 0; i < pages.length; i++) {
481
+ if (i > 0) {
482
+ updatePageOrientationInOptions(pages[i], pdfKitDoc);
483
+ pdfKitDoc.addPage(pdfKitDoc.options);
484
+ }
485
+
486
+ var page = pages[i];
487
+ for (var ii = 0, il = page.items.length; ii < il; ii++) {
488
+ var item = page.items[ii];
489
+ switch (item.type) {
490
+ case 'vector':
491
+ renderVector(item.item, patterns, pdfKitDoc);
492
+ break;
493
+ case 'line':
494
+ renderLine(item.item, item.item.x, item.item.y, patterns, pdfKitDoc);
495
+ break;
496
+ case 'image':
497
+ renderImage(item.item, item.item.x, item.item.y, pdfKitDoc);
498
+ break;
499
+ case 'svg':
500
+ renderSVG(item.item, item.item.x, item.item.y, pdfKitDoc, fontProvider);
501
+ break;
502
+ case 'beginClip':
503
+ beginClip(item.item, pdfKitDoc);
504
+ break;
505
+ case 'endClip':
506
+ endClip(pdfKitDoc);
507
+ break;
508
+ case 'beginVerticalAlign':
509
+ beginVerticalAlign(item.item, pdfKitDoc);
510
+ break;
511
+ case 'endVerticalAlign':
512
+ endVerticalAlign(item.item, pdfKitDoc);
513
+ break;
514
+ }
515
+ renderedItems++;
516
+ progressCallback(renderedItems / totalItems);
517
+ }
518
+ if (page.watermark) {
519
+ renderWatermark(page, pdfKitDoc);
520
+ }
521
+ }
522
+ }
523
+
524
+ /**
525
+ * Shift the "y" height of the text baseline up or down (superscript or subscript,
526
+ * respectively). The exact shift can / should be changed according to standard
527
+ * conventions.
528
+ *
529
+ * @param {number} y
530
+ * @param {any} inline
531
+ */
532
+ function offsetText(y, inline) {
533
+ var newY = y;
534
+ if (inline.sup) {
535
+ newY -= inline.fontSize * 0.75;
536
+ }
537
+ if (inline.sub) {
538
+ newY += inline.fontSize * 0.35;
539
+ }
540
+ return newY;
541
+ }
542
+
543
+ function renderLine(line, x, y, patterns, pdfKitDoc) {
544
+ function preparePageNodeRefLine(_pageNodeRef, inline) {
545
+ var newWidth;
546
+ var diffWidth;
547
+ var textTools = new TextTools(null);
548
+
549
+ if (isUndefined(_pageNodeRef.positions)) {
550
+ throw 'Page reference id not found';
551
+ }
552
+
553
+ var pageNumber = _pageNodeRef.positions[0].pageNumber.toString();
554
+
555
+ inline.text = pageNumber;
556
+ newWidth = textTools.widthOfString(inline.text, inline.font, inline.fontSize, inline.characterSpacing, inline.fontFeatures);
557
+ diffWidth = inline.width - newWidth;
558
+ inline.width = newWidth;
559
+
560
+ switch (inline.alignment) {
561
+ case 'right':
562
+ inline.x += diffWidth;
563
+ break;
564
+ case 'center':
565
+ inline.x += diffWidth / 2;
566
+ break;
567
+ }
568
+ }
569
+
570
+ if (line._pageNodeRef) {
571
+ preparePageNodeRefLine(line._pageNodeRef, line.inlines[0]);
572
+ }
573
+
574
+ if (line._tocItemNode) {
575
+ preparePageNodeRefLine(line._tocItemNode, line.inlines[0]);
576
+ }
577
+
578
+ x = x || 0;
579
+ y = y || 0;
580
+
581
+ var lineHeight = line.getHeight();
582
+ var ascenderHeight = line.getAscenderHeight();
583
+ var descent = lineHeight - ascenderHeight;
584
+
585
+ textDecorator.drawBackground(line, x, y, patterns, pdfKitDoc);
586
+
587
+ //TODO: line.optimizeInlines();
588
+ for (var i = 0, l = line.inlines.length; i < l; i++) {
589
+ var inline = line.inlines[i];
590
+ var shiftToBaseline = lineHeight - ((inline.font.ascender / 1000) * inline.fontSize) - descent;
591
+
592
+ if (inline._pageNodeRef) {
593
+ preparePageNodeRefLine(inline._pageNodeRef, inline);
594
+ }
595
+
596
+ if (inline._tocItemNode) {
597
+ preparePageNodeRefLine(inline._tocItemNode, inline);
598
+ }
599
+
600
+ var options = {
601
+ lineBreak: false,
602
+ textWidth: inline.width,
603
+ characterSpacing: inline.characterSpacing,
604
+ wordCount: 1,
605
+ link: inline.link
606
+ };
607
+
608
+ if (inline.linkToDestination) {
609
+ options.goTo = inline.linkToDestination;
610
+ }
611
+
612
+ if (line.id && i === 0) {
613
+ options.destination = line.id;
614
+ }
615
+
616
+ if (inline.fontFeatures) {
617
+ options.features = inline.fontFeatures;
618
+ }
619
+
620
+ var opacity = isNumber(inline.opacity) ? inline.opacity : 1;
621
+ pdfKitDoc.opacity(opacity);
622
+ pdfKitDoc.fill(inline.color || 'black');
623
+
624
+ pdfKitDoc._font = inline.font;
625
+ pdfKitDoc.fontSize(inline.fontSize);
626
+
627
+ var shiftedY = offsetText(y + shiftToBaseline, inline);
628
+ pdfKitDoc.text(inline.text, x + inline.x, shiftedY, options);
629
+
630
+ if (inline.linkToPage) {
631
+ pdfKitDoc.ref({ Type: 'Action', S: 'GoTo', D: [inline.linkToPage, 0, 0] }).end();
632
+ pdfKitDoc.annotate(x + inline.x, shiftedY, inline.width, inline.height, {
633
+ Subtype: 'Link',
634
+ Dest: [inline.linkToPage - 1, 'XYZ', null, null, null]
635
+ });
636
+ }
637
+
638
+ }
639
+ // Decorations won't draw correctly for superscript
640
+ textDecorator.drawDecorations(line, x, y, pdfKitDoc);
641
+ }
642
+
643
+ function renderWatermark(page, pdfKitDoc) {
644
+ var watermark = page.watermark;
645
+
646
+ pdfKitDoc.fill(watermark.color);
647
+ pdfKitDoc.opacity(watermark.opacity);
648
+
649
+ pdfKitDoc.save();
650
+
651
+ pdfKitDoc.rotate(watermark.angle, { origin: [pdfKitDoc.page.width / 2, pdfKitDoc.page.height / 2] });
652
+
653
+ var x = pdfKitDoc.page.width / 2 - watermark._size.size.width / 2;
654
+ var y = pdfKitDoc.page.height / 2 - watermark._size.size.height / 2;
655
+
656
+ pdfKitDoc._font = watermark.font;
657
+ pdfKitDoc.fontSize(watermark.fontSize);
658
+ pdfKitDoc.text(watermark.text, x, y, { lineBreak: false });
659
+
660
+ pdfKitDoc.restore();
661
+ }
662
+
663
+ function renderVector(vector, patterns, pdfKitDoc) {
664
+ //TODO: pdf optimization (there's no need to write all properties everytime)
665
+ pdfKitDoc.lineWidth(vector.lineWidth || 1);
666
+ if (vector.dash) {
667
+ pdfKitDoc.dash(vector.dash.length, { space: vector.dash.space || vector.dash.length, phase: vector.dash.phase || 0 });
668
+ } else {
669
+ pdfKitDoc.undash();
670
+ }
671
+ pdfKitDoc.lineJoin(vector.lineJoin || 'miter');
672
+ pdfKitDoc.lineCap(vector.lineCap || 'butt');
673
+
674
+ //TODO: clipping
675
+
676
+ var gradient = null;
677
+
678
+ switch (vector.type) {
679
+ case 'ellipse':
680
+ pdfKitDoc.ellipse(vector.x, vector.y, vector.r1, vector.r2);
681
+
682
+ if (vector.linearGradient) {
683
+ gradient = pdfKitDoc.linearGradient(vector.x - vector.r1, vector.y, vector.x + vector.r1, vector.y);
684
+ }
685
+ break;
686
+ case 'rect':
687
+ if (vector.r) {
688
+ pdfKitDoc.roundedRect(vector.x, vector.y, vector.w, vector.h, vector.r);
689
+ } else {
690
+ pdfKitDoc.rect(vector.x, vector.y, vector.w, vector.h);
691
+ }
692
+
693
+ if (vector.linearGradient) {
694
+ gradient = pdfKitDoc.linearGradient(vector.x, vector.y, vector.x + vector.w, vector.y);
695
+ }
696
+ break;
697
+ case 'line':
698
+ pdfKitDoc.moveTo(vector.x1, vector.y1);
699
+ pdfKitDoc.lineTo(vector.x2, vector.y2);
700
+ break;
701
+ case 'polyline':
702
+ if (vector.points.length === 0) {
703
+ break;
704
+ }
705
+
706
+ pdfKitDoc.moveTo(vector.points[0].x, vector.points[0].y);
707
+ for (var i = 1, l = vector.points.length; i < l; i++) {
708
+ pdfKitDoc.lineTo(vector.points[i].x, vector.points[i].y);
709
+ }
710
+
711
+ if (vector.points.length > 1) {
712
+ var p1 = vector.points[0];
713
+ var pn = vector.points[vector.points.length - 1];
714
+
715
+ if (vector.closePath || p1.x === pn.x && p1.y === pn.y) {
716
+ pdfKitDoc.closePath();
717
+ }
718
+ }
719
+ break;
720
+ case 'path':
721
+ pdfKitDoc.path(vector.d);
722
+ break;
723
+ }
724
+
725
+ if (vector.linearGradient && gradient) {
726
+ var step = 1 / (vector.linearGradient.length - 1);
727
+
728
+ for (var i = 0; i < vector.linearGradient.length; i++) {
729
+ gradient.stop(i * step, vector.linearGradient[i]);
730
+ }
731
+
732
+ vector.color = gradient;
733
+ }
734
+
735
+ if (isPattern(vector.color)) {
736
+ vector.color = getPattern(vector.color, patterns);
737
+ }
738
+
739
+ var fillOpacity = isNumber(vector.fillOpacity) ? vector.fillOpacity : 1;
740
+ var strokeOpacity = isNumber(vector.strokeOpacity) ? vector.strokeOpacity : 1;
741
+
742
+ if (vector.color && vector.lineColor) {
743
+ pdfKitDoc.fillColor(vector.color, fillOpacity);
744
+ pdfKitDoc.strokeColor(vector.lineColor, strokeOpacity);
745
+ pdfKitDoc.fillAndStroke();
746
+ } else if (vector.color) {
747
+ pdfKitDoc.fillColor(vector.color, fillOpacity);
748
+ pdfKitDoc.fill();
749
+ } else {
750
+ pdfKitDoc.strokeColor(vector.lineColor || 'black', strokeOpacity);
751
+ pdfKitDoc.stroke();
752
+ }
753
+ }
754
+
755
+ function renderImage(image, x, y, pdfKitDoc) {
756
+ var opacity = isNumber(image.opacity) ? image.opacity : 1;
757
+ pdfKitDoc.opacity(opacity);
758
+ if (image.cover) {
759
+ var align = image.cover.align || 'center';
760
+ var valign = image.cover.valign || 'center';
761
+ var width = image.cover.width ? image.cover.width : image.width;
762
+ var height = image.cover.height ? image.cover.height : image.height;
763
+ pdfKitDoc.save();
764
+ pdfKitDoc.rect(image.x, image.y, width, height).clip();
765
+ pdfKitDoc.image(image.image, image.x, image.y, { cover: [width, height], align: align, valign: valign });
766
+ pdfKitDoc.restore();
767
+ } else {
768
+ pdfKitDoc.image(image.image, image.x, image.y, { width: image._width, height: image._height });
769
+ }
770
+ if (image.link) {
771
+ pdfKitDoc.link(image.x, image.y, image._width, image._height, image.link);
772
+ }
773
+ if (image.linkToPage) {
774
+ pdfKitDoc.ref({ Type: 'Action', S: 'GoTo', D: [image.linkToPage, 0, 0] }).end();
775
+ pdfKitDoc.annotate(image.x, image.y, image._width, image._height, { Subtype: 'Link', Dest: [image.linkToPage - 1, 'XYZ', null, null, null] });
776
+ }
777
+ if (image.linkToDestination) {
778
+ pdfKitDoc.goTo(image.x, image.y, image._width, image._height, image.linkToDestination);
779
+ }
780
+ }
781
+
782
+ function beginVerticalAlign(item, pdfKitDoc) {
783
+ switch (item.verticalAlign) {
784
+ case 'center':
785
+ pdfKitDoc.save();
786
+ pdfKitDoc.translate(0, -(item.nodeHeight - item.viewHeight) / 2);
787
+ break;
788
+ case 'bottom':
789
+ pdfKitDoc.save();
790
+ pdfKitDoc.translate(0, -(item.nodeHeight - item.viewHeight));
791
+ break;
792
+ }
793
+ }
794
+
795
+ function endVerticalAlign(item, pdfKitDoc) {
796
+ switch (item.verticalAlign) {
797
+ case 'center':
798
+ case 'bottom':
799
+ pdfKitDoc.restore();
800
+ break;
801
+ }
802
+ }
803
+
804
+ function renderSVG(svg, x, y, pdfKitDoc, fontProvider) {
805
+ var options = Object.assign({ width: svg._width, height: svg._height, assumePt: true }, svg.options);
806
+ options.fontCallback = function (family, bold, italic) {
807
+ var fontsFamily = family.split(',').map(function (f) { return f.trim().replace(/('|")/g, ''); });
808
+ var font = findFont(fontProvider.fonts, fontsFamily, svg.font || 'Roboto');
809
+
810
+ var fontFile = fontProvider.getFontFile(font, bold, italic);
811
+ if (fontFile === null) {
812
+ var type = fontProvider.getFontType(bold, italic);
813
+ throw new Error('Font \'' + font + '\' in style \'' + type + '\' is not defined in the font section of the document definition.');
814
+ }
815
+
816
+ return fontFile;
817
+ };
818
+
819
+ SVGtoPDF(pdfKitDoc, svg.svg, svg.x, svg.y, options);
820
+
821
+ if (svg.link) {
822
+ pdfKitDoc.link(svg.x, svg.y, svg._width, svg._height, svg.link);
823
+ }
824
+ if (svg.linkToPage) {
825
+ pdfKitDoc.ref({Type: 'Action', S: 'GoTo', D: [svg.linkToPage, 0, 0]}).end();
826
+ pdfKitDoc.annotate(svg.x, svg.y, svg._width, svg._height, { Subtype: 'Link', Dest: [svg.linkToPage - 1, 'XYZ', null, null, null] });
827
+ }
828
+ if (svg.linkToDestination) {
829
+ pdfKitDoc.goTo(svg.x, svg.y, svg._width, svg._height, svg.linkToDestination);
830
+ }
831
+ }
832
+
833
+ function beginClip(rect, pdfKitDoc) {
834
+ pdfKitDoc.save();
835
+ pdfKitDoc.addContent('' + rect.x + ' ' + rect.y + ' ' + rect.width + ' ' + rect.height + ' re');
836
+ pdfKitDoc.clip();
837
+ }
838
+
839
+ function endClip(pdfKitDoc) {
840
+ pdfKitDoc.restore();
841
+ }
842
+
843
+ function createPatterns(patternDefinitions, pdfKitDoc) {
844
+ var patterns = {};
845
+ Object.keys(patternDefinitions).forEach(function (p) {
846
+ var pattern = patternDefinitions[p];
847
+ patterns[p] = pdfKitDoc.pattern(pattern.boundingBox, pattern.xStep, pattern.yStep, pattern.pattern, pattern.colored);
848
+ });
849
+ return patterns;
850
+ }
851
+
852
+ function resolveRemoteImages(docDefinition, timeoutMs) {
853
+ if (!docDefinition || typeof docDefinition !== 'object') {
854
+ return Promise.resolve();
855
+ }
856
+
857
+ if (docDefinition[REMOTE_RESOLVED_KEY]) {
858
+ return Promise.resolve();
859
+ }
860
+
861
+ docDefinition.images = docDefinition.images || {};
862
+ var images = docDefinition.images;
863
+ var remoteTargets = new Map();
864
+ var cache = this._remoteImageCache || (this._remoteImageCache = new Map());
865
+
866
+ Object.keys(images).forEach(function (key) {
867
+ var descriptor = parseRemoteDescriptor(images[key]);
868
+ if (descriptor) {
869
+ remoteTargets.set(key, descriptor);
870
+ }
871
+ });
872
+
873
+ collectInlineImages(docDefinition.content, remoteTargets, images);
874
+
875
+ if (remoteTargets.size === 0) {
876
+ markRemoteResolved(docDefinition);
877
+ return Promise.resolve();
878
+ }
879
+
880
+ var timeout = typeof timeoutMs === 'number' && timeoutMs > 0 ? timeoutMs : undefined;
881
+ var tasks = [];
882
+ var cacheConfig = this._cacheConfig;
883
+
884
+ remoteTargets.forEach(function (descriptor, key) {
885
+ var cacheKey = createCacheKey(descriptor.url, descriptor.headers);
886
+ tasks.push(
887
+ ensureRemoteBuffer(descriptor.url, descriptor.headers, cacheKey, cache, timeout, cacheConfig)
888
+ .then(function (buffer) {
889
+ images[key] = buffer;
890
+ })
891
+ .catch(function () {
892
+ if (TRANSPARENT_PNG_PLACEHOLDER) {
893
+ images[key] = Buffer.from(TRANSPARENT_PNG_PLACEHOLDER);
894
+ } else {
895
+ delete images[key];
896
+ }
897
+ })
898
+ );
899
+ });
900
+
901
+ var self = this;
902
+ return Promise.all(tasks).then(function () {
903
+ markRemoteResolved(docDefinition);
904
+ return self;
905
+ });
906
+ }
907
+
908
+ function collectInlineImages(node, remoteTargets, images) {
909
+ if (!node) {
910
+ return;
911
+ }
912
+
913
+ if (Array.isArray(node)) {
914
+ node.forEach(function (item) {
915
+ collectInlineImages(item, remoteTargets, images);
916
+ });
917
+ return;
918
+ }
919
+
920
+ if (typeof node !== 'object') {
921
+ return;
922
+ }
923
+
924
+ if (node.image) {
925
+ var resolvedKey = registerInlineImage(node, images);
926
+ if (resolvedKey) {
927
+ var descriptor = parseRemoteDescriptor(images[resolvedKey]);
928
+ if (descriptor) {
929
+ remoteTargets.set(resolvedKey, descriptor);
930
+ }
931
+ }
932
+ }
933
+
934
+ Object.keys(node).forEach(function (prop) {
935
+ if (prop === 'image' || prop.charAt(0) === '_') {
936
+ return;
937
+ }
938
+ collectInlineImages(node[prop], remoteTargets, images);
939
+ });
940
+ }
941
+
942
+ function registerInlineImage(node, images) {
943
+ var value = node.image;
944
+ if (typeof value === 'string') {
945
+ if (isRemoteUrl(value)) {
946
+ if (!images[value]) {
947
+ images[value] = value;
948
+ }
949
+ node.image = value;
950
+ return value;
951
+ }
952
+
953
+ var existing = images[value];
954
+ if (existing) {
955
+ var descriptor = parseRemoteDescriptor(existing);
956
+ if (descriptor) {
957
+ return value;
958
+ }
959
+ }
960
+ } else if (value && typeof value === 'object') {
961
+ if (typeof Buffer !== 'undefined' && Buffer.isBuffer && Buffer.isBuffer(value)) {
962
+ return null;
963
+ }
964
+
965
+ if (typeof Uint8Array !== 'undefined' && value instanceof Uint8Array) {
966
+ node.image = typeof Buffer !== 'undefined' ? Buffer.from(value) : value;
967
+ return null;
968
+ }
969
+
970
+ if (value.type === 'Buffer' && Array.isArray(value.data)) {
971
+ node.image = typeof Buffer !== 'undefined' ? Buffer.from(value.data) : value.data;
972
+ return null;
973
+ }
974
+
975
+ var url = value.url;
976
+ if (typeof url === 'string' && isRemoteUrl(url)) {
977
+ var key = url;
978
+ if (!images[key]) {
979
+ images[key] = value;
980
+ }
981
+ node.image = key;
982
+ return key;
983
+ }
984
+ }
985
+
986
+ return null;
987
+ }
988
+
989
+ function ensureRemoteBuffer(url, headers, cacheKey, cache, timeout, cacheConfig) {
990
+ var now = Date.now();
991
+
992
+ // Check if image is in cache and not expired
993
+ var cached = cache.get(cacheKey);
994
+ if (cached) {
995
+ var age = now - cached.timestamp;
996
+ if (age < cacheConfig.ttl) {
997
+ // Move to end (LRU: mark as recently used)
998
+ cache.delete(cacheKey);
999
+ cache.set(cacheKey, cached);
1000
+ return Promise.resolve(cached.buffer);
1001
+ } else {
1002
+ // Expired - remove from cache
1003
+ cache.delete(cacheKey);
1004
+ }
1005
+ }
1006
+
1007
+ // Fetch remote image and cache the result
1008
+ return fetchRemote(url, headers, timeout).then(function (buffer) {
1009
+ // Implement LRU eviction if cache is full
1010
+ if (cache.size >= cacheConfig.maxSize) {
1011
+ // Remove oldest entry (first entry in Map)
1012
+ var firstKey = cache.keys().next().value;
1013
+ cache.delete(firstKey);
1014
+ }
1015
+
1016
+ // Store with timestamp for TTL
1017
+ cache.set(cacheKey, {
1018
+ buffer: buffer,
1019
+ timestamp: now
1020
+ });
1021
+
1022
+ return buffer;
1023
+ });
1024
+ }
1025
+
1026
+ function parseRemoteDescriptor(value) {
1027
+ if (!value) {
1028
+ return null;
1029
+ }
1030
+
1031
+ if (typeof Buffer !== 'undefined' && Buffer.isBuffer && Buffer.isBuffer(value)) {
1032
+ return null;
1033
+ }
1034
+
1035
+ if (typeof Uint8Array !== 'undefined' && value instanceof Uint8Array) {
1036
+ return null;
1037
+ }
1038
+
1039
+ if (typeof value === 'string') {
1040
+ if (isRemoteUrl(value)) {
1041
+ return { url: value, headers: {} };
1042
+ }
1043
+ return null;
1044
+ }
1045
+
1046
+ if (typeof value === 'object') {
1047
+ if (typeof value.url === 'string' && isRemoteUrl(value.url)) {
1048
+ return { url: value.url, headers: value.headers || {} };
1049
+ }
1050
+ }
1051
+
1052
+ return null;
1053
+ }
1054
+
1055
+ function isRemoteUrl(value) {
1056
+ return typeof value === 'string' && REMOTE_PROTOCOL_REGEX.test(value) && !DATA_URL_REGEX.test(value);
1057
+ }
1058
+
1059
+ function createCacheKey(url, headers) {
1060
+ var normalizedHeaders = {};
1061
+ if (headers && typeof headers === 'object') {
1062
+ Object.keys(headers).sort().forEach(function (key) {
1063
+ normalizedHeaders[key.toLowerCase()] = headers[key];
1064
+ });
1065
+ }
1066
+
1067
+ return url + '::' + JSON.stringify(normalizedHeaders);
1068
+ }
1069
+
1070
+ function markRemoteResolved(docDefinition) {
1071
+ try {
1072
+ Object.defineProperty(docDefinition, REMOTE_RESOLVED_KEY, {
1073
+ value: true,
1074
+ enumerable: false,
1075
+ configurable: true,
1076
+ writable: true
1077
+ });
1078
+ } catch (error) {
1079
+ void error;
1080
+ docDefinition[REMOTE_RESOLVED_KEY] = true;
1081
+ }
1082
+ }
1083
+
1084
+ function fetchRemote(url, headers, timeoutMs) {
1085
+ if (typeof fetch === 'function') {
1086
+ return fetchWithGlobal(url, headers, timeoutMs);
1087
+ }
1088
+
1089
+ return fetchWithNode(url, headers, timeoutMs);
1090
+ }
1091
+
1092
+ function fetchWithGlobal(url, headers, timeoutMs) {
1093
+ var controller = (typeof AbortController !== 'undefined') ? new AbortController() : null;
1094
+ var timer = null;
1095
+ var options = { headers: headers || {} };
1096
+ if (controller) {
1097
+ options.signal = controller.signal;
1098
+ }
1099
+
1100
+ if (controller && timeoutMs) {
1101
+ timer = setTimeout(function () {
1102
+ controller.abort();
1103
+ }, timeoutMs);
1104
+ }
1105
+
1106
+ return fetch(url, options).then(function (response) {
1107
+ if (timer) {
1108
+ clearTimeout(timer);
1109
+ }
1110
+
1111
+ if (!response.ok) {
1112
+ var statusText = response.statusText || 'Unknown error';
1113
+ throw new Error('Failed to fetch remote image (' + response.status + ' ' + statusText + ')');
1114
+ }
1115
+
1116
+ return response.arrayBuffer();
1117
+ }).then(function (buffer) {
1118
+ return typeof Buffer !== 'undefined' ? Buffer.from(buffer) : buffer;
1119
+ });
1120
+ }
1121
+
1122
+ function fetchWithNode(url, headers, timeoutMs) {
1123
+ return new Promise(function (resolve, reject) {
1124
+ var parsedUrl;
1125
+ try {
1126
+ parsedUrl = new URL(url);
1127
+ } catch (err) {
1128
+ reject(err);
1129
+ return;
1130
+ }
1131
+
1132
+ var transport = parsedUrl.protocol === 'https:' ? require('https') : require('http');
1133
+ var requestOptions = {
1134
+ protocol: parsedUrl.protocol,
1135
+ hostname: parsedUrl.hostname,
1136
+ port: parsedUrl.port,
1137
+ path: parsedUrl.pathname + (parsedUrl.search || ''),
1138
+ method: 'GET',
1139
+ headers: headers || {}
1140
+ };
1141
+
1142
+ var timeoutTriggered = false;
1143
+ var req = transport.request(requestOptions, function (res) {
1144
+ if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
1145
+ var redirectUrl = new URL(res.headers.location, parsedUrl);
1146
+ res.resume();
1147
+ fetchWithNode(redirectUrl.toString(), headers, timeoutMs).then(resolve, reject);
1148
+ return;
1149
+ }
1150
+
1151
+ if (res.statusCode < 200 || res.statusCode >= 300) {
1152
+ reject(new Error('Failed to fetch remote image (' + res.statusCode + ')'));
1153
+ res.resume();
1154
+ return;
1155
+ }
1156
+
1157
+ var chunks = [];
1158
+ res.on('data', function (chunk) {
1159
+ chunks.push(chunk);
1160
+ });
1161
+ res.on('end', function () {
1162
+ if (timeoutTriggered) {
1163
+ return;
1164
+ }
1165
+ resolve(Buffer.concat(chunks));
1166
+ });
1167
+ });
1168
+
1169
+ req.on('error', function (err) {
1170
+ if (timeoutTriggered) {
1171
+ return;
1172
+ }
1173
+ reject(err);
1174
+ });
1175
+
1176
+ if (timeoutMs) {
1177
+ req.setTimeout(timeoutMs, function () {
1178
+ timeoutTriggered = true;
1179
+ req.abort();
1180
+ reject(new Error('Remote image request timed out'));
1181
+ });
1182
+ }
1183
+
1184
+ req.end();
1185
+ });
1186
+ }
1187
+
1188
+ module.exports = PdfPrinter;
1189
+
1190
+ /* temporary browser extension */
1191
+ PdfPrinter.prototype.fs = require('fs');