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.

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