@flowaccount/pdfmake 1.0.1 → 1.0.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/build/pdfmake.js +604 -489
- package/build/pdfmake.min.js +2 -2
- package/build/pdfmake.min.js.map +1 -1
- package/package.json +1 -1
- package/src/documentContext.js +2 -2
- package/src/layoutBuilder.js +13 -3
- package/src/printer.js +110 -6
package/package.json
CHANGED
package/src/documentContext.js
CHANGED
|
@@ -75,7 +75,7 @@ DocumentContext.prototype.resetMarginXTopParent = function () {
|
|
|
75
75
|
this.marginXTopParent = null;
|
|
76
76
|
};
|
|
77
77
|
|
|
78
|
-
DocumentContext.prototype.beginColumn = function (width, offset, endingCell) {
|
|
78
|
+
DocumentContext.prototype.beginColumn = function (width, offset, endingCell, heightOffset) {
|
|
79
79
|
var saved = this.snapshots[this.snapshots.length - 1];
|
|
80
80
|
|
|
81
81
|
this.calculateBottomMost(saved, endingCell);
|
|
@@ -84,7 +84,7 @@ DocumentContext.prototype.beginColumn = function (width, offset, endingCell) {
|
|
|
84
84
|
this.x = this.x + this.lastColumnWidth + (offset || 0);
|
|
85
85
|
this.y = saved.y;
|
|
86
86
|
this.availableWidth = width; //saved.availableWidth - offset;
|
|
87
|
-
this.availableHeight = saved.availableHeight;
|
|
87
|
+
this.availableHeight = saved.availableHeight - (heightOffset || 0);
|
|
88
88
|
|
|
89
89
|
this.lastColumnWidth = width;
|
|
90
90
|
};
|
package/src/layoutBuilder.js
CHANGED
|
@@ -990,7 +990,7 @@ LayoutBuilder.prototype._getRowSpanEndingCell = function (tableBody, rowIndex, c
|
|
|
990
990
|
return null;
|
|
991
991
|
};
|
|
992
992
|
|
|
993
|
-
LayoutBuilder.prototype.processRow = function ({ marginX = [0, 0], dontBreakRows = false, rowsWithoutPageBreak = 0, cells, widths, gaps, tableNode, tableBody, rowIndex, height }) {
|
|
993
|
+
LayoutBuilder.prototype.processRow = function ({ marginX = [0, 0], dontBreakRows = false, rowsWithoutPageBreak = 0, cells, widths, gaps, tableNode, tableBody, rowIndex, height, heightOffset = 0 }) {
|
|
994
994
|
var self = this;
|
|
995
995
|
var isUnbreakableRow = dontBreakRows || rowIndex <= rowsWithoutPageBreak - 1;
|
|
996
996
|
var pageBreaks = [];
|
|
@@ -1053,6 +1053,13 @@ LayoutBuilder.prototype.processRow = function ({ marginX = [0, 0], dontBreakRows
|
|
|
1053
1053
|
}
|
|
1054
1054
|
}
|
|
1055
1055
|
|
|
1056
|
+
// Apply heightOffset from table definition to adjust page break calculation
|
|
1057
|
+
// This allows fine-tuning of page break detection for specific tables
|
|
1058
|
+
// heightOffset is passed as parameter from processTable
|
|
1059
|
+
if (heightOffset) {
|
|
1060
|
+
estimatedHeight = (estimatedHeight || 0) + heightOffset;
|
|
1061
|
+
}
|
|
1062
|
+
|
|
1056
1063
|
// Check if row won't fit on current page
|
|
1057
1064
|
// Strategy: Force break if row won't fit AND we're not too close to page boundary
|
|
1058
1065
|
// "Too close" means availableHeight is very small (< 5px) - at that point forcing
|
|
@@ -1141,7 +1148,7 @@ LayoutBuilder.prototype.processRow = function ({ marginX = [0, 0], dontBreakRows
|
|
|
1141
1148
|
}
|
|
1142
1149
|
|
|
1143
1150
|
// We pass the endingSpanCell reference to store the context just after processing rowspan cell
|
|
1144
|
-
self.writer.context().beginColumn(width, leftOffset, endOfRowSpanCell);
|
|
1151
|
+
self.writer.context().beginColumn(width, leftOffset, endOfRowSpanCell, heightOffset);
|
|
1145
1152
|
|
|
1146
1153
|
if (!cell._span) {
|
|
1147
1154
|
self.processNode(cell);
|
|
@@ -1328,6 +1335,8 @@ LayoutBuilder.prototype.processTable = function (tableNode) {
|
|
|
1328
1335
|
height = undefined;
|
|
1329
1336
|
}
|
|
1330
1337
|
|
|
1338
|
+
var heightOffset = tableNode.heightOffset != undefined ? tableNode.heightOffset : 0;
|
|
1339
|
+
|
|
1331
1340
|
var pageBeforeProcessing = this.writer.context().page;
|
|
1332
1341
|
|
|
1333
1342
|
var result = this.processRow({
|
|
@@ -1340,7 +1349,8 @@ LayoutBuilder.prototype.processTable = function (tableNode) {
|
|
|
1340
1349
|
tableBody: tableNode.table.body,
|
|
1341
1350
|
tableNode,
|
|
1342
1351
|
rowIndex: i,
|
|
1343
|
-
height
|
|
1352
|
+
height,
|
|
1353
|
+
heightOffset
|
|
1344
1354
|
});
|
|
1345
1355
|
addAll(tableNode.positions, result.positions);
|
|
1346
1356
|
|
package/src/printer.js
CHANGED
|
@@ -45,6 +45,9 @@ var findFont = function (fonts, requiredFonts, defaultFont) {
|
|
|
45
45
|
* @class Creates an instance of a PdfPrinter which turns document definition into a pdf
|
|
46
46
|
*
|
|
47
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)
|
|
48
51
|
*
|
|
49
52
|
* @example
|
|
50
53
|
* var fontDescriptors = {
|
|
@@ -56,10 +59,22 @@ var findFont = function (fonts, requiredFonts, defaultFont) {
|
|
|
56
59
|
* }
|
|
57
60
|
* };
|
|
58
61
|
*
|
|
59
|
-
* var printer = new PdfPrinter(fontDescriptors
|
|
62
|
+
* var printer = new PdfPrinter(fontDescriptors, {
|
|
63
|
+
* maxImageCacheSize: 50,
|
|
64
|
+
* imageCacheTTL: 30 * 60 * 1000 // 30 minutes
|
|
65
|
+
* });
|
|
60
66
|
*/
|
|
61
|
-
function PdfPrinter(fontDescriptors) {
|
|
67
|
+
function PdfPrinter(fontDescriptors, options) {
|
|
62
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
|
|
63
78
|
this._remoteImageCache = new Map();
|
|
64
79
|
}
|
|
65
80
|
|
|
@@ -200,6 +215,62 @@ PdfPrinter.prototype.resolveRemoteImages = function (docDefinition, timeoutMs) {
|
|
|
200
215
|
return resolveRemoteImages.call(this, docDefinition, timeoutMs);
|
|
201
216
|
};
|
|
202
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
|
+
|
|
203
274
|
PdfPrinter.prototype.createPdfKitDocumentAsync = function (docDefinition, options) {
|
|
204
275
|
if (!docDefinition || typeof docDefinition !== 'object') {
|
|
205
276
|
return Promise.reject(new Error('docDefinition parameter is required and must be an object'));
|
|
@@ -808,11 +879,12 @@ function resolveRemoteImages(docDefinition, timeoutMs) {
|
|
|
808
879
|
|
|
809
880
|
var timeout = typeof timeoutMs === 'number' && timeoutMs > 0 ? timeoutMs : undefined;
|
|
810
881
|
var tasks = [];
|
|
882
|
+
var cacheConfig = this._cacheConfig;
|
|
811
883
|
|
|
812
884
|
remoteTargets.forEach(function (descriptor, key) {
|
|
813
885
|
var cacheKey = createCacheKey(descriptor.url, descriptor.headers);
|
|
814
886
|
tasks.push(
|
|
815
|
-
ensureRemoteBuffer(descriptor.url, descriptor.headers, cacheKey, cache, timeout)
|
|
887
|
+
ensureRemoteBuffer(descriptor.url, descriptor.headers, cacheKey, cache, timeout, cacheConfig)
|
|
816
888
|
.then(function (buffer) {
|
|
817
889
|
images[key] = buffer;
|
|
818
890
|
})
|
|
@@ -914,9 +986,41 @@ function registerInlineImage(node, images) {
|
|
|
914
986
|
return null;
|
|
915
987
|
}
|
|
916
988
|
|
|
917
|
-
function ensureRemoteBuffer(url, headers, cacheKey, cache, timeout) {
|
|
918
|
-
|
|
919
|
-
|
|
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
|
+
});
|
|
920
1024
|
}
|
|
921
1025
|
|
|
922
1026
|
function parseRemoteDescriptor(value) {
|