pdfmake 0.3.0-beta.8 → 0.3.0-beta.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/build/pdfmake.js +74624 -74535
  3. package/build/pdfmake.js.map +1 -1
  4. package/build/pdfmake.min.js +2 -2
  5. package/build/pdfmake.min.js.map +1 -1
  6. package/js/3rd-party/svg-to-pdfkit/source.js +3626 -0
  7. package/js/3rd-party/svg-to-pdfkit.js +7 -0
  8. package/js/DocMeasure.js +626 -0
  9. package/js/DocPreprocessor.js +238 -0
  10. package/js/DocumentContext.js +258 -0
  11. package/js/ElementWriter.js +331 -0
  12. package/js/LayoutBuilder.js +744 -0
  13. package/js/Line.js +105 -0
  14. package/js/OutputDocument.js +76 -0
  15. package/js/OutputDocumentServer.js +27 -0
  16. package/js/PDFDocument.js +144 -0
  17. package/js/PageElementWriter.js +140 -0
  18. package/js/PageSize.js +74 -0
  19. package/js/Printer.js +291 -0
  20. package/js/Renderer.js +375 -0
  21. package/js/SVGMeasure.js +69 -0
  22. package/js/StyleContextStack.js +164 -0
  23. package/js/TableProcessor.js +522 -0
  24. package/js/TextBreaker.js +139 -0
  25. package/js/TextDecorator.js +143 -0
  26. package/js/TextInlines.js +206 -0
  27. package/js/URLResolver.js +73 -0
  28. package/js/base.js +52 -0
  29. package/js/browser-extensions/OutputDocumentBrowser.js +118 -0
  30. package/js/browser-extensions/URLBrowserResolver.js +76 -0
  31. package/js/browser-extensions/fonts/Roboto.js +38 -0
  32. package/js/browser-extensions/index.js +53 -0
  33. package/js/browser-extensions/pdfMake.js +15 -0
  34. package/js/browser-extensions/standard-fonts/Courier.js +38 -0
  35. package/js/browser-extensions/standard-fonts/Helvetica.js +38 -0
  36. package/js/browser-extensions/standard-fonts/Symbol.js +23 -0
  37. package/js/browser-extensions/standard-fonts/Times.js +38 -0
  38. package/js/browser-extensions/standard-fonts/ZapfDingbats.js +23 -0
  39. package/js/browser-extensions/virtual-fs-cjs.js +3 -0
  40. package/js/columnCalculator.js +148 -0
  41. package/js/helpers/node.js +98 -0
  42. package/js/helpers/tools.js +40 -0
  43. package/js/helpers/variableType.js +59 -0
  44. package/js/index.js +15 -0
  45. package/js/qrEnc.js +721 -0
  46. package/js/standardPageSizes.js +56 -0
  47. package/js/tableLayouts.js +98 -0
  48. package/js/virtual-fs.js +60 -0
  49. package/package.json +1 -1
  50. package/src/DocMeasure.js +7 -6
  51. package/src/DocumentContext.js +6 -13
  52. package/src/LayoutBuilder.js +52 -2
  53. package/src/StyleContextStack.js +3 -44
  54. package/src/TableProcessor.js +22 -6
  55. package/src/columnCalculator.js +24 -3
  56. package/src/helpers/variableType.js +11 -0
@@ -0,0 +1,56 @@
1
+ "use strict";
2
+
3
+ exports.__esModule = true;
4
+ exports.default = void 0;
5
+ var _default = exports.default = {
6
+ '4A0': [4767.87, 6740.79],
7
+ '2A0': [3370.39, 4767.87],
8
+ A0: [2383.94, 3370.39],
9
+ A1: [1683.78, 2383.94],
10
+ A2: [1190.55, 1683.78],
11
+ A3: [841.89, 1190.55],
12
+ A4: [595.28, 841.89],
13
+ A5: [419.53, 595.28],
14
+ A6: [297.64, 419.53],
15
+ A7: [209.76, 297.64],
16
+ A8: [147.40, 209.76],
17
+ A9: [104.88, 147.40],
18
+ A10: [73.70, 104.88],
19
+ B0: [2834.65, 4008.19],
20
+ B1: [2004.09, 2834.65],
21
+ B2: [1417.32, 2004.09],
22
+ B3: [1000.63, 1417.32],
23
+ B4: [708.66, 1000.63],
24
+ B5: [498.90, 708.66],
25
+ B6: [354.33, 498.90],
26
+ B7: [249.45, 354.33],
27
+ B8: [175.75, 249.45],
28
+ B9: [124.72, 175.75],
29
+ B10: [87.87, 124.72],
30
+ C0: [2599.37, 3676.54],
31
+ C1: [1836.85, 2599.37],
32
+ C2: [1298.27, 1836.85],
33
+ C3: [918.43, 1298.27],
34
+ C4: [649.13, 918.43],
35
+ C5: [459.21, 649.13],
36
+ C6: [323.15, 459.21],
37
+ C7: [229.61, 323.15],
38
+ C8: [161.57, 229.61],
39
+ C9: [113.39, 161.57],
40
+ C10: [79.37, 113.39],
41
+ RA0: [2437.80, 3458.27],
42
+ RA1: [1729.13, 2437.80],
43
+ RA2: [1218.90, 1729.13],
44
+ RA3: [864.57, 1218.90],
45
+ RA4: [609.45, 864.57],
46
+ SRA0: [2551.18, 3628.35],
47
+ SRA1: [1814.17, 2551.18],
48
+ SRA2: [1275.59, 1814.17],
49
+ SRA3: [907.09, 1275.59],
50
+ SRA4: [637.80, 907.09],
51
+ EXECUTIVE: [521.86, 756.00],
52
+ FOLIO: [612.00, 936.00],
53
+ LEGAL: [612.00, 1008.00],
54
+ LETTER: [612.00, 792.00],
55
+ TABLOID: [792.00, 1224.00]
56
+ };
@@ -0,0 +1,98 @@
1
+ "use strict";
2
+
3
+ exports.__esModule = true;
4
+ exports.tableLayouts = exports.defaultTableLayout = void 0;
5
+ /*eslint no-unused-vars: ["error", {"args": "none"}]*/
6
+
7
+ const tableLayouts = exports.tableLayouts = {
8
+ noBorders: {
9
+ hLineWidth(i) {
10
+ return 0;
11
+ },
12
+ vLineWidth(i) {
13
+ return 0;
14
+ },
15
+ paddingLeft(i) {
16
+ return i && 4 || 0;
17
+ },
18
+ paddingRight(i, node) {
19
+ return i < node.table.widths.length - 1 ? 4 : 0;
20
+ }
21
+ },
22
+ headerLineOnly: {
23
+ hLineWidth(i, node) {
24
+ if (i === 0 || i === node.table.body.length) {
25
+ return 0;
26
+ }
27
+ return i === node.table.headerRows ? 2 : 0;
28
+ },
29
+ vLineWidth(i) {
30
+ return 0;
31
+ },
32
+ paddingLeft(i) {
33
+ return i === 0 ? 0 : 8;
34
+ },
35
+ paddingRight(i, node) {
36
+ return i === node.table.widths.length - 1 ? 0 : 8;
37
+ }
38
+ },
39
+ lightHorizontalLines: {
40
+ hLineWidth(i, node) {
41
+ if (i === 0 || i === node.table.body.length) {
42
+ return 0;
43
+ }
44
+ return i === node.table.headerRows ? 2 : 1;
45
+ },
46
+ vLineWidth(i) {
47
+ return 0;
48
+ },
49
+ hLineColor(i) {
50
+ return i === 1 ? 'black' : '#aaa';
51
+ },
52
+ paddingLeft(i) {
53
+ return i === 0 ? 0 : 8;
54
+ },
55
+ paddingRight(i, node) {
56
+ return i === node.table.widths.length - 1 ? 0 : 8;
57
+ }
58
+ }
59
+ };
60
+ const defaultTableLayout = exports.defaultTableLayout = {
61
+ hLineWidth(i, node) {
62
+ return 1;
63
+ },
64
+ vLineWidth(i, node) {
65
+ return 1;
66
+ },
67
+ hLineColor(i, node) {
68
+ return 'black';
69
+ },
70
+ vLineColor(i, node) {
71
+ return 'black';
72
+ },
73
+ hLineStyle(i, node) {
74
+ return null;
75
+ },
76
+ vLineStyle(i, node) {
77
+ return null;
78
+ },
79
+ paddingLeft(i, node) {
80
+ return 4;
81
+ },
82
+ paddingRight(i, node) {
83
+ return 4;
84
+ },
85
+ paddingTop(i, node) {
86
+ return 2;
87
+ },
88
+ paddingBottom(i, node) {
89
+ return 2;
90
+ },
91
+ fillColor(i, node) {
92
+ return null;
93
+ },
94
+ fillOpacity(i, node) {
95
+ return 1;
96
+ },
97
+ defaultBorder: true
98
+ };
@@ -0,0 +1,60 @@
1
+ "use strict";
2
+
3
+ exports.__esModule = true;
4
+ exports.default = void 0;
5
+ const normalizeFilename = filename => {
6
+ if (filename.indexOf(__dirname) === 0) {
7
+ filename = filename.substring(__dirname.length);
8
+ }
9
+ if (filename.indexOf('/') === 0) {
10
+ filename = filename.substring(1);
11
+ }
12
+ return filename;
13
+ };
14
+ class VirtualFileSystem {
15
+ constructor() {
16
+ this.storage = {};
17
+ }
18
+
19
+ /**
20
+ * @param {string} filename
21
+ * @returns {boolean}
22
+ */
23
+ existsSync(filename) {
24
+ const normalizedFilename = normalizeFilename(filename);
25
+ return typeof this.storage[normalizedFilename] !== 'undefined';
26
+ }
27
+
28
+ /**
29
+ * @param {string} filename
30
+ * @param {?string|?object} options
31
+ * @returns {string|Buffer}
32
+ */
33
+ readFileSync(filename, options) {
34
+ const normalizedFilename = normalizeFilename(filename);
35
+ const encoding = typeof options === 'object' ? options.encoding : options;
36
+ if (!this.existsSync(normalizedFilename)) {
37
+ throw new Error(`File '${normalizedFilename}' not found in virtual file system`);
38
+ }
39
+ const buffer = this.storage[normalizedFilename];
40
+ if (encoding) {
41
+ return buffer.toString(encoding);
42
+ }
43
+ return buffer;
44
+ }
45
+
46
+ /**
47
+ * @param {string} filename
48
+ * @param {string|Buffer} content
49
+ * @param {?string|?object} options
50
+ */
51
+ writeFileSync(filename, content, options) {
52
+ const normalizedFilename = normalizeFilename(filename);
53
+ const encoding = typeof options === 'object' ? options.encoding : options;
54
+ if (!content && !options) {
55
+ throw new Error('No content');
56
+ }
57
+ this.storage[normalizedFilename] = encoding || typeof content === 'string' ? new Buffer(content, encoding) : content;
58
+ }
59
+ }
60
+ var _default = exports.default = new VirtualFileSystem();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pdfmake",
3
- "version": "0.3.0-beta.8",
3
+ "version": "0.3.0-beta.9",
4
4
  "description": "Client/server side PDF printing in pure JavaScript",
5
5
  "main": "js/index.js",
6
6
  "esnext": "src/index.js",
package/src/DocMeasure.js CHANGED
@@ -433,16 +433,17 @@ class DocMeasure {
433
433
  if (item.listMarker._inlines) {
434
434
  node._gapSize.width = Math.max(node._gapSize.width, item.listMarker._inlines[0].width);
435
435
  }
436
- } // TODO: else - nested lists numbering
436
+
437
+ if (node.reversed) {
438
+ counter--;
439
+ } else {
440
+ counter++;
441
+ }
442
+ }
437
443
 
438
444
  node._minWidth = Math.max(node._minWidth, items[i]._minWidth);
439
445
  node._maxWidth = Math.max(node._maxWidth, items[i]._maxWidth);
440
446
 
441
- if (node.reversed) {
442
- counter--;
443
- } else {
444
- counter++;
445
- }
446
447
  }
447
448
 
448
449
  node._minWidth += node._gapSize.width;
@@ -18,7 +18,6 @@ class DocumentContext extends EventEmitter {
18
18
  this.page = -1;
19
19
 
20
20
  this.snapshots = [];
21
- this.endingCell = null;
22
21
  this.backgroundLength = [];
23
22
 
24
23
  this.addPage(pageSize);
@@ -38,7 +37,6 @@ class DocumentContext extends EventEmitter {
38
37
  availableWidth: this.availableWidth,
39
38
  page: this.page
40
39
  },
41
- endingCell: this.endingCell,
42
40
  lastColumnWidth: this.lastColumnWidth
43
41
  });
44
42
 
@@ -48,9 +46,8 @@ class DocumentContext extends EventEmitter {
48
46
  beginColumn(width, offset, endingCell) {
49
47
  let saved = this.snapshots[this.snapshots.length - 1];
50
48
 
51
- this.calculateBottomMost(saved);
49
+ this.calculateBottomMost(saved, endingCell);
52
50
 
53
- this.endingCell = endingCell;
54
51
  this.page = saved.page;
55
52
  this.x = this.x + this.lastColumnWidth + (offset || 0);
56
53
  this.y = saved.y;
@@ -60,10 +57,9 @@ class DocumentContext extends EventEmitter {
60
57
  this.lastColumnWidth = width;
61
58
  }
62
59
 
63
- calculateBottomMost(destContext) {
64
- if (this.endingCell) {
65
- this.saveContextInEndingCell(this.endingCell);
66
- this.endingCell = null;
60
+ calculateBottomMost(destContext, endingCell) {
61
+ if (endingCell) {
62
+ this.saveContextInEndingCell(endingCell);
67
63
  } else {
68
64
  destContext.bottomMost = bottomMostContext(this, destContext.bottomMost);
69
65
  }
@@ -89,12 +85,11 @@ class DocumentContext extends EventEmitter {
89
85
  };
90
86
  }
91
87
 
92
- completeColumnGroup(height) {
88
+ completeColumnGroup(height, endingCell) {
93
89
  let saved = this.snapshots.pop();
94
90
 
95
- this.calculateBottomMost(saved);
91
+ this.calculateBottomMost(saved, endingCell);
96
92
 
97
- this.endingCell = null;
98
93
  this.x = saved.x;
99
94
 
100
95
  let y = saved.bottomMost.y;
@@ -171,7 +166,6 @@ class DocumentContext extends EventEmitter {
171
166
  availableHeight: this.availableHeight,
172
167
  availableWidth: this.availableWidth,
173
168
  page: this.page,
174
- endingCell: this.endingCell,
175
169
  lastColumnWidth: this.lastColumnWidth
176
170
  });
177
171
  }
@@ -184,7 +178,6 @@ class DocumentContext extends EventEmitter {
184
178
  this.availableWidth = saved.availableWidth;
185
179
  this.availableHeight = saved.availableHeight;
186
180
  this.page = saved.page;
187
- this.endingCell = saved.endingCell;
188
181
  this.lastColumnWidth = saved.lastColumnWidth;
189
182
  }
190
183
 
@@ -495,6 +495,21 @@ class LayoutBuilder {
495
495
  }
496
496
  }
497
497
 
498
+ findStartingSpanCell(arr, i) {
499
+ let requiredColspan = 1;
500
+ for (let index = i - 1; index >= 0; index--) {
501
+ if (!arr[index]._span) {
502
+ if (arr[index].rowSpan > 1 && (arr[index].colSpan || 1) === requiredColspan) {
503
+ return arr[index];
504
+ } else {
505
+ return null;
506
+ }
507
+ }
508
+ requiredColspan++;
509
+ }
510
+ return null;
511
+ }
512
+
498
513
  processRow(columns, widths, gaps, tableBody, tableRow, height) {
499
514
  const storePageBreakData = data => {
500
515
  let pageDesc;
@@ -535,17 +550,52 @@ class LayoutBuilder {
535
550
  }
536
551
  }
537
552
 
538
- this.writer.context().beginColumn(width, leftOffset, getEndingCell(column, i));
553
+ // if rowspan starts in this cell, we retrieve the last cell affected by the rowspan
554
+ const endingCell = getEndingCell(column, i);
555
+ if (endingCell) {
556
+ // We store a reference of the ending cell in the first cell of the rowspan
557
+ column._endingCell = endingCell;
558
+ }
559
+
560
+ // Check if exists and retrieve the cell that started the rowspan in case we are in the cell just after
561
+ let startingSpanCell = this.findStartingSpanCell(columns, i);
562
+ let endingSpanCell = null;
563
+ if (startingSpanCell && startingSpanCell._endingCell) {
564
+ // Reference to the last cell of the rowspan
565
+ endingSpanCell = startingSpanCell._endingCell;
566
+ }
567
+
568
+ // We pass the endingSpanCell reference to store the context just after processing rowspan cell
569
+ this.writer.context().beginColumn(width, leftOffset, endingSpanCell);
539
570
  if (!column._span) {
540
571
  this.processNode(column);
541
572
  addAll(positions, column.positions);
542
573
  } else if (column._columnEndingContext) {
543
574
  // row-span ending
575
+ // Recover the context after processing the rowspanned cell
544
576
  this.writer.context().markEnding(column);
545
577
  }
546
578
  }
547
579
 
548
- this.writer.context().completeColumnGroup(height);
580
+ // Check if last cell is part of a span
581
+ let endingSpanCell = null;
582
+ const lastColumn = columns.length > 0 ? columns[columns.length - 1] : null;
583
+ if (lastColumn) {
584
+ // Previous column cell has a rowspan
585
+ if (lastColumn._endingCell) {
586
+ endingSpanCell = lastColumn._endingCell;
587
+ // Previous column cell is part of a span
588
+ } else if (lastColumn._span === true) {
589
+ // We get the cell that started the span where we set a reference to the ending cell
590
+ const startingSpanCell = this.findStartingSpanCell(columns, columns.length);
591
+ if (startingSpanCell) {
592
+ // Context will be stored here (ending cell)
593
+ endingSpanCell = startingSpanCell._endingCell;
594
+ }
595
+ }
596
+ }
597
+
598
+ this.writer.context().completeColumnGroup(height, endingSpanCell);
549
599
 
550
600
  this.writer.removeListener('pageChanged', storePageBreakData);
551
601
 
@@ -79,50 +79,9 @@ class StyleContextStack {
79
79
  this.push(styleNames[i]);
80
80
  }
81
81
 
82
- let styleProperties = [
83
- 'font',
84
- 'fontSize',
85
- 'fontFeatures',
86
- 'bold',
87
- 'italics',
88
- 'alignment',
89
- 'color',
90
- 'columnGap',
91
- 'fillColor',
92
- 'fillOpacity',
93
- 'decoration',
94
- 'decorationStyle',
95
- 'decorationColor',
96
- 'background',
97
- 'lineHeight',
98
- 'characterSpacing',
99
- 'noWrap',
100
- 'markerColor',
101
- 'leadingIndent',
102
- 'sup',
103
- 'sub'
104
- //'tableCellPadding'
105
- // 'cellBorder',
106
- // 'headerCellBorder',
107
- // 'oddRowCellBorder',
108
- // 'evenRowCellBorder',
109
- // 'tableBorder'
110
- ];
111
- let styleOverrideObject = {};
112
- let pushStyleOverrideObject = false;
113
-
114
- styleProperties.forEach(key => {
115
- if (isValue(item[key])) {
116
- styleOverrideObject[key] = item[key];
117
- pushStyleOverrideObject = true;
118
- }
119
- });
120
-
121
- if (pushStyleOverrideObject) {
122
- this.push(styleOverrideObject);
123
- }
124
-
125
- return styleNames.length + (pushStyleOverrideObject ? 1 : 0);
82
+ // rather than spend significant time making a styleOverrideObject, just add item
83
+ this.push(item);
84
+ return styleNames.length + 1;
126
85
  }
127
86
 
128
87
  /**
@@ -1,5 +1,5 @@
1
1
  import ColumnCalculator from './columnCalculator';
2
- import { isNumber } from './helpers/variableType';
2
+ import { isNumber, isPositiveInteger } from './helpers/variableType';
3
3
 
4
4
  class TableProcessor {
5
5
  constructor(tableNode) {
@@ -95,18 +95,34 @@ class TableProcessor {
95
95
  this.layout = tableNode._layout;
96
96
 
97
97
  availableWidth = writer.context().availableWidth - this.offsets.total;
98
- ColumnCalculator.buildColumnWidths(tableNode.table.widths, availableWidth);
98
+ ColumnCalculator.buildColumnWidths(tableNode.table.widths, availableWidth, this.offsets.total, tableNode);
99
99
 
100
100
  this.tableWidth = tableNode._offsets.total + getTableInnerContentWidth();
101
101
  this.rowSpanData = prepareRowSpanData();
102
102
  this.cleanUpRepeatables = false;
103
103
 
104
- this.headerRows = tableNode.table.headerRows || 0;
105
- if (this.headerRows > tableNode.table.body.length) {
106
- throw new Error(`Too few rows in the table. Property headerRows requires at least ${this.headerRows}, contains only ${tableNode.table.body.length}`);
104
+ // headersRows and rowsWithoutPageBreak (headerRows + keepWithHeaderRows)
105
+ this.headerRows = 0;
106
+ this.rowsWithoutPageBreak = 0;
107
+
108
+ const headerRows = tableNode.table.headerRows;
109
+
110
+ if (isPositiveInteger(headerRows)) {
111
+ this.headerRows = headerRows;
112
+
113
+ if (this.headerRows > tableNode.table.body.length) {
114
+ throw new Error(`Too few rows in the table. Property headerRows requires at least ${this.headerRows}, contains only ${tableNode.table.body.length}`);
115
+ }
116
+
117
+ this.rowsWithoutPageBreak = this.headerRows;
118
+
119
+ const keepWithHeaderRows = tableNode.table.keepWithHeaderRows;
120
+
121
+ if (isPositiveInteger(keepWithHeaderRows)) {
122
+ this.rowsWithoutPageBreak += keepWithHeaderRows;
123
+ }
107
124
  }
108
125
 
109
- this.rowsWithoutPageBreak = this.headerRows + (tableNode.table.keepWithHeaderRows || 0);
110
126
  this.dontBreakRows = tableNode.table.dontBreakRows || false;
111
127
 
112
128
  if (this.rowsWithoutPageBreak) {
@@ -1,6 +1,6 @@
1
1
  import { isString } from './helpers/variableType';
2
2
 
3
- function buildColumnWidths(columns, availableWidth) {
3
+ function buildColumnWidths(columns, availableWidth, offsetTotal = 0, tableNode) {
4
4
  let autoColumns = [];
5
5
  let autoMin = 0;
6
6
  let autoMax = 0;
@@ -24,10 +24,31 @@ function buildColumnWidths(columns, availableWidth) {
24
24
  }
25
25
  });
26
26
 
27
- fixedColumns.forEach(col => {
27
+ fixedColumns.forEach((col, colIndex) => {
28
28
  // width specified as %
29
29
  if (isString(col.width) && /\d+%/.test(col.width)) {
30
- col.width = parseFloat(col.width) * initial_availableWidth / 100;
30
+ // In tables we have to take into consideration the reserved width for paddings and borders
31
+ let reservedWidth = 0;
32
+ if (tableNode) {
33
+ const paddingLeft = tableNode._layout.paddingLeft(colIndex, tableNode);
34
+ const paddingRight = tableNode._layout.paddingRight(colIndex, tableNode);
35
+ const borderLeft = tableNode._layout.vLineWidth (colIndex, tableNode);
36
+ const borderRight = tableNode._layout.vLineWidth (colIndex + 1, tableNode);
37
+ if (colIndex === 0) {
38
+ // first column assumes whole borderLeft and half of border right
39
+ reservedWidth = paddingLeft + paddingRight + borderLeft + (borderRight / 2);
40
+
41
+ } else if (colIndex === fixedColumns.length - 1) {
42
+ // last column assumes whole borderRight and half of border left
43
+ reservedWidth = paddingLeft + paddingRight + (borderLeft / 2) + borderRight;
44
+
45
+ } else {
46
+ // Columns in the middle assume half of each border
47
+ reservedWidth = paddingLeft + paddingRight + (borderLeft / 2) + (borderRight / 2);
48
+ }
49
+ }
50
+ const totalAvailableWidth = initial_availableWidth + offsetTotal;
51
+ col.width = (parseFloat(col.width) * totalAvailableWidth / 100) - reservedWidth;
31
52
  }
32
53
  if (col.width < (col._minWidth) && col.elasticWidth) {
33
54
  col._calcWidth = col._minWidth;
@@ -14,6 +14,17 @@ export function isNumber(variable) {
14
14
  return (typeof variable === 'number') || (variable instanceof Number);
15
15
  }
16
16
 
17
+ /**
18
+ * @param {any} variable
19
+ * @returns {boolean}
20
+ */
21
+ export function isPositiveInteger(variable) {
22
+ if (!isNumber(variable) || !Number.isInteger(variable) || variable <= 0) {
23
+ return false;
24
+ }
25
+ return true;
26
+ }
27
+
17
28
  /**
18
29
  * @param {any} variable
19
30
  * @returns {boolean}