@eva/plugin-renderer-text 2.1.0-beta.13 → 2.1.0-beta.15

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.
@@ -22,6 +22,18 @@ OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
22
22
  PERFORMANCE OF THIS SOFTWARE.
23
23
  ***************************************************************************** */
24
24
 
25
+ function __rest(s, e) {
26
+ var t = {};
27
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
28
+ t[p] = s[p];
29
+ if (s != null && typeof Object.getOwnPropertySymbols === "function")
30
+ for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
31
+ if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
32
+ t[p[i]] = s[p[i]];
33
+ }
34
+ return t;
35
+ }
36
+
25
37
  function __decorate(decorators, target, key, desc) {
26
38
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
27
39
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
@@ -608,105 +620,261 @@ let Text = class Text extends pluginRenderer.Renderer {
608
620
  constructor() {
609
621
  super(...arguments);
610
622
  this.name = 'Text';
623
+ this.presentationAddFlushEnabled = true;
611
624
  this.texts = {};
625
+ this.retired = false;
626
+ this.pendingHTMLTextComponents = {};
627
+ this.fontIdentityIds = new WeakMap();
628
+ this.nextFontIdentityId = 1;
612
629
  }
613
630
  init() {
614
631
  this.renderSystem = this.game.getSystem(pluginRenderer.RendererSystem);
615
632
  this.renderSystem.rendererManager.register(this);
616
633
  }
634
+ shouldFlushPresentationAddition(changed) {
635
+ return changed.componentName === 'Text' || changed.componentName === 'HTMLText' || changed.componentName === 'BitmapText';
636
+ }
617
637
  componentChanged(changed) {
638
+ var _a, _b, _c, _d, _e;
618
639
  return __awaiter(this, void 0, void 0, function* () {
640
+ if (this.retired)
641
+ return;
619
642
  const isText = changed.componentName === 'Text';
620
643
  const isHTMLText = changed.componentName === 'HTMLText';
621
644
  const isBitmapText = changed.componentName === 'BitmapText';
622
645
  if (!isText && !isHTMLText && !isBitmapText)
623
646
  return;
647
+ const gameObjectId = changed.gameObject.id;
624
648
  if (changed.type === eva_js.OBSERVER_TYPE.ADD) {
625
649
  if (isText) {
626
- yield this.addTextComponent(changed);
650
+ const loading = this.addTextComponent(changed);
651
+ if (loading)
652
+ yield loading;
627
653
  }
628
654
  else if (isHTMLText) {
629
- yield this.addHTMLTextComponent(changed);
655
+ const loading = this.addHTMLTextComponent(changed);
656
+ if (loading)
657
+ yield loading;
630
658
  }
631
659
  else {
632
- yield this.addBitmapTextComponent(changed);
660
+ const loading = this.addBitmapTextComponent(changed);
661
+ if (loading)
662
+ yield loading;
663
+ }
664
+ if (((_a = this.texts[gameObjectId]) === null || _a === void 0 ? void 0 : _a.component) === changed.component) {
665
+ this.setSize(changed);
633
666
  }
634
- this.setSize(changed);
635
667
  }
636
668
  else if (changed.type === eva_js.OBSERVER_TYPE.REMOVE) {
637
- this.containerManager.getContainer(changed.gameObject.id).removeChild(this.texts[changed.gameObject.id].text);
638
- this.texts[changed.gameObject.id].text.destroy({ children: true });
639
- delete this.texts[changed.gameObject.id];
669
+ if (((_b = this.texts[gameObjectId]) === null || _b === void 0 ? void 0 : _b.component) !== changed.component && this.pendingHTMLTextComponents[gameObjectId] !== changed.component)
670
+ return;
671
+ this.increaseAsyncId(gameObjectId);
672
+ if (this.pendingHTMLTextComponents[gameObjectId] === changed.component) {
673
+ delete this.pendingHTMLTextComponents[gameObjectId];
674
+ }
675
+ const current = this.texts[gameObjectId];
676
+ if (!current || current.component !== changed.component)
677
+ return;
678
+ this.containerManager.getContainer(gameObjectId).removeChild(current.text);
679
+ current.text.destroy({ children: true });
680
+ delete this.texts[gameObjectId];
640
681
  }
641
682
  else {
642
- this.change(changed);
643
- // 如果样式改变且涉及字体,也需要等待字体资源加载
683
+ // Only a font-family replacement can introduce a new async font asset.
644
684
  const component = changed.component;
645
- if (changed.prop.prop[0] === 'style' && component.style && component.style.fontFamily) {
646
- const { text } = this.texts[changed.gameObject.id];
685
+ const propPath = changed.prop.prop;
686
+ const fontFamilyChanged = propPath[0] === 'style' && (propPath.length === 1 || propPath[1] === 'fontFamily');
687
+ const current = this.texts[gameObjectId];
688
+ // A registered-font HTMLText ADD is intentionally not mounted until the
689
+ // font is ready. While it is pending, ordinary updates are already held
690
+ // by the component; a font-family change restarts (or completes) ADD.
691
+ if (!current) {
692
+ if (isHTMLText &&
693
+ this.pendingHTMLTextComponents[gameObjectId] === component &&
694
+ fontFamilyChanged) {
695
+ yield this.addHTMLTextComponent(changed);
696
+ if (((_c = this.texts[gameObjectId]) === null || _c === void 0 ? void 0 : _c.component) === component) {
697
+ this.setSize(changed);
698
+ }
699
+ }
700
+ return;
701
+ }
702
+ if (current.component !== component)
703
+ return;
704
+ const waitsForFontResource = fontFamilyChanged && component.style && this.needsFontLoad(component.style.fontFamily);
705
+ if (fontFamilyChanged && !waitsForFontResource) {
706
+ // A system/CSS font switch is synchronous, but it must still cancel an
707
+ // older registered-font request for the same GameObject.
708
+ this.increaseAsyncId(gameObjectId);
709
+ }
710
+ this.change(changed);
711
+ if (waitsForFontResource) {
712
+ const { text } = current;
647
713
  yield this.waitForFontResource(text, changed, component.style.fontFamily);
648
714
  }
649
- this.setSize(changed);
715
+ if (((_d = this.texts[gameObjectId]) === null || _d === void 0 ? void 0 : _d.text) === current.text && ((_e = this.texts[gameObjectId]) === null || _e === void 0 ? void 0 : _e.component) === component) {
716
+ this.setSize(changed);
717
+ }
650
718
  }
651
719
  });
652
720
  }
653
721
  addTextComponent(changed) {
654
- return __awaiter(this, void 0, void 0, function* () {
655
- const component = changed.component;
656
- // 创建文本样式副本,先不设置 fontFamily
657
- const styleWithoutFont = this.processStyle(component.style);
658
- const fontFamily = styleWithoutFont.fontFamily;
722
+ const component = changed.component;
723
+ // Registered Eva font assets must load before the first glyph. Ordinary
724
+ // CSS/system families are already valid Pixi styles and render directly.
725
+ const styleWithoutFont = this.processStyle(component.style);
726
+ const fontFamily = styleWithoutFont.fontFamily;
727
+ const waitsForFontResource = this.needsFontLoad(fontFamily);
728
+ if (waitsForFontResource)
659
729
  delete styleWithoutFont.fontFamily;
660
- const initialText = fontFamily ? '' : component.text;
661
- const text = new rendererAdapter.Text(initialText, styleWithoutFont);
662
- this.containerManager.getContainer(changed.gameObject.id).addChildAt(text, 0);
663
- this.texts[changed.gameObject.id] = {
664
- text,
665
- component,
666
- };
667
- // 如果指定了字体资源,等待资源加载完成后设置 fontFamily
668
- if (fontFamily) {
669
- yield this.waitForFontResource(text, changed, fontFamily);
670
- }
671
- });
730
+ const initialText = waitsForFontResource ? '' : component.text;
731
+ const text = new rendererAdapter.Text(initialText, styleWithoutFont);
732
+ this.containerManager.getContainer(changed.gameObject.id).addChildAt(text, 0);
733
+ this.texts[changed.gameObject.id] = {
734
+ text,
735
+ component,
736
+ awaitingInitialFont: waitsForFontResource,
737
+ };
738
+ // 如果指定了字体资源,等待资源加载完成后设置 fontFamily
739
+ if (waitsForFontResource) {
740
+ return this.waitForFontResource(text, changed, fontFamily);
741
+ }
672
742
  }
673
743
  addHTMLTextComponent(changed) {
744
+ const component = changed.component;
745
+ const gameObjectId = changed.gameObject.id;
746
+ const initialStyle = this.processStyle(component.style);
747
+ const fontFamily = initialStyle.fontFamily;
748
+ const waitsForFontResource = this.needsFontLoad(fontFamily);
749
+ if (waitsForFontResource) {
750
+ this.pendingHTMLTextComponents[gameObjectId] = component;
751
+ return this.addHTMLTextAfterFont(changed, component, fontFamily, this.increaseAsyncId(gameObjectId));
752
+ }
753
+ else if (this.pendingHTMLTextComponents[gameObjectId] === component) {
754
+ delete this.pendingHTMLTextComponents[gameObjectId];
755
+ }
756
+ this.mountHTMLText(changed, component);
757
+ }
758
+ addHTMLTextAfterFont(changed, component, fontFamily, asyncId) {
759
+ var _a;
674
760
  return __awaiter(this, void 0, void 0, function* () {
675
- const component = changed.component;
676
- // 创建样式副本,先不设置 fontFamily
677
- const styleWithoutFont = this.processStyle(component.style);
678
- const fontFamily = styleWithoutFont.fontFamily;
679
- delete styleWithoutFont.fontFamily;
680
- const initialText = fontFamily ? '' : component.text;
681
- const htmlText = new rendererAdapter.HTMLText(Object.assign({ text: initialText, style: styleWithoutFont }, (component.textureStyle && { textureStyle: component.textureStyle })));
682
- this.containerManager.getContainer(changed.gameObject.id).addChildAt(htmlText, 0);
683
- this.texts[changed.gameObject.id] = {
684
- text: htmlText,
685
- component,
686
- };
687
- // 如果指定了字体资源,等待资源加载完成后设置 fontFamily
688
- if (fontFamily) {
689
- yield this.waitForFontResource(htmlText, changed, fontFamily);
690
- }
761
+ const gameObjectId = changed.gameObject.id;
762
+ const fontName = this.getFontName(fontFamily);
763
+ const loaded = yield this.loadFontResource(fontFamily);
764
+ if (!loaded || this.retired || changed.gameObject.destroyed ||
765
+ !this.validateAsyncId(gameObjectId, asyncId) ||
766
+ this.pendingHTMLTextComponents[gameObjectId] !== component ||
767
+ this.getFontName((_a = component.style) === null || _a === void 0 ? void 0 : _a.fontFamily) !== fontName)
768
+ return;
769
+ this.mountHTMLText(changed, component);
691
770
  });
692
771
  }
772
+ mountHTMLText(changed, component) {
773
+ const gameObjectId = changed.gameObject.id;
774
+ const style = this.processStyle(component.style);
775
+ const _a = component.textureStyle || {}, sampling = __rest(_a, ["resolution"]);
776
+ const htmlText = new rendererAdapter.HTMLText({ text: component.text, style, textureStyle: Object.assign({}, sampling) });
777
+ htmlText.rasterCacheFontIdentity = (families) => this.getHTMLRasterFontIdentity(families);
778
+ this.applyHTMLResolution(htmlText, component, changed);
779
+ this.containerManager.getContainer(gameObjectId).addChildAt(htmlText, 0);
780
+ this.texts[gameObjectId] = {
781
+ text: htmlText,
782
+ component,
783
+ };
784
+ delete this.pendingHTMLTextComponents[gameObjectId];
785
+ }
786
+ getHTMLRasterFontIdentity(families) {
787
+ var _a;
788
+ const uniqueFamilies = [...new Set(families.filter((family) => typeof family === 'string')
789
+ .map(family => family.replace(/["']/g, '').trim()).filter(Boolean))];
790
+ if (!uniqueFamilies.length)
791
+ return 'external:<default>';
792
+ const describe = (face) => [
793
+ this.objectIdentity(face), face.family, face.style, face.weight, face.stretch,
794
+ face.unicodeRange, face.variant, face.featureSettings, face.display,
795
+ ].map(value => String(value !== null && value !== void 0 ? value : '')).join(':');
796
+ const identities = [];
797
+ for (const family of uniqueFamilies) {
798
+ const current = eva_js.resource.resourcesMap[family];
799
+ if (!current || current.type !== eva_js.RESOURCE_TYPE.FONT) {
800
+ identities.push(`external:${family}`);
801
+ continue;
802
+ }
803
+ if (current.complete !== true)
804
+ return null;
805
+ const rawFaces = (_a = current.data) === null || _a === void 0 ? void 0 : _a.font;
806
+ const faces = Array.isArray(rawFaces) ? rawFaces : [rawFaces];
807
+ if (!faces.length || faces.some(face => !face || typeof face !== 'object' || face.status !== 'loaded'))
808
+ return null;
809
+ identities.push(`${family}|resource:${this.objectIdentity(current)}|faces:${faces.map(describe).join(',')}`);
810
+ }
811
+ return identities.join(';');
812
+ }
813
+ objectIdentity(value) {
814
+ let id = this.fontIdentityIds.get(value);
815
+ if (!id) {
816
+ id = this.nextFontIdentityId++;
817
+ this.fontIdentityIds.set(value, id);
818
+ }
819
+ return id;
820
+ }
693
821
  addBitmapTextComponent(changed) {
694
- return __awaiter(this, void 0, void 0, function* () {
695
- const component = changed.component;
696
- // 创建样式副本,先不设置 fontFamily
697
- const styleWithoutFont = this.processStyle(component.style);
698
- const fontFamily = styleWithoutFont.fontFamily;
822
+ const component = changed.component;
823
+ // 创建样式副本,先不设置 fontFamily
824
+ const styleWithoutFont = this.processStyle(component.style);
825
+ const fontFamily = styleWithoutFont.fontFamily;
826
+ const waitsForFontResource = this.needsFontLoad(fontFamily);
827
+ if (waitsForFontResource)
699
828
  delete styleWithoutFont.fontFamily;
700
- const initialText = fontFamily ? '' : component.text;
701
- const bitmapText = new rendererAdapter.BitmapText(initialText, styleWithoutFont);
702
- this.containerManager.getContainer(changed.gameObject.id).addChildAt(bitmapText, 0);
703
- this.texts[changed.gameObject.id] = {
704
- text: bitmapText,
705
- component,
706
- };
707
- // 如果指定了字体资源,等待资源加载完成后设置 fontFamily
708
- if (fontFamily) {
709
- yield this.waitForFontResource(bitmapText, changed, fontFamily);
829
+ const initialText = waitsForFontResource ? '' : component.text;
830
+ const bitmapText = new rendererAdapter.BitmapText(initialText, styleWithoutFont);
831
+ this.containerManager.getContainer(changed.gameObject.id).addChildAt(bitmapText, 0);
832
+ this.texts[changed.gameObject.id] = {
833
+ text: bitmapText,
834
+ component,
835
+ awaitingInitialFont: waitsForFontResource,
836
+ };
837
+ // 如果指定了字体资源,等待资源加载完成后设置 fontFamily
838
+ if (waitsForFontResource) {
839
+ return this.waitForFontResource(bitmapText, changed, fontFamily);
840
+ }
841
+ }
842
+ hasRegisteredFont(fontFamily) {
843
+ var _a;
844
+ if (!fontFamily)
845
+ return false;
846
+ const fontName = this.getFontName(fontFamily);
847
+ return ((_a = eva_js.resource.resourcesMap[fontName]) === null || _a === void 0 ? void 0 : _a.type) === eva_js.RESOURCE_TYPE.FONT;
848
+ }
849
+ needsFontLoad(fontFamily) {
850
+ if (!this.hasRegisteredFont(fontFamily))
851
+ return false;
852
+ // Consult the current resource generation, not a name-based font cache.
853
+ // A completed FONT already has its face installed; yielding here would
854
+ // present an empty placeholder even when the application preloaded it.
855
+ return eva_js.resource.resourcesMap[this.getFontName(fontFamily)].complete !== true;
856
+ }
857
+ getFontName(fontFamily) {
858
+ if (!fontFamily)
859
+ return undefined;
860
+ return Array.isArray(fontFamily) ? fontFamily[0] : fontFamily;
861
+ }
862
+ loadFontResource(fontFamily) {
863
+ return __awaiter(this, void 0, void 0, function* () {
864
+ const fontName = this.getFontName(fontFamily);
865
+ if (!fontName || !this.hasRegisteredFont(fontFamily))
866
+ return false;
867
+ const expectedResource = eva_js.resource.resourcesMap[fontName];
868
+ try {
869
+ const loaded = yield eva_js.resource.getResource(fontName);
870
+ // Resource resolves {} on failure or cancellation. Only the completed
871
+ // resource generation we requested can safely replace visible glyphs.
872
+ return loaded === expectedResource && loaded.complete === true &&
873
+ eva_js.resource.resourcesMap[fontName] === expectedResource;
874
+ }
875
+ catch (error) {
876
+ console.warn(`字体资源 ${fontFamily} 加载失败:`, error);
877
+ return false;
710
878
  }
711
879
  });
712
880
  }
@@ -714,42 +882,66 @@ let Text = class Text extends pluginRenderer.Renderer {
714
882
  * 等待字体资源加载完成并更新文本
715
883
  */
716
884
  waitForFontResource(text, changed, fontFamily) {
885
+ var _a;
717
886
  return __awaiter(this, void 0, void 0, function* () {
718
887
  if (!fontFamily) {
719
888
  return;
720
889
  }
721
- try {
722
- const fontName = Array.isArray(fontFamily) ? fontFamily[0] : fontFamily;
723
- // 通过 resource 系统获取字体资源
724
- const asyncId = this.increaseAsyncId(changed.gameObject.id);
725
- yield eva_js.resource.getResource(fontName);
726
- // 验证异步操作是否仍然有效(防止组件已被移除)
727
- if (!this.validateAsyncId(changed.gameObject.id, asyncId))
728
- return;
729
- // 字体资源加载成功后,设置 fontFamily 并重新设置文本内容以触发重新渲染
730
- const component = this.texts[changed.gameObject.id].component;
731
- text.style.fontFamily = fontFamily;
732
- text.text = component.text;
733
- // 更新尺寸
734
- }
735
- catch (error) {
736
- console.warn(`字体资源 ${fontFamily} 加载失败:`, error);
890
+ const gameObjectId = changed.gameObject.id;
891
+ const fontName = this.getFontName(fontFamily);
892
+ if (!fontName || !this.hasRegisteredFont(fontFamily))
893
+ return;
894
+ const asyncId = this.increaseAsyncId(gameObjectId);
895
+ const loaded = yield this.loadFontResource(fontFamily);
896
+ const current = this.texts[gameObjectId];
897
+ if (!loaded || this.retired || changed.gameObject.destroyed ||
898
+ !this.validateAsyncId(gameObjectId, asyncId) ||
899
+ !current ||
900
+ current.text !== text ||
901
+ current.component !== changed.component) {
902
+ return;
737
903
  }
904
+ const currentFontFamily = (_a = current.component.style) === null || _a === void 0 ? void 0 : _a.fontFamily;
905
+ if (this.getFontName(currentFontFamily) !== fontName)
906
+ return;
907
+ // 字体资源加载成功后,设置 fontFamily 并重新设置文本内容以触发重新渲染
908
+ text.style.fontFamily = currentFontFamily;
909
+ current.awaitingInitialFont = false;
910
+ text.text = current.component.text;
738
911
  });
739
912
  }
740
913
  /**
741
914
  * 将 Eva.js 的样式格式转换为 PixiJS v8 格式
742
915
  */
743
916
  processStyle(style) {
744
- const processed = Object.assign({}, style);
745
- // stroke + strokeThickness -> stroke: { color, width }
746
- if (processed.strokeThickness) {
747
- const color = processed.stroke;
748
- processed.stroke = {
749
- color,
750
- width: processed.strokeThickness,
751
- };
752
- delete processed.strokeThickness;
917
+ const processed = this.cloneStyleValue(style);
918
+ // Preserve native v8 stroke options when merging v7 aliases. Zero is an
919
+ // intentional width update, not an absent value.
920
+ if (processed.strokeThickness != null || processed.lineJoin != null || processed.miterLimit != null) {
921
+ const stroke = processed.stroke;
922
+ const isOptions = stroke && typeof stroke === 'object' &&
923
+ ['color', 'width', 'fill', 'texture', 'join'].some(key => key in stroke);
924
+ const merged = isOptions ? Object.assign({}, stroke) : { color: stroke !== null && stroke !== void 0 ? stroke : 0x000000 };
925
+ if (processed.strokeThickness != null)
926
+ merged.width = processed.strokeThickness;
927
+ if (processed.lineJoin != null)
928
+ merged.join = processed.lineJoin;
929
+ if (processed.miterLimit != null)
930
+ merged.miterLimit = processed.miterLimit;
931
+ // Join-only defaults must not enable a previously disabled stroke.
932
+ if (stroke != null || processed.strokeThickness != null)
933
+ processed.stroke = merged;
934
+ }
935
+ delete processed.strokeThickness;
936
+ delete processed.lineJoin;
937
+ delete processed.miterLimit;
938
+ // Eva exposes an enabled switch; Pixi treats every shadow object as enabled.
939
+ // Normalize only the cloned style so toggling back preserves configuration.
940
+ if (processed.dropShadow && typeof processed.dropShadow === 'object' && !Array.isArray(processed.dropShadow)) {
941
+ if (processed.dropShadow.enabled === false)
942
+ processed.dropShadow = false;
943
+ else
944
+ delete processed.dropShadow.enabled;
753
945
  }
754
946
  // dropShadow*(v7 平铺 alias) -> dropShadow: { color, distance, angle, alpha, blur }
755
947
  // 关键:只有当存在任何 v7 平铺 alias 时才合并;否则保留 v8 嵌套对象(以及
@@ -785,67 +977,129 @@ let Text = class Text extends pluginRenderer.Renderer {
785
977
  delete processed.dropShadowAngle;
786
978
  delete processed.dropShadowAlpha;
787
979
  delete processed.dropShadowBlur;
788
- // fill 数组 -> 取第一个值 (deprecated)
980
+ // Legacy fill is the color array; fillGradientStops contains offsets.
981
+ // Local coordinates keep the direction valid for any font size or bounds.
789
982
  if (Array.isArray(processed.fill)) {
790
- processed.fill = processed.fill[0];
791
- }
792
- // fillGradientStops -> FillGradient 对象
793
- if (Array.isArray(processed.fillGradientStops)) {
794
- let fontSize;
795
- if (processed.fontSize == null) {
796
- fontSize = pixi_js.TextStyle.defaultTextStyle.fontSize;
797
- }
798
- else if (typeof processed.fontSize === 'string') {
799
- fontSize = parseInt(processed.fontSize, 10);
983
+ const colors = processed.fill;
984
+ if (colors.length > 1) {
985
+ const supplied = processed.fillGradientStops;
986
+ const stops = Array.isArray(supplied) && supplied.length === colors.length
987
+ ? supplied : colors.map((_, index) => index / (colors.length - 1));
988
+ const horizontal = processed.fillGradientType === 1;
989
+ const gradient = new pixi_js.FillGradient({
990
+ start: { x: 0, y: 0 },
991
+ end: horizontal ? { x: 1, y: 0 } : { x: 0, y: 1 },
992
+ textureSpace: 'local',
993
+ });
994
+ colors.forEach((color, index) => {
995
+ gradient.addColorStop(stops[index], pixi_js.Color.shared.setValue(color).toNumber());
996
+ });
997
+ processed.fill = { fill: gradient };
800
998
  }
801
999
  else {
802
- fontSize = processed.fontSize;
1000
+ processed.fill = colors[0];
803
1001
  }
804
- const gradientFill = new pixi_js.FillGradient(0, 0, 0, fontSize * 1.7);
805
- const fills = processed.fillGradientStops.map((color) => pixi_js.Color.shared.setValue(color).toNumber());
806
- fills.forEach((number, index) => {
807
- const ratio = index / (fills.length - 1);
808
- gradientFill.addColorStop(ratio, number);
809
- });
810
- processed.fill = { fill: gradientFill };
811
- delete processed.fillGradientStops;
812
1002
  }
1003
+ delete processed.fillGradientStops;
1004
+ delete processed.fillGradientType;
813
1005
  return processed;
814
1006
  }
815
1007
  change(changed) {
816
- const { text, component } = this.texts[changed.gameObject.id];
1008
+ var _a, _b, _c, _d, _e, _f, _g, _h;
1009
+ const current = this.texts[changed.gameObject.id];
1010
+ const { text, component } = current;
817
1011
  const isHTMLText = changed.componentName === 'HTMLText';
818
1012
  if (changed.prop.prop[0] === 'text') {
819
- text.text = component.text;
1013
+ // First glyphs must not leak through the temporary default font while
1014
+ // the registered font is loading. Already-visible text can still update.
1015
+ if (!current.awaitingInitialFont)
1016
+ text.text = component.text;
820
1017
  }
821
1018
  else if (changed.prop.prop[0] === 'style') {
822
1019
  const processedStyle = this.processStyle(component.style);
1020
+ if (this.needsFontLoad(processedStyle.fontFamily))
1021
+ delete processedStyle.fontFamily;
823
1022
  Object.assign(text.style, processedStyle);
1023
+ if (current.awaitingInitialFont && !this.needsFontLoad((_a = component.style) === null || _a === void 0 ? void 0 : _a.fontFamily)) {
1024
+ current.awaitingInitialFont = false;
1025
+ text.text = component.text;
1026
+ }
824
1027
  }
825
1028
  else if (changed.prop.prop[0] === 'textureStyle' && isHTMLText) {
826
- // HTMLText 纹理样式变化需要重新创建
1029
+ // Sampling updates do not require destroying a visible HTMLText. Passing
1030
+ // its observed component.style back to Pixi also allowed legacy style
1031
+ // conversion to re-enter Eva's observer with a nested color object.
827
1032
  const htmlComponent = component;
828
- const container = this.containerManager.getContainer(changed.gameObject.id);
829
- const index = container.getChildIndex(text);
830
- container.removeChild(text);
831
- text.destroy({ children: true });
832
- const newText = new rendererAdapter.HTMLText({
833
- text: htmlComponent.text,
834
- style: htmlComponent.style,
835
- textureStyle: htmlComponent.textureStyle,
836
- });
837
- container.addChildAt(newText, index);
838
- this.texts[changed.gameObject.id].text = newText;
1033
+ const htmlText = text;
1034
+ const _j = htmlComponent.textureStyle || {}, sampling = __rest(_j, ["resolution"]);
1035
+ if (htmlText.textureStyle) {
1036
+ const options = Object.assign(Object.assign({}, ((pixi_js.TextureStyle === null || pixi_js.TextureStyle === void 0 ? void 0 : pixi_js.TextureStyle.defaultOptions) || { addressMode: 'clamp-to-edge', scaleMode: 'linear' })), sampling);
1037
+ htmlText.textureStyle.addressMode = options.addressMode;
1038
+ htmlText.textureStyle.scaleMode = options.scaleMode;
1039
+ Object.assign(htmlText.textureStyle, {
1040
+ addressModeU: (_b = options.addressModeU) !== null && _b !== void 0 ? _b : options.addressMode,
1041
+ addressModeV: (_c = options.addressModeV) !== null && _c !== void 0 ? _c : options.addressMode,
1042
+ addressModeW: (_d = options.addressModeW) !== null && _d !== void 0 ? _d : options.addressMode,
1043
+ magFilter: (_e = options.magFilter) !== null && _e !== void 0 ? _e : options.scaleMode,
1044
+ minFilter: (_f = options.minFilter) !== null && _f !== void 0 ? _f : options.scaleMode,
1045
+ mipmapFilter: (_g = options.mipmapFilter) !== null && _g !== void 0 ? _g : options.scaleMode,
1046
+ lodMinClamp: options.lodMinClamp, lodMaxClamp: options.lodMaxClamp,
1047
+ compare: options.compare, maxAnisotropy: (_h = options.maxAnisotropy) !== null && _h !== void 0 ? _h : 1,
1048
+ });
1049
+ htmlText.textureStyle.update();
1050
+ }
1051
+ else {
1052
+ htmlText.textureStyle = new pixi_js.TextureStyle(sampling);
1053
+ }
1054
+ this.applyHTMLResolution(htmlText, htmlComponent, changed);
839
1055
  }
840
1056
  }
1057
+ applyHTMLResolution(text, component, changed) {
1058
+ var _a, _b, _c, _d;
1059
+ // Render's explicit resolution remains authoritative, including when HTML
1060
+ // mounts after an asynchronous font load and misses Render's ADD callback.
1061
+ const render = (_b = (_a = changed.gameObject).getComponent) === null || _b === void 0 ? void 0 : _b.call(_a, 'Render');
1062
+ const resolution = (_c = render === null || render === void 0 ? void 0 : render.resolution) !== null && _c !== void 0 ? _c : (_d = component.textureStyle) === null || _d === void 0 ? void 0 : _d.resolution;
1063
+ if (typeof resolution === 'number' && Number.isFinite(resolution) && resolution > 0 && text.resolution !== resolution) {
1064
+ text.resolution = resolution;
1065
+ }
1066
+ else if (resolution == null) {
1067
+ text.resolution = null;
1068
+ }
1069
+ }
1070
+ onDestroy() {
1071
+ var _a, _b;
1072
+ this.retired = true;
1073
+ for (const id of Object.keys(this.asyncIdMap))
1074
+ this.increaseAsyncId(Number(id));
1075
+ this.pendingHTMLTextComponents = {};
1076
+ for (const { text } of Object.values(this.texts)) {
1077
+ (_a = text.parent) === null || _a === void 0 ? void 0 : _a.removeChild(text);
1078
+ if (!text.destroyed)
1079
+ (_b = text.destroy) === null || _b === void 0 ? void 0 : _b.call(text, { children: true });
1080
+ }
1081
+ this.texts = {};
1082
+ }
1083
+ cloneStyleValue(value) {
1084
+ if (Array.isArray(value))
1085
+ return value.map(item => this.cloneStyleValue(item));
1086
+ if (value && Object.getPrototypeOf(value) === Object.prototype) {
1087
+ return Object.fromEntries(Object.entries(value).map(([key, child]) => [key, this.cloneStyleValue(child)]));
1088
+ }
1089
+ // Preserve class-based Pixi fills/gradients/textures; clone plain component data.
1090
+ return value;
1091
+ }
841
1092
  setSize(changed) {
842
1093
  const { transform } = changed.gameObject;
843
1094
  if (!transform)
844
1095
  return;
845
1096
  const { text } = this.texts[changed.gameObject.id];
846
1097
  const size = text.getSize();
1098
+ if (transform.size.width === size.width && transform.size.height === size.height)
1099
+ return;
847
1100
  transform.size.width = size.width;
848
1101
  transform.size.height = size.height;
1102
+ this.renderSystem.commitIntrinsicSize(changed.gameObject);
849
1103
  }
850
1104
  };
851
1105
  Text.systemName = 'Text';