Skip to content

Commit 0381bc5

Browse files
committed
Implement step 7.4.5: Unequal column widths
1 parent 821004a commit 0381bc5

4 files changed

Lines changed: 134 additions & 9 deletions

File tree

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
namespace PanoramicData.Render.Test;
2+
3+
using AwesomeAssertions;
4+
using DocumentFormat.OpenXml.Wordprocessing;
5+
using Xunit;
6+
7+
public sealed class UnequalColumnWidthTests
8+
{
9+
[Fact]
10+
public void Paginate_UsesExplicitUnequalColumnWidthsForPlacements()
11+
{
12+
var section = new SectionInfo
13+
{
14+
ColumnCount = 2,
15+
ColumnsEqualWidth = false,
16+
Columns =
17+
[
18+
new SectionColumnDefinition(3000, 360),
19+
new SectionColumnDefinition(6000, 0)
20+
]
21+
};
22+
var blocks = new[]
23+
{
24+
MakeBlock(12000f),
25+
MakeBlock(1000f),
26+
};
27+
28+
var result = PageBuilder.Paginate(blocks, section);
29+
30+
result.Should().ContainSingle();
31+
result[0].BlockPlacements.Should().HaveCount(2);
32+
result[0].BlockPlacements[0].ColumnIndex.Should().Be(0);
33+
result[0].BlockPlacements[0].XTwips.Should().Be(1440f);
34+
result[0].BlockPlacements[0].ContentWidthTwips.Should().Be(3000f);
35+
result[0].BlockPlacements[1].ColumnIndex.Should().Be(1);
36+
result[0].BlockPlacements[1].XTwips.Should().Be(4800f);
37+
result[0].BlockPlacements[1].ContentWidthTwips.Should().Be(6000f);
38+
}
39+
40+
[Fact]
41+
public void Paginate_UsesRemainingWidthForUnspecifiedExplicitColumns()
42+
{
43+
var section = new SectionInfo
44+
{
45+
ColumnCount = 3,
46+
ColumnsEqualWidth = false,
47+
ColumnSpacingTwips = 240,
48+
Columns =
49+
[
50+
new SectionColumnDefinition(2000, 360),
51+
new SectionColumnDefinition(0, 240)
52+
]
53+
};
54+
var blocks = new[]
55+
{
56+
MakeBlock(12000f),
57+
MakeBlock(12000f),
58+
MakeBlock(1000f),
59+
};
60+
61+
var result = PageBuilder.Paginate(blocks, section);
62+
63+
result.Should().ContainSingle();
64+
result[0].BlockPlacements.Should().HaveCount(3);
65+
result[0].BlockPlacements[0].ContentWidthTwips.Should().Be(2000f);
66+
result[0].BlockPlacements[1].ContentWidthTwips.Should().Be(3380f);
67+
result[0].BlockPlacements[2].ContentWidthTwips.Should().Be(3380f);
68+
result[0].BlockPlacements[1].XTwips.Should().Be(3800f);
69+
result[0].BlockPlacements[2].XTwips.Should().Be(7420f);
70+
}
71+
72+
private static LayoutBlock MakeBlock(float heightTwips)
73+
{
74+
var para = new ParagraphBlock { SourceElement = new Paragraph() };
75+
return new LayoutBlock(para, heightTwips);
76+
}
77+
}

PanoramicData.Render/PageBuilder.cs

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -873,12 +873,60 @@ private static IReadOnlyList<ColumnRegion> ComputeColumnRegions(SectionInfo sect
873873
{
874874
var contentLeft = section.MarginLeft;
875875
var contentWidth = MathF.Max(0f, section.PageWidth - section.MarginLeft - section.MarginRight);
876-
var columnCount = Math.Max(1, section.ColumnCount);
876+
var columnCount = Math.Max(1, Math.Max(section.ColumnCount, section.Columns.Count));
877877
if (columnCount == 1)
878878
{
879879
return [new ColumnRegion(contentLeft, contentWidth)];
880880
}
881881

882+
if (!section.ColumnsEqualWidth && section.Columns.Count > 0)
883+
{
884+
var totalFixedWidth = 0f;
885+
var totalExplicitSpacing = 0f;
886+
var unresolvedColumnCount = 0;
887+
888+
for (var i = 0; i < columnCount; i++)
889+
{
890+
var definition = i < section.Columns.Count ? section.Columns[i] : default;
891+
if (definition.WidthTwips > 0)
892+
{
893+
totalFixedWidth += definition.WidthTwips;
894+
}
895+
else
896+
{
897+
unresolvedColumnCount++;
898+
}
899+
900+
if (i < columnCount - 1)
901+
{
902+
totalExplicitSpacing += i < section.Columns.Count
903+
? MathF.Max(0f, definition.SpaceAfterTwips)
904+
: MathF.Max(0f, section.ColumnSpacingTwips);
905+
}
906+
}
907+
908+
var remainingWidth = MathF.Max(0f, contentWidth - totalFixedWidth - totalExplicitSpacing);
909+
var fallbackWidth = unresolvedColumnCount > 0 ? remainingWidth / unresolvedColumnCount : 0f;
910+
var explicitResult = new ColumnRegion[columnCount];
911+
var explicitX = (float)contentLeft;
912+
913+
for (var i = 0; i < columnCount; i++)
914+
{
915+
var definition = i < section.Columns.Count ? section.Columns[i] : default;
916+
var width = definition.WidthTwips > 0 ? definition.WidthTwips : fallbackWidth;
917+
explicitResult[i] = new ColumnRegion(explicitX, MathF.Max(0f, width));
918+
if (i < columnCount - 1)
919+
{
920+
var spacingAfter = i < section.Columns.Count
921+
? MathF.Max(0f, definition.SpaceAfterTwips)
922+
: MathF.Max(0f, section.ColumnSpacingTwips);
923+
explicitX += MathF.Max(0f, width) + spacingAfter;
924+
}
925+
}
926+
927+
return explicitResult;
928+
}
929+
882930
var spacing = MathF.Max(0f, section.ColumnSpacingTwips);
883931
var totalSpacing = spacing * (columnCount - 1);
884932
var columnWidth = MathF.Max(0f, (contentWidth - totalSpacing) / columnCount);

docs/plans/current-status.md

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,18 +10,18 @@ Phase 7: Advanced Features — **IN PROGRESS**
1010

1111
## Current Step
1212

13-
Step 7.4.4balanced columns**COMPLETE**
13+
Step 7.4.5unequal column widths**COMPLETE**
1414

15-
Completed balanced-column support for the last page of a section:
16-
- Added a final-page rebalance pass in `PageBuilder` that redistributes remaining multi-column content using existing line-splitting support
17-
- Balanced splittable content across columns on the last page while leaving earlier pages greedily filled
18-
- Preserved explicit column-break behavior by skipping last-page balancing when a block carries an explicit column-break directive
15+
Completed unequal-width column support:
16+
- Updated `PageBuilder.ComputeColumnRegions` to honor parsed explicit column widths and per-column spacing when a section uses non-equal columns
17+
- Distributed remaining content width across unspecified explicit columns as a fallback so partially specified column sets still paginate sensibly
18+
- Added focused placement coverage for fully explicit unequal columns and mixed explicit-plus-fallback column widths
1919

20-
1785 tests passing (17821785, +3 balanced-column tests).
20+
1787 tests passing (17851787, +2 unequal-width column tests).
2121

2222
## Next Step
2323

24-
Step 7.4.5handle unequal column widths
24+
Step 7.4.6integrate column layout with floating objects and wrapping within a column
2525

2626
## Last Commit
2727

docs/plans/phase-7-advanced-features.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ Implement the remaining document features needed for high-fidelity rendering of
4949
- [x] 7.4.2 — Implement column flow: text fills the first column, then overflows to the next
5050
- [x] 7.4.3 — Handle column breaks (`<w:br w:type="column"/>`)
5151
- [x] 7.4.4 — Handle balanced columns (distribute content evenly, typically on the last page of a section)
52-
- [ ] 7.4.5 — Handle unequal column widths
52+
- [x] 7.4.5 — Handle unequal column widths
5353
- [ ] 7.4.6 — Integrate column layout with floating objects: wrapping within a column
5454
- [ ] 7.4.7 — Unit tests: verify column flow and break positions
5555

0 commit comments

Comments
 (0)