@flowaccount/pdfmake 1.0.5 → 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/LICENSE +21 -21
- package/README.md +297 -297
- package/build/pdfmake.js +49968 -50213
- package/build/pdfmake.min.js +2 -2
- package/build/pdfmake.min.js.map +1 -1
- package/build/vfs_fonts.js +6 -6
- package/package.json +110 -110
- package/src/3rd-party/svg-to-pdfkit.js +3 -3
- package/src/browser-extensions/URLBrowserResolver.js +96 -96
- package/src/browser-extensions/pdfMake.js +361 -361
- package/src/browser-extensions/tokenizer-shim.js +15 -15
- package/src/browser-extensions/virtual-fs.js +55 -55
- package/src/columnCalculator.js +157 -157
- package/src/docMeasure.js +831 -831
- package/src/docPreprocessor.js +277 -277
- package/src/documentContext.js +383 -383
- package/src/elementWriter.js +442 -434
- package/src/fontProvider.js +68 -68
- package/src/helpers.js +138 -138
- package/src/imageMeasure.js +70 -70
- package/src/layoutBuilder.js +1998 -1770
- package/src/line.js +91 -91
- package/src/pageElementWriter.js +362 -362
- package/src/pdfKitEngine.js +21 -21
- package/src/printer.js +1191 -1191
- package/src/qrEnc.js +790 -790
- package/src/standardPageSizes.js +54 -54
- package/src/styleContextStack.js +138 -138
- package/src/svgMeasure.js +70 -70
- package/src/tableProcessor.js +791 -789
- package/src/textDecorator.js +157 -157
- package/src/textTools.js +442 -442
- package/src/traversalTracker.js +47 -47
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');
|