pixi.js
Version:
<p align="center"> <a href="https://pixijs.com" target="_blank" rel="noopener noreferrer"> <img height="150" src="https://files.pixijs.download/branding/pixijs-logo-transparent-dark.svg?v=1" alt="PixiJS logo"> </a> </p> <br/> <p align="center">
496 lines (492 loc) • 15.5 kB
JavaScript
'use strict';
var EventEmitter = require('eventemitter3');
var Color = require('../../color/Color.js');
var deprecation = require('../../utils/logging/deprecation.js');
var FillGradient = require('../graphics/shared/fill/FillGradient.js');
var FillPattern = require('../graphics/shared/fill/FillPattern.js');
var GraphicsContext = require('../graphics/shared/GraphicsContext.js');
var convertFillInputToFillStyle = require('../graphics/shared/utils/convertFillInputToFillStyle.js');
var generateTextStyleKey = require('./utils/generateTextStyleKey.js');
"use strict";
const _TextStyle = class _TextStyle extends EventEmitter {
constructor(style = {}) {
super();
convertV7Tov8Style(style);
const fullStyle = { ..._TextStyle.defaultTextStyle, ...style };
for (const key in fullStyle) {
const thisKey = key;
this[thisKey] = fullStyle[key];
}
this.update();
}
/**
* Alignment for multiline text, does not affect single line text.
* @type {'left'|'center'|'right'|'justify'}
*/
get align() {
return this._align;
}
set align(value) {
this._align = value;
this.update();
}
/** Indicates if lines can be wrapped within words, it needs wordWrap to be set to true. */
get breakWords() {
return this._breakWords;
}
set breakWords(value) {
this._breakWords = value;
this.update();
}
/** Set a drop shadow for the text. */
get dropShadow() {
return this._dropShadow;
}
set dropShadow(value) {
if (value !== null && typeof value === "object") {
this._dropShadow = this._createProxy({ ..._TextStyle.defaultDropShadow, ...value });
} else {
this._dropShadow = value ? this._createProxy({ ..._TextStyle.defaultDropShadow }) : null;
}
this.update();
}
/** The font family, can be a single font name, or a list of names where the first is the preferred font. */
get fontFamily() {
return this._fontFamily;
}
set fontFamily(value) {
this._fontFamily = value;
this.update();
}
/** The font size (as a number it converts to px, but as a string, equivalents are '26px','20pt','160%' or '1.6em') */
get fontSize() {
return this._fontSize;
}
set fontSize(value) {
if (typeof value === "string") {
this._fontSize = parseInt(value, 10);
} else {
this._fontSize = value;
}
this.update();
}
/**
* The font style.
* @type {'normal'|'italic'|'oblique'}
*/
get fontStyle() {
return this._fontStyle;
}
set fontStyle(value) {
this._fontStyle = value.toLowerCase();
this.update();
}
/**
* The font variant.
* @type {'normal'|'small-caps'}
*/
get fontVariant() {
return this._fontVariant;
}
set fontVariant(value) {
this._fontVariant = value;
this.update();
}
/**
* The font weight.
* @type {'normal'|'bold'|'bolder'|'lighter'|'100'|'200'|'300'|'400'|'500'|'600'|'700'|'800'|'900'}
*/
get fontWeight() {
return this._fontWeight;
}
set fontWeight(value) {
this._fontWeight = value;
this.update();
}
/** The space between lines. */
get leading() {
return this._leading;
}
set leading(value) {
this._leading = value;
this.update();
}
/** The amount of spacing between letters, default is 0. */
get letterSpacing() {
return this._letterSpacing;
}
set letterSpacing(value) {
this._letterSpacing = value;
this.update();
}
/** The line height, a number that represents the vertical space that a letter uses. */
get lineHeight() {
return this._lineHeight;
}
set lineHeight(value) {
this._lineHeight = value;
this.update();
}
/**
* Occasionally some fonts are cropped. Adding some padding will prevent this from happening
* by adding padding to all sides of the text.
* > [!NOTE] This will NOT affect the positioning or bounds of the text.
*/
get padding() {
return this._padding;
}
set padding(value) {
this._padding = value;
this.update();
}
/**
* An optional filter or array of filters to apply to the text, allowing for advanced visual effects.
* These filters will be applied to the text as it is created, resulting in faster rendering for static text
* compared to applying the filter directly to the text object (which would be applied at run time).
* @default null
*/
get filters() {
return this._filters;
}
set filters(value) {
this._filters = value;
this.update();
}
/**
* Trim transparent borders from the text texture.
* > [!IMPORTANT] PERFORMANCE WARNING:
* > This is a costly operation as it requires scanning pixel alpha values.
* > Avoid using `trim: true` for dynamic text, as it could significantly impact performance.
*/
get trim() {
return this._trim;
}
set trim(value) {
this._trim = value;
this.update();
}
/**
* The baseline of the text that is rendered.
* @type {'alphabetic'|'top'|'hanging'|'middle'|'ideographic'|'bottom'}
*/
get textBaseline() {
return this._textBaseline;
}
set textBaseline(value) {
this._textBaseline = value;
this.update();
}
/**
* How newlines and spaces should be handled.
* Default is 'pre' (preserve, preserve).
*
* value | New lines | Spaces
* --- | --- | ---
* 'normal' | Collapse | Collapse
* 'pre' | Preserve | Preserve
* 'pre-line' | Preserve | Collapse
* @type {'normal'|'pre'|'pre-line'}
*/
get whiteSpace() {
return this._whiteSpace;
}
set whiteSpace(value) {
this._whiteSpace = value;
this.update();
}
/** Indicates if word wrap should be used. */
get wordWrap() {
return this._wordWrap;
}
set wordWrap(value) {
this._wordWrap = value;
this.update();
}
/** The width at which text will wrap, it needs wordWrap to be set to true. */
get wordWrapWidth() {
return this._wordWrapWidth;
}
set wordWrapWidth(value) {
this._wordWrapWidth = value;
this.update();
}
/**
* The fill style that will be used to color the text.
* This can be:
* - A color string like 'red', '#00FF00', or 'rgba(255,0,0,0.5)'
* - A hex number like 0xff0000 for red
* - A FillStyle object with properties like { color: 0xff0000, alpha: 0.5 }
* - A FillGradient for gradient fills
* - A FillPattern for pattern/texture fills
*
* When using a FillGradient, vertical gradients (angle of 90 degrees) are applied per line of text,
* while gradients at any other angle are spread across the entire text body as a whole.
* @example
* // Vertical gradient applied per line
* const verticalGradient = new FillGradient(0, 0, 0, 1)
* .addColorStop(0, 0xff0000)
* .addColorStop(1, 0x0000ff);
*
* const text = new Text({
* text: 'Line 1\nLine 2',
* style: { fill: verticalGradient }
* });
*
* To manage the gradient in a global scope, set the textureSpace property of the FillGradient to 'global'.
* @type {string|number|FillStyle|FillGradient|FillPattern}
*/
get fill() {
return this._originalFill;
}
set fill(value) {
if (value === this._originalFill)
return;
this._originalFill = value;
if (this._isFillStyle(value)) {
this._originalFill = this._createProxy({ ...GraphicsContext.GraphicsContext.defaultFillStyle, ...value }, () => {
this._fill = convertFillInputToFillStyle.toFillStyle(
{ ...this._originalFill },
GraphicsContext.GraphicsContext.defaultFillStyle
);
});
}
this._fill = convertFillInputToFillStyle.toFillStyle(
value === 0 ? "black" : value,
GraphicsContext.GraphicsContext.defaultFillStyle
);
this.update();
}
/** A fillstyle that will be used on the text stroke, e.g., 'blue', '#FCFF00'. */
get stroke() {
return this._originalStroke;
}
set stroke(value) {
if (value === this._originalStroke)
return;
this._originalStroke = value;
if (this._isFillStyle(value)) {
this._originalStroke = this._createProxy({ ...GraphicsContext.GraphicsContext.defaultStrokeStyle, ...value }, () => {
this._stroke = convertFillInputToFillStyle.toStrokeStyle(
{ ...this._originalStroke },
GraphicsContext.GraphicsContext.defaultStrokeStyle
);
});
}
this._stroke = convertFillInputToFillStyle.toStrokeStyle(value, GraphicsContext.GraphicsContext.defaultStrokeStyle);
this.update();
}
_generateKey() {
this._styleKey = generateTextStyleKey.generateTextStyleKey(this);
return this._styleKey;
}
update() {
this._styleKey = null;
this.emit("update", this);
}
/** Resets all properties to the default values */
reset() {
const defaultStyle = _TextStyle.defaultTextStyle;
for (const key in defaultStyle) {
this[key] = defaultStyle[key];
}
}
/** @internal */
get styleKey() {
return this._styleKey || this._generateKey();
}
/**
* Creates a new TextStyle object with the same values as this one.
* @returns New cloned TextStyle object
*/
clone() {
return new _TextStyle({
align: this.align,
breakWords: this.breakWords,
dropShadow: this._dropShadow ? { ...this._dropShadow } : null,
fill: this._fill,
fontFamily: this.fontFamily,
fontSize: this.fontSize,
fontStyle: this.fontStyle,
fontVariant: this.fontVariant,
fontWeight: this.fontWeight,
leading: this.leading,
letterSpacing: this.letterSpacing,
lineHeight: this.lineHeight,
padding: this.padding,
stroke: this._stroke,
textBaseline: this.textBaseline,
whiteSpace: this.whiteSpace,
wordWrap: this.wordWrap,
wordWrapWidth: this.wordWrapWidth,
filters: this._filters ? [...this._filters] : void 0
});
}
/**
* Returns the final padding for the text style, taking into account any filters applied.
* Used internally for correct measurements
* @internal
* @returns {number} The final padding for the text style.
*/
_getFinalPadding() {
let filterPadding = 0;
if (this._filters) {
for (let i = 0; i < this._filters.length; i++) {
filterPadding += this._filters[i].padding;
}
}
return Math.max(this._padding, filterPadding);
}
/**
* Destroys this text style.
* @param options - Options parameter. A boolean will act as if all options
* have been set to that value
* @example
* // Destroy the text style and its textures
* textStyle.destroy({ texture: true, textureSource: true });
* textStyle.destroy(true);
*/
destroy(options = false) {
this.removeAllListeners();
const destroyTexture = typeof options === "boolean" ? options : options?.texture;
if (destroyTexture) {
const destroyTextureSource = typeof options === "boolean" ? options : options?.textureSource;
if (this._fill?.texture) {
this._fill.texture.destroy(destroyTextureSource);
}
if (this._originalFill?.texture) {
this._originalFill.texture.destroy(destroyTextureSource);
}
if (this._stroke?.texture) {
this._stroke.texture.destroy(destroyTextureSource);
}
if (this._originalStroke?.texture) {
this._originalStroke.texture.destroy(destroyTextureSource);
}
}
this._fill = null;
this._stroke = null;
this.dropShadow = null;
this._originalStroke = null;
this._originalFill = null;
}
_createProxy(value, cb) {
return new Proxy(value, {
set: (target, property, newValue) => {
target[property] = newValue;
cb?.(property, newValue);
this.update();
return true;
}
});
}
_isFillStyle(value) {
return (value ?? null) !== null && !(Color.Color.isColorLike(value) || value instanceof FillGradient.FillGradient || value instanceof FillPattern.FillPattern);
}
};
/**
* Default drop shadow settings used when enabling drop shadows on text.
* These values are used as the base configuration when drop shadows are enabled without specific settings.
* @example
* ```ts
* // Customize default settings globally
* TextStyle.defaultDropShadow.alpha = 0.5; // 50% opacity for all shadows
* TextStyle.defaultDropShadow.blur = 2; // 2px blur for all shadows
* TextStyle.defaultDropShadow.color = 'blue'; // Blue shadows by default
* ```
*/
_TextStyle.defaultDropShadow = {
alpha: 1,
angle: Math.PI / 6,
blur: 0,
color: "black",
distance: 5
};
/**
* Default text style settings used when creating new text objects.
* These values serve as the base configuration and can be customized globally.
* @example
* ```ts
* // Customize default text style globally
* TextStyle.defaultTextStyle.fontSize = 16;
* TextStyle.defaultTextStyle.fill = 0x333333;
* TextStyle.defaultTextStyle.fontFamily = ['Arial', 'Helvetica', 'sans-serif'];
* ```
*/
_TextStyle.defaultTextStyle = {
align: "left",
breakWords: false,
dropShadow: null,
fill: "black",
fontFamily: "Arial",
fontSize: 26,
fontStyle: "normal",
fontVariant: "normal",
fontWeight: "normal",
leading: 0,
letterSpacing: 0,
lineHeight: 0,
padding: 0,
stroke: null,
textBaseline: "alphabetic",
trim: false,
whiteSpace: "pre",
wordWrap: false,
wordWrapWidth: 100
};
let TextStyle = _TextStyle;
function convertV7Tov8Style(style) {
const oldStyle = style;
if (typeof oldStyle.dropShadow === "boolean" && oldStyle.dropShadow) {
const defaults = TextStyle.defaultDropShadow;
style.dropShadow = {
alpha: oldStyle.dropShadowAlpha ?? defaults.alpha,
angle: oldStyle.dropShadowAngle ?? defaults.angle,
blur: oldStyle.dropShadowBlur ?? defaults.blur,
color: oldStyle.dropShadowColor ?? defaults.color,
distance: oldStyle.dropShadowDistance ?? defaults.distance
};
}
if (oldStyle.strokeThickness !== void 0) {
deprecation.deprecation(deprecation.v8_0_0, "strokeThickness is now a part of stroke");
const color = oldStyle.stroke;
let obj = {};
if (Color.Color.isColorLike(color)) {
obj.color = color;
} else if (color instanceof FillGradient.FillGradient || color instanceof FillPattern.FillPattern) {
obj.fill = color;
} else if (Object.hasOwnProperty.call(color, "color") || Object.hasOwnProperty.call(color, "fill")) {
obj = color;
} else {
throw new Error("Invalid stroke value.");
}
style.stroke = {
...obj,
width: oldStyle.strokeThickness
};
}
if (Array.isArray(oldStyle.fillGradientStops)) {
deprecation.deprecation(deprecation.v8_0_0, "gradient fill is now a fill pattern: `new FillGradient(...)`");
let fontSize;
if (style.fontSize == null) {
style.fontSize = TextStyle.defaultTextStyle.fontSize;
} else if (typeof style.fontSize === "string") {
fontSize = parseInt(style.fontSize, 10);
} else {
fontSize = style.fontSize;
}
const gradientFill = new FillGradient.FillGradient({
start: { x: 0, y: 0 },
end: { x: 0, y: (fontSize || 0) * 1.7 }
});
const fills = oldStyle.fillGradientStops.map((color) => Color.Color.shared.setValue(color).toNumber());
fills.forEach((number, index) => {
const ratio = index / (fills.length - 1);
gradientFill.addColorStop(ratio, number);
});
style.fill = {
fill: gradientFill
};
}
}
exports.TextStyle = TextStyle;
//# sourceMappingURL=TextStyle.js.map