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
JavaScript
'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