Skip to content

Commit

Permalink
Feature: Simplify the Rendering Pipeline (pixijs-userland#34)
Browse files Browse the repository at this point in the history
  • Loading branch information
bigtimebuddy authored Dec 21, 2022
1 parent cfca70b commit e9f66d9
Show file tree
Hide file tree
Showing 4 changed files with 110 additions and 74 deletions.
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,6 @@ Not all styles and values are compatible between PIXI.Text, mainly because Text
* `dropShadowDistance`
* `dropShadowBlur`
* `dropShadowColor`
* `trim`
* `stroke`
* `strokeThickness`

Expand All @@ -91,8 +90,11 @@ Not all styles and values are compatible between PIXI.Text, mainly because Text

* `fillGradientStops`
* `fillGradientType`
* `lineJoin`
* `miterLimit`
* `textBaseline`
* `trim`
* `leading`

## Example

Expand Down
2 changes: 1 addition & 1 deletion demo/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ <h2>Internal Preview</h2>
text2.style.loadFont('./OpenSans-Bold.ttf', { family: 'OpenSans', weight: 'bold' }),
]).then(() => app.render());

document.getElementById('text').appendChild(text2.canvas);
document.getElementById('text').appendChild(text2.source);
const rect = new PIXI.Graphics();

const refreshBounds = () => rect
Expand Down
100 changes: 40 additions & 60 deletions src/HTMLText.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Sprite } from '@pixi/sprite';
import { Texture, Rectangle, settings, utils, ICanvas, ICanvasRenderingContext2D, ISize } from '@pixi/core';
import { Texture, Rectangle, settings, utils, ISize, ImageResource } from '@pixi/core';
import { TextStyle } from '@pixi/text';
import { HTMLTextStyle } from './HTMLTextStyle';

Expand Down Expand Up @@ -52,8 +52,6 @@ export class HTMLText extends Sprite
private _svgRoot: SVGSVGElement;
private _foreignObject: SVGForeignObjectElement;
private _image: HTMLImageElement;
private canvas: ICanvas;
private context: ICanvasRenderingContext2D;
private _resolution: number;
private _text: string | null = null;
private _style: HTMLTextStyle | null = null;
Expand All @@ -72,14 +70,16 @@ export class HTMLText extends Sprite
* @param {HTMLTextStyle|PIXI.TextStyle|PIXI.ITextStyle} [style] - Style setting to use.
* Strongly recommend using an HTMLTextStyle object. Providing a PIXI.TextStyle
* will convert the TextStyle to an HTMLTextStyle and will no longer be linked.
* @param {HTMLCanvasElement} [canvas] - Optional canvas to use for rendering.
*. if undefined, will generate it's own canvas using createElement.
*/
constructor(text = '', style: HTMLTextStyle | TextStyle | Partial<ITextStyle> = {}, canvas?: ICanvas)
constructor(text = '', style: HTMLTextStyle | TextStyle | Partial<ITextStyle> = {})
{
canvas = canvas || settings.ADAPTER.createCanvas(3, 3);

const texture = Texture.from(canvas, { scaleMode: settings.SCALE_MODE });
const image = new Image();
const texture = Texture.from<ImageResource>(image, {
scaleMode: settings.SCALE_MODE,
resourceOptions: {
autoLoad: false,
},
});

texture.orig = new Rectangle();
texture.trim = new Rectangle();
Expand Down Expand Up @@ -109,7 +109,7 @@ export class HTMLText extends Sprite
this._foreignObject = foreignObject;
this._foreignObject.appendChild(styleElement);
this._foreignObject.appendChild(domElement);
this._image = new Image();
this._image = image;
this._autoResolution = HTMLText.defaultAutoResolution;
this._shadowRoot = shadow.attachShadow({ mode: 'open' });
this._shadowRoot.appendChild(svgRoot);
Expand All @@ -122,9 +122,6 @@ export class HTMLText extends Sprite
height: '1px',
});
document.body.appendChild(shadow);

this.canvas = canvas;
this.context = canvas.getContext('2d') as ICanvasRenderingContext2D;
this._resolution = HTMLText.defaultResolution ?? settings.RESOLUTION;
this.text = text;
this.style = style;
Expand Down Expand Up @@ -188,7 +185,7 @@ export class HTMLText extends Sprite
*/
async updateText(respectDirty = true): Promise<void>
{
const { style, canvas, context } = this;
const { style, _image: image } = this;

// check if style has changed..
if (this.localStyleID !== style.styleID)
Expand All @@ -206,27 +203,17 @@ export class HTMLText extends Sprite

// 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)));
image.width = Math.ceil((Math.max(1, width)));
image.height = Math.ceil((Math.max(1, height)));

if (!this._loading)
{
this._loading = true;
await new Promise<void>((resolve) =>
{
const image = this._image;

image.onload = async () =>
{
await style.onBeforeDraw();
context.clearRect(0, 0, canvas.width, canvas.height);
context.drawImage(
image,
0, 0, width, height,
0, 0, width, height,
);
image.src = '';
image.onload = null;
this._loading = false;
this.updateTexture();
resolve();
Expand All @@ -238,34 +225,35 @@ export class HTMLText extends Sprite
}
}

/** The raw image element that is rendered under-the-hood. */
public get source(): HTMLImageElement
{
return this._image;
}

/**
* @deprecated since 3.2.0
* @see HTMLText#image
*/
public get canvas(): HTMLImageElement
{
utils.deprecation('3.2.0', 'HTMLText property "canvas" is deprecated, use "source" instead.');

return this._image;
}

/**
* Update the texture resource.
* @private
*/
updateTexture()
{
const { style, texture, resolution } = this;

const canvas = this.canvas as HTMLCanvasElement;
const context = this.context as CanvasRenderingContext2D;
const { style, texture, _image: image, resolution } = this;
const { padding } = style;
const { baseTexture } = texture;

if (style.trim)
{
const { width, height, data } = utils.trimCanvas(canvas);

if (data)
{
canvas.width = width;
canvas.height = height;
context.putImageData(data, 0, 0);
}
}

const padding = style.trim ? 0 : style.padding;
const baseTexture = texture.baseTexture;

texture.trim.width = texture._frame.width = canvas.width / resolution;
texture.trim.height = texture._frame.height = canvas.height / resolution;
texture.trim.width = texture._frame.width = image.width / resolution;
texture.trim.height = texture._frame.height = image.height / resolution;
texture.trim.x = -padding;
texture.trim.y = -padding;

Expand All @@ -275,7 +263,7 @@ export class HTMLText extends Sprite
// call sprite onTextureUpdate to update scale if _width or _height were set
this._onTextureUpdate();

baseTexture.setRealSize(canvas.width, canvas.height, resolution);
baseTexture.setRealSize(image.width, image.height, resolution);

this.dirty = false;
}
Expand Down Expand Up @@ -367,19 +355,11 @@ export class HTMLText extends Sprite

const forceClear: any = null;

// make sure to reset the the context and canvas..
// dont want this hanging around in memory!
this.context = null as any;
if (this.canvas)
{
this.canvas.width = this.canvas.height = 0; // Safari hack
}
// Remove any loaded fonts if we created the HTMLTextStyle
if (this.ownsStyle)
{
this._style?.cleanFonts();
}
this.canvas = forceClear;
this._style = forceClear;
this._svgRoot?.remove();
this._svgRoot = forceClear;
Expand All @@ -405,7 +385,7 @@ export class HTMLText extends Sprite
{
this.updateText(true);

return Math.abs(this.scale.x) * this.canvas.width / this.resolution;
return Math.abs(this.scale.x) * this._image.width / this.resolution;
}

set width(value) // eslint-disable-line require-jsdoc
Expand All @@ -414,7 +394,7 @@ export class HTMLText extends Sprite

const s = utils.sign(this.scale.x) || 1;

this.scale.x = s * value / this.canvas.width / this.resolution;
this.scale.x = s * value / this._image.width / this.resolution;
this._width = value;
}

Expand All @@ -426,7 +406,7 @@ export class HTMLText extends Sprite
{
this.updateText(true);

return Math.abs(this.scale.y) * this.canvas.height / this.resolution;
return Math.abs(this.scale.y) * this._image.height / this.resolution;
}

set height(value) // eslint-disable-line require-jsdoc
Expand All @@ -435,7 +415,7 @@ export class HTMLText extends Sprite

const s = utils.sign(this.scale.y) || 1;

this.scale.y = s * value / this.canvas.height / this.resolution;
this.scale.y = s * value / this._image.height / this.resolution;
this._height = value;
}

Expand Down
78 changes: 66 additions & 12 deletions src/HTMLTextStyle.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
import { settings, utils } from '@pixi/core';
import { TextStyle, TextStyleFontStyle, TextStyleFontWeight } from '@pixi/text';
import { TextStyle, TextStyleFontStyle, TextStyleFontWeight, TextStyleLineJoin } from '@pixi/text';

import type { ITextStyle, TextStyleTextBaseline } from '@pixi/text';

// HTMLText support more white-space options
type HTMLTextStyleWhiteSpace = 'normal' | 'pre' | 'pre-line' | 'nowrap' | 'pre-wrap';

// Subset of ITextStyle
type ITextStyleIgnore = 'whiteSpace' | 'fillGradientStops' | 'fillGradientType' | 'miterLimit' | 'textBaseline';
type ITextStyleIgnore = 'whiteSpace'
| 'fillGradientStops'
| 'fillGradientType'
| 'miterLimit'
| 'textBaseline'
| 'trim'
| 'leading'
| 'lineJoin';

/**
* Modifed versions from ITextStyle.
Expand Down Expand Up @@ -62,31 +69,54 @@ class HTMLTextStyle extends TextStyle
* with the exception of whiteSpace, which is set to 'normal' by default.
*/
public static readonly defaultOptions: IHTMLTextStyle = {
/** Align */
align: 'left',
/** Break words */
breakWords: false,
/** Drop shadow */
dropShadow: false,
/** Drop shadow alpha */
dropShadowAlpha: 1,
/**
* Drop shadow angle
* @type {number}
* @default Math.PI / 6
*/
dropShadowAngle: Math.PI / 6,
/** Drop shadow blur */
dropShadowBlur: 0,
/** Drop shadow color */
dropShadowColor: 'black',
/** Drop shadow distance */
dropShadowDistance: 5,
/** Fill */
fill: 'black',
/** Font family */
fontFamily: 'Arial',
/** Font size */
fontSize: 26,
/** Font style */
fontStyle: 'normal',
/** Font variant */
fontVariant: 'normal',
/** Font weight */
fontWeight: 'normal',
/** Letter spacing */
letterSpacing: 0,
/** Line height */
lineHeight: 0,
lineJoin: 'miter',
/** Padding */
padding: 0,
/** Stroke */
stroke: 'black',
/** Stroke thickness */
strokeThickness: 0,
trim: false,
/** White space */
whiteSpace: 'normal',
/** Word wrap */
wordWrap: false,
/** Word wrap width */
wordWrapWidth: 100,
leading: 0,
};

/** For using custom fonts */
Expand Down Expand Up @@ -327,20 +357,14 @@ class HTMLTextStyle extends TextStyle
let color = this.normalizeColor(this.dropShadowColor);
const alpha = this.dropShadowAlpha;
const x = Math.round(Math.cos(this.dropShadowAngle) * this.dropShadowDistance);
let y = Math.round(Math.sin(this.dropShadowAngle) * this.dropShadowDistance);
const y = Math.round(Math.sin(this.dropShadowAngle) * this.dropShadowDistance);

// Append alpha to color
if (color.startsWith('#') && alpha < 1)
{
color += (alpha * 255 | 0).toString(16).padStart(2, '0');
}

// Hack: text-shadow is flipped on Safari, boo!
if (this.isSafari)
{
y *= -1;
}

const position = `${x * scale}px ${y * scale}px`;

if (this.dropShadowBlur > 0)
Expand Down Expand Up @@ -421,6 +445,16 @@ class HTMLTextStyle extends TextStyle
return super.miterLimit;
}

/** @ignore trim is not supported by HTMLText */
override set trim(_value: boolean)
{
console.warn('[HTMLTextStyle] trim is not supported by HTMLText');
}
override get trim()
{
return super.trim;
}

/** @ignore textBaseline is not supported by HTMLText */
override set textBaseline(_value: TextStyleTextBaseline)
{
Expand All @@ -430,6 +464,26 @@ class HTMLTextStyle extends TextStyle
{
return super.textBaseline;
}

/** @ignore leading is not supported by HTMLText */
override set leading(_value: number)
{
console.warn('[HTMLTextStyle] leading is not supported by HTMLText');
}
override get leading()
{
return super.leading;
}

/** @ignore lineJoin is not supported by HTMLText */
override set lineJoin(_value: TextStyleLineJoin)
{
console.warn('[HTMLTextStyle] lineJoin is not supported by HTMLText');
}
override get lineJoin()
{
return super.lineJoin;
}
}

export { HTMLTextStyle };
Expand Down

0 comments on commit e9f66d9

Please sign in to comment.