@digicole/pdfmake-rtl 2.1.0 → 2.1.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/CHANGELOG.md +118 -83
- package/README.md +11 -10
- package/build/pdfmake.js +71 -42
- package/build/pdfmake.js.map +1 -1
- package/build/pdfmake.min.js +2 -2
- package/build/pdfmake.min.js.map +1 -1
- package/build/vfs_fonts.js +11 -11
- package/js/3rd-party/svg-to-pdfkit/source.js +3823 -0
- package/js/3rd-party/svg-to-pdfkit.js +7 -0
- package/js/DocMeasure.js +713 -0
- package/js/DocPreprocessor.js +275 -0
- package/js/DocumentContext.js +310 -0
- package/js/ElementWriter.js +687 -0
- package/js/LayoutBuilder.js +1240 -0
- package/js/Line.js +113 -0
- package/js/OutputDocument.js +64 -0
- package/js/OutputDocumentServer.js +29 -0
- package/js/PDFDocument.js +144 -0
- package/js/PageElementWriter.js +161 -0
- package/js/PageSize.js +74 -0
- package/js/Printer.js +351 -0
- package/js/Renderer.js +417 -0
- package/js/SVGMeasure.js +92 -0
- package/js/StyleContextStack.js +191 -0
- package/js/TableProcessor.js +575 -0
- package/js/TextBreaker.js +166 -0
- package/js/TextDecorator.js +152 -0
- package/js/TextInlines.js +244 -0
- package/js/URLResolver.js +43 -0
- package/js/base.js +59 -0
- package/js/browser-extensions/OutputDocumentBrowser.js +82 -0
- package/js/browser-extensions/fonts/Cairo.js +38 -0
- package/js/browser-extensions/fonts/Roboto.js +38 -0
- package/js/browser-extensions/index.js +59 -0
- package/js/browser-extensions/pdfMake.js +3 -0
- package/js/browser-extensions/standard-fonts/Courier.js +38 -0
- package/js/browser-extensions/standard-fonts/Helvetica.js +38 -0
- package/js/browser-extensions/standard-fonts/Symbol.js +23 -0
- package/js/browser-extensions/standard-fonts/Times.js +38 -0
- package/js/browser-extensions/standard-fonts/ZapfDingbats.js +23 -0
- package/js/browser-extensions/virtual-fs-cjs.js +3 -0
- package/js/columnCalculator.js +148 -0
- package/js/helpers/node.js +123 -0
- package/js/helpers/tools.js +46 -0
- package/js/helpers/variableType.js +59 -0
- package/js/index.js +15 -0
- package/js/qrEnc.js +721 -0
- package/js/rtlUtils.js +519 -0
- package/js/standardPageSizes.js +56 -0
- package/js/tableLayouts.js +98 -0
- package/js/virtual-fs.js +60 -0
- package/package.json +1 -1
- package/src/{docMeasure.js → DocMeasure.js} +8 -8
- package/src/{elementWriter.js → ElementWriter.js} +3 -3
- package/src/{layoutBuilder.js → LayoutBuilder.js} +1406 -1393
- package/src/{tableProcessor.js → TableProcessor.js} +633 -620
- package/src/rtlUtils.js +503 -500
- /package/src/{docPreprocessor.js → DocPreprocessor.js} +0 -0
- /package/src/{documentContext.js → DocumentContext.js} +0 -0
- /package/src/{line.js → Line.js} +0 -0
- /package/src/{pageElementWriter.js → PageElementWriter.js} +0 -0
- /package/src/{printer.js → Printer.js} +0 -0
- /package/src/{svgMeasure.js → SVGMeasure.js} +0 -0
- /package/src/{styleContextStack.js → StyleContextStack.js} +0 -0
- /package/src/{textDecorator.js → TextDecorator.js} +0 -0
|
@@ -0,0 +1,687 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
exports.__esModule = true;
|
|
4
|
+
exports.default = void 0;
|
|
5
|
+
var _variableType = require("./helpers/variableType");
|
|
6
|
+
var _tools = require("./helpers/tools");
|
|
7
|
+
var _DocumentContext = _interopRequireDefault(require("./DocumentContext"));
|
|
8
|
+
var _events = require("events");
|
|
9
|
+
var _rtlUtils = require("./rtlUtils");
|
|
10
|
+
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
11
|
+
/**
|
|
12
|
+
* A line/vector writer, which adds elements to current page and sets
|
|
13
|
+
* their positions based on the context
|
|
14
|
+
*/
|
|
15
|
+
class ElementWriter extends _events.EventEmitter {
|
|
16
|
+
/**
|
|
17
|
+
* @param {DocumentContext} context
|
|
18
|
+
*/
|
|
19
|
+
constructor(context) {
|
|
20
|
+
super();
|
|
21
|
+
this._context = context;
|
|
22
|
+
this.contextStack = [];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* @returns {DocumentContext}
|
|
27
|
+
*/
|
|
28
|
+
context() {
|
|
29
|
+
return this._context;
|
|
30
|
+
}
|
|
31
|
+
addLine(line, dontUpdateContextPosition, index) {
|
|
32
|
+
let height = line.getHeight();
|
|
33
|
+
let context = this.context();
|
|
34
|
+
let page = context.getCurrentPage();
|
|
35
|
+
let position = this.getCurrentPositionOnPage();
|
|
36
|
+
if (context.availableHeight < height || !page) {
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
line.x = context.x + (line.x || 0);
|
|
40
|
+
line.y = context.y + (line.y || 0);
|
|
41
|
+
this.alignLine(line);
|
|
42
|
+
addPageItem(page, {
|
|
43
|
+
type: 'line',
|
|
44
|
+
item: line
|
|
45
|
+
}, index);
|
|
46
|
+
this.emit('lineAdded', line);
|
|
47
|
+
if (!dontUpdateContextPosition) {
|
|
48
|
+
context.moveDown(height);
|
|
49
|
+
}
|
|
50
|
+
return position;
|
|
51
|
+
}
|
|
52
|
+
alignLine(line) {
|
|
53
|
+
// Skip alignment for list marker lines - their position is manually
|
|
54
|
+
// calculated in processList and should not be affected by inherited
|
|
55
|
+
// alignment or direction properties
|
|
56
|
+
if (line.listMarker) {
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
let width = this.context().availableWidth;
|
|
60
|
+
let lineWidth = line.getWidth();
|
|
61
|
+
let alignment = line.inlines && line.inlines.length > 0 && line.inlines[0].alignment;
|
|
62
|
+
let isRTL = line.isRTL && line.isRTL();
|
|
63
|
+
let offset = 0;
|
|
64
|
+
|
|
65
|
+
// For RTL lines, apply special handling
|
|
66
|
+
if (isRTL) {
|
|
67
|
+
if (!alignment || alignment === 'left') {
|
|
68
|
+
alignment = 'right';
|
|
69
|
+
}
|
|
70
|
+
this.adjustRTLInlines(line, width);
|
|
71
|
+
}
|
|
72
|
+
switch (alignment) {
|
|
73
|
+
case 'right':
|
|
74
|
+
offset = width - lineWidth;
|
|
75
|
+
break;
|
|
76
|
+
case 'center':
|
|
77
|
+
offset = (width - lineWidth) / 2;
|
|
78
|
+
break;
|
|
79
|
+
}
|
|
80
|
+
if (offset) {
|
|
81
|
+
line.x = (line.x || 0) + offset;
|
|
82
|
+
}
|
|
83
|
+
if (alignment === 'justify' && !line.newLineForced && !line.lastLineInParagraph && line.inlines.length > 1) {
|
|
84
|
+
let additionalSpacing = (width - lineWidth) / (line.inlines.length - 1);
|
|
85
|
+
for (let i = 1, l = line.inlines.length; i < l; i++) {
|
|
86
|
+
offset = i * additionalSpacing;
|
|
87
|
+
line.inlines[i].x += offset;
|
|
88
|
+
line.inlines[i].justifyShift = additionalSpacing;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Adjust RTL inline positioning - reorder inlines for proper visual display.
|
|
95
|
+
*
|
|
96
|
+
* Implements a simplified Unicode Bidirectional Algorithm (UBA):
|
|
97
|
+
* 0. Pre-split inlines at RTL↔neutral boundaries so punctuation like "/" between
|
|
98
|
+
* Arabic and Latin text is treated as a separate neutral inline
|
|
99
|
+
* 1. Classify each inline as RTL, LTR, or neutral
|
|
100
|
+
* 2. Group consecutive same-direction inlines into directional "runs"
|
|
101
|
+
* 3. Resolve neutral runs: attach to adjacent run based on surrounding context
|
|
102
|
+
* 4. Reverse the order of runs (base direction is RTL)
|
|
103
|
+
* 5. Within each LTR run keep order; within each RTL run reverse inlines
|
|
104
|
+
* 6. Recalculate x positions
|
|
105
|
+
*
|
|
106
|
+
* This preserves the positional relationship between adjacent text and
|
|
107
|
+
* punctuation (e.g. "العربية/arabic" keeps the "/" attached correctly).
|
|
108
|
+
*
|
|
109
|
+
* @param {object} line - Line containing RTL text
|
|
110
|
+
*/
|
|
111
|
+
adjustRTLInlines(line) {
|
|
112
|
+
if (!line.inlines || line.inlines.length === 0) {
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
const LTR_REGEX = /[A-Za-z\u00C0-\u024F\u1E00-\u1EFF]/;
|
|
116
|
+
const NUMBER_PUNCTUATION_REGEX = /^(\d+)([.:/\-)(]+)(\s*)$/;
|
|
117
|
+
// Characters that are "boundary neutral" — separators/punctuation between scripts
|
|
118
|
+
const BOUNDARY_NEUTRAL = /[/\\\-()[\]{}<>:;.,!?@#$%^&*_=+|~`'"،؛؟\s]/;
|
|
119
|
+
|
|
120
|
+
// --- Step 0: Pre-split inlines at RTL↔neutral and LTR↔neutral boundaries ---
|
|
121
|
+
// e.g. "العربية/" → ["العربية", "/"] and "hello-" → ["hello", "-"]
|
|
122
|
+
let splitInlines = [];
|
|
123
|
+
line.inlines.forEach(inline => {
|
|
124
|
+
let text = inline.text;
|
|
125
|
+
if (!text || text.length === 0) {
|
|
126
|
+
splitInlines.push(inline);
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
let hasStrongRTL = (0, _rtlUtils.containsRTL)(text);
|
|
130
|
+
let hasStrongLTR = LTR_REGEX.test(text);
|
|
131
|
+
|
|
132
|
+
// Only split if the inline has strong directional chars AND trailing/leading neutrals
|
|
133
|
+
if ((hasStrongRTL || hasStrongLTR) && text.length > 1) {
|
|
134
|
+
// Split trailing neutral characters (e.g. "العربية/" → "العربية" + "/")
|
|
135
|
+
let trailingStart = text.length;
|
|
136
|
+
while (trailingStart > 0) {
|
|
137
|
+
let ch = text[trailingStart - 1];
|
|
138
|
+
if (BOUNDARY_NEUTRAL.test(ch) && !(0, _rtlUtils.containsRTL)(ch) && !LTR_REGEX.test(ch)) {
|
|
139
|
+
trailingStart--;
|
|
140
|
+
} else {
|
|
141
|
+
break;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Split leading neutral characters (e.g. "/العربية" → "/" + "العربية")
|
|
146
|
+
let leadingEnd = 0;
|
|
147
|
+
while (leadingEnd < text.length) {
|
|
148
|
+
let ch = text[leadingEnd];
|
|
149
|
+
if (BOUNDARY_NEUTRAL.test(ch) && !(0, _rtlUtils.containsRTL)(ch) && !LTR_REGEX.test(ch)) {
|
|
150
|
+
leadingEnd++;
|
|
151
|
+
} else {
|
|
152
|
+
break;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Only split if there's a meaningful core left
|
|
157
|
+
if ((leadingEnd > 0 || trailingStart < text.length) && leadingEnd < trailingStart) {
|
|
158
|
+
let leadingText = text.slice(0, leadingEnd);
|
|
159
|
+
let coreText = text.slice(leadingEnd, trailingStart);
|
|
160
|
+
let trailingText = text.slice(trailingStart);
|
|
161
|
+
if (leadingText) {
|
|
162
|
+
let clone = Object.assign({}, inline);
|
|
163
|
+
clone.text = leadingText;
|
|
164
|
+
clone.width = inline.font ? inline.font.widthOfString(leadingText, inline.fontSize, inline.fontFeatures) + (inline.characterSpacing || 0) * (leadingText.length - 1) : 0;
|
|
165
|
+
clone._isSplit = true;
|
|
166
|
+
splitInlines.push(clone);
|
|
167
|
+
}
|
|
168
|
+
if (coreText) {
|
|
169
|
+
let clone = Object.assign({}, inline);
|
|
170
|
+
clone.text = coreText;
|
|
171
|
+
clone.width = inline.font ? inline.font.widthOfString(coreText, inline.fontSize, inline.fontFeatures) + (inline.characterSpacing || 0) * (coreText.length - 1) : 0;
|
|
172
|
+
clone._isSplit = true;
|
|
173
|
+
splitInlines.push(clone);
|
|
174
|
+
}
|
|
175
|
+
if (trailingText) {
|
|
176
|
+
let clone = Object.assign({}, inline);
|
|
177
|
+
clone.text = trailingText;
|
|
178
|
+
clone.width = inline.font ? inline.font.widthOfString(trailingText, inline.fontSize, inline.fontFeatures) + (inline.characterSpacing || 0) * (trailingText.length - 1) : 0;
|
|
179
|
+
clone._isSplit = true;
|
|
180
|
+
splitInlines.push(clone);
|
|
181
|
+
}
|
|
182
|
+
} else {
|
|
183
|
+
splitInlines.push(inline);
|
|
184
|
+
}
|
|
185
|
+
} else {
|
|
186
|
+
splitInlines.push(inline);
|
|
187
|
+
}
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
// --- Step 1: Classify each inline ---
|
|
191
|
+
const classified = splitInlines.map(inline => {
|
|
192
|
+
let hasStrongLTR = LTR_REGEX.test(inline.text);
|
|
193
|
+
let hasStrongRTL = (0, _rtlUtils.containsRTL)(inline.text);
|
|
194
|
+
let dir;
|
|
195
|
+
if (hasStrongRTL && hasStrongLTR) {
|
|
196
|
+
// Mixed — treat as RTL (predominant for RTL lines)
|
|
197
|
+
dir = 'rtl';
|
|
198
|
+
} else if (hasStrongRTL) {
|
|
199
|
+
dir = 'rtl';
|
|
200
|
+
} else if (hasStrongLTR) {
|
|
201
|
+
dir = 'ltr';
|
|
202
|
+
} else {
|
|
203
|
+
dir = 'neutral'; // punctuation, digits, spaces only
|
|
204
|
+
}
|
|
205
|
+
return {
|
|
206
|
+
inline,
|
|
207
|
+
dir
|
|
208
|
+
};
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
// --- Step 2: Build directional runs (groups of consecutive same-direction) ---
|
|
212
|
+
let runs = [];
|
|
213
|
+
let currentRun = null;
|
|
214
|
+
classified.forEach(item => {
|
|
215
|
+
if (!currentRun || currentRun.dir !== item.dir) {
|
|
216
|
+
currentRun = {
|
|
217
|
+
dir: item.dir,
|
|
218
|
+
inlines: []
|
|
219
|
+
};
|
|
220
|
+
runs.push(currentRun);
|
|
221
|
+
}
|
|
222
|
+
currentRun.inlines.push(item.inline);
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
// --- Step 3: Resolve neutral runs ---
|
|
226
|
+
// Step 3a: Bracket pair resolution (UBA rule N0).
|
|
227
|
+
// Find matching bracket pairs across runs. If the content between
|
|
228
|
+
// a "(" neutral run and a ")" neutral run is predominantly one direction,
|
|
229
|
+
// merge the opening bracket, content, and closing bracket into that direction.
|
|
230
|
+
const OPEN_BRACKETS = /[(\\[{<]/;
|
|
231
|
+
// const CLOSE_BRACKETS = /[)\]}>]/;
|
|
232
|
+
const BRACKET_MATCH = {
|
|
233
|
+
'(': ')',
|
|
234
|
+
'[': ']',
|
|
235
|
+
'{': '}',
|
|
236
|
+
'<': '>'
|
|
237
|
+
};
|
|
238
|
+
for (let i = 0; i < runs.length; i++) {
|
|
239
|
+
if (runs[i].dir !== 'neutral') continue;
|
|
240
|
+
|
|
241
|
+
// Check if this neutral run contains an opening bracket
|
|
242
|
+
let openBracket = null;
|
|
243
|
+
for (let k = 0; k < runs[i].inlines.length; k++) {
|
|
244
|
+
let txt = runs[i].inlines[k].text.trim();
|
|
245
|
+
if (OPEN_BRACKETS.test(txt)) {
|
|
246
|
+
openBracket = txt.match(OPEN_BRACKETS)[0];
|
|
247
|
+
break;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
if (!openBracket) continue;
|
|
251
|
+
let closeBracket = BRACKET_MATCH[openBracket];
|
|
252
|
+
|
|
253
|
+
// Search forward for the matching closing bracket
|
|
254
|
+
for (let j = i + 1; j < runs.length; j++) {
|
|
255
|
+
if (runs[j].dir === 'neutral') {
|
|
256
|
+
let hasClose = false;
|
|
257
|
+
for (let k = 0; k < runs[j].inlines.length; k++) {
|
|
258
|
+
if (runs[j].inlines[k].text.indexOf(closeBracket) >= 0) {
|
|
259
|
+
hasClose = true;
|
|
260
|
+
break;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
if (!hasClose) continue;
|
|
264
|
+
|
|
265
|
+
// Found matching close bracket at run j.
|
|
266
|
+
// Determine predominant direction of content between i and j
|
|
267
|
+
let innerLtr = 0,
|
|
268
|
+
innerRtl = 0;
|
|
269
|
+
for (let m = i + 1; m < j; m++) {
|
|
270
|
+
if (runs[m].dir === 'ltr') innerLtr += runs[m].inlines.length;else if (runs[m].dir === 'rtl') innerRtl += runs[m].inlines.length;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// Resolve bracket pair to inner content direction, or LTR if neutral-only
|
|
274
|
+
let pairDir = innerLtr >= innerRtl ? 'ltr' : 'rtl';
|
|
275
|
+
|
|
276
|
+
// Set the direction for the opening and closing bracket runs
|
|
277
|
+
runs[i].dir = pairDir;
|
|
278
|
+
runs[j].dir = pairDir;
|
|
279
|
+
break; // only match the first closing bracket
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// Step 3b: General neutral resolution.
|
|
285
|
+
// A neutral run takes the direction of its neighbors. If both neighbors
|
|
286
|
+
// agree, use that direction. If they disagree, use the base direction (RTL).
|
|
287
|
+
// If only one neighbor exists, use that neighbor's resolved direction.
|
|
288
|
+
for (let i = 0; i < runs.length; i++) {
|
|
289
|
+
if (runs[i].dir !== 'neutral') continue;
|
|
290
|
+
let prevDir = null;
|
|
291
|
+
for (let j = i - 1; j >= 0; j--) {
|
|
292
|
+
if (runs[j].dir !== 'neutral') {
|
|
293
|
+
prevDir = runs[j].dir;
|
|
294
|
+
break;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
let nextDir = null;
|
|
298
|
+
for (let j = i + 1; j < runs.length; j++) {
|
|
299
|
+
if (runs[j].dir !== 'neutral') {
|
|
300
|
+
nextDir = runs[j].dir;
|
|
301
|
+
break;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
if (prevDir && nextDir) {
|
|
305
|
+
runs[i].dir = prevDir === nextDir ? prevDir : 'rtl';
|
|
306
|
+
} else if (prevDir) {
|
|
307
|
+
runs[i].dir = prevDir;
|
|
308
|
+
} else if (nextDir) {
|
|
309
|
+
runs[i].dir = nextDir;
|
|
310
|
+
} else {
|
|
311
|
+
runs[i].dir = 'rtl'; // all neutral → base direction
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// --- Step 3c: Merge adjacent runs that now share the same direction ---
|
|
316
|
+
let merged = [runs[0]];
|
|
317
|
+
for (let i = 1; i < runs.length; i++) {
|
|
318
|
+
let last = merged[merged.length - 1];
|
|
319
|
+
if (last.dir === runs[i].dir) {
|
|
320
|
+
last.inlines = last.inlines.concat(runs[i].inlines);
|
|
321
|
+
} else {
|
|
322
|
+
merged.push(runs[i]);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
runs = merged;
|
|
326
|
+
|
|
327
|
+
// --- Step 4: Reverse run order (base direction is RTL) ---
|
|
328
|
+
runs.reverse();
|
|
329
|
+
|
|
330
|
+
// --- Step 5: Within each RTL run, reverse the inline order ---
|
|
331
|
+
runs.forEach(run => {
|
|
332
|
+
if (run.dir === 'rtl') {
|
|
333
|
+
run.inlines.reverse();
|
|
334
|
+
}
|
|
335
|
+
// LTR runs keep their original inline order
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
// --- Step 6: Flatten, apply bracket mirroring, recalculate x positions ---
|
|
339
|
+
// UBA Rule L4: after reordering, mirror bracket glyphs in RTL context
|
|
340
|
+
let reorderedInlines = [];
|
|
341
|
+
let currentX = 0;
|
|
342
|
+
const MIRROR_MAP = {
|
|
343
|
+
'(': ')',
|
|
344
|
+
')': '(',
|
|
345
|
+
'[': ']',
|
|
346
|
+
']': '[',
|
|
347
|
+
'{': '}',
|
|
348
|
+
'}': '{',
|
|
349
|
+
'<': '>',
|
|
350
|
+
'>': '<'
|
|
351
|
+
};
|
|
352
|
+
runs.forEach(run => {
|
|
353
|
+
run.inlines.forEach(inline => {
|
|
354
|
+
// Apply context-aware bracket mirroring for RTL inlines that contain Arabic text
|
|
355
|
+
if (run.dir === 'rtl' && (0, _rtlUtils.containsRTL)(inline.text)) {
|
|
356
|
+
inline.text = (0, _rtlUtils.fixArabicTextUsingReplace)(inline.text);
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// UBA Rule L4: Mirror standalone bracket characters in RTL runs.
|
|
360
|
+
// After Step 5 reversed the inline order, brackets like "(" and ")"
|
|
361
|
+
// are in swapped positions. Mirroring the glyph restores correct visuals.
|
|
362
|
+
// e.g. reversed ")" at position 0 → mirror to "(" → visually correct.
|
|
363
|
+
if (run.dir === 'rtl' && !(0, _rtlUtils.containsRTL)(inline.text) && !LTR_REGEX.test(inline.text)) {
|
|
364
|
+
let mirrored = '';
|
|
365
|
+
for (let c = 0; c < inline.text.length; c++) {
|
|
366
|
+
let ch = inline.text[c];
|
|
367
|
+
mirrored += MIRROR_MAP[ch] !== undefined ? MIRROR_MAP[ch] : ch;
|
|
368
|
+
}
|
|
369
|
+
inline.text = mirrored;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// Fix number+punctuation rendering in RTL context
|
|
373
|
+
if (run.dir === 'rtl' && NUMBER_PUNCTUATION_REGEX.test(inline.text)) {
|
|
374
|
+
inline.text = inline.text.replace(NUMBER_PUNCTUATION_REGEX, ' $3$2$1');
|
|
375
|
+
}
|
|
376
|
+
inline.x = currentX;
|
|
377
|
+
currentX += inline.width;
|
|
378
|
+
reorderedInlines.push(inline);
|
|
379
|
+
});
|
|
380
|
+
});
|
|
381
|
+
line.inlines = reorderedInlines;
|
|
382
|
+
}
|
|
383
|
+
addImage(image, index) {
|
|
384
|
+
let context = this.context();
|
|
385
|
+
let page = context.getCurrentPage();
|
|
386
|
+
let position = this.getCurrentPositionOnPage();
|
|
387
|
+
if (!page || image.absolutePosition === undefined && context.availableHeight < image._height && page.items.length > 0) {
|
|
388
|
+
return false;
|
|
389
|
+
}
|
|
390
|
+
if (image._x === undefined) {
|
|
391
|
+
image._x = image.x || 0;
|
|
392
|
+
}
|
|
393
|
+
image.x = context.x + image._x;
|
|
394
|
+
image.y = context.y;
|
|
395
|
+
this.alignImage(image);
|
|
396
|
+
addPageItem(page, {
|
|
397
|
+
type: 'image',
|
|
398
|
+
item: image
|
|
399
|
+
}, index);
|
|
400
|
+
context.moveDown(image._height);
|
|
401
|
+
return position;
|
|
402
|
+
}
|
|
403
|
+
addCanvas(node, index) {
|
|
404
|
+
let context = this.context();
|
|
405
|
+
let page = context.getCurrentPage();
|
|
406
|
+
let positions = [];
|
|
407
|
+
let height = node._minHeight;
|
|
408
|
+
if (!page || node.absolutePosition === undefined && context.availableHeight < height) {
|
|
409
|
+
// TODO: support for canvas larger than a page
|
|
410
|
+
// TODO: support for other overflow methods
|
|
411
|
+
|
|
412
|
+
return false;
|
|
413
|
+
}
|
|
414
|
+
this.alignCanvas(node);
|
|
415
|
+
node.canvas.forEach(function (vector) {
|
|
416
|
+
let position = this.addVector(vector, false, false, index);
|
|
417
|
+
positions.push(position);
|
|
418
|
+
if (index !== undefined) {
|
|
419
|
+
index++;
|
|
420
|
+
}
|
|
421
|
+
}, this);
|
|
422
|
+
context.moveDown(height);
|
|
423
|
+
return positions;
|
|
424
|
+
}
|
|
425
|
+
addSVG(image, index) {
|
|
426
|
+
// TODO: same as addImage
|
|
427
|
+
let context = this.context();
|
|
428
|
+
let page = context.getCurrentPage();
|
|
429
|
+
let position = this.getCurrentPositionOnPage();
|
|
430
|
+
if (!page || image.absolutePosition === undefined && context.availableHeight < image._height && page.items.length > 0) {
|
|
431
|
+
return false;
|
|
432
|
+
}
|
|
433
|
+
if (image._x === undefined) {
|
|
434
|
+
image._x = image.x || 0;
|
|
435
|
+
}
|
|
436
|
+
image.x = context.x + image._x;
|
|
437
|
+
image.y = context.y;
|
|
438
|
+
this.alignImage(image);
|
|
439
|
+
addPageItem(page, {
|
|
440
|
+
type: 'svg',
|
|
441
|
+
item: image
|
|
442
|
+
}, index);
|
|
443
|
+
context.moveDown(image._height);
|
|
444
|
+
return position;
|
|
445
|
+
}
|
|
446
|
+
addQr(qr, index) {
|
|
447
|
+
let context = this.context();
|
|
448
|
+
let page = context.getCurrentPage();
|
|
449
|
+
let position = this.getCurrentPositionOnPage();
|
|
450
|
+
if (!page || qr.absolutePosition === undefined && context.availableHeight < qr._height) {
|
|
451
|
+
return false;
|
|
452
|
+
}
|
|
453
|
+
if (qr._x === undefined) {
|
|
454
|
+
qr._x = qr.x || 0;
|
|
455
|
+
}
|
|
456
|
+
qr.x = context.x + qr._x;
|
|
457
|
+
qr.y = context.y;
|
|
458
|
+
this.alignImage(qr);
|
|
459
|
+
for (let i = 0, l = qr._canvas.length; i < l; i++) {
|
|
460
|
+
let vector = qr._canvas[i];
|
|
461
|
+
vector.x += qr.x;
|
|
462
|
+
vector.y += qr.y;
|
|
463
|
+
this.addVector(vector, true, true, index);
|
|
464
|
+
}
|
|
465
|
+
context.moveDown(qr._height);
|
|
466
|
+
return position;
|
|
467
|
+
}
|
|
468
|
+
addAttachment(attachment, index) {
|
|
469
|
+
let context = this.context();
|
|
470
|
+
let page = context.getCurrentPage();
|
|
471
|
+
let position = this.getCurrentPositionOnPage();
|
|
472
|
+
if (!page || attachment.absolutePosition === undefined && context.availableHeight < attachment._height && page.items.length > 0) {
|
|
473
|
+
return false;
|
|
474
|
+
}
|
|
475
|
+
if (attachment._x === undefined) {
|
|
476
|
+
attachment._x = attachment.x || 0;
|
|
477
|
+
}
|
|
478
|
+
attachment.x = context.x + attachment._x;
|
|
479
|
+
attachment.y = context.y;
|
|
480
|
+
addPageItem(page, {
|
|
481
|
+
type: 'attachment',
|
|
482
|
+
item: attachment
|
|
483
|
+
}, index);
|
|
484
|
+
context.moveDown(attachment._height);
|
|
485
|
+
return position;
|
|
486
|
+
}
|
|
487
|
+
alignImage(image) {
|
|
488
|
+
let width = this.context().availableWidth;
|
|
489
|
+
let imageWidth = image._minWidth;
|
|
490
|
+
let offset = 0;
|
|
491
|
+
switch (image._alignment) {
|
|
492
|
+
case 'right':
|
|
493
|
+
offset = width - imageWidth;
|
|
494
|
+
break;
|
|
495
|
+
case 'center':
|
|
496
|
+
offset = (width - imageWidth) / 2;
|
|
497
|
+
break;
|
|
498
|
+
}
|
|
499
|
+
if (offset) {
|
|
500
|
+
image.x = (image.x || 0) + offset;
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
alignCanvas(node) {
|
|
504
|
+
let width = this.context().availableWidth;
|
|
505
|
+
let canvasWidth = node._minWidth;
|
|
506
|
+
let offset = 0;
|
|
507
|
+
switch (node._alignment) {
|
|
508
|
+
case 'right':
|
|
509
|
+
offset = width - canvasWidth;
|
|
510
|
+
break;
|
|
511
|
+
case 'center':
|
|
512
|
+
offset = (width - canvasWidth) / 2;
|
|
513
|
+
break;
|
|
514
|
+
}
|
|
515
|
+
if (offset) {
|
|
516
|
+
node.canvas.forEach(vector => {
|
|
517
|
+
(0, _tools.offsetVector)(vector, offset, 0);
|
|
518
|
+
});
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
addVector(vector, ignoreContextX, ignoreContextY, index, forcePage) {
|
|
522
|
+
let context = this.context();
|
|
523
|
+
let page = context.getCurrentPage();
|
|
524
|
+
if ((0, _variableType.isNumber)(forcePage)) {
|
|
525
|
+
page = context.pages[forcePage];
|
|
526
|
+
}
|
|
527
|
+
let position = this.getCurrentPositionOnPage();
|
|
528
|
+
if (page) {
|
|
529
|
+
(0, _tools.offsetVector)(vector, ignoreContextX ? 0 : context.x, ignoreContextY ? 0 : context.y);
|
|
530
|
+
addPageItem(page, {
|
|
531
|
+
type: 'vector',
|
|
532
|
+
item: vector
|
|
533
|
+
}, index);
|
|
534
|
+
return position;
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
beginClip(width, height) {
|
|
538
|
+
let ctx = this.context();
|
|
539
|
+
let page = ctx.getCurrentPage();
|
|
540
|
+
page.items.push({
|
|
541
|
+
type: 'beginClip',
|
|
542
|
+
item: {
|
|
543
|
+
x: ctx.x,
|
|
544
|
+
y: ctx.y,
|
|
545
|
+
width: width,
|
|
546
|
+
height: height
|
|
547
|
+
}
|
|
548
|
+
});
|
|
549
|
+
return true;
|
|
550
|
+
}
|
|
551
|
+
endClip() {
|
|
552
|
+
let ctx = this.context();
|
|
553
|
+
let page = ctx.getCurrentPage();
|
|
554
|
+
page.items.push({
|
|
555
|
+
type: 'endClip'
|
|
556
|
+
});
|
|
557
|
+
return true;
|
|
558
|
+
}
|
|
559
|
+
beginVerticalAlignment(verticalAlignment) {
|
|
560
|
+
let page = this.context().getCurrentPage();
|
|
561
|
+
let item = {
|
|
562
|
+
type: 'beginVerticalAlignment',
|
|
563
|
+
item: {
|
|
564
|
+
verticalAlignment: verticalAlignment
|
|
565
|
+
}
|
|
566
|
+
};
|
|
567
|
+
page.items.push(item);
|
|
568
|
+
return item;
|
|
569
|
+
}
|
|
570
|
+
endVerticalAlignment(verticalAlignment) {
|
|
571
|
+
let page = this.context().getCurrentPage();
|
|
572
|
+
let item = {
|
|
573
|
+
type: 'endVerticalAlignment',
|
|
574
|
+
item: {
|
|
575
|
+
verticalAlignment: verticalAlignment
|
|
576
|
+
}
|
|
577
|
+
};
|
|
578
|
+
page.items.push(item);
|
|
579
|
+
return item;
|
|
580
|
+
}
|
|
581
|
+
addFragment(block, useBlockXOffset, useBlockYOffset, dontUpdateContextPosition) {
|
|
582
|
+
let ctx = this.context();
|
|
583
|
+
let page = ctx.getCurrentPage();
|
|
584
|
+
if (!useBlockXOffset && block.height > ctx.availableHeight) {
|
|
585
|
+
return false;
|
|
586
|
+
}
|
|
587
|
+
block.items.forEach(item => {
|
|
588
|
+
switch (item.type) {
|
|
589
|
+
case 'line':
|
|
590
|
+
var l = item.item.clone();
|
|
591
|
+
if (l._node) {
|
|
592
|
+
l._node.positions[0].pageNumber = ctx.page + 1;
|
|
593
|
+
}
|
|
594
|
+
l.x = (l.x || 0) + (useBlockXOffset ? block.xOffset || 0 : ctx.x);
|
|
595
|
+
l.y = (l.y || 0) + (useBlockYOffset ? block.yOffset || 0 : ctx.y);
|
|
596
|
+
page.items.push({
|
|
597
|
+
type: 'line',
|
|
598
|
+
item: l
|
|
599
|
+
});
|
|
600
|
+
break;
|
|
601
|
+
case 'vector':
|
|
602
|
+
var v = (0, _tools.pack)(item.item);
|
|
603
|
+
(0, _tools.offsetVector)(v, useBlockXOffset ? block.xOffset || 0 : ctx.x, useBlockYOffset ? block.yOffset || 0 : ctx.y);
|
|
604
|
+
if (v._isFillColorFromUnbreakable) {
|
|
605
|
+
// If the item is a fillColor from an unbreakable block
|
|
606
|
+
// We have to add it at the beginning of the items body array of the page
|
|
607
|
+
delete v._isFillColorFromUnbreakable;
|
|
608
|
+
const endOfBackgroundItemsIndex = ctx.backgroundLength[ctx.page];
|
|
609
|
+
page.items.splice(endOfBackgroundItemsIndex, 0, {
|
|
610
|
+
type: 'vector',
|
|
611
|
+
item: v
|
|
612
|
+
});
|
|
613
|
+
} else {
|
|
614
|
+
page.items.push({
|
|
615
|
+
type: 'vector',
|
|
616
|
+
item: v
|
|
617
|
+
});
|
|
618
|
+
}
|
|
619
|
+
break;
|
|
620
|
+
case 'image':
|
|
621
|
+
case 'svg':
|
|
622
|
+
case 'beginClip':
|
|
623
|
+
case 'endClip':
|
|
624
|
+
case 'beginVerticalAlignment':
|
|
625
|
+
case 'endVerticalAlignment':
|
|
626
|
+
var img = (0, _tools.pack)(item.item);
|
|
627
|
+
img.x = (img.x || 0) + (useBlockXOffset ? block.xOffset || 0 : ctx.x);
|
|
628
|
+
img.y = (img.y || 0) + (useBlockYOffset ? block.yOffset || 0 : ctx.y);
|
|
629
|
+
page.items.push({
|
|
630
|
+
type: item.type,
|
|
631
|
+
item: img
|
|
632
|
+
});
|
|
633
|
+
break;
|
|
634
|
+
}
|
|
635
|
+
});
|
|
636
|
+
if (!dontUpdateContextPosition) {
|
|
637
|
+
ctx.moveDown(block.height);
|
|
638
|
+
}
|
|
639
|
+
return true;
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
/**
|
|
643
|
+
* Pushes the provided context onto the stack or creates a new one
|
|
644
|
+
*
|
|
645
|
+
* pushContext(context) - pushes the provided context and makes it current
|
|
646
|
+
* pushContext(width, height) - creates and pushes a new context with the specified width and height
|
|
647
|
+
* pushContext() - creates a new context for unbreakable blocks (with current availableWidth and full-page-height)
|
|
648
|
+
*
|
|
649
|
+
* @param {DocumentContext|number} contextOrWidth
|
|
650
|
+
* @param {number} height
|
|
651
|
+
*/
|
|
652
|
+
pushContext(contextOrWidth, height) {
|
|
653
|
+
if (contextOrWidth === undefined) {
|
|
654
|
+
height = this.context().getCurrentPage().height - this.context().pageMargins.top - this.context().pageMargins.bottom;
|
|
655
|
+
contextOrWidth = this.context().availableWidth;
|
|
656
|
+
}
|
|
657
|
+
if ((0, _variableType.isNumber)(contextOrWidth)) {
|
|
658
|
+
let width = contextOrWidth;
|
|
659
|
+
contextOrWidth = new _DocumentContext.default();
|
|
660
|
+
contextOrWidth.addPage({
|
|
661
|
+
width: width,
|
|
662
|
+
height: height
|
|
663
|
+
}, {
|
|
664
|
+
left: 0,
|
|
665
|
+
right: 0,
|
|
666
|
+
top: 0,
|
|
667
|
+
bottom: 0
|
|
668
|
+
});
|
|
669
|
+
}
|
|
670
|
+
this.contextStack.push(this.context());
|
|
671
|
+
this._context = contextOrWidth;
|
|
672
|
+
}
|
|
673
|
+
popContext() {
|
|
674
|
+
this._context = this.contextStack.pop();
|
|
675
|
+
}
|
|
676
|
+
getCurrentPositionOnPage() {
|
|
677
|
+
return (this.contextStack[0] || this.context()).getCurrentPosition();
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
function addPageItem(page, item, index) {
|
|
681
|
+
if (index === null || index === undefined || index < 0 || index > page.items.length) {
|
|
682
|
+
page.items.push(item);
|
|
683
|
+
} else {
|
|
684
|
+
page.items.splice(index, 0, item);
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
var _default = exports.default = ElementWriter;
|