465 lines
14 KiB
JavaScript
465 lines
14 KiB
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.
|
|
* @member {'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.
|
|
* @member {'normal'|'italic'|'oblique'}
|
|
*/
|
|
get fontStyle() {
|
|
return this._fontStyle;
|
|
}
|
|
set fontStyle(value) {
|
|
this._fontStyle = value;
|
|
this.update();
|
|
}
|
|
/**
|
|
* The font variant.
|
|
* @member {'normal'|'small-caps'}
|
|
*/
|
|
get fontVariant() {
|
|
return this._fontVariant;
|
|
}
|
|
set fontVariant(value) {
|
|
this._fontVariant = value;
|
|
this.update();
|
|
}
|
|
/**
|
|
* The font weight.
|
|
* @member {'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.
|
|
*/
|
|
get padding() {
|
|
return this._padding;
|
|
}
|
|
set padding(value) {
|
|
this._padding = value;
|
|
this.update();
|
|
}
|
|
/** Trim transparent borders. This is an expensive operation so only use this if you have to! */
|
|
get trim() {
|
|
return this._trim;
|
|
}
|
|
set trim(value) {
|
|
this._trim = value;
|
|
this.update();
|
|
}
|
|
/**
|
|
* The baseline of the text that is rendered.
|
|
* @member {'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
|
|
* @member {'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();
|
|
}
|
|
/** A fillstyle that will be used on the text e.g., 'red', '#00FF00'. */
|
|
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];
|
|
}
|
|
}
|
|
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
|
|
});
|
|
}
|
|
/**
|
|
* Destroys this text style.
|
|
* @param options - Options parameter. A boolean will act as if all options
|
|
* have been set to that value
|
|
* @param {boolean} [options.texture=false] - Should it destroy the texture of the this style
|
|
* @param {boolean} [options.textureSource=false] - Should it destroy the textureSource of the this style
|
|
*/
|
|
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);
|
|
}
|
|
};
|
|
/** The default drop shadow settings */
|
|
_TextStyle.defaultDropShadow = {
|
|
/** Set alpha for the drop shadow */
|
|
alpha: 1,
|
|
/** Set a angle of the drop shadow */
|
|
angle: Math.PI / 6,
|
|
/** Set a shadow blur radius */
|
|
blur: 0,
|
|
/** A fill style to be used on the e.g., 'red', '#00FF00' */
|
|
color: "black",
|
|
/** Set a distance of the drop shadow */
|
|
distance: 5
|
|
};
|
|
/** The default text style settings */
|
|
_TextStyle.defaultTextStyle = {
|
|
/**
|
|
* See {@link TextStyle.align}
|
|
* @type {'left'|'center'|'right'|'justify'}
|
|
*/
|
|
align: "left",
|
|
/** See {@link TextStyle.breakWords} */
|
|
breakWords: false,
|
|
/** See {@link TextStyle.dropShadow} */
|
|
dropShadow: null,
|
|
/**
|
|
* See {@link TextStyle.fill}
|
|
* @type {string|string[]|number|number[]|CanvasGradient|CanvasPattern}
|
|
*/
|
|
fill: "black",
|
|
/**
|
|
* See {@link TextStyle.fontFamily}
|
|
* @type {string|string[]}
|
|
*/
|
|
fontFamily: "Arial",
|
|
/**
|
|
* See {@link TextStyle.fontSize}
|
|
* @type {number|string}
|
|
*/
|
|
fontSize: 26,
|
|
/**
|
|
* See {@link TextStyle.fontStyle}
|
|
* @type {'normal'|'italic'|'oblique'}
|
|
*/
|
|
fontStyle: "normal",
|
|
/**
|
|
* See {@link TextStyle.fontVariant}
|
|
* @type {'normal'|'small-caps'}
|
|
*/
|
|
fontVariant: "normal",
|
|
/**
|
|
* See {@link TextStyle.fontWeight}
|
|
* @type {'normal'|'bold'|'bolder'|'lighter'|'100'|'200'|'300'|'400'|'500'|'600'|'700'|'800'|'900'}
|
|
*/
|
|
fontWeight: "normal",
|
|
/** See {@link TextStyle.leading} */
|
|
leading: 0,
|
|
/** See {@link TextStyle.letterSpacing} */
|
|
letterSpacing: 0,
|
|
/** See {@link TextStyle.lineHeight} */
|
|
lineHeight: 0,
|
|
/** See {@link TextStyle.padding} */
|
|
padding: 0,
|
|
/**
|
|
* See {@link TextStyle.stroke}
|
|
* @type {string|number}
|
|
*/
|
|
stroke: null,
|
|
/**
|
|
* See {@link TextStyle.textBaseline}
|
|
* @type {'alphabetic'|'top'|'hanging'|'middle'|'ideographic'|'bottom'}
|
|
*/
|
|
textBaseline: "alphabetic",
|
|
/** See {@link TextStyle.trim} */
|
|
trim: false,
|
|
/**
|
|
* See {@link TextStyle.whiteSpace}
|
|
* @type {'normal'|'pre'|'pre-line'}
|
|
*/
|
|
whiteSpace: "pre",
|
|
/** See {@link TextStyle.wordWrap} */
|
|
wordWrap: false,
|
|
/** See {@link TextStyle.wordWrapWidth} */
|
|
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(0, 0, 0, fontSize * 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
|