@eva/plugin-renderer-text 2.1.0-beta.14 → 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
1
  import { Component, type, Field, resource, RESOURCE_TYPE, decorators, OBSERVER_TYPE } from '@eva/eva.js';
2
- import { TextStyle, FillGradient, Color } from 'pixi.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,166 +616,328 @@ 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
681
  const propPath = changed.prop.prop;
642
682
  const fontFamilyChanged = propPath[0] === 'style' && (propPath.length === 1 || propPath[1] === 'fontFamily');
643
- if (fontFamilyChanged && component.style && this.hasRegisteredFont(component.style.fontFamily)) {
644
- const { text } = this.texts[changed.gameObject.id];
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;
645
709
  yield this.waitForFontResource(text, changed, component.style.fontFamily);
646
710
  }
647
- 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
+ }
648
714
  }
649
715
  });
650
716
  }
651
717
  addTextComponent(changed) {
652
- return __awaiter(this, void 0, void 0, function* () {
653
- const component = changed.component;
654
- // Registered Eva font assets must load before the first glyph. Ordinary
655
- // CSS/system families are already valid Pixi styles and render directly.
656
- const styleWithoutFont = this.processStyle(component.style);
657
- const fontFamily = styleWithoutFont.fontFamily;
658
- const waitsForFontResource = this.hasRegisteredFont(fontFamily);
659
- if (waitsForFontResource)
660
- delete styleWithoutFont.fontFamily;
661
- const initialText = waitsForFontResource ? '' : component.text;
662
- const text = new Text$3(initialText, styleWithoutFont);
663
- this.containerManager.getContainer(changed.gameObject.id).addChildAt(text, 0);
664
- this.texts[changed.gameObject.id] = {
665
- text,
666
- component,
667
- };
668
- // 如果指定了字体资源,等待资源加载完成后设置 fontFamily
669
- if (waitsForFontResource) {
670
- yield this.waitForFontResource(text, changed, fontFamily);
671
- }
672
- });
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)
725
+ delete styleWithoutFont.fontFamily;
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
+ }
673
738
  }
674
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;
675
756
  return __awaiter(this, void 0, void 0, function* () {
676
- const component = changed.component;
677
- // 创建样式副本,先不设置 fontFamily
678
- const styleWithoutFont = this.processStyle(component.style);
679
- const fontFamily = styleWithoutFont.fontFamily;
680
- const waitsForFontResource = this.hasRegisteredFont(fontFamily);
681
- if (waitsForFontResource)
682
- delete styleWithoutFont.fontFamily;
683
- const initialText = waitsForFontResource ? '' : component.text;
684
- const htmlText = new HTMLText$1(Object.assign({ text: initialText, style: styleWithoutFont }, (component.textureStyle && { textureStyle: component.textureStyle })));
685
- this.containerManager.getContainer(changed.gameObject.id).addChildAt(htmlText, 0);
686
- this.texts[changed.gameObject.id] = {
687
- text: htmlText,
688
- component,
689
- };
690
- // 如果指定了字体资源,等待资源加载完成后设置 fontFamily
691
- if (waitsForFontResource) {
692
- yield this.waitForFontResource(htmlText, changed, fontFamily);
693
- }
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);
694
766
  });
695
767
  }
696
- addBitmapTextComponent(changed) {
697
- return __awaiter(this, void 0, void 0, function* () {
698
- const component = changed.component;
699
- // 创建样式副本,先不设置 fontFamily
700
- const styleWithoutFont = this.processStyle(component.style);
701
- const fontFamily = styleWithoutFont.fontFamily;
702
- const waitsForFontResource = this.hasRegisteredFont(fontFamily);
703
- if (waitsForFontResource)
704
- delete styleWithoutFont.fontFamily;
705
- const initialText = waitsForFontResource ? '' : component.text;
706
- const bitmapText = new BitmapText$1(initialText, styleWithoutFont);
707
- this.containerManager.getContainer(changed.gameObject.id).addChildAt(bitmapText, 0);
708
- this.texts[changed.gameObject.id] = {
709
- text: bitmapText,
710
- component,
711
- };
712
- // 如果指定了字体资源,等待资源加载完成后设置 fontFamily
713
- if (waitsForFontResource) {
714
- yield this.waitForFontResource(bitmapText, changed, fontFamily);
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;
715
798
  }
716
- });
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
+ }
817
+ addBitmapTextComponent(changed) {
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)
824
+ delete styleWithoutFont.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
+ }
717
837
  }
718
838
  hasRegisteredFont(fontFamily) {
719
839
  var _a;
720
840
  if (!fontFamily)
721
841
  return false;
722
- const fontName = Array.isArray(fontFamily) ? fontFamily[0] : fontFamily;
842
+ const fontName = this.getFontName(fontFamily);
723
843
  return ((_a = resource.resourcesMap[fontName]) === null || _a === void 0 ? void 0 : _a.type) === RESOURCE_TYPE.FONT;
724
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;
874
+ }
875
+ });
876
+ }
725
877
  /**
726
878
  * 等待字体资源加载完成并更新文本
727
879
  */
728
880
  waitForFontResource(text, changed, fontFamily) {
881
+ var _a;
729
882
  return __awaiter(this, void 0, void 0, function* () {
730
883
  if (!fontFamily) {
731
884
  return;
732
885
  }
733
- try {
734
- const fontName = Array.isArray(fontFamily) ? fontFamily[0] : fontFamily;
735
- if (!this.hasRegisteredFont(fontFamily))
736
- return;
737
- // 通过 resource 系统获取字体资源
738
- const asyncId = this.increaseAsyncId(changed.gameObject.id);
739
- yield resource.getResource(fontName);
740
- // 验证异步操作是否仍然有效(防止组件已被移除)
741
- if (!this.validateAsyncId(changed.gameObject.id, asyncId))
742
- return;
743
- // 字体资源加载成功后,设置 fontFamily 并重新设置文本内容以触发重新渲染
744
- const component = this.texts[changed.gameObject.id].component;
745
- text.style.fontFamily = fontFamily;
746
- text.text = component.text;
747
- // 更新尺寸
748
- }
749
- catch (error) {
750
- 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;
751
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;
752
907
  });
753
908
  }
754
909
  /**
755
910
  * 将 Eva.js 的样式格式转换为 PixiJS v8 格式
756
911
  */
757
912
  processStyle(style) {
758
- const processed = Object.assign({}, style);
759
- // stroke + strokeThickness -> stroke: { color, width }
760
- if (processed.strokeThickness) {
761
- const color = processed.stroke;
762
- processed.stroke = {
763
- color,
764
- width: processed.strokeThickness,
765
- };
766
- 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;
767
941
  }
768
942
  // dropShadow*(v7 平铺 alias) -> dropShadow: { color, distance, angle, alpha, blur }
769
943
  // 关键:只有当存在任何 v7 平铺 alias 时才合并;否则保留 v8 嵌套对象(以及
@@ -799,60 +973,117 @@ let Text = class Text extends Renderer {
799
973
  delete processed.dropShadowAngle;
800
974
  delete processed.dropShadowAlpha;
801
975
  delete processed.dropShadowBlur;
802
- // 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.
803
978
  if (Array.isArray(processed.fill)) {
804
- processed.fill = processed.fill[0];
805
- }
806
- // fillGradientStops -> FillGradient 对象
807
- if (Array.isArray(processed.fillGradientStops)) {
808
- let fontSize;
809
- if (processed.fontSize == null) {
810
- fontSize = TextStyle.defaultTextStyle.fontSize;
811
- }
812
- else if (typeof processed.fontSize === 'string') {
813
- 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 };
814
994
  }
815
995
  else {
816
- fontSize = processed.fontSize;
996
+ processed.fill = colors[0];
817
997
  }
818
- const gradientFill = new FillGradient(0, 0, 0, fontSize * 1.7);
819
- const fills = processed.fillGradientStops.map((color) => Color.shared.setValue(color).toNumber());
820
- fills.forEach((number, index) => {
821
- const ratio = index / (fills.length - 1);
822
- gradientFill.addColorStop(ratio, number);
823
- });
824
- processed.fill = { fill: gradientFill };
825
- delete processed.fillGradientStops;
826
998
  }
999
+ delete processed.fillGradientStops;
1000
+ delete processed.fillGradientType;
827
1001
  return processed;
828
1002
  }
829
1003
  change(changed) {
830
- 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;
831
1007
  const isHTMLText = changed.componentName === 'HTMLText';
832
1008
  if (changed.prop.prop[0] === 'text') {
833
- 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;
834
1013
  }
835
1014
  else if (changed.prop.prop[0] === 'style') {
836
1015
  const processedStyle = this.processStyle(component.style);
837
- if (this.hasRegisteredFont(processedStyle.fontFamily))
1016
+ if (this.needsFontLoad(processedStyle.fontFamily))
838
1017
  delete processedStyle.fontFamily;
839
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
+ }
840
1023
  }
841
1024
  else if (changed.prop.prop[0] === 'textureStyle' && isHTMLText) {
842
- // 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.
843
1028
  const htmlComponent = component;
844
- const container = this.containerManager.getContainer(changed.gameObject.id);
845
- const index = container.getChildIndex(text);
846
- container.removeChild(text);
847
- text.destroy({ children: true });
848
- const newText = new HTMLText$1({
849
- text: htmlComponent.text,
850
- style: htmlComponent.style,
851
- textureStyle: htmlComponent.textureStyle,
852
- });
853
- container.addChildAt(newText, index);
854
- 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);
1051
+ }
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)]));
855
1084
  }
1085
+ // Preserve class-based Pixi fills/gradients/textures; clone plain component data.
1086
+ return value;
856
1087
  }
857
1088
  setSize(changed) {
858
1089
  const { transform } = changed.gameObject;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eva/plugin-renderer-text",
3
- "version": "2.1.0-beta.14",
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.14",
23
- "@eva/renderer-adapter": "2.1.0-beta.14",
24
- "@eva/eva.js": "2.1.0-beta.14",
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
  }