@eva/plugin-renderer-text 2.0.1-beta.3 → 2.0.1-beta.31

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -81,6 +81,30 @@ var _EVA_IIFE_text = function (exports, pixi_js, eva_js, pluginRenderer, rendere
81
81
  }
82
82
  Text$2.componentName = 'Text';
83
83
  __decorate([type('string')], Text$2.prototype, "text", void 0);
84
+ class HTMLText extends eva_js.Component {
85
+ constructor() {
86
+ super(...arguments);
87
+ this.text = '';
88
+ this.style = {};
89
+ this.textureStyle = {};
90
+ }
91
+ init(obj) {
92
+ this.style = _extends({
93
+ fontSize: 24,
94
+ fill: '#000000',
95
+ fontFamily: 'Arial'
96
+ }, obj === null || obj === void 0 ? void 0 : obj.style);
97
+ this.textureStyle = _extends({
98
+ scaleMode: 'linear',
99
+ resolution: window.devicePixelRatio || 1
100
+ }, obj === null || obj === void 0 ? void 0 : obj.textureStyle);
101
+ if (obj) {
102
+ this.text = obj.text;
103
+ }
104
+ }
105
+ }
106
+ HTMLText.componentName = 'HTMLText';
107
+ __decorate([type('string')], HTMLText.prototype, "text", void 0);
84
108
  let Text = class Text extends pluginRenderer.Renderer {
85
109
  constructor() {
86
110
  super(...arguments);
@@ -93,15 +117,15 @@ var _EVA_IIFE_text = function (exports, pixi_js, eva_js, pluginRenderer, rendere
93
117
  }
94
118
  componentChanged(changed) {
95
119
  return __awaiter(this, void 0, void 0, function* () {
96
- if (changed.componentName !== 'Text') return;
120
+ const isText = changed.componentName === 'Text';
121
+ const isHTMLText = changed.componentName === 'HTMLText';
122
+ if (!isText && !isHTMLText) return;
97
123
  if (changed.type === eva_js.OBSERVER_TYPE.ADD) {
98
- const component = changed.component;
99
- const text = new rendererAdapter.Text(component.text, component.style);
100
- this.containerManager.getContainer(changed.gameObject.id).addChildAt(text, 0);
101
- this.texts[changed.gameObject.id] = {
102
- text,
103
- component: changed.component
104
- };
124
+ if (isText) {
125
+ yield this.addTextComponent(changed);
126
+ } else {
127
+ yield this.addHTMLTextComponent(changed);
128
+ }
105
129
  this.setSize(changed);
106
130
  } else if (changed.type === eva_js.OBSERVER_TYPE.REMOVE) {
107
131
  this.containerManager.getContainer(changed.gameObject.id).removeChild(this.texts[changed.gameObject.id].text);
@@ -111,19 +135,101 @@ var _EVA_IIFE_text = function (exports, pixi_js, eva_js, pluginRenderer, rendere
111
135
  delete this.texts[changed.gameObject.id];
112
136
  } else {
113
137
  this.change(changed);
138
+ const component = changed.component;
139
+ if (changed.prop.prop[0] === 'style' && component.style && component.style.fontFamily) {
140
+ const {
141
+ text
142
+ } = this.texts[changed.gameObject.id];
143
+ yield this.waitForFontResource(text, changed, component.style.fontFamily);
144
+ }
114
145
  this.setSize(changed);
115
146
  }
116
147
  });
117
148
  }
149
+ addTextComponent(changed) {
150
+ return __awaiter(this, void 0, void 0, function* () {
151
+ const component = changed.component;
152
+ const styleWithoutFont = _extends({}, component.style);
153
+ const fontFamily = styleWithoutFont.fontFamily;
154
+ delete styleWithoutFont.fontFamily;
155
+ const initialText = fontFamily ? '' : component.text;
156
+ const text = new rendererAdapter.Text(initialText, styleWithoutFont);
157
+ this.containerManager.getContainer(changed.gameObject.id).addChildAt(text, 0);
158
+ this.texts[changed.gameObject.id] = {
159
+ text,
160
+ component
161
+ };
162
+ if (fontFamily) {
163
+ yield this.waitForFontResource(text, changed, fontFamily);
164
+ }
165
+ });
166
+ }
167
+ addHTMLTextComponent(changed) {
168
+ return __awaiter(this, void 0, void 0, function* () {
169
+ const component = changed.component;
170
+ const styleWithoutFont = _extends({}, component.style);
171
+ const fontFamily = styleWithoutFont.fontFamily;
172
+ delete styleWithoutFont.fontFamily;
173
+ const initialText = fontFamily ? '' : component.text;
174
+ const htmlText = new rendererAdapter.HTMLText(_extends({
175
+ text: initialText,
176
+ style: styleWithoutFont
177
+ }, component.textureStyle && {
178
+ textureStyle: component.textureStyle
179
+ }));
180
+ this.containerManager.getContainer(changed.gameObject.id).addChildAt(htmlText, 0);
181
+ this.texts[changed.gameObject.id] = {
182
+ text: htmlText,
183
+ component
184
+ };
185
+ if (fontFamily) {
186
+ yield this.waitForFontResource(htmlText, changed, fontFamily);
187
+ }
188
+ });
189
+ }
190
+ waitForFontResource(text, changed, fontFamily) {
191
+ return __awaiter(this, void 0, void 0, function* () {
192
+ if (!fontFamily) {
193
+ return;
194
+ }
195
+ try {
196
+ const fontName = Array.isArray(fontFamily) ? fontFamily[0] : fontFamily;
197
+ const asyncId = this.increaseAsyncId(changed.gameObject.id);
198
+ yield eva_js.resource.getResource(fontName);
199
+ if (!this.validateAsyncId(changed.gameObject.id, asyncId)) return;
200
+ const component = this.texts[changed.gameObject.id].component;
201
+ text.style.fontFamily = fontFamily;
202
+ text.text = component.text;
203
+ } catch (error) {
204
+ console.warn(`字体资源 ${fontFamily} 加载失败:`, error);
205
+ }
206
+ });
207
+ }
118
208
  change(changed) {
119
209
  const {
120
210
  text,
121
211
  component
122
212
  } = this.texts[changed.gameObject.id];
213
+ const isHTMLText = changed.componentName === 'HTMLText';
123
214
  if (changed.prop.prop[0] === 'text') {
124
215
  text.text = component.text;
125
216
  } else if (changed.prop.prop[0] === 'style') {
126
- _extends(text.style, changed.component.style);
217
+ _extends(text.style, component.style);
218
+ } else if (changed.prop.prop[0] === 'textureStyle' && isHTMLText) {
219
+ const htmlComponent = component;
220
+ const container = this.containerManager.getContainer(changed.gameObject.id);
221
+ const index = container.getChildIndex(text);
222
+ container.removeChild(text);
223
+ text.destroy({
224
+ children: true
225
+ });
226
+ const newText = new rendererAdapter.HTMLText({
227
+ text: htmlComponent.text,
228
+ style: htmlComponent.style,
229
+ textureStyle: htmlComponent.textureStyle
230
+ });
231
+ container.addChildAt(newText, index);
232
+ this.texts[changed.gameObject.id].text = newText;
127
233
  }
128
234
  }
129
235
  setSize(changed) {
@@ -131,8 +237,12 @@ var _EVA_IIFE_text = function (exports, pixi_js, eva_js, pluginRenderer, rendere
131
237
  transform
132
238
  } = changed.gameObject;
133
239
  if (!transform) return;
134
- transform.size.width = this.texts[changed.gameObject.id].text.width;
135
- transform.size.height = this.texts[changed.gameObject.id].text.height;
240
+ const {
241
+ text
242
+ } = this.texts[changed.gameObject.id];
243
+ const size = text.getSize();
244
+ transform.size.width = size.width;
245
+ transform.size.height = size.height;
136
246
  }
137
247
  };
138
248
  Text.systemName = 'Text';
@@ -140,9 +250,17 @@ var _EVA_IIFE_text = function (exports, pixi_js, eva_js, pluginRenderer, rendere
140
250
  Text: ['text', {
141
251
  prop: ['style'],
142
252
  deep: true
253
+ }],
254
+ HTMLText: ['text', {
255
+ prop: ['style'],
256
+ deep: true
257
+ }, {
258
+ prop: ['textureStyle'],
259
+ deep: true
143
260
  }]
144
261
  })], Text);
145
262
  var Text$1 = Text;
263
+ exports.HTMLText = HTMLText;
146
264
  exports.Text = Text$2;
147
265
  exports.TextSystem = Text$1;
148
266
  Object.defineProperty(exports, '__esModule', {
@@ -1 +1 @@
1
- function _extends(){return _extends=Object.assign?Object.assign.bind():function(e){for(var t=1;t<arguments.length;t++){var n=arguments[t];for(var r in n)({}).hasOwnProperty.call(n,r)&&(e[r]=n[r])}return e},_extends.apply(null,arguments)}globalThis.EVA=globalThis.EVA||{},globalThis.EVA.plugin=globalThis.EVA.plugin||{},globalThis.EVA.plugin.renderer=globalThis.EVA.plugin.renderer||{};var _EVA_IIFE_text=function(e,t,n,r,s){"use strict";function o(e,t,n,r){var s,o=arguments.length,i=o<3?t:null===r?r=Object.getOwnPropertyDescriptor(t,n):r;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)i=Reflect.decorate(e,t,n,r);else for(var c=e.length-1;c>=0;c--)(s=e[c])&&(i=(o<3?s(i):o>3?s(t,n,i):s(t,n))||i);return o>3&&i&&Object.defineProperty(t,n,i),i}function i(e,t,n,r){return new(n||(n=Promise))((function(s,o){function i(e){try{a(r.next(e))}catch(e){o(e)}}function c(e){try{a(r.throw(e))}catch(e){o(e)}}function a(e){var t;e.done?s(e.value):(t=e.value,t instanceof n?t:new n((function(e){e(t)}))).then(i,c)}a((r=r.apply(e,t||[])).next())}))}class c extends n.Component{constructor(){super(...arguments),this.text="",this.style={}}init(e){const n=new t.TextStyle({fontSize:20}),r={};for(const e in n)0===e.indexOf("_")&&(r[e.substring(1)]=n[e]);delete r.styleKey,this.style=r,e&&(this.text=e.text,_extends(this.style,e.style))}}c.componentName="Text",o([function(e){return function(t,n){var r=function(e,t){return e.constructor.IDEProps||(e.constructor.IDEProps={}),e.constructor.IDEProps[t]||(e.constructor.IDEProps[t]={}),e.constructor.IDEProps[t]}(t,n);r.key=n,r.type=e}}("string")],c.prototype,"text",void 0);let a=class extends r.Renderer{constructor(){super(...arguments),this.name="Text",this.texts={}}init(){this.renderSystem=this.game.getSystem(r.RendererSystem),this.renderSystem.rendererManager.register(this)}componentChanged(e){return i(this,void 0,void 0,(function*(){if("Text"===e.componentName)if(e.type===n.OBSERVER_TYPE.ADD){const t=e.component,n=new s.Text(t.text,t.style);this.containerManager.getContainer(e.gameObject.id).addChildAt(n,0),this.texts[e.gameObject.id]={text:n,component:e.component},this.setSize(e)}else e.type===n.OBSERVER_TYPE.REMOVE?(this.containerManager.getContainer(e.gameObject.id).removeChild(this.texts[e.gameObject.id].text),this.texts[e.gameObject.id].text.destroy({children:!0}),delete this.texts[e.gameObject.id]):(this.change(e),this.setSize(e))}))}change(e){const{text:t,component:n}=this.texts[e.gameObject.id];"text"===e.prop.prop[0]?t.text=n.text:"style"===e.prop.prop[0]&&_extends(t.style,e.component.style)}setSize(e){const{transform:t}=e.gameObject;t&&(t.size.width=this.texts[e.gameObject.id].text.width,t.size.height=this.texts[e.gameObject.id].text.height)}};a.systemName="Text",a=o([n.decorators.componentObserver({Text:["text",{prop:["style"],deep:!0}]})],a);var l=a;return e.Text=c,e.TextSystem=l,Object.defineProperty(e,"__esModule",{value:!0}),e}({},PIXI,EVA,EVA.plugin.renderer,EVA.rendererAdapter);globalThis.EVA.plugin.renderer.text=globalThis.EVA.plugin.renderer.text||_EVA_IIFE_text;
1
+ function _extends(){return _extends=Object.assign?Object.assign.bind():function(t){for(var e=1;e<arguments.length;e++){var n=arguments[e];for(var i in n)({}).hasOwnProperty.call(n,i)&&(t[i]=n[i])}return t},_extends.apply(null,arguments)}globalThis.EVA=globalThis.EVA||{},globalThis.EVA.plugin=globalThis.EVA.plugin||{},globalThis.EVA.plugin.renderer=globalThis.EVA.plugin.renderer||{};var _EVA_IIFE_text=function(t,e,n,i,o){"use strict";function s(t,e,n,i){var o,s=arguments.length,r=s<3?e:null===i?i=Object.getOwnPropertyDescriptor(e,n):i;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)r=Reflect.decorate(t,e,n,i);else for(var c=t.length-1;c>=0;c--)(o=t[c])&&(r=(s<3?o(r):s>3?o(e,n,r):o(e,n))||r);return s>3&&r&&Object.defineProperty(e,n,r),r}function r(t,e,n,i){return new(n||(n=Promise))((function(o,s){function r(t){try{l(i.next(t))}catch(t){s(t)}}function c(t){try{l(i.throw(t))}catch(t){s(t)}}function l(t){var e;t.done?o(t.value):(e=t.value,e instanceof n?e:new n((function(t){t(e)}))).then(r,c)}l((i=i.apply(t,e||[])).next())}))}function c(t){return function(e,n){var i=function(t,e){return t.constructor.IDEProps||(t.constructor.IDEProps={}),t.constructor.IDEProps[e]||(t.constructor.IDEProps[e]={}),t.constructor.IDEProps[e]}(e,n);i.key=n,i.type=t}}class l extends n.Component{constructor(){super(...arguments),this.text="",this.style={}}init(t){const n=new e.TextStyle({fontSize:20}),i={};for(const t in n)0===t.indexOf("_")&&(i[t.substring(1)]=n[t]);delete i.styleKey,this.style=i,t&&(this.text=t.text,_extends(this.style,t.style))}}l.componentName="Text",s([c("string")],l.prototype,"text",void 0);class a extends n.Component{constructor(){super(...arguments),this.text="",this.style={},this.textureStyle={}}init(t){this.style=_extends({fontSize:24,fill:"#000000",fontFamily:"Arial"},null==t?void 0:t.style),this.textureStyle=_extends({scaleMode:"linear",resolution:window.devicePixelRatio||1},null==t?void 0:t.textureStyle),t&&(this.text=t.text)}}a.componentName="HTMLText",s([c("string")],a.prototype,"text",void 0);let d=class extends i.Renderer{constructor(){super(...arguments),this.name="Text",this.texts={}}init(){this.renderSystem=this.game.getSystem(i.RendererSystem),this.renderSystem.rendererManager.register(this)}componentChanged(t){return r(this,void 0,void 0,(function*(){const e="Text"===t.componentName,i="HTMLText"===t.componentName;if(e||i)if(t.type===n.OBSERVER_TYPE.ADD)e?yield this.addTextComponent(t):yield this.addHTMLTextComponent(t),this.setSize(t);else if(t.type===n.OBSERVER_TYPE.REMOVE)this.containerManager.getContainer(t.gameObject.id).removeChild(this.texts[t.gameObject.id].text),this.texts[t.gameObject.id].text.destroy({children:!0}),delete this.texts[t.gameObject.id];else{this.change(t);const e=t.component;if("style"===t.prop.prop[0]&&e.style&&e.style.fontFamily){const{text:n}=this.texts[t.gameObject.id];yield this.waitForFontResource(n,t,e.style.fontFamily)}this.setSize(t)}}))}addTextComponent(t){return r(this,void 0,void 0,(function*(){const e=t.component,n=_extends({},e.style),i=n.fontFamily;delete n.fontFamily;const s=i?"":e.text,r=new o.Text(s,n);this.containerManager.getContainer(t.gameObject.id).addChildAt(r,0),this.texts[t.gameObject.id]={text:r,component:e},i&&(yield this.waitForFontResource(r,t,i))}))}addHTMLTextComponent(t){return r(this,void 0,void 0,(function*(){const e=t.component,n=_extends({},e.style),i=n.fontFamily;delete n.fontFamily;const s=i?"":e.text,r=new o.HTMLText(_extends({text:s,style:n},e.textureStyle&&{textureStyle:e.textureStyle}));this.containerManager.getContainer(t.gameObject.id).addChildAt(r,0),this.texts[t.gameObject.id]={text:r,component:e},i&&(yield this.waitForFontResource(r,t,i))}))}waitForFontResource(t,e,i){return r(this,void 0,void 0,(function*(){if(i)try{const o=Array.isArray(i)?i[0]:i,s=this.increaseAsyncId(e.gameObject.id);if(yield n.resource.getResource(o),!this.validateAsyncId(e.gameObject.id,s))return;const r=this.texts[e.gameObject.id].component;t.style.fontFamily=i,t.text=r.text}catch(t){console.warn(`字体资源 ${i} 加载失败:`,t)}}))}change(t){const{text:e,component:n}=this.texts[t.gameObject.id],i="HTMLText"===t.componentName;if("text"===t.prop.prop[0])e.text=n.text;else if("style"===t.prop.prop[0])_extends(e.style,n.style);else if("textureStyle"===t.prop.prop[0]&&i){const i=n,s=this.containerManager.getContainer(t.gameObject.id),r=s.getChildIndex(e);s.removeChild(e),e.destroy({children:!0});const c=new o.HTMLText({text:i.text,style:i.style,textureStyle:i.textureStyle});s.addChildAt(c,r),this.texts[t.gameObject.id].text=c}}setSize(t){const{transform:e}=t.gameObject;if(!e)return;const{text:n}=this.texts[t.gameObject.id],i=n.getSize();e.size.width=i.width,e.size.height=i.height}};d.systemName="Text",d=s([n.decorators.componentObserver({Text:["text",{prop:["style"],deep:!0}],HTMLText:["text",{prop:["style"],deep:!0},{prop:["textureStyle"],deep:!0}]})],d);var x=d;return t.HTMLText=a,t.Text=l,t.TextSystem=x,Object.defineProperty(t,"__esModule",{value:!0}),t}({},PIXI,EVA,EVA.plugin.renderer,EVA.rendererAdapter);globalThis.EVA.plugin.renderer.text=globalThis.EVA.plugin.renderer.text||_EVA_IIFE_text;
@@ -39,12 +39,63 @@ function __awaiter(thisArg, _arguments, P, generator) {
39
39
  });
40
40
  }
41
41
 
42
+ /**
43
+ * 文本组件(基于 PixiJS Text)
44
+ *
45
+ * Text 组件用于渲染文本内容,支持丰富的文本样式配置。
46
+ * 它基于 PixiJS 的 Text 实现,支持字体、颜色、描边、阴影、对齐等多种样式。
47
+ *
48
+ * 主要特性:
49
+ * - 支持多种字体和字号
50
+ * - 支持文本颜色、渐变填充
51
+ * - 支持描边和投影效果
52
+ * - 支持文本对齐和换行
53
+ *
54
+ * @example
55
+ * ```typescript
56
+ * // 基础文本
57
+ * const label = new GameObject('label');
58
+ * label.addComponent(new Text({
59
+ * text: 'Hello EVA!',
60
+ * style: {
61
+ * fontSize: 32,
62
+ * fill: 0xffffff
63
+ * }
64
+ * }));
65
+ *
66
+ * // 带样式的文本
67
+ * label.addComponent(new Text({
68
+ * text: '得分: 9999',
69
+ * style: {
70
+ * fontFamily: 'Arial',
71
+ * fontSize: 48,
72
+ * fontWeight: 'bold',
73
+ * fill: ['#ff0000', '#ffff00'], // 渐变色
74
+ * stroke: '#000000',
75
+ * strokeThickness: 4,
76
+ * dropShadow: true,
77
+ * dropShadowDistance: 3
78
+ * }
79
+ * }));
80
+ * // 如需高清渲染,使用 Render 组件的 resolution 属性
81
+ * label.addComponent(new Render({ resolution: 2 }));
82
+ * ```
83
+ */
42
84
  class Text$2 extends eva_js.Component {
43
85
  constructor() {
44
86
  super(...arguments);
87
+ /** 文本内容 */
45
88
  this.text = '';
89
+ /** 文本样式配置 */
90
+ // @decorators.IDEProp 复杂编辑后续添加
46
91
  this.style = {};
47
92
  }
93
+ /**
94
+ * 初始化组件
95
+ * @param obj - 初始化参数
96
+ * @param obj.text - 文本内容
97
+ * @param obj.style - 文本样式
98
+ */
48
99
  init(obj) {
49
100
  const style = new pixi_js.TextStyle({
50
101
  fontSize: 20,
@@ -63,11 +114,104 @@ class Text$2 extends eva_js.Component {
63
114
  }
64
115
  }
65
116
  }
117
+ /** 组件名称 */
66
118
  Text$2.componentName = 'Text';
67
119
  __decorate([
68
120
  inspectorDecorator.type('string')
69
121
  ], Text$2.prototype, "text", void 0);
70
122
 
123
+ /**
124
+ * HTML 富文本组件
125
+ *
126
+ * HTMLText 组件支持渲染带有 HTML 标签的富文本内容。
127
+ * 可以在文本中使用 HTML 标签(如 `<b>`, `<i>`, `<span>` 等)来实现丰富的文本样式,
128
+ * 适用于聊天对话、新闻内容、富文本显示等需要多样式文本的场景。
129
+ *
130
+ * 支持的 HTML 标签:
131
+ * - `<b>` - 粗体
132
+ * - `<i>` - 斜体
133
+ * - `<span style="color:#ff0000">` - 自定义样式
134
+ * - `<br>` - 换行
135
+ * 以及更多标准 HTML 文本标签
136
+ *
137
+ * @example
138
+ * ```typescript
139
+ * // 基础富文本
140
+ * const label = new GameObject('label');
141
+ * label.addComponent(new HTMLText({
142
+ * text: '这是<b>粗体</b>和<i>斜体</i>文本',
143
+ * style: {
144
+ * fontSize: 24,
145
+ * fill: '#000000',
146
+ * fontFamily: 'Arial'
147
+ * }
148
+ * }));
149
+ *
150
+ * // 带颜色的富文本
151
+ * label.addComponent(new HTMLText({
152
+ * text: '欢迎 <span style="color:#ff0000">玩家123</span> 加入游戏!',
153
+ * style: {
154
+ * fontSize: 20,
155
+ * wordWrap: true,
156
+ * wordWrapWidth: 300
157
+ * }
158
+ * }));
159
+ *
160
+ * // 自定义标签样式
161
+ * label.addComponent(new HTMLText({
162
+ * text: '获得 <gold>100</gold> 金币',
163
+ * style: {
164
+ * fontSize: 18,
165
+ * tagStyles: {
166
+ * gold: {
167
+ * fill: '#ffd700',
168
+ * fontWeight: 'bold'
169
+ * }
170
+ * }
171
+ * }
172
+ * }));
173
+ *
174
+ * // 高分辨率渲染(推荐使用 Render 组件的 resolution 属性)
175
+ * label.addComponent(new HTMLText({
176
+ * text: '高清文本',
177
+ * textureStyle: {
178
+ * scaleMode: 'linear'
179
+ * }
180
+ * }));
181
+ * label.addComponent(new Render({ resolution: 2 }));
182
+ * ```
183
+ */
184
+ class HTMLText extends eva_js.Component {
185
+ constructor() {
186
+ super(...arguments);
187
+ /** 富文本内容(支持 HTML 标签) */
188
+ this.text = '';
189
+ /** 文本样式配置 */
190
+ this.style = {};
191
+ /** 纹理渲染配置 */
192
+ this.textureStyle = {};
193
+ }
194
+ /**
195
+ * 初始化组件
196
+ * @param obj - 初始化参数
197
+ * @param obj.text - 富文本内容
198
+ * @param obj.style - 文本样式
199
+ * @param obj.textureStyle - 纹理配置
200
+ */
201
+ init(obj) {
202
+ this.style = Object.assign({ fontSize: 24, fill: '#000000', fontFamily: 'Arial' }, obj === null || obj === void 0 ? void 0 : obj.style);
203
+ this.textureStyle = Object.assign({ scaleMode: 'linear', resolution: window.devicePixelRatio || 1 }, obj === null || obj === void 0 ? void 0 : obj.textureStyle);
204
+ if (obj) {
205
+ this.text = obj.text;
206
+ }
207
+ }
208
+ }
209
+ /** 组件名称 */
210
+ HTMLText.componentName = 'HTMLText';
211
+ __decorate([
212
+ inspectorDecorator.type('string')
213
+ ], HTMLText.prototype, "text", void 0);
214
+
71
215
  let Text = class Text extends pluginRenderer.Renderer {
72
216
  constructor() {
73
217
  super(...arguments);
@@ -80,16 +224,17 @@ let Text = class Text extends pluginRenderer.Renderer {
80
224
  }
81
225
  componentChanged(changed) {
82
226
  return __awaiter(this, void 0, void 0, function* () {
83
- if (changed.componentName !== 'Text')
227
+ const isText = changed.componentName === 'Text';
228
+ const isHTMLText = changed.componentName === 'HTMLText';
229
+ if (!isText && !isHTMLText)
84
230
  return;
85
231
  if (changed.type === eva_js.OBSERVER_TYPE.ADD) {
86
- const component = changed.component;
87
- const text = new rendererAdapter.Text(component.text, component.style);
88
- this.containerManager.getContainer(changed.gameObject.id).addChildAt(text, 0);
89
- this.texts[changed.gameObject.id] = {
90
- text,
91
- component: changed.component,
92
- };
232
+ if (isText) {
233
+ yield this.addTextComponent(changed);
234
+ }
235
+ else {
236
+ yield this.addHTMLTextComponent(changed);
237
+ }
93
238
  this.setSize(changed);
94
239
  }
95
240
  else if (changed.type === eva_js.OBSERVER_TYPE.REMOVE) {
@@ -99,34 +244,127 @@ let Text = class Text extends pluginRenderer.Renderer {
99
244
  }
100
245
  else {
101
246
  this.change(changed);
247
+ // 如果样式改变且涉及字体,也需要等待字体资源加载
248
+ const component = changed.component;
249
+ if (changed.prop.prop[0] === 'style' && component.style && component.style.fontFamily) {
250
+ const { text } = this.texts[changed.gameObject.id];
251
+ yield this.waitForFontResource(text, changed, component.style.fontFamily);
252
+ }
102
253
  this.setSize(changed);
103
254
  }
104
255
  });
105
256
  }
257
+ addTextComponent(changed) {
258
+ return __awaiter(this, void 0, void 0, function* () {
259
+ const component = changed.component;
260
+ // 创建文本样式副本,先不设置 fontFamily
261
+ const styleWithoutFont = Object.assign({}, component.style);
262
+ const fontFamily = styleWithoutFont.fontFamily;
263
+ delete styleWithoutFont.fontFamily;
264
+ const initialText = fontFamily ? '' : component.text;
265
+ const text = new rendererAdapter.Text(initialText, styleWithoutFont);
266
+ this.containerManager.getContainer(changed.gameObject.id).addChildAt(text, 0);
267
+ this.texts[changed.gameObject.id] = {
268
+ text,
269
+ component,
270
+ };
271
+ // 如果指定了字体资源,等待资源加载完成后设置 fontFamily
272
+ if (fontFamily) {
273
+ yield this.waitForFontResource(text, changed, fontFamily);
274
+ }
275
+ });
276
+ }
277
+ addHTMLTextComponent(changed) {
278
+ return __awaiter(this, void 0, void 0, function* () {
279
+ const component = changed.component;
280
+ // 创建样式副本,先不设置 fontFamily
281
+ const styleWithoutFont = Object.assign({}, component.style);
282
+ const fontFamily = styleWithoutFont.fontFamily;
283
+ delete styleWithoutFont.fontFamily;
284
+ const initialText = fontFamily ? '' : component.text;
285
+ const htmlText = new rendererAdapter.HTMLText(Object.assign({ text: initialText, style: styleWithoutFont }, (component.textureStyle && { textureStyle: component.textureStyle })));
286
+ this.containerManager.getContainer(changed.gameObject.id).addChildAt(htmlText, 0);
287
+ this.texts[changed.gameObject.id] = {
288
+ text: htmlText,
289
+ component,
290
+ };
291
+ // 如果指定了字体资源,等待资源加载完成后设置 fontFamily
292
+ if (fontFamily) {
293
+ yield this.waitForFontResource(htmlText, changed, fontFamily);
294
+ }
295
+ });
296
+ }
297
+ /**
298
+ * 等待字体资源加载完成并更新文本
299
+ */
300
+ waitForFontResource(text, changed, fontFamily) {
301
+ return __awaiter(this, void 0, void 0, function* () {
302
+ if (!fontFamily) {
303
+ return;
304
+ }
305
+ try {
306
+ const fontName = Array.isArray(fontFamily) ? fontFamily[0] : fontFamily;
307
+ // 通过 resource 系统获取字体资源
308
+ const asyncId = this.increaseAsyncId(changed.gameObject.id);
309
+ yield eva_js.resource.getResource(fontName);
310
+ // 验证异步操作是否仍然有效(防止组件已被移除)
311
+ if (!this.validateAsyncId(changed.gameObject.id, asyncId))
312
+ return;
313
+ // 字体资源加载成功后,设置 fontFamily 并重新设置文本内容以触发重新渲染
314
+ const component = this.texts[changed.gameObject.id].component;
315
+ text.style.fontFamily = fontFamily;
316
+ text.text = component.text;
317
+ // 更新尺寸
318
+ }
319
+ catch (error) {
320
+ console.warn(`字体资源 ${fontFamily} 加载失败:`, error);
321
+ }
322
+ });
323
+ }
106
324
  change(changed) {
107
325
  const { text, component } = this.texts[changed.gameObject.id];
326
+ const isHTMLText = changed.componentName === 'HTMLText';
108
327
  if (changed.prop.prop[0] === 'text') {
109
328
  text.text = component.text;
110
329
  }
111
330
  else if (changed.prop.prop[0] === 'style') {
112
- Object.assign(text.style, changed.component.style);
331
+ Object.assign(text.style, component.style);
332
+ }
333
+ else if (changed.prop.prop[0] === 'textureStyle' && isHTMLText) {
334
+ // HTMLText 纹理样式变化需要重新创建
335
+ const htmlComponent = component;
336
+ const container = this.containerManager.getContainer(changed.gameObject.id);
337
+ const index = container.getChildIndex(text);
338
+ container.removeChild(text);
339
+ text.destroy({ children: true });
340
+ const newText = new rendererAdapter.HTMLText({
341
+ text: htmlComponent.text,
342
+ style: htmlComponent.style,
343
+ textureStyle: htmlComponent.textureStyle
344
+ });
345
+ container.addChildAt(newText, index);
346
+ this.texts[changed.gameObject.id].text = newText;
113
347
  }
114
348
  }
115
349
  setSize(changed) {
116
350
  const { transform } = changed.gameObject;
117
351
  if (!transform)
118
352
  return;
119
- transform.size.width = this.texts[changed.gameObject.id].text.width;
120
- transform.size.height = this.texts[changed.gameObject.id].text.height;
353
+ const { text } = this.texts[changed.gameObject.id];
354
+ const size = text.getSize();
355
+ transform.size.width = size.width;
356
+ transform.size.height = size.height;
121
357
  }
122
358
  };
123
359
  Text.systemName = 'Text';
124
360
  Text = __decorate([
125
361
  eva_js.decorators.componentObserver({
126
362
  Text: ['text', { prop: ['style'], deep: true }],
363
+ HTMLText: ['text', { prop: ['style'], deep: true }, { prop: ['textureStyle'], deep: true }],
127
364
  })
128
365
  ], Text);
129
366
  var Text$1 = Text;
130
367
 
368
+ exports.HTMLText = HTMLText;
131
369
  exports.Text = Text$2;
132
370
  exports.TextSystem = Text$1;
@@ -1,4 +1,4 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:!0});var e=require("pixi.js"),t=require("@eva/eva.js"),n=require("@eva/inspector-decorator"),s=require("@eva/plugin-renderer"),r=require("@eva/renderer-adapter");
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:!0});var e=require("pixi.js"),t=require("@eva/eva.js"),n=require("@eva/inspector-decorator"),i=require("@eva/plugin-renderer"),s=require("@eva/renderer-adapter");
2
2
  /*! *****************************************************************************
3
3
  Copyright (c) Microsoft Corporation. All rights reserved.
4
4
  Licensed under the Apache License, Version 2.0 (the "License"); you may not use
@@ -13,4 +13,4 @@ MERCHANTABLITY OR NON-INFRINGEMENT.
13
13
  See the Apache Version 2.0 License for specific language governing permissions
14
14
  and limitations under the License.
15
15
  ***************************************************************************** */
16
- function i(e,t,n,s){var r,i=arguments.length,o=i<3?t:null===s?s=Object.getOwnPropertyDescriptor(t,n):s;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)o=Reflect.decorate(e,t,n,s);else for(var c=e.length-1;c>=0;c--)(r=e[c])&&(o=(i<3?r(o):i>3?r(t,n,o):r(t,n))||o);return i>3&&o&&Object.defineProperty(t,n,o),o}class o extends t.Component{constructor(){super(...arguments),this.text="",this.style={}}init(t){const n=new e.TextStyle({fontSize:20}),s={};for(const e in n)0===e.indexOf("_")&&(s[e.substring(1)]=n[e]);delete s.styleKey,this.style=s,t&&(this.text=t.text,Object.assign(this.style,t.style))}}o.componentName="Text",i([n.type("string")],o.prototype,"text",void 0);let c=class extends s.Renderer{constructor(){super(...arguments),this.name="Text",this.texts={}}init(){this.renderSystem=this.game.getSystem(s.RendererSystem),this.renderSystem.rendererManager.register(this)}componentChanged(e){return n=this,s=void 0,o=function*(){if("Text"===e.componentName)if(e.type===t.OBSERVER_TYPE.ADD){const t=e.component,n=new r.Text(t.text,t.style);this.containerManager.getContainer(e.gameObject.id).addChildAt(n,0),this.texts[e.gameObject.id]={text:n,component:e.component},this.setSize(e)}else e.type===t.OBSERVER_TYPE.REMOVE?(this.containerManager.getContainer(e.gameObject.id).removeChild(this.texts[e.gameObject.id].text),this.texts[e.gameObject.id].text.destroy({children:!0}),delete this.texts[e.gameObject.id]):(this.change(e),this.setSize(e))},new((i=void 0)||(i=Promise))((function(e,t){function r(e){try{a(o.next(e))}catch(e){t(e)}}function c(e){try{a(o.throw(e))}catch(e){t(e)}}function a(t){t.done?e(t.value):new i((function(e){e(t.value)})).then(r,c)}a((o=o.apply(n,s||[])).next())}));var n,s,i,o}change(e){const{text:t,component:n}=this.texts[e.gameObject.id];"text"===e.prop.prop[0]?t.text=n.text:"style"===e.prop.prop[0]&&Object.assign(t.style,e.component.style)}setSize(e){const{transform:t}=e.gameObject;t&&(t.size.width=this.texts[e.gameObject.id].text.width,t.size.height=this.texts[e.gameObject.id].text.height)}};c.systemName="Text",c=i([t.decorators.componentObserver({Text:["text",{prop:["style"],deep:!0}]})],c);var a=c;exports.Text=o,exports.TextSystem=a;
16
+ function o(e,t,n,i){var s,o=arguments.length,r=o<3?t:null===i?i=Object.getOwnPropertyDescriptor(t,n):i;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)r=Reflect.decorate(e,t,n,i);else for(var c=e.length-1;c>=0;c--)(s=e[c])&&(r=(o<3?s(r):o>3?s(t,n,r):s(t,n))||r);return o>3&&r&&Object.defineProperty(t,n,r),r}function r(e,t,n,i){return new(n||(n=Promise))((function(s,o){function r(e){try{a(i.next(e))}catch(e){o(e)}}function c(e){try{a(i.throw(e))}catch(e){o(e)}}function a(e){e.done?s(e.value):new n((function(t){t(e.value)})).then(r,c)}a((i=i.apply(e,t||[])).next())}))}class c extends t.Component{constructor(){super(...arguments),this.text="",this.style={}}init(t){const n=new e.TextStyle({fontSize:20}),i={};for(const e in n)0===e.indexOf("_")&&(i[e.substring(1)]=n[e]);delete i.styleKey,this.style=i,t&&(this.text=t.text,Object.assign(this.style,t.style))}}c.componentName="Text",o([n.type("string")],c.prototype,"text",void 0);class a extends t.Component{constructor(){super(...arguments),this.text="",this.style={},this.textureStyle={}}init(e){this.style=Object.assign({fontSize:24,fill:"#000000",fontFamily:"Arial"},null==e?void 0:e.style),this.textureStyle=Object.assign({scaleMode:"linear",resolution:window.devicePixelRatio||1},null==e?void 0:e.textureStyle),e&&(this.text=e.text)}}a.componentName="HTMLText",o([n.type("string")],a.prototype,"text",void 0);let d=class extends i.Renderer{constructor(){super(...arguments),this.name="Text",this.texts={}}init(){this.renderSystem=this.game.getSystem(i.RendererSystem),this.renderSystem.rendererManager.register(this)}componentChanged(e){return r(this,void 0,void 0,(function*(){const n="Text"===e.componentName,i="HTMLText"===e.componentName;if(n||i)if(e.type===t.OBSERVER_TYPE.ADD)n?yield this.addTextComponent(e):yield this.addHTMLTextComponent(e),this.setSize(e);else if(e.type===t.OBSERVER_TYPE.REMOVE)this.containerManager.getContainer(e.gameObject.id).removeChild(this.texts[e.gameObject.id].text),this.texts[e.gameObject.id].text.destroy({children:!0}),delete this.texts[e.gameObject.id];else{this.change(e);const t=e.component;if("style"===e.prop.prop[0]&&t.style&&t.style.fontFamily){const{text:n}=this.texts[e.gameObject.id];yield this.waitForFontResource(n,e,t.style.fontFamily)}this.setSize(e)}}))}addTextComponent(e){return r(this,void 0,void 0,(function*(){const t=e.component,n=Object.assign({},t.style),i=n.fontFamily;delete n.fontFamily;const o=i?"":t.text,r=new s.Text(o,n);this.containerManager.getContainer(e.gameObject.id).addChildAt(r,0),this.texts[e.gameObject.id]={text:r,component:t},i&&(yield this.waitForFontResource(r,e,i))}))}addHTMLTextComponent(e){return r(this,void 0,void 0,(function*(){const t=e.component,n=Object.assign({},t.style),i=n.fontFamily;delete n.fontFamily;const o=i?"":t.text,r=new s.HTMLText(Object.assign({text:o,style:n},t.textureStyle&&{textureStyle:t.textureStyle}));this.containerManager.getContainer(e.gameObject.id).addChildAt(r,0),this.texts[e.gameObject.id]={text:r,component:t},i&&(yield this.waitForFontResource(r,e,i))}))}waitForFontResource(e,n,i){return r(this,void 0,void 0,(function*(){if(i)try{const s=Array.isArray(i)?i[0]:i,o=this.increaseAsyncId(n.gameObject.id);if(yield t.resource.getResource(s),!this.validateAsyncId(n.gameObject.id,o))return;const r=this.texts[n.gameObject.id].component;e.style.fontFamily=i,e.text=r.text}catch(e){console.warn(`字体资源 ${i} 加载失败:`,e)}}))}change(e){const{text:t,component:n}=this.texts[e.gameObject.id],i="HTMLText"===e.componentName;if("text"===e.prop.prop[0])t.text=n.text;else if("style"===e.prop.prop[0])Object.assign(t.style,n.style);else if("textureStyle"===e.prop.prop[0]&&i){const i=n,o=this.containerManager.getContainer(e.gameObject.id),r=o.getChildIndex(t);o.removeChild(t),t.destroy({children:!0});const c=new s.HTMLText({text:i.text,style:i.style,textureStyle:i.textureStyle});o.addChildAt(c,r),this.texts[e.gameObject.id].text=c}}setSize(e){const{transform:t}=e.gameObject;if(!t)return;const{text:n}=this.texts[e.gameObject.id],i=n.getSize();t.size.width=i.width,t.size.height=i.height}};d.systemName="Text",d=o([t.decorators.componentObserver({Text:["text",{prop:["style"],deep:!0}],HTMLText:["text",{prop:["style"],deep:!0},{prop:["textureStyle"],deep:!0}]})],d);var l=d;exports.HTMLText=a,exports.Text=c,exports.TextSystem=l;
@@ -1,6 +1,7 @@
1
1
  import { Component } from '@eva/eva.js';
2
2
  import { ComponentChanged } from '@eva/eva.js';
3
3
  import { ContainerManager } from '@eva/plugin-renderer';
4
+ import { HTMLText as HTMLText_2 } from '@eva/renderer-adapter';
4
5
  import { Renderer } from '@eva/plugin-renderer';
5
6
  import { RendererManager } from '@eva/plugin-renderer';
6
7
  import { RendererSystem } from '@eva/plugin-renderer';
@@ -12,10 +13,180 @@ import { TextStyleFontWeight } from 'pixi.js';
12
13
  import { TextStyleTextBaseline } from 'pixi.js';
13
14
  import { TextStyleWhiteSpace } from 'pixi.js';
14
15
 
16
+ /**
17
+ * HTML 富文本组件
18
+ *
19
+ * HTMLText 组件支持渲染带有 HTML 标签的富文本内容。
20
+ * 可以在文本中使用 HTML 标签(如 `<b>`, `<i>`, `<span>` 等)来实现丰富的文本样式,
21
+ * 适用于聊天对话、新闻内容、富文本显示等需要多样式文本的场景。
22
+ *
23
+ * 支持的 HTML 标签:
24
+ * - `<b>` - 粗体
25
+ * - `<i>` - 斜体
26
+ * - `<span style="color:#ff0000">` - 自定义样式
27
+ * - `<br>` - 换行
28
+ * 以及更多标准 HTML 文本标签
29
+ *
30
+ * @example
31
+ * ```typescript
32
+ * // 基础富文本
33
+ * const label = new GameObject('label');
34
+ * label.addComponent(new HTMLText({
35
+ * text: '这是<b>粗体</b>和<i>斜体</i>文本',
36
+ * style: {
37
+ * fontSize: 24,
38
+ * fill: '#000000',
39
+ * fontFamily: 'Arial'
40
+ * }
41
+ * }));
42
+ *
43
+ * // 带颜色的富文本
44
+ * label.addComponent(new HTMLText({
45
+ * text: '欢迎 <span style="color:#ff0000">玩家123</span> 加入游戏!',
46
+ * style: {
47
+ * fontSize: 20,
48
+ * wordWrap: true,
49
+ * wordWrapWidth: 300
50
+ * }
51
+ * }));
52
+ *
53
+ * // 自定义标签样式
54
+ * label.addComponent(new HTMLText({
55
+ * text: '获得 <gold>100</gold> 金币',
56
+ * style: {
57
+ * fontSize: 18,
58
+ * tagStyles: {
59
+ * gold: {
60
+ * fill: '#ffd700',
61
+ * fontWeight: 'bold'
62
+ * }
63
+ * }
64
+ * }
65
+ * }));
66
+ *
67
+ * // 高分辨率渲染(推荐使用 Render 组件的 resolution 属性)
68
+ * label.addComponent(new HTMLText({
69
+ * text: '高清文本',
70
+ * textureStyle: {
71
+ * scaleMode: 'linear'
72
+ * }
73
+ * }));
74
+ * label.addComponent(new Render({ resolution: 2 }));
75
+ * ```
76
+ */
77
+ export declare class HTMLText extends Component<HTMLTextParams> {
78
+ /** 组件名称 */
79
+ static componentName: string;
80
+ /** 富文本内容(支持 HTML 标签) */
81
+ text: string;
82
+ /** 文本样式配置 */
83
+ style: HTMLTextParams['style'];
84
+ /** 纹理渲染配置 */
85
+ textureStyle: HTMLTextParams['textureStyle'];
86
+ /**
87
+ * 初始化组件
88
+ * @param obj - 初始化参数
89
+ * @param obj.text - 富文本内容
90
+ * @param obj.style - 文本样式
91
+ * @param obj.textureStyle - 纹理配置
92
+ */
93
+ init(obj?: HTMLTextParams): void;
94
+ }
95
+
96
+ export declare interface HTMLTextParams {
97
+ text: string;
98
+ style?: HTMLTextStyleOptions & {
99
+ cssOverrides?: string[];
100
+ wordWrap?: boolean;
101
+ wordWrapWidth?: number;
102
+ tagStyles?: Record<string, HTMLTextStyleOptions>;
103
+ };
104
+ textureStyle?: {
105
+ scaleMode?: 'linear' | 'nearest';
106
+ resolution?: number;
107
+ };
108
+ }
109
+
110
+ declare interface HTMLTextStyleOptions {
111
+ fontFamily?: string | string[];
112
+ fontSize?: number | string;
113
+ fill?: string | number;
114
+ align?: 'left' | 'center' | 'right' | 'justify';
115
+ breakWords?: boolean;
116
+ letterSpacing?: number;
117
+ lineHeight?: number;
118
+ padding?: number;
119
+ stroke?: string | number;
120
+ strokeThickness?: number;
121
+ dropShadow?: boolean;
122
+ dropShadowAlpha?: number;
123
+ dropShadowAngle?: number;
124
+ dropShadowBlur?: number;
125
+ dropShadowColor?: string | number;
126
+ dropShadowDistance?: number;
127
+ trim?: boolean;
128
+ fontWeight?: string;
129
+ fontStyle?: string;
130
+ fontVariant?: string;
131
+ textBaseline?: string;
132
+ whiteSpace?: string;
133
+ }
134
+
135
+ /**
136
+ * 文本组件(基于 PixiJS Text)
137
+ *
138
+ * Text 组件用于渲染文本内容,支持丰富的文本样式配置。
139
+ * 它基于 PixiJS 的 Text 实现,支持字体、颜色、描边、阴影、对齐等多种样式。
140
+ *
141
+ * 主要特性:
142
+ * - 支持多种字体和字号
143
+ * - 支持文本颜色、渐变填充
144
+ * - 支持描边和投影效果
145
+ * - 支持文本对齐和换行
146
+ *
147
+ * @example
148
+ * ```typescript
149
+ * // 基础文本
150
+ * const label = new GameObject('label');
151
+ * label.addComponent(new Text({
152
+ * text: 'Hello EVA!',
153
+ * style: {
154
+ * fontSize: 32,
155
+ * fill: 0xffffff
156
+ * }
157
+ * }));
158
+ *
159
+ * // 带样式的文本
160
+ * label.addComponent(new Text({
161
+ * text: '得分: 9999',
162
+ * style: {
163
+ * fontFamily: 'Arial',
164
+ * fontSize: 48,
165
+ * fontWeight: 'bold',
166
+ * fill: ['#ff0000', '#ffff00'], // 渐变色
167
+ * stroke: '#000000',
168
+ * strokeThickness: 4,
169
+ * dropShadow: true,
170
+ * dropShadowDistance: 3
171
+ * }
172
+ * }));
173
+ * // 如需高清渲染,使用 Render 组件的 resolution 属性
174
+ * label.addComponent(new Render({ resolution: 2 }));
175
+ * ```
176
+ */
15
177
  declare class Text_2 extends Component<TextParams> {
178
+ /** 组件名称 */
16
179
  static componentName: string;
180
+ /** 文本内容 */
17
181
  text: string;
182
+ /** 文本样式配置 */
18
183
  style: TextParams['style'];
184
+ /**
185
+ * 初始化组件
186
+ * @param obj - 初始化参数
187
+ * @param obj.text - 文本内容
188
+ * @param obj.style - 文本样式
189
+ */
19
190
  init(obj?: TextParams): void;
20
191
  }
21
192
  export { Text_2 as Text }
@@ -60,8 +231,8 @@ export declare class TextSystem extends Renderer {
60
231
  name: string;
61
232
  texts: {
62
233
  [propName: number]: {
63
- text: Text_3;
64
- component: Text_2;
234
+ text: Text_3 | HTMLText_2;
235
+ component: Text_2 | HTMLText;
65
236
  };
66
237
  };
67
238
  renderSystem: RendererSystem;
@@ -69,6 +240,12 @@ export declare class TextSystem extends Renderer {
69
240
  containerManager: ContainerManager;
70
241
  init(): void;
71
242
  componentChanged(changed: ComponentChanged): Promise<void>;
243
+ private addTextComponent;
244
+ private addHTMLTextComponent;
245
+ /**
246
+ * 等待字体资源加载完成并更新文本
247
+ */
248
+ private waitForFontResource;
72
249
  change(changed: ComponentChanged): void;
73
250
  setSize(changed: ComponentChanged): void;
74
251
  }
@@ -1,8 +1,8 @@
1
1
  import { TextStyle } from 'pixi.js';
2
- import { Component, decorators, OBSERVER_TYPE } from '@eva/eva.js';
2
+ import { Component, decorators, OBSERVER_TYPE, resource } from '@eva/eva.js';
3
3
  import { type } from '@eva/inspector-decorator';
4
4
  import { Renderer, RendererSystem } from '@eva/plugin-renderer';
5
- import { Text as Text$3 } from '@eva/renderer-adapter';
5
+ import { HTMLText as HTMLText$1, Text as Text$3 } from '@eva/renderer-adapter';
6
6
 
7
7
  /*! *****************************************************************************
8
8
  Copyright (c) Microsoft Corporation. All rights reserved.
@@ -35,12 +35,63 @@ function __awaiter(thisArg, _arguments, P, generator) {
35
35
  });
36
36
  }
37
37
 
38
+ /**
39
+ * 文本组件(基于 PixiJS Text)
40
+ *
41
+ * Text 组件用于渲染文本内容,支持丰富的文本样式配置。
42
+ * 它基于 PixiJS 的 Text 实现,支持字体、颜色、描边、阴影、对齐等多种样式。
43
+ *
44
+ * 主要特性:
45
+ * - 支持多种字体和字号
46
+ * - 支持文本颜色、渐变填充
47
+ * - 支持描边和投影效果
48
+ * - 支持文本对齐和换行
49
+ *
50
+ * @example
51
+ * ```typescript
52
+ * // 基础文本
53
+ * const label = new GameObject('label');
54
+ * label.addComponent(new Text({
55
+ * text: 'Hello EVA!',
56
+ * style: {
57
+ * fontSize: 32,
58
+ * fill: 0xffffff
59
+ * }
60
+ * }));
61
+ *
62
+ * // 带样式的文本
63
+ * label.addComponent(new Text({
64
+ * text: '得分: 9999',
65
+ * style: {
66
+ * fontFamily: 'Arial',
67
+ * fontSize: 48,
68
+ * fontWeight: 'bold',
69
+ * fill: ['#ff0000', '#ffff00'], // 渐变色
70
+ * stroke: '#000000',
71
+ * strokeThickness: 4,
72
+ * dropShadow: true,
73
+ * dropShadowDistance: 3
74
+ * }
75
+ * }));
76
+ * // 如需高清渲染,使用 Render 组件的 resolution 属性
77
+ * label.addComponent(new Render({ resolution: 2 }));
78
+ * ```
79
+ */
38
80
  class Text$2 extends Component {
39
81
  constructor() {
40
82
  super(...arguments);
83
+ /** 文本内容 */
41
84
  this.text = '';
85
+ /** 文本样式配置 */
86
+ // @decorators.IDEProp 复杂编辑后续添加
42
87
  this.style = {};
43
88
  }
89
+ /**
90
+ * 初始化组件
91
+ * @param obj - 初始化参数
92
+ * @param obj.text - 文本内容
93
+ * @param obj.style - 文本样式
94
+ */
44
95
  init(obj) {
45
96
  const style = new TextStyle({
46
97
  fontSize: 20,
@@ -59,11 +110,104 @@ class Text$2 extends Component {
59
110
  }
60
111
  }
61
112
  }
113
+ /** 组件名称 */
62
114
  Text$2.componentName = 'Text';
63
115
  __decorate([
64
116
  type('string')
65
117
  ], Text$2.prototype, "text", void 0);
66
118
 
119
+ /**
120
+ * HTML 富文本组件
121
+ *
122
+ * HTMLText 组件支持渲染带有 HTML 标签的富文本内容。
123
+ * 可以在文本中使用 HTML 标签(如 `<b>`, `<i>`, `<span>` 等)来实现丰富的文本样式,
124
+ * 适用于聊天对话、新闻内容、富文本显示等需要多样式文本的场景。
125
+ *
126
+ * 支持的 HTML 标签:
127
+ * - `<b>` - 粗体
128
+ * - `<i>` - 斜体
129
+ * - `<span style="color:#ff0000">` - 自定义样式
130
+ * - `<br>` - 换行
131
+ * 以及更多标准 HTML 文本标签
132
+ *
133
+ * @example
134
+ * ```typescript
135
+ * // 基础富文本
136
+ * const label = new GameObject('label');
137
+ * label.addComponent(new HTMLText({
138
+ * text: '这是<b>粗体</b>和<i>斜体</i>文本',
139
+ * style: {
140
+ * fontSize: 24,
141
+ * fill: '#000000',
142
+ * fontFamily: 'Arial'
143
+ * }
144
+ * }));
145
+ *
146
+ * // 带颜色的富文本
147
+ * label.addComponent(new HTMLText({
148
+ * text: '欢迎 <span style="color:#ff0000">玩家123</span> 加入游戏!',
149
+ * style: {
150
+ * fontSize: 20,
151
+ * wordWrap: true,
152
+ * wordWrapWidth: 300
153
+ * }
154
+ * }));
155
+ *
156
+ * // 自定义标签样式
157
+ * label.addComponent(new HTMLText({
158
+ * text: '获得 <gold>100</gold> 金币',
159
+ * style: {
160
+ * fontSize: 18,
161
+ * tagStyles: {
162
+ * gold: {
163
+ * fill: '#ffd700',
164
+ * fontWeight: 'bold'
165
+ * }
166
+ * }
167
+ * }
168
+ * }));
169
+ *
170
+ * // 高分辨率渲染(推荐使用 Render 组件的 resolution 属性)
171
+ * label.addComponent(new HTMLText({
172
+ * text: '高清文本',
173
+ * textureStyle: {
174
+ * scaleMode: 'linear'
175
+ * }
176
+ * }));
177
+ * label.addComponent(new Render({ resolution: 2 }));
178
+ * ```
179
+ */
180
+ class HTMLText extends Component {
181
+ constructor() {
182
+ super(...arguments);
183
+ /** 富文本内容(支持 HTML 标签) */
184
+ this.text = '';
185
+ /** 文本样式配置 */
186
+ this.style = {};
187
+ /** 纹理渲染配置 */
188
+ this.textureStyle = {};
189
+ }
190
+ /**
191
+ * 初始化组件
192
+ * @param obj - 初始化参数
193
+ * @param obj.text - 富文本内容
194
+ * @param obj.style - 文本样式
195
+ * @param obj.textureStyle - 纹理配置
196
+ */
197
+ init(obj) {
198
+ this.style = Object.assign({ fontSize: 24, fill: '#000000', fontFamily: 'Arial' }, obj === null || obj === void 0 ? void 0 : obj.style);
199
+ this.textureStyle = Object.assign({ scaleMode: 'linear', resolution: window.devicePixelRatio || 1 }, obj === null || obj === void 0 ? void 0 : obj.textureStyle);
200
+ if (obj) {
201
+ this.text = obj.text;
202
+ }
203
+ }
204
+ }
205
+ /** 组件名称 */
206
+ HTMLText.componentName = 'HTMLText';
207
+ __decorate([
208
+ type('string')
209
+ ], HTMLText.prototype, "text", void 0);
210
+
67
211
  let Text = class Text extends Renderer {
68
212
  constructor() {
69
213
  super(...arguments);
@@ -76,16 +220,17 @@ let Text = class Text extends Renderer {
76
220
  }
77
221
  componentChanged(changed) {
78
222
  return __awaiter(this, void 0, void 0, function* () {
79
- if (changed.componentName !== 'Text')
223
+ const isText = changed.componentName === 'Text';
224
+ const isHTMLText = changed.componentName === 'HTMLText';
225
+ if (!isText && !isHTMLText)
80
226
  return;
81
227
  if (changed.type === OBSERVER_TYPE.ADD) {
82
- const component = changed.component;
83
- const text = new Text$3(component.text, component.style);
84
- this.containerManager.getContainer(changed.gameObject.id).addChildAt(text, 0);
85
- this.texts[changed.gameObject.id] = {
86
- text,
87
- component: changed.component,
88
- };
228
+ if (isText) {
229
+ yield this.addTextComponent(changed);
230
+ }
231
+ else {
232
+ yield this.addHTMLTextComponent(changed);
233
+ }
89
234
  this.setSize(changed);
90
235
  }
91
236
  else if (changed.type === OBSERVER_TYPE.REMOVE) {
@@ -95,33 +240,125 @@ let Text = class Text extends Renderer {
95
240
  }
96
241
  else {
97
242
  this.change(changed);
243
+ // 如果样式改变且涉及字体,也需要等待字体资源加载
244
+ const component = changed.component;
245
+ if (changed.prop.prop[0] === 'style' && component.style && component.style.fontFamily) {
246
+ const { text } = this.texts[changed.gameObject.id];
247
+ yield this.waitForFontResource(text, changed, component.style.fontFamily);
248
+ }
98
249
  this.setSize(changed);
99
250
  }
100
251
  });
101
252
  }
253
+ addTextComponent(changed) {
254
+ return __awaiter(this, void 0, void 0, function* () {
255
+ const component = changed.component;
256
+ // 创建文本样式副本,先不设置 fontFamily
257
+ const styleWithoutFont = Object.assign({}, component.style);
258
+ const fontFamily = styleWithoutFont.fontFamily;
259
+ delete styleWithoutFont.fontFamily;
260
+ const initialText = fontFamily ? '' : component.text;
261
+ const text = new Text$3(initialText, styleWithoutFont);
262
+ this.containerManager.getContainer(changed.gameObject.id).addChildAt(text, 0);
263
+ this.texts[changed.gameObject.id] = {
264
+ text,
265
+ component,
266
+ };
267
+ // 如果指定了字体资源,等待资源加载完成后设置 fontFamily
268
+ if (fontFamily) {
269
+ yield this.waitForFontResource(text, changed, fontFamily);
270
+ }
271
+ });
272
+ }
273
+ addHTMLTextComponent(changed) {
274
+ return __awaiter(this, void 0, void 0, function* () {
275
+ const component = changed.component;
276
+ // 创建样式副本,先不设置 fontFamily
277
+ const styleWithoutFont = Object.assign({}, component.style);
278
+ const fontFamily = styleWithoutFont.fontFamily;
279
+ delete styleWithoutFont.fontFamily;
280
+ const initialText = fontFamily ? '' : component.text;
281
+ const htmlText = new HTMLText$1(Object.assign({ text: initialText, style: styleWithoutFont }, (component.textureStyle && { textureStyle: component.textureStyle })));
282
+ this.containerManager.getContainer(changed.gameObject.id).addChildAt(htmlText, 0);
283
+ this.texts[changed.gameObject.id] = {
284
+ text: htmlText,
285
+ component,
286
+ };
287
+ // 如果指定了字体资源,等待资源加载完成后设置 fontFamily
288
+ if (fontFamily) {
289
+ yield this.waitForFontResource(htmlText, changed, fontFamily);
290
+ }
291
+ });
292
+ }
293
+ /**
294
+ * 等待字体资源加载完成并更新文本
295
+ */
296
+ waitForFontResource(text, changed, fontFamily) {
297
+ return __awaiter(this, void 0, void 0, function* () {
298
+ if (!fontFamily) {
299
+ return;
300
+ }
301
+ try {
302
+ const fontName = Array.isArray(fontFamily) ? fontFamily[0] : fontFamily;
303
+ // 通过 resource 系统获取字体资源
304
+ const asyncId = this.increaseAsyncId(changed.gameObject.id);
305
+ yield resource.getResource(fontName);
306
+ // 验证异步操作是否仍然有效(防止组件已被移除)
307
+ if (!this.validateAsyncId(changed.gameObject.id, asyncId))
308
+ return;
309
+ // 字体资源加载成功后,设置 fontFamily 并重新设置文本内容以触发重新渲染
310
+ const component = this.texts[changed.gameObject.id].component;
311
+ text.style.fontFamily = fontFamily;
312
+ text.text = component.text;
313
+ // 更新尺寸
314
+ }
315
+ catch (error) {
316
+ console.warn(`字体资源 ${fontFamily} 加载失败:`, error);
317
+ }
318
+ });
319
+ }
102
320
  change(changed) {
103
321
  const { text, component } = this.texts[changed.gameObject.id];
322
+ const isHTMLText = changed.componentName === 'HTMLText';
104
323
  if (changed.prop.prop[0] === 'text') {
105
324
  text.text = component.text;
106
325
  }
107
326
  else if (changed.prop.prop[0] === 'style') {
108
- Object.assign(text.style, changed.component.style);
327
+ Object.assign(text.style, component.style);
328
+ }
329
+ else if (changed.prop.prop[0] === 'textureStyle' && isHTMLText) {
330
+ // HTMLText 纹理样式变化需要重新创建
331
+ const htmlComponent = component;
332
+ const container = this.containerManager.getContainer(changed.gameObject.id);
333
+ const index = container.getChildIndex(text);
334
+ container.removeChild(text);
335
+ text.destroy({ children: true });
336
+ const newText = new HTMLText$1({
337
+ text: htmlComponent.text,
338
+ style: htmlComponent.style,
339
+ textureStyle: htmlComponent.textureStyle
340
+ });
341
+ container.addChildAt(newText, index);
342
+ this.texts[changed.gameObject.id].text = newText;
109
343
  }
110
344
  }
111
345
  setSize(changed) {
112
346
  const { transform } = changed.gameObject;
113
347
  if (!transform)
114
348
  return;
115
- transform.size.width = this.texts[changed.gameObject.id].text.width;
116
- transform.size.height = this.texts[changed.gameObject.id].text.height;
349
+ const { text } = this.texts[changed.gameObject.id];
350
+ const size = text.getSize();
351
+ transform.size.width = size.width;
352
+ transform.size.height = size.height;
117
353
  }
118
354
  };
119
355
  Text.systemName = 'Text';
120
356
  Text = __decorate([
121
357
  decorators.componentObserver({
122
358
  Text: ['text', { prop: ['style'], deep: true }],
359
+ HTMLText: ['text', { prop: ['style'], deep: true }, { prop: ['textureStyle'], deep: true }],
123
360
  })
124
361
  ], Text);
125
362
  var Text$1 = Text;
126
363
 
127
- export { Text$2 as Text, Text$1 as TextSystem };
364
+ export { HTMLText, Text$2 as Text, Text$1 as TextSystem };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eva/plugin-renderer-text",
3
- "version": "2.0.1-beta.3",
3
+ "version": "2.0.1-beta.31",
4
4
  "description": "@eva/plugin-renderer-text",
5
5
  "main": "index.js",
6
6
  "module": "dist/plugin-renderer-text.esm.js",
@@ -19,9 +19,9 @@
19
19
  "homepage": "https://eva.js.org",
20
20
  "dependencies": {
21
21
  "@eva/inspector-decorator": "^0.0.5",
22
- "@eva/plugin-renderer": "2.0.1-beta.3",
23
- "@eva/renderer-adapter": "2.0.1-beta.3",
24
- "@eva/eva.js": "2.0.1-beta.3",
22
+ "@eva/plugin-renderer": "2.0.1-beta.31",
23
+ "@eva/renderer-adapter": "2.0.1-beta.31",
24
+ "@eva/eva.js": "2.0.1-beta.31",
25
25
  "pixi.js": "^8.8.1"
26
26
  }
27
27
  }