@@ -9,6 +9,7 @@ import * as lng from '@lightningjs/renderer';
99import { EventEmitter } from '@lightningjs/renderer/utils' ;
1010import { Config } from '../config.js' ;
1111import type {
12+ DomRendererMainSettings ,
1213 ExtractProps ,
1314 IRendererMain ,
1415 IRendererNode ,
@@ -29,6 +30,7 @@ import {
2930 isRenderStateInBounds ,
3031 nodeHasTextureSource ,
3132 computeRenderStateForNode ,
33+ compactString ,
3234} from './domRendererUtils.js' ;
3335
3436// Feature detection for legacy brousers
@@ -297,7 +299,7 @@ function updateNodeStyles(node: DOMNode | DOMText) {
297299 if ( textProps . fontWeight !== 'normal' ) {
298300 style += `font-weight: ${ textProps . fontWeight } ;` ;
299301 }
300- if ( textProps . fontStretch !== 'normal' ) {
302+ if ( textProps . fontStretch && textProps . fontStretch !== 'normal' ) {
301303 style += `font-stretch: ${ textProps . fontStretch } ;` ;
302304 }
303305 if ( textProps . lineHeight != null ) {
@@ -774,7 +776,7 @@ function updateNodeStyles(node: DOMNode | DOMText) {
774776 }
775777 }
776778
777- node . div . setAttribute ( 'style' , style ) ;
779+ node . div . setAttribute ( 'style' , compactString ( style ) ) ;
778780
779781 if ( node instanceof DOMNode && node !== node . stage . root ) {
780782 const hasTextureSrc = nodeHasTextureSource ( node ) ;
@@ -791,6 +793,8 @@ function updateNodeStyles(node: DOMNode | DOMText) {
791793}
792794
793795const textNodesToMeasure = new Set < DOMText > ( ) ;
796+ const containTextNodes = new Set < DOMText > ( ) ;
797+ let fontLoadingListenerSetup = false ;
794798
795799type Size = { width : number ; height : number } ;
796800
@@ -827,12 +831,17 @@ function getElSize(node: DOMNode): Size {
827831*/
828832function updateDOMTextSize ( node : DOMText ) : void {
829833 let size : Size ;
834+ let dimensionsChanged = false ;
830835 switch ( node . contain ) {
831836 case 'width' :
832837 size = getElSize ( node ) ;
838+ if ( node . props . width !== size . width ) {
839+ node . width = size . width ;
840+ dimensionsChanged = true ;
841+ }
833842 if ( node . props . height !== size . height ) {
834- node . props . height = size . height ;
835- updateNodeStyles ( node ) ;
843+ node . height = size . height ;
844+ dimensionsChanged = true ;
836845 }
837846 break ;
838847 case 'none' :
@@ -841,19 +850,19 @@ function updateDOMTextSize(node: DOMText): void {
841850 node . props . height !== size . height ||
842851 node . props . width !== size . width
843852 ) {
844- node . props . width = size . width ;
845- node . props . height = size . height ;
846- updateNodeStyles ( node ) ;
853+ node . width = size . width ;
854+ node . height = size . height ;
855+ dimensionsChanged = true ;
847856 }
848857 break ;
849858 }
850859
851- if ( ! node . loaded ) {
860+ if ( ! node . loaded || dimensionsChanged ) {
852861 const payload : lng . NodeTextLoadedPayload = {
853862 type : 'text' ,
854863 dimensions : {
855- width : node . props . width ,
856- height : node . props . height ,
864+ width : node . width ,
865+ height : node . height ,
857866 } ,
858867 } ;
859868 node . emit ( 'loaded' , payload ) ;
@@ -866,21 +875,69 @@ function updateDOMTextMeasurements() {
866875 textNodesToMeasure . clear ( ) ;
867876}
868877
878+ function shouldTrackContainTextNode ( node : DOMText ) : boolean {
879+ return node . contain === 'width' || node . contain === 'none' ;
880+ }
881+
882+ function syncContainTextNodeTracking ( node : DOMText ) : void {
883+ if ( shouldTrackContainTextNode ( node ) ) {
884+ containTextNodes . add ( node ) ;
885+ } else {
886+ containTextNodes . delete ( node ) ;
887+ }
888+ }
889+
890+ function scheduleContainTextNodesMeasurement ( ) : void {
891+ if ( containTextNodes . size === 0 ) return ;
892+
893+ containTextNodes . forEach ( ( node ) => {
894+ if ( node . div . isConnected ) {
895+ textNodesToMeasure . add ( node ) ;
896+ }
897+ } ) ;
898+
899+ if ( textNodesToMeasure . size > 0 ) {
900+ setTimeout ( updateDOMTextMeasurements ) ;
901+ }
902+ }
903+
904+ function setupFontLoadingListeners ( ) : void {
905+ if ( fontLoadingListenerSetup ) return ;
906+ if (
907+ typeof document === 'undefined' ||
908+ ! ( document . fonts as FontFaceSet | undefined )
909+ ) {
910+ return ;
911+ }
912+
913+ const fonts = document . fonts ;
914+ if ( typeof fonts . addEventListener === 'function' ) {
915+ fonts . addEventListener ( 'loadingdone' , scheduleContainTextNodesMeasurement ) ;
916+ }
917+
918+ fontLoadingListenerSetup = true ;
919+ }
920+
869921function scheduleUpdateDOMTextMeasurement ( node : DOMText ) {
870922 /*
871923 Make sure the font is loaded before measuring
872924 */
873925
926+ setupFontLoadingListeners ( ) ;
927+
874928 if ( textNodesToMeasure . size === 0 ) {
875- const fonts = document . fonts ;
876- if ( document . fonts . status === 'loaded' ) {
877- setTimeout ( updateDOMTextMeasurements ) ;
878- } else {
879- if ( fonts && fonts . ready && typeof fonts . ready . then === 'function' ) {
929+ if ( typeof document !== 'undefined' && ' fonts' in document ) {
930+ const fonts = document . fonts ;
931+ if ( fonts . status === 'loaded' ) {
932+ setTimeout ( updateDOMTextMeasurements ) ;
933+ } else if ( fonts . ready && typeof fonts . ready . then === 'function' ) {
880934 fonts . ready . then ( updateDOMTextMeasurements ) ;
881935 } else {
882936 setTimeout ( updateDOMTextMeasurements , 500 ) ;
883937 }
938+ } else {
939+ // Fallback for devices without FontFaceSet.ready()
940+ setTimeout ( updateDOMTextMeasurements , 500 ) ;
884941 }
885942 }
886943
@@ -1284,69 +1341,98 @@ export class DOMNode extends EventEmitter implements IRendererNode {
12841341 set scale ( v ) {
12851342 if ( this . props . scale === v ) return ;
12861343 this . props . scale = v ;
1344+ this . boundsDirty = true ;
1345+ this . markChildrenBoundsDirty ( ) ;
12871346 updateNodeStyles ( this ) ;
12881347 }
12891348 get scaleX ( ) {
12901349 return this . props . scaleX ;
12911350 }
12921351 set scaleX ( v ) {
1352+ if ( this . props . scaleX === v ) return ;
12931353 this . props . scaleX = v ;
1354+ this . boundsDirty = true ;
1355+ this . markChildrenBoundsDirty ( ) ;
12941356 updateNodeStyles ( this ) ;
12951357 }
12961358 get scaleY ( ) {
12971359 return this . props . scaleY ;
12981360 }
12991361 set scaleY ( v ) {
1362+ if ( this . props . scaleY === v ) return ;
13001363 this . props . scaleY = v ;
1364+ this . boundsDirty = true ;
1365+ this . markChildrenBoundsDirty ( ) ;
13011366 updateNodeStyles ( this ) ;
13021367 }
13031368 get mount ( ) {
13041369 return this . props . mount ;
13051370 }
13061371 set mount ( v ) {
1372+ if ( this . props . mount === v ) return ;
13071373 this . props . mount = v ;
1374+ this . boundsDirty = true ;
1375+ this . markChildrenBoundsDirty ( ) ;
13081376 updateNodeStyles ( this ) ;
13091377 }
13101378 get mountX ( ) {
13111379 return this . props . mountX ;
13121380 }
13131381 set mountX ( v ) {
1382+ if ( this . props . mountX === v ) return ;
13141383 this . props . mountX = v ;
1384+ this . boundsDirty = true ;
1385+ this . markChildrenBoundsDirty ( ) ;
13151386 updateNodeStyles ( this ) ;
13161387 }
13171388 get mountY ( ) {
13181389 return this . props . mountY ;
13191390 }
13201391 set mountY ( v ) {
1392+ if ( this . props . mountY === v ) return ;
13211393 this . props . mountY = v ;
1394+ this . boundsDirty = true ;
1395+ this . markChildrenBoundsDirty ( ) ;
13221396 updateNodeStyles ( this ) ;
13231397 }
13241398 get pivot ( ) {
13251399 return this . props . pivot ;
13261400 }
13271401 set pivot ( v ) {
1402+ if ( this . props . pivot === v ) return ;
13281403 this . props . pivot = v ;
1404+ this . boundsDirty = true ;
1405+ this . markChildrenBoundsDirty ( ) ;
13291406 updateNodeStyles ( this ) ;
13301407 }
13311408 get pivotX ( ) {
13321409 return this . props . pivotX ;
13331410 }
13341411 set pivotX ( v ) {
1412+ if ( this . props . pivotX === v ) return ;
13351413 this . props . pivotX = v ;
1414+ this . boundsDirty = true ;
1415+ this . markChildrenBoundsDirty ( ) ;
13361416 updateNodeStyles ( this ) ;
13371417 }
13381418 get pivotY ( ) {
13391419 return this . props . pivotY ;
13401420 }
13411421 set pivotY ( v ) {
1422+ if ( this . props . pivotY === v ) return ;
13421423 this . props . pivotY = v ;
1424+ this . boundsDirty = true ;
1425+ this . markChildrenBoundsDirty ( ) ;
13431426 updateNodeStyles ( this ) ;
13441427 }
13451428 get rotation ( ) {
13461429 return this . props . rotation ;
13471430 }
13481431 set rotation ( v ) {
1432+ if ( this . props . rotation === v ) return ;
13491433 this . props . rotation = v ;
1434+ this . boundsDirty = true ;
1435+ this . markChildrenBoundsDirty ( ) ;
13501436 updateNodeStyles ( this ) ;
13511437 }
13521438 get rtt ( ) {
@@ -1446,9 +1532,16 @@ class DOMText extends DOMNode {
14461532 ) {
14471533 super ( stage , props ) ;
14481534 this . div . innerText = props . text ;
1535+ syncContainTextNodeTracking ( this ) ;
14491536 scheduleUpdateDOMTextMeasurement ( this ) ;
14501537 }
14511538
1539+ override destroy ( ) : void {
1540+ textNodesToMeasure . delete ( this ) ;
1541+ containTextNodes . delete ( this ) ;
1542+ super . destroy ( ) ;
1543+ }
1544+
14521545 get text ( ) {
14531546 return this . props . text ;
14541547 }
@@ -1552,6 +1645,7 @@ class DOMText extends DOMNode {
15521645 set contain ( v ) {
15531646 if ( this . props . contain === v ) return ;
15541647 this . props . contain = v ;
1648+ syncContainTextNodeTracking ( this ) ;
15551649 updateNodeStyles ( this ) ;
15561650 scheduleUpdateDOMTextMeasurement ( this ) ;
15571651 }
@@ -1637,7 +1731,7 @@ export class DOMRendererMain implements IRendererMain {
16371731 new Map ( ) ;
16381732
16391733 constructor (
1640- public settings : Partial < lng . RendererMainSettings > ,
1734+ public settings : DomRendererMainSettings ,
16411735 rawTarget : string | HTMLElement ,
16421736 ) {
16431737 let target : HTMLElement ;
0 commit comments