@digicole/pdfmake-rtl 1.2.0 → 2.1.1
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/.vscode/tasks.json +17 -0
- package/CHANGELOG.md +83 -128
- package/LICENSE +22 -22
- package/README.md +188 -681
- package/build/fonts/Cairo/Cairo-Black.ttf +0 -0
- package/build/fonts/Cairo/Cairo-Bold.ttf +0 -0
- package/build/fonts/Cairo/Cairo-ExtraLight.ttf +0 -0
- package/build/fonts/Cairo/Cairo-Light.ttf +0 -0
- package/build/fonts/Cairo/Cairo-Regular.ttf +0 -0
- package/build/fonts/Cairo/Cairo-SemiBold.ttf +0 -0
- package/build/fonts/Cairo.js +27 -0
- package/build/fonts/Roboto/Roboto-Italic.ttf +0 -0
- package/build/fonts/Roboto/Roboto-Medium.ttf +0 -0
- package/build/fonts/Roboto/Roboto-MediumItalic.ttf +0 -0
- package/build/fonts/Roboto/Roboto-Regular.ttf +0 -0
- package/build/fonts/Roboto.js +27 -0
- package/build/pdfmake.js +63736 -71285
- package/build/pdfmake.js.map +1 -1
- package/build/pdfmake.min.js +2 -2
- package/build/pdfmake.min.js.map +1 -1
- package/build/standard-fonts/Courier.js +27 -0
- package/build/standard-fonts/Helvetica.js +27 -0
- package/build/standard-fonts/Symbol.js +21 -0
- package/build/standard-fonts/Times.js +27 -0
- package/build/standard-fonts/ZapfDingbats.js +21 -0
- package/build/vfs_fonts.js +11 -7
- package/build-vfs.js +44 -44
- package/fonts/Cairo/Cairo-Black.ttf +0 -0
- package/fonts/Cairo/Cairo-Bold.ttf +0 -0
- package/fonts/Cairo/Cairo-ExtraLight.ttf +0 -0
- package/fonts/Cairo/Cairo-Light.ttf +0 -0
- package/fonts/Cairo/Cairo-Regular.ttf +0 -0
- package/fonts/Cairo/Cairo-SemiBold.ttf +0 -0
- package/fonts/Cairo.js +8 -0
- package/fonts/Roboto/Roboto-Italic.ttf +0 -0
- package/fonts/Roboto/Roboto-Medium.ttf +0 -0
- package/fonts/Roboto/Roboto-MediumItalic.ttf +0 -0
- package/fonts/Roboto/Roboto-Regular.ttf +0 -0
- package/fonts/Roboto.js +8 -0
- package/index.js +26 -26
- package/package.json +42 -39
- package/src/3rd-party/svg-to-pdfkit/LICENSE +9 -9
- package/src/3rd-party/svg-to-pdfkit/source.js +229 -36
- package/src/3rd-party/svg-to-pdfkit.js +3 -3
- package/src/OutputDocument.js +64 -0
- package/src/OutputDocumentServer.js +32 -0
- package/src/PDFDocument.js +174 -0
- package/src/PageSize.js +53 -0
- package/src/Renderer.js +445 -0
- package/src/TextBreaker.js +168 -0
- package/src/TextInlines.js +263 -0
- package/src/URLResolver.js +43 -0
- package/src/base.js +70 -0
- package/src/browser-extensions/OutputDocumentBrowser.js +80 -0
- package/src/browser-extensions/fonts/Cairo.js +27 -0
- package/src/browser-extensions/fonts/Roboto.js +27 -0
- package/src/browser-extensions/index.js +61 -0
- package/src/browser-extensions/pdfMake.js +1 -355
- package/src/browser-extensions/standard-fonts/Courier.js +27 -0
- package/src/browser-extensions/standard-fonts/Helvetica.js +27 -0
- package/src/browser-extensions/standard-fonts/Symbol.js +21 -0
- package/src/browser-extensions/standard-fonts/Times.js +27 -0
- package/src/browser-extensions/standard-fonts/ZapfDingbats.js +21 -0
- package/src/browser-extensions/virtual-fs-cjs.js +1 -0
- package/src/columnCalculator.js +154 -157
- package/src/docMeasure.js +802 -810
- package/src/docPreprocessor.js +306 -273
- package/src/documentContext.js +345 -340
- package/src/elementWriter.js +736 -411
- package/src/helpers/node.js +136 -0
- package/src/helpers/tools.js +44 -0
- package/src/helpers/variableType.js +50 -0
- package/src/index.js +16 -0
- package/src/layoutBuilder.js +1393 -1197
- package/src/line.js +122 -104
- package/src/pageElementWriter.js +187 -174
- package/src/printer.js +370 -727
- package/src/qrEnc.js +796 -791
- package/src/rtlUtils.js +500 -485
- package/src/standardPageSizes.js +52 -54
- package/src/styleContextStack.js +208 -138
- package/src/svgMeasure.js +109 -70
- package/src/tableLayouts.js +100 -0
- package/src/tableProcessor.js +620 -606
- package/src/textDecorator.js +175 -157
- package/src/virtual-fs.js +66 -0
- package/standard-fonts/Courier.js +8 -0
- package/standard-fonts/Helvetica.js +8 -0
- package/standard-fonts/Symbol.js +5 -0
- package/standard-fonts/Times.js +8 -0
- package/standard-fonts/ZapfDingbats.js +5 -0
- package/index.html +0 -396
- package/src/browser-extensions/URLBrowserResolver.js +0 -96
- package/src/browser-extensions/virtual-fs.js +0 -55
- package/src/fontProvider.js +0 -68
- package/src/helpers.js +0 -138
- package/src/imageMeasure.js +0 -62
- package/src/pdfKitEngine.js +0 -21
- package/src/textTools.js +0 -391
- package/src/traversalTracker.js +0 -47
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import LineBreaker from 'linebreak';
|
|
2
|
+
import { isObject } from './helpers/variableType';
|
|
3
|
+
import StyleContextStack from './StyleContextStack';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @param {string} text
|
|
7
|
+
* @param {boolean} noWrap
|
|
8
|
+
* @param {boolean} breakAll
|
|
9
|
+
* @returns {Array}
|
|
10
|
+
*/
|
|
11
|
+
const splitWords = (text, noWrap, breakAll = false) => {
|
|
12
|
+
let words = [];
|
|
13
|
+
if (text === undefined || text === null) {
|
|
14
|
+
text = '';
|
|
15
|
+
} else {
|
|
16
|
+
text = String(text);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (noWrap) {
|
|
20
|
+
words.push({ text: text });
|
|
21
|
+
return words;
|
|
22
|
+
}
|
|
23
|
+
if (breakAll) {
|
|
24
|
+
return text.split('').map(c => {
|
|
25
|
+
if(c.match(/^\n$|^\r$/)) { // new line
|
|
26
|
+
return { text: '', lineEnd: true };
|
|
27
|
+
}
|
|
28
|
+
return { text: c };
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (text.length === 0) {
|
|
33
|
+
words.push({ text: '' });
|
|
34
|
+
return words;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
let breaker = new LineBreaker(text);
|
|
38
|
+
let last = 0;
|
|
39
|
+
let bk;
|
|
40
|
+
|
|
41
|
+
while ((bk = breaker.nextBreak())) {
|
|
42
|
+
let word = text.slice(last, bk.position);
|
|
43
|
+
|
|
44
|
+
if (bk.required || word.match(/\r?\n$|\r$/)) { // new line
|
|
45
|
+
word = word.replace(/\r?\n$|\r$/, '');
|
|
46
|
+
words.push({ text: word, lineEnd: true });
|
|
47
|
+
} else {
|
|
48
|
+
words.push({ text: word });
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
last = bk.position;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return words;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* @param {Array} words
|
|
59
|
+
* @param {boolean} noWrap
|
|
60
|
+
* @returns {?string}
|
|
61
|
+
*/
|
|
62
|
+
const getFirstWord = (words, noWrap) => {
|
|
63
|
+
let word = words[0];
|
|
64
|
+
if (word === undefined) {
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (noWrap) { // text was not wrapped, we need only first word
|
|
69
|
+
let tmpWords = splitWords(word.text, false);
|
|
70
|
+
if (tmpWords[0] === undefined) {
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
word = tmpWords[0];
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return word.text;
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* @param {Array} words
|
|
81
|
+
* @param {boolean} noWrap
|
|
82
|
+
* @returns {?string}
|
|
83
|
+
*/
|
|
84
|
+
const getLastWord = (words, noWrap) => {
|
|
85
|
+
let word = words[words.length - 1];
|
|
86
|
+
if (word === undefined) {
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (word.lineEnd) {
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (noWrap) { // text was not wrapped, we need only last word
|
|
95
|
+
let tmpWords = splitWords(word.text, false);
|
|
96
|
+
if (tmpWords[tmpWords.length - 1] === undefined) {
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
word = tmpWords[tmpWords.length - 1];
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return word.text;
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
class TextBreaker {
|
|
106
|
+
/**
|
|
107
|
+
* @param {string|Array} texts
|
|
108
|
+
* @param {StyleContextStack} styleContextStack
|
|
109
|
+
* @returns {Array}
|
|
110
|
+
*/
|
|
111
|
+
getBreaks(texts, styleContextStack) {
|
|
112
|
+
let results = [];
|
|
113
|
+
|
|
114
|
+
if (!Array.isArray(texts)) {
|
|
115
|
+
texts = [texts];
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
let lastWord = null;
|
|
119
|
+
for (let i = 0, l = texts.length; i < l; i++) {
|
|
120
|
+
let item = texts[i];
|
|
121
|
+
let style = null;
|
|
122
|
+
let words;
|
|
123
|
+
let breakAll = StyleContextStack.getStyleProperty(item || {}, styleContextStack, 'wordBreak', 'normal') === 'break-all';
|
|
124
|
+
let noWrap = StyleContextStack.getStyleProperty(item || {}, styleContextStack, 'noWrap', false);
|
|
125
|
+
if (isObject(item)) {
|
|
126
|
+
if (item._textRef && item._textRef._textNodeRef.text) {
|
|
127
|
+
item.text = item._textRef._textNodeRef.text;
|
|
128
|
+
}
|
|
129
|
+
words = splitWords(item.text, noWrap, breakAll);
|
|
130
|
+
style = StyleContextStack.copyStyle(item);
|
|
131
|
+
} else {
|
|
132
|
+
words = splitWords(item, noWrap, breakAll);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (lastWord && words.length) {
|
|
136
|
+
let firstWord = getFirstWord(words, noWrap);
|
|
137
|
+
|
|
138
|
+
let wrapWords = splitWords(lastWord + firstWord, false);
|
|
139
|
+
if (wrapWords.length === 1) {
|
|
140
|
+
results[results.length - 1].noNewLine = true;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
for (let i2 = 0, l2 = words.length; i2 < l2; i2++) {
|
|
145
|
+
let result = {
|
|
146
|
+
text: words[i2].text
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
if (words[i2].lineEnd) {
|
|
150
|
+
result.lineEnd = true;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
StyleContextStack.copyStyle(style, result);
|
|
154
|
+
|
|
155
|
+
results.push(result);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
lastWord = null;
|
|
159
|
+
if (i + 1 < l) {
|
|
160
|
+
lastWord = getLastWord(words, noWrap);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return results;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export default TextBreaker;
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
import TextBreaker from './TextBreaker';
|
|
2
|
+
import StyleContextStack from './StyleContextStack';
|
|
3
|
+
import { containsRTL, getTextDirection } from './rtlUtils';
|
|
4
|
+
|
|
5
|
+
const LEADING = /^(\s)+/g;
|
|
6
|
+
const TRAILING = /(\s)+$/g;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @param {Array} array
|
|
10
|
+
* @returns {Array}
|
|
11
|
+
*/
|
|
12
|
+
const flattenTextArray = array => {
|
|
13
|
+
function flatten(array) {
|
|
14
|
+
return array.reduce((prev, cur) => {
|
|
15
|
+
let current = Array.isArray(cur.text) ? flatten(cur.text) : cur;
|
|
16
|
+
let more = [].concat(current).some(Array.isArray);
|
|
17
|
+
return prev.concat(more ? flatten(current) : current);
|
|
18
|
+
}, []);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (!Array.isArray(array)) {
|
|
22
|
+
array = [array];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// TODO: Styling in nested text (issue: https://github.com/bpampuch/pdfmake/issues/1174)
|
|
26
|
+
|
|
27
|
+
array = flatten(array);
|
|
28
|
+
|
|
29
|
+
return array;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Text measurement utility
|
|
35
|
+
*/
|
|
36
|
+
class TextInlines {
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* @param {object} pdfDocument object is instance of PDFDocument
|
|
40
|
+
*/
|
|
41
|
+
constructor(pdfDocument) {
|
|
42
|
+
this.pdfDocument = pdfDocument;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Converts an array of strings (or inline-definition-objects) into a collection
|
|
47
|
+
* of inlines and calculated minWidth/maxWidth and their min/max widths
|
|
48
|
+
*
|
|
49
|
+
* @param {Array|object} textArray an array of inline-definition-objects (or strings)
|
|
50
|
+
* @param {StyleContextStack} styleContextStack current style stack
|
|
51
|
+
* @returns {object} collection of inlines, minWidth, maxWidth
|
|
52
|
+
*/
|
|
53
|
+
buildInlines(textArray, styleContextStack) {
|
|
54
|
+
const getTrimmedWidth = item => {
|
|
55
|
+
return Math.max(0, item.width - item.leadingCut - item.trailingCut);
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
let minWidth = 0;
|
|
59
|
+
let maxWidth = 0;
|
|
60
|
+
let currentLineWidth;
|
|
61
|
+
|
|
62
|
+
let flattenedTextArray = flattenTextArray(textArray);
|
|
63
|
+
|
|
64
|
+
const textBreaker = new TextBreaker();
|
|
65
|
+
let brokenText = textBreaker.getBreaks(flattenedTextArray, styleContextStack);
|
|
66
|
+
|
|
67
|
+
let measuredText = this.measure(brokenText, styleContextStack);
|
|
68
|
+
|
|
69
|
+
measuredText.forEach(inline => {
|
|
70
|
+
minWidth = Math.max(minWidth, getTrimmedWidth(inline));
|
|
71
|
+
|
|
72
|
+
if (!currentLineWidth) {
|
|
73
|
+
currentLineWidth = { width: 0, leadingCut: inline.leadingCut, trailingCut: 0 };
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
currentLineWidth.width += inline.width;
|
|
77
|
+
currentLineWidth.trailingCut = inline.trailingCut;
|
|
78
|
+
|
|
79
|
+
maxWidth = Math.max(maxWidth, getTrimmedWidth(currentLineWidth));
|
|
80
|
+
|
|
81
|
+
if (inline.lineEnd) {
|
|
82
|
+
currentLineWidth = null;
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
if (StyleContextStack.getStyleProperty({}, styleContextStack, 'noWrap', false)) {
|
|
87
|
+
minWidth = maxWidth;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return {
|
|
91
|
+
items: measuredText,
|
|
92
|
+
minWidth: minWidth,
|
|
93
|
+
maxWidth: maxWidth
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
measure(array, styleContextStack) {
|
|
98
|
+
if (array.length) {
|
|
99
|
+
let leadingIndent = StyleContextStack.getStyleProperty(array[0], styleContextStack, 'leadingIndent', 0);
|
|
100
|
+
if (leadingIndent) {
|
|
101
|
+
array[0].leadingCut = -leadingIndent;
|
|
102
|
+
array[0].leadingIndent = leadingIndent;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
array.forEach(item => {
|
|
107
|
+
// Font resolution priority:
|
|
108
|
+
// 1. Item-level font (set directly on the text node)
|
|
109
|
+
// 2. Style/named-style font (from style stack)
|
|
110
|
+
// 3. defaultStyle font (from document definition)
|
|
111
|
+
// 4. Auto-detect: Cairo for RTL/Arabic text, Roboto for LTR/Latin text
|
|
112
|
+
let font = StyleContextStack.getStyleProperty(item, styleContextStack, 'font', null);
|
|
113
|
+
let bold = StyleContextStack.getStyleProperty(item, styleContextStack, 'bold', false);
|
|
114
|
+
let italics = StyleContextStack.getStyleProperty(item, styleContextStack, 'italics', false);
|
|
115
|
+
|
|
116
|
+
if (!font) {
|
|
117
|
+
// No font set by item, style, or defaultStyle — auto-detect from text content
|
|
118
|
+
if (item.text && containsRTL(item.text)) {
|
|
119
|
+
font = 'Cairo';
|
|
120
|
+
} else {
|
|
121
|
+
font = 'Roboto';
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
item.font = this.pdfDocument.provideFont(font, bold, italics);
|
|
126
|
+
|
|
127
|
+
item.alignment = StyleContextStack.getStyleProperty(item, styleContextStack, 'alignment', 'left');
|
|
128
|
+
|
|
129
|
+
// RTL Support: detect direction and set isRTL on each inline
|
|
130
|
+
let direction = StyleContextStack.getStyleProperty(item, styleContextStack, 'direction', null);
|
|
131
|
+
if (direction === 'rtl') {
|
|
132
|
+
item.isRTL = true;
|
|
133
|
+
item.direction = 'rtl';
|
|
134
|
+
} else if (direction === 'ltr') {
|
|
135
|
+
item.isRTL = false;
|
|
136
|
+
item.direction = 'ltr';
|
|
137
|
+
} else {
|
|
138
|
+
// Auto-detect from text content
|
|
139
|
+
let textDir = getTextDirection(item.text);
|
|
140
|
+
item.isRTL = textDir === 'rtl';
|
|
141
|
+
item.direction = textDir === 'rtl' ? 'rtl' : 'ltr';
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// For RTL text, auto-default alignment to 'right' if not explicitly set
|
|
145
|
+
if (item.isRTL && item.alignment === 'left') {
|
|
146
|
+
let explicitAlignment = StyleContextStack.getStyleProperty(item, styleContextStack, 'alignment', null);
|
|
147
|
+
if (!explicitAlignment) {
|
|
148
|
+
item.alignment = 'right';
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
item.fontSize = StyleContextStack.getStyleProperty(item, styleContextStack, 'fontSize', 12);
|
|
153
|
+
item.fontFeatures = StyleContextStack.getStyleProperty(item, styleContextStack, 'fontFeatures', null);
|
|
154
|
+
item.characterSpacing = StyleContextStack.getStyleProperty(item, styleContextStack, 'characterSpacing', 0);
|
|
155
|
+
item.color = StyleContextStack.getStyleProperty(item, styleContextStack, 'color', 'black');
|
|
156
|
+
item.decoration = StyleContextStack.getStyleProperty(item, styleContextStack, 'decoration', null);
|
|
157
|
+
item.decorationColor = StyleContextStack.getStyleProperty(item, styleContextStack, 'decorationColor', null);
|
|
158
|
+
item.decorationStyle = StyleContextStack.getStyleProperty(item, styleContextStack, 'decorationStyle', null);
|
|
159
|
+
item.decorationThickness = StyleContextStack.getStyleProperty(item, styleContextStack, 'decorationThickness', null);
|
|
160
|
+
item.background = StyleContextStack.getStyleProperty(item, styleContextStack, 'background', null);
|
|
161
|
+
item.link = StyleContextStack.getStyleProperty(item, styleContextStack, 'link', null);
|
|
162
|
+
item.linkToPage = StyleContextStack.getStyleProperty(item, styleContextStack, 'linkToPage', null);
|
|
163
|
+
item.linkToDestination = StyleContextStack.getStyleProperty(item, styleContextStack, 'linkToDestination', null);
|
|
164
|
+
item.noWrap = StyleContextStack.getStyleProperty(item, styleContextStack, 'noWrap', null);
|
|
165
|
+
item.opacity = StyleContextStack.getStyleProperty(item, styleContextStack, 'opacity', 1);
|
|
166
|
+
item.sup = StyleContextStack.getStyleProperty(item, styleContextStack, 'sup', false);
|
|
167
|
+
item.sub = StyleContextStack.getStyleProperty(item, styleContextStack, 'sub', false);
|
|
168
|
+
|
|
169
|
+
if (item.sup || item.sub) {
|
|
170
|
+
// font size reduction taken from here: https://en.wikipedia.org/wiki/Subscript_and_superscript#Desktop_publishing
|
|
171
|
+
item.fontSize *= 0.58;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
let lineHeight = StyleContextStack.getStyleProperty(item, styleContextStack, 'lineHeight', 1);
|
|
175
|
+
|
|
176
|
+
item.width = this.widthOfText(item.text, item);
|
|
177
|
+
item.height = item.font.lineHeight(item.fontSize) * lineHeight;
|
|
178
|
+
|
|
179
|
+
if (!item.leadingCut) {
|
|
180
|
+
item.leadingCut = 0;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
let preserveLeadingSpaces = StyleContextStack.getStyleProperty(item, styleContextStack, 'preserveLeadingSpaces', false);
|
|
184
|
+
if (!preserveLeadingSpaces) {
|
|
185
|
+
let leadingSpaces = item.text.match(LEADING);
|
|
186
|
+
if (leadingSpaces) {
|
|
187
|
+
item.leadingCut += this.widthOfText(leadingSpaces[0], item);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
item.trailingCut = 0;
|
|
192
|
+
|
|
193
|
+
let preserveTrailingSpaces = StyleContextStack.getStyleProperty(item, styleContextStack, 'preserveTrailingSpaces', false);
|
|
194
|
+
if (!preserveTrailingSpaces) {
|
|
195
|
+
let trailingSpaces = item.text.match(TRAILING);
|
|
196
|
+
if (trailingSpaces) {
|
|
197
|
+
item.trailingCut = this.widthOfText(trailingSpaces[0], item);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}, this);
|
|
201
|
+
|
|
202
|
+
return array;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Width of text
|
|
207
|
+
*
|
|
208
|
+
* @param {string} text
|
|
209
|
+
* @param {object} inline
|
|
210
|
+
* @returns {number}
|
|
211
|
+
*/
|
|
212
|
+
widthOfText(text, inline) {
|
|
213
|
+
return inline.font.widthOfString(text, inline.fontSize, inline.fontFeatures) + ((inline.characterSpacing || 0) * (text.length - 1));
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Returns size of the specified string (without breaking it) using the current style
|
|
218
|
+
*
|
|
219
|
+
* @param {string} text text to be measured
|
|
220
|
+
* @param {object} styleContextStack current style stack
|
|
221
|
+
* @returns {object} size of the specified string
|
|
222
|
+
*/
|
|
223
|
+
sizeOfText(text, styleContextStack) {
|
|
224
|
+
//TODO: refactor - extract from measure
|
|
225
|
+
let fontName = StyleContextStack.getStyleProperty({}, styleContextStack, 'font', 'Roboto');
|
|
226
|
+
let fontSize = StyleContextStack.getStyleProperty({}, styleContextStack, 'fontSize', 12);
|
|
227
|
+
let fontFeatures = StyleContextStack.getStyleProperty({}, styleContextStack, 'fontFeatures', null);
|
|
228
|
+
let bold = StyleContextStack.getStyleProperty({}, styleContextStack, 'bold', false);
|
|
229
|
+
let italics = StyleContextStack.getStyleProperty({}, styleContextStack, 'italics', false);
|
|
230
|
+
let lineHeight = StyleContextStack.getStyleProperty({}, styleContextStack, 'lineHeight', 1);
|
|
231
|
+
let characterSpacing = StyleContextStack.getStyleProperty({}, styleContextStack, 'characterSpacing', 0);
|
|
232
|
+
|
|
233
|
+
let font = this.pdfDocument.provideFont(fontName, bold, italics);
|
|
234
|
+
|
|
235
|
+
return {
|
|
236
|
+
width: this.widthOfText(text, { font: font, fontSize: fontSize, characterSpacing: characterSpacing, fontFeatures: fontFeatures }),
|
|
237
|
+
height: font.lineHeight(fontSize) * lineHeight,
|
|
238
|
+
fontSize: fontSize,
|
|
239
|
+
lineHeight: lineHeight,
|
|
240
|
+
ascender: font.ascender / 1000 * fontSize,
|
|
241
|
+
descender: font.descender / 1000 * fontSize
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Returns size of the specified rotated string (without breaking it) using the current style
|
|
247
|
+
*
|
|
248
|
+
* @param {string} text text to be measured
|
|
249
|
+
* @param {number} angle
|
|
250
|
+
* @param {object} styleContextStack current style stack
|
|
251
|
+
* @returns {object} size of the specified string
|
|
252
|
+
*/
|
|
253
|
+
sizeOfRotatedText(text, angle, styleContextStack) {
|
|
254
|
+
let angleRad = angle * Math.PI / -180;
|
|
255
|
+
let size = this.sizeOfText(text, styleContextStack);
|
|
256
|
+
return {
|
|
257
|
+
width: Math.abs(size.height * Math.sin(angleRad)) + Math.abs(size.width * Math.cos(angleRad)),
|
|
258
|
+
height: Math.abs(size.width * Math.sin(angleRad)) + Math.abs(size.height * Math.cos(angleRad))
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
export default TextInlines;
|
|
@@ -0,0 +1,43 @@
|
|
|
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}")`);
|
|
6
|
+
}
|
|
7
|
+
return await response.arrayBuffer();
|
|
8
|
+
} catch (error) {
|
|
9
|
+
throw new Error(`Network request failed (url: "${url}", error: ${error.message})`);
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
class URLResolver {
|
|
14
|
+
constructor(fs) {
|
|
15
|
+
this.fs = fs;
|
|
16
|
+
this.resolving = {};
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
resolve(url, headers = {}) {
|
|
20
|
+
const resolveUrlInternal = async () => {
|
|
21
|
+
if (url.toLowerCase().startsWith('https://') || url.toLowerCase().startsWith('http://')) {
|
|
22
|
+
if (this.fs.existsSync(url)) {
|
|
23
|
+
return; // url was downloaded earlier
|
|
24
|
+
}
|
|
25
|
+
const buffer = await fetchUrl(url, headers);
|
|
26
|
+
this.fs.writeFileSync(url, buffer);
|
|
27
|
+
}
|
|
28
|
+
// else cannot be resolved
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
if (!this.resolving[url]) {
|
|
32
|
+
this.resolving[url] = resolveUrlInternal();
|
|
33
|
+
}
|
|
34
|
+
return this.resolving[url];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
resolved() {
|
|
38
|
+
return Promise.all(Object.values(this.resolving));
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export default URLResolver;
|
package/src/base.js
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import Printer from './Printer';
|
|
2
|
+
import virtualfs from './virtual-fs';
|
|
3
|
+
import { pack } from './helpers/tools';
|
|
4
|
+
import { isObject } from './helpers/variableType';
|
|
5
|
+
|
|
6
|
+
class pdfmake {
|
|
7
|
+
|
|
8
|
+
constructor() {
|
|
9
|
+
this.virtualfs = virtualfs;
|
|
10
|
+
this.urlResolver = null;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @param {object} docDefinition
|
|
15
|
+
* @param {?object} options
|
|
16
|
+
* @returns {object}
|
|
17
|
+
*/
|
|
18
|
+
createPdf(docDefinition, options = {}) {
|
|
19
|
+
if (!isObject(docDefinition)) {
|
|
20
|
+
throw new Error("Parameter 'docDefinition' has an invalid type. Object expected.");
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (!isObject(options)) {
|
|
24
|
+
throw new Error("Parameter 'options' has an invalid type. Object expected.");
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
options.progressCallback = this.progressCallback;
|
|
28
|
+
options.tableLayouts = this.tableLayouts;
|
|
29
|
+
|
|
30
|
+
let printer = new Printer(this.fonts, this.virtualfs, this.urlResolver());
|
|
31
|
+
const pdfDocumentPromise = printer.createPdfKitDocument(docDefinition, options);
|
|
32
|
+
|
|
33
|
+
return this._transformToDocument(pdfDocumentPromise);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
setProgressCallback(callback) {
|
|
37
|
+
this.progressCallback = callback;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
addTableLayouts(tableLayouts) {
|
|
41
|
+
this.tableLayouts = pack(this.tableLayouts, tableLayouts);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
setTableLayouts(tableLayouts) {
|
|
45
|
+
this.tableLayouts = tableLayouts;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
clearTableLayouts() {
|
|
49
|
+
this.tableLayouts = {};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
addFonts(fonts) {
|
|
53
|
+
this.fonts = pack(this.fonts, fonts);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
setFonts(fonts) {
|
|
57
|
+
this.fonts = fonts;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
clearFonts() {
|
|
61
|
+
this.fonts = {};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
_transformToDocument(doc) {
|
|
65
|
+
return doc;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export default pdfmake;
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import OutputDocument from '../OutputDocument';
|
|
2
|
+
import { saveAs } from 'file-saver';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @returns {Window}
|
|
6
|
+
*/
|
|
7
|
+
const openWindow = () => {
|
|
8
|
+
// we have to open the window immediately and store the reference
|
|
9
|
+
// otherwise popup blockers will stop us
|
|
10
|
+
let win = window.open('', '_blank');
|
|
11
|
+
if (win === null) {
|
|
12
|
+
throw new Error('Open PDF in new window blocked by browser');
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
return win;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
class OutputDocumentBrowser extends OutputDocument {
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* @returns {Promise<Blob>}
|
|
22
|
+
*/
|
|
23
|
+
async getBlob() {
|
|
24
|
+
const buffer = await this.getBuffer();
|
|
25
|
+
return new Blob([buffer], { type: 'application/pdf' });
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* @param {string} filename
|
|
30
|
+
* @returns {Promise}
|
|
31
|
+
*/
|
|
32
|
+
async download(filename = 'file.pdf') {
|
|
33
|
+
const blob = await this.getBlob();
|
|
34
|
+
saveAs(blob, filename);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* @param {Window} win
|
|
39
|
+
* @returns {Promise}
|
|
40
|
+
*/
|
|
41
|
+
async open(win = null) {
|
|
42
|
+
if (!win) {
|
|
43
|
+
win = openWindow();
|
|
44
|
+
}
|
|
45
|
+
const blob = await this.getBlob();
|
|
46
|
+
try {
|
|
47
|
+
let urlCreator = window.URL || window.webkitURL;
|
|
48
|
+
let pdfUrl = urlCreator.createObjectURL(blob);
|
|
49
|
+
win.location.href = pdfUrl;
|
|
50
|
+
|
|
51
|
+
/* temporarily disabled
|
|
52
|
+
if (win === window) {
|
|
53
|
+
return;
|
|
54
|
+
} else {
|
|
55
|
+
setTimeout(() => {
|
|
56
|
+
if (win.window === null) { // is closed by AdBlock
|
|
57
|
+
window.location.href = pdfUrl; // open in actual window
|
|
58
|
+
}
|
|
59
|
+
return;
|
|
60
|
+
}, 500);
|
|
61
|
+
}
|
|
62
|
+
*/
|
|
63
|
+
} catch (e) {
|
|
64
|
+
win.close();
|
|
65
|
+
throw e;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* @param {Window} win
|
|
71
|
+
* @returns {Promise}
|
|
72
|
+
*/
|
|
73
|
+
async print(win = null) {
|
|
74
|
+
const stream = await this.getStream();
|
|
75
|
+
stream.setOpenActionAsPrint();
|
|
76
|
+
await this.open(win);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export default OutputDocumentBrowser;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
var fs = require('fs');
|
|
2
|
+
|
|
3
|
+
var fontContainer = {
|
|
4
|
+
vfs: {
|
|
5
|
+
'Cairo-Regular.ttf': { data: fs.readFileSync(__dirname + '/../../../fonts/Cairo/Cairo-Regular.ttf', 'base64'), encoding: 'base64' },
|
|
6
|
+
'Cairo-Bold.ttf': { data: fs.readFileSync(__dirname + '/../../../fonts/Cairo/Cairo-Bold.ttf', 'base64'), encoding: 'base64' },
|
|
7
|
+
'Cairo-Light.ttf': { data: fs.readFileSync(__dirname + '/../../../fonts/Cairo/Cairo-Light.ttf', 'base64'), encoding: 'base64' },
|
|
8
|
+
'Cairo-SemiBold.ttf': { data: fs.readFileSync(__dirname + '/../../../fonts/Cairo/Cairo-SemiBold.ttf', 'base64'), encoding: 'base64' }
|
|
9
|
+
},
|
|
10
|
+
fonts: {
|
|
11
|
+
Cairo: {
|
|
12
|
+
normal: 'Cairo-Regular.ttf',
|
|
13
|
+
bold: 'Cairo-Bold.ttf',
|
|
14
|
+
italics: 'Cairo-Light.ttf',
|
|
15
|
+
bolditalics: 'Cairo-SemiBold.ttf'
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
var _global = typeof window === 'object' ? window : typeof global === 'object' ? global : typeof self === 'object' ? self : this;
|
|
21
|
+
if (typeof _global.pdfMake !== 'undefined' && typeof _global.pdfMake.addFontContainer !== 'undefined') {
|
|
22
|
+
_global.pdfMake.addFontContainer(fontContainer);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (typeof module !== 'undefined') {
|
|
26
|
+
module.exports = fontContainer;
|
|
27
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
var fs = require('fs');
|
|
2
|
+
|
|
3
|
+
var fontContainer = {
|
|
4
|
+
vfs: {
|
|
5
|
+
'Roboto-Regular.ttf': { data: fs.readFileSync(__dirname + '/../../../fonts/Roboto/Roboto-Regular.ttf', 'base64'), encoding: 'base64' },
|
|
6
|
+
'Roboto-Medium.ttf': { data: fs.readFileSync(__dirname + '/../../../fonts/Roboto/Roboto-Medium.ttf', 'base64'), encoding: 'base64' },
|
|
7
|
+
'Roboto-Italic.ttf': { data: fs.readFileSync(__dirname + '/../../../fonts/Roboto/Roboto-Italic.ttf', 'base64'), encoding: 'base64' },
|
|
8
|
+
'Roboto-MediumItalic.ttf': { data: fs.readFileSync(__dirname + '/../../../fonts/Roboto/Roboto-MediumItalic.ttf', 'base64'), encoding: 'base64' }
|
|
9
|
+
},
|
|
10
|
+
fonts: {
|
|
11
|
+
Roboto: {
|
|
12
|
+
normal: 'Roboto-Regular.ttf',
|
|
13
|
+
bold: 'Roboto-Medium.ttf',
|
|
14
|
+
italics: 'Roboto-Italic.ttf',
|
|
15
|
+
bolditalics: 'Roboto-MediumItalic.ttf'
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
var _global = typeof window === 'object' ? window : typeof global === 'object' ? global : typeof self === 'object' ? self : this;
|
|
21
|
+
if (typeof _global.pdfMake !== 'undefined' && typeof _global.pdfMake.addFontContainer !== 'undefined') {
|
|
22
|
+
_global.pdfMake.addFontContainer(fontContainer);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (typeof module !== 'undefined') {
|
|
26
|
+
module.exports = fontContainer;
|
|
27
|
+
}
|