@@ -518,6 +518,14 @@ export type LayoutOptions = {
518518 * overlay behavior in paragraph-free header/footer regions.
519519 */
520520 allowParagraphlessAnchoredTableFallback ?: boolean ;
521+ /**
522+ * Allow body layout to synthesize page 1 when section metadata exists but no
523+ * renderable body blocks survive conversion.
524+ *
525+ * Header/footer layout keeps this disabled to preserve existing empty-region
526+ * behavior for paragraph-free overlays.
527+ */
528+ allowSectionBreakOnlyPageFallback ?: boolean ;
521529} ;
522530
523531export type HeaderFooterConstraints = {
@@ -591,6 +599,10 @@ const shouldSkipRedundantPageBreakBefore = (block: PageBreakBlock, state: PageSt
591599 return isAtTopOfFreshPage ;
592600} ;
593601
602+ const hasOnlySectionBreakBlocks = ( blocks : readonly FlowBlock [ ] ) : boolean => {
603+ return blocks . length > 0 && blocks . every ( ( block ) => block . kind === 'sectionBreak' ) ;
604+ } ;
605+
594606// List constants sourced from shared/common
595607
596608// Context types moved to modular layouters
@@ -813,6 +825,7 @@ export function layoutDocument(blocks: FlowBlock[], measures: Measure[], options
813825 let activeColumns = cloneColumnLayout ( options . columns ) ;
814826 let pendingColumns : ColumnLayout | null = null ;
815827 const allowParagraphlessAnchoredTableFallback = options . allowParagraphlessAnchoredTableFallback !== false ;
828+ const allowSectionBreakOnlyPageFallback = options . allowSectionBreakOnlyPageFallback !== false ;
816829
817830 // Track active and pending orientation
818831 let activeOrientation : 'portrait' | 'landscape' | null = null ;
@@ -1082,6 +1095,7 @@ export function layoutDocument(blocks: FlowBlock[], measures: Measure[], options
10821095 let activeNumberFormat : 'decimal' | 'lowerLetter' | 'upperLetter' | 'lowerRoman' | 'upperRoman' | 'numberInDash' =
10831096 'decimal' ;
10841097 let activePageCounter = 1 ;
1098+ let activeSectionPageCounterStart = activePageCounter ;
10851099 let pendingNumbering : SectionNumbering | null = null ;
10861100 // Section header/footer ref tracking state
10871101 type SectionRefs = {
@@ -1109,6 +1123,7 @@ export function layoutDocument(blocks: FlowBlock[], measures: Measure[], options
11091123 }
11101124 if ( typeof initialSectionMetadata ?. numbering ?. start === 'number' ) {
11111125 activePageCounter = initialSectionMetadata . numbering . start ;
1126+ activeSectionPageCounterStart = activePageCounter ;
11121127 }
11131128 let activeSectionRefs : SectionRefs | null = null ;
11141129 let pendingSectionRefs : SectionRefs | null = null ;
@@ -1152,6 +1167,22 @@ export function layoutDocument(blocks: FlowBlock[], measures: Measure[], options
11521167 if ( ! state ) {
11531168 // Track if we're entering a new section (pendingSectionIndex was just set)
11541169 const isEnteringNewSection = pendingSectionIndex !== null ;
1170+ const isApplyingPendingSection =
1171+ pendingTopMargin !== null ||
1172+ pendingBottomMargin !== null ||
1173+ pendingLeftMargin !== null ||
1174+ pendingRightMargin !== null ||
1175+ pendingHeaderDistance !== null ||
1176+ pendingFooterDistance !== null ||
1177+ pendingPageSize !== null ||
1178+ pendingColumns !== null ||
1179+ pendingOrientation !== null ||
1180+ pendingNumbering !== null ||
1181+ pendingSectionRefs !== null ||
1182+ pendingSectionIndex !== null ||
1183+ pendingVAlign !== undefined ||
1184+ pendingSectionBaseTopMargin !== null ||
1185+ pendingSectionBaseBottomMargin !== null ;
11551186
11561187 const applied = applyPendingToActive ( {
11571188 activeTopMargin,
@@ -1233,6 +1264,9 @@ export function layoutDocument(blocks: FlowBlock[], measures: Measure[], options
12331264 activeSectionBaseBottomMargin = pendingSectionBaseBottomMargin ;
12341265 pendingSectionBaseBottomMargin = null ;
12351266 }
1267+ if ( isApplyingPendingSection ) {
1268+ activeSectionPageCounterStart = activePageCounter ;
1269+ }
12361270 pageCount += 1 ;
12371271
12381272 // Calculate the page number for this new page
@@ -1750,6 +1784,7 @@ export function layoutDocument(blocks: FlowBlock[], measures: Measure[], options
17501784 if ( sectionMetadata . numbering . format ) activeNumberFormat = sectionMetadata . numbering . format ;
17511785 if ( typeof sectionMetadata . numbering . start === 'number' ) {
17521786 activePageCounter = sectionMetadata . numbering . start ;
1787+ activeSectionPageCounterStart = activePageCounter ;
17531788 }
17541789 } else {
17551790 // Non-first section: schedule for next page
@@ -1760,6 +1795,7 @@ export function layoutDocument(blocks: FlowBlock[], measures: Measure[], options
17601795 if ( effectiveBlock . numbering . format ) activeNumberFormat = effectiveBlock . numbering . format ;
17611796 if ( typeof effectiveBlock . numbering . start === 'number' ) {
17621797 activePageCounter = effectiveBlock . numbering . start ;
1798+ activeSectionPageCounterStart = activePageCounter ;
17631799 }
17641800 } else {
17651801 pendingNumbering = { ...effectiveBlock . numbering } ;
@@ -2262,6 +2298,20 @@ export function layoutDocument(blocks: FlowBlock[], measures: Measure[], options
22622298 // a final blank page for continuous final sections.
22632299 paginator . pruneTrailingEmptyPages ( ) ;
22642300
2301+ const resetPaginationStateForBlankPageFallback = ( ) : void => {
2302+ pageCount = 0 ;
2303+ activePageCounter = activeSectionPageCounterStart ;
2304+ sectionFirstPageNumbers . clear ( ) ;
2305+ } ;
2306+
2307+ if (
2308+ pages . length === 0 &&
2309+ ( ( allowParagraphlessAnchoredTableFallback && paragraphlessAnchoredTables . length > 0 ) ||
2310+ ( allowSectionBreakOnlyPageFallback && hasOnlySectionBreakBlocks ( blocks ) ) )
2311+ ) {
2312+ resetPaginationStateForBlankPageFallback ( ) ;
2313+ }
2314+
22652315 if ( allowParagraphlessAnchoredTableFallback && pages . length === 0 && paragraphlessAnchoredTables . length > 0 ) {
22662316 const state = paginator . ensurePage ( ) ;
22672317
@@ -2284,6 +2334,10 @@ export function layoutDocument(blocks: FlowBlock[], measures: Measure[], options
22842334 }
22852335 }
22862336
2337+ if ( allowSectionBreakOnlyPageFallback && pages . length === 0 && hasOnlySectionBreakBlocks ( blocks ) ) {
2338+ paginator . ensurePage ( ) ;
2339+ }
2340+
22872341 // Post-process pages with vertical alignment (center, bottom, both)
22882342 // For each page, calculate content bounds and apply Y offset to all fragments
22892343 for ( const page of pages ) {
@@ -2605,6 +2659,7 @@ export function layoutHeaderFooter(
26052659 pageSize : { w : width , h : height } ,
26062660 margins : { top : 0 , right : 0 , bottom : 0 , left : 0 } ,
26072661 allowParagraphlessAnchoredTableFallback : false ,
2662+ allowSectionBreakOnlyPageFallback : false ,
26082663 } ) ;
26092664
26102665 // Post-normalize page-relative anchored fragment Y positions for footers.
0 commit comments