vis.js is a dynamic, browser-based visualization library
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

549 lines
15 KiB

Network: Fix handling of multi-fonts (#3486) * The next fix on Travis unit test failure This is the next escalation on the war against the Travis unit tests failing (which came into being by yours truly). By accident, I could recreate the unit test failure on my development machine. This led to a more directed effort to squash the bug. The insight here is that test `(window === undefined)` fails, but `(typeof window === 'undefined`)` succeeds. This undoubtedly has to do with the special status `window` has as a global object. Changes: - Added check on presence of `window` in `Canvas._requestNextFrame()`, fixed local source errors. - Added catch clause in `CanvasRendered._determinePixelRatio()` - small fix: raised timeout for the network `worldCup2014` unit test * Preliminary refactoring in utils.js * Added unit tests for extend routines, commenting and small fixes * More unit tests for extend routines * - Completed unit tests for extend routines in - Small fixes and cleanup in `util.js` - Removed `util.protoExtend()`, not used anywhere * Added unit tests for known font options * Interim save before trying out another proto chain strategy * Fixed problem in first example #3408 * Removed silly file that shouldn't be there * Added unit test for multi-fonts * Comment edits * Verufy unit tests, small adjustments for groups * Further work on getting unit tests to work. PARTS NEED TO BE CLEANED UP! * Further tweaks to get unit tests working * All unit tests passing * Fixes due to linting * Small edits * Removed prototype handling from font pile * Fixes during testing examples of #3408 * Added unit test for edge labels, small fixes * Added unit tests for shorthand string fonts; some tests still failing * All unit tests pass * Removed Label.parseOptions() * Completed shorthand font tests, code cleanup, fixed choosify for edges * Addressed review comments * Addressed review comments, cleanup
7 years ago
Network: Fix handling of multi-fonts (#3486) * The next fix on Travis unit test failure This is the next escalation on the war against the Travis unit tests failing (which came into being by yours truly). By accident, I could recreate the unit test failure on my development machine. This led to a more directed effort to squash the bug. The insight here is that test `(window === undefined)` fails, but `(typeof window === 'undefined`)` succeeds. This undoubtedly has to do with the special status `window` has as a global object. Changes: - Added check on presence of `window` in `Canvas._requestNextFrame()`, fixed local source errors. - Added catch clause in `CanvasRendered._determinePixelRatio()` - small fix: raised timeout for the network `worldCup2014` unit test * Preliminary refactoring in utils.js * Added unit tests for extend routines, commenting and small fixes * More unit tests for extend routines * - Completed unit tests for extend routines in - Small fixes and cleanup in `util.js` - Removed `util.protoExtend()`, not used anywhere * Added unit tests for known font options * Interim save before trying out another proto chain strategy * Fixed problem in first example #3408 * Removed silly file that shouldn't be there * Added unit test for multi-fonts * Comment edits * Verufy unit tests, small adjustments for groups * Further work on getting unit tests to work. PARTS NEED TO BE CLEANED UP! * Further tweaks to get unit tests working * All unit tests passing * Fixes due to linting * Small edits * Removed prototype handling from font pile * Fixes during testing examples of #3408 * Added unit test for edge labels, small fixes * Added unit tests for shorthand string fonts; some tests still failing * All unit tests pass * Removed Label.parseOptions() * Completed shorthand font tests, code cleanup, fixed choosify for edges * Addressed review comments * Addressed review comments, cleanup
7 years ago
Network: Fix handling of multi-fonts (#3486) * The next fix on Travis unit test failure This is the next escalation on the war against the Travis unit tests failing (which came into being by yours truly). By accident, I could recreate the unit test failure on my development machine. This led to a more directed effort to squash the bug. The insight here is that test `(window === undefined)` fails, but `(typeof window === 'undefined`)` succeeds. This undoubtedly has to do with the special status `window` has as a global object. Changes: - Added check on presence of `window` in `Canvas._requestNextFrame()`, fixed local source errors. - Added catch clause in `CanvasRendered._determinePixelRatio()` - small fix: raised timeout for the network `worldCup2014` unit test * Preliminary refactoring in utils.js * Added unit tests for extend routines, commenting and small fixes * More unit tests for extend routines * - Completed unit tests for extend routines in - Small fixes and cleanup in `util.js` - Removed `util.protoExtend()`, not used anywhere * Added unit tests for known font options * Interim save before trying out another proto chain strategy * Fixed problem in first example #3408 * Removed silly file that shouldn't be there * Added unit test for multi-fonts * Comment edits * Verufy unit tests, small adjustments for groups * Further work on getting unit tests to work. PARTS NEED TO BE CLEANED UP! * Further tweaks to get unit tests working * All unit tests passing * Fixes due to linting * Small edits * Removed prototype handling from font pile * Fixes during testing examples of #3408 * Added unit test for edge labels, small fixes * Added unit tests for shorthand string fonts; some tests still failing * All unit tests pass * Removed Label.parseOptions() * Completed shorthand font tests, code cleanup, fixed choosify for edges * Addressed review comments * Addressed review comments, cleanup
7 years ago
Network: Fix handling of multi-fonts (#3486) * The next fix on Travis unit test failure This is the next escalation on the war against the Travis unit tests failing (which came into being by yours truly). By accident, I could recreate the unit test failure on my development machine. This led to a more directed effort to squash the bug. The insight here is that test `(window === undefined)` fails, but `(typeof window === 'undefined`)` succeeds. This undoubtedly has to do with the special status `window` has as a global object. Changes: - Added check on presence of `window` in `Canvas._requestNextFrame()`, fixed local source errors. - Added catch clause in `CanvasRendered._determinePixelRatio()` - small fix: raised timeout for the network `worldCup2014` unit test * Preliminary refactoring in utils.js * Added unit tests for extend routines, commenting and small fixes * More unit tests for extend routines * - Completed unit tests for extend routines in - Small fixes and cleanup in `util.js` - Removed `util.protoExtend()`, not used anywhere * Added unit tests for known font options * Interim save before trying out another proto chain strategy * Fixed problem in first example #3408 * Removed silly file that shouldn't be there * Added unit test for multi-fonts * Comment edits * Verufy unit tests, small adjustments for groups * Further work on getting unit tests to work. PARTS NEED TO BE CLEANED UP! * Further tweaks to get unit tests working * All unit tests passing * Fixes due to linting * Small edits * Removed prototype handling from font pile * Fixes during testing examples of #3408 * Added unit test for edge labels, small fixes * Added unit tests for shorthand string fonts; some tests still failing * All unit tests pass * Removed Label.parseOptions() * Completed shorthand font tests, code cleanup, fixed choosify for edges * Addressed review comments * Addressed review comments, cleanup
7 years ago
Network: Fix handling of multi-fonts (#3486) * The next fix on Travis unit test failure This is the next escalation on the war against the Travis unit tests failing (which came into being by yours truly). By accident, I could recreate the unit test failure on my development machine. This led to a more directed effort to squash the bug. The insight here is that test `(window === undefined)` fails, but `(typeof window === 'undefined`)` succeeds. This undoubtedly has to do with the special status `window` has as a global object. Changes: - Added check on presence of `window` in `Canvas._requestNextFrame()`, fixed local source errors. - Added catch clause in `CanvasRendered._determinePixelRatio()` - small fix: raised timeout for the network `worldCup2014` unit test * Preliminary refactoring in utils.js * Added unit tests for extend routines, commenting and small fixes * More unit tests for extend routines * - Completed unit tests for extend routines in - Small fixes and cleanup in `util.js` - Removed `util.protoExtend()`, not used anywhere * Added unit tests for known font options * Interim save before trying out another proto chain strategy * Fixed problem in first example #3408 * Removed silly file that shouldn't be there * Added unit test for multi-fonts * Comment edits * Verufy unit tests, small adjustments for groups * Further work on getting unit tests to work. PARTS NEED TO BE CLEANED UP! * Further tweaks to get unit tests working * All unit tests passing * Fixes due to linting * Small edits * Removed prototype handling from font pile * Fixes during testing examples of #3408 * Added unit test for edge labels, small fixes * Added unit tests for shorthand string fonts; some tests still failing * All unit tests pass * Removed Label.parseOptions() * Completed shorthand font tests, code cleanup, fixed choosify for edges * Addressed review comments * Addressed review comments, cleanup
7 years ago
  1. let LabelAccumulator = require('./LabelAccumulator').default;
  2. let ComponentUtil = require('./ComponentUtil').default;
  3. /**
  4. * Helper class for Label which explodes the label text into lines and blocks within lines
  5. *
  6. * @private
  7. */
  8. class LabelSplitter {
  9. /**
  10. * @param {CanvasRenderingContext2D} ctx Canvas rendering context
  11. * @param {Label} parent reference to the Label instance using current instance
  12. * @param {boolean} selected
  13. * @param {boolean} hover
  14. */
  15. constructor(ctx, parent, selected, hover) {
  16. this.ctx = ctx;
  17. this.parent = parent;
  18. /**
  19. * Callback to determine text width; passed to LabelAccumulator instance
  20. *
  21. * @param {String} text string to determine width of
  22. * @param {String} mod font type to use for this text
  23. * @return {Object} { width, values} width in pixels and font attributes
  24. */
  25. let textWidth = (text, mod) => {
  26. if (text === undefined) return 0;
  27. // TODO: This can be done more efficiently with caching
  28. let values = this.parent.getFormattingValues(ctx, selected, hover, mod);
  29. let width = 0;
  30. if (text !== '') {
  31. // NOTE: The following may actually be *incorrect* for the mod fonts!
  32. // This returns the size with a regular font, bold etc. may
  33. // have different sizes.
  34. let measure = this.ctx.measureText(text);
  35. width = measure.width;
  36. }
  37. return {width, values: values};
  38. };
  39. this.lines = new LabelAccumulator(textWidth);
  40. }
  41. /**
  42. * Split passed text of a label into lines and blocks.
  43. *
  44. * # NOTE
  45. *
  46. * The handling of spacing is option dependent:
  47. *
  48. * - if `font.multi : false`, all spaces are retained
  49. * - if `font.multi : true`, every sequence of spaces is compressed to a single space
  50. *
  51. * This might not be the best way to do it, but this is as it has been working till now.
  52. * In order not to break existing functionality, for the time being this behaviour will
  53. * be retained in any code changes.
  54. *
  55. * @param {string} text text to split
  56. * @returns {Array<line>}
  57. */
  58. process(text) {
  59. if (!ComponentUtil.isValidLabel(text)) {
  60. return this.lines.finalize();
  61. }
  62. var font = this.parent.fontOptions;
  63. // Normalize the end-of-line's to a single representation - order important
  64. text = text.replace(/\r\n/g, '\n'); // Dos EOL's
  65. text = text.replace(/\r/g, '\n'); // Mac EOL's
  66. // Note that at this point, there can be no \r's in the text.
  67. // This is used later on splitStringIntoLines() to split multifont texts.
  68. let nlLines = String(text).split('\n');
  69. let lineCount = nlLines.length;
  70. if (font.multi) {
  71. // Multi-font case: styling tags active
  72. for (let i = 0; i < lineCount; i++) {
  73. let blocks = this.splitBlocks(nlLines[i], font.multi);
  74. // Post: Sequences of tabs and spaces are reduced to single space
  75. if (blocks === undefined) continue;
  76. if (blocks.length === 0) {
  77. this.lines.newLine("");
  78. continue;
  79. }
  80. if (font.maxWdt > 0) {
  81. // widthConstraint.maximum defined
  82. //console.log('Running widthConstraint multi, max: ' + this.fontOptions.maxWdt);
  83. for (let j = 0; j < blocks.length; j++) {
  84. let mod = blocks[j].mod;
  85. let text = blocks[j].text;
  86. this.splitStringIntoLines(text, mod, true);
  87. }
  88. } else {
  89. // widthConstraint.maximum NOT defined
  90. for (let j = 0; j < blocks.length; j++) {
  91. let mod = blocks[j].mod;
  92. let text = blocks[j].text;
  93. this.lines.append(text, mod);
  94. }
  95. }
  96. this.lines.newLine();
  97. }
  98. } else {
  99. // Single-font case
  100. if (font.maxWdt > 0) {
  101. // widthConstraint.maximum defined
  102. // console.log('Running widthConstraint normal, max: ' + this.fontOptions.maxWdt);
  103. for (let i = 0; i < lineCount; i++) {
  104. this.splitStringIntoLines(nlLines[i]);
  105. }
  106. } else {
  107. // widthConstraint.maximum NOT defined
  108. for (let i = 0; i < lineCount; i++) {
  109. this.lines.newLine(nlLines[i]);
  110. }
  111. }
  112. }
  113. return this.lines.finalize();
  114. }
  115. /**
  116. * normalize the markup system
  117. *
  118. * @param {boolean|'md'|'markdown'|'html'} markupSystem
  119. * @returns {string}
  120. */
  121. decodeMarkupSystem(markupSystem) {
  122. let system = 'none';
  123. if (markupSystem === 'markdown' || markupSystem === 'md') {
  124. system = 'markdown';
  125. } else if (markupSystem === true || markupSystem === 'html') {
  126. system = 'html'
  127. }
  128. return system;
  129. }
  130. /**
  131. *
  132. * @param {string} text
  133. * @returns {Array}
  134. */
  135. splitHtmlBlocks(text) {
  136. let blocks = [];
  137. // TODO: consolidate following + methods/closures with splitMarkdownBlocks()
  138. // NOTE: sequences of tabs and spaces are reduced to single space; scan usage of `this.spacing` within method
  139. let s = {
  140. bold: false,
  141. ital: false,
  142. mono: false,
  143. spacing: false,
  144. position: 0,
  145. buffer: "",
  146. modStack: []
  147. };
  148. s.mod = function() {
  149. return (this.modStack.length === 0) ? 'normal' : this.modStack[0];
  150. };
  151. s.modName = function() {
  152. if (this.modStack.length === 0)
  153. return 'normal';
  154. else if (this.modStack[0] === 'mono')
  155. return 'mono';
  156. else {
  157. if (s.bold && s.ital) {
  158. return 'boldital';
  159. } else if (s.bold) {
  160. return 'bold';
  161. } else if (s.ital) {
  162. return 'ital';
  163. }
  164. }
  165. };
  166. s.emitBlock = function(override=false) { // eslint-disable-line no-unused-vars
  167. if (this.spacing) {
  168. this.add(" ");
  169. this.spacing = false;
  170. }
  171. if (this.buffer.length > 0) {
  172. blocks.push({ text: this.buffer, mod: this.modName() });
  173. this.buffer = "";
  174. }
  175. };
  176. s.add = function(text) {
  177. if (text === " ") {
  178. s.spacing = true;
  179. }
  180. if (s.spacing) {
  181. this.buffer += " ";
  182. this.spacing = false;
  183. }
  184. if (text != " ") {
  185. this.buffer += text;
  186. }
  187. };
  188. while (s.position < text.length) {
  189. let ch = text.charAt(s.position);
  190. if (/[ \t]/.test(ch)) {
  191. if (!s.mono) {
  192. s.spacing = true;
  193. } else {
  194. s.add(ch);
  195. }
  196. } else if (/</.test(ch)) {
  197. if (!s.mono && !s.bold && /<b>/.test(text.substr(s.position,3))) {
  198. s.emitBlock();
  199. s.bold = true;
  200. s.modStack.unshift("bold");
  201. s.position += 2;
  202. } else if (!s.mono && !s.ital && /<i>/.test(text.substr(s.position,3))) {
  203. s.emitBlock();
  204. s.ital = true;
  205. s.modStack.unshift("ital");
  206. s.position += 2;
  207. } else if (!s.mono && /<code>/.test(text.substr(s.position,6))) {
  208. s.emitBlock();
  209. s.mono = true;
  210. s.modStack.unshift("mono");
  211. s.position += 5;
  212. } else if (!s.mono && (s.mod() === 'bold') && /<\/b>/.test(text.substr(s.position,4))) {
  213. s.emitBlock();
  214. s.bold = false;
  215. s.modStack.shift();
  216. s.position += 3;
  217. } else if (!s.mono && (s.mod() === 'ital') && /<\/i>/.test(text.substr(s.position,4))) {
  218. s.emitBlock();
  219. s.ital = false;
  220. s.modStack.shift();
  221. s.position += 3;
  222. } else if ((s.mod() === 'mono') && /<\/code>/.test(text.substr(s.position,7))) {
  223. s.emitBlock();
  224. s.mono = false;
  225. s.modStack.shift();
  226. s.position += 6;
  227. } else {
  228. s.add(ch);
  229. }
  230. } else if (/&/.test(ch)) {
  231. if (/&lt;/.test(text.substr(s.position,4))) {
  232. s.add("<");
  233. s.position += 3;
  234. } else if (/&amp;/.test(text.substr(s.position,5))) {
  235. s.add("&");
  236. s.position += 4;
  237. } else {
  238. s.add("&");
  239. }
  240. } else {
  241. s.add(ch);
  242. }
  243. s.position++
  244. }
  245. s.emitBlock();
  246. return blocks;
  247. }
  248. /**
  249. *
  250. * @param {string} text
  251. * @returns {Array}
  252. */
  253. splitMarkdownBlocks(text) {
  254. let blocks = [];
  255. // TODO: consolidate following + methods/closures with splitHtmlBlocks()
  256. // NOTE: sequences of tabs and spaces are reduced to single space; scan usage of `this.spacing` within method
  257. let s = {
  258. bold: false,
  259. ital: false,
  260. mono: false,
  261. beginable: true,
  262. spacing: false,
  263. position: 0,
  264. buffer: "",
  265. modStack: []
  266. };
  267. s.mod = function() {
  268. return (this.modStack.length === 0) ? 'normal' : this.modStack[0];
  269. };
  270. s.modName = function() {
  271. if (this.modStack.length === 0)
  272. return 'normal';
  273. else if (this.modStack[0] === 'mono')
  274. return 'mono';
  275. else {
  276. if (s.bold && s.ital) {
  277. return 'boldital';
  278. } else if (s.bold) {
  279. return 'bold';
  280. } else if (s.ital) {
  281. return 'ital';
  282. }
  283. }
  284. };
  285. s.emitBlock = function(override=false) { // eslint-disable-line no-unused-vars
  286. if (this.spacing) {
  287. this.add(" ");
  288. this.spacing = false;
  289. }
  290. if (this.buffer.length > 0) {
  291. blocks.push({ text: this.buffer, mod: this.modName() });
  292. this.buffer = "";
  293. }
  294. };
  295. s.add = function(text) {
  296. if (text === " ") {
  297. s.spacing = true;
  298. }
  299. if (s.spacing) {
  300. this.buffer += " ";
  301. this.spacing = false;
  302. }
  303. if (text != " ") {
  304. this.buffer += text;
  305. }
  306. };
  307. while (s.position < text.length) {
  308. let ch = text.charAt(s.position);
  309. if (/[ \t]/.test(ch)) {
  310. if (!s.mono) {
  311. s.spacing = true;
  312. } else {
  313. s.add(ch);
  314. }
  315. s.beginable = true
  316. } else if (/\\/.test(ch)) {
  317. if (s.position < text.length+1) {
  318. s.position++;
  319. ch = text.charAt(s.position);
  320. if (/ \t/.test(ch)) {
  321. s.spacing = true;
  322. } else {
  323. s.add(ch);
  324. s.beginable = false;
  325. }
  326. }
  327. } else if (!s.mono && !s.bold && (s.beginable || s.spacing) && /\*/.test(ch)) {
  328. s.emitBlock();
  329. s.bold = true;
  330. s.modStack.unshift("bold");
  331. } else if (!s.mono && !s.ital && (s.beginable || s.spacing) && /\_/.test(ch)) {
  332. s.emitBlock();
  333. s.ital = true;
  334. s.modStack.unshift("ital");
  335. } else if (!s.mono && (s.beginable || s.spacing) && /`/.test(ch)) {
  336. s.emitBlock();
  337. s.mono = true;
  338. s.modStack.unshift("mono");
  339. } else if (!s.mono && (s.mod() === "bold") && /\*/.test(ch)) {
  340. if ((s.position === text.length-1) || /[.,_` \t\n]/.test(text.charAt(s.position+1))) {
  341. s.emitBlock();
  342. s.bold = false;
  343. s.modStack.shift();
  344. } else {
  345. s.add(ch);
  346. }
  347. } else if (!s.mono && (s.mod() === "ital") && /\_/.test(ch)) {
  348. if ((s.position === text.length-1) || /[.,*` \t\n]/.test(text.charAt(s.position+1))) {
  349. s.emitBlock();
  350. s.ital = false;
  351. s.modStack.shift();
  352. } else {
  353. s.add(ch);
  354. }
  355. } else if (s.mono && (s.mod() === "mono") && /`/.test(ch)) {
  356. if ((s.position === text.length-1) || (/[.,*_ \t\n]/.test(text.charAt(s.position+1)))) {
  357. s.emitBlock();
  358. s.mono = false;
  359. s.modStack.shift();
  360. } else {
  361. s.add(ch);
  362. }
  363. } else {
  364. s.add(ch);
  365. s.beginable = false;
  366. }
  367. s.position++
  368. }
  369. s.emitBlock();
  370. return blocks;
  371. }
  372. /**
  373. * Explodes a piece of text into single-font blocks using a given markup
  374. *
  375. * @param {string} text
  376. * @param {boolean|'md'|'markdown'|'html'} markupSystem
  377. * @returns {Array.<{text: string, mod: string}>}
  378. * @private
  379. */
  380. splitBlocks(text, markupSystem) {
  381. let system = this.decodeMarkupSystem(markupSystem);
  382. if (system === 'none') {
  383. return [{
  384. text: text,
  385. mod: 'normal'
  386. }]
  387. } else if (system === 'markdown') {
  388. return this.splitMarkdownBlocks(text);
  389. } else if (system === 'html') {
  390. return this.splitHtmlBlocks(text);
  391. }
  392. }
  393. /**
  394. * @param {string} text
  395. * @returns {boolean} true if text length over the current max with
  396. * @private
  397. */
  398. overMaxWidth(text) {
  399. let width = this.ctx.measureText(text).width;
  400. return (this.lines.curWidth() + width > this.parent.fontOptions.maxWdt);
  401. }
  402. /**
  403. * Determine the longest part of the sentence which still fits in the
  404. * current max width.
  405. *
  406. * @param {Array} words Array of strings signifying a text lines
  407. * @return {number} index of first item in string making string go over max
  408. * @private
  409. */
  410. getLongestFit(words) {
  411. let text = '';
  412. let w = 0;
  413. while (w < words.length) {
  414. let pre = (text === '') ? '' : ' ';
  415. let newText = text + pre + words[w];
  416. if (this.overMaxWidth(newText)) break;
  417. text = newText;
  418. w++;
  419. }
  420. return w;
  421. }
  422. /**
  423. * Determine the longest part of the string which still fits in the
  424. * current max width.
  425. *
  426. * @param {Array} words Array of strings signifying a text lines
  427. * @return {number} index of first item in string making string go over max
  428. */
  429. getLongestFitWord(words) {
  430. let w = 0;
  431. while (w < words.length) {
  432. if (this.overMaxWidth(words.slice(0,w))) break;
  433. w++;
  434. }
  435. return w;
  436. }
  437. /**
  438. * Split the passed text into lines, according to width constraint (if any).
  439. *
  440. * The method assumes that the input string is a single line, i.e. without lines break.
  441. *
  442. * This method retains spaces, if still present (case `font.multi: false`).
  443. * A space which falls on an internal line break, will be replaced by a newline.
  444. * There is no special handling of tabs; these go along with the flow.
  445. *
  446. * @param {string} str
  447. * @param {string} [mod='normal']
  448. * @param {boolean} [appendLast=false]
  449. * @private
  450. */
  451. splitStringIntoLines(str, mod = 'normal', appendLast = false) {
  452. // Still-present spaces are relevant, retain them
  453. str = str.replace(/^( +)/g, '$1\r');
  454. str = str.replace(/([^\r][^ ]*)( +)/g, '$1\r$2\r');
  455. let words = str.split('\r');
  456. while (words.length > 0) {
  457. let w = this.getLongestFit(words);
  458. if (w === 0) {
  459. // Special case: the first word is already larger than the max width.
  460. let word = words[0];
  461. // Break the word to the largest part that fits the line
  462. let x = this.getLongestFitWord(word);
  463. this.lines.newLine(word.slice(0, x), mod);
  464. // Adjust the word, so that the rest will be done next iteration
  465. words[0] = word.slice(x);
  466. } else {
  467. // skip any space that is replaced by a newline
  468. let newW = w;
  469. if (words[w - 1] === ' ') {
  470. w--;
  471. } else if (words[newW] === ' ') {
  472. newW++;
  473. }
  474. let text = words.slice(0, w).join("");
  475. if (w == words.length && appendLast) {
  476. this.lines.append(text, mod);
  477. } else {
  478. this.lines.newLine(text, mod);
  479. }
  480. // Adjust the word, so that the rest will be done next iteration
  481. words = words.slice(newW);
  482. }
  483. }
  484. }
  485. }
  486. export default LabelSplitter;