src/node/label.js
import {ENV, parseFont} from '@mesh.js/core';
import {createTexture, deleteTexture} from '../utils/texture';
import Block from './block';
import Attr from '../attribute/label';
import ownerDocument from '../document';
const _textImage = Symbol('textImage');
const _textImageTask = Symbol('textImageTask');
const _textureContext = Symbol('textureContext');
const _updateTextureRect = Symbol('updateTextureRect');
const _textCanvas = Symbol('textCanvas');
export default class Label extends Block {
static Attr = Attr;
constructor(attrs = {}) {
if(typeof attrs === 'string') attrs = {text: attrs};
super(attrs);
}
/* override */
get contentSize() {
let [w, h] = super.contentSize;
const {width, height} = this.attributes;
if(width == null || height == null) {
const img = this[_textImage];
if(img) {
if(width == null) w = img.rect[2];
if(height == null) h = img.rect[3];
}
}
return [w, h];
}
get text() {
return this.attributes.text;
}
set text(value) {
this.attributes.text = value;
}
get textContent() {
return this.attributes.text;
}
set textContent(value) {
this.attributes.text = value;
}
get textImage() {
return this[_textImage] || {};
}
get textImageReady() {
return this[_textImageTask] || Promise.resolve();
}
/* override */
draw(meshes) {
super.draw(meshes);
const mesh = this.mesh;
if(mesh) {
const textImage = this[_textImage];
if(textImage) {
let texture = mesh.texture;
if(!texture
|| this[_textureContext] && this[_textureContext] !== this.renderer
|| textImage.needsUpdate) {
textImage.needsUpdate = false;
deleteTexture(textImage.image, this.renderer);
texture = createTexture(textImage.image, this.renderer);
this[_updateTextureRect] = true;
} else {
texture = mesh.uniforms.u_texSampler;
}
if(this[_updateTextureRect]) {
const [width, height] = textImage.rect.slice(2);
const [w, h] = this.contentSize;
const textAlign = this.attributes.textAlign;
const verticalAlign = this.attributes.verticalAlign;
let x = 0;
if(textAlign === 'center') {
x = (w - width) / 2;
} else if(textAlign === 'right' || textAlign === 'end') {
x = w - width;
}
const fontHeight = this.attributes.fontSize;
const lineHeight = this.attributes.lineHeight;
let y = 0; // middle
if(verticalAlign === 'top') {
y = (fontHeight - lineHeight) / 2;
} else if(verticalAlign === 'bottom') {
y = (lineHeight - fontHeight) / 2;
}
const {paddingLeft, paddingTop} = this.attributes;
const {borderWidth} = this.attributes;
x += paddingLeft + borderWidth;
y += paddingTop + borderWidth;
const {anchorX, anchorY} = this.attributes;
x -= this.offsetSize[0] * anchorX;
y -= this.offsetSize[1] * anchorY;
mesh.setTexture(texture, {
rect: [x, y, width, height],
});
this[_updateTextureRect] = false;
this[_textureContext] = this.renderer;
}
}
}
return meshes;
}
/* override */
onPropertyChange(key, newValue, oldValue) {
if(key === 'text' || key === 'fontSize' || key === 'fontFamily'
|| key === 'fontStyle' || key === 'fontVariant' || key === 'fontWeight'
|| key === 'fontStretch' || key === 'lineHeight'
|| key === 'strokeColor' || key === 'fillColor'
|| key === 'strokeWidth') {
this.updateText();
} else {
if(key === 'textAlign' || key === 'verticalAlign') {
this[_updateTextureRect] = true;
}
super.onPropertyChange(key, newValue, oldValue);
}
}
/* override */
updateContours() {
super.updateContours();
this[_updateTextureRect] = true;
}
updateText() {
if(!this[_textImageTask]) {
this[_textImageTask] = Promise.resolve().then(() => {
this[_textImageTask] = null;
const {text, font, fillColor, strokeColor, strokeWidth} = this.attributes;
const ratio = this.layer ? this.layer.displayRatio : 1;
this[_textCanvas] = this[_textCanvas] || ENV.createCanvas(1, 1);
this[_textImage] = ENV.createText(text, {
font, fillColor, strokeColor, strokeWidth, parseFont, ratio, textCanvas: this[_textCanvas]});
this[_textImage].needsUpdate = true;
this.updateContours();
this.forceUpdate();
return this[_textImage];
});
}
}
}
ownerDocument.registerNode(Label, 'label');