import { Utils } from '../common/Utils'
import { Marker } from './Marker';
import { ImageTexture } from '../resources/ImageTexture';
import { CanvasRenderer } from '../renderers/CanvasRenderer';
import { AlignType, ImageWrapType, FontWeight } from '../const';
const __ = {
private: Symbol('private'),
}
/**
* Label - 文字标注
* 用于在 3D 场景中创建和管理文字标注,支持自定义字体、颜色、阴影和对齐方式。
* @class Label
* @memberof THING
* @extends THING.Marker
* @public
* @example
const label = new THING.Label({
fontText: 'Hello ThingJS',
fontSize: 16,
fontColor: '#ffffff',
position: [0, 2, 0],
});
*/
class Label extends Marker {
static canvasRenderer = null;
/**
* 构造方法
* @public
* @param {object} param 参数对象。
* @param {string} param.fontText 文字内容
* @param {string} [param.fontType = 'Arial'] 字体
* @param {number} [param.fontSize=10] 字号
* @param {FontWeight} [param.fontWeight=THING.FontWeight.Normal] 字体粗细
* @param {number} [param.fontLineWidth=Infinity] 文本换行最大宽度
* @param {number} [param.fontLineHeight=0] 文本换行高度
* @param {number[]|string} [param.fontColor='#ffffff'] 文本颜色
* @param {number[]|string} [param.fontShadowColor='#000000'] 阴影颜色
* @param {number} [param.fontShadowAlpha=1.0] 阴影透明度
* @param {number} [param.fontShadowAngle=45] 阴影旋转角度
* @param {number} [param.fontShadowBlur=1] 阴影模糊系数
* @param {number} [param.fontShadowDistance=1] 阴影距离
* @param {AlignType} [param.fontAlignType=THING.AlignType.Center] 文本对齐方式
* @param {boolean} [param.richText=false] 是否开启富文本模式
* @param {string} [param.mode='defalt'] 文本渲染模式 包括 'sdf','default','msdf','msdf-woreker'
*/
constructor(param = {}) {
super(param);
// Renderer
Label.canvasRenderer = Label.canvasRenderer || new CanvasRenderer();
this[__.private] = {};
let _private = this[__.private];
// The font info
_private.text = '';
_private.fontInfo = {};
_private.image = new ImageTexture();
_private.image.wrapType = ImageWrapType.ClampToEdge;
_private.fontInfo = {
type: 'Arial',
size: 10,
lineWidth: +Infinity,
lineHeight: 0, // 0 indicates use font size as default
color: [1, 1, 1],
shadowColor: [0, 0, 0],
shadowAlpha: 1,
shadowAngle: 45,
shadowBlur: 1,
shadowDistance: 1,
alignType: AlignType.Center,
richText: false,
fontWeight: FontWeight.Normal,
mode: 'default',
paddingTop: 0,
paddingBottom: 0,
paddingLeft: 0,
paddingRight: 0,
};
// Sync text
_private.text = '' + Utils.parseValue(param['fontText'], _private.text);
// Sync font info
var fontInfo = _private.fontInfo;
fontInfo.type = Utils.parseValue(param['fontType'], fontInfo.type);
fontInfo.size = Utils.parseValue(param['fontSize'], fontInfo.size);
fontInfo.lineWidth = Utils.parseValue(param['fontLineWidth'], fontInfo.lineWidth);
fontInfo.lineHeight = Utils.parseValue(param['fontLineHeight'], fontInfo.lineHeight);
fontInfo.color = Utils.parseColor(param['fontColor'], fontInfo.color);
fontInfo.shadowColor = Utils.parseColor(param['fontShadowColor'], fontInfo.shadowColor);
fontInfo.shadowAlpha = Utils.parseValue(param['fontShadowAlpha'], fontInfo.shadowAlpha);
fontInfo.shadowAngle = Utils.parseValue(param['fontShadowAngle'], fontInfo.shadowAngle);
fontInfo.shadowBlur = Utils.parseValue(param['fontShadowBlur'], fontInfo.shadowBlur);
fontInfo.shadowDistance = Utils.parseValue(param['fontShadowDistance'], fontInfo.shadowDistance);
fontInfo.alignType = Utils.parseValue(param['fontAlignType'], fontInfo.alignType);
fontInfo.richText = Utils.parseValue(param['richText'], fontInfo.richText);
fontInfo.fontWeight = Utils.parseValue(param['fontWeight'], fontInfo.fontWeight);
fontInfo.mode = Utils.parseValue(param['mode'], fontInfo.mode);
fontInfo.paddingTop = Utils.parseValue(param['paddingTop'], fontInfo.paddingTop);
fontInfo.paddingBottom = Utils.parseValue(param['paddingBottom'], fontInfo.paddingBottom);
fontInfo.paddingLeft = Utils.parseValue(param['paddingLeft'], fontInfo.paddingLeft);
fontInfo.paddingRight = Utils.parseValue(param['paddingRight'], fontInfo.paddingRight);
let background = param['background'];
// 初始化 background,并从构造函数参数中提取值
if (background) {
_private.background = new LabelBackground(background, this);
}
// Set render order to high value for rendering in the end
if (Utils.isNull(param['renderOrder'])) {
this.renderOrder = 1000 * 1000;
}
this.autoFitBodyScale = Utils.parseValue(param['autoFitBodyScale'], true);
this.scaleFactor = Utils.parseValue(param['scaleFactor'], 0.035);
this._sdfRenderType = param['renderType'] || 'Sprite';
// Seems we need to dirty here to load resources
this.setDirty(true);
}
// #region Private
// #endregion
// #region Overrides
onCopy(object) {
super.onCopy(object);
let _private = this[__.private];
_private.text = object.fontText;
Object.assign(_private.fontInfo, object.fontInfo);
this.onRefresh();
}
onLoadResource(options, resolve, reject) {
if (options.mode == 'sdf' || options.mode == 'msdf' || options.mode == 'msdf-worker') {
this.creatNode(options, () => {
resolve();
this.onRefresh();
});
this.app.objectManager.addResizableObject(this);
}
else {
super.onLoadResource(options, () => {
this.onRefresh();
resolve();
}, reject);
}
}
onRefresh() {
if (this.destroyed) {
return;
}
if (!this.loaded) {
return;
}
let _private = this[__.private];
if (_private.fontInfo.mode == 'sdf' || _private.fontInfo.mode == 'msdf' || _private.fontInfo.mode == 'msdf-worker') {
let scale = 1;
if (_private.scale) {
scale = _private.scale[0];
}
const scaleFactor = this._scaleFactor;
this.body.localScale = [scaleFactor, scaleFactor, 1];
_private.fontInfo.renderType = this._sdfRenderType;
if (this.bodyNode) {
this.bodyNode.update({ ..._private.fontInfo, text: _private.text, scaleFactor: scaleFactor * scale }, (node) => {
node.$boundingBox.selfBoundingBoxNeedsUpdate = true;
this.setPivot(this.pivot);
});
}
this.superOnRefresh();
return;
}
else {
// Update text
if (_private.text) {
// Update background
let canvasRenderer = Label.canvasRenderer;
canvasRenderer.drawText(_private.text, _private.fontInfo, _private.background);
// Update image
_private.image.resource = canvasRenderer.getImageData();
// _private.image.resource = canvasRenderer.getContext();
if (this.style.image != _private.image) {
this.style.image = _private.image;
}
}
super.onRefresh();
}
}
onExportExternalData(options) {
let _private = this[__.private];
let data = super.onExportExternalData(options) || {};
if (_private.text) {
data['fontText'] = _private.text;
}
const fontInfo = _private.fontInfo;
if (fontInfo.type !== 'Arial') {
data['fontType'] = fontInfo.type;
}
if (fontInfo.size !== 10) {
data['fontSize'] = fontInfo.size;
}
if (fontInfo.lineWidth !== Infinity) {
data['fontLineWidth'] = fontInfo.lineWidth;
}
if (fontInfo.lineHeight !== 0) {
data['fontLineHeight'] = fontInfo.lineHeight;
}
if (fontInfo.color !== '#FFFFFF') {
data['fontColor'] = fontInfo.color;
}
if (fontInfo.shadowColor !== '#000000') {
data['fontShadowColor'] = fontInfo.shadowColor;
}
if (fontInfo.shadowAlpha !== 1) {
data['fontShadowAlpha'] = fontInfo.shadowAlpha;
}
if (fontInfo.shadowAngle !== 45) {
data['fontShadowAngle'] = fontInfo.shadowAngle;
}
if (fontInfo.shadowBlur !== 1) {
data['fontShadowBlur'] = fontInfo.shadowBlur;
}
if (fontInfo.shadowDistance !== 1) {
data['fontShadowDistance'] = fontInfo.shadowDistance;
}
if (fontInfo.alignType !== AlignType.Center) {
data['fontAlignType'] = fontInfo.alignType;
}
if (fontInfo.richText !== false) {
data['richText'] = fontInfo.richText;
}
if (fontInfo.fontWeight !== FontWeight.Normal) {
data['fontWeight'] = fontInfo.fontWeight;
}
if (this.renderOrder !== 1000 * 1000) {
data['renderOrder'] = this.renderOrder;
}
if (fontInfo.mode !== 'default') {
data['mode'] = fontInfo.mode;
}
return data;
}
onCreateBodyNode(type) {
let node;
let _private = this[__.private];
if (_private.fontInfo.mode == 'default') {
node = super.onCreateBodyNode(type);
this.loaded = true;
}
else {
// let _private = this[__.private];
// node = this.creatNode({ ..._private.fontInfo, fontText: _private.text, renderType: this._sdfRenderType });
}
return node;
}
onBeforeSetup(options) {
if (options.mode == 'sdf' || options.mode == 'msdf' || options.mode == 'msdf-worker') {
// this.creatNode(options);
// this.app.objectManager.addResizableObject(this);
}
else {
super.onBeforeSetup(options);
}
}
creatNode(param, callback) {
if (!param.lineHeight) {
delete param.lineHeight;
}
let renderableNode = param['renderableNode'];
if (renderableNode) {
return;
}
let scale = 1;
if (param.scale) {
scale = param.scale[0];
}
let fontInfo = {
type: 'Arial',
shadowColor: [0, 0, 0],
shadowAlpha: 1,
shadowAngle: 45,
shadowBlur: 1,
shadowDistance: 1,
fontColor: [1, 1, 1],
fontText: 'sdf'
}
fontInfo.fontColor = Utils.parseColor(param['fontColor'], fontInfo.fontColor);
fontInfo.type = Utils.parseValue(param['fontType'], fontInfo.type);
fontInfo.shadowColor = Utils.parseColor(param['fontShadowColor'], fontInfo.shadowColor);
fontInfo.shadowAlpha = Utils.parseValue(param['fontShadowAlpha'], fontInfo.shadowAlpha);
fontInfo.shadowAngle = Utils.parseValue(param['fontShadowAngle'], fontInfo.shadowAngle);
fontInfo.shadowBlur = Utils.parseValue(param['fontShadowBlur'], fontInfo.shadowBlur);
fontInfo.shadowDistance = Utils.parseValue(param['fontShadowDistance'], fontInfo.shadowDistance);
fontInfo.fontText = Utils.parseValue(param['fontText'], fontInfo.fontText);
let color = Utils.parseColor(param['fontColor'], [1, 1, 1]);
renderableNode = Utils.createObject('Font', {
app: this.app,
renderType: 'Sprite', ...param, ...fontInfo, fontScale: 0.035 * scale, fontColor: color, callback: (node) => {
let pivot = param['pivot'];
if (pivot) {
node.$boundingBox.selfBoundingBoxNeedsUpdate = true;
this.setPivot(pivot);
}
this.loaded = true;
callback(true)
}
},);
this.setBodyNode(renderableNode);
param['renderableNode'] = renderableNode;
return renderableNode;
}
onResize(width, height) {
let _private = this[__.private];
if (_private.fontInfo.mode == 'sdf' || _private.fontInfo.mode == 'msdf' || _private.fontInfo.mode == 'msdf-worker') {
if (this.bodyNode.updateSize) {
this.bodyNode.updateSize(width, height);
}
}
super.onResize(width, height);
}
_useSpriteCenterAsPivot() {
let _private = this[__.private];
if (_private) {
if (_private.fontInfo.mode == 'sdf' || _private.fontInfo.mode == 'msdf' || _private.fontInfo.mode == 'msdf-worker') {
return false;
}
}
return super._useSpriteCenterAsPivot();
}
// #endregion
/**
* 设置/获取 文本内容
* @type {string}
* @public
*/
get fontText() {
let _private = this[__.private];
return _private.text;
}
set fontText(value) {
let _private = this[__.private];
_private.text = value;
this.setDirty();
}
/**
* Get font info.
* @type {object}
* @private
*/
get fontInfo() {
let _private = this[__.private];
return _private.fontInfo;
}
/**
* 获取/设置 文本字体类型
* @type {string}
* @default 'Arial'
* @public
*/
get fontType() {
let _private = this[__.private];
return _private.fontInfo.type;
}
set fontType(value) {
let _private = this[__.private];
_private.fontInfo.type = value;
this.setDirty();
}
/**
* 获取/设置 文本尺寸大小
* @type {number}
* @public
*/
get fontSize() {
let _private = this[__.private];
return _private.fontInfo.size;
}
set fontSize(value) {
let _private = this[__.private];
_private.fontInfo.size = value;
this.setDirty();
}
/**
* 获取/设置文本换行最大宽度,单位:像素。
* @type {number}
* @public
*/
get fontLineWidth() {
let _private = this[__.private];
return _private.fontInfo.lineWidth;
}
set fontLineWidth(value) {
let _private = this[__.private];
_private.fontInfo.lineWidth = value;
this.setDirty();
}
/**
* 获取/设置文本换行高度,单位:像素。0 表示自动调整尺寸
* @type {number}
* @public
*/
get fontLineHeight() {
let _private = this[__.private];
return _private.fontInfo.lineHeight;
}
set fontLineHeight(value) {
let _private = this[__.private];
_private.fontInfo.lineHeight = value;
this.setDirty();
}
/**
* 获取/设置文本颜色
* @type {number|string|Array<number>}
* @public
*/
get fontColor() {
let _private = this[__.private];
return Utils.colorToHexString(_private.fontInfo.color);
}
set fontColor(value) {
let _private = this[__.private];
_private.fontInfo.color = Utils.parseColor(value);
this.setDirty();
}
/**
* 获取/设置文本阴影颜色
* @type {number|string|Array<number>}
* @public
*/
get fontShadowColor() {
let _private = this[__.private];
return Utils.colorToHexString(_private.fontInfo.shadowColor);
}
set fontShadowColor(value) {
let _private = this[__.private];
_private.fontInfo.shadowColor = Utils.parseColor(value);
this.setDirty();
}
/**
* 获取/设置文本阴影透明度。
* @type {number}
* @public
*/
get fontShadowAlpha() {
let _private = this[__.private];
return _private.fontInfo.shadowAlpha;
}
set fontShadowAlpha(value) {
let _private = this[__.private];
_private.fontInfo.shadowAlpha = value;
this.setDirty();
}
/**
* 获取/设置文本阴影角度。
* @type {number}
* @public
*/
get fontShadowAngle() {
let _private = this[__.private];
return _private.fontInfo.shadowAngle;
}
set fontShadowAngle(value) {
let _private = this[__.private];
_private.fontInfo.shadowAngle = value;
this.setDirty();
}
/**
* 获取/设置文本阴影模糊系数
* @type {number}
* @public
*/
get fontShadowBlur() {
let _private = this[__.private];
return _private.fontInfo.shadowBlur;
}
set fontShadowBlur(value) {
let _private = this[__.private];
_private.fontInfo.shadowBlur = value;
this.setDirty();
}
/**
* 获取/设置文本阴影距离
* @type {number}
* @public
*/
get fontShadowDistance() {
let _private = this[__.private];
return _private.fontInfo.shadowDistance;
}
set fontShadowDistance(value) {
let _private = this[__.private];
_private.fontInfo.shadowDistance = value;
this.setDirty();
}
/**
* 获取/设置文本对齐方式
* @type {AlignType}
* @public
*/
get fontAlignType() {
let _private = this[__.private];
return _private.fontInfo.alignType;
}
set fontAlignType(value) {
let _private = this[__.private];
_private.fontInfo.alignType = value;
this.setDirty();
}
/**
* 开启/关闭富文本模式
* @type {boolean}
* @public
*/
get richText() {
let _private = this[__.private];
return _private.fontInfo.richText;
}
set richText(value) {
let _private = this[__.private];
_private.fontInfo.richText = value;
this.setDirty();
}
get isLabel() {
return true;
}
/**
* 获取/设置字体粗细
* @type {FontWeight}
* @public
*/
get fontWeight() {
let _private = this[__.private];
return _private.fontInfo.fontWeight;
}
set fontWeight(value) {
let _private = this[__.private];
_private.fontInfo.fontWeight = value;
this.setDirty();
}
set renderType(value) {
let _private = this[__.private];
if (_private.fontInfo.mode == 'default') {
super.renderType = value;
}
else {
this._sdfRenderType = value;
this[__.private].fontInfo.renderType = value;
this.setDirty();
}
}
get renderType() {
return super.renderType;
}
get mode() {
let _private = this[__.private];
return _private.fontInfo.mode;
}
/**
* 获取/设置上内边距
* @type {number}
* @public
*/
get paddingTop() {
let _private = this[__.private];
return _private.fontInfo.paddingTop;
}
set paddingTop(value) {
let _private = this[__.private];
_private.fontInfo.paddingTop = value;
this.setDirty();
}
/**
* 获取/设置下内边距
* @type {number}
* @public
*/
get paddingBottom() {
let _private = this[__.private];
return _private.fontInfo.paddingBottom;
}
set paddingBottom(value) {
let _private = this[__.private];
_private.fontInfo.paddingBottom = value;
this.setDirty();
}
/**
* 获取/设置左内边距
* @type {number}
* @public
*/
get paddingLeft() {
let _private = this[__.private];
return _private.fontInfo.paddingLeft;
}
set paddingLeft(value) {
let _private = this[__.private];
_private.fontInfo.paddingLeft = value;
this.setDirty();
}
/**
* 获取/设置右内边距
* @type {number}
* @public
*/
get paddingRight() {
let _private = this[__.private];
return _private.fontInfo.paddingRight;
}
set paddingRight(value) {
let _private = this[__.private];
_private.fontInfo.paddingRight = value;
this.setDirty();
}
/**
* 获取/设置背景
* @type {LabelBackground}
* @public
*/
get background() {
let _private = this[__.private];
return _private.background;
}
set background(value) {
let _private = this[__.private];
if (value instanceof LabelBackground) {
_private.background = value;
}
else if (typeof value === 'object' && value !== null) {
_private.background = new LabelBackground(value);
}
else {
console.error('Invalid value for background');
}
}
}
export { Label }
class LabelBackground {
constructor({ color = "white", opacity = 1, cornerRadius = 0, border } = {}, label) {
this._color = color;
this._opacity = opacity;
this._cornerRadius = cornerRadius;
this._border = border ? new LabelBackgroundBorder(border, label) : undefined;
this._label = label;
}
// Getter and Setter for color
get color() {
return this._color;
}
set color(value) {
this._color = value;
this._label.setDirty();
}
// Getter and Setter for opacity
get opacity() {
return this._opacity;
}
set opacity(value) {
if (typeof value === 'number') {
this._opacity = value;
this._label.setDirty();
}
else {
console.error('Invalid value for background opacity');
}
}
// Getter and Setter for cornerRadius
get cornerRadius() {
return this._cornerRadius;
}
set cornerRadius(value) {
if (typeof value === 'number') {
this._cornerRadius = value;
this._label.setDirty();
}
else {
console.error('Invalid value for cornerRadius');
}
}
// Getter and Setter for border
get border() {
return this._border;
}
set border(value) {
if (value instanceof LabelBackgroundBorder) {
this._border = value;
}
else if (typeof value === 'object' && value !== null) {
this._border = new LabelBackgroundBorder(value);
}
else {
console.error('Invalid value for border');
}
}
}
class LabelBackgroundBorder {
constructor({ color = "white", opacity = 1, width = 1 } = {}, label) {
this._color = color;
this._opacity = opacity;
this._width = width;
this._label = label;
}
// Getter and Setter for color
get color() {
return this._color;
}
set color(value) {
this._color = value;
this._label.setDirty();
}
// Getter and Setter for opacity
get opacity() {
return this._opacity;
}
set opacity(value) {
if (typeof value === 'number') {
this._opacity = value;
this._label.setDirty();
}
else {
console.error('Invalid value for border opacity');
}
}
// Getter and Setter for width
get width() {
return this._width;
}
set width(value) {
if (typeof value === 'number') {
this._width = value;
this._label.setDirty();
}
else {
console.error('Invalid value for border width');
}
}
}