@flowaccount/pdfmake 1.0.4 → 1.0.5-staging.0

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/textTools.js CHANGED
@@ -1,442 +1,442 @@
1
- /* jslint node: true */
2
- 'use strict';
3
-
4
- var LineBreaker = require('@foliojs-fork/linebreak');
5
- const Tokenizer = require('@flowaccount/node-icu-tokenizer');
6
- const fontkit = require('fontkit');
7
-
8
- var LEADING = /^(\s)+/g;
9
- var TRAILING = /(\s)+$/g;
10
-
11
- var fontCacheName_new = '';
12
- var fontCache_new = {};
13
- var fontSubstituteCache = [];
14
- var defaultFont = '';
15
- /**
16
- * Creates an instance of TextTools - text measurement utility
17
- *
18
- * @constructor
19
- * @param {FontProvider} fontProvider
20
- */
21
- function TextTools(fontProvider) {
22
- this.fontProvider = fontProvider;
23
- }
24
-
25
- /**
26
- * Converts an array of strings (or inline-definition-objects) into a collection
27
- * of inlines and calculated minWidth/maxWidth.
28
- * and their min/max widths
29
- * @param {Object} textArray - an array of inline-definition-objects (or strings)
30
- * @param {Object} styleContextStack current style stack
31
- * @return {Object} collection of inlines, minWidth, maxWidth
32
- */
33
- TextTools.prototype.buildInlines = function (textArray, styleContextStack) {
34
-
35
-
36
- defaultFont = (styleContextStack && styleContextStack.getProperty('font')) || 'Roboto';
37
- // if(!fontCacheName_new)
38
- // {
39
- fontCacheName_new = defaultFont;
40
- // Only open font files if fonts exist and provider has fonts property
41
- if (this.fontProvider.fonts && this.fontProvider.fonts[fontCacheName_new] && this.fontProvider.fonts[fontCacheName_new].normal) {
42
- try {
43
- fontCache_new = fontkit.openSync(this.fontProvider.fonts[fontCacheName_new].normal);
44
- } catch (e) {
45
- // Font file doesn't exist (likely in tests), provide mock methods
46
- fontCache_new = {
47
- glyphsForString: function(text) {
48
- // Return mock glyphs - all valid (id > 0)
49
- return text.split('').map(function(char, i) {
50
- return { id: i + 1, char: char };
51
- });
52
- }
53
- };
54
- }
55
- }
56
- //}
57
- //var fcount = 0;
58
- if (this.fontProvider.fonts) {
59
- for(let fontItem in this.fontProvider.fonts) {
60
- if(fontCacheName_new != fontItem) {
61
- if(!fontSubstituteCache[fontItem])
62
- {
63
- try {
64
- var fontObj = fontkit.openSync(this.fontProvider.fonts[fontItem].normal);
65
- fontSubstituteCache[fontItem] = { "Name" : fontItem, "FontObj" : fontObj };
66
- } catch (e) {
67
- // Font file doesn't exist (likely in tests), provide mock font object
68
- fontSubstituteCache[fontItem] = {
69
- "Name" : fontItem,
70
- "FontObj" : {
71
- glyphsForString: function(text) {
72
- // Return mock glyphs - all valid (id > 0)
73
- return text.split('').map(function(char, i) {
74
- return { id: i + 1, char: char };
75
- });
76
- }
77
- }
78
- };
79
- }
80
- }
81
- }
82
- }
83
- }
84
- var measured = measure(this.fontProvider, textArray, styleContextStack);
85
- var minWidth = 0,
86
- maxWidth = 0,
87
- currentLineWidth;
88
-
89
- measured.forEach(function (inline) {
90
- minWidth = Math.max(minWidth, inline.width - inline.leadingCut - inline.trailingCut);
91
-
92
- if (!currentLineWidth) {
93
- currentLineWidth = {width: 0, leadingCut: inline.leadingCut, trailingCut: 0};
94
- }
95
-
96
- currentLineWidth.width += inline.width;
97
- currentLineWidth.trailingCut = inline.trailingCut;
98
-
99
- maxWidth = Math.max(maxWidth, getTrimmedWidth(currentLineWidth));
100
-
101
- if (inline.lineEnd) {
102
- currentLineWidth = null;
103
- }
104
- });
105
-
106
- if (getStyleProperty({}, styleContextStack, 'noWrap', false)) {
107
- minWidth = maxWidth;
108
- }
109
-
110
- return {
111
- items: measured,
112
- minWidth: minWidth,
113
- maxWidth: maxWidth
114
- };
115
-
116
- function getTrimmedWidth(item) {
117
- return Math.max(0, item.width - item.leadingCut - item.trailingCut);
118
- }
119
- };
120
-
121
- /**
122
- * Returns size of the specified string (without breaking it) using the current style
123
- * @param {String} text text to be measured
124
- * @param {Object} styleContextStack current style stack
125
- * @return {Object} size of the specified string
126
- */
127
- TextTools.prototype.sizeOfString = function (text, styleContextStack) {
128
- text = text ? text.toString().replace('\t', ' ') : '';
129
-
130
- //TODO: refactor - extract from measure
131
- var fontName = getStyleProperty({}, styleContextStack, 'font', 'Roboto');
132
- var fontSize = getStyleProperty({}, styleContextStack, 'fontSize', 12);
133
- var bold = getStyleProperty({}, styleContextStack, 'bold', false);
134
- var italics = getStyleProperty({}, styleContextStack, 'italics', false);
135
- var lineHeight = getStyleProperty({}, styleContextStack, 'lineHeight', 1);
136
- var characterSpacing = getStyleProperty({}, styleContextStack, 'characterSpacing', 0);
137
-
138
- var font = this.fontProvider.provideFont(fontName, bold, italics);
139
-
140
- return {
141
- width: widthOfString(text, font, fontSize, characterSpacing),
142
- height: font.lineHeight(fontSize) * lineHeight,
143
- fontSize: fontSize,
144
- lineHeight: lineHeight,
145
- ascender: font.ascender / 1000 * fontSize,
146
- descender: font.descender / 1000 * fontSize
147
- };
148
- };
149
-
150
- TextTools.prototype.widthOfString = function (text, font, fontSize, characterSpacing) {
151
- return widthOfString(text, font, fontSize, characterSpacing);
152
- };
153
-
154
- function tokenizerWords(word) {
155
-
156
- var regex = / /;
157
- word = word.replace(regex, "[BLANK]");
158
-
159
- var tokenizer = new Tokenizer().tokenize(word, { ignoreWhitespaceTokens:false });
160
- for(var tItem in tokenizer){
161
- if(tItem < tokenizer.length){
162
- var numIndex = parseInt(tItem);
163
- if(tokenizer[numIndex].token == '[' && tokenizer[numIndex + 1].token == 'BLANK' && tokenizer[numIndex + 2].token == ']') {
164
- tokenizer[numIndex].token = ' ';
165
- tokenizer[numIndex + 1].del = true;
166
- tokenizer[numIndex + 2].del = true;
167
- }
168
-
169
- } else {
170
- break;
171
- }
172
- }
173
- return tokenizer;
174
- }
175
-
176
- function splitWords(text, noWrap) {
177
- var results = [];
178
-
179
- text = text.replace(/\t/g, ' ');
180
- text = text.replace(/\r/g, '');
181
-
182
- // Remove Hidden Unicode
183
- text = text.replace(/[\u200D\uFEFF\u200E\u200F\u202A-\u202E\u2028\u2029\u180E\u2060-\u2064\u206A-\u206F]/g, '');
184
-
185
- if (noWrap) {
186
- results.push({text: text});
187
- return results;
188
- }
189
-
190
- var breaker = new LineBreaker(text);
191
- var last = 0;
192
- var bk;
193
-
194
- while (bk = breaker.nextBreak()) {
195
- var word = text.slice(last, bk.position);
196
-
197
- if(exceptTokenizer(word.trim()))
198
- {
199
- results.push({text: word, lineEnd: (bk.required || word.match(/\r?\n$|\r$/))});
200
- }
201
- else if (bk.required || word.match(/\r?\n$|\r$/)) {
202
- //word = word.replace(/\r?\n$|\r$/, '');
203
-
204
- var tokenWord = tokenizerWords(word);
205
- for (var tIndex in tokenWord) {
206
- if (!tokenWord[tIndex].del) {
207
- if (tIndex < tokenWord.length - 1 && tokenWord[tIndex].token != '\n') {
208
- results.push({ text: tokenWord[tIndex].token });
209
- } else {
210
- results.push({ text: tokenWord[tIndex].token, lineEnd: true });
211
- }
212
- }
213
- }
214
- } else {
215
- var tokenWord = tokenizerWords(word);
216
- for (var tIndex in tokenWord) {
217
- if (!tokenWord[tIndex].del) {
218
- results.push({ text: tokenWord[tIndex].token });
219
- }
220
- }
221
- }
222
-
223
- last = bk.position;
224
- }
225
- return results;
226
- }
227
-
228
- function copyStyle(source, destination) {
229
- destination = destination || {};
230
- source = source || {}; //TODO: default style
231
-
232
- for (var key in source) {
233
- if (key != 'text' && source.hasOwnProperty(key)) {
234
- destination[key] = source[key];
235
- }
236
- }
237
-
238
- return destination;
239
- }
240
-
241
- function normalizeTextArray(array, styleContextStack) {
242
- var results = [];
243
-
244
- if (!Array.isArray(array)) {
245
- array = [array];
246
- }
247
-
248
- for (var i = 0, l = array.length; i < l; i++) {
249
- var item = array[i];
250
- var style = null;
251
- var words;
252
-
253
- var noWrap = getStyleProperty(item || {}, styleContextStack, 'noWrap', false);
254
-
255
- if (item !== null && (typeof item === 'object' || item instanceof Object)) {
256
- words = splitWords(normalizeString(item.text), noWrap);
257
- style = copyStyle(item);
258
- } else {
259
- words = splitWords(normalizeString(item), noWrap);
260
- }
261
-
262
- for (var i2 = 0, l2 = words.length; i2 < l2; i2++) {
263
- var result = {
264
- text: words[i2].text
265
- };
266
-
267
- if (words[i2].lineEnd) {
268
- result.lineEnd = true;
269
- }
270
-
271
- copyStyle(style, result);
272
- results.push(result);
273
- }
274
- }
275
- return results;
276
- }
277
-
278
- function normalizeString(value) {
279
- if (value === undefined || value === null) {
280
- return '';
281
- } else if (typeof value === 'number') {
282
- return value.toString();
283
- } else if (typeof value === 'string' || value instanceof String) {
284
- return value;
285
- } else if (Array.isArray(value)) {
286
- // Handle arrays - extract text from first element if it exists
287
- if (value.length > 0) {
288
- return normalizeString(value[0]);
289
- }
290
- return '';
291
- } else if (typeof value === 'object' && value.text !== undefined) {
292
- // Recursively extract nested text property (handles { text: { text: 'hello' } })
293
- return normalizeString(value.text);
294
- } else {
295
- return value.toString();
296
- }
297
- }
298
-
299
- function getStyleProperty(item, styleContextStack, property, defaultValue) {
300
- var value;
301
-
302
- if (item[property] !== undefined && item[property] !== null) {
303
- // item defines this property
304
- return item[property];
305
- }
306
-
307
- if (!styleContextStack) {
308
- return defaultValue;
309
- }
310
-
311
- if (styleContextStack.auto && typeof styleContextStack.auto === 'function') {
312
- styleContextStack.auto(item, function () {
313
- value = styleContextStack.getProperty(property);
314
- });
315
- } else if (styleContextStack.getProperty && typeof styleContextStack.getProperty === 'function') {
316
- value = styleContextStack.getProperty(property);
317
- }
318
-
319
- if (value !== null && value !== undefined) {
320
- return value;
321
- } else {
322
- return defaultValue;
323
- }
324
- }
325
-
326
- function getFontCompaitible(item, fontProvider, styleContextStack) {
327
- // Guard against missing or non-string text
328
- if (!item.text || typeof item.text !== 'string') {
329
- return defaultFont;
330
- }
331
-
332
- if(fontCacheName_new && fontCache_new && typeof fontCache_new.glyphsForString === 'function') {
333
- var glyphList = fontCache_new.glyphsForString(item.text);
334
- if(glyphList.filter(function(x) {return x.id <= 0;}).length == 0) {
335
- return fontCacheName_new;
336
- }
337
- }
338
- for(let count in fontSubstituteCache) {
339
- var fontItem = fontSubstituteCache[count];
340
- if(fontCacheName_new != fontItem.Name) {
341
- var glyphList = fontItem.FontObj.glyphsForString(item.text);
342
- if(glyphList.filter(function(x) {return x.id <= 0;}).length == 0) {
343
- return fontItem.Name;
344
- }
345
- }
346
- }
347
- return defaultFont;
348
- }
349
-
350
- function measure(fontProvider, textArray, styleContextStack) {
351
- var normalized = normalizeTextArray(textArray, styleContextStack);
352
-
353
- // Filter out items without valid text (empty objects, etc.)
354
- normalized = normalized.filter(function(item) {
355
- return item.text && typeof item.text === 'string';
356
- });
357
-
358
- if (normalized.length) {
359
- var leadingIndent = getStyleProperty(normalized[0], styleContextStack, 'leadingIndent', 0);
360
-
361
- if (leadingIndent) {
362
- normalized[0].leadingCut = -leadingIndent;
363
- normalized[0].leadingIndent = leadingIndent;
364
- }
365
- }
366
- normalized.forEach(function (item) {
367
-
368
- var fontName = getFontCompaitible(item, fontProvider, styleContextStack);//getStyleProperty(item, styleContextStack, 'font', 'Roboto');
369
- var fontSize = getStyleProperty(item, styleContextStack, 'fontSize', 12);
370
- var bold = getStyleProperty(item, styleContextStack, 'bold', false);
371
- var italics = getStyleProperty(item, styleContextStack, 'italics', false);
372
- var color = getStyleProperty(item, styleContextStack, 'color', 'black');
373
- var decoration = getStyleProperty(item, styleContextStack, 'decoration', null);
374
- var decorationColor = getStyleProperty(item, styleContextStack, 'decorationColor', null);
375
- var decorationStyle = getStyleProperty(item, styleContextStack, 'decorationStyle', null);
376
- var background = getStyleProperty(item, styleContextStack, 'background', null);
377
- var lineHeight = getStyleProperty(item, styleContextStack, 'lineHeight', 1);
378
- var characterSpacing = getStyleProperty(item, styleContextStack, 'characterSpacing', 0);
379
- var link = getStyleProperty(item, styleContextStack, 'link', null);
380
- var linkToPage = getStyleProperty(item, styleContextStack, 'linkToPage', null);
381
- var noWrap = getStyleProperty(item, styleContextStack, 'noWrap', null);
382
- var preserveLeadingSpaces = getStyleProperty(item, styleContextStack, 'preserveLeadingSpaces', false);
383
-
384
- var font = fontProvider.provideFont(fontName, bold, italics);
385
-
386
- item.width = widthOfString(item.text, font, fontSize, characterSpacing);
387
- item.height = font.lineHeight(fontSize) * lineHeight;
388
-
389
- var leadingSpaces = item.text.match(LEADING);
390
-
391
- if (!item.leadingCut) {
392
- item.leadingCut = 0;
393
- }
394
-
395
- if (leadingSpaces && !preserveLeadingSpaces) {
396
- item.leadingCut += widthOfString(leadingSpaces[0], font, fontSize, characterSpacing);
397
- }
398
-
399
- var trailingSpaces = item.text.match(TRAILING);
400
- if (trailingSpaces) {
401
- item.trailingCut = widthOfString(trailingSpaces[0], font, fontSize, characterSpacing);
402
- } else {
403
- item.trailingCut = 0;
404
- }
405
-
406
- item.alignment = getStyleProperty(item, styleContextStack, 'alignment', 'left');
407
- item.font = font;
408
- item.fontSize = fontSize;
409
- item.characterSpacing = characterSpacing;
410
- item.color = color;
411
- item.decoration = decoration;
412
- item.decorationColor = decorationColor;
413
- item.decorationStyle = decorationStyle;
414
- item.background = background;
415
- item.link = link;
416
- item.linkToPage = linkToPage;
417
- item.noWrap = noWrap;
418
- });
419
-
420
- return normalized;
421
- }
422
-
423
- function widthOfString(text, font, fontSize, characterSpacing) {
424
- return font.widthOfString(text, fontSize) + ((characterSpacing || 0) * (text.length - 1));
425
- }
426
-
427
- function exceptTokenizer(word) {
428
- var listExceptWord = [
429
- '(ไทยแลนด์)',
430
- '(ประเทศไทย)',
431
- '(สำนักงานใหญ่)',
432
- '(มหาชน)',
433
- '(Thailand)',
434
- '(Main Branch)',
435
- '(Head office)',
436
- '(กรุ๊ป)'
437
- ];
438
-
439
- return listExceptWord.indexOf(word) > -1;
440
- }
441
-
442
- module.exports = TextTools;
1
+ /* jslint node: true */
2
+ 'use strict';
3
+
4
+ var LineBreaker = require('@foliojs-fork/linebreak');
5
+ const Tokenizer = require('@flowaccount/node-icu-tokenizer');
6
+ const fontkit = require('fontkit');
7
+
8
+ var LEADING = /^(\s)+/g;
9
+ var TRAILING = /(\s)+$/g;
10
+
11
+ var fontCacheName_new = '';
12
+ var fontCache_new = {};
13
+ var fontSubstituteCache = [];
14
+ var defaultFont = '';
15
+ /**
16
+ * Creates an instance of TextTools - text measurement utility
17
+ *
18
+ * @constructor
19
+ * @param {FontProvider} fontProvider
20
+ */
21
+ function TextTools(fontProvider) {
22
+ this.fontProvider = fontProvider;
23
+ }
24
+
25
+ /**
26
+ * Converts an array of strings (or inline-definition-objects) into a collection
27
+ * of inlines and calculated minWidth/maxWidth.
28
+ * and their min/max widths
29
+ * @param {Object} textArray - an array of inline-definition-objects (or strings)
30
+ * @param {Object} styleContextStack current style stack
31
+ * @return {Object} collection of inlines, minWidth, maxWidth
32
+ */
33
+ TextTools.prototype.buildInlines = function (textArray, styleContextStack) {
34
+
35
+
36
+ defaultFont = (styleContextStack && styleContextStack.getProperty('font')) || 'Roboto';
37
+ // if(!fontCacheName_new)
38
+ // {
39
+ fontCacheName_new = defaultFont;
40
+ // Only open font files if fonts exist and provider has fonts property
41
+ if (this.fontProvider.fonts && this.fontProvider.fonts[fontCacheName_new] && this.fontProvider.fonts[fontCacheName_new].normal) {
42
+ try {
43
+ fontCache_new = fontkit.openSync(this.fontProvider.fonts[fontCacheName_new].normal);
44
+ } catch (e) {
45
+ // Font file doesn't exist (likely in tests), provide mock methods
46
+ fontCache_new = {
47
+ glyphsForString: function(text) {
48
+ // Return mock glyphs - all valid (id > 0)
49
+ return text.split('').map(function(char, i) {
50
+ return { id: i + 1, char: char };
51
+ });
52
+ }
53
+ };
54
+ }
55
+ }
56
+ //}
57
+ //var fcount = 0;
58
+ if (this.fontProvider.fonts) {
59
+ for(let fontItem in this.fontProvider.fonts) {
60
+ if(fontCacheName_new != fontItem) {
61
+ if(!fontSubstituteCache[fontItem])
62
+ {
63
+ try {
64
+ var fontObj = fontkit.openSync(this.fontProvider.fonts[fontItem].normal);
65
+ fontSubstituteCache[fontItem] = { "Name" : fontItem, "FontObj" : fontObj };
66
+ } catch (e) {
67
+ // Font file doesn't exist (likely in tests), provide mock font object
68
+ fontSubstituteCache[fontItem] = {
69
+ "Name" : fontItem,
70
+ "FontObj" : {
71
+ glyphsForString: function(text) {
72
+ // Return mock glyphs - all valid (id > 0)
73
+ return text.split('').map(function(char, i) {
74
+ return { id: i + 1, char: char };
75
+ });
76
+ }
77
+ }
78
+ };
79
+ }
80
+ }
81
+ }
82
+ }
83
+ }
84
+ var measured = measure(this.fontProvider, textArray, styleContextStack);
85
+ var minWidth = 0,
86
+ maxWidth = 0,
87
+ currentLineWidth;
88
+
89
+ measured.forEach(function (inline) {
90
+ minWidth = Math.max(minWidth, inline.width - inline.leadingCut - inline.trailingCut);
91
+
92
+ if (!currentLineWidth) {
93
+ currentLineWidth = {width: 0, leadingCut: inline.leadingCut, trailingCut: 0};
94
+ }
95
+
96
+ currentLineWidth.width += inline.width;
97
+ currentLineWidth.trailingCut = inline.trailingCut;
98
+
99
+ maxWidth = Math.max(maxWidth, getTrimmedWidth(currentLineWidth));
100
+
101
+ if (inline.lineEnd) {
102
+ currentLineWidth = null;
103
+ }
104
+ });
105
+
106
+ if (getStyleProperty({}, styleContextStack, 'noWrap', false)) {
107
+ minWidth = maxWidth;
108
+ }
109
+
110
+ return {
111
+ items: measured,
112
+ minWidth: minWidth,
113
+ maxWidth: maxWidth
114
+ };
115
+
116
+ function getTrimmedWidth(item) {
117
+ return Math.max(0, item.width - item.leadingCut - item.trailingCut);
118
+ }
119
+ };
120
+
121
+ /**
122
+ * Returns size of the specified string (without breaking it) using the current style
123
+ * @param {String} text text to be measured
124
+ * @param {Object} styleContextStack current style stack
125
+ * @return {Object} size of the specified string
126
+ */
127
+ TextTools.prototype.sizeOfString = function (text, styleContextStack) {
128
+ text = text ? text.toString().replace('\t', ' ') : '';
129
+
130
+ //TODO: refactor - extract from measure
131
+ var fontName = getStyleProperty({}, styleContextStack, 'font', 'Roboto');
132
+ var fontSize = getStyleProperty({}, styleContextStack, 'fontSize', 12);
133
+ var bold = getStyleProperty({}, styleContextStack, 'bold', false);
134
+ var italics = getStyleProperty({}, styleContextStack, 'italics', false);
135
+ var lineHeight = getStyleProperty({}, styleContextStack, 'lineHeight', 1);
136
+ var characterSpacing = getStyleProperty({}, styleContextStack, 'characterSpacing', 0);
137
+
138
+ var font = this.fontProvider.provideFont(fontName, bold, italics);
139
+
140
+ return {
141
+ width: widthOfString(text, font, fontSize, characterSpacing),
142
+ height: font.lineHeight(fontSize) * lineHeight,
143
+ fontSize: fontSize,
144
+ lineHeight: lineHeight,
145
+ ascender: font.ascender / 1000 * fontSize,
146
+ descender: font.descender / 1000 * fontSize
147
+ };
148
+ };
149
+
150
+ TextTools.prototype.widthOfString = function (text, font, fontSize, characterSpacing) {
151
+ return widthOfString(text, font, fontSize, characterSpacing);
152
+ };
153
+
154
+ function tokenizerWords(word) {
155
+
156
+ var regex = / /;
157
+ word = word.replace(regex, "[BLANK]");
158
+
159
+ var tokenizer = new Tokenizer().tokenize(word, { ignoreWhitespaceTokens:false });
160
+ for(var tItem in tokenizer){
161
+ if(tItem < tokenizer.length){
162
+ var numIndex = parseInt(tItem);
163
+ if(tokenizer[numIndex].token == '[' && tokenizer[numIndex + 1].token == 'BLANK' && tokenizer[numIndex + 2].token == ']') {
164
+ tokenizer[numIndex].token = ' ';
165
+ tokenizer[numIndex + 1].del = true;
166
+ tokenizer[numIndex + 2].del = true;
167
+ }
168
+
169
+ } else {
170
+ break;
171
+ }
172
+ }
173
+ return tokenizer;
174
+ }
175
+
176
+ function splitWords(text, noWrap) {
177
+ var results = [];
178
+
179
+ text = text.replace(/\t/g, ' ');
180
+ text = text.replace(/\r/g, '');
181
+
182
+ // Remove Hidden Unicode
183
+ text = text.replace(/[\u200D\uFEFF\u200E\u200F\u202A-\u202E\u2028\u2029\u180E\u2060-\u2064\u206A-\u206F]/g, '');
184
+
185
+ if (noWrap) {
186
+ results.push({text: text});
187
+ return results;
188
+ }
189
+
190
+ var breaker = new LineBreaker(text);
191
+ var last = 0;
192
+ var bk;
193
+
194
+ while (bk = breaker.nextBreak()) {
195
+ var word = text.slice(last, bk.position);
196
+
197
+ if(exceptTokenizer(word.trim()))
198
+ {
199
+ results.push({text: word, lineEnd: (bk.required || word.match(/\r?\n$|\r$/))});
200
+ }
201
+ else if (bk.required || word.match(/\r?\n$|\r$/)) {
202
+ //word = word.replace(/\r?\n$|\r$/, '');
203
+
204
+ var tokenWord = tokenizerWords(word);
205
+ for (var tIndex in tokenWord) {
206
+ if (!tokenWord[tIndex].del) {
207
+ if (tIndex < tokenWord.length - 1 && tokenWord[tIndex].token != '\n') {
208
+ results.push({ text: tokenWord[tIndex].token });
209
+ } else {
210
+ results.push({ text: tokenWord[tIndex].token, lineEnd: true });
211
+ }
212
+ }
213
+ }
214
+ } else {
215
+ var tokenWord = tokenizerWords(word);
216
+ for (var tIndex in tokenWord) {
217
+ if (!tokenWord[tIndex].del) {
218
+ results.push({ text: tokenWord[tIndex].token });
219
+ }
220
+ }
221
+ }
222
+
223
+ last = bk.position;
224
+ }
225
+ return results;
226
+ }
227
+
228
+ function copyStyle(source, destination) {
229
+ destination = destination || {};
230
+ source = source || {}; //TODO: default style
231
+
232
+ for (var key in source) {
233
+ if (key != 'text' && source.hasOwnProperty(key)) {
234
+ destination[key] = source[key];
235
+ }
236
+ }
237
+
238
+ return destination;
239
+ }
240
+
241
+ function normalizeTextArray(array, styleContextStack) {
242
+ var results = [];
243
+
244
+ if (!Array.isArray(array)) {
245
+ array = [array];
246
+ }
247
+
248
+ for (var i = 0, l = array.length; i < l; i++) {
249
+ var item = array[i];
250
+ var style = null;
251
+ var words;
252
+
253
+ var noWrap = getStyleProperty(item || {}, styleContextStack, 'noWrap', false);
254
+
255
+ if (item !== null && (typeof item === 'object' || item instanceof Object)) {
256
+ words = splitWords(normalizeString(item.text), noWrap);
257
+ style = copyStyle(item);
258
+ } else {
259
+ words = splitWords(normalizeString(item), noWrap);
260
+ }
261
+
262
+ for (var i2 = 0, l2 = words.length; i2 < l2; i2++) {
263
+ var result = {
264
+ text: words[i2].text
265
+ };
266
+
267
+ if (words[i2].lineEnd) {
268
+ result.lineEnd = true;
269
+ }
270
+
271
+ copyStyle(style, result);
272
+ results.push(result);
273
+ }
274
+ }
275
+ return results;
276
+ }
277
+
278
+ function normalizeString(value) {
279
+ if (value === undefined || value === null) {
280
+ return '';
281
+ } else if (typeof value === 'number') {
282
+ return value.toString();
283
+ } else if (typeof value === 'string' || value instanceof String) {
284
+ return value;
285
+ } else if (Array.isArray(value)) {
286
+ // Handle arrays - extract text from first element if it exists
287
+ if (value.length > 0) {
288
+ return normalizeString(value[0]);
289
+ }
290
+ return '';
291
+ } else if (typeof value === 'object' && value.text !== undefined) {
292
+ // Recursively extract nested text property (handles { text: { text: 'hello' } })
293
+ return normalizeString(value.text);
294
+ } else {
295
+ return value.toString();
296
+ }
297
+ }
298
+
299
+ function getStyleProperty(item, styleContextStack, property, defaultValue) {
300
+ var value;
301
+
302
+ if (item[property] !== undefined && item[property] !== null) {
303
+ // item defines this property
304
+ return item[property];
305
+ }
306
+
307
+ if (!styleContextStack) {
308
+ return defaultValue;
309
+ }
310
+
311
+ if (styleContextStack.auto && typeof styleContextStack.auto === 'function') {
312
+ styleContextStack.auto(item, function () {
313
+ value = styleContextStack.getProperty(property);
314
+ });
315
+ } else if (styleContextStack.getProperty && typeof styleContextStack.getProperty === 'function') {
316
+ value = styleContextStack.getProperty(property);
317
+ }
318
+
319
+ if (value !== null && value !== undefined) {
320
+ return value;
321
+ } else {
322
+ return defaultValue;
323
+ }
324
+ }
325
+
326
+ function getFontCompaitible(item, fontProvider, styleContextStack) {
327
+ // Guard against missing or non-string text
328
+ if (!item.text || typeof item.text !== 'string') {
329
+ return defaultFont;
330
+ }
331
+
332
+ if(fontCacheName_new && fontCache_new && typeof fontCache_new.glyphsForString === 'function') {
333
+ var glyphList = fontCache_new.glyphsForString(item.text);
334
+ if(glyphList.filter(function(x) {return x.id <= 0;}).length == 0) {
335
+ return fontCacheName_new;
336
+ }
337
+ }
338
+ for(let count in fontSubstituteCache) {
339
+ var fontItem = fontSubstituteCache[count];
340
+ if(fontCacheName_new != fontItem.Name) {
341
+ var glyphList = fontItem.FontObj.glyphsForString(item.text);
342
+ if(glyphList.filter(function(x) {return x.id <= 0;}).length == 0) {
343
+ return fontItem.Name;
344
+ }
345
+ }
346
+ }
347
+ return defaultFont;
348
+ }
349
+
350
+ function measure(fontProvider, textArray, styleContextStack) {
351
+ var normalized = normalizeTextArray(textArray, styleContextStack);
352
+
353
+ // Filter out items without valid text (empty objects, etc.)
354
+ normalized = normalized.filter(function(item) {
355
+ return item.text && typeof item.text === 'string';
356
+ });
357
+
358
+ if (normalized.length) {
359
+ var leadingIndent = getStyleProperty(normalized[0], styleContextStack, 'leadingIndent', 0);
360
+
361
+ if (leadingIndent) {
362
+ normalized[0].leadingCut = -leadingIndent;
363
+ normalized[0].leadingIndent = leadingIndent;
364
+ }
365
+ }
366
+ normalized.forEach(function (item) {
367
+
368
+ var fontName = getFontCompaitible(item, fontProvider, styleContextStack);//getStyleProperty(item, styleContextStack, 'font', 'Roboto');
369
+ var fontSize = getStyleProperty(item, styleContextStack, 'fontSize', 12);
370
+ var bold = getStyleProperty(item, styleContextStack, 'bold', false);
371
+ var italics = getStyleProperty(item, styleContextStack, 'italics', false);
372
+ var color = getStyleProperty(item, styleContextStack, 'color', 'black');
373
+ var decoration = getStyleProperty(item, styleContextStack, 'decoration', null);
374
+ var decorationColor = getStyleProperty(item, styleContextStack, 'decorationColor', null);
375
+ var decorationStyle = getStyleProperty(item, styleContextStack, 'decorationStyle', null);
376
+ var background = getStyleProperty(item, styleContextStack, 'background', null);
377
+ var lineHeight = getStyleProperty(item, styleContextStack, 'lineHeight', 1);
378
+ var characterSpacing = getStyleProperty(item, styleContextStack, 'characterSpacing', 0);
379
+ var link = getStyleProperty(item, styleContextStack, 'link', null);
380
+ var linkToPage = getStyleProperty(item, styleContextStack, 'linkToPage', null);
381
+ var noWrap = getStyleProperty(item, styleContextStack, 'noWrap', null);
382
+ var preserveLeadingSpaces = getStyleProperty(item, styleContextStack, 'preserveLeadingSpaces', false);
383
+
384
+ var font = fontProvider.provideFont(fontName, bold, italics);
385
+
386
+ item.width = widthOfString(item.text, font, fontSize, characterSpacing);
387
+ item.height = font.lineHeight(fontSize) * lineHeight;
388
+
389
+ var leadingSpaces = item.text.match(LEADING);
390
+
391
+ if (!item.leadingCut) {
392
+ item.leadingCut = 0;
393
+ }
394
+
395
+ if (leadingSpaces && !preserveLeadingSpaces) {
396
+ item.leadingCut += widthOfString(leadingSpaces[0], font, fontSize, characterSpacing);
397
+ }
398
+
399
+ var trailingSpaces = item.text.match(TRAILING);
400
+ if (trailingSpaces) {
401
+ item.trailingCut = widthOfString(trailingSpaces[0], font, fontSize, characterSpacing);
402
+ } else {
403
+ item.trailingCut = 0;
404
+ }
405
+
406
+ item.alignment = getStyleProperty(item, styleContextStack, 'alignment', 'left');
407
+ item.font = font;
408
+ item.fontSize = fontSize;
409
+ item.characterSpacing = characterSpacing;
410
+ item.color = color;
411
+ item.decoration = decoration;
412
+ item.decorationColor = decorationColor;
413
+ item.decorationStyle = decorationStyle;
414
+ item.background = background;
415
+ item.link = link;
416
+ item.linkToPage = linkToPage;
417
+ item.noWrap = noWrap;
418
+ });
419
+
420
+ return normalized;
421
+ }
422
+
423
+ function widthOfString(text, font, fontSize, characterSpacing) {
424
+ return font.widthOfString(text, fontSize) + ((characterSpacing || 0) * (text.length - 1));
425
+ }
426
+
427
+ function exceptTokenizer(word) {
428
+ var listExceptWord = [
429
+ '(ไทยแลนด์)',
430
+ '(ประเทศไทย)',
431
+ '(สำนักงานใหญ่)',
432
+ '(มหาชน)',
433
+ '(Thailand)',
434
+ '(Main Branch)',
435
+ '(Head office)',
436
+ '(กรุ๊ป)'
437
+ ];
438
+
439
+ return listExceptWord.indexOf(word) > -1;
440
+ }
441
+
442
+ module.exports = TextTools;