pixi.js
Version:
<p align="center"> <a href="https://pixijs.com" target="_blank" rel="noopener noreferrer"> <img height="150" src="https://files.pixijs.download/branding/pixijs-logo-transparent-dark.svg?v=1" alt="PixiJS logo"> </a> </p> <br/> <p align="center">
1 lines • 11.3 kB
Source Map (JSON)
{"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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"}