Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,26 @@ follow semantic versioning; release dates are ISO 8601.
(`y = (1 - yRatio - heightRatio) * pageHeight`); full-page and
full-height column fills are unchanged. Adds top-/bottom-/mid-band
regression tests.
- **`GraphCompose.document().pageBackgrounds(emptyList())` now actually
clears.** The builder's Javadoc promised that an explicit empty list
overrides any earlier `pageBackground(color)` on the same builder, but
the implementation skipped empty lists, so `pageBackground(LIGHT_GRAY)`
followed by `pageBackgrounds(List.of())` still emitted the grey
background. The guard is removed; the empty list is now the documented
clear. Adds a regression test.
- **`distributeRowSlotWidths` weights / children mismatch.** When a row
was constructed with a `weights` list whose size did not match the
number of children (only reachable by bypassing `RowBuilder` and
building a `RowNode` directly), the engine's row distribution code
walked off the end of the `weights` list with a raw
`IndexOutOfBoundsException`. Both row-distribution call sites
(`LayoutCompiler#distributeRowSlotWidths`, `NodeDefinitionSupport#measureRow`)
now reject the mismatch with an `IllegalArgumentException` whose
message names both sizes and the expected fix. `RowNode`'s canonical
constructor already validated this at construction time; the new
engine guards are defence-in-depth for any path that bypasses it
(e.g. reflection-based deserialization). Adds regression tests for
the canonical-constructor IAE and the `RowBuilder.build()` ISE.

### Build

Expand Down
4 changes: 1 addition & 3 deletions src/main/java/com/demcha/compose/GraphCompose.java
Original file line number Diff line number Diff line change
Expand Up @@ -384,9 +384,7 @@ public DocumentSession create() {
// Explicit pageBackgrounds() call wins — even an empty
// list is an intentional clear that should override any
// earlier pageBackground(color) on the same builder.
if (!pageBackgrounds.isEmpty()) {
session.pageBackgrounds(pageBackgrounds);
}
session.pageBackgrounds(pageBackgrounds);
} else if (pageBackground != null) {
session.pageBackground(pageBackground);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -746,6 +746,11 @@ private static double[] distributeRowSlotWidths(List<DocumentNode> children,
}
return slots;
}
if (weights.size() != n) {
throw new IllegalArgumentException(
"Row weights size (" + weights.size() + ") must match children size (" + n
+ "). Pass exactly " + n + " weight(s) or leave weights empty for an even split.");
}
double total = 0.0;
for (double w : weights) {
total += w;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,11 @@ public static MeasureResult measureRow(RowNode node,
slotWidths[i] = share;
}
} else {
if (node.weights().size() != n) {
throw new IllegalArgumentException(
"Row weights size (" + node.weights().size() + ") must match children size (" + n
+ "). Pass exactly " + n + " weight(s) or leave weights empty for an even split.");
}
double total = 0.0;
for (Double w : node.weights()) {
total += w;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -386,6 +386,29 @@ void topBandAndBottomBandHelpersRenderAtCorrectEdges() {
}
}

@Test
void pageBackgroundsWithEmptyListClearsEarlierPageBackgroundColor() {
// Calling pageBackgrounds(emptyList()) on the builder must override
// an earlier pageBackground(color) on the same builder. The empty
// list is an intentional clear, not a "leave the earlier value
// unchanged" signal — the Javadoc on the builder promises that.
try (DocumentSession session = GraphCompose.document()
.pageSize(400, 300)
.margin(DocumentInsets.of(20))
.pageBackground(DocumentColor.of(Color.LIGHT_GRAY))
.pageBackgrounds(List.of())
.create()) {

session.add(new SpacerNode("Block", 200, 80,
DocumentInsets.zero(), DocumentInsets.zero()));
LayoutGraph graph = session.layoutGraph();

assertThat(graph.fragments()).noneMatch(this::isPageBackgroundFragment);
} catch (Exception e) {
throw new RuntimeException(e);
}
}

private boolean isPageBackgroundFragment(PlacedFragment fragment) {
return fragment.payload() instanceof ShapeFragmentPayload payload
&& payload.fillColor() != null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -382,6 +382,46 @@ void spacingAndDeprecatedGapProduceTheSameRowNode() {
assertThat(viaSpacing.gap()).isEqualTo(viaGap.gap());
}

@Test
void rowBuilderWeightsMismatchAtBuildFailsWithDescriptiveMessage() {
// Two weights paired with three children must be rejected at the
// build() call rather than turning into an IOOBE downstream.
assertThatThrownBy(() -> new RowBuilder()
.name("Row")
.weights(1.0, 2.0)
.addParagraph(p -> p.name("A").text("A").textStyle(DocumentTextStyle.DEFAULT))
.addParagraph(p -> p.name("B").text("B").textStyle(DocumentTextStyle.DEFAULT))
.addParagraph(p -> p.name("C").text("C").textStyle(DocumentTextStyle.DEFAULT))
.build())
.isInstanceOf(IllegalStateException.class)
.hasMessageContaining("weights")
.hasMessageContaining("children");
}

@Test
void rowNodeConstructorRejectsWeightsThatDoNotMatchChildren() {
// Bypassing RowBuilder.validate() by constructing RowNode directly
// must still surface a domain-level IllegalArgumentException — no
// raw IndexOutOfBoundsException from the engine's distribution code.
SpacerNode a = new SpacerNode("A", 10, 10, DocumentInsets.zero(), DocumentInsets.zero());
SpacerNode b = new SpacerNode("B", 10, 10, DocumentInsets.zero(), DocumentInsets.zero());

assertThatThrownBy(() -> new RowNode(
"Row",
List.of(a, b),
List.of(1.0, 2.0, 3.0),
0.0,
DocumentInsets.zero(),
DocumentInsets.zero(),
null,
null,
null,
null))
.isInstanceOf(IllegalArgumentException.class)
.hasMessageContaining("weights size")
.hasMessageContaining("children size");
}

private static PlacedNode findNodeBySemanticName(List<PlacedNode> nodes, String name) {
for (PlacedNode node : nodes) {
if (name.equals(node.semanticName())) {
Expand Down