@@ -195,6 +195,11 @@ private static IReadOnlyList<LayoutPage> PaginateStartingAt(
195195 return [ ] ;
196196 }
197197
198+ if ( section . ColumnCount > 1 )
199+ {
200+ return PaginateAcrossColumnsStartingAt ( blocks , section , startPageNumber , headerHeight , footerHeight , footnoteHeight ) ;
201+ }
202+
198203 var availableHeight = ComputeAvailableContentHeight ( section , headerHeight , footerHeight , footnoteHeight ) ;
199204 var pages = new List < LayoutPage > ( ) ;
200205 var currentPageBlocks = new List < LayoutBlock > ( ) ;
@@ -241,7 +246,7 @@ private static IReadOnlyList<LayoutPage> PaginateStartingAt(
241246 currentPageBlocks . RemoveRange ( keepStart , pullBackCount ) ;
242247 currentHeight -= pulledBack . Sum ( b => b . HeightTwips ) ;
243248
244- pages . Add ( CreatePage ( section , pageNumber , currentPageBlocks ) ) ;
249+ pages . Add ( CreatePage ( section , pageNumber , currentPageBlocks , headerHeight : headerHeight , footerHeight : footerHeight , footnoteHeight : footnoteHeight ) ) ;
245250 pageNumber ++ ;
246251 currentPageBlocks = new List < LayoutBlock > ( pulledBack ) ;
247252 currentHeight = pulledBack . Sum ( b => b . HeightTwips ) ;
@@ -260,7 +265,7 @@ private static IReadOnlyList<LayoutPage> PaginateStartingAt(
260265 {
261266 // Place the first part on the current page, queue the second part.
262267 currentPageBlocks . Add ( split . Value . First ) ;
263- pages . Add ( CreatePage ( section , pageNumber , currentPageBlocks ) ) ;
268+ pages . Add ( CreatePage ( section , pageNumber , currentPageBlocks , headerHeight : headerHeight , footerHeight : footerHeight , footnoteHeight : footnoteHeight ) ) ;
264269 pageNumber ++ ;
265270 currentPageBlocks = [ ] ;
266271 currentHeight = 0f ;
@@ -269,7 +274,7 @@ private static IReadOnlyList<LayoutPage> PaginateStartingAt(
269274 else if ( currentPageBlocks . Count > 0 )
270275 {
271276 // Cannot split and page has content. Finalize page and retry on a fresh page.
272- pages . Add ( CreatePage ( section , pageNumber , currentPageBlocks ) ) ;
277+ pages . Add ( CreatePage ( section , pageNumber , currentPageBlocks , headerHeight : headerHeight , footerHeight : footerHeight , footnoteHeight : footnoteHeight ) ) ;
273278 pageNumber ++ ;
274279 currentPageBlocks = [ ] ;
275280 currentHeight = 0f ;
@@ -287,12 +292,134 @@ private static IReadOnlyList<LayoutPage> PaginateStartingAt(
287292 // Finalize the last page.
288293 if ( currentPageBlocks . Count > 0 )
289294 {
290- pages . Add ( CreatePage ( section , pageNumber , currentPageBlocks ) ) ;
295+ pages . Add ( CreatePage ( section , pageNumber , currentPageBlocks , headerHeight : headerHeight , footerHeight : footerHeight , footnoteHeight : footnoteHeight ) ) ;
291296 }
292297
293298 return pages ;
294299 }
295300
301+ private static IReadOnlyList < LayoutPage > PaginateAcrossColumnsStartingAt (
302+ IReadOnlyList < LayoutBlock > blocks ,
303+ SectionInfo section ,
304+ int startPageNumber ,
305+ float headerHeight = 0f ,
306+ float footerHeight = 0f ,
307+ float footnoteHeight = 0f )
308+ {
309+ var availableHeight = ComputeAvailableContentHeight ( section , headerHeight , footerHeight , footnoteHeight ) ;
310+ var contentTop = ComputeContentTop ( section , headerHeight ) ;
311+ var columns = ComputeColumnRegions ( section ) ;
312+ var pages = new List < LayoutPage > ( ) ;
313+ var currentPageBlocks = new List < LayoutBlock > ( ) ;
314+ var currentPlacements = new List < LayoutBlockPlacement > ( ) ;
315+ var currentHeight = 0f ;
316+ var currentColumnIndex = 0 ;
317+ var currentColumnHasBlocks = false ;
318+ var pageNumber = startPageNumber ;
319+
320+ var index = 0 ;
321+ LayoutBlock ? pending = null ;
322+
323+ while ( index < blocks . Count || pending is not null )
324+ {
325+ var block = pending ?? blocks [ index ] ;
326+ if ( pending is null )
327+ {
328+ index ++ ;
329+ }
330+
331+ pending = null ;
332+
333+ if ( block . ForcePageBreakBefore && currentPageBlocks . Count > 0 )
334+ {
335+ FinalizeCurrentPage ( ) ;
336+ pending = block ;
337+ continue ;
338+ }
339+
340+ if ( currentHeight + block . HeightTwips <= availableHeight )
341+ {
342+ AddCurrentBlock ( block ) ;
343+ continue ;
344+ }
345+
346+ var remainingSpace = currentColumnHasBlocks
347+ ? availableHeight - currentHeight
348+ : availableHeight ;
349+
350+ var split = TrySplitBlock ( block , remainingSpace ) ;
351+
352+ if ( split is not null )
353+ {
354+ AddCurrentBlock ( split . Value . First ) ;
355+ pending = split . Value . Second ;
356+ AdvanceColumnOrPage ( ) ;
357+ continue ;
358+ }
359+
360+ if ( currentColumnHasBlocks )
361+ {
362+ pending = block ;
363+ AdvanceColumnOrPage ( ) ;
364+ continue ;
365+ }
366+
367+ AddCurrentBlock ( block ) ;
368+ }
369+
370+ if ( currentPageBlocks . Count > 0 )
371+ {
372+ FinalizeCurrentPage ( ) ;
373+ }
374+
375+ return pages ;
376+
377+ void AddCurrentBlock ( LayoutBlock block )
378+ {
379+ var column = columns [ currentColumnIndex ] ;
380+ currentPageBlocks . Add ( block ) ;
381+ currentPlacements . Add ( new LayoutBlockPlacement (
382+ block ,
383+ column . XTwips ,
384+ contentTop + currentHeight ,
385+ column . WidthTwips ,
386+ currentColumnIndex ) ) ;
387+ currentHeight += block . HeightTwips ;
388+ currentColumnHasBlocks = true ;
389+ }
390+
391+ void AdvanceColumnOrPage ( )
392+ {
393+ if ( currentColumnIndex + 1 < columns . Count )
394+ {
395+ currentColumnIndex ++ ;
396+ currentHeight = 0f ;
397+ currentColumnHasBlocks = false ;
398+ return ;
399+ }
400+
401+ FinalizeCurrentPage ( ) ;
402+ }
403+
404+ void FinalizeCurrentPage ( )
405+ {
406+ pages . Add ( CreatePage (
407+ section ,
408+ pageNumber ,
409+ currentPageBlocks ,
410+ currentPlacements ,
411+ headerHeight ,
412+ footerHeight ,
413+ footnoteHeight ) ) ;
414+ pageNumber ++ ;
415+ currentPageBlocks = [ ] ;
416+ currentPlacements = [ ] ;
417+ currentColumnIndex = 0 ;
418+ currentHeight = 0f ;
419+ currentColumnHasBlocks = false ;
420+ }
421+ }
422+
296423 /// <summary>
297424 /// The default minimum number of lines for widow/orphan control.
298425 /// </summary>
@@ -463,11 +590,20 @@ internal static IReadOnlyList<LayoutBlock> CreateTableRowLayoutBlocks(
463590 private static LayoutPage CreatePage (
464591 SectionInfo section ,
465592 int pageNumber ,
466- List < LayoutBlock > blocks ) => new ( )
593+ List < LayoutBlock > blocks ,
594+ IReadOnlyList < LayoutBlockPlacement > ? blockPlacements = null ,
595+ float headerHeight = 0f ,
596+ float footerHeight = 0f ,
597+ float footnoteHeight = 0f ) => new ( )
467598 {
468599 Section = section ,
469600 PageNumber = pageNumber ,
470- Blocks = blocks . ToArray ( )
601+ Blocks = blocks . ToArray ( ) ,
602+ BlockPlacements = blockPlacements ? . ToArray ( ) ?? [ ] ,
603+ HeaderTopTwips = ComputeHeaderTop ( section ) ,
604+ ContentTopTwips = ComputeContentTop ( section , headerHeight ) ,
605+ FooterTopTwips = ComputeFooterTop ( section , footerHeight ) ,
606+ FootnoteTopTwips = ComputeFootnoteTop ( section , footerHeight , footnoteHeight )
471607 } ;
472608
473609 /// <summary>
@@ -576,9 +712,37 @@ private static int ApplySectionBreak(
576712 {
577713 Section = section ,
578714 PageNumber = pageNumber ,
579- Blocks = [ ]
715+ Blocks = [ ] ,
716+ HeaderTopTwips = ComputeHeaderTop ( section ) ,
717+ ContentTopTwips = ComputeContentTop ( section ) ,
718+ FooterTopTwips = ComputeFooterTop ( section ) ,
719+ FootnoteTopTwips = ComputeFootnoteTop ( section )
580720 } ;
581721
722+ private static IReadOnlyList < ColumnRegion > ComputeColumnRegions ( SectionInfo section )
723+ {
724+ var contentLeft = section . MarginLeft ;
725+ var contentWidth = MathF . Max ( 0f , section . PageWidth - section . MarginLeft - section . MarginRight ) ;
726+ var columnCount = Math . Max ( 1 , section . ColumnCount ) ;
727+ if ( columnCount == 1 )
728+ {
729+ return [ new ColumnRegion ( contentLeft , contentWidth ) ] ;
730+ }
731+
732+ var spacing = MathF . Max ( 0f , section . ColumnSpacingTwips ) ;
733+ var totalSpacing = spacing * ( columnCount - 1 ) ;
734+ var columnWidth = MathF . Max ( 0f , ( contentWidth - totalSpacing ) / columnCount ) ;
735+ var result = new ColumnRegion [ columnCount ] ;
736+ var currentX = ( float ) contentLeft ;
737+ for ( var i = 0 ; i < columnCount ; i ++ )
738+ {
739+ result [ i ] = new ColumnRegion ( currentX , columnWidth ) ;
740+ currentX += columnWidth + spacing ;
741+ }
742+
743+ return result ;
744+ }
745+
582746 /// <summary>
583747 /// Computes the available content height for body text, accounting for page dimensions,
584748 /// margins, header/footer content heights, and footnote space.
@@ -649,4 +813,6 @@ internal static float ComputeFootnoteTop(SectionInfo section, float footerHeight
649813
650814 private static float ComputeEffectiveBottomMargin ( SectionInfo section , float footerHeight )
651815 => Math . Max ( section . MarginBottom , section . MarginFooter + footerHeight ) ;
816+
817+ private readonly record struct ColumnRegion ( float XTwips , float WidthTwips ) ;
652818}
0 commit comments