Skip to content

Commit

Permalink
Add a way to insert global styles, fix cross-browser bugs
Browse files Browse the repository at this point in the history
  • Loading branch information
bigtimebuddy committed Dec 1, 2022
1 parent 9dec54b commit e11a424
Show file tree
Hide file tree
Showing 2 changed files with 59 additions and 23 deletions.
34 changes: 26 additions & 8 deletions src/HTMLText.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export class HTMLText extends Sprite
public static defaultAutoResolution = true;

private _domElement: HTMLElement;
private _fontElement: HTMLElement;
private _styleElement: HTMLElement;
private _svgRoot: SVGSVGElement;
private _foreignObject: SVGForeignObjectElement;
private _image: HTMLImageElement;
Expand All @@ -37,6 +37,9 @@ export class HTMLText extends Sprite
private localStyleID = -1;
private dirty = false;

/** The HTMLTextStyle object is owned by this instance */
private ownsStyle = false;

/**
* @param {string} [text] - Text contents
* @param {HTMLTextStyle|PIXI.TextStyle|PIXI.ITextStyle} [style] - Style setting to use.
Expand All @@ -63,14 +66,15 @@ export class HTMLText extends Sprite
const svgRoot = document.createElementNS(ns, 'svg');
const foreignObject = document.createElementNS(ns, 'foreignObject');
const domElement = document.createElementNS('http://www.w3.org/1999/xhtml', 'div');
const fontElement = document.createElementNS('http://www.w3.org/1999/xhtml', 'style');
const styleElement = document.createElementNS('http://www.w3.org/1999/xhtml', 'style');

foreignObject.setAttribute('height', '100%');
foreignObject.setAttribute('width', '100%');
svgRoot.appendChild(foreignObject);
svgRoot.style.paintOrder = 'stroke fill';

this._domElement = domElement;
this._fontElement = fontElement;
this._styleElement = styleElement;
this._svgRoot = svgRoot;
this._foreignObject = foreignObject;
this._image = new Image();
Expand Down Expand Up @@ -106,13 +110,13 @@ export class HTMLText extends Sprite
}

const dom = this._domElement;
const font = this._fontElement;
const globalStyles = this._styleElement;

Object.assign(dom, {
innerHTML: this._text,
style: style.toCSS(),
style: style.toCSS(resolution),
});
font.innerHTML = style.toFontCSS();
globalStyles.innerHTML = style.toGlobalCSS();

// Measure the contents
document.body.appendChild(dom);
Expand All @@ -123,7 +127,7 @@ export class HTMLText extends Sprite
document.body.removeChild(dom);

// Assemble the svg output
this._foreignObject.appendChild(font);
this._foreignObject.appendChild(globalStyles);
this._foreignObject.appendChild(dom);
this._svgRoot.setAttribute('width', width.toString());
this._svgRoot.setAttribute('height', height.toString());
Expand All @@ -140,7 +144,6 @@ export class HTMLText extends Sprite
image.src = `data:image/svg+xml;charset=utf8,${encodeURIComponent(svgURL)}`;
image.onload = () =>
{
context.scale(resolution, resolution);
context.clearRect(0, 0, canvas.width, canvas.height);
context.drawImage(
image,
Expand Down Expand Up @@ -284,11 +287,17 @@ export class HTMLText extends Sprite
{
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 = forceClear;
this._domElement = forceClear;
this._foreignObject = forceClear;
this._styleElement = forceClear;
this._image.onload = null;
this._image.src = '';
this._image = forceClear;
Expand Down Expand Up @@ -344,21 +353,30 @@ export class HTMLText extends Sprite

set style(style: HTMLTextStyle | TextStyle | Partial<ITextStyle>) // eslint-disable-line require-jsdoc
{
// Don't do anything if we're re-assigning
if (this._style === style)
{
return;
}

style = style || {};

if (style instanceof HTMLTextStyle)
{
this.ownsStyle = false;
this._style = style;
}
// Clone TextStyle
else if (style instanceof TextStyle)
{
console.warn('[HTMLText] Cloning TextStyle, if this is not what you want, use HTMLTextStyle');

this.ownsStyle = true;
this._style = HTMLTextStyle.from(style);
}
else
{
this.ownsStyle = true;
this._style = new HTMLTextStyle(style);
}

Expand Down
48 changes: 33 additions & 15 deletions src/HTMLTextStyle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,9 @@ class HTMLTextStyle extends TextStyle
/** List of internal style rules */
private _overrides: string[] = [];

/** Global rules or stylesheet, useful for creating rules for rendering */
public globalCSS = '';

/**
* Convert a TextStyle to HTMLTextStyle
* @example
Expand Down Expand Up @@ -158,49 +161,49 @@ class HTMLTextStyle extends TextStyle
* Internally converts all of the style properties into CSS equivalents.
* @return The CSS style string, for setting `style` property of root HTMLElement.
*/
public toCSS(): string
public toCSS(scale: number): string
{
return [
'display: inline-block',
`color: ${this.normalizeColor(this.fill)}`,
`font-size: ${this.fontSize}px`,
`font-size: ${(this.fontSize as number) * scale}px`,
`font-family: ${this.fontFamily}`,
`font-weight: ${this.fontWeight}`,
`font-style: ${this.fontStyle}`,
`font-variant: ${this.fontVariant}`,
`letter-spacing: ${this.letterSpacing}px`,
`letter-spacing: ${this.letterSpacing * scale}px`,
`text-align: ${this.align}`,
`padding: ${this.padding}px`,
`padding: ${this.padding * scale}px`,
`white-space: ${this.whiteSpace}`,
...this.lineHeight ? [`line-height: ${this.lineHeight}px`] : [],
...this.lineHeight ? [`line-height: ${this.lineHeight * scale}px`] : [],
...this.wordWrap ? [
`word-wrap: ${this.breakWords ? 'break-all' : 'break-word'}`,
`max-width: ${this.wordWrapWidth}px`
`max-width: ${this.wordWrapWidth * scale}px`
] : [],
...this.strokeThickness ? [
`-webkit-text-stroke-width: ${this.strokeThickness}px`,
`-webkit-text-stroke-width: ${this.strokeThickness * scale}px`,
`-webkit-text-stroke-color: ${this.normalizeColor(this.stroke)}`,
`text-stroke-width: ${this.strokeThickness}px`,
`text-stroke-width: ${this.strokeThickness * scale}px`,
`text-stroke-color: ${this.normalizeColor(this.stroke)}`,
'paint-order: stroke',
] : [],
...this.dropShadow ? [this.dropShadowToCSS()] : [],
...this.dropShadow ? [this.dropShadowToCSS(scale)] : [],
...this._overrides,
].join(';');
}

/** Get the font CSS styles from the loaded font, If available. */
public toFontCSS(): string
public toGlobalCSS(): string
{
return this._fonts.reduce((result, font) => (
`${result}
@font-face {
font-family: "${font.family}";
src: url(${font.url});
src: url('${font.url}');
font-weight: ${font.weight};
font-style: ${font.style};
}`
), '');
), this.globalCSS);
}

/** Convert numerical colors into hex-strings */
Expand All @@ -220,20 +223,35 @@ class HTMLTextStyle extends TextStyle
}

/** Convert the internal drop-shadow settings to CSS text-shadow */
private dropShadowToCSS(): string
private dropShadowToCSS(scale: number): string
{
let color = this.normalizeColor(this.dropShadowColor);
const alpha = this.dropShadowAlpha;
const x = Math.round(Math.cos(this.dropShadowAngle) * this.dropShadowDistance);
const y = Math.round(Math.sin(this.dropShadowAngle) * this.dropShadowDistance);
let 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');
}

return `text-shadow: ${x}px ${y}px ${this.dropShadowBlur}px ${color}`;
const { userAgent } = settings.ADAPTER.getNavigator();

// Shadow is flipped on Safari
if ((/^((?!chrome|android).)*safari/i).test(userAgent))
{
y *= -1;
}

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

if (this.dropShadowBlur > 0)
{
return `text-shadow: ${position} ${this.dropShadowBlur}px ${color}`;
}

return `text-shadow: ${position} ${color}`;
}

/** Resets all properties to the defaults specified in TextStyle.prototype._default */
Expand Down

0 comments on commit e11a424

Please sign in to comment.