@@ -25,6 +25,10 @@ import { remeasureParagraph } from './remeasure';
2525import { computeDirtyRegions } from './diff' ;
2626import { MeasureCache } from './cache' ;
2727import { layoutHeaderFooterWithCache , HeaderFooterLayoutCache , type HeaderFooterBatch } from './layoutHeaderFooter' ;
28+ import {
29+ buildSectionAwareHeaderFooterLayoutKey ,
30+ buildSectionAwareHeaderFooterMeasurementGroups ,
31+ } from './sectionAwareHeaderFooter' ;
2832import { FeatureFlags } from './featureFlags' ;
2933import { PageTokenLogger , HeaderFooterCacheLogger , globalMetrics } from './instrumentation' ;
3034import { HeaderFooterCacheState , invalidateHeaderFooterCache } from './cacheInvalidation' ;
@@ -886,10 +890,83 @@ export async function incrementalLayout(
886890 * Values are the actual content heights in pixels.
887891 */
888892 let headerContentHeightsByRId : Map < string , number > | undefined ;
893+ let headerContentHeightsBySectionRef : Map < string , number > | undefined ;
889894
890895 // Check if we have headers via either headerBlocks (by variant) or headerBlocksByRId (by relationship ID)
891896 const hasHeaderBlocks = headerFooter ?. headerBlocks && Object . keys ( headerFooter . headerBlocks ) . length > 0 ;
892897 const hasHeaderBlocksByRId = headerFooter ?. headerBlocksByRId && headerFooter . headerBlocksByRId . size > 0 ;
898+ const sectionMetadata = options . sectionMetadata ?? [ ] ;
899+
900+ const measureHeightsByReference = async (
901+ kind : 'header' | 'footer' ,
902+ blocksByRId : Map < string , FlowBlock [ ] > | undefined ,
903+ constraints : HeaderFooterConstraints ,
904+ measureFn : HeaderFooterMeasureFn ,
905+ ) : Promise < {
906+ heightsByRId ?: Map < string , number > ;
907+ heightsBySectionRef ?: Map < string , number > ;
908+ } > => {
909+ if ( ! blocksByRId || blocksByRId . size === 0 ) {
910+ return { } ;
911+ }
912+
913+ const heightsByRId = new Map < string , number > ( ) ;
914+ const heightsBySectionRef = new Map < string , number > ( ) ;
915+ const sectionAwareGroups = buildSectionAwareHeaderFooterMeasurementGroups (
916+ kind ,
917+ blocksByRId ,
918+ sectionMetadata ,
919+ constraints ,
920+ ) ;
921+
922+ if ( sectionAwareGroups . length > 0 ) {
923+ for ( const group of sectionAwareGroups ) {
924+ const blocks = blocksByRId . get ( group . rId ) ;
925+ if ( ! blocks || blocks . length === 0 ) continue ;
926+
927+ const measureConstraints = {
928+ maxWidth : group . sectionConstraints . width ,
929+ maxHeight : group . sectionConstraints . height ,
930+ } ;
931+ const measures = await Promise . all ( blocks . map ( ( block ) => measureFn ( block , measureConstraints ) ) ) ;
932+ const layout = layoutHeaderFooter ( blocks , measures , group . sectionConstraints , kind ) ;
933+ if ( ! ( layout . height > 0 ) ) continue ;
934+
935+ const nextHeight = Math . max ( 0 , layout . height ) ;
936+ const currentHeight = heightsByRId . get ( group . rId ) ?? 0 ;
937+ if ( nextHeight > currentHeight ) {
938+ heightsByRId . set ( group . rId , nextHeight ) ;
939+ }
940+
941+ for ( const sectionIndex of group . sectionIndices ) {
942+ heightsBySectionRef . set ( buildSectionAwareHeaderFooterLayoutKey ( group . rId , sectionIndex ) , nextHeight ) ;
943+ }
944+ }
945+
946+ return {
947+ heightsByRId : heightsByRId . size > 0 ? heightsByRId : undefined ,
948+ heightsBySectionRef : heightsBySectionRef . size > 0 ? heightsBySectionRef : undefined ,
949+ } ;
950+ }
951+
952+ for ( const [ rId , blocks ] of blocksByRId ) {
953+ if ( ! blocks || blocks . length === 0 ) continue ;
954+
955+ const measureConstraints = {
956+ maxWidth : constraints . width ,
957+ maxHeight : constraints . height ,
958+ } ;
959+ const measures = await Promise . all ( blocks . map ( ( block ) => measureFn ( block , measureConstraints ) ) ) ;
960+ const layout = layoutHeaderFooter ( blocks , measures , constraints , kind ) ;
961+ if ( layout . height > 0 ) {
962+ heightsByRId . set ( rId , layout . height ) ;
963+ }
964+ }
965+
966+ return {
967+ heightsByRId : heightsByRId . size > 0 ? heightsByRId : undefined ,
968+ } ;
969+ } ;
893970
894971 if ( headerFooter ?. constraints && ( hasHeaderBlocks || hasHeaderBlocksByRId ) ) {
895972 const hfPreStart = performance . now ( ) ;
@@ -953,22 +1030,14 @@ export async function incrementalLayout(
9531030 // Also extract heights from headerBlocksByRId (for multi-section documents)
9541031 // Store each rId's height separately for per-page margin calculation
9551032 if ( hasHeaderBlocksByRId && headerFooter . headerBlocksByRId ) {
956- headerContentHeightsByRId = new Map < string , number > ( ) ;
957- for ( const [ rId , blocks ] of headerFooter . headerBlocksByRId ) {
958- if ( ! blocks || blocks . length === 0 ) continue ;
959- // Measure blocks to get height
960- const measureConstraints = {
961- maxWidth : headerFooter . constraints . width ,
962- maxHeight : headerFooter . constraints . height ,
963- } ;
964- const measures = await Promise . all ( blocks . map ( ( block ) => measureFn ( block , measureConstraints ) ) ) ;
965- // Layout to get actual height — pass full constraints for page-relative normalization
966- const layout = layoutHeaderFooter ( blocks , measures , headerFooter . constraints , 'header' ) ;
967- if ( layout . height > 0 ) {
968- // Store height by rId for per-page margin calculation
969- headerContentHeightsByRId . set ( rId , layout . height ) ;
970- }
971- }
1033+ const measuredHeights = await measureHeightsByReference (
1034+ 'header' ,
1035+ headerFooter . headerBlocksByRId ,
1036+ headerFooter . constraints ,
1037+ measureFn ,
1038+ ) ;
1039+ headerContentHeightsByRId = measuredHeights . heightsByRId ;
1040+ headerContentHeightsBySectionRef = measuredHeights . heightsBySectionRef ;
9721041 }
9731042
9741043 const hfPreEnd = performance . now ( ) ;
@@ -993,6 +1062,7 @@ export async function incrementalLayout(
9931062 * Values are the actual content heights in pixels.
9941063 */
9951064 let footerContentHeightsByRId : Map < string , number > | undefined ;
1065+ let footerContentHeightsBySectionRef : Map < string , number > | undefined ;
9961066
9971067 // Check if we have footers via either footerBlocks (by variant) or footerBlocksByRId (by relationship ID)
9981068 const hasFooterBlocks = headerFooter ?. footerBlocks && Object . keys ( headerFooter . footerBlocks ) . length > 0 ;
@@ -1064,22 +1134,14 @@ export async function incrementalLayout(
10641134 // Also extract heights from footerBlocksByRId (for multi-section documents)
10651135 // Store each rId's height separately for per-page margin calculation
10661136 if ( hasFooterBlocksByRId && headerFooter . footerBlocksByRId ) {
1067- footerContentHeightsByRId = new Map < string , number > ( ) ;
1068- for ( const [ rId , blocks ] of headerFooter . footerBlocksByRId ) {
1069- if ( ! blocks || blocks . length === 0 ) continue ;
1070- // Measure blocks to get height
1071- const measureConstraints = {
1072- maxWidth : headerFooter . constraints . width ,
1073- maxHeight : headerFooter . constraints . height ,
1074- } ;
1075- const measures = await Promise . all ( blocks . map ( ( block ) => measureFn ( block , measureConstraints ) ) ) ;
1076- // Layout to get actual height — pass full constraints for page-relative normalization
1077- const layout = layoutHeaderFooter ( blocks , measures , headerFooter . constraints , 'footer' ) ;
1078- if ( layout . height > 0 ) {
1079- // Store height by rId for per-page margin calculation
1080- footerContentHeightsByRId . set ( rId , layout . height ) ;
1081- }
1082- }
1137+ const measuredHeights = await measureHeightsByReference (
1138+ 'footer' ,
1139+ headerFooter . footerBlocksByRId ,
1140+ headerFooter . constraints ,
1141+ measureFn ,
1142+ ) ;
1143+ footerContentHeightsByRId = measuredHeights . heightsByRId ;
1144+ footerContentHeightsBySectionRef = measuredHeights . heightsBySectionRef ;
10831145 }
10841146 } catch ( error ) {
10851147 console . error ( '[Layout] Footer pre-layout failed:' , error ) ;
@@ -1095,7 +1157,9 @@ export async function incrementalLayout(
10951157 ...options ,
10961158 headerContentHeights, // Pass header heights to prevent overlap (per-variant)
10971159 footerContentHeights, // Pass footer heights to prevent overlap (per-variant)
1160+ headerContentHeightsBySectionRef, // Pass header heights by rId+section for exact page-specific margin calculation
10981161 headerContentHeightsByRId, // Pass header heights by rId for per-page margin calculation
1162+ footerContentHeightsBySectionRef, // Pass footer heights by rId+section for exact page-specific margin calculation
10991163 footerContentHeightsByRId, // Pass footer heights by rId for per-page margin calculation
11001164 remeasureParagraph : ( block : FlowBlock , maxWidth : number , firstLineIndent ?: number ) =>
11011165 remeasureParagraph ( block as ParagraphBlock , maxWidth , firstLineIndent ) ,
@@ -1179,7 +1243,9 @@ export async function incrementalLayout(
11791243 ...options ,
11801244 headerContentHeights, // Pass header heights to prevent overlap (per-variant)
11811245 footerContentHeights, // Pass footer heights to prevent overlap (per-variant)
1246+ headerContentHeightsBySectionRef, // Pass header heights by rId+section for exact page-specific margin calculation
11821247 headerContentHeightsByRId, // Pass header heights by rId for per-page margin calculation
1248+ footerContentHeightsBySectionRef, // Pass footer heights by rId+section for exact page-specific margin calculation
11831249 footerContentHeightsByRId, // Pass footer heights by rId for per-page margin calculation
11841250 remeasureParagraph : ( block : FlowBlock , maxWidth : number , firstLineIndent ?: number ) =>
11851251 remeasureParagraph ( block as ParagraphBlock , maxWidth , firstLineIndent ) ,
@@ -1771,6 +1837,10 @@ export async function incrementalLayout(
17711837 footnoteReservedByPageIndex,
17721838 headerContentHeights,
17731839 footerContentHeights,
1840+ headerContentHeightsBySectionRef,
1841+ headerContentHeightsByRId,
1842+ footerContentHeightsBySectionRef,
1843+ footerContentHeightsByRId,
17741844 remeasureParagraph : ( block : FlowBlock , maxWidth : number , firstLineIndent ?: number ) =>
17751845 remeasureParagraph ( block as ParagraphBlock , maxWidth , firstLineIndent ) ,
17761846 } ) ;
0 commit comments