diff --git a/src/element/textElement.test.ts b/src/element/textElement.test.ts index 106ed7beaba6..f83eafd1b42d 100644 --- a/src/element/textElement.test.ts +++ b/src/element/textElement.test.ts @@ -9,6 +9,7 @@ import { detectLineHeight, getLineHeightInPx, getDefaultLineHeight, + parseTokens, } from "./textElement"; import { FontString } from "./types"; @@ -183,6 +184,56 @@ now`, expect(wrapText(text, font, -1)).toEqual(text); expect(wrapText(text, font, Infinity)).toEqual(text); }); + + it("should wrap the text correctly when text contains hyphen", () => { + let text = + "Wikipedia is hosted by Wikimedia- Foundation, a non-profit organization that also hosts a range-of other projects"; + const res = wrapText(text, font, 110); + expect(res).toBe( + `Wikipedia \nis hosted \nby \nWikimedia-\nFoundation,\na non-\nprofit \norganizati\non that \nalso hosts\na range-of\nother \nprojects`, + ); + + text = "Hello thereusing-now"; + expect(wrapText(text, font, 100)).toEqual("Hello \nthereusin\ng-now"); + }); +}); + +describe("Test parseTokens", () => { + it("should split into tokens correctly", () => { + let text = "Excalidraw is a virtual collaborative whiteboard"; + expect(parseTokens(text)).toEqual([ + "Excalidraw", + "is", + "a", + "virtual", + "collaborative", + "whiteboard", + ]); + + text = + "Wikipedia is hosted by Wikimedia- Foundation, a non-profit organization that also hosts a range-of other projects"; + expect(parseTokens(text)).toEqual([ + "Wikipedia", + "is", + "hosted", + "by", + "Wikimedia-", + "", + "Foundation,", + "a", + "non-", + "profit", + "organization", + "that", + "also", + "hosts", + "a", + "range-", + "of", + "other", + "projects", + ]); + }); }); describe("Test measureText", () => { diff --git a/src/element/textElement.ts b/src/element/textElement.ts index 38da5df5a454..f01ba3e1b9eb 100644 --- a/src/element/textElement.ts +++ b/src/element/textElement.ts @@ -419,6 +419,24 @@ export const getTextHeight = ( return getLineHeightInPx(fontSize, lineHeight) * lineCount; }; +export const parseTokens = (text: string) => { + // Splitting words containing "-" as those are treated as separate words + // by css wrapping algorithm eg non-profit => non-, profit + const words = text.split("-"); + if (words.length > 1) { + // non-proft org => ['non-', 'profit org'] + words.forEach((word, index) => { + if (index !== words.length - 1) { + words[index] = word += "-"; + } + }); + } + // Joining the words with space and splitting them again with space to get the + // final list of tokens + // ['non-', 'profit org'] =>,'non- proft org' => ['non-','profit','org'] + return words.join(" ").split(" "); +}; + export const wrapText = (text: string, font: FontString, maxWidth: number) => { // if maxWidth is not finite or NaN which can happen in case of bugs in // computation, we need to make sure we don't continue as we'll end up @@ -444,17 +462,16 @@ export const wrapText = (text: string, font: FontString, maxWidth: number) => { currentLine = ""; currentLineWidthTillNow = 0; }; - originalLines.forEach((originalLine) => { const currentLineWidth = getTextWidth(originalLine, font); - //Push the line if its <= maxWidth + // Push the line if its <= maxWidth if (currentLineWidth <= maxWidth) { lines.push(originalLine); return; // continue } - const words = originalLine.split(" "); + const words = parseTokens(originalLine); resetParams(); let index = 0; @@ -472,6 +489,7 @@ export const wrapText = (text: string, font: FontString, maxWidth: number) => { else if (currentWordWidth > maxWidth) { // push current line since the current word exceeds the max width // so will be appended in next line + push(currentLine); resetParams(); @@ -492,15 +510,15 @@ export const wrapText = (text: string, font: FontString, maxWidth: number) => { currentLine += currentChar; } } - // push current line if appending space exceeds max width if (currentLineWidthTillNow + spaceWidth >= maxWidth) { push(currentLine); resetParams(); - } else { // space needs to be appended before next word // as currentLine contains chars which couldn't be appended - // to previous line + // to previous line unless the line ends with hyphen to sync + // with css word-wrap + } else if (!currentLine.endsWith("-")) { currentLine += " "; currentLineWidthTillNow += spaceWidth; } @@ -518,12 +536,23 @@ export const wrapText = (text: string, font: FontString, maxWidth: number) => { break; } index++; - currentLine += `${word} `; + + // if word ends with "-" then we don't need to add space + // to sync with css word-wrap + const shouldAppendSpace = !word.endsWith("-"); + currentLine += word; + + if (shouldAppendSpace) { + currentLine += " "; + } // Push the word if appending space exceeds max width if (currentLineWidthTillNow + spaceWidth >= maxWidth) { - const word = currentLine.slice(0, -1); - push(word); + if (shouldAppendSpace) { + lines.push(currentLine.slice(0, -1)); + } else { + lines.push(currentLine); + } resetParams(); break; }