From b36a0177dbb0ecc0b703613f30ce4ab7bad6aeb7 Mon Sep 17 00:00:00 2001 From: Sidharth Vinod Date: Tue, 13 Jun 2023 11:42:39 +0530 Subject: [PATCH] Use joiner to split unicode --- .../src/rendering-util/splitText.spec.ts | 29 +++++++++++++++---- .../mermaid/src/rendering-util/splitText.ts | 29 +++++++++++++++---- 2 files changed, 46 insertions(+), 12 deletions(-) diff --git a/packages/mermaid/src/rendering-util/splitText.spec.ts b/packages/mermaid/src/rendering-util/splitText.spec.ts index 77bd6102c..3dafb80ee 100644 --- a/packages/mermaid/src/rendering-util/splitText.spec.ts +++ b/packages/mermaid/src/rendering-util/splitText.spec.ts @@ -13,26 +13,43 @@ describe('splitText', () => { }); describe('split lines', () => { + const createCheckFn = (width: number): CheckFitFunction => { + return (text: string) => { + return splitTextToChars(text).length <= width; + }; + }; + it.each([ // empty string - { str: '', width: 1, split: [''] }, - // Width >= Individual words - { str: 'hello world', width: 5, split: ['hello', 'world'] }, { str: 'hello world', width: 7, split: ['hello', 'world'] }, // width > full line { str: 'hello world', width: 20, split: ['hello world'] }, // width < individual word { str: 'hello world', width: 3, split: ['hel', 'lo', 'wor', 'ld'] }, { str: 'hello 12 world', width: 4, split: ['hell', 'o 12', 'worl', 'd'] }, + { str: 'hello 1 2 world', width: 4, split: ['hell', 'o 1', '2', 'worl', 'd'] }, + { str: 'hello 1 2 world', width: 6, split: ['hello', ' 1 2', 'world'] }, { str: 'πŸ³οΈβ€βš§οΈπŸ³οΈβ€πŸŒˆπŸ‘©πŸΎβ€β€οΈβ€πŸ‘¨πŸ»', width: 1, split: ['πŸ³οΈβ€βš§οΈ', 'πŸ³οΈβ€πŸŒˆ', 'πŸ‘©πŸΎβ€β€οΈβ€πŸ‘¨πŸ»'] }, + { str: 'πŸ³οΈβ€βš§οΈπŸ³οΈβ€πŸŒˆπŸ‘©πŸΎβ€β€οΈβ€πŸ‘¨πŸ»', width: 2, split: ['πŸ³οΈβ€βš§οΈπŸ³οΈβ€πŸŒˆ', 'πŸ‘©πŸΎβ€β€οΈβ€πŸ‘¨πŸ»'] }, + { str: 'πŸ³οΈβ€βš§οΈπŸ³οΈβ€πŸŒˆπŸ‘©πŸΎβ€β€οΈβ€πŸ‘¨πŸ»', width: 3, split: ['πŸ³οΈβ€βš§οΈπŸ³οΈβ€πŸŒˆπŸ‘©πŸΎβ€β€οΈβ€πŸ‘¨πŸ»'] }, + { str: 'δΈ­ζ–‡δΈ­', width: 1, split: ['δΈ­', 'ζ–‡', 'δΈ­'] }, + { str: 'δΈ­ζ–‡δΈ­', width: 2, split: ['δΈ­ζ–‡', 'δΈ­'] }, + { str: 'δΈ­ζ–‡δΈ­', width: 3, split: ['δΈ­ζ–‡δΈ­'] }, { str: 'Flag πŸ³οΈβ€βš§οΈ this πŸ³οΈβ€πŸŒˆ', width: 6, split: ['Flag πŸ³οΈβ€βš§οΈ', 'this πŸ³οΈβ€πŸŒˆ'] }, ])( 'should split $str into lines of $width characters', ({ str, split, width }: { str: string; width: number; split: string[] }) => { - const checkFn: CheckFitFunction = (text: string) => { - return splitTextToChars(text).length <= width; - }; + const checkFn = createCheckFn(width); expect(splitLineToFitWidth(str, checkFn)).toEqual(split); } ); + + it('should handle strings with newlines', () => { + const checkFn: CheckFitFunction = createCheckFn(6); + const str = `Flag + πŸ³οΈβ€βš§οΈ this πŸ³οΈβ€πŸŒˆ`; + expect(() => splitLineToFitWidth(str, checkFn)).toThrowErrorMatchingInlineSnapshot( + '"splitLineToFitWidth does not support newlines in the line"' + ); + }); }); diff --git a/packages/mermaid/src/rendering-util/splitText.ts b/packages/mermaid/src/rendering-util/splitText.ts index 1cd16e35b..c1d25ea13 100644 --- a/packages/mermaid/src/rendering-util/splitText.ts +++ b/packages/mermaid/src/rendering-util/splitText.ts @@ -15,11 +15,17 @@ export function splitTextToChars(text: string): string[] { */ function splitLineToWords(text: string): string[] { if (Intl.Segmenter) { - return [...new Intl.Segmenter(undefined, { granularity: 'word' }).segment(text)] - .map((s) => s.segment) - .filter((word) => word !== ' '); + return [...new Intl.Segmenter(undefined, { granularity: 'word' }).segment(text)].map( + (s) => s.segment + ); } - return text.split(' '); + // Split by ' ' removes the ' 's from the result. + const words = text.split(' '); + // Add the ' 's back to the result. + const wordsWithSpaces = words.flatMap((s) => [s, ' ']); + // Remove last space. + wordsWithSpaces.pop(); + return wordsWithSpaces; } /** @@ -55,7 +61,11 @@ function splitWordToFitWidthRecursion( } export function splitLineToFitWidth(line: string, checkFit: CheckFitFunction): string[] { - return splitLineToFitWidthRecursion(splitLineToWords(line), checkFit); + if (line.includes('\n')) { + throw new Error('splitLineToFitWidth does not support newlines in the line'); + } + const words = splitLineToWords(line); + return splitLineToFitWidthRecursion(words, checkFit); } function splitLineToFitWidthRecursion( @@ -74,8 +84,15 @@ function splitLineToFitWidthRecursion( } return lines.length > 0 ? lines : ['']; } + let joiner = ''; + if (words[0] === ' ') { + joiner = ' '; + words.shift(); + } const nextWord = words.shift() ?? ' '; - const lineWithNextWord = newLine ? `${newLine} ${nextWord}` : nextWord; + + const nextWordWithJoiner = joiner + nextWord; + const lineWithNextWord = newLine ? `${newLine}${nextWordWithJoiner}` : nextWordWithJoiner; if (checkFit(lineWithNextWord)) { // nextWord fits, so we can add it to the new line and continue return splitLineToFitWidthRecursion(words, checkFit, lines, lineWithNextWord);