pdfmake 0.3.6 → 0.3.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/Printer.js CHANGED
@@ -13,11 +13,13 @@ class PdfPrinter {
13
13
  * @param {object} fontDescriptors font definition dictionary
14
14
  * @param {object} virtualfs
15
15
  * @param {object} urlResolver
16
+ * @param {(path: string) => boolean} localAccessPolicy
16
17
  */
17
- constructor(fontDescriptors, virtualfs, urlResolver) {
18
+ constructor(fontDescriptors, virtualfs, urlResolver, localAccessPolicy) {
18
19
  this.fontDescriptors = fontDescriptors;
19
20
  this.virtualfs = virtualfs;
20
21
  this.urlResolver = urlResolver;
22
+ this.localAccessPolicy = localAccessPolicy;
21
23
  }
22
24
 
23
25
  /**
@@ -69,7 +71,7 @@ class PdfPrinter {
69
71
  font: null
70
72
  };
71
73
 
72
- this.pdfKitDoc = new PDFDocument(this.fontDescriptors, docDefinition.images, docDefinition.patterns, docDefinition.attachments, pdfOptions, this.virtualfs);
74
+ this.pdfKitDoc = new PDFDocument(this.fontDescriptors, docDefinition.images, docDefinition.patterns, docDefinition.attachments, pdfOptions, this.virtualfs, this.localAccessPolicy);
73
75
  embedFiles(docDefinition, this.pdfKitDoc);
74
76
 
75
77
  const builder = new LayoutBuilder(pageSize, normalizePageMargin(docDefinition.pageMargins), new SVGMeasure());
package/src/Renderer.js CHANGED
@@ -188,7 +188,7 @@ class Renderer {
188
188
 
189
189
  let opacity = isNumber(inline.opacity) ? inline.opacity : 1;
190
190
  this.pdfDocument.opacity(opacity);
191
- this.pdfDocument.fill(inline.color || 'black');
191
+ this.pdfDocument.fill(this.pdfDocument.resolveColor(inline.color, 'black'));
192
192
 
193
193
  this.pdfDocument._font = inline.font;
194
194
  this.pdfDocument.fontSize(inline.fontSize);
@@ -287,14 +287,14 @@ class Renderer {
287
287
  let strokeOpacity = isNumber(vector.strokeOpacity) ? vector.strokeOpacity : 1;
288
288
 
289
289
  if (vector.color && vector.lineColor) {
290
- this.pdfDocument.fillColor(vector.color, fillOpacity);
291
- this.pdfDocument.strokeColor(vector.lineColor, strokeOpacity);
290
+ this.pdfDocument.fillColor(this.pdfDocument.resolveColor(vector.color, 'black'), fillOpacity);
291
+ this.pdfDocument.strokeColor(this.pdfDocument.resolveColor(vector.lineColor, 'black'), strokeOpacity);
292
292
  this.pdfDocument.fillAndStroke();
293
293
  } else if (vector.color) {
294
- this.pdfDocument.fillColor(vector.color, fillOpacity);
294
+ this.pdfDocument.fillColor(this.pdfDocument.resolveColor(vector.color, 'black'), fillOpacity);
295
295
  this.pdfDocument.fill();
296
296
  } else {
297
- this.pdfDocument.strokeColor(vector.lineColor || 'black', strokeOpacity);
297
+ this.pdfDocument.strokeColor(this.pdfDocument.resolveColor(vector.lineColor, 'black'), strokeOpacity);
298
298
  this.pdfDocument.stroke();
299
299
  }
300
300
  }
@@ -436,7 +436,7 @@ class Renderer {
436
436
  renderWatermark(page) {
437
437
  let watermark = page.watermark;
438
438
 
439
- this.pdfDocument.fill(watermark.color);
439
+ this.pdfDocument.fill(this.pdfDocument.resolveColor(watermark.color, 'black'));
440
440
  this.pdfDocument.opacity(watermark.opacity);
441
441
 
442
442
  this.pdfDocument.save();
@@ -1,9 +1,20 @@
1
1
  import ColumnCalculator from './columnCalculator';
2
2
  import { isNumber, isPositiveInteger } from './helpers/variableType';
3
3
 
4
+ const PAGE_BREAK_VALUES = new Set(['before', 'beforeOdd', 'beforeEven', 'after', 'afterOdd', 'afterEven']);
5
+
6
+ const hasExplicitPageBreak = cell => {
7
+ if (!cell || typeof cell !== 'object') {
8
+ return false;
9
+ }
10
+
11
+ return PAGE_BREAK_VALUES.has(cell.pageBreak);
12
+ };
13
+
4
14
  class TableProcessor {
5
15
  constructor(tableNode) {
6
16
  this.tableNode = tableNode;
17
+ this._isCurrentRowUnbreakable = false;
7
18
  }
8
19
 
9
20
  beginTable(writer) {
@@ -163,7 +174,11 @@ class TableProcessor {
163
174
 
164
175
  this.rowTopPageY = writer.context().y + this.rowPaddingTop;
165
176
 
166
- if (this.dontBreakRows && rowIndex > 0) {
177
+ const rowCells = this.tableNode.table.body[rowIndex] || [];
178
+ const rowHasPageBreak = rowCells.some(hasExplicitPageBreak);
179
+ this._isCurrentRowUnbreakable = this.dontBreakRows && rowIndex > 0 && !rowHasPageBreak;
180
+
181
+ if (this._isCurrentRowUnbreakable) {
167
182
  writer.beginUnbreakableBlock();
168
183
  }
169
184
  this.rowTopY = writer.context().y;
@@ -589,7 +604,9 @@ class TableProcessor {
589
604
  this.headerRepeatable = writer.currentBlockToRepeatable();
590
605
  }
591
606
 
592
- if (this.dontBreakRows) {
607
+ const shouldCommitCurrentRowUnbreakable = this.dontBreakRows && (rowIndex === 0 || this._isCurrentRowUnbreakable);
608
+
609
+ if (shouldCommitCurrentRowUnbreakable) {
593
610
  const pageChangedCallback = () => {
594
611
  if (rowIndex > 0 && !this.headerRows && this.layout.hLineWhenBroken !== false) {
595
612
  // Draw the top border of the row after a page break
@@ -604,6 +621,8 @@ class TableProcessor {
604
621
  writer.removeListener('pageChanged', pageChangedCallback);
605
622
  }
606
623
 
624
+ this._isCurrentRowUnbreakable = false;
625
+
607
626
  if (this.headerRepeatable && (rowIndex === (this.rowsWithoutPageBreak - 1) || rowIndex === this.tableNode.table.body.length - 1)) {
608
627
  writer.commitUnbreakableBlock();
609
628
  writer.pushToRepeatables(this.headerRepeatable);
@@ -1,6 +1,6 @@
1
1
  import { isNumber } from './helpers/variableType';
2
2
 
3
- const groupDecorations = line => {
3
+ const groupDecorations = (line, pdfDocument) => {
4
4
  let groups = [];
5
5
  let currentGroup = null;
6
6
  for (let i = 0, l = line.inlines.length; i < l; i++) {
@@ -13,7 +13,7 @@ const groupDecorations = line => {
13
13
  if (!Array.isArray(decoration)) {
14
14
  decoration = [decoration];
15
15
  }
16
- let color = inline.decorationColor || inline.color || 'black';
16
+ let color = pdfDocument.resolveColor(pdfDocument.resolveColor(inline.decorationColor, inline.color), 'black');
17
17
  let style = inline.decorationStyle || 'solid';
18
18
  let thickness = isNumber(inline.decorationThickness) ? inline.decorationThickness : null;
19
19
  for (let ii = 0, ll = decoration.length; ii < ll; ii++) {
@@ -49,11 +49,12 @@ class TextDecorator {
49
49
  let height = line.getHeight();
50
50
  for (let i = 0, l = line.inlines.length; i < l; i++) {
51
51
  let inline = line.inlines[i];
52
- if (!inline.background) {
52
+
53
+ let color = this.pdfDocument.resolveColor(inline.background, undefined);
54
+ if (!color) {
53
55
  continue;
54
56
  }
55
57
 
56
- let color = inline.background;
57
58
  let patternColor = this.pdfDocument.providePattern(inline.background);
58
59
  if (patternColor !== null) {
59
60
  color = patternColor;
@@ -67,7 +68,7 @@ class TextDecorator {
67
68
  }
68
69
 
69
70
  drawDecorations(line, x, y) {
70
- let groups = groupDecorations(line);
71
+ let groups = groupDecorations(line, this.pdfDocument);
71
72
  for (let i = 0, l = groups.length; i < l; i++) {
72
73
  this._drawDecoration(groups[i], x, y);
73
74
  }
@@ -1,13 +1,48 @@
1
- async function fetchUrl(url, headers = {}) {
2
- try {
3
- const response = await fetch(url, { headers });
4
- if (!response.ok) {
5
- throw new Error(`Failed to fetch (status code: ${response.status}, url: "${url}")`);
1
+
2
+ const MAX_REDIRECTS = 30;
3
+
4
+ /**
5
+ * @param {string} url
6
+ * @param {object} headers
7
+ * @param {(url: string) => boolean} urlAccessPolicy
8
+ * @returns {Promise<Response>}
9
+ */
10
+ async function fetchUrl(url, headers = {}, urlAccessPolicy) {
11
+ for (let i = 0; i <= MAX_REDIRECTS; i++) {
12
+ if ((typeof urlAccessPolicy !== 'undefined') && (urlAccessPolicy(url) !== true)) {
13
+ throw new Error(`Access to URL denied by resource access policy: ${url}`);
14
+ }
15
+
16
+ try {
17
+ let response = await fetch(url, { headers, redirect: 'manual' });
18
+
19
+ // redirect url
20
+ if (response.status >= 300 && response.status < 400) {
21
+ let location = response.headers.get('location');
22
+ if (!location) {
23
+ throw new Error('Redirect response missing Location header');
24
+ }
25
+ url = new URL(location, url).href;
26
+ continue;
27
+ }
28
+
29
+ // browsers do not support redirect: 'manual'
30
+ if (response.type === 'opaqueredirect') {
31
+ response = await fetch(url, {headers});
32
+ }
33
+
34
+ if (!response.ok) {
35
+ throw new Error(`Failed to fetch (status code: ${response.status})`);
36
+ }
37
+
38
+ return response;
39
+
40
+ } catch (error) {
41
+ throw new Error(`Network request failed (url: "${url}", error: ${error.message})`, {cause: error});
6
42
  }
7
- return await response.arrayBuffer();
8
- } catch (error) {
9
- throw new Error(`Network request failed (url: "${url}", error: ${error.message})`, { cause: error });
10
43
  }
44
+
45
+ throw new Error(`Network request failed (url: "${url}", error: Too many redirects)`);
11
46
  }
12
47
 
13
48
  class URLResolver {
@@ -31,11 +66,16 @@ class URLResolver {
31
66
  return; // url was downloaded earlier
32
67
  }
33
68
 
34
- if ((typeof this.urlAccessPolicy !== 'undefined') && (this.urlAccessPolicy(url) !== true)) {
35
- throw new Error(`Access to URL denied by resource access policy: ${url}`);
69
+ const response = await fetchUrl(url, headers, this.urlAccessPolicy);
70
+
71
+ // validate access policy on redirected url (in browsers, only the final URL is validated)
72
+ if (response.redirected) {
73
+ if ((typeof this.urlAccessPolicy !== 'undefined') && (this.urlAccessPolicy(response.url) !== true)) {
74
+ throw new Error(`Access to URL denied by resource access policy: ${response.url}`);
75
+ }
36
76
  }
37
77
 
38
- const buffer = await fetchUrl(url, headers);
78
+ const buffer = await response.arrayBuffer();
39
79
  this.fs.writeFileSync(url, buffer);
40
80
  }
41
81
  // else cannot be resolved
package/src/base.js CHANGED
@@ -9,6 +9,7 @@ class pdfmake {
9
9
  constructor() {
10
10
  this.virtualfs = virtualfs;
11
11
  this.urlAccessPolicy = undefined;
12
+ this.localAccessPolicy = undefined;
12
13
  }
13
14
 
14
15
  /**
@@ -34,11 +35,16 @@ class pdfmake {
34
35
  'No URL access policy defined. Consider using setUrlAccessPolicy() to restrict external resource downloads.'
35
36
  );
36
37
  }
38
+ if (typeof this.localAccessPolicy === 'undefined' && isServer) {
39
+ console.warn(
40
+ 'No local access policy defined. Consider using setLocalAccessPolicy() to restrict local file system access.'
41
+ );
42
+ }
37
43
 
38
44
  let urlResolver = new URLResolver(this.virtualfs);
39
45
  urlResolver.setUrlAccessPolicy(this.urlAccessPolicy);
40
46
 
41
- let printer = new Printer(this.fonts, this.virtualfs, urlResolver);
47
+ let printer = new Printer(this.fonts, this.virtualfs, urlResolver, this.localAccessPolicy);
42
48
  const pdfDocumentPromise = printer.createPdfKitDocument(docDefinition, options);
43
49
 
44
50
  return this._transformToDocument(pdfDocumentPromise);
package/src/index.js CHANGED
@@ -6,6 +6,17 @@ class pdfmake extends pdfmakeBase {
6
6
  super();
7
7
  }
8
8
 
9
+ /**
10
+ * @param {(path: string) => boolean} callback
11
+ */
12
+ setLocalAccessPolicy(callback) {
13
+ if (callback !== undefined && typeof callback !== 'function') {
14
+ throw new Error("Parameter 'callback' has an invalid type. Function or undefined expected.");
15
+ }
16
+
17
+ this.localAccessPolicy = callback;
18
+ }
19
+
9
20
  _transformToDocument(doc) {
10
21
  return new OutputDocumentServer(doc);
11
22
  }