This commit is contained in:
Akko
2025-08-04 18:57:35 +02:00
parent 8cf6e78a79
commit 9495868c2e
5030 changed files with 518594 additions and 17609 deletions

View File

@@ -0,0 +1,246 @@
import type { ICanvas } from '../../../environment/canvas/ICanvas';
import type { ICanvasRenderingContext2D } from '../../../environment/canvas/ICanvasRenderingContext2D';
import type { TextStyle } from '../TextStyle';
/**
* A number, or a string containing a number.
* @memberof text
* @typedef {object} FontMetrics
* @property {number} ascent - Font ascent
* @property {number} descent - Font descent
* @property {number} fontSize - Font size
*/
export interface FontMetrics {
ascent: number;
descent: number;
fontSize: number;
}
/**
* The TextMetrics object represents the measurement of a block of text with a specified style.
* @example
* import { TextMetrics, TextStyle } from 'pixi.js';
*
* const style = new TextStyle({
* fontFamily: 'Arial',
* fontSize: 24,
* fill: 0xff1010,
* align: 'center',
* });
* const textMetrics = TextMetrics.measureText('Your text', style);
* @memberof text
*/
export declare class CanvasTextMetrics {
/** The text that was measured. */
text: string;
/** The style that was measured. */
style: TextStyle;
/** The measured width of the text. */
width: number;
/** The measured height of the text. */
height: number;
/** An array of lines of the text broken by new lines and wrapping is specified in style. */
lines: string[];
/** An array of the line widths for each line matched to `lines`. */
lineWidths: number[];
/** The measured line height for this style. */
lineHeight: number;
/** The maximum line width for all measured lines. */
maxLineWidth: number;
/** The font properties object from TextMetrics.measureFont. */
fontProperties: FontMetrics;
/**
* String used for calculate font metrics.
* These characters are all tall to help calculate the height required for text.
*/
static METRICS_STRING: string;
/** Baseline symbol for calculate font metrics. */
static BASELINE_SYMBOL: string;
/** Baseline multiplier for calculate font metrics. */
static BASELINE_MULTIPLIER: number;
/** Height multiplier for setting height of canvas to calculate font metrics. */
static HEIGHT_MULTIPLIER: number;
/**
* A Unicode "character", or "grapheme cluster", can be composed of multiple Unicode code points,
* such as letters with diacritical marks (e.g. `'\u0065\u0301'`, letter e with acute)
* or emojis with modifiers (e.g. `'\uD83E\uDDD1\u200D\uD83D\uDCBB'`, technologist).
* The new `Intl.Segmenter` API in ES2022 can split the string into grapheme clusters correctly. If it is not available,
* PixiJS will fallback to use the iterator of String, which can only spilt the string into code points.
* If you want to get full functionality in environments that don't support `Intl.Segmenter` (such as Firefox),
* you can use other libraries such as [grapheme-splitter]{@link https://www.npmjs.com/package/grapheme-splitter}
* or [graphemer]{@link https://www.npmjs.com/package/graphemer} to create a polyfill. Since these libraries can be
* relatively large in size to handle various Unicode grapheme clusters properly, PixiJS won't use them directly.
*/
static graphemeSegmenter: (s: string) => string[];
static _experimentalLetterSpacingSupported?: boolean;
/**
* Checking that we can use modern canvas 2D API.
*
* Note: This is an unstable API, Chrome < 94 use `textLetterSpacing`, later versions use `letterSpacing`.
* @see TextMetrics.experimentalLetterSpacing
* @see https://developer.mozilla.org/en-US/docs/Web/API/ICanvasRenderingContext2D/letterSpacing
* @see https://developer.chrome.com/origintrials/#/view_trial/3585991203293757441
*/
static get experimentalLetterSpacingSupported(): boolean;
/**
* New rendering behavior for letter-spacing which uses Chrome's new native API. This will
* lead to more accurate letter-spacing results because it does not try to manually draw
* each character. However, this Chrome API is experimental and may not serve all cases yet.
* @see TextMetrics.experimentalLetterSpacingSupported
*/
static experimentalLetterSpacing: boolean;
/** Cache of {@see TextMetrics.FontMetrics} objects. */
private static _fonts;
/** Cache of new line chars. */
private static readonly _newlines;
/** Cache of breaking spaces. */
private static readonly _breakingSpaces;
private static __canvas;
private static __context;
private static readonly _measurementCache;
/**
* @param text - the text that was measured
* @param style - the style that was measured
* @param width - the measured width of the text
* @param height - the measured height of the text
* @param lines - an array of the lines of text broken by new lines and wrapping if specified in style
* @param lineWidths - an array of the line widths for each line matched to `lines`
* @param lineHeight - the measured line height for this style
* @param maxLineWidth - the maximum line width for all measured lines
* @param {FontMetrics} fontProperties - the font properties object from TextMetrics.measureFont
*/
constructor(text: string, style: TextStyle, width: number, height: number, lines: string[], lineWidths: number[], lineHeight: number, maxLineWidth: number, fontProperties: FontMetrics);
/**
* Measures the supplied string of text and returns a Rectangle.
* @param text - The text to measure.
* @param style - The text style to use for measuring
* @param canvas - optional specification of the canvas to use for measuring.
* @param wordWrap
* @returns Measured width and height of the text.
*/
static measureText(text: string, style: TextStyle, canvas?: ICanvas, wordWrap?: boolean): CanvasTextMetrics;
private static _measureText;
/**
* Applies newlines to a string to have it optimally fit into the horizontal
* bounds set by the Text object's wordWrapWidth property.
* @param text - String to apply word wrapping to
* @param style - the style to use when wrapping
* @param canvas - optional specification of the canvas to use for measuring.
* @returns New string with new lines applied where required
*/
private static _wordWrap;
/**
* Convenience function for logging each line added during the wordWrap method.
* @param line - The line of text to add
* @param newLine - Add new line character to end
* @returns A formatted line
*/
private static _addLine;
/**
* Gets & sets the widths of calculated characters in a cache object
* @param key - The key
* @param letterSpacing - The letter spacing
* @param cache - The cache
* @param context - The canvas context
* @returns The from cache.
*/
private static _getFromCache;
/**
* Determines whether we should collapse breaking spaces.
* @param whiteSpace - The TextStyle property whiteSpace
* @returns Should collapse
*/
private static _collapseSpaces;
/**
* Determines whether we should collapse newLine chars.
* @param whiteSpace - The white space
* @returns should collapse
*/
private static _collapseNewlines;
/**
* Trims breaking whitespaces from string.
* @param text - The text
* @returns Trimmed string
*/
private static _trimRight;
/**
* Determines if char is a newline.
* @param char - The character
* @returns True if newline, False otherwise.
*/
private static _isNewline;
/**
* Determines if char is a breaking whitespace.
*
* It allows one to determine whether char should be a breaking whitespace
* For example certain characters in CJK langs or numbers.
* It must return a boolean.
* @param char - The character
* @param [_nextChar] - The next character
* @returns True if whitespace, False otherwise.
*/
static isBreakingSpace(char: string, _nextChar?: string): boolean;
/**
* Splits a string into words, breaking-spaces and newLine characters
* @param text - The text
* @returns A tokenized array
*/
private static _tokenize;
/**
* Overridable helper method used internally by TextMetrics, exposed to allow customizing the class's behavior.
*
* It allows one to customise which words should break
* Examples are if the token is CJK or numbers.
* It must return a boolean.
* @param _token - The token
* @param breakWords - The style attr break words
* @returns Whether to break word or not
*/
static canBreakWords(_token: string, breakWords: boolean): boolean;
/**
* Overridable helper method used internally by TextMetrics, exposed to allow customizing the class's behavior.
*
* It allows one to determine whether a pair of characters
* should be broken by newlines
* For example certain characters in CJK langs or numbers.
* It must return a boolean.
* @param _char - The character
* @param _nextChar - The next character
* @param _token - The token/word the characters are from
* @param _index - The index in the token of the char
* @param _breakWords - The style attr break words
* @returns whether to break word or not
*/
static canBreakChars(_char: string, _nextChar: string, _token: string, _index: number, _breakWords: boolean): boolean;
/**
* Overridable helper method used internally by TextMetrics, exposed to allow customizing the class's behavior.
*
* It is called when a token (usually a word) has to be split into separate pieces
* in order to determine the point to break a word.
* It must return an array of characters.
* @param token - The token to split
* @returns The characters of the token
* @see CanvasTextMetrics.graphemeSegmenter
*/
static wordWrapSplit(token: string): string[];
/**
* Calculates the ascent, descent and fontSize of a given font-style
* @param font - String representing the style of the font
* @returns Font properties object
*/
static measureFont(font: string): FontMetrics;
/**
* Clear font metrics in metrics cache.
* @param {string} [font] - font name. If font name not set then clear cache for all fonts.
*/
static clearMetrics(font?: string): void;
/**
* Cached canvas element for measuring text
* TODO: this should be private, but isn't because of backward compat, will fix later.
* @ignore
*/
static get _canvas(): ICanvas;
/**
* TODO: this should be private, but isn't because of backward compat, will fix later.
* @ignore
*/
static get _context(): ICanvasRenderingContext2D;
}

View File

@@ -0,0 +1,532 @@
'use strict';
var adapter = require('../../../environment/adapter.js');
var fontStringFromTextStyle = require('./utils/fontStringFromTextStyle.js');
"use strict";
const contextSettings = {
// TextMetrics requires getImageData readback for measuring fonts.
willReadFrequently: true
};
const _CanvasTextMetrics = class _CanvasTextMetrics {
/**
* Checking that we can use modern canvas 2D API.
*
* Note: This is an unstable API, Chrome < 94 use `textLetterSpacing`, later versions use `letterSpacing`.
* @see TextMetrics.experimentalLetterSpacing
* @see https://developer.mozilla.org/en-US/docs/Web/API/ICanvasRenderingContext2D/letterSpacing
* @see https://developer.chrome.com/origintrials/#/view_trial/3585991203293757441
*/
static get experimentalLetterSpacingSupported() {
let result = _CanvasTextMetrics._experimentalLetterSpacingSupported;
if (result !== void 0) {
const proto = adapter.DOMAdapter.get().getCanvasRenderingContext2D().prototype;
result = _CanvasTextMetrics._experimentalLetterSpacingSupported = "letterSpacing" in proto || "textLetterSpacing" in proto;
}
return result;
}
/**
* @param text - the text that was measured
* @param style - the style that was measured
* @param width - the measured width of the text
* @param height - the measured height of the text
* @param lines - an array of the lines of text broken by new lines and wrapping if specified in style
* @param lineWidths - an array of the line widths for each line matched to `lines`
* @param lineHeight - the measured line height for this style
* @param maxLineWidth - the maximum line width for all measured lines
* @param {FontMetrics} fontProperties - the font properties object from TextMetrics.measureFont
*/
constructor(text, style, width, height, lines, lineWidths, lineHeight, maxLineWidth, fontProperties) {
this.text = text;
this.style = style;
this.width = width;
this.height = height;
this.lines = lines;
this.lineWidths = lineWidths;
this.lineHeight = lineHeight;
this.maxLineWidth = maxLineWidth;
this.fontProperties = fontProperties;
}
/**
* Measures the supplied string of text and returns a Rectangle.
* @param text - The text to measure.
* @param style - The text style to use for measuring
* @param canvas - optional specification of the canvas to use for measuring.
* @param wordWrap
* @returns Measured width and height of the text.
*/
static measureText(text = " ", style, canvas = _CanvasTextMetrics._canvas, wordWrap = style.wordWrap) {
const textKey = `${text}:${style.styleKey}`;
if (_CanvasTextMetrics._measurementCache[textKey])
return _CanvasTextMetrics._measurementCache[textKey];
const font = fontStringFromTextStyle.fontStringFromTextStyle(style);
const fontProperties = _CanvasTextMetrics.measureFont(font);
if (fontProperties.fontSize === 0) {
fontProperties.fontSize = style.fontSize;
fontProperties.ascent = style.fontSize;
}
const context = _CanvasTextMetrics.__context;
context.font = font;
const outputText = wordWrap ? _CanvasTextMetrics._wordWrap(text, style, canvas) : text;
const lines = outputText.split(/(?:\r\n|\r|\n)/);
const lineWidths = new Array(lines.length);
let maxLineWidth = 0;
for (let i = 0; i < lines.length; i++) {
const lineWidth = _CanvasTextMetrics._measureText(lines[i], style.letterSpacing, context);
lineWidths[i] = lineWidth;
maxLineWidth = Math.max(maxLineWidth, lineWidth);
}
const strokeWidth = style._stroke?.width || 0;
let width = maxLineWidth + strokeWidth;
if (style.dropShadow) {
width += style.dropShadow.distance;
}
const lineHeight = style.lineHeight || fontProperties.fontSize;
let height = Math.max(lineHeight, fontProperties.fontSize + strokeWidth) + (lines.length - 1) * (lineHeight + style.leading);
if (style.dropShadow) {
height += style.dropShadow.distance;
}
const measurements = new _CanvasTextMetrics(
text,
style,
width,
height,
lines,
lineWidths,
lineHeight + style.leading,
maxLineWidth,
fontProperties
);
return measurements;
}
static _measureText(text, letterSpacing, context) {
let useExperimentalLetterSpacing = false;
if (_CanvasTextMetrics.experimentalLetterSpacingSupported) {
if (_CanvasTextMetrics.experimentalLetterSpacing) {
context.letterSpacing = `${letterSpacing}px`;
context.textLetterSpacing = `${letterSpacing}px`;
useExperimentalLetterSpacing = true;
} else {
context.letterSpacing = "0px";
context.textLetterSpacing = "0px";
}
}
let width = context.measureText(text).width;
if (width > 0) {
if (useExperimentalLetterSpacing) {
width -= letterSpacing;
} else {
width += (_CanvasTextMetrics.graphemeSegmenter(text).length - 1) * letterSpacing;
}
}
return width;
}
/**
* Applies newlines to a string to have it optimally fit into the horizontal
* bounds set by the Text object's wordWrapWidth property.
* @param text - String to apply word wrapping to
* @param style - the style to use when wrapping
* @param canvas - optional specification of the canvas to use for measuring.
* @returns New string with new lines applied where required
*/
static _wordWrap(text, style, canvas = _CanvasTextMetrics._canvas) {
const context = canvas.getContext("2d", contextSettings);
let width = 0;
let line = "";
let lines = "";
const cache = /* @__PURE__ */ Object.create(null);
const { letterSpacing, whiteSpace } = style;
const collapseSpaces = _CanvasTextMetrics._collapseSpaces(whiteSpace);
const collapseNewlines = _CanvasTextMetrics._collapseNewlines(whiteSpace);
let canPrependSpaces = !collapseSpaces;
const wordWrapWidth = style.wordWrapWidth + letterSpacing;
const tokens = _CanvasTextMetrics._tokenize(text);
for (let i = 0; i < tokens.length; i++) {
let token = tokens[i];
if (_CanvasTextMetrics._isNewline(token)) {
if (!collapseNewlines) {
lines += _CanvasTextMetrics._addLine(line);
canPrependSpaces = !collapseSpaces;
line = "";
width = 0;
continue;
}
token = " ";
}
if (collapseSpaces) {
const currIsBreakingSpace = _CanvasTextMetrics.isBreakingSpace(token);
const lastIsBreakingSpace = _CanvasTextMetrics.isBreakingSpace(line[line.length - 1]);
if (currIsBreakingSpace && lastIsBreakingSpace) {
continue;
}
}
const tokenWidth = _CanvasTextMetrics._getFromCache(token, letterSpacing, cache, context);
if (tokenWidth > wordWrapWidth) {
if (line !== "") {
lines += _CanvasTextMetrics._addLine(line);
line = "";
width = 0;
}
if (_CanvasTextMetrics.canBreakWords(token, style.breakWords)) {
const characters = _CanvasTextMetrics.wordWrapSplit(token);
for (let j = 0; j < characters.length; j++) {
let char = characters[j];
let lastChar = char;
let k = 1;
while (characters[j + k]) {
const nextChar = characters[j + k];
if (!_CanvasTextMetrics.canBreakChars(lastChar, nextChar, token, j, style.breakWords)) {
char += nextChar;
} else {
break;
}
lastChar = nextChar;
k++;
}
j += k - 1;
const characterWidth = _CanvasTextMetrics._getFromCache(char, letterSpacing, cache, context);
if (characterWidth + width > wordWrapWidth) {
lines += _CanvasTextMetrics._addLine(line);
canPrependSpaces = false;
line = "";
width = 0;
}
line += char;
width += characterWidth;
}
} else {
if (line.length > 0) {
lines += _CanvasTextMetrics._addLine(line);
line = "";
width = 0;
}
const isLastToken = i === tokens.length - 1;
lines += _CanvasTextMetrics._addLine(token, !isLastToken);
canPrependSpaces = false;
line = "";
width = 0;
}
} else {
if (tokenWidth + width > wordWrapWidth) {
canPrependSpaces = false;
lines += _CanvasTextMetrics._addLine(line);
line = "";
width = 0;
}
if (line.length > 0 || !_CanvasTextMetrics.isBreakingSpace(token) || canPrependSpaces) {
line += token;
width += tokenWidth;
}
}
}
lines += _CanvasTextMetrics._addLine(line, false);
return lines;
}
/**
* Convenience function for logging each line added during the wordWrap method.
* @param line - The line of text to add
* @param newLine - Add new line character to end
* @returns A formatted line
*/
static _addLine(line, newLine = true) {
line = _CanvasTextMetrics._trimRight(line);
line = newLine ? `${line}
` : line;
return line;
}
/**
* Gets & sets the widths of calculated characters in a cache object
* @param key - The key
* @param letterSpacing - The letter spacing
* @param cache - The cache
* @param context - The canvas context
* @returns The from cache.
*/
static _getFromCache(key, letterSpacing, cache, context) {
let width = cache[key];
if (typeof width !== "number") {
width = _CanvasTextMetrics._measureText(key, letterSpacing, context) + letterSpacing;
cache[key] = width;
}
return width;
}
/**
* Determines whether we should collapse breaking spaces.
* @param whiteSpace - The TextStyle property whiteSpace
* @returns Should collapse
*/
static _collapseSpaces(whiteSpace) {
return whiteSpace === "normal" || whiteSpace === "pre-line";
}
/**
* Determines whether we should collapse newLine chars.
* @param whiteSpace - The white space
* @returns should collapse
*/
static _collapseNewlines(whiteSpace) {
return whiteSpace === "normal";
}
/**
* Trims breaking whitespaces from string.
* @param text - The text
* @returns Trimmed string
*/
static _trimRight(text) {
if (typeof text !== "string") {
return "";
}
for (let i = text.length - 1; i >= 0; i--) {
const char = text[i];
if (!_CanvasTextMetrics.isBreakingSpace(char)) {
break;
}
text = text.slice(0, -1);
}
return text;
}
/**
* Determines if char is a newline.
* @param char - The character
* @returns True if newline, False otherwise.
*/
static _isNewline(char) {
if (typeof char !== "string") {
return false;
}
return _CanvasTextMetrics._newlines.includes(char.charCodeAt(0));
}
/**
* Determines if char is a breaking whitespace.
*
* It allows one to determine whether char should be a breaking whitespace
* For example certain characters in CJK langs or numbers.
* It must return a boolean.
* @param char - The character
* @param [_nextChar] - The next character
* @returns True if whitespace, False otherwise.
*/
static isBreakingSpace(char, _nextChar) {
if (typeof char !== "string") {
return false;
}
return _CanvasTextMetrics._breakingSpaces.includes(char.charCodeAt(0));
}
/**
* Splits a string into words, breaking-spaces and newLine characters
* @param text - The text
* @returns A tokenized array
*/
static _tokenize(text) {
const tokens = [];
let token = "";
if (typeof text !== "string") {
return tokens;
}
for (let i = 0; i < text.length; i++) {
const char = text[i];
const nextChar = text[i + 1];
if (_CanvasTextMetrics.isBreakingSpace(char, nextChar) || _CanvasTextMetrics._isNewline(char)) {
if (token !== "") {
tokens.push(token);
token = "";
}
tokens.push(char);
continue;
}
token += char;
}
if (token !== "") {
tokens.push(token);
}
return tokens;
}
/**
* Overridable helper method used internally by TextMetrics, exposed to allow customizing the class's behavior.
*
* It allows one to customise which words should break
* Examples are if the token is CJK or numbers.
* It must return a boolean.
* @param _token - The token
* @param breakWords - The style attr break words
* @returns Whether to break word or not
*/
static canBreakWords(_token, breakWords) {
return breakWords;
}
/**
* Overridable helper method used internally by TextMetrics, exposed to allow customizing the class's behavior.
*
* It allows one to determine whether a pair of characters
* should be broken by newlines
* For example certain characters in CJK langs or numbers.
* It must return a boolean.
* @param _char - The character
* @param _nextChar - The next character
* @param _token - The token/word the characters are from
* @param _index - The index in the token of the char
* @param _breakWords - The style attr break words
* @returns whether to break word or not
*/
static canBreakChars(_char, _nextChar, _token, _index, _breakWords) {
return true;
}
/**
* Overridable helper method used internally by TextMetrics, exposed to allow customizing the class's behavior.
*
* It is called when a token (usually a word) has to be split into separate pieces
* in order to determine the point to break a word.
* It must return an array of characters.
* @param token - The token to split
* @returns The characters of the token
* @see CanvasTextMetrics.graphemeSegmenter
*/
static wordWrapSplit(token) {
return _CanvasTextMetrics.graphemeSegmenter(token);
}
/**
* Calculates the ascent, descent and fontSize of a given font-style
* @param font - String representing the style of the font
* @returns Font properties object
*/
static measureFont(font) {
if (_CanvasTextMetrics._fonts[font]) {
return _CanvasTextMetrics._fonts[font];
}
const context = _CanvasTextMetrics._context;
context.font = font;
const metrics = context.measureText(_CanvasTextMetrics.METRICS_STRING + _CanvasTextMetrics.BASELINE_SYMBOL);
const properties = {
ascent: metrics.actualBoundingBoxAscent,
descent: metrics.actualBoundingBoxDescent,
fontSize: metrics.actualBoundingBoxAscent + metrics.actualBoundingBoxDescent
};
_CanvasTextMetrics._fonts[font] = properties;
return properties;
}
/**
* Clear font metrics in metrics cache.
* @param {string} [font] - font name. If font name not set then clear cache for all fonts.
*/
static clearMetrics(font = "") {
if (font) {
delete _CanvasTextMetrics._fonts[font];
} else {
_CanvasTextMetrics._fonts = {};
}
}
/**
* Cached canvas element for measuring text
* TODO: this should be private, but isn't because of backward compat, will fix later.
* @ignore
*/
static get _canvas() {
if (!_CanvasTextMetrics.__canvas) {
let canvas;
try {
const c = new OffscreenCanvas(0, 0);
const context = c.getContext("2d", contextSettings);
if (context?.measureText) {
_CanvasTextMetrics.__canvas = c;
return c;
}
canvas = adapter.DOMAdapter.get().createCanvas();
} catch (ex) {
canvas = adapter.DOMAdapter.get().createCanvas();
}
canvas.width = canvas.height = 10;
_CanvasTextMetrics.__canvas = canvas;
}
return _CanvasTextMetrics.__canvas;
}
/**
* TODO: this should be private, but isn't because of backward compat, will fix later.
* @ignore
*/
static get _context() {
if (!_CanvasTextMetrics.__context) {
_CanvasTextMetrics.__context = _CanvasTextMetrics._canvas.getContext("2d", contextSettings);
}
return _CanvasTextMetrics.__context;
}
};
/**
* String used for calculate font metrics.
* These characters are all tall to help calculate the height required for text.
*/
_CanvasTextMetrics.METRICS_STRING = "|\xC9q\xC5";
/** Baseline symbol for calculate font metrics. */
_CanvasTextMetrics.BASELINE_SYMBOL = "M";
/** Baseline multiplier for calculate font metrics. */
_CanvasTextMetrics.BASELINE_MULTIPLIER = 1.4;
/** Height multiplier for setting height of canvas to calculate font metrics. */
_CanvasTextMetrics.HEIGHT_MULTIPLIER = 2;
/**
* A Unicode "character", or "grapheme cluster", can be composed of multiple Unicode code points,
* such as letters with diacritical marks (e.g. `'\u0065\u0301'`, letter e with acute)
* or emojis with modifiers (e.g. `'\uD83E\uDDD1\u200D\uD83D\uDCBB'`, technologist).
* The new `Intl.Segmenter` API in ES2022 can split the string into grapheme clusters correctly. If it is not available,
* PixiJS will fallback to use the iterator of String, which can only spilt the string into code points.
* If you want to get full functionality in environments that don't support `Intl.Segmenter` (such as Firefox),
* you can use other libraries such as [grapheme-splitter]{@link https://www.npmjs.com/package/grapheme-splitter}
* or [graphemer]{@link https://www.npmjs.com/package/graphemer} to create a polyfill. Since these libraries can be
* relatively large in size to handle various Unicode grapheme clusters properly, PixiJS won't use them directly.
*/
_CanvasTextMetrics.graphemeSegmenter = (() => {
if (typeof Intl?.Segmenter === "function") {
const segmenter = new Intl.Segmenter();
return (s) => [...segmenter.segment(s)].map((x) => x.segment);
}
return (s) => [...s];
})();
/**
* New rendering behavior for letter-spacing which uses Chrome's new native API. This will
* lead to more accurate letter-spacing results because it does not try to manually draw
* each character. However, this Chrome API is experimental and may not serve all cases yet.
* @see TextMetrics.experimentalLetterSpacingSupported
*/
_CanvasTextMetrics.experimentalLetterSpacing = false;
/** Cache of {@see TextMetrics.FontMetrics} objects. */
_CanvasTextMetrics._fonts = {};
/** Cache of new line chars. */
_CanvasTextMetrics._newlines = [
10,
// line feed
13
// carriage return
];
/** Cache of breaking spaces. */
_CanvasTextMetrics._breakingSpaces = [
9,
// character tabulation
32,
// space
8192,
// en quad
8193,
// em quad
8194,
// en space
8195,
// em space
8196,
// three-per-em space
8197,
// four-per-em space
8198,
// six-per-em space
8200,
// punctuation space
8201,
// thin space
8202,
// hair space
8287,
// medium mathematical space
12288
// ideographic space
];
_CanvasTextMetrics._measurementCache = {};
let CanvasTextMetrics = _CanvasTextMetrics;
exports.CanvasTextMetrics = CanvasTextMetrics;
//# sourceMappingURL=CanvasTextMetrics.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,530 @@
import { DOMAdapter } from '../../../environment/adapter.mjs';
import { fontStringFromTextStyle } from './utils/fontStringFromTextStyle.mjs';
"use strict";
const contextSettings = {
// TextMetrics requires getImageData readback for measuring fonts.
willReadFrequently: true
};
const _CanvasTextMetrics = class _CanvasTextMetrics {
/**
* Checking that we can use modern canvas 2D API.
*
* Note: This is an unstable API, Chrome < 94 use `textLetterSpacing`, later versions use `letterSpacing`.
* @see TextMetrics.experimentalLetterSpacing
* @see https://developer.mozilla.org/en-US/docs/Web/API/ICanvasRenderingContext2D/letterSpacing
* @see https://developer.chrome.com/origintrials/#/view_trial/3585991203293757441
*/
static get experimentalLetterSpacingSupported() {
let result = _CanvasTextMetrics._experimentalLetterSpacingSupported;
if (result !== void 0) {
const proto = DOMAdapter.get().getCanvasRenderingContext2D().prototype;
result = _CanvasTextMetrics._experimentalLetterSpacingSupported = "letterSpacing" in proto || "textLetterSpacing" in proto;
}
return result;
}
/**
* @param text - the text that was measured
* @param style - the style that was measured
* @param width - the measured width of the text
* @param height - the measured height of the text
* @param lines - an array of the lines of text broken by new lines and wrapping if specified in style
* @param lineWidths - an array of the line widths for each line matched to `lines`
* @param lineHeight - the measured line height for this style
* @param maxLineWidth - the maximum line width for all measured lines
* @param {FontMetrics} fontProperties - the font properties object from TextMetrics.measureFont
*/
constructor(text, style, width, height, lines, lineWidths, lineHeight, maxLineWidth, fontProperties) {
this.text = text;
this.style = style;
this.width = width;
this.height = height;
this.lines = lines;
this.lineWidths = lineWidths;
this.lineHeight = lineHeight;
this.maxLineWidth = maxLineWidth;
this.fontProperties = fontProperties;
}
/**
* Measures the supplied string of text and returns a Rectangle.
* @param text - The text to measure.
* @param style - The text style to use for measuring
* @param canvas - optional specification of the canvas to use for measuring.
* @param wordWrap
* @returns Measured width and height of the text.
*/
static measureText(text = " ", style, canvas = _CanvasTextMetrics._canvas, wordWrap = style.wordWrap) {
const textKey = `${text}:${style.styleKey}`;
if (_CanvasTextMetrics._measurementCache[textKey])
return _CanvasTextMetrics._measurementCache[textKey];
const font = fontStringFromTextStyle(style);
const fontProperties = _CanvasTextMetrics.measureFont(font);
if (fontProperties.fontSize === 0) {
fontProperties.fontSize = style.fontSize;
fontProperties.ascent = style.fontSize;
}
const context = _CanvasTextMetrics.__context;
context.font = font;
const outputText = wordWrap ? _CanvasTextMetrics._wordWrap(text, style, canvas) : text;
const lines = outputText.split(/(?:\r\n|\r|\n)/);
const lineWidths = new Array(lines.length);
let maxLineWidth = 0;
for (let i = 0; i < lines.length; i++) {
const lineWidth = _CanvasTextMetrics._measureText(lines[i], style.letterSpacing, context);
lineWidths[i] = lineWidth;
maxLineWidth = Math.max(maxLineWidth, lineWidth);
}
const strokeWidth = style._stroke?.width || 0;
let width = maxLineWidth + strokeWidth;
if (style.dropShadow) {
width += style.dropShadow.distance;
}
const lineHeight = style.lineHeight || fontProperties.fontSize;
let height = Math.max(lineHeight, fontProperties.fontSize + strokeWidth) + (lines.length - 1) * (lineHeight + style.leading);
if (style.dropShadow) {
height += style.dropShadow.distance;
}
const measurements = new _CanvasTextMetrics(
text,
style,
width,
height,
lines,
lineWidths,
lineHeight + style.leading,
maxLineWidth,
fontProperties
);
return measurements;
}
static _measureText(text, letterSpacing, context) {
let useExperimentalLetterSpacing = false;
if (_CanvasTextMetrics.experimentalLetterSpacingSupported) {
if (_CanvasTextMetrics.experimentalLetterSpacing) {
context.letterSpacing = `${letterSpacing}px`;
context.textLetterSpacing = `${letterSpacing}px`;
useExperimentalLetterSpacing = true;
} else {
context.letterSpacing = "0px";
context.textLetterSpacing = "0px";
}
}
let width = context.measureText(text).width;
if (width > 0) {
if (useExperimentalLetterSpacing) {
width -= letterSpacing;
} else {
width += (_CanvasTextMetrics.graphemeSegmenter(text).length - 1) * letterSpacing;
}
}
return width;
}
/**
* Applies newlines to a string to have it optimally fit into the horizontal
* bounds set by the Text object's wordWrapWidth property.
* @param text - String to apply word wrapping to
* @param style - the style to use when wrapping
* @param canvas - optional specification of the canvas to use for measuring.
* @returns New string with new lines applied where required
*/
static _wordWrap(text, style, canvas = _CanvasTextMetrics._canvas) {
const context = canvas.getContext("2d", contextSettings);
let width = 0;
let line = "";
let lines = "";
const cache = /* @__PURE__ */ Object.create(null);
const { letterSpacing, whiteSpace } = style;
const collapseSpaces = _CanvasTextMetrics._collapseSpaces(whiteSpace);
const collapseNewlines = _CanvasTextMetrics._collapseNewlines(whiteSpace);
let canPrependSpaces = !collapseSpaces;
const wordWrapWidth = style.wordWrapWidth + letterSpacing;
const tokens = _CanvasTextMetrics._tokenize(text);
for (let i = 0; i < tokens.length; i++) {
let token = tokens[i];
if (_CanvasTextMetrics._isNewline(token)) {
if (!collapseNewlines) {
lines += _CanvasTextMetrics._addLine(line);
canPrependSpaces = !collapseSpaces;
line = "";
width = 0;
continue;
}
token = " ";
}
if (collapseSpaces) {
const currIsBreakingSpace = _CanvasTextMetrics.isBreakingSpace(token);
const lastIsBreakingSpace = _CanvasTextMetrics.isBreakingSpace(line[line.length - 1]);
if (currIsBreakingSpace && lastIsBreakingSpace) {
continue;
}
}
const tokenWidth = _CanvasTextMetrics._getFromCache(token, letterSpacing, cache, context);
if (tokenWidth > wordWrapWidth) {
if (line !== "") {
lines += _CanvasTextMetrics._addLine(line);
line = "";
width = 0;
}
if (_CanvasTextMetrics.canBreakWords(token, style.breakWords)) {
const characters = _CanvasTextMetrics.wordWrapSplit(token);
for (let j = 0; j < characters.length; j++) {
let char = characters[j];
let lastChar = char;
let k = 1;
while (characters[j + k]) {
const nextChar = characters[j + k];
if (!_CanvasTextMetrics.canBreakChars(lastChar, nextChar, token, j, style.breakWords)) {
char += nextChar;
} else {
break;
}
lastChar = nextChar;
k++;
}
j += k - 1;
const characterWidth = _CanvasTextMetrics._getFromCache(char, letterSpacing, cache, context);
if (characterWidth + width > wordWrapWidth) {
lines += _CanvasTextMetrics._addLine(line);
canPrependSpaces = false;
line = "";
width = 0;
}
line += char;
width += characterWidth;
}
} else {
if (line.length > 0) {
lines += _CanvasTextMetrics._addLine(line);
line = "";
width = 0;
}
const isLastToken = i === tokens.length - 1;
lines += _CanvasTextMetrics._addLine(token, !isLastToken);
canPrependSpaces = false;
line = "";
width = 0;
}
} else {
if (tokenWidth + width > wordWrapWidth) {
canPrependSpaces = false;
lines += _CanvasTextMetrics._addLine(line);
line = "";
width = 0;
}
if (line.length > 0 || !_CanvasTextMetrics.isBreakingSpace(token) || canPrependSpaces) {
line += token;
width += tokenWidth;
}
}
}
lines += _CanvasTextMetrics._addLine(line, false);
return lines;
}
/**
* Convenience function for logging each line added during the wordWrap method.
* @param line - The line of text to add
* @param newLine - Add new line character to end
* @returns A formatted line
*/
static _addLine(line, newLine = true) {
line = _CanvasTextMetrics._trimRight(line);
line = newLine ? `${line}
` : line;
return line;
}
/**
* Gets & sets the widths of calculated characters in a cache object
* @param key - The key
* @param letterSpacing - The letter spacing
* @param cache - The cache
* @param context - The canvas context
* @returns The from cache.
*/
static _getFromCache(key, letterSpacing, cache, context) {
let width = cache[key];
if (typeof width !== "number") {
width = _CanvasTextMetrics._measureText(key, letterSpacing, context) + letterSpacing;
cache[key] = width;
}
return width;
}
/**
* Determines whether we should collapse breaking spaces.
* @param whiteSpace - The TextStyle property whiteSpace
* @returns Should collapse
*/
static _collapseSpaces(whiteSpace) {
return whiteSpace === "normal" || whiteSpace === "pre-line";
}
/**
* Determines whether we should collapse newLine chars.
* @param whiteSpace - The white space
* @returns should collapse
*/
static _collapseNewlines(whiteSpace) {
return whiteSpace === "normal";
}
/**
* Trims breaking whitespaces from string.
* @param text - The text
* @returns Trimmed string
*/
static _trimRight(text) {
if (typeof text !== "string") {
return "";
}
for (let i = text.length - 1; i >= 0; i--) {
const char = text[i];
if (!_CanvasTextMetrics.isBreakingSpace(char)) {
break;
}
text = text.slice(0, -1);
}
return text;
}
/**
* Determines if char is a newline.
* @param char - The character
* @returns True if newline, False otherwise.
*/
static _isNewline(char) {
if (typeof char !== "string") {
return false;
}
return _CanvasTextMetrics._newlines.includes(char.charCodeAt(0));
}
/**
* Determines if char is a breaking whitespace.
*
* It allows one to determine whether char should be a breaking whitespace
* For example certain characters in CJK langs or numbers.
* It must return a boolean.
* @param char - The character
* @param [_nextChar] - The next character
* @returns True if whitespace, False otherwise.
*/
static isBreakingSpace(char, _nextChar) {
if (typeof char !== "string") {
return false;
}
return _CanvasTextMetrics._breakingSpaces.includes(char.charCodeAt(0));
}
/**
* Splits a string into words, breaking-spaces and newLine characters
* @param text - The text
* @returns A tokenized array
*/
static _tokenize(text) {
const tokens = [];
let token = "";
if (typeof text !== "string") {
return tokens;
}
for (let i = 0; i < text.length; i++) {
const char = text[i];
const nextChar = text[i + 1];
if (_CanvasTextMetrics.isBreakingSpace(char, nextChar) || _CanvasTextMetrics._isNewline(char)) {
if (token !== "") {
tokens.push(token);
token = "";
}
tokens.push(char);
continue;
}
token += char;
}
if (token !== "") {
tokens.push(token);
}
return tokens;
}
/**
* Overridable helper method used internally by TextMetrics, exposed to allow customizing the class's behavior.
*
* It allows one to customise which words should break
* Examples are if the token is CJK or numbers.
* It must return a boolean.
* @param _token - The token
* @param breakWords - The style attr break words
* @returns Whether to break word or not
*/
static canBreakWords(_token, breakWords) {
return breakWords;
}
/**
* Overridable helper method used internally by TextMetrics, exposed to allow customizing the class's behavior.
*
* It allows one to determine whether a pair of characters
* should be broken by newlines
* For example certain characters in CJK langs or numbers.
* It must return a boolean.
* @param _char - The character
* @param _nextChar - The next character
* @param _token - The token/word the characters are from
* @param _index - The index in the token of the char
* @param _breakWords - The style attr break words
* @returns whether to break word or not
*/
static canBreakChars(_char, _nextChar, _token, _index, _breakWords) {
return true;
}
/**
* Overridable helper method used internally by TextMetrics, exposed to allow customizing the class's behavior.
*
* It is called when a token (usually a word) has to be split into separate pieces
* in order to determine the point to break a word.
* It must return an array of characters.
* @param token - The token to split
* @returns The characters of the token
* @see CanvasTextMetrics.graphemeSegmenter
*/
static wordWrapSplit(token) {
return _CanvasTextMetrics.graphemeSegmenter(token);
}
/**
* Calculates the ascent, descent and fontSize of a given font-style
* @param font - String representing the style of the font
* @returns Font properties object
*/
static measureFont(font) {
if (_CanvasTextMetrics._fonts[font]) {
return _CanvasTextMetrics._fonts[font];
}
const context = _CanvasTextMetrics._context;
context.font = font;
const metrics = context.measureText(_CanvasTextMetrics.METRICS_STRING + _CanvasTextMetrics.BASELINE_SYMBOL);
const properties = {
ascent: metrics.actualBoundingBoxAscent,
descent: metrics.actualBoundingBoxDescent,
fontSize: metrics.actualBoundingBoxAscent + metrics.actualBoundingBoxDescent
};
_CanvasTextMetrics._fonts[font] = properties;
return properties;
}
/**
* Clear font metrics in metrics cache.
* @param {string} [font] - font name. If font name not set then clear cache for all fonts.
*/
static clearMetrics(font = "") {
if (font) {
delete _CanvasTextMetrics._fonts[font];
} else {
_CanvasTextMetrics._fonts = {};
}
}
/**
* Cached canvas element for measuring text
* TODO: this should be private, but isn't because of backward compat, will fix later.
* @ignore
*/
static get _canvas() {
if (!_CanvasTextMetrics.__canvas) {
let canvas;
try {
const c = new OffscreenCanvas(0, 0);
const context = c.getContext("2d", contextSettings);
if (context?.measureText) {
_CanvasTextMetrics.__canvas = c;
return c;
}
canvas = DOMAdapter.get().createCanvas();
} catch (ex) {
canvas = DOMAdapter.get().createCanvas();
}
canvas.width = canvas.height = 10;
_CanvasTextMetrics.__canvas = canvas;
}
return _CanvasTextMetrics.__canvas;
}
/**
* TODO: this should be private, but isn't because of backward compat, will fix later.
* @ignore
*/
static get _context() {
if (!_CanvasTextMetrics.__context) {
_CanvasTextMetrics.__context = _CanvasTextMetrics._canvas.getContext("2d", contextSettings);
}
return _CanvasTextMetrics.__context;
}
};
/**
* String used for calculate font metrics.
* These characters are all tall to help calculate the height required for text.
*/
_CanvasTextMetrics.METRICS_STRING = "|\xC9q\xC5";
/** Baseline symbol for calculate font metrics. */
_CanvasTextMetrics.BASELINE_SYMBOL = "M";
/** Baseline multiplier for calculate font metrics. */
_CanvasTextMetrics.BASELINE_MULTIPLIER = 1.4;
/** Height multiplier for setting height of canvas to calculate font metrics. */
_CanvasTextMetrics.HEIGHT_MULTIPLIER = 2;
/**
* A Unicode "character", or "grapheme cluster", can be composed of multiple Unicode code points,
* such as letters with diacritical marks (e.g. `'\u0065\u0301'`, letter e with acute)
* or emojis with modifiers (e.g. `'\uD83E\uDDD1\u200D\uD83D\uDCBB'`, technologist).
* The new `Intl.Segmenter` API in ES2022 can split the string into grapheme clusters correctly. If it is not available,
* PixiJS will fallback to use the iterator of String, which can only spilt the string into code points.
* If you want to get full functionality in environments that don't support `Intl.Segmenter` (such as Firefox),
* you can use other libraries such as [grapheme-splitter]{@link https://www.npmjs.com/package/grapheme-splitter}
* or [graphemer]{@link https://www.npmjs.com/package/graphemer} to create a polyfill. Since these libraries can be
* relatively large in size to handle various Unicode grapheme clusters properly, PixiJS won't use them directly.
*/
_CanvasTextMetrics.graphemeSegmenter = (() => {
if (typeof Intl?.Segmenter === "function") {
const segmenter = new Intl.Segmenter();
return (s) => [...segmenter.segment(s)].map((x) => x.segment);
}
return (s) => [...s];
})();
/**
* New rendering behavior for letter-spacing which uses Chrome's new native API. This will
* lead to more accurate letter-spacing results because it does not try to manually draw
* each character. However, this Chrome API is experimental and may not serve all cases yet.
* @see TextMetrics.experimentalLetterSpacingSupported
*/
_CanvasTextMetrics.experimentalLetterSpacing = false;
/** Cache of {@see TextMetrics.FontMetrics} objects. */
_CanvasTextMetrics._fonts = {};
/** Cache of new line chars. */
_CanvasTextMetrics._newlines = [
10,
// line feed
13
// carriage return
];
/** Cache of breaking spaces. */
_CanvasTextMetrics._breakingSpaces = [
9,
// character tabulation
32,
// space
8192,
// en quad
8193,
// em quad
8194,
// en space
8195,
// em space
8196,
// three-per-em space
8197,
// four-per-em space
8198,
// six-per-em space
8200,
// punctuation space
8201,
// thin space
8202,
// hair space
8287,
// medium mathematical space
12288
// ideographic space
];
_CanvasTextMetrics._measurementCache = {};
let CanvasTextMetrics = _CanvasTextMetrics;
export { CanvasTextMetrics };
//# sourceMappingURL=CanvasTextMetrics.mjs.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,33 @@
import { ExtensionType } from '../../../extensions/Extensions';
import { BatchableSprite } from '../../sprite/BatchableSprite';
import type { InstructionSet } from '../../../rendering/renderers/shared/instructions/InstructionSet';
import type { RenderPipe } from '../../../rendering/renderers/shared/instructions/RenderPipe';
import type { Texture } from '../../../rendering/renderers/shared/texture/Texture';
import type { Renderer } from '../../../rendering/renderers/types';
import type { Text } from '../Text';
export declare class CanvasTextPipe implements RenderPipe<Text> {
/** @ignore */
static extension: {
readonly type: readonly [ExtensionType.WebGLPipes, ExtensionType.WebGPUPipes, ExtensionType.CanvasPipes];
readonly name: "text";
};
private _renderer;
private _gpuText;
private readonly _destroyRenderableBound;
constructor(renderer: Renderer);
resolutionChange(): void;
validateRenderable(text: Text): boolean;
addRenderable(text: Text, instructionSet: InstructionSet): void;
updateRenderable(text: Text): void;
destroyRenderable(text: Text): void;
private _destroyRenderableById;
private _updateText;
private _updateGpuText;
private _getGpuText;
initGpuText(text: Text): {
texture: Texture<import("../../..").TextureSource<any>>;
currentKey: string;
batchableSprite: BatchableSprite;
};
destroy(): void;
}

View File

@@ -0,0 +1,132 @@
'use strict';
var Extensions = require('../../../extensions/Extensions.js');
var updateQuadBounds = require('../../../utils/data/updateQuadBounds.js');
var PoolGroup = require('../../../utils/pool/PoolGroup.js');
var BatchableSprite = require('../../sprite/BatchableSprite.js');
"use strict";
class CanvasTextPipe {
constructor(renderer) {
this._gpuText = /* @__PURE__ */ Object.create(null);
this._destroyRenderableBound = this.destroyRenderable.bind(this);
this._renderer = renderer;
this._renderer.runners.resolutionChange.add(this);
}
resolutionChange() {
for (const i in this._gpuText) {
const gpuText = this._gpuText[i];
if (!gpuText)
continue;
const text = gpuText.batchableSprite.renderable;
if (text._autoResolution) {
text._resolution = this._renderer.resolution;
text.onViewUpdate();
}
}
}
validateRenderable(text) {
const gpuText = this._getGpuText(text);
const newKey = text._getKey();
if (gpuText.currentKey !== newKey) {
const { width, height } = this._renderer.canvasText.getTextureSize(
text.text,
text.resolution,
text._style
);
if (
// is only being used by this text:
this._renderer.canvasText.getReferenceCount(gpuText.currentKey) === 1 && width === gpuText.texture._source.width && height === gpuText.texture._source.height
) {
return false;
}
return true;
}
return false;
}
addRenderable(text, instructionSet) {
const gpuText = this._getGpuText(text);
const batchableSprite = gpuText.batchableSprite;
if (text._didTextUpdate) {
this._updateText(text);
}
this._renderer.renderPipes.batch.addToBatch(batchableSprite, instructionSet);
}
updateRenderable(text) {
const gpuText = this._getGpuText(text);
const batchableSprite = gpuText.batchableSprite;
if (text._didTextUpdate) {
this._updateText(text);
}
batchableSprite._batcher.updateElement(batchableSprite);
}
destroyRenderable(text) {
text.off("destroyed", this._destroyRenderableBound);
this._destroyRenderableById(text.uid);
}
_destroyRenderableById(textUid) {
const gpuText = this._gpuText[textUid];
this._renderer.canvasText.decreaseReferenceCount(gpuText.currentKey);
PoolGroup.BigPool.return(gpuText.batchableSprite);
this._gpuText[textUid] = null;
}
_updateText(text) {
const newKey = text._getKey();
const gpuText = this._getGpuText(text);
const batchableSprite = gpuText.batchableSprite;
if (gpuText.currentKey !== newKey) {
this._updateGpuText(text);
}
text._didTextUpdate = false;
const padding = text._style.padding;
updateQuadBounds.updateQuadBounds(batchableSprite.bounds, text._anchor, batchableSprite.texture, padding);
}
_updateGpuText(text) {
const gpuText = this._getGpuText(text);
const batchableSprite = gpuText.batchableSprite;
if (gpuText.texture) {
this._renderer.canvasText.decreaseReferenceCount(gpuText.currentKey);
}
gpuText.texture = batchableSprite.texture = this._renderer.canvasText.getManagedTexture(text);
gpuText.currentKey = text._getKey();
batchableSprite.texture = gpuText.texture;
}
_getGpuText(text) {
return this._gpuText[text.uid] || this.initGpuText(text);
}
initGpuText(text) {
const gpuTextData = {
texture: null,
currentKey: "--",
batchableSprite: PoolGroup.BigPool.get(BatchableSprite.BatchableSprite)
};
gpuTextData.batchableSprite.renderable = text;
gpuTextData.batchableSprite.transform = text.groupTransform;
gpuTextData.batchableSprite.bounds = { minX: 0, maxX: 1, minY: 0, maxY: 0 };
gpuTextData.batchableSprite.roundPixels = this._renderer._roundPixels | text._roundPixels;
this._gpuText[text.uid] = gpuTextData;
text._resolution = text._autoResolution ? this._renderer.resolution : text.resolution;
this._updateText(text);
text.on("destroyed", this._destroyRenderableBound);
return gpuTextData;
}
destroy() {
for (const i in this._gpuText) {
this._destroyRenderableById(i);
}
this._gpuText = null;
this._renderer = null;
}
}
/** @ignore */
CanvasTextPipe.extension = {
type: [
Extensions.ExtensionType.WebGLPipes,
Extensions.ExtensionType.WebGPUPipes,
Extensions.ExtensionType.CanvasPipes
],
name: "text"
};
exports.CanvasTextPipe = CanvasTextPipe;
//# sourceMappingURL=CanvasTextPipe.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,130 @@
import { ExtensionType } from '../../../extensions/Extensions.mjs';
import { updateQuadBounds } from '../../../utils/data/updateQuadBounds.mjs';
import { BigPool } from '../../../utils/pool/PoolGroup.mjs';
import { BatchableSprite } from '../../sprite/BatchableSprite.mjs';
"use strict";
class CanvasTextPipe {
constructor(renderer) {
this._gpuText = /* @__PURE__ */ Object.create(null);
this._destroyRenderableBound = this.destroyRenderable.bind(this);
this._renderer = renderer;
this._renderer.runners.resolutionChange.add(this);
}
resolutionChange() {
for (const i in this._gpuText) {
const gpuText = this._gpuText[i];
if (!gpuText)
continue;
const text = gpuText.batchableSprite.renderable;
if (text._autoResolution) {
text._resolution = this._renderer.resolution;
text.onViewUpdate();
}
}
}
validateRenderable(text) {
const gpuText = this._getGpuText(text);
const newKey = text._getKey();
if (gpuText.currentKey !== newKey) {
const { width, height } = this._renderer.canvasText.getTextureSize(
text.text,
text.resolution,
text._style
);
if (
// is only being used by this text:
this._renderer.canvasText.getReferenceCount(gpuText.currentKey) === 1 && width === gpuText.texture._source.width && height === gpuText.texture._source.height
) {
return false;
}
return true;
}
return false;
}
addRenderable(text, instructionSet) {
const gpuText = this._getGpuText(text);
const batchableSprite = gpuText.batchableSprite;
if (text._didTextUpdate) {
this._updateText(text);
}
this._renderer.renderPipes.batch.addToBatch(batchableSprite, instructionSet);
}
updateRenderable(text) {
const gpuText = this._getGpuText(text);
const batchableSprite = gpuText.batchableSprite;
if (text._didTextUpdate) {
this._updateText(text);
}
batchableSprite._batcher.updateElement(batchableSprite);
}
destroyRenderable(text) {
text.off("destroyed", this._destroyRenderableBound);
this._destroyRenderableById(text.uid);
}
_destroyRenderableById(textUid) {
const gpuText = this._gpuText[textUid];
this._renderer.canvasText.decreaseReferenceCount(gpuText.currentKey);
BigPool.return(gpuText.batchableSprite);
this._gpuText[textUid] = null;
}
_updateText(text) {
const newKey = text._getKey();
const gpuText = this._getGpuText(text);
const batchableSprite = gpuText.batchableSprite;
if (gpuText.currentKey !== newKey) {
this._updateGpuText(text);
}
text._didTextUpdate = false;
const padding = text._style.padding;
updateQuadBounds(batchableSprite.bounds, text._anchor, batchableSprite.texture, padding);
}
_updateGpuText(text) {
const gpuText = this._getGpuText(text);
const batchableSprite = gpuText.batchableSprite;
if (gpuText.texture) {
this._renderer.canvasText.decreaseReferenceCount(gpuText.currentKey);
}
gpuText.texture = batchableSprite.texture = this._renderer.canvasText.getManagedTexture(text);
gpuText.currentKey = text._getKey();
batchableSprite.texture = gpuText.texture;
}
_getGpuText(text) {
return this._gpuText[text.uid] || this.initGpuText(text);
}
initGpuText(text) {
const gpuTextData = {
texture: null,
currentKey: "--",
batchableSprite: BigPool.get(BatchableSprite)
};
gpuTextData.batchableSprite.renderable = text;
gpuTextData.batchableSprite.transform = text.groupTransform;
gpuTextData.batchableSprite.bounds = { minX: 0, maxX: 1, minY: 0, maxY: 0 };
gpuTextData.batchableSprite.roundPixels = this._renderer._roundPixels | text._roundPixels;
this._gpuText[text.uid] = gpuTextData;
text._resolution = text._autoResolution ? this._renderer.resolution : text.resolution;
this._updateText(text);
text.on("destroyed", this._destroyRenderableBound);
return gpuTextData;
}
destroy() {
for (const i in this._gpuText) {
this._destroyRenderableById(i);
}
this._gpuText = null;
this._renderer = null;
}
}
/** @ignore */
CanvasTextPipe.extension = {
type: [
ExtensionType.WebGLPipes,
ExtensionType.WebGPUPipes,
ExtensionType.CanvasPipes
],
name: "text"
};
export { CanvasTextPipe };
//# sourceMappingURL=CanvasTextPipe.mjs.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,80 @@
import { ExtensionType } from '../../../extensions/Extensions';
import { TextStyle } from '../TextStyle';
import type { ICanvas } from '../../../environment/canvas/ICanvas';
import type { ICanvasRenderingContext2D } from '../../../environment/canvas/ICanvasRenderingContext2D';
import type { System } from '../../../rendering/renderers/shared/system/System';
import type { Texture } from '../../../rendering/renderers/shared/texture/Texture';
import type { Renderer } from '../../../rendering/renderers/types';
import type { TextOptions } from '../AbstractText';
import type { Text } from '../Text';
interface CanvasAndContext {
canvas: ICanvas;
context: ICanvasRenderingContext2D;
}
/**
* System plugin to the renderer to manage canvas text.
* @memberof rendering
*/
export declare class CanvasTextSystem implements System {
/** @ignore */
static extension: {
readonly type: readonly [ExtensionType.WebGLSystem, ExtensionType.WebGPUSystem, ExtensionType.CanvasSystem];
readonly name: "canvasText";
};
private _activeTextures;
private readonly _renderer;
constructor(_renderer: Renderer);
getTextureSize(text: string, resolution: number, style: TextStyle): {
width: number;
height: number;
};
/**
* This is a function that will create a texture from a text string, style and resolution.
* Useful if you want to make a texture of your text and use if for various other pixi things!
* @param options - The options of the text that will be used to generate the texture.
* @param options.text - the text to render
* @param options.style - the style of the text
* @param options.resolution - the resolution of the texture
* @returns the newly created texture
*/
/** @deprecated since 8.0.0 */
getTexture(text: string, resolution: number, style: TextStyle, textKey: string): Texture;
getTexture(options: TextOptions): Texture;
createTextureAndCanvas(options: {
text: string;
style: TextStyle;
resolution?: number;
}): {
texture: Texture<import("../../..").TextureSource<any>>;
canvasAndContext: import("../../../rendering/renderers/shared/texture/CanvasPool").CanvasAndContext;
};
getManagedTexture(text: Text): Texture<import("../../..").TextureSource<any>>;
private _increaseReferenceCount;
decreaseReferenceCount(textKey: string): void;
getReferenceCount(textKey: string): number;
/**
* Renders text to its canvas, and updates its texture.
*
* By default this is used internally to ensure the texture is correct before rendering,
* but it can be used called externally, for example from this class to 'pre-generate' the texture from a piece of text,
* and then shared across multiple Sprites.
* @param text
* @param style
* @param resolution
* @param canvasAndContext
*/
renderTextToCanvas(text: string, style: TextStyle, resolution: number, canvasAndContext: CanvasAndContext): void;
/**
* Render the text with letter-spacing.
* @param text - The text to draw
* @param style
* @param canvasAndContext
* @param x - Horizontal position to draw the text
* @param y - Vertical position to draw the text
* @param isStroke - Is this drawing for the outside stroke of the
* text? If not, it's for the inside fill
*/
private _drawLetterSpacing;
destroy(): void;
}
export {};

View File

@@ -0,0 +1,264 @@
'use strict';
var Color = require('../../../color/Color.js');
var Extensions = require('../../../extensions/Extensions.js');
var pow2 = require('../../../maths/misc/pow2.js');
var CanvasPool = require('../../../rendering/renderers/shared/texture/CanvasPool.js');
var TexturePool = require('../../../rendering/renderers/shared/texture/TexturePool.js');
var getCanvasBoundingBox = require('../../../utils/canvas/getCanvasBoundingBox.js');
var deprecation = require('../../../utils/logging/deprecation.js');
var TextStyle = require('../TextStyle.js');
var getPo2TextureFromSource = require('../utils/getPo2TextureFromSource.js');
var CanvasTextMetrics = require('./CanvasTextMetrics.js');
var fontStringFromTextStyle = require('./utils/fontStringFromTextStyle.js');
var getCanvasFillStyle = require('./utils/getCanvasFillStyle.js');
"use strict";
class CanvasTextSystem {
constructor(_renderer) {
this._activeTextures = {};
this._renderer = _renderer;
}
getTextureSize(text, resolution, style) {
const measured = CanvasTextMetrics.CanvasTextMetrics.measureText(text || " ", style);
let width = Math.ceil(Math.ceil(Math.max(1, measured.width) + style.padding * 2) * resolution);
let height = Math.ceil(Math.ceil(Math.max(1, measured.height) + style.padding * 2) * resolution);
width = Math.ceil(width - 1e-6);
height = Math.ceil(height - 1e-6);
width = pow2.nextPow2(width);
height = pow2.nextPow2(height);
return { width, height };
}
getTexture(options, resolution, style, _textKey) {
if (typeof options === "string") {
deprecation.deprecation("8.0.0", "CanvasTextSystem.getTexture: Use object TextOptions instead of separate arguments");
options = {
text: options,
style,
resolution
};
}
if (!(options.style instanceof TextStyle.TextStyle)) {
options.style = new TextStyle.TextStyle(options.style);
}
const { texture, canvasAndContext } = this.createTextureAndCanvas(
options
);
this._renderer.texture.initSource(texture._source);
CanvasPool.CanvasPool.returnCanvasAndContext(canvasAndContext);
return texture;
}
createTextureAndCanvas(options) {
const { text, style } = options;
const resolution = options.resolution ?? this._renderer.resolution;
const measured = CanvasTextMetrics.CanvasTextMetrics.measureText(text || " ", style);
const width = Math.ceil(Math.ceil(Math.max(1, measured.width) + style.padding * 2) * resolution);
const height = Math.ceil(Math.ceil(Math.max(1, measured.height) + style.padding * 2) * resolution);
const canvasAndContext = CanvasPool.CanvasPool.getOptimalCanvasAndContext(width, height);
const { canvas } = canvasAndContext;
this.renderTextToCanvas(text, style, resolution, canvasAndContext);
const texture = getPo2TextureFromSource.getPo2TextureFromSource(canvas, width, height, resolution);
if (style.trim) {
const trimmed = getCanvasBoundingBox.getCanvasBoundingBox(canvas, resolution);
texture.frame.copyFrom(trimmed);
texture.updateUvs();
}
return { texture, canvasAndContext };
}
getManagedTexture(text) {
text._resolution = text._autoResolution ? this._renderer.resolution : text.resolution;
const textKey = text._getKey();
if (this._activeTextures[textKey]) {
this._increaseReferenceCount(textKey);
return this._activeTextures[textKey].texture;
}
const { texture, canvasAndContext } = this.createTextureAndCanvas(text);
this._activeTextures[textKey] = {
canvasAndContext,
texture,
usageCount: 1
};
return texture;
}
_increaseReferenceCount(textKey) {
this._activeTextures[textKey].usageCount++;
}
decreaseReferenceCount(textKey) {
const activeTexture = this._activeTextures[textKey];
activeTexture.usageCount--;
if (activeTexture.usageCount === 0) {
CanvasPool.CanvasPool.returnCanvasAndContext(activeTexture.canvasAndContext);
TexturePool.TexturePool.returnTexture(activeTexture.texture);
const source = activeTexture.texture.source;
source.resource = null;
source.uploadMethodId = "unknown";
source.alphaMode = "no-premultiply-alpha";
this._activeTextures[textKey] = null;
}
}
getReferenceCount(textKey) {
return this._activeTextures[textKey].usageCount;
}
/**
* Renders text to its canvas, and updates its texture.
*
* By default this is used internally to ensure the texture is correct before rendering,
* but it can be used called externally, for example from this class to 'pre-generate' the texture from a piece of text,
* and then shared across multiple Sprites.
* @param text
* @param style
* @param resolution
* @param canvasAndContext
*/
renderTextToCanvas(text, style, resolution, canvasAndContext) {
const { canvas, context } = canvasAndContext;
const font = fontStringFromTextStyle.fontStringFromTextStyle(style);
const measured = CanvasTextMetrics.CanvasTextMetrics.measureText(text || " ", style);
const lines = measured.lines;
const lineHeight = measured.lineHeight;
const lineWidths = measured.lineWidths;
const maxLineWidth = measured.maxLineWidth;
const fontProperties = measured.fontProperties;
const height = canvas.height;
context.resetTransform();
context.scale(resolution, resolution);
const padding = style.padding * 2;
context.clearRect(0, 0, measured.width + 4 + padding, measured.height + 4 + padding);
if (style._stroke?.width) {
const strokeStyle = style._stroke;
context.lineWidth = strokeStyle.width;
context.miterLimit = strokeStyle.miterLimit;
context.lineJoin = strokeStyle.join;
context.lineCap = strokeStyle.cap;
}
context.font = font;
let linePositionX;
let linePositionY;
const passesCount = style.dropShadow ? 2 : 1;
for (let i = 0; i < passesCount; ++i) {
const isShadowPass = style.dropShadow && i === 0;
const dsOffsetText = isShadowPass ? Math.ceil(Math.max(1, height) + style.padding * 2) : 0;
const dsOffsetShadow = dsOffsetText * resolution;
if (isShadowPass) {
context.fillStyle = "black";
context.strokeStyle = "black";
const shadowOptions = style.dropShadow;
const dropShadowColor = shadowOptions.color;
const dropShadowAlpha = shadowOptions.alpha;
context.shadowColor = Color.Color.shared.setValue(dropShadowColor).setAlpha(dropShadowAlpha).toRgbaString();
const dropShadowBlur = shadowOptions.blur * resolution;
const dropShadowDistance = shadowOptions.distance * resolution;
context.shadowBlur = dropShadowBlur;
context.shadowOffsetX = Math.cos(shadowOptions.angle) * dropShadowDistance;
context.shadowOffsetY = Math.sin(shadowOptions.angle) * dropShadowDistance + dsOffsetShadow;
} else {
context.globalAlpha = style._fill?.alpha ?? 1;
context.fillStyle = style._fill ? getCanvasFillStyle.getCanvasFillStyle(style._fill, context) : null;
if (style._stroke?.width) {
context.strokeStyle = getCanvasFillStyle.getCanvasFillStyle(style._stroke, context);
}
context.shadowColor = "black";
}
let linePositionYShift = (lineHeight - fontProperties.fontSize) / 2;
if (lineHeight - fontProperties.fontSize < 0) {
linePositionYShift = 0;
}
const strokeWidth = style._stroke?.width ?? 0;
for (let i2 = 0; i2 < lines.length; i2++) {
linePositionX = strokeWidth / 2;
linePositionY = strokeWidth / 2 + i2 * lineHeight + fontProperties.ascent + linePositionYShift;
if (style.align === "right") {
linePositionX += maxLineWidth - lineWidths[i2];
} else if (style.align === "center") {
linePositionX += (maxLineWidth - lineWidths[i2]) / 2;
}
if (style._stroke?.width) {
this._drawLetterSpacing(
lines[i2],
style,
canvasAndContext,
linePositionX + style.padding,
linePositionY + style.padding - dsOffsetText,
true
);
}
if (style._fill !== void 0) {
this._drawLetterSpacing(
lines[i2],
style,
canvasAndContext,
linePositionX + style.padding,
linePositionY + style.padding - dsOffsetText
);
}
}
}
}
/**
* Render the text with letter-spacing.
* @param text - The text to draw
* @param style
* @param canvasAndContext
* @param x - Horizontal position to draw the text
* @param y - Vertical position to draw the text
* @param isStroke - Is this drawing for the outside stroke of the
* text? If not, it's for the inside fill
*/
_drawLetterSpacing(text, style, canvasAndContext, x, y, isStroke = false) {
const { context } = canvasAndContext;
const letterSpacing = style.letterSpacing;
let useExperimentalLetterSpacing = false;
if (CanvasTextMetrics.CanvasTextMetrics.experimentalLetterSpacingSupported) {
if (CanvasTextMetrics.CanvasTextMetrics.experimentalLetterSpacing) {
context.letterSpacing = `${letterSpacing}px`;
context.textLetterSpacing = `${letterSpacing}px`;
useExperimentalLetterSpacing = true;
} else {
context.letterSpacing = "0px";
context.textLetterSpacing = "0px";
}
}
if (letterSpacing === 0 || useExperimentalLetterSpacing) {
if (isStroke) {
context.strokeText(text, x, y);
} else {
context.fillText(text, x, y);
}
return;
}
let currentPosition = x;
const stringArray = CanvasTextMetrics.CanvasTextMetrics.graphemeSegmenter(text);
let previousWidth = context.measureText(text).width;
let currentWidth = 0;
for (let i = 0; i < stringArray.length; ++i) {
const currentChar = stringArray[i];
if (isStroke) {
context.strokeText(currentChar, currentPosition, y);
} else {
context.fillText(currentChar, currentPosition, y);
}
let textStr = "";
for (let j = i + 1; j < stringArray.length; ++j) {
textStr += stringArray[j];
}
currentWidth = context.measureText(textStr).width;
currentPosition += previousWidth - currentWidth + letterSpacing;
previousWidth = currentWidth;
}
}
destroy() {
this._activeTextures = null;
}
}
/** @ignore */
CanvasTextSystem.extension = {
type: [
Extensions.ExtensionType.WebGLSystem,
Extensions.ExtensionType.WebGPUSystem,
Extensions.ExtensionType.CanvasSystem
],
name: "canvasText"
};
exports.CanvasTextSystem = CanvasTextSystem;
//# sourceMappingURL=CanvasTextSystem.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,262 @@
import { Color } from '../../../color/Color.mjs';
import { ExtensionType } from '../../../extensions/Extensions.mjs';
import { nextPow2 } from '../../../maths/misc/pow2.mjs';
import { CanvasPool } from '../../../rendering/renderers/shared/texture/CanvasPool.mjs';
import { TexturePool } from '../../../rendering/renderers/shared/texture/TexturePool.mjs';
import { getCanvasBoundingBox } from '../../../utils/canvas/getCanvasBoundingBox.mjs';
import { deprecation } from '../../../utils/logging/deprecation.mjs';
import { TextStyle } from '../TextStyle.mjs';
import { getPo2TextureFromSource } from '../utils/getPo2TextureFromSource.mjs';
import { CanvasTextMetrics } from './CanvasTextMetrics.mjs';
import { fontStringFromTextStyle } from './utils/fontStringFromTextStyle.mjs';
import { getCanvasFillStyle } from './utils/getCanvasFillStyle.mjs';
"use strict";
class CanvasTextSystem {
constructor(_renderer) {
this._activeTextures = {};
this._renderer = _renderer;
}
getTextureSize(text, resolution, style) {
const measured = CanvasTextMetrics.measureText(text || " ", style);
let width = Math.ceil(Math.ceil(Math.max(1, measured.width) + style.padding * 2) * resolution);
let height = Math.ceil(Math.ceil(Math.max(1, measured.height) + style.padding * 2) * resolution);
width = Math.ceil(width - 1e-6);
height = Math.ceil(height - 1e-6);
width = nextPow2(width);
height = nextPow2(height);
return { width, height };
}
getTexture(options, resolution, style, _textKey) {
if (typeof options === "string") {
deprecation("8.0.0", "CanvasTextSystem.getTexture: Use object TextOptions instead of separate arguments");
options = {
text: options,
style,
resolution
};
}
if (!(options.style instanceof TextStyle)) {
options.style = new TextStyle(options.style);
}
const { texture, canvasAndContext } = this.createTextureAndCanvas(
options
);
this._renderer.texture.initSource(texture._source);
CanvasPool.returnCanvasAndContext(canvasAndContext);
return texture;
}
createTextureAndCanvas(options) {
const { text, style } = options;
const resolution = options.resolution ?? this._renderer.resolution;
const measured = CanvasTextMetrics.measureText(text || " ", style);
const width = Math.ceil(Math.ceil(Math.max(1, measured.width) + style.padding * 2) * resolution);
const height = Math.ceil(Math.ceil(Math.max(1, measured.height) + style.padding * 2) * resolution);
const canvasAndContext = CanvasPool.getOptimalCanvasAndContext(width, height);
const { canvas } = canvasAndContext;
this.renderTextToCanvas(text, style, resolution, canvasAndContext);
const texture = getPo2TextureFromSource(canvas, width, height, resolution);
if (style.trim) {
const trimmed = getCanvasBoundingBox(canvas, resolution);
texture.frame.copyFrom(trimmed);
texture.updateUvs();
}
return { texture, canvasAndContext };
}
getManagedTexture(text) {
text._resolution = text._autoResolution ? this._renderer.resolution : text.resolution;
const textKey = text._getKey();
if (this._activeTextures[textKey]) {
this._increaseReferenceCount(textKey);
return this._activeTextures[textKey].texture;
}
const { texture, canvasAndContext } = this.createTextureAndCanvas(text);
this._activeTextures[textKey] = {
canvasAndContext,
texture,
usageCount: 1
};
return texture;
}
_increaseReferenceCount(textKey) {
this._activeTextures[textKey].usageCount++;
}
decreaseReferenceCount(textKey) {
const activeTexture = this._activeTextures[textKey];
activeTexture.usageCount--;
if (activeTexture.usageCount === 0) {
CanvasPool.returnCanvasAndContext(activeTexture.canvasAndContext);
TexturePool.returnTexture(activeTexture.texture);
const source = activeTexture.texture.source;
source.resource = null;
source.uploadMethodId = "unknown";
source.alphaMode = "no-premultiply-alpha";
this._activeTextures[textKey] = null;
}
}
getReferenceCount(textKey) {
return this._activeTextures[textKey].usageCount;
}
/**
* Renders text to its canvas, and updates its texture.
*
* By default this is used internally to ensure the texture is correct before rendering,
* but it can be used called externally, for example from this class to 'pre-generate' the texture from a piece of text,
* and then shared across multiple Sprites.
* @param text
* @param style
* @param resolution
* @param canvasAndContext
*/
renderTextToCanvas(text, style, resolution, canvasAndContext) {
const { canvas, context } = canvasAndContext;
const font = fontStringFromTextStyle(style);
const measured = CanvasTextMetrics.measureText(text || " ", style);
const lines = measured.lines;
const lineHeight = measured.lineHeight;
const lineWidths = measured.lineWidths;
const maxLineWidth = measured.maxLineWidth;
const fontProperties = measured.fontProperties;
const height = canvas.height;
context.resetTransform();
context.scale(resolution, resolution);
const padding = style.padding * 2;
context.clearRect(0, 0, measured.width + 4 + padding, measured.height + 4 + padding);
if (style._stroke?.width) {
const strokeStyle = style._stroke;
context.lineWidth = strokeStyle.width;
context.miterLimit = strokeStyle.miterLimit;
context.lineJoin = strokeStyle.join;
context.lineCap = strokeStyle.cap;
}
context.font = font;
let linePositionX;
let linePositionY;
const passesCount = style.dropShadow ? 2 : 1;
for (let i = 0; i < passesCount; ++i) {
const isShadowPass = style.dropShadow && i === 0;
const dsOffsetText = isShadowPass ? Math.ceil(Math.max(1, height) + style.padding * 2) : 0;
const dsOffsetShadow = dsOffsetText * resolution;
if (isShadowPass) {
context.fillStyle = "black";
context.strokeStyle = "black";
const shadowOptions = style.dropShadow;
const dropShadowColor = shadowOptions.color;
const dropShadowAlpha = shadowOptions.alpha;
context.shadowColor = Color.shared.setValue(dropShadowColor).setAlpha(dropShadowAlpha).toRgbaString();
const dropShadowBlur = shadowOptions.blur * resolution;
const dropShadowDistance = shadowOptions.distance * resolution;
context.shadowBlur = dropShadowBlur;
context.shadowOffsetX = Math.cos(shadowOptions.angle) * dropShadowDistance;
context.shadowOffsetY = Math.sin(shadowOptions.angle) * dropShadowDistance + dsOffsetShadow;
} else {
context.globalAlpha = style._fill?.alpha ?? 1;
context.fillStyle = style._fill ? getCanvasFillStyle(style._fill, context) : null;
if (style._stroke?.width) {
context.strokeStyle = getCanvasFillStyle(style._stroke, context);
}
context.shadowColor = "black";
}
let linePositionYShift = (lineHeight - fontProperties.fontSize) / 2;
if (lineHeight - fontProperties.fontSize < 0) {
linePositionYShift = 0;
}
const strokeWidth = style._stroke?.width ?? 0;
for (let i2 = 0; i2 < lines.length; i2++) {
linePositionX = strokeWidth / 2;
linePositionY = strokeWidth / 2 + i2 * lineHeight + fontProperties.ascent + linePositionYShift;
if (style.align === "right") {
linePositionX += maxLineWidth - lineWidths[i2];
} else if (style.align === "center") {
linePositionX += (maxLineWidth - lineWidths[i2]) / 2;
}
if (style._stroke?.width) {
this._drawLetterSpacing(
lines[i2],
style,
canvasAndContext,
linePositionX + style.padding,
linePositionY + style.padding - dsOffsetText,
true
);
}
if (style._fill !== void 0) {
this._drawLetterSpacing(
lines[i2],
style,
canvasAndContext,
linePositionX + style.padding,
linePositionY + style.padding - dsOffsetText
);
}
}
}
}
/**
* Render the text with letter-spacing.
* @param text - The text to draw
* @param style
* @param canvasAndContext
* @param x - Horizontal position to draw the text
* @param y - Vertical position to draw the text
* @param isStroke - Is this drawing for the outside stroke of the
* text? If not, it's for the inside fill
*/
_drawLetterSpacing(text, style, canvasAndContext, x, y, isStroke = false) {
const { context } = canvasAndContext;
const letterSpacing = style.letterSpacing;
let useExperimentalLetterSpacing = false;
if (CanvasTextMetrics.experimentalLetterSpacingSupported) {
if (CanvasTextMetrics.experimentalLetterSpacing) {
context.letterSpacing = `${letterSpacing}px`;
context.textLetterSpacing = `${letterSpacing}px`;
useExperimentalLetterSpacing = true;
} else {
context.letterSpacing = "0px";
context.textLetterSpacing = "0px";
}
}
if (letterSpacing === 0 || useExperimentalLetterSpacing) {
if (isStroke) {
context.strokeText(text, x, y);
} else {
context.fillText(text, x, y);
}
return;
}
let currentPosition = x;
const stringArray = CanvasTextMetrics.graphemeSegmenter(text);
let previousWidth = context.measureText(text).width;
let currentWidth = 0;
for (let i = 0; i < stringArray.length; ++i) {
const currentChar = stringArray[i];
if (isStroke) {
context.strokeText(currentChar, currentPosition, y);
} else {
context.fillText(currentChar, currentPosition, y);
}
let textStr = "";
for (let j = i + 1; j < stringArray.length; ++j) {
textStr += stringArray[j];
}
currentWidth = context.measureText(textStr).width;
currentPosition += previousWidth - currentWidth + letterSpacing;
previousWidth = currentWidth;
}
}
destroy() {
this._activeTextures = null;
}
}
/** @ignore */
CanvasTextSystem.extension = {
type: [
ExtensionType.WebGLSystem,
ExtensionType.WebGPUSystem,
ExtensionType.CanvasSystem
],
name: "canvasText"
};
export { CanvasTextSystem };
//# sourceMappingURL=CanvasTextSystem.mjs.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,7 @@
import type { TextStyle } from '../../TextStyle';
/**
* Generates a font style string to use for `TextMetrics.measureFont()`.
* @param style
* @returns Font style string, for passing to `TextMetrics.measureFont()`
*/
export declare function fontStringFromTextStyle(style: TextStyle): string;

View File

@@ -0,0 +1,29 @@
'use strict';
"use strict";
const genericFontFamilies = [
"serif",
"sans-serif",
"monospace",
"cursive",
"fantasy",
"system-ui"
];
function fontStringFromTextStyle(style) {
const fontSizeString = typeof style.fontSize === "number" ? `${style.fontSize}px` : style.fontSize;
let fontFamilies = style.fontFamily;
if (!Array.isArray(style.fontFamily)) {
fontFamilies = style.fontFamily.split(",");
}
for (let i = fontFamilies.length - 1; i >= 0; i--) {
let fontFamily = fontFamilies[i].trim();
if (!/([\"\'])[^\'\"]+\1/.test(fontFamily) && !genericFontFamilies.includes(fontFamily)) {
fontFamily = `"${fontFamily}"`;
}
fontFamilies[i] = fontFamily;
}
return `${style.fontStyle} ${style.fontVariant} ${style.fontWeight} ${fontSizeString} ${fontFamilies.join(",")}`;
}
exports.fontStringFromTextStyle = fontStringFromTextStyle;
//# sourceMappingURL=fontStringFromTextStyle.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"fontStringFromTextStyle.js","sources":["../../../../../src/scene/text/canvas/utils/fontStringFromTextStyle.ts"],"sourcesContent":["import type { TextStyle } from '../../TextStyle';\n\nconst genericFontFamilies = [\n 'serif',\n 'sans-serif',\n 'monospace',\n 'cursive',\n 'fantasy',\n 'system-ui',\n];\n\n/**\n * Generates a font style string to use for `TextMetrics.measureFont()`.\n * @param style\n * @returns Font style string, for passing to `TextMetrics.measureFont()`\n */\nexport function fontStringFromTextStyle(style: TextStyle): string\n{\n // build canvas api font setting from individual components. Convert a numeric style.fontSize to px\n const fontSizeString = (typeof style.fontSize === 'number') ? `${style.fontSize}px` : style.fontSize;\n\n // Clean-up fontFamily property by quoting each font name\n // this will support font names with spaces\n let fontFamilies: string | string[] = style.fontFamily;\n\n if (!Array.isArray(style.fontFamily))\n {\n fontFamilies = style.fontFamily.split(',');\n }\n\n for (let i = fontFamilies.length - 1; i >= 0; i--)\n {\n // Trim any extra white-space\n let fontFamily = fontFamilies[i].trim();\n\n // Check if font already contains strings\n if (!(/([\\\"\\'])[^\\'\\\"]+\\1/).test(fontFamily) && !genericFontFamilies.includes(fontFamily))\n {\n fontFamily = `\"${fontFamily}\"`;\n }\n (fontFamilies as string[])[i] = fontFamily;\n }\n\n // eslint-disable-next-line max-len\n return `${style.fontStyle} ${style.fontVariant} ${style.fontWeight} ${fontSizeString} ${(fontFamilies as string[]).join(',')}`;\n}\n"],"names":[],"mappings":";;;AAEA,MAAM,mBAAsB,GAAA;AAAA,EACxB,OAAA;AAAA,EACA,YAAA;AAAA,EACA,WAAA;AAAA,EACA,SAAA;AAAA,EACA,SAAA;AAAA,EACA,WAAA;AACJ,CAAA,CAAA;AAOO,SAAS,wBAAwB,KACxC,EAAA;AAEI,EAAM,MAAA,cAAA,GAAkB,OAAO,KAAM,CAAA,QAAA,KAAa,WAAY,CAAG,EAAA,KAAA,CAAM,QAAQ,CAAA,EAAA,CAAA,GAAO,KAAM,CAAA,QAAA,CAAA;AAI5F,EAAA,IAAI,eAAkC,KAAM,CAAA,UAAA,CAAA;AAE5C,EAAA,IAAI,CAAC,KAAA,CAAM,OAAQ,CAAA,KAAA,CAAM,UAAU,CACnC,EAAA;AACI,IAAe,YAAA,GAAA,KAAA,CAAM,UAAW,CAAA,KAAA,CAAM,GAAG,CAAA,CAAA;AAAA,GAC7C;AAEA,EAAA,KAAA,IAAS,IAAI,YAAa,CAAA,MAAA,GAAS,CAAG,EAAA,CAAA,IAAK,GAAG,CAC9C,EAAA,EAAA;AAEI,IAAA,IAAI,UAAa,GAAA,YAAA,CAAa,CAAC,CAAA,CAAE,IAAK,EAAA,CAAA;AAGtC,IAAI,IAAA,CAAE,qBAAsB,IAAK,CAAA,UAAU,KAAK,CAAC,mBAAA,CAAoB,QAAS,CAAA,UAAU,CACxF,EAAA;AACI,MAAA,UAAA,GAAa,IAAI,UAAU,CAAA,CAAA,CAAA,CAAA;AAAA,KAC/B;AACA,IAAC,YAAA,CAA0B,CAAC,CAAI,GAAA,UAAA,CAAA;AAAA,GACpC;AAGA,EAAA,OAAO,CAAG,EAAA,KAAA,CAAM,SAAS,CAAA,CAAA,EAAI,MAAM,WAAW,CAAA,CAAA,EAAI,KAAM,CAAA,UAAU,IAAI,cAAc,CAAA,CAAA,EAAK,YAA0B,CAAA,IAAA,CAAK,GAAG,CAAC,CAAA,CAAA,CAAA;AAChI;;;;"}

View File

@@ -0,0 +1,27 @@
"use strict";
const genericFontFamilies = [
"serif",
"sans-serif",
"monospace",
"cursive",
"fantasy",
"system-ui"
];
function fontStringFromTextStyle(style) {
const fontSizeString = typeof style.fontSize === "number" ? `${style.fontSize}px` : style.fontSize;
let fontFamilies = style.fontFamily;
if (!Array.isArray(style.fontFamily)) {
fontFamilies = style.fontFamily.split(",");
}
for (let i = fontFamilies.length - 1; i >= 0; i--) {
let fontFamily = fontFamilies[i].trim();
if (!/([\"\'])[^\'\"]+\1/.test(fontFamily) && !genericFontFamilies.includes(fontFamily)) {
fontFamily = `"${fontFamily}"`;
}
fontFamilies[i] = fontFamily;
}
return `${style.fontStyle} ${style.fontVariant} ${style.fontWeight} ${fontSizeString} ${fontFamilies.join(",")}`;
}
export { fontStringFromTextStyle };
//# sourceMappingURL=fontStringFromTextStyle.mjs.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"fontStringFromTextStyle.mjs","sources":["../../../../../src/scene/text/canvas/utils/fontStringFromTextStyle.ts"],"sourcesContent":["import type { TextStyle } from '../../TextStyle';\n\nconst genericFontFamilies = [\n 'serif',\n 'sans-serif',\n 'monospace',\n 'cursive',\n 'fantasy',\n 'system-ui',\n];\n\n/**\n * Generates a font style string to use for `TextMetrics.measureFont()`.\n * @param style\n * @returns Font style string, for passing to `TextMetrics.measureFont()`\n */\nexport function fontStringFromTextStyle(style: TextStyle): string\n{\n // build canvas api font setting from individual components. Convert a numeric style.fontSize to px\n const fontSizeString = (typeof style.fontSize === 'number') ? `${style.fontSize}px` : style.fontSize;\n\n // Clean-up fontFamily property by quoting each font name\n // this will support font names with spaces\n let fontFamilies: string | string[] = style.fontFamily;\n\n if (!Array.isArray(style.fontFamily))\n {\n fontFamilies = style.fontFamily.split(',');\n }\n\n for (let i = fontFamilies.length - 1; i >= 0; i--)\n {\n // Trim any extra white-space\n let fontFamily = fontFamilies[i].trim();\n\n // Check if font already contains strings\n if (!(/([\\\"\\'])[^\\'\\\"]+\\1/).test(fontFamily) && !genericFontFamilies.includes(fontFamily))\n {\n fontFamily = `\"${fontFamily}\"`;\n }\n (fontFamilies as string[])[i] = fontFamily;\n }\n\n // eslint-disable-next-line max-len\n return `${style.fontStyle} ${style.fontVariant} ${style.fontWeight} ${fontSizeString} ${(fontFamilies as string[]).join(',')}`;\n}\n"],"names":[],"mappings":";AAEA,MAAM,mBAAsB,GAAA;AAAA,EACxB,OAAA;AAAA,EACA,YAAA;AAAA,EACA,WAAA;AAAA,EACA,SAAA;AAAA,EACA,SAAA;AAAA,EACA,WAAA;AACJ,CAAA,CAAA;AAOO,SAAS,wBAAwB,KACxC,EAAA;AAEI,EAAM,MAAA,cAAA,GAAkB,OAAO,KAAM,CAAA,QAAA,KAAa,WAAY,CAAG,EAAA,KAAA,CAAM,QAAQ,CAAA,EAAA,CAAA,GAAO,KAAM,CAAA,QAAA,CAAA;AAI5F,EAAA,IAAI,eAAkC,KAAM,CAAA,UAAA,CAAA;AAE5C,EAAA,IAAI,CAAC,KAAA,CAAM,OAAQ,CAAA,KAAA,CAAM,UAAU,CACnC,EAAA;AACI,IAAe,YAAA,GAAA,KAAA,CAAM,UAAW,CAAA,KAAA,CAAM,GAAG,CAAA,CAAA;AAAA,GAC7C;AAEA,EAAA,KAAA,IAAS,IAAI,YAAa,CAAA,MAAA,GAAS,CAAG,EAAA,CAAA,IAAK,GAAG,CAC9C,EAAA,EAAA;AAEI,IAAA,IAAI,UAAa,GAAA,YAAA,CAAa,CAAC,CAAA,CAAE,IAAK,EAAA,CAAA;AAGtC,IAAI,IAAA,CAAE,qBAAsB,IAAK,CAAA,UAAU,KAAK,CAAC,mBAAA,CAAoB,QAAS,CAAA,UAAU,CACxF,EAAA;AACI,MAAA,UAAA,GAAa,IAAI,UAAU,CAAA,CAAA,CAAA,CAAA;AAAA,KAC/B;AACA,IAAC,YAAA,CAA0B,CAAC,CAAI,GAAA,UAAA,CAAA;AAAA,GACpC;AAGA,EAAA,OAAO,CAAG,EAAA,KAAA,CAAM,SAAS,CAAA,CAAA,EAAI,MAAM,WAAW,CAAA,CAAA,EAAI,KAAM,CAAA,UAAU,IAAI,cAAc,CAAA,CAAA,EAAK,YAA0B,CAAA,IAAA,CAAK,GAAG,CAAC,CAAA,CAAA,CAAA;AAChI;;;;"}

View File

@@ -0,0 +1,3 @@
import type { ICanvasRenderingContext2D } from '../../../../environment/canvas/ICanvasRenderingContext2D';
import type { ConvertedFillStyle } from '../../../graphics/shared/FillTypes';
export declare function getCanvasFillStyle(fillStyle: ConvertedFillStyle, context: ICanvasRenderingContext2D): string | CanvasGradient | CanvasPattern;

View File

@@ -0,0 +1,50 @@
'use strict';
var Color = require('../../../../color/Color.js');
var Matrix = require('../../../../maths/matrix/Matrix.js');
var Texture = require('../../../../rendering/renderers/shared/texture/Texture.js');
var warn = require('../../../../utils/logging/warn.js');
var FillGradient = require('../../../graphics/shared/fill/FillGradient.js');
var FillPattern = require('../../../graphics/shared/fill/FillPattern.js');
"use strict";
function getCanvasFillStyle(fillStyle, context) {
if (fillStyle.texture === Texture.Texture.WHITE && !fillStyle.fill) {
return Color.Color.shared.setValue(fillStyle.color).setAlpha(fillStyle.alpha ?? 1).toHexa();
} else if (!fillStyle.fill) {
const pattern = context.createPattern(fillStyle.texture.source.resource, "repeat");
const tempMatrix = fillStyle.matrix.copyTo(Matrix.Matrix.shared);
tempMatrix.scale(fillStyle.texture.frame.width, fillStyle.texture.frame.height);
pattern.setTransform(tempMatrix);
return pattern;
} else if (fillStyle.fill instanceof FillPattern.FillPattern) {
const fillPattern = fillStyle.fill;
const pattern = context.createPattern(fillPattern.texture.source.resource, "repeat");
const tempMatrix = fillPattern.transform.copyTo(Matrix.Matrix.shared);
tempMatrix.scale(
fillPattern.texture.frame.width,
fillPattern.texture.frame.height
);
pattern.setTransform(tempMatrix);
return pattern;
} else if (fillStyle.fill instanceof FillGradient.FillGradient) {
const fillGradient = fillStyle.fill;
if (fillGradient.type === "linear") {
const gradient = context.createLinearGradient(
fillGradient.x0,
fillGradient.y0,
fillGradient.x1,
fillGradient.y1
);
fillGradient.gradientStops.forEach((stop) => {
gradient.addColorStop(stop.offset, Color.Color.shared.setValue(stop.color).toHex());
});
return gradient;
}
}
warn.warn("FillStyle not recognised", fillStyle);
return "red";
}
exports.getCanvasFillStyle = getCanvasFillStyle;
//# sourceMappingURL=getCanvasFillStyle.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"getCanvasFillStyle.js","sources":["../../../../../src/scene/text/canvas/utils/getCanvasFillStyle.ts"],"sourcesContent":["import { Color } from '../../../../color/Color';\nimport { Matrix } from '../../../../maths/matrix/Matrix';\nimport { Texture } from '../../../../rendering/renderers/shared/texture/Texture';\nimport { warn } from '../../../../utils/logging/warn';\nimport { FillGradient } from '../../../graphics/shared/fill/FillGradient';\nimport { FillPattern } from '../../../graphics/shared/fill/FillPattern';\n\nimport type { ICanvasRenderingContext2D } from '../../../../environment/canvas/ICanvasRenderingContext2D';\nimport type { ConvertedFillStyle } from '../../../graphics/shared/FillTypes';\n\nexport function getCanvasFillStyle(\n fillStyle: ConvertedFillStyle,\n context: ICanvasRenderingContext2D): string | CanvasGradient | CanvasPattern\n{\n if (fillStyle.texture === Texture.WHITE && !fillStyle.fill)\n {\n return Color.shared.setValue(fillStyle.color).setAlpha(fillStyle.alpha ?? 1).toHexa();\n }\n else if (!fillStyle.fill)\n {\n // fancy set up...\n const pattern = context.createPattern(fillStyle.texture.source.resource, 'repeat');\n\n // create an inverted scale matrix..\n const tempMatrix = fillStyle.matrix.copyTo(Matrix.shared);\n\n tempMatrix.scale(fillStyle.texture.frame.width, fillStyle.texture.frame.height);\n\n pattern.setTransform(tempMatrix);\n\n return pattern;\n }\n else if (fillStyle.fill instanceof FillPattern)\n {\n const fillPattern = fillStyle.fill;\n\n const pattern = context.createPattern(fillPattern.texture.source.resource, 'repeat');\n\n const tempMatrix = fillPattern.transform.copyTo(Matrix.shared);\n\n tempMatrix.scale(\n fillPattern.texture.frame.width,\n fillPattern.texture.frame.height\n );\n\n pattern.setTransform(tempMatrix);\n\n return pattern;\n }\n else if (fillStyle.fill instanceof FillGradient)\n {\n const fillGradient = fillStyle.fill;\n\n if (fillGradient.type === 'linear')\n {\n const gradient = context.createLinearGradient(\n fillGradient.x0,\n fillGradient.y0,\n fillGradient.x1,\n fillGradient.y1\n );\n\n fillGradient.gradientStops.forEach((stop) =>\n {\n gradient.addColorStop(stop.offset, Color.shared.setValue(stop.color).toHex());\n });\n\n return gradient;\n }\n }\n\n // #if _DEBUG\n warn('FillStyle not recognised', fillStyle);\n // #endif\n\n return 'red';\n}\n"],"names":["Texture","Color","Matrix","FillPattern","FillGradient","warn"],"mappings":";;;;;;;;;;AAUgB,SAAA,kBAAA,CACZ,WACA,OACJ,EAAA;AACI,EAAA,IAAI,UAAU,OAAY,KAAAA,eAAA,CAAQ,KAAS,IAAA,CAAC,UAAU,IACtD,EAAA;AACI,IAAO,OAAAC,WAAA,CAAM,MAAO,CAAA,QAAA,CAAS,SAAU,CAAA,KAAK,CAAE,CAAA,QAAA,CAAS,SAAU,CAAA,KAAA,IAAS,CAAC,CAAA,CAAE,MAAO,EAAA,CAAA;AAAA,GACxF,MAAA,IACS,CAAC,SAAA,CAAU,IACpB,EAAA;AAEI,IAAA,MAAM,UAAU,OAAQ,CAAA,aAAA,CAAc,UAAU,OAAQ,CAAA,MAAA,CAAO,UAAU,QAAQ,CAAA,CAAA;AAGjF,IAAA,MAAM,UAAa,GAAA,SAAA,CAAU,MAAO,CAAA,MAAA,CAAOC,cAAO,MAAM,CAAA,CAAA;AAExD,IAAW,UAAA,CAAA,KAAA,CAAM,UAAU,OAAQ,CAAA,KAAA,CAAM,OAAO,SAAU,CAAA,OAAA,CAAQ,MAAM,MAAM,CAAA,CAAA;AAE9E,IAAA,OAAA,CAAQ,aAAa,UAAU,CAAA,CAAA;AAE/B,IAAO,OAAA,OAAA,CAAA;AAAA,GACX,MAAA,IACS,SAAU,CAAA,IAAA,YAAgBC,uBACnC,EAAA;AACI,IAAA,MAAM,cAAc,SAAU,CAAA,IAAA,CAAA;AAE9B,IAAA,MAAM,UAAU,OAAQ,CAAA,aAAA,CAAc,YAAY,OAAQ,CAAA,MAAA,CAAO,UAAU,QAAQ,CAAA,CAAA;AAEnF,IAAA,MAAM,UAAa,GAAA,WAAA,CAAY,SAAU,CAAA,MAAA,CAAOD,cAAO,MAAM,CAAA,CAAA;AAE7D,IAAW,UAAA,CAAA,KAAA;AAAA,MACP,WAAA,CAAY,QAAQ,KAAM,CAAA,KAAA;AAAA,MAC1B,WAAA,CAAY,QAAQ,KAAM,CAAA,MAAA;AAAA,KAC9B,CAAA;AAEA,IAAA,OAAA,CAAQ,aAAa,UAAU,CAAA,CAAA;AAE/B,IAAO,OAAA,OAAA,CAAA;AAAA,GACX,MAAA,IACS,SAAU,CAAA,IAAA,YAAgBE,yBACnC,EAAA;AACI,IAAA,MAAM,eAAe,SAAU,CAAA,IAAA,CAAA;AAE/B,IAAI,IAAA,YAAA,CAAa,SAAS,QAC1B,EAAA;AACI,MAAA,MAAM,WAAW,OAAQ,CAAA,oBAAA;AAAA,QACrB,YAAa,CAAA,EAAA;AAAA,QACb,YAAa,CAAA,EAAA;AAAA,QACb,YAAa,CAAA,EAAA;AAAA,QACb,YAAa,CAAA,EAAA;AAAA,OACjB,CAAA;AAEA,MAAa,YAAA,CAAA,aAAA,CAAc,OAAQ,CAAA,CAAC,IACpC,KAAA;AACI,QAAS,QAAA,CAAA,YAAA,CAAa,IAAK,CAAA,MAAA,EAAQH,WAAM,CAAA,MAAA,CAAO,SAAS,IAAK,CAAA,KAAK,CAAE,CAAA,KAAA,EAAO,CAAA,CAAA;AAAA,OAC/E,CAAA,CAAA;AAED,MAAO,OAAA,QAAA,CAAA;AAAA,KACX;AAAA,GACJ;AAGA,EAAAI,SAAA,CAAK,4BAA4B,SAAS,CAAA,CAAA;AAG1C,EAAO,OAAA,KAAA,CAAA;AACX;;;;"}

View File

@@ -0,0 +1,48 @@
import { Color } from '../../../../color/Color.mjs';
import { Matrix } from '../../../../maths/matrix/Matrix.mjs';
import { Texture } from '../../../../rendering/renderers/shared/texture/Texture.mjs';
import { warn } from '../../../../utils/logging/warn.mjs';
import { FillGradient } from '../../../graphics/shared/fill/FillGradient.mjs';
import { FillPattern } from '../../../graphics/shared/fill/FillPattern.mjs';
"use strict";
function getCanvasFillStyle(fillStyle, context) {
if (fillStyle.texture === Texture.WHITE && !fillStyle.fill) {
return Color.shared.setValue(fillStyle.color).setAlpha(fillStyle.alpha ?? 1).toHexa();
} else if (!fillStyle.fill) {
const pattern = context.createPattern(fillStyle.texture.source.resource, "repeat");
const tempMatrix = fillStyle.matrix.copyTo(Matrix.shared);
tempMatrix.scale(fillStyle.texture.frame.width, fillStyle.texture.frame.height);
pattern.setTransform(tempMatrix);
return pattern;
} else if (fillStyle.fill instanceof FillPattern) {
const fillPattern = fillStyle.fill;
const pattern = context.createPattern(fillPattern.texture.source.resource, "repeat");
const tempMatrix = fillPattern.transform.copyTo(Matrix.shared);
tempMatrix.scale(
fillPattern.texture.frame.width,
fillPattern.texture.frame.height
);
pattern.setTransform(tempMatrix);
return pattern;
} else if (fillStyle.fill instanceof FillGradient) {
const fillGradient = fillStyle.fill;
if (fillGradient.type === "linear") {
const gradient = context.createLinearGradient(
fillGradient.x0,
fillGradient.y0,
fillGradient.x1,
fillGradient.y1
);
fillGradient.gradientStops.forEach((stop) => {
gradient.addColorStop(stop.offset, Color.shared.setValue(stop.color).toHex());
});
return gradient;
}
}
warn("FillStyle not recognised", fillStyle);
return "red";
}
export { getCanvasFillStyle };
//# sourceMappingURL=getCanvasFillStyle.mjs.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"getCanvasFillStyle.mjs","sources":["../../../../../src/scene/text/canvas/utils/getCanvasFillStyle.ts"],"sourcesContent":["import { Color } from '../../../../color/Color';\nimport { Matrix } from '../../../../maths/matrix/Matrix';\nimport { Texture } from '../../../../rendering/renderers/shared/texture/Texture';\nimport { warn } from '../../../../utils/logging/warn';\nimport { FillGradient } from '../../../graphics/shared/fill/FillGradient';\nimport { FillPattern } from '../../../graphics/shared/fill/FillPattern';\n\nimport type { ICanvasRenderingContext2D } from '../../../../environment/canvas/ICanvasRenderingContext2D';\nimport type { ConvertedFillStyle } from '../../../graphics/shared/FillTypes';\n\nexport function getCanvasFillStyle(\n fillStyle: ConvertedFillStyle,\n context: ICanvasRenderingContext2D): string | CanvasGradient | CanvasPattern\n{\n if (fillStyle.texture === Texture.WHITE && !fillStyle.fill)\n {\n return Color.shared.setValue(fillStyle.color).setAlpha(fillStyle.alpha ?? 1).toHexa();\n }\n else if (!fillStyle.fill)\n {\n // fancy set up...\n const pattern = context.createPattern(fillStyle.texture.source.resource, 'repeat');\n\n // create an inverted scale matrix..\n const tempMatrix = fillStyle.matrix.copyTo(Matrix.shared);\n\n tempMatrix.scale(fillStyle.texture.frame.width, fillStyle.texture.frame.height);\n\n pattern.setTransform(tempMatrix);\n\n return pattern;\n }\n else if (fillStyle.fill instanceof FillPattern)\n {\n const fillPattern = fillStyle.fill;\n\n const pattern = context.createPattern(fillPattern.texture.source.resource, 'repeat');\n\n const tempMatrix = fillPattern.transform.copyTo(Matrix.shared);\n\n tempMatrix.scale(\n fillPattern.texture.frame.width,\n fillPattern.texture.frame.height\n );\n\n pattern.setTransform(tempMatrix);\n\n return pattern;\n }\n else if (fillStyle.fill instanceof FillGradient)\n {\n const fillGradient = fillStyle.fill;\n\n if (fillGradient.type === 'linear')\n {\n const gradient = context.createLinearGradient(\n fillGradient.x0,\n fillGradient.y0,\n fillGradient.x1,\n fillGradient.y1\n );\n\n fillGradient.gradientStops.forEach((stop) =>\n {\n gradient.addColorStop(stop.offset, Color.shared.setValue(stop.color).toHex());\n });\n\n return gradient;\n }\n }\n\n // #if _DEBUG\n warn('FillStyle not recognised', fillStyle);\n // #endif\n\n return 'red';\n}\n"],"names":[],"mappings":";;;;;;;;AAUgB,SAAA,kBAAA,CACZ,WACA,OACJ,EAAA;AACI,EAAA,IAAI,UAAU,OAAY,KAAA,OAAA,CAAQ,KAAS,IAAA,CAAC,UAAU,IACtD,EAAA;AACI,IAAO,OAAA,KAAA,CAAM,MAAO,CAAA,QAAA,CAAS,SAAU,CAAA,KAAK,CAAE,CAAA,QAAA,CAAS,SAAU,CAAA,KAAA,IAAS,CAAC,CAAA,CAAE,MAAO,EAAA,CAAA;AAAA,GACxF,MAAA,IACS,CAAC,SAAA,CAAU,IACpB,EAAA;AAEI,IAAA,MAAM,UAAU,OAAQ,CAAA,aAAA,CAAc,UAAU,OAAQ,CAAA,MAAA,CAAO,UAAU,QAAQ,CAAA,CAAA;AAGjF,IAAA,MAAM,UAAa,GAAA,SAAA,CAAU,MAAO,CAAA,MAAA,CAAO,OAAO,MAAM,CAAA,CAAA;AAExD,IAAW,UAAA,CAAA,KAAA,CAAM,UAAU,OAAQ,CAAA,KAAA,CAAM,OAAO,SAAU,CAAA,OAAA,CAAQ,MAAM,MAAM,CAAA,CAAA;AAE9E,IAAA,OAAA,CAAQ,aAAa,UAAU,CAAA,CAAA;AAE/B,IAAO,OAAA,OAAA,CAAA;AAAA,GACX,MAAA,IACS,SAAU,CAAA,IAAA,YAAgB,WACnC,EAAA;AACI,IAAA,MAAM,cAAc,SAAU,CAAA,IAAA,CAAA;AAE9B,IAAA,MAAM,UAAU,OAAQ,CAAA,aAAA,CAAc,YAAY,OAAQ,CAAA,MAAA,CAAO,UAAU,QAAQ,CAAA,CAAA;AAEnF,IAAA,MAAM,UAAa,GAAA,WAAA,CAAY,SAAU,CAAA,MAAA,CAAO,OAAO,MAAM,CAAA,CAAA;AAE7D,IAAW,UAAA,CAAA,KAAA;AAAA,MACP,WAAA,CAAY,QAAQ,KAAM,CAAA,KAAA;AAAA,MAC1B,WAAA,CAAY,QAAQ,KAAM,CAAA,MAAA;AAAA,KAC9B,CAAA;AAEA,IAAA,OAAA,CAAQ,aAAa,UAAU,CAAA,CAAA;AAE/B,IAAO,OAAA,OAAA,CAAA;AAAA,GACX,MAAA,IACS,SAAU,CAAA,IAAA,YAAgB,YACnC,EAAA;AACI,IAAA,MAAM,eAAe,SAAU,CAAA,IAAA,CAAA;AAE/B,IAAI,IAAA,YAAA,CAAa,SAAS,QAC1B,EAAA;AACI,MAAA,MAAM,WAAW,OAAQ,CAAA,oBAAA;AAAA,QACrB,YAAa,CAAA,EAAA;AAAA,QACb,YAAa,CAAA,EAAA;AAAA,QACb,YAAa,CAAA,EAAA;AAAA,QACb,YAAa,CAAA,EAAA;AAAA,OACjB,CAAA;AAEA,MAAa,YAAA,CAAA,aAAA,CAAc,OAAQ,CAAA,CAAC,IACpC,KAAA;AACI,QAAS,QAAA,CAAA,YAAA,CAAa,IAAK,CAAA,MAAA,EAAQ,KAAM,CAAA,MAAA,CAAO,SAAS,IAAK,CAAA,KAAK,CAAE,CAAA,KAAA,EAAO,CAAA,CAAA;AAAA,OAC/E,CAAA,CAAA;AAED,MAAO,OAAA,QAAA,CAAA;AAAA,KACX;AAAA,GACJ;AAGA,EAAA,IAAA,CAAK,4BAA4B,SAAS,CAAA,CAAA;AAG1C,EAAO,OAAA,KAAA,CAAA;AACX;;;;"}