@@ -5290,3 +5290,136 @@ describe('requirePageBoundary edge cases', () => {
52905290 } ) ;
52915291 } ) ;
52925292} ) ;
5293+
5294+ describe ( 'alternateHeaders (odd/even header differentiation)' , ( ) => {
5295+ // Two tall paragraphs (400px each) that force a 2-page layout.
5296+ const tallBlock = ( id : string ) : FlowBlock => ( {
5297+ kind : 'paragraph' ,
5298+ id,
5299+ runs : [ ] ,
5300+ } ) ;
5301+ const tallMeasure = makeMeasure ( [ 400 ] ) ;
5302+
5303+ it ( 'selects even/odd header heights when alternateHeaders is true' , ( ) => {
5304+ const options : LayoutOptions = {
5305+ pageSize : { w : 600 , h : 800 } ,
5306+ margins : { top : 50 , right : 50 , bottom : 50 , left : 50 , header : 30 } ,
5307+ alternateHeaders : true ,
5308+ headerContentHeights : {
5309+ odd : 80 , // Odd pages: header pushes body start down
5310+ even : 40 , // Even pages: smaller header
5311+ } ,
5312+ } ;
5313+
5314+ const layout = layoutDocument ( [ tallBlock ( 'p1' ) , tallBlock ( 'p2' ) ] , [ tallMeasure , tallMeasure ] , options ) ;
5315+
5316+ expect ( layout . pages ) . toHaveLength ( 2 ) ;
5317+
5318+ // Page 1 is odd (documentPageNumber=1) → uses 'odd' header height (80px)
5319+ // Body should start at max(margin.top, margin.header + headerContentHeight) = max(50, 30+80) = 110
5320+ const p1Fragment = layout . pages [ 0 ] . fragments . find ( ( f ) => f . blockId === 'p1' ) ;
5321+ expect ( p1Fragment ) . toBeDefined ( ) ;
5322+ expect ( p1Fragment ! . y ) . toBeCloseTo ( 110 , 0 ) ;
5323+
5324+ // Page 2 is even (documentPageNumber=2) → uses 'even' header height (40px)
5325+ // Body should start at max(margin.top, margin.header + headerContentHeight) = max(50, 30+40) = 70
5326+ const p2Fragment = layout . pages [ 1 ] . fragments . find ( ( f ) => f . blockId === 'p2' ) ;
5327+ expect ( p2Fragment ) . toBeDefined ( ) ;
5328+ expect ( p2Fragment ! . y ) . toBeCloseTo ( 70 , 0 ) ;
5329+ } ) ;
5330+
5331+ it ( 'uses default header height for all pages when alternateHeaders is false' , ( ) => {
5332+ const options : LayoutOptions = {
5333+ pageSize : { w : 600 , h : 800 } ,
5334+ margins : { top : 50 , right : 50 , bottom : 50 , left : 50 , header : 30 } ,
5335+ alternateHeaders : false ,
5336+ headerContentHeights : {
5337+ default : 60 ,
5338+ odd : 80 ,
5339+ even : 40 ,
5340+ } ,
5341+ } ;
5342+
5343+ const layout = layoutDocument ( [ tallBlock ( 'p1' ) , tallBlock ( 'p2' ) ] , [ tallMeasure , tallMeasure ] , options ) ;
5344+
5345+ expect ( layout . pages ) . toHaveLength ( 2 ) ;
5346+
5347+ // Both pages use 'default' header height (60px)
5348+ // Body start = max(50, 30+60) = 90
5349+ const p1Fragment = layout . pages [ 0 ] . fragments . find ( ( f ) => f . blockId === 'p1' ) ;
5350+ const p2Fragment = layout . pages [ 1 ] . fragments . find ( ( f ) => f . blockId === 'p2' ) ;
5351+ expect ( p1Fragment ! . y ) . toBeCloseTo ( 90 , 0 ) ;
5352+ expect ( p2Fragment ! . y ) . toBeCloseTo ( 90 , 0 ) ;
5353+ } ) ;
5354+
5355+ it ( 'defaults to false when alternateHeaders is omitted' , ( ) => {
5356+ const options : LayoutOptions = {
5357+ pageSize : { w : 600 , h : 800 } ,
5358+ margins : { top : 50 , right : 50 , bottom : 50 , left : 50 , header : 30 } ,
5359+ // alternateHeaders not set
5360+ headerContentHeights : {
5361+ default : 60 ,
5362+ odd : 80 ,
5363+ even : 40 ,
5364+ } ,
5365+ } ;
5366+
5367+ const layout = layoutDocument ( [ tallBlock ( 'p1' ) , tallBlock ( 'p2' ) ] , [ tallMeasure , tallMeasure ] , options ) ;
5368+
5369+ expect ( layout . pages ) . toHaveLength ( 2 ) ;
5370+
5371+ // Both pages should use 'default' (60px), not odd/even
5372+ const p1Fragment = layout . pages [ 0 ] . fragments . find ( ( f ) => f . blockId === 'p1' ) ;
5373+ const p2Fragment = layout . pages [ 1 ] . fragments . find ( ( f ) => f . blockId === 'p2' ) ;
5374+ expect ( p1Fragment ! . y ) . toBeCloseTo ( 90 , 0 ) ;
5375+ expect ( p2Fragment ! . y ) . toBeCloseTo ( 90 , 0 ) ;
5376+ } ) ;
5377+
5378+ it ( 'first page uses first variant when titlePg is enabled with alternateHeaders' , ( ) => {
5379+ const sectionBreak : SectionBreakBlock = {
5380+ kind : 'sectionBreak' ,
5381+ id : 'sb' ,
5382+ attrs : { isFirstSection : true , source : 'sectPr' , sectionIndex : 0 } ,
5383+ pageSize : { w : 600 , h : 800 } ,
5384+ margins : { top : 50 , right : 50 , bottom : 50 , left : 50 , header : 30 } ,
5385+ } ;
5386+
5387+ const options : LayoutOptions = {
5388+ pageSize : { w : 600 , h : 800 } ,
5389+ margins : { top : 50 , right : 50 , bottom : 50 , left : 50 , header : 30 } ,
5390+ alternateHeaders : true ,
5391+ sectionMetadata : [ { sectionIndex : 0 , titlePg : true } ] ,
5392+ headerContentHeights : {
5393+ first : 100 , // First page: tallest header
5394+ odd : 80 ,
5395+ even : 40 ,
5396+ } ,
5397+ } ;
5398+
5399+ const layout = layoutDocument (
5400+ [ sectionBreak , tallBlock ( 'p1' ) , tallBlock ( 'p2' ) , tallBlock ( 'p3' ) ] ,
5401+ [ { kind : 'sectionBreak' } , tallMeasure , tallMeasure , tallMeasure ] ,
5402+ options ,
5403+ ) ;
5404+
5405+ expect ( layout . pages . length ) . toBeGreaterThanOrEqual ( 3 ) ;
5406+
5407+ // Page 1 (first page of section, titlePg=true) → 'first' variant → 100px
5408+ // Body start = max(50, 30+100) = 130
5409+ const p1Fragment = layout . pages [ 0 ] . fragments . find ( ( f ) => f . blockId === 'p1' ) ;
5410+ expect ( p1Fragment ) . toBeDefined ( ) ;
5411+ expect ( p1Fragment ! . y ) . toBeCloseTo ( 130 , 0 ) ;
5412+
5413+ // Page 2 (documentPageNumber=2, even) → 'even' variant → 40px
5414+ // Body start = max(50, 30+40) = 70
5415+ const p2Fragment = layout . pages [ 1 ] . fragments . find ( ( f ) => f . blockId === 'p2' ) ;
5416+ expect ( p2Fragment ) . toBeDefined ( ) ;
5417+ expect ( p2Fragment ! . y ) . toBeCloseTo ( 70 , 0 ) ;
5418+
5419+ // Page 3 (documentPageNumber=3, odd) → 'odd' variant → 80px
5420+ // Body start = max(50, 30+80) = 110
5421+ const p3Fragment = layout . pages [ 2 ] . fragments . find ( ( f ) => f . blockId === 'p3' ) ;
5422+ expect ( p3Fragment ) . toBeDefined ( ) ;
5423+ expect ( p3Fragment ! . y ) . toBeCloseTo ( 110 , 0 ) ;
5424+ } ) ;
5425+ } ) ;
0 commit comments