@flowaccount/pdfmake 1.0.5 → 1.0.6-staging.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/LICENSE +21 -21
- package/README.md +297 -297
- package/build/pdfmake.js +49968 -50213
- package/build/pdfmake.min.js +2 -2
- package/build/pdfmake.min.js.map +1 -1
- package/build/vfs_fonts.js +6 -6
- package/package.json +110 -110
- package/src/3rd-party/svg-to-pdfkit.js +3 -3
- package/src/browser-extensions/URLBrowserResolver.js +96 -96
- package/src/browser-extensions/pdfMake.js +361 -361
- package/src/browser-extensions/tokenizer-shim.js +15 -15
- package/src/browser-extensions/virtual-fs.js +55 -55
- package/src/columnCalculator.js +157 -157
- package/src/docMeasure.js +831 -831
- package/src/docPreprocessor.js +277 -277
- package/src/documentContext.js +383 -383
- package/src/elementWriter.js +442 -434
- package/src/fontProvider.js +68 -68
- package/src/helpers.js +138 -138
- package/src/imageMeasure.js +70 -70
- package/src/layoutBuilder.js +1998 -1770
- package/src/line.js +91 -91
- package/src/pageElementWriter.js +362 -362
- package/src/pdfKitEngine.js +21 -21
- package/src/printer.js +1191 -1191
- package/src/qrEnc.js +790 -790
- package/src/standardPageSizes.js +54 -54
- package/src/styleContextStack.js +138 -138
- package/src/svgMeasure.js +70 -70
- package/src/tableProcessor.js +791 -789
- package/src/textDecorator.js +157 -157
- package/src/textTools.js +442 -442
- package/src/traversalTracker.js +47 -47
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;
|