@@ -422,6 +422,9 @@ export class SNLayoutBuilder {
422422
423423 // 子节点构建完成后,计算 Element 的布局信息
424424 this . finalizeNodeLayout ( element ) ;
425+
426+ // 子节点添加后,立即更新父节点(Line)的高度
427+ this . calculateNodeHeight ( parentNode ) ;
425428 }
426429 }
427430
@@ -557,6 +560,9 @@ export class SNLayoutBuilder {
557560 layoutElement . layout . width ,
558561 ) ;
559562 }
563+
564+ // 子节点添加后,立即更新父节点(Measure Element)的高度
565+ this . calculateNodeHeight ( parentNode ) ;
560566 }
561567 }
562568
@@ -657,8 +663,15 @@ export class SNLayoutBuilder {
657663 lyricBaseOffset +
658664 verseNumber * lyricLineHeight ;
659665 }
666+
667+ // 歌词元素添加后,立即更新父节点(Measure Element)的高度
668+ this . calculateNodeHeight ( parentLayoutElement ) ;
660669 }
661670 }
671+
672+ // 所有歌词元素添加完成后,更新父节点(Measure Element)的高度
673+ // 确保父节点高度包含所有歌词
674+ this . calculateNodeHeight ( parentLayoutElement ) ;
662675 }
663676
664677 /**
@@ -820,13 +833,80 @@ export class SNLayoutBuilder {
820833 }
821834
822835 case SNLayoutNodeType . LINE : {
823- // Line节点高度按配置设置,不需要计算(已在转换器中设置)
824- // 这里不做处理
836+ // Line节点高度需要根据实际内容动态调整
837+ // 根据子节点(measure)的实际高度来计算行高
838+ if ( node instanceof SNLayoutLine ) {
839+ // 默认行高(从配置中获取,如果没有则使用50)
840+ const defaultHeight =
841+ typeof node . layout . height === 'number' && node . layout . height > 0
842+ ? node . layout . height
843+ : 50 ;
844+
845+ // 计算实际需要的行高
846+ let requiredHeight = defaultHeight ;
847+
848+ // 检查是否有子元素
849+ if ( node . children && node . children . length > 0 ) {
850+ // 计算子节点的最大高度
851+ let maxChildHeight = 0 ;
852+ let hasMetadata = false ;
853+
854+ // 遍历所有子元素,计算最大高度
855+ for ( const child of node . children ) {
856+ if ( ! child . layout ) continue ;
857+
858+ const childData = child . data as any ;
859+ const childType = childData ?. type as string | undefined ;
860+
861+ // 检查是否是调号等元信息元素
862+ if (
863+ childType === 'metadata-music-info' ||
864+ childType === 'metadata-contributors'
865+ ) {
866+ hasMetadata = true ;
867+ const childHeight =
868+ typeof child . layout . height === 'number'
869+ ? child . layout . height
870+ : 0 ;
871+ maxChildHeight = Math . max ( maxChildHeight , childHeight ) ;
872+ }
873+
874+ // 检查是否是 measure 元素
875+ if ( childType === 'measure' ) {
876+ const childHeight =
877+ typeof child . layout . height === 'number'
878+ ? child . layout . height
879+ : 0 ;
880+ maxChildHeight = Math . max ( maxChildHeight , childHeight ) ;
881+ }
882+ }
883+
884+ // 如果有子节点,使用子节点的最大高度
885+ if ( maxChildHeight > 0 ) {
886+ requiredHeight = Math . max ( requiredHeight , maxChildHeight ) ;
887+ }
888+
889+ // 如果有调号等元信息,确保行高足够
890+ if ( hasMetadata && maxChildHeight === 0 ) {
891+ const metadataHeightIncrement = 5 ; // 调号等元信息需要的额外行高
892+ requiredHeight = Math . max (
893+ requiredHeight ,
894+ defaultHeight + metadataHeightIncrement ,
895+ ) ;
896+ }
897+ }
898+
899+ // 更新行高
900+ node . layout . height = requiredHeight ;
901+ }
825902 break ;
826903 }
827904
828905 case SNLayoutNodeType . ELEMENT : {
829906 // Element:根据子节点计算高度,如果没有子节点则使用默认值
907+ const nodeData = node . data as any ;
908+ const isMeasure = nodeData ?. type === 'measure' ;
909+
830910 if ( node . children && node . children . length > 0 ) {
831911 const childrenHeight = node . calculateChildrenHeight ( ) ;
832912 const padding = node . layout . padding || {
@@ -837,15 +917,44 @@ export class SNLayoutBuilder {
837917 } ;
838918 // childrenHeight 返回的是 maxBottom,已经包含了 padding.top 的空间
839919 // 所以只需要加上 padding.bottom 即可
840- node . layout . height = childrenHeight + padding . bottom ;
920+ const calculatedHeight = childrenHeight + padding . bottom ;
921+
922+ // 如果是 measure element,需要确保有足够的冗余高度来容纳上下加线和符干
923+ // 五线谱配置:staffTop = 6, staffHeight = 30
924+ // 五线谱范围:从 y=6 到 y=36
925+ // 符干长度:20px,上下加线可能延伸几个线间距(lineGap = 7.5)
926+ // 所以最小高度应该是:顶部冗余(6) + 五线谱高度(30) + 底部冗余(20+15) = 71px
927+ if ( isMeasure ) {
928+ const staffTop = 6 ; // 五线谱顶部偏移
929+ const staffHeight = 30 ; // 五线谱高度
930+ const stemLength = 20 ; // 符干长度
931+ const ledgerLineSpace = 15 ; // 上下加线的冗余空间(约2个线间距)
932+ const minMeasureHeight =
933+ staffTop + staffHeight + stemLength + ledgerLineSpace ; // 71px
934+
935+ // 使用计算出的高度和最小高度的较大值
936+ node . layout . height = Math . max ( calculatedHeight , minMeasureHeight ) ;
937+ } else {
938+ node . layout . height = calculatedHeight ;
939+ }
841940 } else {
842941 // 叶子元素:使用已有高度或默认值
843942 if (
844943 ! node . layout . height ||
845944 typeof node . layout . height !== 'number' ||
846945 node . layout . height === 0
847946 ) {
848- node . layout . height = 20 ;
947+ // measure 的默认高度应该包含五线谱和冗余空间
948+ if ( isMeasure ) {
949+ const staffTop = 6 ;
950+ const staffHeight = 30 ;
951+ const stemLength = 20 ;
952+ const ledgerLineSpace = 15 ;
953+ node . layout . height =
954+ staffTop + staffHeight + stemLength + ledgerLineSpace ; // 71px
955+ } else {
956+ node . layout . height = 20 ;
957+ }
849958 }
850959 }
851960 break ;
0 commit comments