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
@@ -0,0 +1,27 @@
package com.demcha.compose.document.layout;

import java.util.List;

/**
* The cursor + emission context threaded through the compile pass: the page
* cursor, the node-preparation and fragment-emission contexts, and the output
* accumulators. Bundled so the per-node compile collaborators take one context
* argument instead of repeating the same five parameters.
*
* <p>Package-private — engine surface, not public API. The accumulators are the
* caller's live lists (appended to, not copied); the {@code state} cursor is
* mutated as pages advance.</p>
*
* @param state the compiler page cursor (mutated as pages advance)
* @param prepareContext the node-preparation context
* @param fragmentContext the fragment-emission context
* @param nodes accumulator for placed nodes (appended to)
* @param fragments accumulator for placed fragments (appended to)
* @author Artem Demchyshyn
*/
record CompileContext(CompilerState state,
PrepareContext prepareContext,
FragmentContext fragmentContext,
List<PlacedNode> nodes,
List<PlacedFragment> fragments) {
}
170 changes: 18 additions & 152 deletions src/main/java/com/demcha/compose/document/layout/LayoutCompiler.java
Original file line number Diff line number Diff line change
Expand Up @@ -246,9 +246,9 @@ private void compileComposite(PreparedNode<DocumentNode> prepared,
}

if (layoutSpec.axis() == CompositeLayoutSpec.Axis.STACK) {
compileStackedLayer(prepared, definition, path, semanticName, parentPath, childIndex, depth,
regionX, state, prepareContext, fragmentContext, nodes, fragments,
margin, padding, availableWidth, naturalMeasure);
StackedLayerCompiler.compile(this, prepared, definition, path, semanticName, parentPath, childIndex,
depth, regionX, margin, padding, naturalMeasure,
new CompileContext(state, prepareContext, fragmentContext, nodes, fragments));
return;
}

Expand Down Expand Up @@ -562,140 +562,6 @@ private void compileHorizontalRow(PreparedNode<DocumentNode> prepared,
state.usedHeight += rowOuterHeight;
}

private void compileStackedLayer(PreparedNode<DocumentNode> prepared,
NodeDefinition<DocumentNode> definition,
String path,
String semanticName,
String parentPath,
int childIndex,
int depth,
double regionX,
CompilerState state,
PrepareContext prepareContext,
FragmentContext fragmentContext,
List<PlacedNode> nodes,
List<PlacedFragment> fragments,
Margin margin,
Padding padding,
double availableWidth,
MeasureResult naturalMeasure) {
DocumentNode node = prepared.node();
double stackOuterHeight = naturalMeasure.height() + margin.vertical();
admitAtomicBlock(stackOuterHeight, path, state);

int startPage = state.pageIndex;
double placementX = regionX + margin.left();
double placementTopY = state.pageTop() - state.usedHeight - margin.top();
double placementY = placementTopY - naturalMeasure.height();
int decorationInsertIndex = fragments.size();
int stackNodeIndex = nodes.size();
nodes.add(null);

PreparedStackLayout stackLayout =
prepared.requirePreparedLayout(PreparedStackLayout.class);
List<DocumentNode> children = definition.children(node);
double innerWidth = Math.max(0.0, naturalMeasure.width() - padding.horizontal());
double innerHeight = Math.max(0.0, naturalMeasure.height() - padding.vertical());
double innerStartX = placementX + padding.left();
double innerTopY = placementTopY - padding.top();

// Sort layers by ascending zIndex (stable — equal zIndex keeps
// source order). Iteration order then determines render order:
// earlier in the list → drawn first / behind. childIndex below
// is the SOURCE index so semantic paths stay stable for tests
// and snapshots; only the iteration order shifts.
int[] iterationOrder = LayerStackGeometry.zOrder(stackLayout.zIndices());
PlacementContext layerHostCtx = new FixedSlotPlacementContext(
state.pageIndex, state.canvas, prepareContext, fragmentContext, nodes, fragments);
for (int slot = 0; slot < iterationOrder.length; slot++) {
int index = iterationOrder[slot];
placeStackLayer(
children.get(index),
index,
path,
depth,
innerStartX,
innerTopY,
innerWidth,
innerHeight,
stackLayout.alignments().get(index),
stackLayout.offsetsX().get(index),
stackLayout.offsetsY().get(index),
layerHostCtx);
}

int endPage = state.pageIndex;
double endPageBottomY = placementTopY - naturalMeasure.height() + margin.bottom();
List<PlacedFragment> decorationFragments = CompositeDecoration.fill(
prepared,
definition,
path,
parentPath,
childIndex,
depth,
placementX,
placementTopY,
endPageBottomY,
naturalMeasure.width(),
startPage,
endPage,
margin,
padding,
state.canvas,
DocumentBleed.none(),
fragmentContext);
if (!decorationFragments.isEmpty()) {
fragments.addAll(decorationInsertIndex, decorationFragments);
}

// Overlay fragments arrive AFTER children — they are the
// "after the body" half of paired begin/end markers (e.g. the
// graphics-state restore of a ShapeContainerNode clip path).
List<PlacedFragment> overlayFragments = CompositeDecoration.overlay(
prepared,
definition,
path,
parentPath,
childIndex,
depth,
placementX,
placementTopY,
endPageBottomY,
naturalMeasure.width(),
startPage,
endPage,
margin,
padding,
state.canvas,
fragmentContext);
if (!overlayFragments.isEmpty()) {
fragments.addAll(overlayFragments);
}

nodes.set(stackNodeIndex, new PlacedNode(
path,
semanticName,
node.nodeKind(),
parentPath,
childIndex,
depth,
depth,
placementX,
placementY,
placementX,
placementY,
naturalMeasure.width(),
naturalMeasure.height(),
startPage,
endPage,
naturalMeasure.width(),
naturalMeasure.height(),
margin,
padding));

state.usedHeight += stackOuterHeight;
}

private void compileAtomicLeaf(PreparedNode<DocumentNode> prepared,
NodeDefinition<DocumentNode> definition,
String path,
Expand Down Expand Up @@ -791,7 +657,7 @@ private void placeAtomicLeafFragments(PreparedNode<DocumentNode> prepared,

/**
* Places a single layer inside a stack composite at the given inner-box
* coordinates. Shared between {@link #compileStackedLayer} (mutating
* coordinates. Shared between {@link StackedLayerCompiler} (mutating
* placement path) and the STACK branch of {@link #compileNodeInFixedSlot}
* (non-mutating placement path). Both feed per-layer offsets from
* {@link PreparedStackLayout}, so a {@code position(node, dx, dy, align)}
Expand All @@ -802,18 +668,18 @@ private void placeAtomicLeafFragments(PreparedNode<DocumentNode> prepared,
* because each layer occupies a single fixed page slot whose origin
* has already been resolved by the alignment + offset math here.</p>
*/
private void placeStackLayer(DocumentNode child,
int sourceIndex,
String parentPath,
int parentDepth,
double innerStartX,
double innerTopY,
double innerWidth,
double innerHeight,
com.demcha.compose.document.node.LayerAlign align,
double layerOffsetX,
double layerOffsetY,
PlacementContext ctx) {
void placeStackLayer(DocumentNode child,
int sourceIndex,
String parentPath,
int parentDepth,
double innerStartX,
double innerTopY,
double innerWidth,
double innerHeight,
com.demcha.compose.document.node.LayerAlign align,
double layerOffsetX,
double layerOffsetY,
PlacementContext ctx) {
PreparedNode<DocumentNode> childPrepared =
prepareForRegionWidth(ctx.prepareContext(), child, innerWidth);
MeasureResult childMeasure = childPrepared.measureResult();
Expand Down Expand Up @@ -944,7 +810,7 @@ private double compileNodeInFixedSlot(PreparedNode<DocumentNode> prepared,
double stackInnerStartX = placementX + padding.left();
double stackInnerTopY = placementTopY - padding.top();

// Same z-index iteration order as compileStackedLayer
// Same z-index iteration order as StackedLayerCompiler
// (root-level case). Source-order semantic paths are
// preserved — only render order shifts.
int[] iterationOrder = LayerStackGeometry.zOrder(stackLayout.zIndices());
Expand Down Expand Up @@ -1158,7 +1024,7 @@ private String semanticName(DocumentNode node) {
* @param state the compiler cursor (mutated: may advance to a new page)
* @throws AtomicNodeTooLargeException if the block cannot fit on a full page
*/
private void admitAtomicBlock(double outerHeight, String path, CompilerState state) {
void admitAtomicBlock(double outerHeight, String path, CompilerState state) {
double fullPageHeight = state.activeInnerHeight();
if (outerHeight > fullPageHeight + CAPACITY_TOLERANCE) {
throw AtomicNodeTooLargeException.forNode(path, outerHeight, fullPageHeight);
Expand Down
Loading
Loading