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 • 32.5 kB
Source Map (JSON)
{"version":3,"file":"measureTaggedText.mjs","sources":["../../../../../src/scene/text/canvas/utils/measureTaggedText.ts"],"sourcesContent":["import { parseTaggedText, type TextStyleRun } from './parseTaggedText';\nimport {\n collapseNewlines,\n collapseSpaces,\n getCharacterGroups,\n isBreakingSpace,\n isNewline,\n NEWLINE_SPLIT_REGEX,\n tokenize,\n trimRight,\n} from './textTokenization';\n\nimport type { ICanvasRenderingContext2D } from '../../../../environment/canvas/ICanvasRenderingContext2D';\nimport type { TextStyle } from '../../TextStyle';\nimport type {\n CanBreakCharsFn,\n FontMetrics,\n MeasureTextFn,\n WordWrapSplitFn,\n} from './types';\n\n/**\n * Function type for measuring font metrics.\n * @internal\n */\ntype MeasureFontFn = (font: string) => FontMetrics;\n\n/**\n * Result of measuring tagged text.\n * @internal\n */\ninterface TaggedMeasurementResult\n{\n /** Total width including stroke and shadow */\n width: number;\n /** Total height including shadow */\n height: number;\n /** Array of line text (for compatibility) */\n lines: string[];\n /** Per-line widths */\n lineWidths: number[];\n /** Base line height from style */\n lineHeight: number;\n /** Maximum line width */\n maxLineWidth: number;\n /** Font properties from base style */\n fontProperties: FontMetrics;\n /** Per-line style runs */\n runsByLine: TextStyleRun[][];\n /** Per-line ascent values */\n lineAscents: number[];\n /** Per-line descent values */\n lineDescents: number[];\n /** Per-line heights */\n lineHeights: number[];\n /** Whether any run has drop shadow */\n hasDropShadow: boolean;\n}\n\n/**\n * Styled token with continuation flag for word wrapping.\n * @internal\n */\ninterface StyledToken\n{\n token: string;\n style: TextStyle;\n continuesFromPrevious: boolean;\n}\n\n/**\n * Regex to replace newlines with spaces.\n * @internal\n */\nconst NEWLINE_TO_SPACE_REGEX = /\\r\\n|\\r|\\n/g;\n\n/**\n * Measures tagged text with multiple styles.\n * Handles per-run font measurement and per-line metrics.\n * @param text - The tagged text to measure\n * @param style - The base text style containing tagStyles\n * @param wordWrap - Whether to apply word wrapping\n * @param context - The canvas 2D context\n * @param measureTextFn - Function to measure text width\n * @param measureFontFn - Function to measure font metrics\n * @param canBreakCharsFn - Function to check if characters can be broken\n * @param wordWrapSplitFn - Function to split words into characters\n * @returns TaggedMeasurementResult with all measurement data\n * @internal\n */\nexport function measureTaggedText(\n text: string,\n style: TextStyle,\n wordWrap: boolean,\n context: ICanvasRenderingContext2D,\n measureTextFn: MeasureTextFn,\n measureFontFn: MeasureFontFn,\n canBreakCharsFn: CanBreakCharsFn,\n wordWrapSplitFn: WordWrapSplitFn,\n): TaggedMeasurementResult\n{\n // Parse text into runs\n const runs = parseTaggedText(text, style);\n\n // Collapse newlines to spaces in 'normal' mode (matching regular text behavior)\n const shouldCollapseNewlines = collapseNewlines(style.whiteSpace);\n\n if (shouldCollapseNewlines)\n {\n for (let i = 0; i < runs.length; i++)\n {\n const run = runs[i];\n\n runs[i] = { text: run.text.replace(NEWLINE_TO_SPACE_REGEX, ' '), style: run.style };\n }\n }\n\n // First, we need to handle newlines and word wrap\n // Split runs by newlines first\n const runsByLine: TextStyleRun[][] = [];\n let currentLineRuns: TextStyleRun[] = [];\n\n for (const run of runs)\n {\n // Split run text by newlines\n const parts = run.text.split(NEWLINE_SPLIT_REGEX);\n\n for (let i = 0; i < parts.length; i++)\n {\n const part = parts[i];\n\n if (part === '\\r\\n' || part === '\\r' || part === '\\n')\n {\n // Push current line and start a new one\n runsByLine.push(currentLineRuns);\n currentLineRuns = [];\n }\n else if (part.length > 0)\n {\n currentLineRuns.push({ text: part, style: run.style });\n }\n }\n }\n // Don't forget the last line\n if (currentLineRuns.length > 0 || runsByLine.length === 0)\n {\n runsByLine.push(currentLineRuns);\n }\n\n // Apply word wrap if enabled\n const wrappedRunsByLine = wordWrap\n ? wordWrapTaggedLines(\n runsByLine,\n style,\n context,\n measureTextFn,\n canBreakCharsFn,\n wordWrapSplitFn,\n )\n : runsByLine;\n\n // Measure each line\n const lineWidths: number[] = [];\n const lineAscents: number[] = [];\n const lineDescents: number[] = [];\n const lineHeightsArr: number[] = [];\n const lines: string[] = [];\n let maxLineWidth = 0;\n\n // Get base font properties for fallback\n const baseFont = style._fontString;\n const baseFontProps = measureFontFn(baseFont);\n\n // Fallback in case UA disallows canvas data extraction\n if (baseFontProps.fontSize === 0)\n {\n baseFontProps.fontSize = style.fontSize as number;\n baseFontProps.ascent = style.fontSize as number;\n }\n\n let lastFont = '';\n let hasDropShadow = !!style.dropShadow;\n let maxRunStrokeWidth = style._stroke?.width || 0;\n\n for (const lineRuns of wrappedRunsByLine)\n {\n let lineWidth = 0;\n let lineAscent = baseFontProps.ascent;\n let lineDescent = baseFontProps.descent;\n let lineText = '';\n\n for (const run of lineRuns)\n {\n const runFont = run.style._fontString;\n const runFontProps = measureFontFn(runFont);\n\n if (runFont !== lastFont)\n {\n context.font = runFont;\n lastFont = runFont;\n }\n\n const runWidth = measureTextFn(run.text, run.style.letterSpacing, context);\n\n lineWidth += runWidth;\n lineAscent = Math.max(lineAscent, runFontProps.ascent);\n lineDescent = Math.max(lineDescent, runFontProps.descent);\n lineText += run.text;\n\n const runStrokeWidth = run.style._stroke?.width || 0;\n\n if (runStrokeWidth > maxRunStrokeWidth) maxRunStrokeWidth = runStrokeWidth;\n\n if (!hasDropShadow && run.style.dropShadow)\n {\n hasDropShadow = true;\n }\n }\n\n // Handle empty lines\n if (lineRuns.length === 0)\n {\n lineAscent = baseFontProps.ascent;\n lineDescent = baseFontProps.descent;\n }\n\n lineWidths.push(lineWidth);\n lineAscents.push(lineAscent);\n lineDescents.push(lineDescent);\n lines.push(lineText);\n\n // Calculate line height - use style.lineHeight if set, otherwise use measured metrics\n const computedLineHeight = style.lineHeight || (lineAscent + lineDescent);\n\n lineHeightsArr.push(computedLineHeight + style.leading);\n maxLineWidth = Math.max(maxLineWidth, lineWidth);\n }\n\n // Calculate total dimensions\n const strokeWidth = maxRunStrokeWidth;\n\n // Calculate base width - use wordWrapWidth for non-left alignment when wrapping\n const useWrapWidth = wordWrap && style.align !== 'left';\n const alignWidth = useWrapWidth ? Math.max(maxLineWidth, style.wordWrapWidth) : maxLineWidth;\n const width = alignWidth + strokeWidth + (style.dropShadow ? style.dropShadow.distance : 0);\n\n // Calculate total height from per-line heights\n let baseHeight = 0;\n\n for (let i = 0; i < lineHeightsArr.length; i++)\n {\n baseHeight += lineHeightsArr[i];\n }\n\n // Add stroke width to height for first line (to match non-tagged behavior)\n baseHeight = Math.max(baseHeight, lineHeightsArr[0] + strokeWidth);\n const height = baseHeight + (style.dropShadow ? style.dropShadow.distance : 0);\n\n // Use the base style's line height for the lineHeight property (for backwards compat)\n const baseLineHeight = style.lineHeight || baseFontProps.fontSize;\n\n return {\n width,\n height,\n lines,\n lineWidths,\n lineHeight: baseLineHeight + style.leading,\n maxLineWidth,\n fontProperties: baseFontProps,\n runsByLine: wrappedRunsByLine,\n lineAscents,\n lineDescents,\n lineHeights: lineHeightsArr,\n hasDropShadow,\n };\n}\n\n/**\n * Applies word wrapping to tagged text lines.\n * Breaks runs at word boundaries while maintaining style information.\n * @param runsByLine - Array of run arrays, one per line\n * @param style - The base text style\n * @param context - The canvas 2D context\n * @param measureTextFn - Function to measure text width\n * @param canBreakCharsFn - Function to check if characters can be broken\n * @param wordWrapSplitFn - Function to split words into characters\n * @returns New runsByLine array with word wrap applied\n * @internal\n */\nexport function wordWrapTaggedLines(\n runsByLine: TextStyleRun[][],\n style: TextStyle,\n context: ICanvasRenderingContext2D,\n measureTextFn: MeasureTextFn,\n canBreakCharsFn: CanBreakCharsFn,\n wordWrapSplitFn: WordWrapSplitFn,\n): TextStyleRun[][]\n{\n const { letterSpacing, whiteSpace, wordWrapWidth, breakWords } = style;\n\n // How to handle whitespaces\n const shouldCollapseSpaces = collapseSpaces(whiteSpace);\n\n // Adjust for letterSpacing (see _wordWrap for explanation)\n const adjustedWrapWidth = wordWrapWidth + letterSpacing;\n\n // Cache for token width measurements and font tracking to avoid redundant work\n const tokenWidthCache: Record<string, number> = {};\n let lastFont = '';\n\n const measureTokenWidth = (token: string, tokenStyle: TextStyle): number =>\n {\n const cacheKey = `${token}|${tokenStyle.styleKey}`;\n let width = tokenWidthCache[cacheKey];\n\n if (width === undefined)\n {\n const font = tokenStyle._fontString;\n\n if (font !== lastFont)\n {\n context.font = font;\n lastFont = font;\n }\n width = measureTextFn(token, tokenStyle.letterSpacing, context)\n + tokenStyle.letterSpacing;\n tokenWidthCache[cacheKey] = width;\n }\n\n return width;\n };\n\n const result: TextStyleRun[][] = [];\n\n // Process each line from the input\n for (const lineRuns of runsByLine)\n {\n // Tokenize all runs on this line into styled tokens\n const styledTokens = tokenizeTaggedRuns(lineRuns);\n\n // Track if we pushed content directly to result (for word groups)\n const resultStartLength = result.length;\n\n // Helper to calculate the total width of a word group starting at index\n // A word group is a sequence of tokens where each subsequent token has continuesFromPrevious: true\n const getWordGroupWidth = (startIndex: number): number =>\n {\n let totalWidth = 0;\n let j = startIndex;\n\n do\n {\n const { token: groupToken, style: groupStyle } = styledTokens[j];\n\n totalWidth += measureTokenWidth(groupToken, groupStyle);\n j++;\n }\n while (j < styledTokens.length && styledTokens[j].continuesFromPrevious);\n\n return totalWidth;\n };\n\n // Helper to get all tokens in a word group\n const getWordGroupTokens = (startIndex: number): Array<{ token: string; style: TextStyle }> =>\n {\n const tokens: Array<{ token: string; style: TextStyle }> = [];\n let j = startIndex;\n\n do\n {\n tokens.push({ token: styledTokens[j].token, style: styledTokens[j].style });\n j++;\n }\n while (j < styledTokens.length && styledTokens[j].continuesFromPrevious);\n\n return tokens;\n };\n\n // Now apply word wrap logic to these tokens\n let currentLineRuns: TextStyleRun[] = [];\n let currentWidth = 0;\n let canPrependSpaces = !shouldCollapseSpaces;\n\n // Track the current \"building\" run (to merge adjacent tokens with same style)\n let buildingRun: TextStyleRun | null = null;\n\n const flushBuildingRun = (): void =>\n {\n if (buildingRun && buildingRun.text.length > 0)\n {\n currentLineRuns.push(buildingRun);\n }\n buildingRun = null;\n };\n\n const startNewLine = (): void =>\n {\n flushBuildingRun();\n // Trim trailing spaces from last run on line\n if (currentLineRuns.length > 0)\n {\n const lastRun = currentLineRuns[currentLineRuns.length - 1];\n\n lastRun.text = trimRight(lastRun.text);\n if (lastRun.text.length === 0)\n {\n currentLineRuns.pop();\n }\n }\n result.push(currentLineRuns);\n currentLineRuns = [];\n currentWidth = 0;\n canPrependSpaces = false;\n };\n\n for (let i = 0; i < styledTokens.length; i++)\n {\n const { token, style: tokenStyle, continuesFromPrevious } = styledTokens[i];\n\n const tokenWidth = measureTokenWidth(token, tokenStyle);\n\n // Handle collapsing spaces\n if (shouldCollapseSpaces)\n {\n const currIsSpace = isBreakingSpace(token);\n const lastChar = buildingRun?.text[buildingRun.text.length - 1]\n ?? currentLineRuns[currentLineRuns.length - 1]?.text.slice(-1)\n ?? '';\n const lastIsSpace = lastChar ? isBreakingSpace(lastChar) : false;\n\n if (currIsSpace && lastIsSpace)\n {\n continue;\n }\n }\n\n // Check if this token starts a word group (not a continuation)\n const startsWordGroup = !continuesFromPrevious;\n\n // Calculate word group width if this starts a new word group\n const wordGroupWidth = startsWordGroup ? getWordGroupWidth(i) : tokenWidth;\n\n // Word group is longer than the wrap width\n if (wordGroupWidth > adjustedWrapWidth && startsWordGroup)\n {\n // Flush any existing content to a new line\n if (currentWidth > 0)\n {\n startNewLine();\n }\n\n // Break the long word group if allowed\n if (breakWords)\n {\n // Get all tokens in this word group\n const wordGroupTokens = getWordGroupTokens(i);\n\n // Process each token in the word group\n for (let g = 0; g < wordGroupTokens.length; g++)\n {\n const groupToken = wordGroupTokens[g].token;\n const groupStyle = wordGroupTokens[g].style;\n const charGroups = getCharacterGroups(\n groupToken,\n breakWords,\n wordWrapSplitFn,\n canBreakCharsFn,\n );\n\n for (const char of charGroups)\n {\n const charWidth = measureTokenWidth(char, groupStyle);\n\n // eslint-disable-next-line max-depth\n if (charWidth + currentWidth > adjustedWrapWidth)\n {\n startNewLine();\n }\n\n // Add char to building run\n // eslint-disable-next-line max-depth\n if (!buildingRun || buildingRun.style !== groupStyle)\n {\n flushBuildingRun();\n buildingRun = { text: char, style: groupStyle };\n }\n else\n {\n buildingRun.text += char;\n }\n currentWidth += charWidth;\n }\n }\n\n // Skip all the tokens we just processed\n i += wordGroupTokens.length - 1;\n }\n else\n {\n // Can't break - put whole word group on its own line\n const wordGroupTokens = getWordGroupTokens(i);\n\n flushBuildingRun();\n result.push(wordGroupTokens.map((t) => ({ text: t.token, style: t.style })));\n canPrependSpaces = false;\n\n // Skip all the tokens we just processed\n i += wordGroupTokens.length - 1;\n }\n }\n // Word group would exceed line width (but fits on its own line)\n else if (wordGroupWidth + currentWidth > adjustedWrapWidth && startsWordGroup)\n {\n // Don't start new line with just a space\n if (isBreakingSpace(token))\n {\n canPrependSpaces = false;\n continue;\n }\n\n startNewLine();\n\n // Start new building run with this token\n buildingRun = { text: token, style: tokenStyle };\n currentWidth = tokenWidth;\n }\n // Token is a continuation of a word group - always add it (don't wrap mid-word when breakWords: false)\n else if (continuesFromPrevious && !breakWords)\n {\n // Add to building run or start new one\n if (!buildingRun || buildingRun.style !== tokenStyle)\n {\n flushBuildingRun();\n buildingRun = { text: token, style: tokenStyle };\n }\n else\n {\n buildingRun.text += token;\n }\n currentWidth += tokenWidth;\n }\n // Token fits on current line (or is a continuation with breakWords: true)\n else\n {\n const isSpace = isBreakingSpace(token);\n\n // Don't prepend spaces to line start unless allowed\n if (currentWidth === 0 && isSpace && !canPrependSpaces)\n {\n continue;\n }\n\n // Add to building run or start new one\n if (!buildingRun || buildingRun.style !== tokenStyle)\n {\n flushBuildingRun();\n buildingRun = { text: token, style: tokenStyle };\n }\n else\n {\n buildingRun.text += token;\n }\n currentWidth += tokenWidth;\n }\n }\n\n // Flush final content\n flushBuildingRun();\n if (currentLineRuns.length > 0)\n {\n // Trim trailing spaces\n const lastRun = currentLineRuns[currentLineRuns.length - 1];\n\n lastRun.text = trimRight(lastRun.text);\n if (lastRun.text.length === 0)\n {\n currentLineRuns.pop();\n }\n }\n\n // Push final line if it has content, or if we haven't pushed anything yet.\n // The second condition (result.length === resultStartLength) ensures we preserve\n // explicit blank lines from the input text. Without this, a line containing only\n // whitespace would be trimmed away entirely, collapsing consecutive newlines.\n if (currentLineRuns.length > 0 || result.length === resultStartLength)\n {\n result.push(currentLineRuns);\n }\n }\n\n return result;\n}\n\n/**\n * Tokenizes an array of TextStyleRuns into individual styled tokens.\n * Each token is a word, space, or newline with its associated style.\n * Tracks whether adjacent tokens across run boundaries form a continuous word.\n * @param runs - The runs to tokenize\n * @returns Array of styled tokens with continuation flags\n * @internal\n */\nexport function tokenizeTaggedRuns(\n runs: TextStyleRun[]\n): StyledToken[]\n{\n const styledTokens: StyledToken[] = [];\n let lastTokenWasWord = false;\n\n for (const run of runs)\n {\n const tokens = tokenize(run.text);\n let isFirstTokenInRun = true;\n\n for (const token of tokens)\n {\n const isSpace = isBreakingSpace(token) || isNewline(token);\n\n // Token continues from previous word if:\n // 1. It's the first token in this run\n // 2. The previous run's last token was a word (not space)\n // 3. This token is also a word (not space)\n const continuesFromPrevious = isFirstTokenInRun && lastTokenWasWord && !isSpace;\n\n styledTokens.push({ token, style: run.style, continuesFromPrevious });\n\n lastTokenWasWord = !isSpace;\n isFirstTokenInRun = false;\n }\n }\n\n return styledTokens;\n}\n"],"names":[],"mappings":"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"}