diff --git a/src/HTMLText.ts b/src/HTMLText.ts
index 42fb56f..9fe3172 100644
--- a/src/HTMLText.ts
+++ b/src/HTMLText.ts
@@ -1,5 +1,5 @@
import { Sprite } from '@pixi/sprite';
-import { Texture, Rectangle, settings, utils, ICanvas, ICanvasRenderingContext2D } from '@pixi/core';
+import { Texture, Rectangle, settings, utils, ICanvas, ICanvasRenderingContext2D, ISize } from '@pixi/core';
import { TextStyle } from '@pixi/text';
import { HTMLTextStyle } from './HTMLTextStyle';
@@ -16,7 +16,13 @@ import type { IDestroyOptions } from '@pixi/display';
*/
export class HTMLText extends Sprite
{
- /** Default opens when destroying */
+ /**
+ * Default opens when destroying.
+ * @type {PIXI.IDestroyOptions}
+ * @property {boolean} texture=true - Whether to destroy the texture.
+ * @property {boolean} children=false - Whether to destroy the children.
+ * @property {boolean} baseTexture=true - Whether to destroy the base texture.
+ */
public static defaultDestroyOptions: IDestroyOptions = {
texture: true,
children: false,
@@ -124,6 +130,56 @@ export class HTMLText extends Sprite
this.style = style;
}
+ /**
+ * Calculate the size of the output text without actually drawing it.
+ * This includes the `padding` in the `style` object.
+ * This can be used as a fast-pass to do things like text-fitting.
+ * @param {object} [overrides] - Overrides for the text, style, and resolution.
+ * @param {string} [overrides.text] - The text to measure, if not specified, the current text is used.
+ * @param {HTMLTextStyle} [overrides.style] - The style to measure, if not specified, the current style is used.
+ * @param {number} [overrides.resolution] - The resolution to measure, if not specified, the current resolution is used.
+ * @return {PIXI.ISize} Width and height of the measured text.
+ */
+ measureText(overrides?: { text?: string, style?: HTMLTextStyle, resolution?: number }): ISize
+ {
+ const { text, style, resolution } = Object.assign({
+ text: this._text,
+ style: this._style,
+ resolution: this._resolution,
+ }, overrides);
+
+ Object.assign(this._domElement, {
+ innerHTML: text,
+ style: style.toCSS(resolution),
+ });
+ this._styleElement.textContent = style.toGlobalCSS();
+
+ // Measure the contents using the shadow DOM
+ const contentBounds = this._domElement.getBoundingClientRect();
+
+ const contentWidth = Math.min(this.maxWidth, Math.ceil(contentBounds.width));
+ const contentHeight = Math.min(this.maxHeight, Math.ceil(contentBounds.height));
+
+ this._svgRoot.setAttribute('width', contentWidth.toString());
+ this._svgRoot.setAttribute('height', contentHeight.toString());
+
+ // Undo the changes to the DOM element
+ if (text !== this._text)
+ {
+ this._domElement.innerHTML = this._text as string;
+ }
+ if (style !== this._style)
+ {
+ Object.assign(this._domElement, { style: this._style?.toCSS(resolution) });
+ this._styleElement.textContent = this._style?.toGlobalCSS() as string;
+ }
+
+ return {
+ width: contentWidth + (style.padding * 2),
+ height: contentHeight + (style.padding * 2),
+ };
+ }
+
/**
* Manually refresh the text.
* @public
@@ -132,7 +188,7 @@ export class HTMLText extends Sprite
*/
updateText(respectDirty = true): void
{
- const { style, resolution, canvas, context } = this;
+ const { style, canvas, context } = this;
// check if style has changed..
if (this.localStyleID !== style.styleID)
@@ -146,23 +202,12 @@ export class HTMLText extends Sprite
return;
}
- Object.assign(this._domElement, {
- innerHTML: this._text,
- style: style.toCSS(resolution),
- });
- this._styleElement.textContent = style.toGlobalCSS();
-
- // Measure the contents using the shadow DOM
- const contentBounds = this._domElement.getBoundingClientRect();
-
- const width = Math.min(this.maxWidth, Math.ceil(contentBounds.width));
- const height = Math.min(this.maxHeight, Math.ceil(contentBounds.height));
-
- this._svgRoot.setAttribute('width', width.toString());
- this._svgRoot.setAttribute('height', height.toString());
+ const { width, height } = this.measureText();
- canvas.width = Math.ceil((Math.max(1, width) + (style.padding * 2)));
- canvas.height = Math.ceil((Math.max(1, height) + (style.padding * 2)));
+ // Make sure canvas is at least 1x1 so it drawable
+ // for sub-pixel sizes, round up to avoid clipping
+ canvas.width = Math.ceil((Math.max(1, width)));
+ canvas.height = Math.ceil((Math.max(1, height)));
if (!this._loading)
{
diff --git a/test/HTMLText.test.ts b/test/HTMLText.test.ts
index cb3b3b8..d3586d7 100644
--- a/test/HTMLText.test.ts
+++ b/test/HTMLText.test.ts
@@ -1,4 +1,5 @@
import { HTMLText } from '../src/HTMLText';
+import { HTMLTextStyle } from '../src/HTMLTextStyle';
describe('HTMLText', () =>
{
@@ -43,4 +44,84 @@ describe('HTMLText', () =>
expect(document.querySelector(query)).toBeFalsy();
});
+
+ describe('measureText', () =>
+ {
+ it('should measure default text', () =>
+ {
+ const text = new HTMLText('Hello world!');
+ const size = text.measureText();
+
+ expect(size).toBeTruthy();
+ expect(size.width).toBeGreaterThan(0);
+ expect(size.height).toBeGreaterThan(0);
+
+ text.destroy();
+ });
+
+ it('should measure empty text to be drawable', () =>
+ {
+ const text = new HTMLText();
+ const size = text.measureText({ text: '' });
+
+ expect(size).toBeTruthy();
+ expect(size.width).toBe(0);
+ expect(size.height).toBe(0);
+
+ text.destroy();
+ });
+
+ it('should measure override text', () =>
+ {
+ const text = new HTMLText();
+ const size = text.measureText({ text: 'Hello world!' });
+
+ expect(size).toBeTruthy();
+ expect(size.width).toBeGreaterThan(0);
+ expect(size.height).toBeGreaterThan(0);
+
+ text.destroy();
+ });
+
+ it('should measure with resolution', () =>
+ {
+ const text = new HTMLText('Hello world!');
+ const size = text.measureText();
+ const size2 = text.measureText({ resolution: 2 });
+
+ expect(Math.abs((size2.width / 2) - size.width)).toBeLessThanOrEqual(1);
+ expect(Math.abs((size2.height / 2) - size.height)).toBeLessThanOrEqual(1);
+ text.destroy();
+ });
+
+ it('should apply override style', () =>
+ {
+ const text = new HTMLText('Hello world!', {
+ fontSize: 12,
+ });
+ const style = new HTMLTextStyle({
+ fontSize: 24,
+ });
+ const size = text.measureText();
+ const size2 = text.measureText({ style });
+
+ expect(Math.abs((size2.width / 2) - size.width)).toBeLessThanOrEqual(1);
+ expect(Math.abs((size2.height / 2) - size.height)).toBeLessThanOrEqual(1);
+ text.destroy();
+ });
+
+ it('should apply override style without touching styleID', () =>
+ {
+ const text = new HTMLText('Hello world!');
+ const styleId = text.style.styleID;
+ const style = new HTMLTextStyle();
+ const style2Id = style.styleID;
+
+ text.measureText();
+ text.measureText({ style });
+ expect(styleId).toBe(text.style.styleID);
+ expect(style2Id).toBe(style.styleID);
+ text.destroy();
+ });
+ });
});