{"version":3,"file":"wordWrap.mjs","sources":["../../../../../src/scene/text/canvas/utils/wordWrap.ts"],"sourcesContent":["import {\n    collapseNewlines,\n    collapseSpaces,\n    getCharacterGroups,\n    isBreakingSpace,\n    isNewline,\n    tokenize,\n    trimRight,\n} from './textTokenization';\n\nimport type { ICanvas, ICanvasRenderingContext2DSettings } from '../../../../environment/canvas/ICanvas';\nimport type { ICanvasRenderingContext2D } from '../../../../environment/canvas/ICanvasRenderingContext2D';\nimport type { TextStyle } from '../../TextStyle';\nimport type { CanBreakCharsFn, MeasureTextFn, WordWrapSplitFn } from './types';\n\ntype CharacterWidthCache = Record<string, number>;\n/**\n * Function type for checking if words can be broken.\n * @internal\n */\ntype CanBreakWordsFn = (token: string, breakWords: boolean) => boolean;\n\n// Default settings used for all getContext calls\nconst contextSettings: ICanvasRenderingContext2DSettings = {\n    // TextMetrics requires getImageData readback for measuring fonts.\n    willReadFrequently: true,\n};\n\n/**\n * Gets & sets the widths of calculated characters in a cache object\n * @param key - The key\n * @param letterSpacing - The letter spacing\n * @param cache - The cache\n * @param context - The canvas context\n * @param measureTextFn - Function to measure text\n * @returns The cached or measured width.\n * @internal\n */\nfunction getFromCache(\n    key: string,\n    letterSpacing: number,\n    cache: CharacterWidthCache,\n    context: ICanvasRenderingContext2D,\n    measureTextFn: MeasureTextFn,\n): number\n{\n    let width = cache[key];\n\n    if (typeof width !== 'number')\n    {\n        width = measureTextFn(key, letterSpacing, context) + letterSpacing;\n        cache[key] = width;\n    }\n\n    return width;\n}\n\n/**\n * Applies newlines to a string to have it optimally fit into the horizontal\n * bounds set by the Text object's wordWrapWidth property.\n * @param text - String to apply word wrapping to\n * @param style - the style to use when wrapping\n * @param canvas - specification of the canvas to use for measuring.\n * @param measureTextFn - Function to measure text width\n * @param canBreakWordsFn - Function to check if words can be broken\n * @param canBreakCharsFn - Function to check if characters can be broken\n * @param wordWrapSplitFn - Function to split words into characters\n * @returns New string with new lines applied where required\n * @internal\n */\nexport function wordWrap(\n    text: string,\n    style: TextStyle,\n    canvas: ICanvas,\n    measureTextFn: MeasureTextFn,\n    canBreakWordsFn: CanBreakWordsFn,\n    canBreakCharsFn: CanBreakCharsFn,\n    wordWrapSplitFn: WordWrapSplitFn,\n): string\n{\n    const context = canvas.getContext('2d', contextSettings);\n\n    context.font = style._fontString;\n\n    let width = 0;\n    let line = '';\n    const linesArray: string[] = [];\n\n    const cache: CharacterWidthCache = Object.create(null);\n    const { letterSpacing, whiteSpace } = style;\n\n    // How to handle whitespaces\n    const shouldCollapseSpaces = collapseSpaces(whiteSpace);\n    const shouldCollapseNewlines = collapseNewlines(whiteSpace);\n\n    // whether or not spaces may be added to the beginning of lines\n    let canPrependSpaces = !shouldCollapseSpaces;\n\n    // There is letterSpacing after every char except the last one\n    // t_h_i_s_' '_i_s_' '_a_n_' '_e_x_a_m_p_l_e_' '_!\n    // so for convenience the above needs to be compared to width + 1 extra letterSpace\n    // t_h_i_s_' '_i_s_' '_a_n_' '_e_x_a_m_p_l_e_' '_!_\n    // ________________________________________________\n    // And then the final space is simply no appended to each line\n    const wordWrapWidth = style.wordWrapWidth + letterSpacing;\n\n    // break text into words, spaces and newline chars\n    const tokens = tokenize(text);\n\n    for (let i = 0; i < tokens.length; i++)\n    {\n        // get the word, space or newlineChar\n        let token = tokens[i];\n\n        // if word is a new line\n        if (isNewline(token))\n        {\n            // keep the new line\n            if (!shouldCollapseNewlines)\n            {\n                linesArray.push(trimRight(line));\n                canPrependSpaces = !shouldCollapseSpaces;\n                line = '';\n                width = 0;\n                continue;\n            }\n\n            // if we should collapse new lines\n            // we simply convert it into a space\n            token = ' ';\n        }\n\n        // if we should collapse repeated whitespaces\n        if (shouldCollapseSpaces)\n        {\n            // check both this and the last tokens for spaces\n            const currIsBreakingSpace = isBreakingSpace(token);\n            const lastIsBreakingSpace = isBreakingSpace(line[line.length - 1]);\n\n            if (currIsBreakingSpace && lastIsBreakingSpace)\n            {\n                continue;\n            }\n        }\n\n        // get word width from cache if possible\n        const tokenWidth = getFromCache(token, letterSpacing, cache, context, measureTextFn);\n\n        // word is longer than desired bounds\n        if (tokenWidth > wordWrapWidth)\n        {\n            // if we are not already at the beginning of a line\n            if (line !== '')\n            {\n                // start newlines for overflow words\n                linesArray.push(trimRight(line));\n                line = '';\n                width = 0;\n            }\n\n            // break large word over multiple lines\n            if (canBreakWordsFn(token, style.breakWords))\n            {\n                // break word into character groups that shouldn't be split\n                const charGroups = getCharacterGroups(token, style.breakWords, wordWrapSplitFn, canBreakCharsFn);\n\n                // loop the character groups\n                for (const char of charGroups)\n                {\n                    const characterWidth = getFromCache(char, letterSpacing, cache, context, measureTextFn);\n\n                    if (characterWidth + width > wordWrapWidth)\n                    {\n                        linesArray.push(trimRight(line));\n                        canPrependSpaces = false;\n                        line = '';\n                        width = 0;\n                    }\n\n                    line += char;\n                    width += characterWidth;\n                }\n            }\n\n            // run word out of the bounds\n            else\n            {\n                // if there are words in this line already\n                // finish that line and start a new one\n                if (line.length > 0)\n                {\n                    linesArray.push(trimRight(line));\n                    line = '';\n                    width = 0;\n                }\n\n                // give it its own line\n                linesArray.push(trimRight(token));\n                canPrependSpaces = false;\n                line = '';\n                width = 0;\n            }\n        }\n\n        // word could fit\n        else\n        {\n            // word won't fit because of existing words\n            // start a new line\n            if (tokenWidth + width > wordWrapWidth)\n            {\n                // if its a space we don't want it\n                canPrependSpaces = false;\n\n                // add a new line\n                linesArray.push(trimRight(line));\n\n                // start a new line\n                line = '';\n                width = 0;\n            }\n\n            // don't add spaces to the beginning of lines\n            if (line.length > 0 || !isBreakingSpace(token) || canPrependSpaces)\n            {\n                // add the word to the current line\n                line += token;\n\n                // update width counter\n                width += tokenWidth;\n            }\n        }\n    }\n\n    const trimmedLine = trimRight(line);\n\n    if (trimmedLine.length > 0)\n    {\n        linesArray.push(trimmedLine);\n    }\n\n    return linesArray.join('\\n');\n}\n"],"names":[],"mappings":"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"}