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