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
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,62 @@ final class CompositeDecoration {
private CompositeDecoration() {
}

/**
* Resolves the decoration (fill / border) box for a composite. Without bleed
* the box is the in-margin content geometry; each declared bleed edge instead
* snaps that side of the box to the trimmed page edge (left/right adjust the x
* span, top/bottom the y span) while the children stay in the content region.
*
* @param bleed the node's bleed edges
* @param x content-box left (in-margin)
* @param width content-box width (in-margin)
* @param topY content-box top y (in-margin)
* @param bottomY content-box bottom y (in-margin)
* @param canvas the page canvas, for the trimmed page-edge coordinates
* @return the resolved decoration box
*/
static BleedBox resolveBleedBox(DocumentBleed bleed,
double x,
double width,
double topY,
double bottomY,
LayoutCanvas canvas) {
double decorX = x;
double decorWidth = width;
double decorTopY = topY;
double decorBottomY = bottomY;
if (bleed.any()) {
double pageWidth = canvas.width();
double pageHeight = canvas.height();
if (bleed.bleeds(DocumentEdge.LEFT)) {
decorWidth += decorX;
decorX = 0.0;
}
if (bleed.bleeds(DocumentEdge.RIGHT)) {
decorWidth = Math.max(0.0, pageWidth - decorX);
}
if (bleed.bleeds(DocumentEdge.TOP)) {
decorTopY = pageHeight;
}
if (bleed.bleeds(DocumentEdge.BOTTOM)) {
decorBottomY = 0.0;
}
}
return new BleedBox(decorX, decorWidth, decorTopY, decorBottomY);
}

/**
* A resolved composite decoration box — the fill / border extent after content
* bleed, in page space.
*
* @param x decoration box left
* @param width decoration box width
* @param topY decoration box top y
* @param bottomY decoration box bottom y
*/
record BleedBox(double x, double width, double topY, double bottomY) {
}

static List<PlacedFragment> fill(PreparedNode<DocumentNode> prepared,
NodeDefinition<DocumentNode> definition,
String path,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
import com.demcha.compose.document.node.RowNode;
import com.demcha.compose.document.node.RowVerticalAlign;
import com.demcha.compose.document.style.DocumentBleed;
import com.demcha.compose.document.style.DocumentEdge;
import com.demcha.compose.engine.components.style.Margin;
import com.demcha.compose.engine.components.style.Padding;
import org.slf4j.Logger;
Expand Down Expand Up @@ -320,43 +319,23 @@ private void compileComposite(PreparedNode<DocumentNode> prepared,
int endPage = state.pageIndex;
double endPageBottomY = state.pageTop() - state.usedHeight + margin.bottom();

// Content bleed: the decoration box (fill/border) extends to the trimmed
// page edge on the declared edges, while children stay in the content
// region (so text never runs off the page). Byte-identical when the node
// does not bleed — every value below collapses to the in-margin geometry.
// Content bleed extends the decoration box to the trimmed page edge on the
// declared edges while children stay in the content region; without bleed the
// box is the in-margin geometry (see CompositeDecoration#resolveBleedBox).
DocumentBleed bleed = node.bleed();
double decorX = placementX;
double decorWidth = naturalMeasure.width();
double decorTopY = placementTopY;
double decorBottomY = endPageBottomY;
if (bleed.any()) {
double pageWidth = state.canvas.width();
double pageHeight = state.canvas.height();
if (bleed.bleeds(DocumentEdge.LEFT)) {
decorWidth += decorX;
decorX = 0.0;
}
if (bleed.bleeds(DocumentEdge.RIGHT)) {
decorWidth = Math.max(0.0, pageWidth - decorX);
}
if (bleed.bleeds(DocumentEdge.TOP)) {
decorTopY = pageHeight;
}
if (bleed.bleeds(DocumentEdge.BOTTOM)) {
decorBottomY = 0.0;
}
}
CompositeDecoration.BleedBox decor = CompositeDecoration.resolveBleedBox(
bleed, placementX, naturalMeasure.width(), placementTopY, endPageBottomY, state.canvas);
List<PlacedFragment> decorationFragments = CompositeDecoration.fill(
prepared,
definition,
path,
parentPath,
childIndex,
depth,
decorX,
decorTopY,
decorBottomY,
decorWidth,
decor.x(),
decor.topY(),
decor.bottomY(),
decor.width(),
startPage,
endPage,
margin,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -174,4 +174,56 @@ void toPlacedFragmentsReindexesFragmentIndexPerPlacement() {
assertThat(f.pageIndex()).isEqualTo(3);
});
}

// resolveBleedBox: content box x=20 w=100, top=80 bottom=30, on the 200×100 canvas.

@Test
void resolveBleedBoxWithoutBleedReturnsTheInMarginBox() {
CompositeDecoration.BleedBox box = CompositeDecoration.resolveBleedBox(
DocumentBleed.none(), 20, 100, 80, 30, canvas);
assertThat(box.x()).isCloseTo(20, within(EPS));
assertThat(box.width()).isCloseTo(100, within(EPS));
assertThat(box.topY()).isCloseTo(80, within(EPS));
assertThat(box.bottomY()).isCloseTo(30, within(EPS));
}

@Test
void resolveBleedBoxLeftSnapsOriginToZeroAndAbsorbsTheLeftGap() {
CompositeDecoration.BleedBox box = CompositeDecoration.resolveBleedBox(
DocumentBleed.of(DocumentEdge.LEFT), 20, 100, 80, 30, canvas);
assertThat(box.x()).isCloseTo(0, within(EPS));
assertThat(box.width()).isCloseTo(120, within(EPS)); // 100 + 20
assertThat(box.topY()).isCloseTo(80, within(EPS));
assertThat(box.bottomY()).isCloseTo(30, within(EPS));
}

@Test
void resolveBleedBoxRightStretchesWidthToThePageEdge() {
CompositeDecoration.BleedBox box = CompositeDecoration.resolveBleedBox(
DocumentBleed.of(DocumentEdge.RIGHT), 20, 100, 80, 30, canvas);
assertThat(box.x()).isCloseTo(20, within(EPS));
assertThat(box.width()).isCloseTo(180, within(EPS)); // pageWidth 200 - x 20
assertThat(box.topY()).isCloseTo(80, within(EPS));
assertThat(box.bottomY()).isCloseTo(30, within(EPS));
}

@Test
void resolveBleedBoxTopAndBottomSnapToThePageEdges() {
CompositeDecoration.BleedBox box = CompositeDecoration.resolveBleedBox(
DocumentBleed.of(DocumentEdge.TOP, DocumentEdge.BOTTOM), 20, 100, 80, 30, canvas);
assertThat(box.x()).isCloseTo(20, within(EPS));
assertThat(box.width()).isCloseTo(100, within(EPS));
assertThat(box.topY()).isCloseTo(100, within(EPS)); // pageHeight
assertThat(box.bottomY()).isCloseTo(0, within(EPS));
}

@Test
void resolveBleedBoxAllSnapsToTheFullPage() {
CompositeDecoration.BleedBox box = CompositeDecoration.resolveBleedBox(
DocumentBleed.all(), 20, 100, 80, 30, canvas);
assertThat(box.x()).isCloseTo(0, within(EPS));
assertThat(box.width()).isCloseTo(200, within(EPS)); // full page width
assertThat(box.topY()).isCloseTo(100, within(EPS));
assertThat(box.bottomY()).isCloseTo(0, within(EPS));
}
}
Loading