UNPKG

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">

300 lines (296 loc) 11.2 kB
'use strict'; var parseTaggedText = require('./parseTaggedText.js'); var textTokenization = require('./textTokenization.js'); "use strict"; const NEWLINE_TO_SPACE_REGEX = /\r\n|\r|\n/g; function measureTaggedText(text, style, wordWrap, context, measureTextFn, measureFontFn, canBreakCharsFn, wordWrapSplitFn) { const runs = parseTaggedText.parseTaggedText(text, style); const shouldCollapseNewlines = textTokenization.collapseNewlines(style.whiteSpace); if (shouldCollapseNewlines) { for (let i = 0; i < runs.length; i++) { const run = runs[i]; runs[i] = { text: run.text.replace(NEWLINE_TO_SPACE_REGEX, " "), style: run.style }; } } const runsByLine = []; let currentLineRuns = []; for (const run of runs) { const parts = run.text.split(textTokenization.NEWLINE_SPLIT_REGEX); for (let i = 0; i < parts.length; i++) { const part = parts[i]; if (part === "\r\n" || part === "\r" || part === "\n") { runsByLine.push(currentLineRuns); currentLineRuns = []; } else if (part.length > 0) { currentLineRuns.push({ text: part, style: run.style }); } } } if (currentLineRuns.length > 0 || runsByLine.length === 0) { runsByLine.push(currentLineRuns); } const wrappedRunsByLine = wordWrap ? wordWrapTaggedLines( runsByLine, style, context, measureTextFn, canBreakCharsFn, wordWrapSplitFn ) : runsByLine; const lineWidths = []; const lineAscents = []; const lineDescents = []; const lineHeightsArr = []; const lines = []; let maxLineWidth = 0; const baseFont = style._fontString; const baseFontProps = measureFontFn(baseFont); if (baseFontProps.fontSize === 0) { baseFontProps.fontSize = style.fontSize; baseFontProps.ascent = style.fontSize; } let lastFont = ""; let hasDropShadow = !!style.dropShadow; let maxRunStrokeWidth = style._stroke?.width || 0; for (const lineRuns of wrappedRunsByLine) { let lineWidth = 0; let lineAscent = baseFontProps.ascent; let lineDescent = baseFontProps.descent; let lineText = ""; for (const run of lineRuns) { const runFont = run.style._fontString; const runFontProps = measureFontFn(runFont); if (runFont !== lastFont) { context.font = runFont; lastFont = runFont; } const runWidth = measureTextFn(run.text, run.style.letterSpacing, context); lineWidth += runWidth; lineAscent = Math.max(lineAscent, runFontProps.ascent); lineDescent = Math.max(lineDescent, runFontProps.descent); lineText += run.text; const runStrokeWidth = run.style._stroke?.width || 0; if (runStrokeWidth > maxRunStrokeWidth) maxRunStrokeWidth = runStrokeWidth; if (!hasDropShadow && run.style.dropShadow) { hasDropShadow = true; } } if (lineRuns.length === 0) { lineAscent = baseFontProps.ascent; lineDescent = baseFontProps.descent; } lineWidths.push(lineWidth); lineAscents.push(lineAscent); lineDescents.push(lineDescent); lines.push(lineText); const computedLineHeight = style.lineHeight || lineAscent + lineDescent; lineHeightsArr.push(computedLineHeight + style.leading); maxLineWidth = Math.max(maxLineWidth, lineWidth); } const strokeWidth = maxRunStrokeWidth; const useWrapWidth = wordWrap && style.align !== "left"; const alignWidth = useWrapWidth ? Math.max(maxLineWidth, style.wordWrapWidth) : maxLineWidth; const width = alignWidth + strokeWidth + (style.dropShadow ? style.dropShadow.distance : 0); let baseHeight = 0; for (let i = 0; i < lineHeightsArr.length; i++) { baseHeight += lineHeightsArr[i]; } baseHeight = Math.max(baseHeight, lineHeightsArr[0] + strokeWidth); const height = baseHeight + (style.dropShadow ? style.dropShadow.distance : 0); const baseLineHeight = style.lineHeight || baseFontProps.fontSize; return { width, height, lines, lineWidths, lineHeight: baseLineHeight + style.leading, maxLineWidth, fontProperties: baseFontProps, runsByLine: wrappedRunsByLine, lineAscents, lineDescents, lineHeights: lineHeightsArr, hasDropShadow }; } function wordWrapTaggedLines(runsByLine, style, context, measureTextFn, canBreakCharsFn, wordWrapSplitFn) { const { letterSpacing, whiteSpace, wordWrapWidth, breakWords } = style; const shouldCollapseSpaces = textTokenization.collapseSpaces(whiteSpace); const adjustedWrapWidth = wordWrapWidth + letterSpacing; const tokenWidthCache = {}; let lastFont = ""; const measureTokenWidth = (token, tokenStyle) => { const cacheKey = `${token}|${tokenStyle.styleKey}`; let width = tokenWidthCache[cacheKey]; if (width === void 0) { const font = tokenStyle._fontString; if (font !== lastFont) { context.font = font; lastFont = font; } width = measureTextFn(token, tokenStyle.letterSpacing, context) + tokenStyle.letterSpacing; tokenWidthCache[cacheKey] = width; } return width; }; const result = []; for (const lineRuns of runsByLine) { const styledTokens = tokenizeTaggedRuns(lineRuns); const resultStartLength = result.length; const getWordGroupWidth = (startIndex) => { let totalWidth = 0; let j = startIndex; do { const { token: groupToken, style: groupStyle } = styledTokens[j]; totalWidth += measureTokenWidth(groupToken, groupStyle); j++; } while (j < styledTokens.length && styledTokens[j].continuesFromPrevious); return totalWidth; }; const getWordGroupTokens = (startIndex) => { const tokens = []; let j = startIndex; do { tokens.push({ token: styledTokens[j].token, style: styledTokens[j].style }); j++; } while (j < styledTokens.length && styledTokens[j].continuesFromPrevious); return tokens; }; let currentLineRuns = []; let currentWidth = 0; let canPrependSpaces = !shouldCollapseSpaces; let buildingRun = null; const flushBuildingRun = () => { if (buildingRun && buildingRun.text.length > 0) { currentLineRuns.push(buildingRun); } buildingRun = null; }; const startNewLine = () => { flushBuildingRun(); if (currentLineRuns.length > 0) { const lastRun = currentLineRuns[currentLineRuns.length - 1]; lastRun.text = textTokenization.trimRight(lastRun.text); if (lastRun.text.length === 0) { currentLineRuns.pop(); } } result.push(currentLineRuns); currentLineRuns = []; currentWidth = 0; canPrependSpaces = false; }; for (let i = 0; i < styledTokens.length; i++) { const { token, style: tokenStyle, continuesFromPrevious } = styledTokens[i]; const tokenWidth = measureTokenWidth(token, tokenStyle); if (shouldCollapseSpaces) { const currIsSpace = textTokenization.isBreakingSpace(token); const lastChar = buildingRun?.text[buildingRun.text.length - 1] ?? currentLineRuns[currentLineRuns.length - 1]?.text.slice(-1) ?? ""; const lastIsSpace = lastChar ? textTokenization.isBreakingSpace(lastChar) : false; if (currIsSpace && lastIsSpace) { continue; } } const startsWordGroup = !continuesFromPrevious; const wordGroupWidth = startsWordGroup ? getWordGroupWidth(i) : tokenWidth; if (wordGroupWidth > adjustedWrapWidth && startsWordGroup) { if (currentWidth > 0) { startNewLine(); } if (breakWords) { const wordGroupTokens = getWordGroupTokens(i); for (let g = 0; g < wordGroupTokens.length; g++) { const groupToken = wordGroupTokens[g].token; const groupStyle = wordGroupTokens[g].style; const charGroups = textTokenization.getCharacterGroups( groupToken, breakWords, wordWrapSplitFn, canBreakCharsFn ); for (const char of charGroups) { const charWidth = measureTokenWidth(char, groupStyle); if (charWidth + currentWidth > adjustedWrapWidth) { startNewLine(); } if (!buildingRun || buildingRun.style !== groupStyle) { flushBuildingRun(); buildingRun = { text: char, style: groupStyle }; } else { buildingRun.text += char; } currentWidth += charWidth; } } i += wordGroupTokens.length - 1; } else { const wordGroupTokens = getWordGroupTokens(i); flushBuildingRun(); result.push(wordGroupTokens.map((t) => ({ text: t.token, style: t.style }))); canPrependSpaces = false; i += wordGroupTokens.length - 1; } } else if (wordGroupWidth + currentWidth > adjustedWrapWidth && startsWordGroup) { if (textTokenization.isBreakingSpace(token)) { canPrependSpaces = false; continue; } startNewLine(); buildingRun = { text: token, style: tokenStyle }; currentWidth = tokenWidth; } else if (continuesFromPrevious && !breakWords) { if (!buildingRun || buildingRun.style !== tokenStyle) { flushBuildingRun(); buildingRun = { text: token, style: tokenStyle }; } else { buildingRun.text += token; } currentWidth += tokenWidth; } else { const isSpace = textTokenization.isBreakingSpace(token); if (currentWidth === 0 && isSpace && !canPrependSpaces) { continue; } if (!buildingRun || buildingRun.style !== tokenStyle) { flushBuildingRun(); buildingRun = { text: token, style: tokenStyle }; } else { buildingRun.text += token; } currentWidth += tokenWidth; } } flushBuildingRun(); if (currentLineRuns.length > 0) { const lastRun = currentLineRuns[currentLineRuns.length - 1]; lastRun.text = textTokenization.trimRight(lastRun.text); if (lastRun.text.length === 0) { currentLineRuns.pop(); } } if (currentLineRuns.length > 0 || result.length === resultStartLength) { result.push(currentLineRuns); } } return result; } function tokenizeTaggedRuns(runs) { const styledTokens = []; let lastTokenWasWord = false; for (const run of runs) { const tokens = textTokenization.tokenize(run.text); let isFirstTokenInRun = true; for (const token of tokens) { const isSpace = textTokenization.isBreakingSpace(token) || textTokenization.isNewline(token); const continuesFromPrevious = isFirstTokenInRun && lastTokenWasWord && !isSpace; styledTokens.push({ token, style: run.style, continuesFromPrevious }); lastTokenWasWord = !isSpace; isFirstTokenInRun = false; } } return styledTokens; } exports.measureTaggedText = measureTaggedText; exports.tokenizeTaggedRuns = tokenizeTaggedRuns; exports.wordWrapTaggedLines = wordWrapTaggedLines; //# sourceMappingURL=measureTaggedText.js.map