rowEntities
-) implements Component {
- public TableLayoutData {
- Objects.requireNonNull(columnWidths, "columnWidths");
- Objects.requireNonNull(rowEntities, "rowEntities");
- columnWidths = List.copyOf(columnWidths);
- rowEntities = List.copyOf(rowEntities);
- }
-}
diff --git a/src/main/java/com/demcha/compose/engine/components/content/table/TableResolvedCell.java b/src/main/java/com/demcha/compose/engine/components/content/table/TableResolvedCell.java
index 1de16a0a5..2694b46a5 100644
--- a/src/main/java/com/demcha/compose/engine/components/content/table/TableResolvedCell.java
+++ b/src/main/java/com/demcha/compose/engine/components/content/table/TableResolvedCell.java
@@ -9,7 +9,7 @@
import java.util.Set;
/**
- * Fully materialized cell description rendered by {@link com.demcha.compose.engine.components.renderable.TableRow}.
+ * Fully materialized cell description consumed by the table row renderer.
*
* {@code yOffset} shifts the cell's bottom edge relative to the row
* fragment's bottom (in PDF coordinates where y grows up). Single-row
diff --git a/src/main/java/com/demcha/compose/engine/components/content/table/TableRowData.java b/src/main/java/com/demcha/compose/engine/components/content/table/TableRowData.java
deleted file mode 100644
index cf90d0e09..000000000
--- a/src/main/java/com/demcha/compose/engine/components/content/table/TableRowData.java
+++ /dev/null
@@ -1,16 +0,0 @@
-package com.demcha.compose.engine.components.content.table;
-
-import com.demcha.compose.engine.components.core.Component;
-
-import java.util.List;
-import java.util.Objects;
-
-/**
- * Resolved row payload rendered atomically as a single leaf entity.
- */
-public record TableRowData(List cells) implements Component {
- public TableRowData {
- Objects.requireNonNull(cells, "cells");
- cells = List.copyOf(cells);
- }
-}
diff --git a/src/main/java/com/demcha/compose/engine/components/content/text/BlockTextLineMetrics.java b/src/main/java/com/demcha/compose/engine/components/content/text/BlockTextLineMetrics.java
deleted file mode 100644
index 3db130ed9..000000000
--- a/src/main/java/com/demcha/compose/engine/components/content/text/BlockTextLineMetrics.java
+++ /dev/null
@@ -1,149 +0,0 @@
-package com.demcha.compose.engine.components.content.text;
-
-import com.demcha.compose.engine.core.EntityManager;
-import com.demcha.compose.engine.measurement.TextMeasurementSystem;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Resolves per-line vertical metrics for block text.
- *
- * The helper prefers line-local cached metrics when they are already present
- * and only falls back to the active {@link TextMeasurementSystem} for
- * uncached/manual lines. That keeps layout and page-breaking logic backend-
- * neutral while avoiding repeated measurement work during ordinary block-text
- * processing.
- */
-public final class BlockTextLineMetrics {
-
- private BlockTextLineMetrics() {
- }
-
- /**
- * Resolves line metrics from a single {@link TextStyle} using the active
- * measurement system.
- *
- * @param entityManager entity manager providing the measurement system
- * @param style text style to measure, defaults to {@link TextStyle#DEFAULT_STYLE}
- * @return resolved line metrics (ascent, descent, leading)
- */
- public static TextMeasurementSystem.LineMetrics resolveStyleMetrics(EntityManager entityManager, TextStyle style) {
- TextStyle safeStyle = style == null ? TextStyle.DEFAULT_STYLE : style;
- return measurementSystem(entityManager).lineMetrics(safeStyle);
- }
-
- /**
- * Resolves line metrics for a single line, preferring cached values when present.
- *
- * @param entityManager entity manager providing the measurement system
- * @param line line to resolve metrics for, may be {@code null}
- * @param fallbackStyle style to fall back to when the line or its bodies lack style info
- * @return resolved line metrics
- */
- public static TextMeasurementSystem.LineMetrics resolveLineMetrics(EntityManager entityManager,
- LineTextData line,
- TextStyle fallbackStyle) {
- if (line != null && line.hasCachedLineMetrics()) {
- return line.lineMetrics();
- }
-
- if (line == null) {
- return resolveStyleMetrics(entityManager, fallbackStyle);
- }
-
- return resolveBodiesMetrics(entityManager, line.bodies(), fallbackStyle);
- }
-
- /**
- * Resolves mixed-style line metrics from a list of text bodies by taking the
- * maximum ascent, descent, and leading across all non-null body styles.
- *
- * This method always measures from scratch and does not use any cache.
- * It is called by paragraph preparation and other text materialization
- * paths before pagination/rendering consume the resolved line payload.
- * at line-creation time to populate the per-line cache.
- *
- * @param entityManager entity manager providing the measurement system
- * @param bodies text bodies to measure
- * @param fallbackStyle style to use when a body has no style
- * @return resolved line metrics
- */
- public static TextMeasurementSystem.LineMetrics resolveBodiesMetrics(EntityManager entityManager,
- List bodies,
- TextStyle fallbackStyle) {
- if (bodies == null || bodies.isEmpty()) {
- return resolveStyleMetrics(entityManager, fallbackStyle);
- }
-
- TextMeasurementSystem measurementSystem = measurementSystem(entityManager);
- double ascent = 0.0;
- double descent = 0.0;
- double leading = 0.0;
- boolean hasMetrics = false;
-
- for (TextDataBody body : bodies) {
- if (body == null) {
- continue;
- }
-
- TextStyle style = body.textStyle() == null ? fallbackStyle : body.textStyle();
- if (style == null) {
- continue;
- }
-
- TextMeasurementSystem.LineMetrics metrics = measurementSystem.lineMetrics(style);
- ascent = Math.max(ascent, metrics.ascent());
- descent = Math.max(descent, metrics.descent());
- leading = Math.max(leading, metrics.leading());
- hasMetrics = true;
- }
-
- if (!hasMetrics) {
- return resolveStyleMetrics(entityManager, fallbackStyle);
- }
-
- return new TextMeasurementSystem.LineMetrics(ascent, descent, leading);
- }
-
- /**
- * Batch-resolves line metrics for a list of lines.
- *
- * @param entityManager entity manager providing the measurement system
- * @param lines lines to measure
- * @param fallbackStyle style to fall back to for lines without cached metrics
- * @return list of metrics in the same order as the input lines
- */
- public static List resolveLineMetrics(EntityManager entityManager,
- List lines,
- TextStyle fallbackStyle) {
- List result = new ArrayList<>(lines.size());
- for (LineTextData line : lines) {
- result.add(resolveLineMetrics(entityManager, line, fallbackStyle));
- }
- return result;
- }
-
- /**
- * Computes the vertical gap between two adjacent lines, taking into account
- * outer gap contributions from both lines relative to the base metrics.
- *
- * @param previous metrics of the line above
- * @param next metrics of the line below
- * @param base baseline metrics (usually from the block's default style)
- * @param spacing additional line spacing configured on the block
- * @return total inter-line gap in layout units
- */
- public static double interLineGap(TextMeasurementSystem.LineMetrics previous,
- TextMeasurementSystem.LineMetrics next,
- TextMeasurementSystem.LineMetrics base,
- double spacing) {
- return spacing + previous.outerGap(base) / 2.0 + next.outerGap(base) / 2.0;
- }
-
- private static TextMeasurementSystem measurementSystem(EntityManager entityManager) {
- return entityManager.getSystems()
- .textMeasurement()
- .orElseThrow(() -> new IllegalStateException("TextMeasurementSystem is required to resolve block text metrics."));
- }
-}
diff --git a/src/main/java/com/demcha/compose/engine/components/core/Units.java b/src/main/java/com/demcha/compose/engine/components/core/Units.java
deleted file mode 100644
index cd6e16ec3..000000000
--- a/src/main/java/com/demcha/compose/engine/components/core/Units.java
+++ /dev/null
@@ -1,36 +0,0 @@
-package com.demcha.compose.engine.components.core;
-
-/**
- * Represents supported measurement units for layout and rendering.
- */
-public enum Units {
-
- /**
- * Pixels — device-dependent units.
- * OuterBoxSize changes with screen DPI.
- */
- PX,
-
- /**
- * Points — printing unit, 1 point = 1/72 inch.
- * Common in PDF and typography.
- */
- PT,
-
- /**
- * Millimeters — metric unit.
- * Often used in print layouts.
- */
- MM,
-
- /**
- * Centimeters — metric unit.
- */
- CM,
-
- /**
- * Inches — imperial unit.
- */
- IN
-}
-
diff --git a/src/main/java/com/demcha/compose/engine/components/geometry/Expendable.java b/src/main/java/com/demcha/compose/engine/components/geometry/Expendable.java
deleted file mode 100644
index 7a1d8a517..000000000
--- a/src/main/java/com/demcha/compose/engine/components/geometry/Expendable.java
+++ /dev/null
@@ -1,18 +0,0 @@
-package com.demcha.compose.engine.components.geometry;
-
-
-/**
- * Marker for parent-like entities whose own {@code ContentSize} may grow to fit children.
- *
- * This marker is consumed by the container expansion phase, not by the page breaker.
- * It should be used for entities that own child entities and are expected to expand their
- * box when children require more space.
- *
- * It does not mean that the entity itself can flow across pages.
- * Pagination behavior is controlled separately by {@code Breakable}.
- *
- * The name is kept as-is for compatibility with the current codebase, but conceptually
- * it means {@code expandable-by-children}.
- */
-public interface Expendable {
-}
diff --git a/src/main/java/com/demcha/compose/engine/components/geometry/ModuleWidthSeed.java b/src/main/java/com/demcha/compose/engine/components/geometry/ModuleWidthSeed.java
deleted file mode 100644
index 798c4c3e4..000000000
--- a/src/main/java/com/demcha/compose/engine/components/geometry/ModuleWidthSeed.java
+++ /dev/null
@@ -1,16 +0,0 @@
-package com.demcha.compose.engine.components.geometry;
-
-import com.demcha.compose.engine.components.core.Component;
-
-/**
- * Root-level width hint for semantic modules.
- *
- * Module layout resolves its final content width from the available width of the
- * parent container whenever a parent exists. For root modules there is no parent
- * inner box to inherit from, so builders may attach this seed to describe the
- * available width that the module should occupy before its own horizontal margin
- * is subtracted.
- *
- */
-public record ModuleWidthSeed(double width) implements Component {
-}
diff --git a/src/main/java/com/demcha/compose/engine/components/layout/Align.java b/src/main/java/com/demcha/compose/engine/components/layout/Align.java
deleted file mode 100644
index 4bb5fab37..000000000
--- a/src/main/java/com/demcha/compose/engine/components/layout/Align.java
+++ /dev/null
@@ -1,75 +0,0 @@
-package com.demcha.compose.engine.components.layout;
-
-import com.demcha.compose.engine.components.core.Component;
-import com.demcha.compose.engine.components.core.Entity;
-import com.demcha.compose.engine.components.geometry.OuterBoxSize;
-import com.demcha.compose.engine.components.layout.coordinator.Position;
-import lombok.extern.slf4j.Slf4j;
-
-/**
- * Container-oriented alignment metadata.
- *
- * {@code Align} describes how children should be arranged inside a parent
- * container and what spacing should separate siblings. It plays a role similar
- * to {@link Anchor}, but at the container layout level rather than the per-entity
- * absolute placement level.
- *
- *
- * Builders usually attach this component to containers, and layout utilities
- * later consume it when distributing children inside the parent's inner box.
- */
-@Slf4j
-public record Align(HAnchor h, VAnchor v, double spacing) implements Component {
- public static Align middle(double spacing){
- var v = VAnchor.MIDDLE;
- var h = HAnchor.CENTER;
- return new Align(h,v,spacing);
- }
- public static Align top(double spacing){
- var v = VAnchor.TOP;
- var h = HAnchor.CENTER;
- return new Align(h,v,spacing);
- }
- public static Align bottom(double spacing){
- var v = VAnchor.BOTTOM;
- var h = HAnchor.CENTER;
- return new Align(h,v,spacing);
- }
- public static Align defaultAlign(double spacing){
- log.info("Align defaultAlign");
- var v = VAnchor.DEFAULT;
- var h = HAnchor.DEFAULT;
- return new Align(h,v,spacing);
- }
- public static Align left(double spacing){
- var v = VAnchor.DEFAULT;
- var h = HAnchor.LEFT;
- return new Align(h,v,spacing);
- }
- public static Align right(double spacing){
- var v = VAnchor.DEFAULT;
- var h = HAnchor.RIGHT;
- return new Align(h,v,spacing);
- }
-
- public static void alignHorizontally(Entity entity, double boundingWidth, Align align){
- var outer = OuterBoxSize.from(entity).orElseThrow();
- var position = entity.getComponent(Position.class).orElseThrow();
-
- switch (align.h()) {
- case LEFT -> {
- entity.addComponent(new Position(0.0, position.y()));
- }
- case RIGHT -> {
- double x = boundingWidth - outer.width();
- entity.addComponent(new Position(x, position.y()));
- }
- case CENTER -> {
- double x = (boundingWidth - outer.width())/2;
- entity.addComponent(new Position(x, position.y()));
- }
-
- }
- }
-}
-
diff --git a/src/main/java/com/demcha/compose/engine/components/layout/StackAxis.java b/src/main/java/com/demcha/compose/engine/components/layout/StackAxis.java
deleted file mode 100644
index 87169a028..000000000
--- a/src/main/java/com/demcha/compose/engine/components/layout/StackAxis.java
+++ /dev/null
@@ -1,20 +0,0 @@
-package com.demcha.compose.engine.components.layout;
-
-import com.demcha.compose.engine.components.core.Component;
-
-/**
- * Internal stack direction component used by container layout.
- *
- * This belongs to the V2 engine layout model. Public authoring code should
- * describe rows/modules through canonical document APIs instead of attaching
- * this component directly.
- *
- * @author Artem Demchyshyn
- */
-public enum StackAxis implements Component {
- VERTICAL,
- HORIZONTAL,
- REVERSE_VERTICAL,
- REVERSE_HORIZONTAL,
- DEFAULT
-}
diff --git a/src/main/java/com/demcha/compose/engine/components/layout/Transform.java b/src/main/java/com/demcha/compose/engine/components/layout/Transform.java
deleted file mode 100644
index 60186450d..000000000
--- a/src/main/java/com/demcha/compose/engine/components/layout/Transform.java
+++ /dev/null
@@ -1,24 +0,0 @@
-package com.demcha.compose.engine.components.layout;
-
-import com.demcha.compose.engine.components.core.Component;
-
-/**
- * Represents an object that supports geometric transformations such as
- * scaling and rotation.
- *
- * Transformations affect the rendering or logical representation of
- * an object, without necessarily changing its underlying data.
- *
- *
- * - Scale X / Scale Y – Horizontal and vertical scaling factors
- * (1.0 means no scaling, values > 1 enlarge, values between 0 and 1 shrink).
- * - Rotation – Rotation of the object in degrees, measured
- * clockwise, where 0° represents the original orientation.
- *
- */
-public record Transform(double scaleX, double scaleY, double rotationDeg) implements Component {
- public Transform noScale() {
- return new Transform(1.0, 1.0, 0);
- }
-}
-
diff --git a/src/main/java/com/demcha/compose/engine/components/renderable/BarcodeComponent.java b/src/main/java/com/demcha/compose/engine/components/renderable/BarcodeComponent.java
deleted file mode 100644
index 0a9eea70f..000000000
--- a/src/main/java/com/demcha/compose/engine/components/renderable/BarcodeComponent.java
+++ /dev/null
@@ -1,21 +0,0 @@
-package com.demcha.compose.engine.components.renderable;
-
-import com.demcha.compose.engine.render.Render;
-import lombok.EqualsAndHashCode;
-import lombok.NoArgsConstructor;
-import lombok.extern.slf4j.Slf4j;
-
-/**
- * Render marker for a barcode/QR-code leaf entity.
- *
- * Like all render markers in the engine, this class describes intent
- * without performing any drawing. The actual bitmap generation and PDF
- * rendering is handled by {@code PdfBarcodeRenderHandler}.
- *
- * @author Artem Demchyshyn
- */
-@Slf4j
-@EqualsAndHashCode
-@NoArgsConstructor
-public class BarcodeComponent implements Render {
-}
diff --git a/src/main/java/com/demcha/compose/engine/components/renderable/ChunkedBlockText.java b/src/main/java/com/demcha/compose/engine/components/renderable/ChunkedBlockText.java
deleted file mode 100644
index 86156e4f2..000000000
--- a/src/main/java/com/demcha/compose/engine/components/renderable/ChunkedBlockText.java
+++ /dev/null
@@ -1,17 +0,0 @@
-package com.demcha.compose.engine.components.renderable;
-
-import lombok.Builder;
-import lombok.EqualsAndHashCode;
-import lombok.NoArgsConstructor;
-import lombok.extern.slf4j.Slf4j;
-
-/**
- * Chunked block-text marker kept until the engine fully converges on the
- * main block-text path.
- */
-@Slf4j
-@Builder
-@EqualsAndHashCode(callSuper = true)
-@NoArgsConstructor
-public class ChunkedBlockText extends Container {
-}
diff --git a/src/main/java/com/demcha/compose/engine/components/renderable/Circle.java b/src/main/java/com/demcha/compose/engine/components/renderable/Circle.java
deleted file mode 100644
index 9538b7c97..000000000
--- a/src/main/java/com/demcha/compose/engine/components/renderable/Circle.java
+++ /dev/null
@@ -1,10 +0,0 @@
-package com.demcha.compose.engine.components.renderable;
-
-import com.demcha.compose.engine.render.Render;
-import lombok.EqualsAndHashCode;
-import lombok.extern.slf4j.Slf4j;
-
-@Slf4j
-@EqualsAndHashCode
-public class Circle implements Render {
-}
diff --git a/src/main/java/com/demcha/compose/engine/components/renderable/Container.java b/src/main/java/com/demcha/compose/engine/components/renderable/Container.java
deleted file mode 100644
index 2bbf6a286..000000000
--- a/src/main/java/com/demcha/compose/engine/components/renderable/Container.java
+++ /dev/null
@@ -1,20 +0,0 @@
-package com.demcha.compose.engine.components.renderable;
-
-import com.demcha.compose.engine.components.geometry.Expendable;
-import com.demcha.compose.engine.render.Render;
-import com.demcha.compose.engine.render.guides.GuidesRenderer;
-import com.demcha.compose.engine.pagination.Breakable;
-import lombok.Getter;
-import lombok.extern.slf4j.Slf4j;
-
-import java.util.EnumSet;
-
-/**
- * Represents a basic container render marker.
- */
-@Slf4j
-@Getter
-public class Container implements Render, Expendable, Breakable {
- public static final EnumSet DEFAULT_GUIDES =
- EnumSet.of(GuidesRenderer.Guide.MARGIN, GuidesRenderer.Guide.PADDING, GuidesRenderer.Guide.BOX);
-}
diff --git a/src/main/java/com/demcha/compose/engine/components/renderable/Element.java b/src/main/java/com/demcha/compose/engine/components/renderable/Element.java
deleted file mode 100644
index 6a3311dbd..000000000
--- a/src/main/java/com/demcha/compose/engine/components/renderable/Element.java
+++ /dev/null
@@ -1,10 +0,0 @@
-package com.demcha.compose.engine.components.renderable;
-
-import com.demcha.compose.engine.components.geometry.Expendable;
-import com.demcha.compose.engine.render.Render;
-
-/**
- * This is simply an empty element that can hold anything (image, text, etc.) as a single instance.
- */
-public class Element implements Render, Expendable {
-}
diff --git a/src/main/java/com/demcha/compose/engine/components/renderable/HContainer.java b/src/main/java/com/demcha/compose/engine/components/renderable/HContainer.java
deleted file mode 100644
index 6234cda1e..000000000
--- a/src/main/java/com/demcha/compose/engine/components/renderable/HContainer.java
+++ /dev/null
@@ -1,7 +0,0 @@
-package com.demcha.compose.engine.components.renderable;
-
-import lombok.ToString;
-
-@ToString
-public class HContainer extends Container {
-}
diff --git a/src/main/java/com/demcha/compose/engine/components/renderable/ImageComponent.java b/src/main/java/com/demcha/compose/engine/components/renderable/ImageComponent.java
deleted file mode 100644
index 7a5ae9996..000000000
--- a/src/main/java/com/demcha/compose/engine/components/renderable/ImageComponent.java
+++ /dev/null
@@ -1,12 +0,0 @@
-package com.demcha.compose.engine.components.renderable;
-
-import com.demcha.compose.engine.render.Render;
-import lombok.EqualsAndHashCode;
-import lombok.NoArgsConstructor;
-import lombok.extern.slf4j.Slf4j;
-
-@Slf4j
-@EqualsAndHashCode
-@NoArgsConstructor
-public class ImageComponent implements Render {
-}
diff --git a/src/main/java/com/demcha/compose/engine/components/renderable/Line.java b/src/main/java/com/demcha/compose/engine/components/renderable/Line.java
deleted file mode 100644
index b9b915e78..000000000
--- a/src/main/java/com/demcha/compose/engine/components/renderable/Line.java
+++ /dev/null
@@ -1,10 +0,0 @@
-package com.demcha.compose.engine.components.renderable;
-
-import com.demcha.compose.engine.render.Render;
-import lombok.EqualsAndHashCode;
-import lombok.extern.slf4j.Slf4j;
-
-@Slf4j
-@EqualsAndHashCode
-public class Line implements Render {
-}
diff --git a/src/main/java/com/demcha/compose/engine/components/renderable/Link.java b/src/main/java/com/demcha/compose/engine/components/renderable/Link.java
deleted file mode 100644
index 2ec4a6be1..000000000
--- a/src/main/java/com/demcha/compose/engine/components/renderable/Link.java
+++ /dev/null
@@ -1,6 +0,0 @@
-package com.demcha.compose.engine.components.renderable;
-
-import com.demcha.compose.engine.render.Render;
-
-public class Link implements Render {
-}
diff --git a/src/main/java/com/demcha/compose/engine/components/renderable/Module.java b/src/main/java/com/demcha/compose/engine/components/renderable/Module.java
deleted file mode 100644
index d1c2abd64..000000000
--- a/src/main/java/com/demcha/compose/engine/components/renderable/Module.java
+++ /dev/null
@@ -1,4 +0,0 @@
-package com.demcha.compose.engine.components.renderable;
-
-public class Module extends Container{
-}
diff --git a/src/main/java/com/demcha/compose/engine/components/renderable/PageBreakComponent.java b/src/main/java/com/demcha/compose/engine/components/renderable/PageBreakComponent.java
deleted file mode 100644
index 33f581fac..000000000
--- a/src/main/java/com/demcha/compose/engine/components/renderable/PageBreakComponent.java
+++ /dev/null
@@ -1,21 +0,0 @@
-package com.demcha.compose.engine.components.renderable;
-
-import com.demcha.compose.engine.render.Render;
-import lombok.EqualsAndHashCode;
-import lombok.NoArgsConstructor;
-import lombok.extern.slf4j.Slf4j;
-
-/**
- * Render marker for a forced page break.
- *
- * A page break entity forces the pagination system to start a new page
- * at the point where this entity appears in the layout flow. It has zero
- * visual content but signals the page breaker to advance.
- *
- * @author Artem Demchyshyn
- */
-@Slf4j
-@EqualsAndHashCode
-@NoArgsConstructor
-public class PageBreakComponent implements Render {
-}
diff --git a/src/main/java/com/demcha/compose/engine/components/renderable/Rectangle.java b/src/main/java/com/demcha/compose/engine/components/renderable/Rectangle.java
deleted file mode 100644
index f50e8cc97..000000000
--- a/src/main/java/com/demcha/compose/engine/components/renderable/Rectangle.java
+++ /dev/null
@@ -1,11 +0,0 @@
-package com.demcha.compose.engine.components.renderable;
-
-import com.demcha.compose.engine.components.geometry.Expendable;
-import com.demcha.compose.engine.render.Render;
-import lombok.EqualsAndHashCode;
-import lombok.extern.slf4j.Slf4j;
-
-@Slf4j
-@EqualsAndHashCode
-public class Rectangle implements Render, Expendable {
-}
diff --git a/src/main/java/com/demcha/compose/engine/components/renderable/Row.java b/src/main/java/com/demcha/compose/engine/components/renderable/Row.java
deleted file mode 100644
index 64c53ad9f..000000000
--- a/src/main/java/com/demcha/compose/engine/components/renderable/Row.java
+++ /dev/null
@@ -1,4 +0,0 @@
-package com.demcha.compose.engine.components.renderable;
-
-public class Row extends HContainer{
-}
diff --git a/src/main/java/com/demcha/compose/engine/components/renderable/TableRow.java b/src/main/java/com/demcha/compose/engine/components/renderable/TableRow.java
deleted file mode 100644
index cf7dc71dd..000000000
--- a/src/main/java/com/demcha/compose/engine/components/renderable/TableRow.java
+++ /dev/null
@@ -1,13 +0,0 @@
-package com.demcha.compose.engine.components.renderable;
-
-import com.demcha.compose.engine.render.Render;
-
-/**
- * Render marker for atomic table rows.
- *
- * Backend-specific row drawing lives in renderer-side handlers so table layout
- * stays reusable across output formats.
- *
- */
-public class TableRow implements Render {
-}
diff --git a/src/main/java/com/demcha/compose/engine/components/renderable/VContainer.java b/src/main/java/com/demcha/compose/engine/components/renderable/VContainer.java
deleted file mode 100644
index 65573d246..000000000
--- a/src/main/java/com/demcha/compose/engine/components/renderable/VContainer.java
+++ /dev/null
@@ -1,5 +0,0 @@
-package com.demcha.compose.engine.components.renderable;
-
-public class VContainer extends Container {
-
-}
diff --git a/src/main/java/com/demcha/compose/engine/core/CanvasBox.java b/src/main/java/com/demcha/compose/engine/core/CanvasBox.java
deleted file mode 100644
index 5e107f9cd..000000000
--- a/src/main/java/com/demcha/compose/engine/core/CanvasBox.java
+++ /dev/null
@@ -1,41 +0,0 @@
-package com.demcha.compose.engine.core;
-
-import com.demcha.compose.engine.components.style.Margin;
-import lombok.Getter;
-import lombok.experimental.Accessors;
-
-@Getter
-@Accessors(fluent = true)
-public final class CanvasBox implements Canvas {
- private final float width;
- private final float height;
- private final float x;
- private final float y;
- private Margin margin;
-
- public CanvasBox(float width, float height, float x, float y, Margin margin) {
- this.width = width;
- this.height = height;
- this.x = x;
- this.y = y;
- this.margin = margin;
- }
-
- public CanvasBox(float width, float height, float x, float y) {
- this(width, height, x, y, null);
- }
-
- @Override
- public Margin margin() {
- if (this.margin == null) {
- return Margin.zero();
- }
- return margin;
- }
-
- @Override
- public void addMargin(Margin margin) {
- this.margin = margin;
- }
-}
-
diff --git a/src/main/java/com/demcha/compose/engine/exceptions/BigSizeElementException.java b/src/main/java/com/demcha/compose/engine/exceptions/BigSizeElementException.java
deleted file mode 100644
index 8507ba7cc..000000000
--- a/src/main/java/com/demcha/compose/engine/exceptions/BigSizeElementException.java
+++ /dev/null
@@ -1,36 +0,0 @@
-package com.demcha.compose.engine.exceptions;
-
-
-public class BigSizeElementException extends Throwable {
-
- // 💡 Best Practice: Use a constant for the template to avoid recreation
- private static final String ERROR_TEMPLATE = "Element is too large. Current position on the page: %s, Element height: %s, Available space: %s";
-
- // Standard constructor
- public BigSizeElementException(String message) {
- super(message);
- }
- public BigSizeElementException(Throwable e) {
- super(e);
- }
-
- // Constructor for chaining exceptions
- public BigSizeElementException(String message, Throwable cause) {
- super(message, cause);
- }
-
- /**
- * 🚀 Specialized Constructor
- * Automatically formats the message using the context values.
- *
- * @param currentY The current Y position on the page
- * @param elementHeight The height of the element trying to fit
- * @param availableSpace The remaining space on the page
- */
- public BigSizeElementException(double currentY, double elementHeight, double availableSpace) {
- super(String.format(ERROR_TEMPLATE, currentY, elementHeight, availableSpace));
- }
- public BigSizeElementException(double currentY, double elementHeight, double availableSpace,Object args) {
- super(String.format(ERROR_TEMPLATE, currentY, elementHeight, availableSpace) + args.toString());
- }
-}
diff --git a/src/main/java/com/demcha/compose/engine/exceptions/IllegalAlignException.java b/src/main/java/com/demcha/compose/engine/exceptions/IllegalAlignException.java
deleted file mode 100644
index 7ae121d71..000000000
--- a/src/main/java/com/demcha/compose/engine/exceptions/IllegalAlignException.java
+++ /dev/null
@@ -1,7 +0,0 @@
-package com.demcha.compose.engine.exceptions;
-
-public class IllegalAlignException extends RuntimeException {
- public IllegalAlignException(String entityShouldHaveAlignComponent) {
- super(entityShouldHaveAlignComponent);
- }
-}
diff --git a/src/main/java/com/demcha/compose/engine/exceptions/TextComponentException.java b/src/main/java/com/demcha/compose/engine/exceptions/TextComponentException.java
deleted file mode 100644
index c3f0ccf84..000000000
--- a/src/main/java/com/demcha/compose/engine/exceptions/TextComponentException.java
+++ /dev/null
@@ -1,7 +0,0 @@
-package com.demcha.compose.engine.exceptions;
-
-public class TextComponentException extends RuntimeException {
- public TextComponentException(String message) {
- super(message);
- }
-}
diff --git a/src/main/java/com/demcha/compose/engine/io/FileManagerSystem.java b/src/main/java/com/demcha/compose/engine/io/FileManagerSystem.java
deleted file mode 100644
index c118a4421..000000000
--- a/src/main/java/com/demcha/compose/engine/io/FileManagerSystem.java
+++ /dev/null
@@ -1,13 +0,0 @@
-package com.demcha.compose.engine.io;
-
-import com.demcha.compose.engine.core.SystemECS;
-
-import java.io.IOException;
-import java.nio.file.Path;
-
-public interface FileManagerSystem extends SystemECS {
-
- Path outPath();
-
- boolean saveAtomic() throws IOException;
-}
diff --git a/src/main/java/com/demcha/compose/engine/layout/LayoutSystem.java b/src/main/java/com/demcha/compose/engine/layout/LayoutSystem.java
deleted file mode 100644
index e1fd80599..000000000
--- a/src/main/java/com/demcha/compose/engine/layout/LayoutSystem.java
+++ /dev/null
@@ -1,474 +0,0 @@
-package com.demcha.compose.engine.layout;
-
-import com.demcha.compose.engine.components.content.text.LineTextData;
-import com.demcha.compose.engine.core.Canvas;
-import com.demcha.compose.engine.components.content.text.BlockTextData;
-import com.demcha.compose.engine.components.content.text.TextStyle;
-import com.demcha.compose.engine.components.core.Entity;
-import com.demcha.compose.engine.components.core.EntityName;
-import com.demcha.compose.engine.components.geometry.ContentSize;
-import com.demcha.compose.engine.components.geometry.InnerBoxSize;
-import com.demcha.compose.engine.components.geometry.OuterBoxSize;
-import com.demcha.compose.engine.components.layout.Align;
-import com.demcha.compose.engine.components.layout.Anchor;
-import com.demcha.compose.engine.components.layout.Layer;
-import com.demcha.compose.engine.components.layout.ParentComponent;
-import com.demcha.compose.engine.components.layout.coordinator.ComputedPosition;
-import com.demcha.compose.engine.components.layout.coordinator.PaddingCoordinate;
-import com.demcha.compose.engine.components.layout.coordinator.Position;
-import com.demcha.compose.engine.components.renderable.BlockText;
-import com.demcha.compose.engine.components.renderable.TextComponent;
-import com.demcha.compose.engine.components.style.Padding;
-import com.demcha.compose.engine.core.EntityManager;
-import com.demcha.compose.engine.core.LayoutTraversalContext;
-import com.demcha.compose.engine.exceptions.BigSizeElementException;
-import com.demcha.compose.engine.render.RenderingSystemECS;
-import com.demcha.compose.engine.core.SystemECS;
-import com.demcha.compose.engine.measurement.TextMeasurementSystem;
-import com.demcha.compose.engine.layout.container.ContainerExpander;
-import com.demcha.compose.engine.layout.container.ContainerLayoutManager;
-import com.demcha.compose.engine.layout.container.ModuleWidthResolver;
-import com.demcha.compose.engine.pagination.PageBreaker;
-import com.demcha.compose.engine.pagination.PaginationLayoutSystem;
-import com.demcha.compose.engine.pagination.TextBlockProcessor;
-import lombok.Getter;
-import lombok.RequiredArgsConstructor;
-import lombok.extern.slf4j.Slf4j;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.io.IOException;
-import java.util.*;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Main layout pass for GraphCompose entities.
- *
- * This system turns builder-produced entities into resolved geometry. It walks
- * the entity hierarchy, computes container relationships, expands parent boxes
- * when needed, resolves per-entity positions, and finally delegates to the
- * pagination stage so entities receive final {@code Placement} metadata.
- *
- *
- * Conceptually this is the "builder intent to geometry" phase of the engine.
- *
- * Box model summary:
- *
- * - {@code ContentSize}: declared content dimensions
- * - {@code InnerBoxSize}: available child area after padding
- * - {@code OuterBoxSize}: content plus padding and margin
- * - {@code Placement}: final bounding box and page span
- *
- */
-@Slf4j
-@RequiredArgsConstructor
-public class LayoutSystem> implements SystemECS {
- private static final Logger LIFECYCLE_LOG = LoggerFactory.getLogger("com.demcha.compose.engine.layout");
-
- private final Canvas canvas;
- @Getter
- private final T renderingSystem;
- private TextBlockProcessor textBlockProcessor;
-
- /**
- * Resolves an entity position from its parent context, walking the ancestor
- * chain when intermediate computed positions are still missing.
- *
- * @param childEntity the entity to position
- * @param parentEntity the parent entity, or {@code null} for a root entity
- * @param entityManager entity registry providing parent lookups
- * @return the resolved position when computation succeeds
- */
-
- private Optional calculatePositionFromParent(Entity childEntity,
- Entity parentEntity,
- EntityManager entityManager) {
- log.debug("Starting calculation of computed position for {} from parentEntity {}", childEntity, parentEntity);
-
- // 0) Handle page-level (no parent)
- if (parentEntity == null) {
- InnerBoxSize pageArea = new InnerBoxSize(this.canvas.width(), this.canvas.height());
-
- PaddingCoordinate paddingPercentCoordinate = new PaddingCoordinate(this.canvas.x(), this.canvas.y());
- ComputedPosition local = positionWithAnchor(childEntity, pageArea, paddingPercentCoordinate);
- log.debug("Final computed absolute position (page-level): {}", local);
- return Optional.of(local);
- }
-
- // 1) Build ancestor chain: [root ... parent]
- List chain = new ArrayList<>();
- Set seen = new HashSet<>();
- Entity cur = parentEntity;
-
- while (cur != null) {
- if (!seen.add(cur.getUuid())) {
- log.error("Cycle detected in ParentComponent chain at entity {}", cur);
- throw new IllegalStateException("Cycle detected in parent chain");
- }
- chain.add(cur);
-
- var parentCompOpt = cur.getComponent(ParentComponent.class);
- if (parentCompOpt.isEmpty()) {
- break; // cur is root
- }
- UUID gpId = parentCompOpt.get().uuid();
- cur = entityManager.getEntity(gpId).orElse(null);
- }
-
- int depth = chain.size(); // глубина
-
- // 2) Ensure each ancestor has InnerBoxSize and ComputedPosition
- for (int i = 0; i < depth; i++) {
- Entity e = chain.get(i); // root -> ... -> parent
-
- var inner = InnerBoxSize.from(e).orElseThrow();
-
- if (e.getComponent(ComputedPosition.class).isEmpty()) {
- var parentComp = e.getComponent(ParentComponent.class);
- if (parentComp.isPresent()) {
- Entity p = entityManager.getEntity(parentComp.get().uuid())
- .orElseThrow(() -> new IllegalStateException("Parent not found for " + e));
-
- var parentInner = InnerBoxSize.from(p).orElseThrow();
- var pagingCoordinate = PaddingCoordinate.from(parentEntity);
- ComputedPosition local = positionWithAnchor(e, parentInner, pagingCoordinate);
-
- ComputedPosition parentAbs = p.getComponent(ComputedPosition.class)
- .orElse(new ComputedPosition(0, 0));
-
- e.addComponent(new ComputedPosition(local.x() + parentAbs.x(),
- local.y() + parentAbs.y()));
- } else {
- InnerBoxSize refArea = new InnerBoxSize(this.canvas.width(), this.canvas.height());
- var pagingCoordinate = PaddingCoordinate.from(parentEntity);
- ComputedPosition local = positionWithAnchor(e, refArea, pagingCoordinate);
- e.addComponent(local);
- }
- }
- }
-
- // 3) Compute child's final position
- InnerBoxSize parentInner = InnerBoxSize.from(parentEntity).orElseThrow();
- var pagingCoordinate = PaddingCoordinate.from(parentEntity);
-
- ComputedPosition childLocal = positionWithAnchor(childEntity, parentInner, pagingCoordinate);
- ComputedPosition parentAbs = parentEntity.getComponent(ComputedPosition.class)
- .orElse(new ComputedPosition(0, 0));
-
- ComputedPosition childAbs = new ComputedPosition(
- childLocal.x() + parentAbs.x(),
- childLocal.y() + parentAbs.y()
- );
-
- // Final summary log
- log.debug("Position calculation summary: nodesTraversed={}, depth={}, finalPosition={}",
- chain.size(), depth, childAbs);
-
- return Optional.of(childAbs);
- }
-
- /**
- * Computes a child's anchored position inside the supplied parent area and
- * stores the result back on the entity.
- *
- * @param child entity being positioned
- * @param parentInnerBoxSize available parent area
- * @param paddingCoordinate parent padding origin
- * @return the computed position, also attached to the entity
- */
-
- private ComputedPosition positionWithAnchor(Entity child, InnerBoxSize parentInnerBoxSize, PaddingCoordinate paddingCoordinate) {
-
- var computed = ComputedPosition.from(child, parentInnerBoxSize, paddingCoordinate);
- child.addComponent(computed);
- log.debug("Computed position with Anchor has been created: {}", computed);
- log.debug("{} has been created in: {}", computed, child);
- return computed;
- }
-
-
- @Override
- /**
- * Runs the full layout pass for the current entity graph.
- *
- *
- * The pass resets traversal metadata, materializes one canonical hierarchy
- * snapshot, resolves module/container sizing helpers, computes per-entity
- * positions and depth/layer metadata, and finally delegates to pagination so
- * entities receive final {@code Placement} components.
- *
- *
- * @param entityManager entity registry for the current document
- */
- public void process(EntityManager entityManager) {
- long startNanos = System.nanoTime();
- LIFECYCLE_LOG.debug("layout.system.start entities={}", entityManager.getEntities().size());
- log.info("LayoutSystem: processing...");
- textBlockProcessor = new TextBlockProcessor(entityManager);
- LayoutTraversalContext.resetTraversalState(entityManager);
-
- final var entities = entityManager.getEntities();
- if (entities == null || entities.isEmpty()) {
- log.info("LayoutSystem: no entities to lay out");
- LIFECYCLE_LOG.debug("layout.system.end entities=0 durationMs={}", TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startNanos));
- return;
- }
-
- // Build one canonical hierarchy snapshot for the whole pass so layout,
- // pagination, and future backends all reason over the same sibling order.
- final LayoutTraversalContext traversalContext = LayoutTraversalContext.from(entityManager);
- final Map parentById = traversalContext.parentById();
- final Map> childrenByParent = traversalContext.childrenByParent();
-//TODO currently en testing stage
- ModuleWidthResolver.process(entityManager, canvas);
- ContainerLayoutManager.process(childrenByParent, entityManager);
-
- // 3) Expand parent boxes if needed (any child larger than parent)
- ContainerExpander.process(childrenByParent, entityManager);
-
- // 4) Compute roots
- final Set roots = traversalContext.roots();
-
- // 5) DFS with cycle detection
- final Map visit = new HashMap<>(entities.size());
- entities.keySet().forEach(id -> visit.put(id, Visit.UNSEEN));
-
- final var layers = entityManager.getLayers(); // layer → ordered ids (assumed provided)
- final var depthById = entityManager.getDepthById();
-
- for (UUID root : roots) {
- dfsLayout(
- root,
- null,
- entities,
- childrenByParent,
- visit,
- layers,
- depthById,
- 1
- , entityManager
- );
- }
-
- log.info("LayoutSystem: layout complete (nodes: {})", entities.size());
-
-// Pagination
-
- boolean withPagination = true;
-
- if (withPagination) {
- var pageBreaker = new PageBreaker(entityManager);
- pageBreaker.process(entities, renderingSystem.canvas(), traversalContext, depthById);
- } else {
- PaginationLayoutSystem paginationLayoutSystem = new PaginationLayoutSystem();
- paginationLayoutSystem.process(entityManager);
- }
-
- LIFECYCLE_LOG.debug(
- "layout.system.end entities={} roots={} layers={} durationMs={}",
- entities.size(),
- roots.size(),
- layers.size(),
- TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startNanos));
- }
-
- /**
- * Performs the recursive layout traversal for one hierarchy branch.
- *
- * Parents are processed before children so child layout can depend on
- * parent inner size, padding coordinates, and container ordering metadata.
- */
-
- private void dfsLayout(
- UUID id,
- UUID parentId,
- Map entities,
- Map> childrenByParent,
- Map visit,
- Map> layers, // NEW: layer → ordered ids
- Map depthById, // NEW: id → depth
- int depth, // NEW: current depth
- EntityManager entityManager
-
- ) {
- Visit st = visit.getOrDefault(id, Visit.UNSEEN);
- if (st == Visit.DONE) return;
- if (st == Visit.ACTIVE) throw new IllegalStateException("LayoutSystem: cycle detected at entity " + id);
- visit.put(id, Visit.ACTIVE);
-
- Entity childEntity = entities.get(id);
- if (childEntity == null) {
- log.warn("LayoutSystem: entity {} not found — skipping", id);
- visit.put(id, Visit.DONE);
- return;
- }
-
- // --- LAYERS COLLECTION ---
- layers.computeIfAbsent(depth, k -> new ArrayList<>()).add(id);
- depthById.put(id, depth);
-
- // --- POSITION CALC ---
- ComputedPosition computedPosition;
- if (parentId != null) {
- Entity parent = entities.get(parentId);
- if (parent != null) {
- computedPosition = ComputedPosition.from(childEntity, parent);
- } else {
- log.warn("LayoutSystem: parent {} of {} not found — using root positioning (Position + Margin)", parentId, id);
- computedPosition = ComputedPosition.from(childEntity, this.canvas);
- }
- } else {
- computedPosition = ComputedPosition.from(childEntity, canvas);
- }
-
- // IMPORTANT: store the computed position, not (0,0)
- childEntity.addComponent(computedPosition);
- childEntity.addComponent(new Layer(depth));
-
- if (childEntity.has(Align.class)) {
- alignRearrangeBlockText(childEntity, entityManager);
- }
-
- if (log.isDebugEnabled()) {
- String name = childEntity.getComponent(EntityName.class)
- .map(EntityName::value)
- .orElse(id.toString());
- log.debug("LayoutSystem: {} [depth={}] positioned at ({}, {})",
- name, depth, computedPosition.x(), computedPosition.y());
- }
- if (BlockText.class.isAssignableFrom(childEntity.getRender().getClass())) {
- try {
- textBlockProcessor.processLayoutSystemTextLines(childEntity);
- } catch (IOException e) {
- log.error("Error during processing block text entity {}", childEntity);
- throw new RuntimeException(String.format("Error during processing block text entity %s", childEntity), e);
- } catch (BigSizeElementException e) {
- log.error(String.format("To big size line in block text, Error during processing block text entity %s", childEntity), e);
-
- throw new RuntimeException(e);
- }
- }
-
- // DFS to children with depth+1
- for (UUID childId : childrenByParent.getOrDefault(id, Collections.emptyList())) {
- dfsLayout(childId, id, entities, childrenByParent, visit, layers, depthById, depth + 1, entityManager);
- }
-
- visit.put(id, Visit.DONE);
- }
-
- /**
- * Applies horizontal alignment offsets to block-text line fragments inside the
- * entity's own content box.
- *
- *
- * This is a post-measurement adjustment step. It does not remeasure the full
- * block unless a line payload has no cached width information.
- *
- *
- * @param blockTextBox block-text entity to realign
- * @param entityManager entity registry used to resolve the text measurement
- * system on demand
- * @return the same entity instance after line coordinates are updated
- */
- private Entity alignBlockText(Entity blockTextBox, EntityManager entityManager) {
- Align align = blockTextBox
- .getComponent(Align.class).orElse(Align.defaultAlign(2));
- var component = blockTextBox.getComponent(BlockTextData.class).orElseThrow();
- var size = blockTextBox.getComponent(ContentSize.class).orElseThrow();
- var padding = blockTextBox.getComponent(Padding.class).orElse(Padding.zero());
- TextStyle style = blockTextBox.getComponent(TextStyle.class).orElseThrow();
- TextMeasurementSystem measurementSystem = null;
-
- var lines = component.lines();
- for (LineTextData line : lines) {
- // Ordinary block-text lines already carry builder-time width caches.
- // The fallback is only for manually assembled/un-cached payloads.
- double lineWidth = line.lineWidth();
- if (Double.isNaN(lineWidth)) {
- if (measurementSystem == null) {
- measurementSystem = entityManager.getSystems()
- .textMeasurement()
- .orElseThrow(() -> new IllegalStateException("TextMeasurementSystem is required to align block text."));
- }
- lineWidth = line.width(measurementSystem, style);
- }
- switch (align.h()) {
- case LEFT -> {
- double x = line.x() + padding.left();
- line.x(x);
- }
- case RIGHT -> {
- double x = size.width() - lineWidth - padding.right();
- line.x(x);
- }
- case CENTER -> {
- double x = (size.width() - lineWidth + padding.left()) / 2;
- line.x(x);
- }
-
- }
-
- }
-
-
- return blockTextBox;
- }
-
- /**
- * Aligns block-text payloads when the entity actually carries block-text line
- * data.
- *
- * @param entity entity to inspect
- * @param entityManager entity registry used to resolve layout helpers
- * @return the aligned entity wrapped in {@link Optional}, or empty when the
- * entity is not a block-text payload holder
- */
- public Optional alignRearrangeBlockText(Entity entity, EntityManager entityManager) {
-
- if (entity.has(BlockTextData.class)) {
- return Optional.of(alignBlockText(entity, entityManager));
- } else {
- log.debug("Entity is not a BlockTextData");
- return Optional.empty();
- }
- }
-
-
- /**
- * Returns whether the given entity must NOT be expanded.
- *
- * Rules:
- *
- * - Has {@code TextComponent} → return {@code true} (do not expand)
- * - Has {@code Box} → return {@code false} (allow expansion)
- * - Otherwise → return {@code true} (do not expand)
- *
- *
- * @param entity the entity to check
- * @return {@code true} to skip expansion; {@code false} to allow it
- */
-
- @Deprecated
- private boolean isExpandable(Entity entity) {
- if (entity.has(TextComponent.class)) {
- // Forbidden expend text Entitie
- return false;
- }
-
- return true;
- }
-
-
- @Override
- public String toString() {
- return "LayoutSystem";
- }
-
- /**
- * DFS visit states used during one layout traversal.
- */
- public enum Visit {UNSEEN, ACTIVE, DONE;}
-}
-
-
diff --git a/src/main/java/com/demcha/compose/engine/layout/container/ContainerAligner.java b/src/main/java/com/demcha/compose/engine/layout/container/ContainerAligner.java
deleted file mode 100644
index cbd77e558..000000000
--- a/src/main/java/com/demcha/compose/engine/layout/container/ContainerAligner.java
+++ /dev/null
@@ -1,290 +0,0 @@
-package com.demcha.compose.engine.layout.container;
-
-import com.demcha.compose.engine.components.layout.StackAxis;
-import com.demcha.compose.engine.components.core.Entity;
-import com.demcha.compose.engine.components.geometry.ContentSize;
-import com.demcha.compose.engine.components.geometry.InnerBoxSize;
-import com.demcha.compose.engine.components.geometry.OuterBoxSize;
-import com.demcha.compose.engine.components.layout.Align;
-import com.demcha.compose.engine.components.layout.Anchor;
-import com.demcha.compose.engine.components.layout.HAnchor;
-import com.demcha.compose.engine.components.layout.VAnchor;
-import com.demcha.compose.engine.components.layout.coordinator.Position;
-import com.demcha.compose.engine.components.renderable.Container;
-import com.demcha.compose.engine.components.renderable.Module;
-import com.demcha.compose.engine.components.style.Margin;
-import com.demcha.compose.engine.components.style.Padding;
-import com.demcha.compose.engine.core.EntityManager;
-import lombok.extern.slf4j.Slf4j;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-import java.util.stream.Collectors;
-
-/**
- * ContainerAligner using the Strategy Pattern
- *
- * This class is responsible for aligning child entities within a parent container.
- * It has been refactored from a procedural switch-based approach to use the
- * Strategy design pattern. This decouples the main alignment logic from the
- * specific algorithms for different layout directions (e.g., horizontal, vertical).
- *
- * Design Benefits:
- *
- * - Open/Closed Principle: New layout types can be added by creating a new
- * strategy class without modifying this core aligner class.
- * - Single Responsibility Principle: Each strategy class has one job: to
- * implement a specific layout algorithm. The {@code ContainerAligner} class's only
- * job is to select the correct strategy.
- * - Reduced Complexity: Eliminates large switch statements, making the code
- * cleaner and easier to read.
- *
- *
- * @see LayoutStrategy
- */
-@Slf4j
-public class ContainerAligner {
- private static final double EPS = 1e-6;
-
- private static final Map STRATEGIES = Map.of(
- StackAxis.HORIZONTAL, new HorizontalStrategy(),
- StackAxis.VERTICAL, new VerticalStrategy(),
- StackAxis.REVERSE_HORIZONTAL, new ReverseHorizontalStrategy(),
- StackAxis.REVERSE_VERTICAL, new ReverseVerticalStrategy()
- );
-
- public static void align(Entity parent, EntityManager entityManager) {
- if (!parent.hasAssignable(Container.class)) {
- log.debug("{} is not a Container and will be skipped.", parent);
- return;
- }
-
- StackAxis stackAxis = parent.getComponent(StackAxis.class)
- .orElseThrow(() -> new IllegalStateException("Container Entity must have a StackAxis component."));
-
- LayoutStrategy strategy = parent.has(Module.class)
- ? new ModuleVerticalStrategy()
- : STRATEGIES.get(stackAxis);
-
- if (strategy != null) {
- log.debug("Aligning children of {} using {} strategy.", parent, stackAxis);
- strategy.alignChildren(parent, entityManager);
- } else {
- log.info("No layout strategy found for StackAxis: {}. No alignment will be performed.", stackAxis);
- }
- }
-
- private interface LayoutStrategy {
- void alignChildren(Entity parent, EntityManager entityManager);
- }
-
- private abstract static class BaseLayoutStrategy implements LayoutStrategy {
-
- @Override
- public void alignChildren(Entity parent, EntityManager entityManager) {
- Align align = parent.getComponent(Align.class).orElseThrow();
- List children = getOrderedChildren(parent, entityManager);
- Axes axes = new Axes();
-
- for (int i = 0; i < children.size(); i++) {
- Entity child = children.get(i);
- boolean isLastChild = (i == children.size() - 1);
-
- // IMPORTANT: Recursively align children of any nested containers first.
- ContainerAligner.align(child, entityManager);
-
- updateChildPosition(child, axes);
- updateContainerDimensions(child, axes);
-
- // Apply spacing between elements, but not after the last one.
- if (!isLastChild) {
- axes.main += align.spacing();
- }
- }
-
-
- // After processing all children, update the parent's final size.
- Padding parentPadding = parent.getComponent(Padding.class).orElse(Padding.zero());
- parent.addComponent(getFinalContentSize(axes, parentPadding));
- }
-
- /**
- * CORRECTED: This now returns children in the standard, forward order.
- * The reverse logic is handled by the ReverseLayoutStrategy.
- */
- protected List getOrderedChildren(Entity parent, EntityManager entityManager) {
- List children = parent.getChildren().stream()
- .map(id -> entityManager.getEntity(id).orElseThrow())
- .collect(Collectors.toCollection(ArrayList::new));
- java.util.Collections.reverse(children);
- return children;
- }
-
- protected abstract void updateChildPosition(Entity child, Axes axes);
-
- protected abstract void updateContainerDimensions(Entity child, Axes axes);
-
- /**
- * CORRECTED: The `lastChildMargin` parameter was removed to prevent double-counting.
- * The parent's size is determined by the accumulated children's outer sizes and the parent's own padding.
- */
- protected abstract ContentSize getFinalContentSize(Axes axes, Padding padding);
-
- protected static final class Axes {
- double main = 0;
- double cross = 0;
- }
- }
-
- // --- Concrete Strategy Implementations ---
-
- private static class HorizontalStrategy extends BaseLayoutStrategy {
- @Override
- protected void updateChildPosition(Entity child, Axes axes) {
- Position currentPos = child.getComponent(Position.class).orElse(Position.zero());
- Margin margin = child.getComponent(Margin.class).orElse(Margin.zero());
- Anchor anchor = child.getComponent(Anchor.class).orElse(Anchor.defaultAnchor());
- child.addComponent(new Position(axes.main + margin.left(), currentPos.y()));
- child.addComponent(new Anchor(HAnchor.DEFAULT, anchor.v()));
- }
-
- @Override
- protected void updateContainerDimensions(Entity child, Axes axes) {
- var outbox = OuterBoxSize.from(child).orElseThrow();
- axes.main += outbox.width();
- axes.cross = Math.max(axes.cross, outbox.height());
- }
-
- @Override
- protected ContentSize getFinalContentSize(Axes axes, Padding padding) {
- // CORRECTED: Do not add last child's margin. Parent padding affects both axes.
- return new ContentSize(axes.main + padding.horizontal(), axes.cross + padding.vertical());
- }
- }
-
- private static class VerticalStrategy extends BaseLayoutStrategy {
- @Override
- protected void updateChildPosition(Entity child, Axes axes) {
- Position currentPos = child.getComponent(Position.class).orElse(Position.zero());
- Anchor anchor = child.getComponent(Anchor.class).orElse(Anchor.defaultAnchor());
- Margin margin = child.getComponent(Margin.class).orElse(Margin.zero());
- // CORRECTED: Preserves original x-position for a true vertical stack.
- // The previous logic (currentPos.x() + margin.bottom()) created an unintentional diagonal layout.
- child.addComponent(new Position(currentPos.x(), axes.main + margin.bottom()));
- child.addComponent(new Anchor(anchor.h(), VAnchor.DEFAULT));
- }
-
- @Override
- protected void updateContainerDimensions(Entity child, Axes axes) {
- var outbox = OuterBoxSize.from(child).orElseThrow();
- axes.main += outbox.height();
- axes.cross = Math.max(axes.cross, outbox.width());
- }
-
- @Override
- protected ContentSize getFinalContentSize(Axes axes, Padding padding) {
- // CORRECTED: Do not add last child's margin. Parent padding affects both axes.
- return new ContentSize(axes.cross + padding.horizontal(), axes.main + padding.vertical());
- }
- }
-
- private abstract static class ReverseLayoutStrategy extends BaseLayoutStrategy {
- /**
- * CORRECTED: This class now correctly reverses the order of children.
- */
- @Override
- protected List getOrderedChildren(Entity parent, EntityManager entityManager) {
- return parent.getChildren().stream()
- .map(id -> entityManager.getEntity(id).orElseThrow())
- .collect(Collectors.toList()); // .reversed() is now correctly placed here
- }
- }
-
- private static class ReverseHorizontalStrategy extends ReverseLayoutStrategy {
- private final HorizontalStrategy delegate = new HorizontalStrategy();
-
- @Override
- protected void updateChildPosition(Entity child, Axes axes) {
- delegate.updateChildPosition(child, axes);
- }
-
- @Override
- protected void updateContainerDimensions(Entity child, Axes axes) {
- delegate.updateContainerDimensions(child, axes);
- }
-
- @Override
- protected ContentSize getFinalContentSize(Axes axes, Padding padding) {
- return delegate.getFinalContentSize(axes, padding);
- }
- }
-
- private static class ReverseVerticalStrategy extends ReverseLayoutStrategy {
- private final VerticalStrategy delegate = new VerticalStrategy();
-
- @Override
- protected void updateChildPosition(Entity child, Axes axes) {
- delegate.updateChildPosition(child, axes);
- }
-
- @Override
- protected void updateContainerDimensions(Entity child, Axes axes) {
- delegate.updateContainerDimensions(child, axes);
- }
-
- @Override
- protected ContentSize getFinalContentSize(Axes axes, Padding padding) {
- return delegate.getFinalContentSize(axes, padding);
- }
- }
-
- private static class ModuleVerticalStrategy implements LayoutStrategy {
- @Override
- public void alignChildren(Entity parent, EntityManager entityManager) {
- Align align = parent.getComponent(Align.class).orElseThrow();
- List children = parent.getChildren().stream()
- .map(id -> entityManager.getEntity(id).orElseThrow())
- .collect(Collectors.toCollection(ArrayList::new));
- java.util.Collections.reverse(children);
-
- double main = 0;
- double resolvedWidth = parent.getComponent(ContentSize.class)
- .orElseThrow(() -> new IllegalStateException("Module missing ContentSize: " + parent))
- .width();
- double innerWidth = InnerBoxSize.from(parent)
- .orElseThrow(() -> new IllegalStateException("Module missing InnerBoxSize: " + parent))
- .width();
-
- for (int i = 0; i < children.size(); i++) {
- Entity child = children.get(i);
- boolean isLastChild = (i == children.size() - 1);
-
- ContainerAligner.align(child, entityManager);
-
- Position currentPos = child.getComponent(Position.class).orElse(Position.zero());
- Anchor anchor = child.getComponent(Anchor.class).orElse(Anchor.defaultAnchor());
- Margin margin = child.getComponent(Margin.class).orElse(Margin.zero());
-
- child.addComponent(new Position(currentPos.x(), main + margin.bottom()));
- child.addComponent(new Anchor(anchor.h(), VAnchor.DEFAULT));
-
- OuterBoxSize childOuter = OuterBoxSize.from(child).orElseThrow();
- if (childOuter.width() > innerWidth + EPS) {
- log.warn("Child {} overflows module {} width contract: childOuter={} innerWidth={}",
- child, parent, childOuter.width(), innerWidth);
- }
-
- main += childOuter.height();
- if (!isLastChild) {
- main += align.spacing();
- }
- }
-
- Padding parentPadding = parent.getComponent(Padding.class).orElse(Padding.zero());
- ContentSize current = parent.getComponent(ContentSize.class).orElseThrow();
- double finalHeight = Math.max(current.height(), main + parentPadding.vertical());
- parent.addComponent(new ContentSize(resolvedWidth, finalHeight));
- }
- }
-}
diff --git a/src/main/java/com/demcha/compose/engine/layout/container/ContainerExpander.java b/src/main/java/com/demcha/compose/engine/layout/container/ContainerExpander.java
deleted file mode 100644
index 914b6e54d..000000000
--- a/src/main/java/com/demcha/compose/engine/layout/container/ContainerExpander.java
+++ /dev/null
@@ -1,203 +0,0 @@
-package com.demcha.compose.engine.layout.container;
-
-import com.demcha.compose.engine.components.core.Entity;
-import com.demcha.compose.engine.components.geometry.ContentSize;
-import com.demcha.compose.engine.components.geometry.InnerBoxSize;
-import com.demcha.compose.engine.components.geometry.OuterBoxSize;
-import com.demcha.compose.engine.components.layout.Align;
-import com.demcha.compose.engine.components.renderable.Module;
-import com.demcha.compose.engine.core.EntityManager;
-import com.demcha.compose.engine.exceptions.ContentSizeNotFoundException;
-import com.demcha.compose.engine.components.geometry.Expendable;
-import lombok.extern.slf4j.Slf4j;
-
-import java.util.*;
-
-@Slf4j
-public final class ContainerExpander {
-
- private static final double EPS = 1e-6;
-
- private ContainerExpander() {
- }
-
- /**
- * Ensures the parent's ContentSize is at least large enough to contain all children,
- * based on the parent's InnerBoxSize constraints and children OuterBoxSize.
- *
- * @param parent container entity (must have InnerBoxSize and ContentSize)
- * @param children direct children of the container
- * @return true if parent's ContentSize was increased, false otherwise
- */
- public static boolean expandContentSizeByChildren(Entity parent, Set children) {
- Objects.requireNonNull(parent, "parent cannot be null");
- Objects.requireNonNull(children, "children cannot be null");
-
- if (children.isEmpty()) {
- log.debug("No children; nothing to expand for {}", parent);
- return false;
- }
-
- if (parent.has(Module.class)) {
- return expandModuleContentHeightByChildren(parent, children);
- }
-
- final InnerBoxSize inner = InnerBoxSize.from(parent).orElseThrow(() ->
- new IllegalStateException("Parent is missing InnerBoxSize: " + parent));
-
- // Start from current inner box constraints
- double requiredContentWidth = inner.width();
- double requiredContentHeight = inner.height();
-
- if (log.isTraceEnabled()) {
- log.trace("Initial required size from inner box for {}: {}x{}",
- parent, requiredContentWidth, requiredContentHeight);
- }
-
- for (Entity child : children) {
- Optional childOuterOpt = OuterBoxSize.from(child);
- if (childOuterOpt.isEmpty()) continue;
-
- OuterBoxSize childOuter = childOuterOpt.get();
- if (log.isTraceEnabled())
- log.trace("Child {} outer size: {}x{}", child, childOuter.width(), childOuter.height());
-
- // NOTE: if children can be offset, replace with (childRight, childBottom) calculation
- requiredContentWidth = Math.max(requiredContentWidth, childOuter.width());
- requiredContentHeight = Math.max(requiredContentHeight, childOuter.height());
- }
-
- return applyContentSizeIfLarger(parent, requiredContentWidth, requiredContentHeight);
- }
-
- private static boolean expandModuleContentHeightByChildren(Entity parent, Set children) {
- final InnerBoxSize inner = InnerBoxSize.from(parent).orElseThrow(() ->
- new IllegalStateException("Parent is missing InnerBoxSize: " + parent));
-
- double requiredContentHeight = inner.height();
-
- for (Entity child : children) {
- Optional childOuterOpt = OuterBoxSize.from(child);
- if (childOuterOpt.isEmpty()) continue;
-
- OuterBoxSize childOuter = childOuterOpt.get();
- if (childOuter.width() > inner.width() + EPS) {
- log.warn("Child {} exceeds module {} inner width: childOuter={} innerWidth={}",
- child, parent, childOuter.width(), inner.width());
- }
- requiredContentHeight = Math.max(requiredContentHeight, childOuter.height());
- }
-
- return applyHeightIfLarger(parent, requiredContentHeight);
- }
-
- /**
- * Increases ContentSize if (required > current inner) considering EPS.
- */
- private static boolean applyContentSizeIfLarger(Entity parent, double requiredInnerWidth, double requiredInnerHeight) {
- final InnerBoxSize inner = InnerBoxSize.from(parent).orElseThrow(() ->
- new IllegalStateException("Parent is missing InnerBoxSize: " + parent));
-
- final boolean wider = requiredInnerWidth > inner.width() + EPS;
- final boolean taller = requiredInnerHeight > inner.height() + EPS;
-
- if (!wider && !taller) {
- log.debug("Parent {} content not expanded (required <= inner).", parent);
- return false;
- }
-
- final ContentSize current = parent.getComponent(ContentSize.class).orElseThrow(() -> {
- log.error("All objects must have a ContentSize. Object {} doesn't have one.", parent);
- return new ContentSizeNotFoundException(parent);
- });
-
- final double newWidth = wider ? current.width() + (requiredInnerWidth - inner.width()) : current.width();
- final double newHeight = taller ? current.height() + (requiredInnerHeight - inner.height()) : current.height();
-
- // Avoid churn
- if (Math.abs(newWidth - current.width()) < EPS && Math.abs(newHeight - current.height()) < EPS) {
- log.debug("Computed size equals current ContentSize (within EPS); skipping update for {}", parent);
- return false;
- }
-
- ContentSize updated = new ContentSize(newWidth, newHeight);
- parent.addComponent(updated); // ensure addComponent replaces old instance
-
- log.debug("Expanded ContentSize for {}: {}x{} -> {}x{}",
- parent, current.width(), current.height(), newWidth, newHeight);
- return true;
- }
-
- private static boolean applyHeightIfLarger(Entity parent, double requiredInnerHeight) {
- final InnerBoxSize inner = InnerBoxSize.from(parent).orElseThrow(() ->
- new IllegalStateException("Parent is missing InnerBoxSize: " + parent));
-
- final boolean taller = requiredInnerHeight > inner.height() + EPS;
- if (!taller) {
- log.debug("Parent {} height not expanded (required <= inner).", parent);
- return false;
- }
-
- final ContentSize current = parent.getComponent(ContentSize.class).orElseThrow(() -> {
- log.error("All objects must have a ContentSize. Object {} doesn't have one.", parent);
- return new ContentSizeNotFoundException(parent);
- });
-
- final double newHeight = current.height() + (requiredInnerHeight - inner.height());
- if (Math.abs(newHeight - current.height()) < EPS) {
- log.debug("Computed height equals current ContentSize (within EPS); skipping update for {}", parent);
- return false;
- }
-
- parent.addComponent(new ContentSize(current.width(), newHeight));
- log.debug("Expanded module height for {}: {} -> {}", parent, current.height(), newHeight);
- return true;
- }
-
- /**
- * The method normalizeBoxSize adjusts the size of parent entities. For each parent, it inspects the dimensions (OuterBoxSize) of all its direct children.
- * It then ensures the parent's OuterBoxSize is large enough to encompass its own original size and the size of its largest child by updating it to the maximum width and maximum height found.
- * In simple terms: It makes a parent container at least as big as its biggest child.
- *
- * @param childrenByParents map already sorted by parent
- */
- public static void process(Map> childrenByParents, EntityManager entityManager) {
- log.debug("Box size normalizer");
-
-
- for (Map.Entry> parentUuid : childrenByParents.entrySet()) {
-
- var entityParentOpt = entityManager.getEntity(parentUuid.getKey());
- if (entityParentOpt.isEmpty()) {
- log.warn("Hasn't find a Parent Entity by id {}", parentUuid.getKey());
- continue;
- }
- var parentEntity = entityParentOpt.get();
- if (!isExpandable(parentEntity)) {
- continue;
- }
- // Retriven Entiti
- var childrenEntities = entityManager.getSetEntitiesFromUuids(new LinkedHashSet<>(parentUuid.getValue()));
-
-
- if (parentEntity.has(Align.class)) {
-
- log.debug("It is a container with component Align");
-
-
- }
- ContainerExpander.expandContentSizeByChildren(parentEntity, childrenEntities);
-
- }
- }
-
-
-
- private static boolean isExpandable(Entity entity) {
- if (entity.hasAssignable(Expendable.class)) {
- return true;
- }
- log.debug("Entity {} has no Expendable", entity);
- return false;
- }
-}
diff --git a/src/main/java/com/demcha/compose/engine/layout/container/ContainerLayoutManager.java b/src/main/java/com/demcha/compose/engine/layout/container/ContainerLayoutManager.java
deleted file mode 100644
index d30556a79..000000000
--- a/src/main/java/com/demcha/compose/engine/layout/container/ContainerLayoutManager.java
+++ /dev/null
@@ -1,136 +0,0 @@
-package com.demcha.compose.engine.layout.container;
-
-import com.demcha.compose.engine.components.core.Entity;
-import com.demcha.compose.engine.components.geometry.InnerBoxSize;
-import com.demcha.compose.engine.components.layout.Align;
-import com.demcha.compose.engine.components.renderable.ChunkedBlockText;
-import com.demcha.compose.engine.components.renderable.Container;
-import com.demcha.compose.engine.core.EntityManager;
-import lombok.extern.slf4j.Slf4j;
-
-import java.util.*;
-
-@Slf4j
-public class ContainerLayoutManager {
-
- /**
- * Recursively aligns child entities within their parent containers (post-order).
- *
- * A parent container is aligned only after all of its container-children are aligned.
- * Non-container parents are just marked as processed (nothing to align).
- */
- public static void process(Map> childrenByParent, EntityManager entityManager) {
- if (childrenByParent == null || childrenByParent.isEmpty()) {
- log.debug("process(): nothing to do, childrenByParent is null or empty");
- return;
- }
- Objects.requireNonNull(entityManager, "entityManager");
-
-
- // 'visited' is a clearer name than doneList for graph-style traversals.
- Set visited = new HashSet<>();
-
- for (UUID parentId : childrenByParent.keySet()) {
- if (!visited.contains(parentId)) {
- if (isContainer(parentId, entityManager)) {
- alignContainer(parentId, childrenByParent, visited, entityManager, new ArrayDeque<>());
- } else {
- log.debug("Element Not container {}", entityManager.getEntity(parentId).orElseThrow(() -> new NoSuchElementException("Entity not found: " + parentId)));
- visited.add(parentId);
- }
- }
- }
- }
-
- /**
- * Depth-first post-order alignment with cycle detection.
- */
- private static void alignContainer(
- UUID parentId,
- Map> childrenByParent,
- Set visited,
- EntityManager entityManager,
- Deque stackForCycleCheck
- ) {
- log.debug("Align container start: {}", parentId);
-
- if (!stackForCycleCheck.add(parentId)) {
- // parentId is already in the current DFS path → cycle
- throw new IllegalStateException("Cycle detected in entity hierarchy: " + stackForCycleCheck);
- }
-
- // Iterate children safely (use empty set when absent)
- Collection children = childrenByParent.get(parentId);
- if (children == null) {
- children = Collections.emptyList();
- }
- for (UUID childId : children) {
- if (visited.contains(childId)) continue;
-
- if (isContainerSafe(childId, entityManager)) {
- alignContainer(childId, childrenByParent, visited, entityManager, stackForCycleCheck);
- } else {
- // Non-container child: nothing to align here, but still mark as visited.
- visited.add(childId);
- }
- }
-
- // Post-order: align AFTER children
- alignElement(parentId, entityManager);
- visited.add(parentId);
-
- // Pop for cycle check
- UUID popped = stackForCycleCheck.removeLast();
- assert popped.equals(parentId) : "Cycle stack corrupted";
-
- log.debug("Align container end: {}", parentId);
- }
-
- private static boolean isContainer(UUID id, EntityManager em) {
- Entity e = em.getEntity(id).orElseThrow(() -> new NoSuchElementException("Entity not found: " + id));
- return e.hasAssignable(Container.class);
- }
-
- private static boolean isContainerSafe(UUID id, EntityManager em) {
- return em.getEntity(id)
- .map(e -> e.hasAssignable(Container.class))
- .orElse(false);
- }
-
- private static void alignElement(UUID id, EntityManager em) {
- Entity parent = em.getEntity(id).orElseThrow(() -> new NoSuchElementException("Entity not found: " + id));
-
- if (!parent.hasAssignable(Container.class)) {
- log.trace("alignElement(): {} is not a Container — skipping.", parent);
- return;
- }
-
- log.info("Aligning container: {}", parent);
- ContainerAligner.align(parent, em);
-
- // Optional horizontal text alignment when container also holds text
- if (parent.hasAssignable(ChunkedBlockText.class)) {
- horizontallyAlign(parent, em);
- }
- }
-
- /**
- * Align children horizontally inside parent's inner width.
- * Uses the parent's Align component as the policy.
- */
- private static void horizontallyAlign(Entity parent, EntityManager em) {
- Align parentAlign = parent.getComponent(Align.class)
- .orElseThrow(() -> new NoSuchElementException("Align component missing on parent: " + parent));
-
- double innerW = InnerBoxSize.from(parent)
- .orElseThrow(() -> new NoSuchElementException("InnerBoxSize missing on parent: " + parent))
- .width();
-
- for (UUID childId : parent.getChildren()) {
- Entity child = em.getEntity(childId)
- .orElseThrow(() -> new NoSuchElementException("Child entity not found: " + childId));
-
- Align.alignHorizontally(child, innerW, parentAlign);
- }
- }
-}
diff --git a/src/main/java/com/demcha/compose/engine/layout/container/ModuleWidthResolver.java b/src/main/java/com/demcha/compose/engine/layout/container/ModuleWidthResolver.java
deleted file mode 100644
index 92f7cb884..000000000
--- a/src/main/java/com/demcha/compose/engine/layout/container/ModuleWidthResolver.java
+++ /dev/null
@@ -1,81 +0,0 @@
-package com.demcha.compose.engine.layout.container;
-
-import com.demcha.compose.engine.components.core.Entity;
-import com.demcha.compose.engine.components.geometry.ContentSize;
-import com.demcha.compose.engine.components.geometry.InnerBoxSize;
-import com.demcha.compose.engine.components.geometry.ModuleWidthSeed;
-import com.demcha.compose.engine.components.layout.ParentComponent;
-import com.demcha.compose.engine.components.renderable.Module;
-import com.demcha.compose.engine.components.style.Margin;
-import com.demcha.compose.engine.core.Canvas;
-import com.demcha.compose.engine.core.EntityManager;
-import lombok.extern.slf4j.Slf4j;
-
-import java.util.Comparator;
-import java.util.List;
-
-/**
- * Resolves the stable content width contract for semantic modules before
- * container child alignment begins.
- */
-@Slf4j
-public final class ModuleWidthResolver {
- private ModuleWidthResolver() {
- }
-
- public static void process(EntityManager entityManager, Canvas canvas) {
- List modules = entityManager.getEntities().values().stream()
- .filter(entity -> entity.has(Module.class))
- .sorted(Comparator.comparingInt(module -> depth(module, entityManager)))
- .toList();
- if (modules.isEmpty()) {
- return;
- }
-
- for (Entity module : modules) {
- resolveModuleWidth(module, entityManager, canvas);
- }
- }
-
- private static void resolveModuleWidth(Entity module, EntityManager entityManager, Canvas canvas) {
- Margin margin = module.getComponent(Margin.class).orElse(Margin.zero());
- ContentSize current = module.getComponent(ContentSize.class).orElse(new ContentSize(0, 0));
- double availableWidth = resolveAvailableWidth(module, entityManager, canvas);
- double resolvedWidth = Math.max(0, availableWidth - margin.horizontal());
-
- module.addComponent(new ContentSize(resolvedWidth, current.height()));
- log.debug("Resolved module width for {}: available={} margin={} resolved={}",
- module, availableWidth, margin.horizontal(), resolvedWidth);
- }
-
- private static double resolveAvailableWidth(Entity module, EntityManager entityManager, Canvas canvas) {
- ParentComponent parentComponent = module.getComponent(ParentComponent.class).orElse(null);
- if (parentComponent == null) {
- return module.getComponent(ModuleWidthSeed.class)
- .map(ModuleWidthSeed::width)
- .orElse(canvas.innerWidth());
- }
-
- return entityManager.getEntity(parentComponent.uuid())
- .flatMap(InnerBoxSize::from)
- .map(InnerBoxSize::width)
- .orElseGet(() -> {
- log.warn("Module {} parent width could not be resolved; falling back to canvas inner width.", module);
- return canvas.innerWidth();
- });
- }
-
- private static int depth(Entity entity, EntityManager entityManager) {
- int depth = 0;
- ParentComponent parent = entity.getComponent(ParentComponent.class).orElse(null);
-
- while (parent != null) {
- depth++;
- parent = entityManager.getEntity(parent.uuid())
- .flatMap(parentEntity -> parentEntity.getComponent(ParentComponent.class))
- .orElse(null);
- }
-
- return depth;
- }
-}
diff --git a/src/main/java/com/demcha/compose/engine/layout/container/package-info.java b/src/main/java/com/demcha/compose/engine/layout/container/package-info.java
deleted file mode 100644
index a5f8b923f..000000000
--- a/src/main/java/com/demcha/compose/engine/layout/container/package-info.java
+++ /dev/null
@@ -1,14 +0,0 @@
-/**
- * Container layout helpers (alignment, expansion, module-width resolution) of
- * the legacy ECS engine.
- *
- * Part of the parallel legacy {@code Entity}-based engine; the canonical
- * {@code com.demcha.compose.document.layout} pipeline does not use it.
- *
- * @deprecated Legacy ECS engine, superseded by the canonical
- * {@code com.demcha.compose.document.layout} pipeline. Not reachable from
- * any public API; retained only for legacy regression tests and a candidate
- * for removal. Do not extend it or spend optimization effort here.
- */
-@Deprecated
-package com.demcha.compose.engine.layout.container;
diff --git a/src/main/java/com/demcha/compose/engine/layout/package-info.java b/src/main/java/com/demcha/compose/engine/layout/package-info.java
deleted file mode 100644
index 7554ebfa6..000000000
--- a/src/main/java/com/demcha/compose/engine/layout/package-info.java
+++ /dev/null
@@ -1,17 +0,0 @@
-/**
- * Layout systems of the legacy ECS engine — they turn
- * low-level {@code Entity} graphs into resolved geometry for the original
- * {@code Entity}-based pipeline.
- *
- * Despite the former "shared" framing, the canonical pipeline
- * ({@code com.demcha.compose.document.layout.LayoutCompiler}) imports nothing
- * from this package. It is part of the parallel legacy ECS engine reachable
- * only from the legacy engine regression tests.
- *
- * @deprecated Legacy ECS engine, superseded by the canonical
- * {@code com.demcha.compose.document.layout} pipeline. Not reachable from
- * any public API; retained only for legacy regression tests and a candidate
- * for removal. Do not extend it or spend optimization effort here.
- */
-@Deprecated
-package com.demcha.compose.engine.layout;
diff --git a/src/main/java/com/demcha/compose/engine/pagination/PageBreaker.java b/src/main/java/com/demcha/compose/engine/pagination/PageBreaker.java
deleted file mode 100644
index 6165a2db7..000000000
--- a/src/main/java/com/demcha/compose/engine/pagination/PageBreaker.java
+++ /dev/null
@@ -1,383 +0,0 @@
-package com.demcha.compose.engine.pagination;
-
-import com.demcha.compose.engine.core.Canvas;
-import com.demcha.compose.engine.components.core.Entity;
-import com.demcha.compose.engine.components.geometry.ContentSize;
-import com.demcha.compose.engine.components.layout.Layer;
-import com.demcha.compose.engine.components.layout.coordinator.ComputedPosition;
-import com.demcha.compose.engine.components.layout.coordinator.Placement;
-import com.demcha.compose.engine.components.renderable.BlockText;
-import com.demcha.compose.engine.core.EntityManager;
-import com.demcha.compose.engine.core.LayoutTraversalContext;
-import com.demcha.compose.engine.exceptions.BigSizeElementException;
-import com.demcha.compose.engine.render.RenderingSystemECS;
-import com.demcha.compose.engine.core.SystemECS;
-import lombok.Data;
-import lombok.NonNull;
-import lombok.experimental.Accessors;
-import lombok.extern.slf4j.Slf4j;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Comparator;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.PriorityQueue;
-import java.util.UUID;
-import java.util.concurrent.TimeUnit;
-
-
-/**
- * A utility class responsible for the logic of breaking content across pages.
- *
- * The main responsibilities of this class are:
- *
- * - building a child-first pagination order from the resolved hierarchy
- * - Calculating and assigning a page number and in-page coordinates for each entity.
- * - Handling "breakable" entities, such as {@link BlockText}, whose content can
- * flow onto the next page.
- *
- * The class operates in a coordinate system where the Y-axis points downwards. It does not create new pages
- * but rather calculates and assigns {@link Placement} components to entities.
- *
- * A critical invariant of this class is that descendants must be processed before their ancestor containers.
- * The current implementation enforces that rule with a priority-based topological walk over the resolved
- * hierarchy: only nodes whose children are already processed can enter the ready queue, and unrelated nodes
- * are then ordered by layout position and depth. This keeps the algorithm renderer-agnostic while avoiding
- * repeated ancestor-chain walks inside a comparator.
- */
-@Slf4j
-@Data
-@Accessors(chain = true)
-public class PageBreaker {
- private static final Logger LIFECYCLE_LOG = LoggerFactory.getLogger("com.demcha.compose.engine.pagination");
-
- private final EntityManager entityManager;
- private PageLayoutCalculator pageLayoutCalculator;
- private TextBlockProcessor textBlockProcessor;
-
- /**
- * Creates a page breaker bound to one entity manager and its pagination
- * helpers.
- *
- * @param entityManager entity registry for the current document
- */
- public PageBreaker(EntityManager entityManager) {
- this.entityManager = entityManager;
- this.pageLayoutCalculator = new PageLayoutCalculator(entityManager);
- this.textBlockProcessor = new TextBlockProcessor(entityManager);
- }
-
-
- // === Paging math =========================================================
-
- /**
- * Builds a {@link Placement} for the entity using a replacement Y coordinate
- * and explicit page span.
- *
- * @param entity entity being placed
- * @param yPosition resolved Y coordinate inside the target page flow
- * @param startPage first page touched by the entity
- * @param endPage last page touched by the entity
- * @return placement preserving the entity's resolved X position and size
- */
- private static Placement setYInPlacement(Entity entity, double yPosition, int startPage, int endPage) {
- ComputedPosition computedPosition = entity.require(ComputedPosition.class);
- var size = entity.require(ContentSize.class);
- return new Placement(computedPosition.x(), yPosition, size.width(), size.height(), startPage, endPage);
- }
-
- /**
- * Convenience overload that converts a precomputed page-position result into a
- * {@link Placement}.
- *
- * @param entity entity being placed
- * @param position resolved position/page-span tuple
- * @return placement preserving the entity's resolved X position and size
- */
- private static Placement setYInPlacement(Entity entity, YPositionOnPage position) {
- return setYInPlacement(entity, position.yPosition(), position.startPage(), position.endPage());
- }
-
- /**
- * Runs page-breaking for the current entity set.
- *
- * The ordering step guarantees that descendants are processed before ancestors while still preferring
- * top-to-bottom layout order between unrelated subtrees.
- */
- public void process(@NonNull Map entities, Canvas canvas) {
- LayoutTraversalContext traversalContext = LayoutTraversalContext.from(entityManager);
- process(entities, canvas, traversalContext, entityManager.getDepthById());
- }
-
- /**
- * Runs page-breaking with a caller-supplied traversal snapshot and depth map.
- *
- *
- * This overload is used when layout has already materialized the canonical tree
- * snapshot for the pass and pagination should reuse that exact hierarchy view.
- *
- *
- * @param entities entity map for the current document
- * @param canvas page canvas configuration
- * @param traversalContext canonical parent/child snapshot for this pass
- * @param depthById depth map from layout traversal
- */
- public void process(@NonNull Map entities,
- Canvas canvas,
- @NonNull LayoutTraversalContext traversalContext,
- @NonNull Map depthById) {
- long startNanos = System.nanoTime();
- LIFECYCLE_LOG.debug("pagination.pageBreaker.start entities={} roots={}", entities.size(), traversalContext.roots().size());
- Offset yOffset = new Offset();
- List> orderedEntities = orderedForPagination(
- entities,
- traversalContext.parentById(),
- traversalContext.childrenByParent(),
- depthById);
-
- orderedEntities.forEach(e -> {
- Entity entity = e.getValue();
-
- if (Breakable.class.isAssignableFrom(entity.getRender().getClass())) {
- if (entity.hasAssignable(BlockText.class)) {
- definePlacementForBlockText(canvas, entity, yOffset);
- } else {
- definePlacement(canvas, entity, yOffset, true);
- }
-
- } else {
- definePlacement(canvas, entity, yOffset, false);
- }
- });
- LIFECYCLE_LOG.debug(
- "pagination.pageBreaker.end entities={} ordered={} durationMs={}",
- entities.size(),
- orderedEntities.size(),
- TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startNanos));
- }
-
- /**
- * Builds one pagination order for the whole pass without repeated pairwise ancestry checks.
- *
- * The queue only admits nodes whose descendants are already processed, which preserves
- * the child-before-parent contract. Between unrelated ready nodes we still use layout
- * geometry so the order remains visually intuitive and deterministic.
- */
- private List> orderedForPagination(Map entities,
- Map parentById,
- Map> childrenByParent,
- Map depthById) {
- Map remainingChildren = new LinkedHashMap<>();
- PriorityQueue ready = new PriorityQueue<>(paginationPriority(entities, depthById));
- List> ordered = new ArrayList<>(entities.size());
-
- for (UUID entityId : entities.keySet()) {
- int childCount = 0;
- for (UUID childId : childrenByParent.getOrDefault(entityId, List.of())) {
- if (entities.containsKey(childId)) {
- childCount++;
- }
- }
- remainingChildren.put(entityId, childCount);
- if (childCount == 0) {
- ready.add(entityId);
- }
- }
-
- while (!ready.isEmpty()) {
- UUID entityId = ready.poll();
- Entity entity = entities.get(entityId);
- if (entity == null) {
- continue;
- }
-
- ordered.add(Map.entry(entityId, entity));
-
- UUID parentId = parentById.get(entityId);
- if (parentId == null || !remainingChildren.containsKey(parentId)) {
- continue;
- }
-
- int unresolvedChildren = remainingChildren.merge(parentId, -1, Integer::sum);
- if (unresolvedChildren == 0) {
- ready.add(parentId);
- }
- }
-
- if (ordered.size() != entities.size()) {
- throw new IllegalStateException("Cycle detected while computing pagination order.");
- }
-
- return ordered;
- }
-
- /**
- * Returns the resolved layout Y-coordinate used for pagination ordering.
- *
- * We use {@link ComputedPosition} rather than renderer-specific coordinates because pagination operates on
- * the layout result, before the final rendering pass writes output streams.
- */
- private double positionY(Entity entity) {
- return entity.require(ComputedPosition.class).y();
- }
-
- /**
- * Ready-queue ordering for unrelated nodes in the pagination walk.
- *
- *
The priority keys are chosen so that visually higher entities and deeper
- * subtree leaves are processed first:
- *
- * - Y-position descending — top-of-page entities come first in engine coordinates
- * - Depth descending — deeper children break ties in favour of leaves
- * - UUID string — deterministic fallback for bit-identical layouts
- *
- *
- * @param entities entity map used during this pagination pass
- * @param depthById precomputed depth map from the layout traversal
- * @return comparator for the {@link java.util.PriorityQueue} used in
- * {@link #orderedForPagination}
- */
- private Comparator paginationPriority(Map entities, Map depthById) {
- // Pre-compute Y and depth so PriorityQueue compares stay alloc-free.
- // The previous implementation called requireEntity(entities, id) twice
- // per compare (once for Y, once for depth) and finally fell back to
- // UUID::toString which allocates a 36-char string for every tie-break.
- Map keys = new java.util.HashMap<>(entities.size() * 2);
- for (UUID id : entities.keySet()) {
- Entity entity = entities.get(id);
- double y = entity.require(ComputedPosition.class).y();
- int depth = depthOf(id, entity, depthById);
- keys.put(id, new PaginationKey(y, depth));
- }
- return (left, right) -> {
- PaginationKey leftKey = keys.get(left);
- PaginationKey rightKey = keys.get(right);
- int yCompare = Double.compare(rightKey.y(), leftKey.y()); // Y descending
- if (yCompare != 0) {
- return yCompare;
- }
- int depthCompare = Integer.compare(rightKey.depth(), leftKey.depth()); // depth descending
- if (depthCompare != 0) {
- return depthCompare;
- }
- return left.compareTo(right); // UUID.compareTo() is alloc-free (MSB then LSB)
- };
- }
-
- /**
- * Lightweight precomputed sort key for the pagination priority queue.
- */
- private record PaginationKey(double y, int depth) {
- }
-
- /**
- * Retrieves an entity from the pagination map, throwing if absent.
- *
- * @param entities entity map active during the current pagination pass
- * @param entityId id to look up
- * @return the entity, never {@code null}
- * @throws IllegalStateException if the entity is missing
- */
- private Entity requireEntity(Map entities, UUID entityId) {
- Entity entity = entities.get(entityId);
- if (entity == null) {
- throw new IllegalStateException("Entity not found for pagination: " + entityId);
- }
- return entity;
- }
-
- /**
- * Returns the layout depth of an entity, falling back to its {@link Layer}
- * value when the depth map has no entry (e.g. for dynamically added entities).
- *
- * @param entityId entity id to look up
- * @param entity the resolved entity instance
- * @param depthById precomputed depth map from the layout traversal
- * @return depth value, zero if neither source has a value
- */
- private int depthOf(UUID entityId, Entity entity, Map depthById) {
- return depthById.getOrDefault(entityId, entity.getComponent(Layer.class)
- .map(Layer::value)
- .orElse(0));
- }
-
- /**
- * Determines and adds a {@link Placement} component to an entity.
- * This method updates the entity's vertical position based on the total {@code yOffset},
- * calculates its position on the page, and stores the result in a new {@code Placement} component.
- *
- * For non-breakable entities, the calculator may shift the whole object to the next page. That shift can
- * propagate upward into parent container sizing before this method writes the final placement.
- *
- * @param canvas The canvas configuration.
- * @param entity The entity to process.
- * @param yOffset The total Y-axis offset accumulated from previous elements.
- * @param isBreakable A flag indicating whether the element can be broken across pages.
- */
- private void definePlacement(Canvas canvas, Entity entity, Offset yOffset, boolean isBreakable) {
- var computedPosition = entity.require(ComputedPosition.class);
- entity.require(ContentSize.class);
- log.debug("Defining position for {}", entity);
- YPositionOnPage position;
- try {
- position = pageLayoutCalculator.definePositionOnPage(computedPosition.y() + yOffset.y(), entity, 0, canvas, yOffset, isBreakable);
- } catch (Exception e) {
- log.error("Error while defining position for {}", entity.printInfo(), e);
- throw new RuntimeException(entity.printInfo(), e);
- }
-
- Placement placement = setYInPlacement(entity, position);
- entity.addComponent(placement);
- }
-
- /**
- * Handles pagination for block-text entities, including line-level splitting
- * before the container-level {@link Placement} is written.
- *
- * @param canvas page canvas configuration
- * @param entity block-text entity
- * @param yOffset running pagination offset
- */
- private void definePlacementForBlockText(Canvas canvas, Entity entity, Offset yOffset) {
- try {
- //definition a blockText
- textBlockProcessor.processPageBreakerBlockText(entity, entityManager, canvas, yOffset);
- //definition a placementForBlockTextContainer
- definePlacement(canvas, entity, yOffset, true);
- } catch (BigSizeElementException | IOException ex) {
- log.error("Error while defining position for block text {}", entity.printInfo(), ex);
- throw new RuntimeException(String.format("Error while defining position for block text %s, %s", entity.printInfo(), ex));
- }
-
- }
-
-
- /**
- * Initiates the page-breaking process for all entities managed by the {@link EntityManager}.
- * This method automatically finds the active rendering system ({@link RenderingSystemECS}) to retrieve
- * canvas information ({@link Canvas}).
- *
- */
- public void process() {
- log.debug("Breaking pages");
- RenderingSystemECS renderingSystemECS = null;
- log.debug("Definition a RenderingSystemECS");
- for (SystemECS system : entityManager.getSystems().getStream().toList()) {
- if (RenderingSystemECS.class.isAssignableFrom(system.getClass())) {
- renderingSystemECS = (RenderingSystemECS) system;
- break;
- }
-
- }
- if (renderingSystemECS == null) {
- log.error("No RenderingSystemECS found");
- throw new IllegalStateException("No RenderingSystemECS found");
- }
- process(entityManager.getEntities(), renderingSystemECS.canvas());
- }
-}
-
diff --git a/src/main/java/com/demcha/compose/engine/pagination/PageLayoutCalculator.java b/src/main/java/com/demcha/compose/engine/pagination/PageLayoutCalculator.java
deleted file mode 100644
index c790ec55d..000000000
--- a/src/main/java/com/demcha/compose/engine/pagination/PageLayoutCalculator.java
+++ /dev/null
@@ -1,270 +0,0 @@
-package com.demcha.compose.engine.pagination;
-
-import com.demcha.compose.engine.core.Canvas;
-import com.demcha.compose.engine.components.core.Entity;
-import com.demcha.compose.engine.components.geometry.ContentSize;
-import com.demcha.compose.engine.components.style.Margin;
-import com.demcha.compose.engine.core.EntityManager;
-import com.demcha.compose.engine.exceptions.BigSizeElementException;
-import lombok.Getter;
-import lombok.NonNull;
-import lombok.RequiredArgsConstructor;
-import lombok.extern.slf4j.Slf4j;
-
-@Slf4j
-@RequiredArgsConstructor
-public class PageLayoutCalculator {
- @Getter
- private final EntityManager entityManager;
-
-
-
- public static double downShift(double yPosition,
- double elementHeight,
- double elementMarginBottom,
- double elementMarginTop,
- double canvasHeight,
- double canvasMarginBottom,
- double canvasMarginTop
- ) throws BigSizeElementException {
- double elementOuterHeight = elementHeight + elementMarginBottom + elementMarginTop;
- double canvasInnerHeight = canvasHeight - canvasMarginBottom - canvasMarginTop;
- if (elementOuterHeight > canvasInnerHeight) {
- log.error("Element is too large and non-breakable — it cannot fit between the bounding lines with margins.\n" +
- "requiredHeight: {} availableHeight: {}", elementOuterHeight, canvasInnerHeight);
-
- throw new BigSizeElementException(yPosition, elementHeight, canvasInnerHeight);
- }
- if (yPosition - elementMarginBottom > canvasMarginBottom) {
- if (yPosition + elementHeight + elementMarginTop > canvasHeight - canvasMarginTop) {
- return ((yPosition + elementHeight + elementMarginTop) - (canvasHeight - canvasMarginTop)) * -1;
- } else {
- return 0.0;
- }
- }
-
-
- if (yPosition - elementMarginBottom < canvasMarginBottom) {
- return (yPosition + elementHeight + elementMarginTop + canvasMarginTop ) * -1;
-
- } else {
- return 0.0;
- }
-
- }
-
-
- /**
- * Calculates the page offset relative to a starting page.
- *
- * Given an absolute Y-coordinate and a fixed {@code pageHeight} ($H$), this
- * determines how many pages away the coordinate is from the page that starts at Y=0.
- * The offset is defined as follows (with the Y-axis pointing down):
- *
- * - $Y \in [0, H)$ $\rightarrow$ 0 (same page)
- * - $Y \in [H, 2H)$ $\rightarrow$ -1 (one page down)
- * - $Y \in [-H, 0)$ $\rightarrow$ 1 (one page up)
- *
- * The calculation formula is: $- \lfloor \frac{Y}{H} \rfloor$.
- *
- * @param currentPositionY The entity's absolute Y-coordinate.
- * @param pageHeight The height of a single page (must be a finite positive number).
- * @return The integer page offset.
- * @throws IllegalArgumentException if {@code pageHeight} is not a finite positive number.
- */
- public static int definePage(double currentPositionY, double pageHeight) {
- if (!(pageHeight > 0.0) || Double.isNaN(pageHeight) || Double.isInfinite(pageHeight)) {
- throw new IllegalArgumentException("pageHeight must be a finite positive number; was " + pageHeight);
- }
-
- double pageInnerHeight;
- double pageBottomMargin;
- double pageTopMargin;
-
- double objectPosition;
- double objectHeight;
- double objectMarginTop;
- double objectMarginBottom;
-
- log.debug("definePage: pageHeight={}, y={}", pageHeight, currentPositionY);
-
- // floor(y / H) is ... -2, -1, 0, 1, 2 ...
- // We want offset = -floor(y/H)
- int definedPage = (int) Math.floor(currentPositionY / pageHeight) * -1;
-
- log.debug("definePage -> offset={}", definedPage);
- return definedPage;
- }
-
- /**
- * True mathematical modulo (always positive).
- * a - element height
- * m - canvas height
- */
- private static double positiveModulo(double a, double m) {
- double r = a % m;
- return (r < 0) ? r + m : r;
- }
-
- private static void debugCalculation(double currentPositionY, double objectHeight, double objectMarginTop, double objectMarginBottom, int currentPageNumber, double canvasHeight, double canvasMarginTop, double canvasMarginBottom, Offset yOffset, boolean isBreakable) {
- log.debug("""
- --- Defining Position On Page ---
- Object Settings:
- - currentPositionY: {}
- - objectHeight: {}
- - objectMargin (T/B): {}/{}
- Canvas Settings:
- - canvasHeight: {}
- - canvasMargin (T/B): {}/{}
- State:
- - currentPageNumber: {}
- - isBreakable: {}
- - yOffset (before): {}
- """,
- currentPositionY, objectHeight, objectMarginTop, objectMarginBottom,
- canvasHeight, canvasMarginTop, canvasMarginBottom,
- currentPageNumber, isBreakable, yOffset);
- }
-
- public static double downShift(Entity e, float startY, Canvas canvas) throws BigSizeElementException {
- var size = e.getComponent(ContentSize.class).orElseThrow();
- var margin = e.getComponent(Margin.class).orElse(Margin.zero());
-
-
- return downShift(startY, size.height(), margin.bottom(), margin.top(), canvas.height(), canvas.margin().bottom(), canvas.margin().top());
- }
-
-
- /**
- * Internal delegate method for {@link #definePositionOnPage}.
- */
- private YPositionOnPage definePositionOnPage(double currentPositionY,
- @NonNull Entity entity,
- int currentPageNumber, double canvasHigh, double canvasMarginTop, double canvasMarginBottom, //Canvas Settings
- @NonNull Offset yOffset,
- boolean isBreakable) {
- var size = entity.getComponent(ContentSize.class).orElseThrow();
- var margin = entity.getComponent(Margin.class).orElse(Margin.zero());
-
- double objectHeight = size.height();
- double objectMarginTop = margin.top();
- double objectMarginBottom = margin.bottom();
- try {
- return calculatePageCoordinates(currentPositionY, objectHeight, objectMarginTop, objectMarginBottom, currentPageNumber, canvasHigh, canvasMarginTop, canvasMarginBottom, yOffset, isBreakable, entity);
- } catch (BigSizeElementException | PageOutOfBoundException e) {
- throw new RuntimeException(entity.printInfo(), e);
- }
-
- }
-
- /**
- * Internal delegate method for {@link #definePositionOnPage}.
- */
- public YPositionOnPage definePositionOnPage(double currentPositionY,
- Entity entity,
- int currentPageNumber, Canvas canvas, //Canvas Settings
- Offset yOffset,
- boolean isBreakable) {
- log.debug("Defining position on page for {}", entity);
- log.debug("currentPositionY: {}, currentPageNumber: {} , yOffset: {}, isBreakable: {}", currentPositionY, currentPageNumber, yOffset, isBreakable);
-
- var size = entity.getComponent(ContentSize.class).orElseThrow();
- var margin = entity.getComponent(Margin.class).orElse(Margin.zero());
-
- double objectHeight = size.height();
- double objectMarginTop = margin.top();
- double objectMarginBottom = margin.bottom();
- //canvas
- double canvasHeight = canvas.height();
- double canvasMarginTop = canvas.margin().top();
- double canvasMarginBottom = canvas.margin().bottom();
-
- try {
-
- return calculatePageCoordinates(currentPositionY, objectHeight, objectMarginTop, objectMarginBottom, currentPageNumber, canvasHeight, canvasMarginTop, canvasMarginBottom, yOffset, isBreakable, entity);
-
- } catch (BigSizeElementException e) {
- log.error("{}{}", entity.printInfo(), e.getMessage());
- throw new RuntimeException(entity.printInfo(), e);
- } catch (PageOutOfBoundException e) {
-
- throw new RuntimeException(e);
- }
-
- }
-
- /**
- * Core logic for calculating the position on a page.
- */
- public YPositionOnPage calculatePageCoordinates(double currentPositionY,
- double objectHeight, double objectMarginTop, double objectMarginBottom, //object Settings
- int currentPageNumber, double canvasHeight, double canvasMarginTop, double canvasMarginBottom, //Canvas Settings
- Offset yOffset, boolean isBreakable, Entity entity) throws BigSizeElementException, PageOutOfBoundException {
-
- if (!(canvasHeight > 0.0) || Double.isInfinite(canvasHeight)) {
- throw new IllegalArgumentException("canvasHeight must be a finite positive number; was " + canvasHeight);
- }
-
- if (log.isDebugEnabled()) {
- debugCalculation(currentPositionY, objectHeight, objectMarginTop, objectMarginBottom, currentPageNumber, canvasHeight, canvasMarginTop, canvasMarginBottom, yOffset, isBreakable);
- }
-
- int pageOffset = definePage(currentPositionY, canvasHeight); // may be negative, zero, or positive
- int startPage = Math.addExact(currentPageNumber, pageOffset);
- log.debug("startPage: {}, pageOffset: {}", startPage, pageOffset);
- if (startPage < 0) {
- log.error("Invalid page number {}", startPage);
- throw new IllegalArgumentException("Page number is less than zero after pageOffset: " + startPage);
- }
-
-
- // Normalize y into [0, pageHeight)
- double yInPage = positiveModulo(currentPositionY, canvasHeight);
- double shift = 0.0;
-
-
- int endPage = startPage;
- if (isBreakable) {
- double requireSpace = yInPage + objectHeight + objectMarginTop;
- double currentSize = Math.min(yInPage + requireSpace, canvasHeight - canvasMarginTop);
- if (currentSize < requireSpace) {
- log.debug("Defining a new endPage: {}", endPage);
-
- while (requireSpace >= currentSize) {
- currentSize += canvasHeight;
- endPage--;
- if (endPage < 0) {
- endPage = 0;
- log.error("Page out of bound, Current PageNumber {}, {}", endPage, entity.printInfo());
- //TODO посмотреть почему делает меньше 0
-// throw new PageOutOfBoundException( String.format("Page out of bound, Current PageNumber %d, %s", endPage, entity.printInfo()));
- }
- }
-
- }
- } else {
- log.trace("Element is not breakable {}", entity);
- shift = downShift(yInPage, objectHeight, objectMarginBottom, objectMarginTop, canvasHeight, canvasMarginBottom, canvasMarginTop);
- if (shift == 0) {
- return new YPositionOnPage(yInPage, startPage, endPage);
- }
- yInPage += shift;
- pageOffset = definePage(yInPage, canvasHeight);
- yInPage = positiveModulo(yInPage, canvasHeight);
- startPage += pageOffset;
- log.debug("Shift: {}", shift);
- endPage = startPage;
- }
- if (entity != null) {
- ParentContainerUpdater.updateParentContainerSize(entity, entityManager, shift);
- }
- yOffset.incrementY(shift);
- YPositionOnPage result = new YPositionOnPage(yInPage, startPage, endPage);
-
-
- log.debug("Defined position on page: {}", result);
- return result;
-
- }
-
-}
diff --git a/src/main/java/com/demcha/compose/engine/pagination/PaginationLayoutSystem.java b/src/main/java/com/demcha/compose/engine/pagination/PaginationLayoutSystem.java
deleted file mode 100644
index 8c1a32f0a..000000000
--- a/src/main/java/com/demcha/compose/engine/pagination/PaginationLayoutSystem.java
+++ /dev/null
@@ -1,54 +0,0 @@
-package com.demcha.compose.engine.pagination;
-
-import com.demcha.compose.engine.components.geometry.ContentSize;
-import com.demcha.compose.engine.components.layout.coordinator.ComputedPosition;
-import com.demcha.compose.engine.components.layout.coordinator.Placement;
-import com.demcha.compose.engine.core.EntityManager;
-import com.demcha.compose.engine.core.SystemECS;
-import lombok.extern.slf4j.Slf4j;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.util.concurrent.TimeUnit;
-
-/**
- * Simple pagination fallback that converts resolved positions into single-page
- * {@code Placement} components.
- *
- * This implementation does not perform advanced page breaking. Instead it maps
- * each entity's existing {@code ComputedPosition} and {@code ContentSize} to a
- * placement on page {@code 0}. It is therefore mainly useful as a minimal
- * placement bridge when the full page-breaker path is not used.
- *
- */
-@Slf4j
-public class PaginationLayoutSystem implements SystemECS {
- private static final Logger LIFECYCLE_LOG = LoggerFactory.getLogger("com.demcha.compose.engine.pagination");
-
-
- /**
- * Converts each positioned entity into a basic single-page placement.
- *
- * @param entityManager registry containing entities with computed positions
- */
- @Override
- public void process(EntityManager entityManager) {
- long startNanos = System.nanoTime();
- LIFECYCLE_LOG.debug("pagination.fallback.start entities={}", entityManager.getEntities().size());
- entityManager.getEntities().forEach((id, entity) -> {
- if (entity.has(ComputedPosition.class)) {
- double x = entity.getComponent(ComputedPosition.class).get().x();
- double y = entity.getComponent(ComputedPosition.class).get().y();
- ContentSize contentSize = entity.getComponent(ContentSize.class).orElseThrow();
- Placement placement = new Placement(x, y, contentSize.width(), contentSize.height(), 0, 0);
- entity.addComponent(placement);
- }
- });
- LIFECYCLE_LOG.debug(
- "pagination.fallback.end entities={} durationMs={}",
- entityManager.getEntities().size(),
- TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startNanos));
-
- }
-
-}
diff --git a/src/main/java/com/demcha/compose/engine/pagination/TextBlockProcessor.java b/src/main/java/com/demcha/compose/engine/pagination/TextBlockProcessor.java
deleted file mode 100644
index 98e66af70..000000000
--- a/src/main/java/com/demcha/compose/engine/pagination/TextBlockProcessor.java
+++ /dev/null
@@ -1,252 +0,0 @@
-package com.demcha.compose.engine.pagination;
-
-import com.demcha.compose.engine.components.content.text.BlockTextData;
-import com.demcha.compose.engine.components.content.text.BlockTextLineMetrics;
-import com.demcha.compose.engine.components.content.text.LineTextData;
-import com.demcha.compose.engine.components.content.text.TextStyle;
-import com.demcha.compose.engine.components.core.Entity;
-import com.demcha.compose.engine.components.geometry.ContentSize;
-import com.demcha.compose.engine.components.geometry.InnerBoxSize;
-import com.demcha.compose.engine.components.layout.Align;
-import com.demcha.compose.engine.components.layout.coordinator.ComputedPosition;
-import com.demcha.compose.engine.components.layout.coordinator.RenderingPosition;
-import com.demcha.compose.engine.components.renderable.BlockText;
-import com.demcha.compose.engine.components.style.Padding;
-import com.demcha.compose.engine.core.Canvas;
-import com.demcha.compose.engine.core.EntityManager;
-import com.demcha.compose.engine.exceptions.BigSizeElementException;
-import com.demcha.compose.engine.measurement.TextMeasurementSystem;
-import lombok.NonNull;
-import lombok.extern.slf4j.Slf4j;
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
-
-@Slf4j
-public class TextBlockProcessor {
- private final PageLayoutCalculator pageLayoutCalculator;
-
- public TextBlockProcessor(EntityManager entityManager) {
- this.pageLayoutCalculator = new PageLayoutCalculator(entityManager);
- }
-
- private static boolean checkBlockCondition(Entity e) {
- if (!e.hasAssignable(BlockTextData.class)) {
- log.debug("Entity doesn't have TextComponent; skipping: {}", e);
- return true;
- }
-
- var positionOpt = RenderingPosition.from(e);
- if (positionOpt.isEmpty()) {
- log.warn("TextComponent has no RenderingPosition; skipping: {}", e);
- return true;
- }
- return false;
- }
-
- /**
- * Calculates and assigns the position for each line of text within a {@link BlockText}.
- * This method iterates through the lines, determines their position on the page, handles
- * page breaks, and updates the entity's {@link BlockTextData} component with the new,
- * positioned data.
- *
- * @param e The entity containing the {@link BlockText}.
- * offsets caused by page breaks within the text block.
- * @throws IOException if an error occurs while working with fonts.
- * @throws IllegalStateException if the entity is missing required components.
- */
- public void processLayoutSystemTextLines(Entity e) throws IOException, BigSizeElementException {
-
- if (checkBlockCondition(e)) {
- return;
- }
-
- var position = e.getComponent(ComputedPosition.class).orElseThrow();
- InnerBoxSize innerBoxSize = InnerBoxSize.from(e).orElseThrow();
- Padding padding = e.getComponent(Padding.class).orElse(Padding.zero());
- BlockText.ValidatedTextData validateText = getValidatedTextData(e);
- BlockTextData blockTextComponent = validateText.textValue();
- var blockTextData = blockTextComponent.lines();
-
- List lineMetrics = resolveLineMetrics(blockTextData, validateText.style());
- TextMeasurementSystem.LineMetrics baseMetrics = BlockTextLineMetrics.resolveStyleMetrics(
- pageLayoutCalculator.getEntityManager(),
- validateText.style());
-
- double spacing = e.getComponent(Align.class).orElseGet(() -> {
- log.warn("TextComponent has no Align; using default: {}", e);
- return Align.defaultAlign(2);
- }).spacing();
-
- if (log.isDebugEnabled()) {
- log.debug("Rendering textBlock '{}' at position ({}, {})", blockTextData, position.x(), position.y());
- log.debug("baseTextStyle={}", validateText.style());
- }
-
- double startX = position.x();
- double cursorTop = position.y() + padding.bottom() + innerBoxSize.height();
- List assignPositionTextData = new ArrayList<>(blockTextData.size());
-
- for (int i = 0; i < blockTextData.size(); i++) {
- LineTextData ltd = blockTextData.get(i);
- TextMeasurementSystem.LineMetrics metrics = lineMetrics.get(i);
- double baselineOffset = baselineOffset(ltd, metrics);
- double currentX = ltd.x() + startX;
- double baselineY = cursorTop - metrics.lineHeight() + baselineOffset;
-
- if (log.isDebugEnabled()) {
- log.debug("line {} metrics={} baselineY={}", i, metrics, baselineY);
- }
-
- // Preserve cached measurement payload and only rewrite resolved placement.
- assignPositionTextData.add(new LineTextData(ltd, currentX, baselineY, 0));
-
- cursorTop -= metrics.lineHeight();
- if (i < lineMetrics.size() - 1) {
- cursorTop -= BlockTextLineMetrics.interLineGap(
- metrics,
- lineMetrics.get(i + 1),
- baseMetrics,
- spacing);
- }
- }
-
- e.addComponent(blockTextComponent.withLines(assignPositionTextData));
- }
-
- public Offset processPageBreakerBlockText(Entity entity,
- EntityManager entityManager,
- Canvas canvas,
- @NonNull Offset yOffset) throws IOException, BigSizeElementException {
- if (checkBlockCondition(entity)) {
- return new Offset();
- }
-
- BlockText.ValidatedTextData validatedTextData = getValidatedTextData(entity);
- BlockTextData blockTextComponent = validatedTextData.textValue();
- var blockTextData = blockTextComponent.lines();
- List lineMetrics = resolveLineMetrics(blockTextData, validatedTextData.style());
-
- double spacing = entity.getComponent(Align.class).orElseGet(() -> {
- log.warn("TextComponent has no Align; using default: {}", entity);
- return Align.defaultAlign(2);
- }).spacing();
-
- final int currentPage = 0;
- Offset entityYOffset = new Offset();
- List assignPositionTextData = new ArrayList<>(blockTextData.size());
-
- for (int i = 0; i < blockTextData.size(); i++) {
- LineTextData ltd = blockTextData.get(i);
- TextMeasurementSystem.LineMetrics metrics = lineMetrics.get(i);
- double baselineOffset = baselineOffset(ltd, metrics);
- double currentBottomY = ltd.y() - baselineOffset + yOffset.y() + entityYOffset.y();
-
- YPositionOnPage yPositionOnPage = definePositionOnPage(
- currentBottomY,
- metrics.lineHeight(),
- currentPage,
- canvas,
- entityYOffset,
- false,
- entity);
-
- double baselineY = yPositionOnPage.yPosition() + baselineOffset;
- // Preserve cached measurement payload and only rewrite page-local placement.
- assignPositionTextData.add(new LineTextData(ltd, ltd.x(), baselineY, yPositionOnPage.startPage()));
- }
-
- finalizePageBreakingAndDefinition(entity, blockTextComponent, yOffset, assignPositionTextData, entityYOffset);
- return entityYOffset;
- }
-
- private void finalizePageBreakingAndDefinition(Entity entity,
- BlockTextData blockTextData,
- Offset yOffset,
- List assignPositionTextData,
- Offset entityYOffset) {
- BlockTextData newBlockTextData = blockTextData.withLines(assignPositionTextData);
-
- var size = entity.getComponent(ContentSize.class).orElseThrow();
- var newSize = new ContentSize(size.width(), size.height() + Math.abs(entityYOffset.y()));
- entity.addComponent(newSize);
-
- yOffset.incrementY(entityYOffset);
- log.debug("Returned Offset: {} , {}", yOffset, entity);
- entity.addComponent(newBlockTextData);
- }
-
- private YPositionOnPage definePositionOnPage(double currentY,
- double textHeight,
- int currentPage,
- Canvas canvas,
- Offset entityYOffset,
- boolean isBreakable,
- Entity entity) {
- double canvasHeight = canvas.height();
- double canvasMarginTop = canvas.margin().top();
- double canvasMarginBottom = canvas.margin().bottom();
- try {
- return pageLayoutCalculator.calculatePageCoordinates(
- currentY,
- textHeight,
- 0.0,
- 0.0,
- currentPage,
- canvasHeight,
- canvasMarginTop,
- canvasMarginBottom,
- entityYOffset,
- isBreakable,
- entity);
- } catch (BigSizeElementException ex) {
- log.error("BigSizeElementException {}, {}", ex, entity);
- throw new RuntimeException(String.format("BigSizeElementException %s, %s", ex, entity.printInfo()));
- }
- }
-
- private BlockText.ValidatedTextData getValidatedTextData(Entity e) {
- var textValue = e.getComponent(BlockTextData.class).orElse(BlockTextData.empty());
- TextStyle style = e.getComponent(TextStyle.class).orElse(TextStyle.DEFAULT_STYLE);
- return new BlockText.ValidatedTextData(style, textValue);
- }
-
- private List resolveLineMetrics(List lines, TextStyle fallbackStyle) {
- List metrics = new ArrayList<>(lines.size());
- for (LineTextData line : lines) {
- metrics.add(line.hasCachedLineMetrics()
- ? line.lineMetrics()
- : BlockTextLineMetrics.resolveLineMetrics(
- pageLayoutCalculator.getEntityManager(),
- line,
- fallbackStyle));
- }
- return metrics;
- }
-
- private double baselineOffset(LineTextData line, TextMeasurementSystem.LineMetrics metrics) {
- if (line.hasCachedBaselineOffset()) {
- return line.baselineOffset();
- }
- return metrics.baselineOffsetFromBottom();
- }
-
- /**
- * Functional interface to decouple TextProcessor from the main PageBreaker logic.
- */
- @FunctionalInterface
- public interface PagePositionCalculator {
- YPositionOnPage calculate(
- double currentY,
- double height,
- double marginTop,
- double marginBottom,
- int currentPage,
- Canvas canvas,
- Offset yOffset,
- boolean isBreakable,
- Entity entity
- ) throws BigSizeElementException;
- }
-}
diff --git a/src/main/java/com/demcha/compose/engine/pagination/YPositionOnPage.java b/src/main/java/com/demcha/compose/engine/pagination/YPositionOnPage.java
deleted file mode 100644
index 733514c46..000000000
--- a/src/main/java/com/demcha/compose/engine/pagination/YPositionOnPage.java
+++ /dev/null
@@ -1,8 +0,0 @@
-package com.demcha.compose.engine.pagination;
-
-public record YPositionOnPage(double yPosition, int startPage, int endPage){
- public YPositionOnPage(double yPosition, int startPage) {
- this(yPosition, startPage, startPage);
- }
-
-}
diff --git a/src/main/java/com/demcha/compose/engine/pagination/package-info.java b/src/main/java/com/demcha/compose/engine/pagination/package-info.java
index 9f2097079..07d256192 100644
--- a/src/main/java/com/demcha/compose/engine/pagination/package-info.java
+++ b/src/main/java/com/demcha/compose/engine/pagination/package-info.java
@@ -1,19 +1,16 @@
/**
* Pagination and page-placement helpers of the legacy ECS engine.
*
- * This package paginates the resolved {@code Entity} graph for the original
- * {@code Entity}-based pipeline ({@code PageBreaker} for the pagination walk,
- * {@code PageLayoutCalculator} for page-coordinate math, {@code TextBlockProcessor}
- * for block-text fragmentation, {@code ParentContainerUpdater} for upward
- * size/position propagation). It is renderer-neutral, but it is part of the
- * parallel legacy ECS engine: the canonical pipeline
- * ({@code com.demcha.compose.document.layout}) does its own pagination and
- * imports nothing here.
+ * This package holds the {@code Entity}-based pagination helpers and markers
+ * ({@code ParentContainerUpdater} for upward size/position propagation, the
+ * {@code Breakable} marker, and {@code Offset}). It is renderer-neutral; the
+ * canonical pipeline ({@code com.demcha.compose.document.layout}) does its own
+ * pagination.
*
- * @deprecated Legacy ECS engine, superseded by the canonical
- * {@code com.demcha.compose.document.layout} pipeline. Not reachable from
- * any public API; retained only for legacy regression tests and a candidate
- * for removal. Do not extend it or spend optimization effort here.
+ * @deprecated Part of the legacy {@code Entity} engine, superseded by the
+ * canonical {@code com.demcha.compose.document.layout} pipeline; a candidate
+ * for removal once the {@code Entity} model is retired. Do not spend
+ * optimization effort here.
*/
@Deprecated
package com.demcha.compose.engine.pagination;
diff --git a/src/main/java/com/demcha/compose/engine/render/EntityRenderOrder.java b/src/main/java/com/demcha/compose/engine/render/EntityRenderOrder.java
deleted file mode 100644
index 0b1975a4f..000000000
--- a/src/main/java/com/demcha/compose/engine/render/EntityRenderOrder.java
+++ /dev/null
@@ -1,135 +0,0 @@
-package com.demcha.compose.engine.render;
-
-import com.demcha.compose.engine.components.core.Entity;
-import com.demcha.compose.engine.components.layout.coordinator.ComputedPosition;
-import com.demcha.compose.engine.components.layout.coordinator.RenderingPosition;
-import com.demcha.compose.engine.components.style.Margin;
-import com.demcha.compose.engine.core.EntityManager;
-import lombok.experimental.UtilityClass;
-
-import java.util.Comparator;
-import java.util.LinkedHashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Objects;
-import java.util.UUID;
-
-/**
- * Stable renderer-neutral ordering for already-laid-out entities.
- *
- *
- * This helper belongs to the rendering layer rather than pagination because it
- * only answers "in what order should resolved entities be handed to a
- * renderer?"
- * It does not decide page-breaking or parent/child pagination rules.
- *
- *
- *
- * The ordering contract is:
- *
- *
- * - higher {@link RenderingPosition#y()} first (top to bottom in engine
- * coordinates)
- * - lower {@link RenderingPosition#x()} first for stable left-to-right
- * ties
- * - original layer order when provided
- * - {@link UUID} as the final deterministic fallback
- *
- */
-@UtilityClass
-public class EntityRenderOrder {
-
- /**
- * Resolves entities from the manager and returns them sorted by rendering
- * position.
- *
- *
- * Duplicate or missing entity IDs are silently skipped. The returned map
- * preserves the computed render order.
- *
- *
- * @param entityManager entity registry to resolve entities from
- * @param entityUuids ordered list of entity IDs to include
- * @return entities sorted by the {@linkplain #renderOrderComparator rendering
- * order}
- */
- public static LinkedHashMap sortByRenderingPosition(EntityManager entityManager,
- List entityUuids) {
- Objects.requireNonNull(entityManager, "entityManager must not be null");
- Objects.requireNonNull(entityUuids, "entityUuids must not be null");
-
- LinkedHashMap entities = new LinkedHashMap<>();
- HashSet seen = new HashSet<>();
-
- for (int i = 0; i < entityUuids.size(); i++) {
- UUID entityId = entityUuids.get(i);
- if (!seen.add(entityId)) {
- continue;
- }
- Entity entity = entityManager.getEntity(entityId).orElse(null);
- if (entity == null) {
- continue;
- }
- entities.put(entityId, entity);
- }
-
- if (entities.size() <= 1) {
- return entities;
- }
-
- List resolvedEntries = new java.util.ArrayList<>(entities.size());
- int originalIndex = 0;
- for (var entry : entities.entrySet()) {
- resolvedEntries.add(resolveRenderEntry(entry.getKey(), entry.getValue(), originalIndex++));
- }
-
- resolvedEntries.sort(renderOrderComparator());
-
- LinkedHashMap ordered = new LinkedHashMap<>(resolvedEntries.size());
- for (RenderEntry entry : resolvedEntries) {
- ordered.put(entry.id(), entry.entity());
- }
- return ordered;
- }
-
- /**
- * Builds the render-order comparator using Y descending, X ascending, original
- * layer index, and UUID string as the final deterministic fallback.
- */
- private static Comparator renderOrderComparator() {
- return Comparator
- .comparingDouble(RenderEntry::y)
- .reversed()
- .thenComparingDouble(RenderEntry::x)
- .thenComparingInt(RenderEntry::originalIndex)
- .thenComparing(entry -> entry.id().toString());
- }
-
- /**
- * Resolves the lightweight sort payload for one entity.
- *
- *
- * This precomputation keeps component lookups and margin fallback handling out
- * of the comparator hot path.
- *
- *
- * @param entityId entity identifier
- * @param entity resolved entity instance
- * @param originalIndex original order inside the incoming layer
- * @return immutable render-order entry
- */
- private static RenderEntry resolveRenderEntry(UUID entityId, Entity entity, int originalIndex) {
- ComputedPosition computedPosition = entity.getComponent(ComputedPosition.class)
- .orElseThrow(() -> new IllegalStateException("Entity " + entity + " has no RenderingPosition"));
- Margin margin = entity.getComponent(Margin.class).orElse(Margin.zero());
- double x = computedPosition.x() + margin.left();
- double y = computedPosition.y() + margin.bottom();
- return new RenderEntry(entityId, entity, x, y, originalIndex);
- }
-
- /**
- * Immutable sort payload used during one render-order computation.
- */
- private record RenderEntry(UUID id, Entity entity, double x, double y, int originalIndex) {
- }
-}
diff --git a/src/main/java/com/demcha/compose/engine/render/RenderingSystemBase.java b/src/main/java/com/demcha/compose/engine/render/RenderingSystemBase.java
deleted file mode 100644
index 759409db3..000000000
--- a/src/main/java/com/demcha/compose/engine/render/RenderingSystemBase.java
+++ /dev/null
@@ -1,113 +0,0 @@
-package com.demcha.compose.engine.render;
-
-import com.demcha.compose.engine.core.Canvas;
-import com.demcha.compose.engine.components.content.shape.Side;
-import com.demcha.compose.engine.components.layout.coordinator.RenderCoordinateContext;
-import com.demcha.compose.engine.core.EntityManager;
-import com.demcha.compose.engine.render.guides.GuidLineSettings;
-import com.demcha.compose.engine.render.guides.GuidesRenderer;
-import lombok.Data;
-import lombok.experimental.Accessors;
-
-import java.awt.*;
-import java.io.IOException;
-import java.util.Optional;
-import java.util.Set;
-
-/**
- * Abstract base class for rendering systems within the Entity-Component-System (ECS) architecture.
- * This class provides common functionalities and abstract methods for rendering various graphical elements
- * such as borders, rectangles, and circles onto a rendering stream. It integrates with a {@link Canvas},
- * {@link GuidLineSettings}, and a {@link RenderStream} to manage rendering operations.
- *
- * @param The type of the rendering stream, which must implement {@link AutoCloseable}.
- */
-@Data
-@Accessors(fluent = true)
-public abstract class RenderingSystemBase implements RenderingSystemECS {
- protected final Canvas canvas;
- protected final GuidLineSettings guidLineSettings;
- protected final RenderStream stream;
- protected GuidesRenderer guidesRenderer;
- protected final RenderHandlerRegistry renderHandlers = new RenderHandlerRegistry();
- protected RenderPassSession activeRenderSession;
-
-
- /**
- * Renders a border around a specified context.
- *
- * @param stream The rendering stream to draw on.
- * @param context The rendering coordinate context defining the area for the border.
- * @param lineDash A boolean indicating whether the border should be dashed.
- * @param sides A set of {@link Side} enums indicating which sides of the context to render the border.
- * @return {@code true} if the border was rendered successfully, {@code false} otherwise.
- * @throws IOException If an I/O error occurs during rendering.
- */
- public abstract boolean renderBorder(T stream, RenderCoordinateContext context,
- boolean lineDash,
- Set sides) throws IOException;
-
- /**
- * Renders a rectangle on the rendering stream.
- *
- * @param stream The rendering stream to draw on.
- * @param context The rendering coordinate context defining the rectangle's position and dimensions.
- * @param lineDash A boolean indicating whether the rectangle's outline should be dashed.
- * @return {@code true} if the rectangle was rendered successfully, {@code false} otherwise.
- * @throws IOException If an I/O error occurs during rendering.
- */
- @Override
- public abstract boolean renderRectangle(T stream, RenderCoordinateContext context, boolean lineDash) throws IOException;
-
- /**
- * Fills a circle with a specified color on the rendering stream.
- *
- * @param stream The rendering stream to draw on.
- * @param cx The x-coordinate of the circle's center.
- * @param cy The y-coordinate of the circle's center.
- * @param r The radius of the circle.
- * @param fill The {@link Color} to fill the circle with.
- */
- public abstract void fillCircle(T stream, float cx, float cy, float r, Color fill) throws IOException;
-
-
- /**
- * Processes entities within the {@link EntityManager} for rendering.
- * This method defines the core logic for how the rendering system interacts with entities
- * to perform its rendering tasks.
- *
- * @param entityManager The entity manager containing the entities to be processed.
- */
- public abstract void process(EntityManager entityManager);
-
- @Override
- public Optional> activeRenderSession() {
- return Optional.ofNullable(activeRenderSession);
- }
-
- /**
- * Initializes the {@link GuidesRenderer}.
- *
- * Subclasses must implement this to provide a concrete, format-specific renderer (e.g., for PDF or images).
- * This method is intended to be called in the constructor to ensure the renderer is immediately available.
- *
- *
{@code
- * // Example implementation:
- * public PdfRenderingSystemECS(PDDocument doc, Canvas canvas) {
- * super(canvas, new GuidLineSettings(), new PdfStream(doc, canvas));
- * // Init and set the guides renderer
- * guidesRendererInitializer(new PdfGuidesRenderer(this));
- * }
- *
- * @Override
- * protected void guidesRendererInitializer(GuidesRenderer renderer) {
- * guidesRenderer(renderer); // Call base setter
- * }
- * }
- *
- * @param guidesRenderer The {@link GuidesRenderer} instance used to draw debug guides.
- */
- protected abstract void guidesRendererInitializer(GuidesRenderer guidesRenderer);
-
-}
-
diff --git a/src/main/java/com/demcha/compose/engine/render/guides/impl/BoxRenderImpl.java b/src/main/java/com/demcha/compose/engine/render/guides/impl/BoxRenderImpl.java
deleted file mode 100644
index f81035adf..000000000
--- a/src/main/java/com/demcha/compose/engine/render/guides/impl/BoxRenderImpl.java
+++ /dev/null
@@ -1,71 +0,0 @@
-package com.demcha.compose.engine.render.guides.impl;
-
-import com.demcha.compose.engine.components.core.Entity;
-import com.demcha.compose.engine.components.layout.coordinator.Placement;
-import com.demcha.compose.engine.components.layout.coordinator.RenderCoordinateContext;
-import com.demcha.compose.engine.render.RenderingSystemECS;
-import com.demcha.compose.engine.render.guides.BoxRender;
-import lombok.Data;
-import lombok.NonNull;
-import lombok.ToString;
-import lombok.experimental.Accessors;
-
-import java.io.IOException;
-import java.util.Objects;
-
-@Accessors(fluent = true)
-@Data
-public final class BoxRenderImpl implements BoxRender {
- @ToString.Exclude
- private final RenderingSystemECS renderingSystem;
-
- public BoxRenderImpl(RenderingSystemECS renderingSystem) {
- this.renderingSystem = renderingSystem;
- }
-
- /**
- * @param entity
- * @param stream
- * @return
- */
- @Override
- public boolean fromStream(@NonNull Entity entity, T stream) {
- Placement placement = entity.getComponent(Placement.class).orElseThrow();
- var color = renderingSystem().guidLineSettings().BOX_COLOR();
- var stroke = renderingSystem().guidLineSettings().BOX_STROKE();
- RenderCoordinateContext context = new RenderCoordinateContext(placement.x(), placement.y(), placement.width(), placement.height(), placement.startPage(), placement.endPage(), stroke, color);
-
- try {
- return renderingSystem().renderRectangle(stream, context, false);
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
-
- }
-
- @Override
- public RenderingSystemECS renderingSystem() {
- return renderingSystem;
- }
-
- @Override
- public boolean equals(Object obj) {
- if (obj == this) return true;
- if (obj == null || obj.getClass() != this.getClass()) return false;
- var that = (BoxRenderImpl) obj;
- return Objects.equals(this.renderingSystem, that.renderingSystem);
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(renderingSystem);
- }
-
- @Override
- public String toString() {
- return "BoxRenderImpl[" +
- "renderingSystem=" + renderingSystem + ']';
- }
-
-
-}
diff --git a/src/main/java/com/demcha/compose/engine/render/guides/impl/MarginRenderImpl.java b/src/main/java/com/demcha/compose/engine/render/guides/impl/MarginRenderImpl.java
deleted file mode 100644
index 8611d0698..000000000
--- a/src/main/java/com/demcha/compose/engine/render/guides/impl/MarginRenderImpl.java
+++ /dev/null
@@ -1,80 +0,0 @@
-package com.demcha.compose.engine.render.guides.impl;
-
-import com.demcha.compose.engine.components.core.Entity;
-import com.demcha.compose.engine.components.layout.coordinator.RenderCoordinateContext;
-import com.demcha.compose.engine.render.RenderingSystemECS;
-import com.demcha.compose.engine.render.guides.MarginRender;
-import lombok.Data;
-import lombok.NonNull;
-import lombok.ToString;
-import lombok.experimental.Accessors;
-
-import java.io.IOException;
-import java.util.Objects;
-import java.util.Optional;
-
-/**
- *
- */
-@Accessors(fluent = true)
-@Data
-public final class MarginRenderImpl implements MarginRender {
- @ToString.Exclude
- private final RenderingSystemECS renderingSystem;
-
- /**
- * @param renderingSystem
- */
- public MarginRenderImpl(
- RenderingSystemECS renderingSystem) {
- this.renderingSystem = renderingSystem;
- }
-
- /**
- * @param entity
- * @param stream
- * @return
- */
- @Override
- public boolean fromStream(@NonNull Entity entity, T stream) {
- Optional marginOpt = margin(entity);
- if (marginOpt.isPresent()) {
- try {
- var margin = marginOpt.get();
- renderingSystem().renderRectangle(stream, margin, true);
- markers(stream, margin);
- return true;
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
- }
- return false;
-
- }
-
- @Override
- public RenderingSystemECS renderingSystem() {
- return renderingSystem;
- }
-
- @Override
- public boolean equals(Object obj) {
- if (obj == this) return true;
- if (obj == null || obj.getClass() != this.getClass()) return false;
- var that = (MarginRenderImpl) obj;
- return Objects.equals(this.renderingSystem, that.renderingSystem);
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(renderingSystem);
- }
-
- @Override
- public String toString() {
- return "MarginRenderImpl[" +
- "renderingSystem=" + renderingSystem + ']';
- }
-
-
-}
diff --git a/src/main/java/com/demcha/compose/engine/render/guides/impl/PaddingRenderImpl.java b/src/main/java/com/demcha/compose/engine/render/guides/impl/PaddingRenderImpl.java
deleted file mode 100644
index aba99035f..000000000
--- a/src/main/java/com/demcha/compose/engine/render/guides/impl/PaddingRenderImpl.java
+++ /dev/null
@@ -1,78 +0,0 @@
-package com.demcha.compose.engine.render.guides.impl;
-
-import com.demcha.compose.engine.components.core.Entity;
-import com.demcha.compose.engine.components.layout.coordinator.RenderCoordinateContext;
-import com.demcha.compose.engine.render.RenderingSystemECS;
-import com.demcha.compose.engine.render.guides.PaddingRender;
-import lombok.Data;
-import lombok.NonNull;
-import lombok.ToString;
-import lombok.experimental.Accessors;
-
-import java.io.IOException;
-import java.util.Objects;
-import java.util.Optional;
-
-/**
- *
- */
-@Accessors(fluent = true)
-@Data
-public final class PaddingRenderImpl implements PaddingRender {
- @ToString.Exclude
- private final RenderingSystemECS renderingSystem;
-
- /**
- * @param renderingSystem
- */
- public PaddingRenderImpl(
- RenderingSystemECS renderingSystem) {
- this.renderingSystem = renderingSystem;
- }
-
- /**
- * @param entity
- * @param stream
- * @return
- */
- @Override
- public boolean fromStream(@NonNull Entity entity, T stream) {
- Optional padding = padding(entity);
- if (padding.isPresent()) {
- try {
- renderingSystem().renderRectangle(stream, padding.get(), true);
- markers(stream, padding.get());
- return true;
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
- }
- return false;
- }
-
- @Override
- public RenderingSystemECS renderingSystem() {
- return renderingSystem;
- }
-
- @Override
- public boolean equals(Object obj) {
- if (obj == this) return true;
- if (obj == null || obj.getClass() != this.getClass()) return false;
- var that = (PaddingRenderImpl) obj;
- return Objects.equals(this.renderingSystem, that.renderingSystem);
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(renderingSystem);
- }
-
- @Override
- public String toString() {
- return "PaddingRenderImpl[" +
- "renderingSystem=" + renderingSystem + ']';
- }
-
-
-}
diff --git a/src/main/java/com/demcha/compose/engine/render/guides/impl/package-info.java b/src/main/java/com/demcha/compose/engine/render/guides/impl/package-info.java
deleted file mode 100644
index 6642a6f01..000000000
--- a/src/main/java/com/demcha/compose/engine/render/guides/impl/package-info.java
+++ /dev/null
@@ -1,7 +0,0 @@
-/**
- * Debug guide-line rendering contracts and implementations.
- *
- * Ownership: Owned by render diagnostics and used only when guide overlays are enabled.
- * Extension rules: Extend per backend without changing document authoring APIs.
- */
-package com.demcha.compose.engine.render.guides.impl;
\ No newline at end of file
diff --git a/src/main/java/com/demcha/compose/engine/utils/TextSanitizer.java b/src/main/java/com/demcha/compose/engine/utils/TextSanitizer.java
deleted file mode 100644
index 2a0dea075..000000000
--- a/src/main/java/com/demcha/compose/engine/utils/TextSanitizer.java
+++ /dev/null
@@ -1,46 +0,0 @@
-package com.demcha.compose.engine.utils;
-
-import java.text.Normalizer;
-import java.util.Map;
-
-public final class TextSanitizer {
-
- private static final Map REPLACEMENTS = Map.ofEntries(
- Map.entry(0x2192, "->"), // → right arrow
- Map.entry(0x2190, "<-"), // ← left arrow
- Map.entry(0x2022, "*"), // • bullet
- Map.entry(0x2013, "-"), // – en dash
- Map.entry(0x2014, "--"), // — em dash
- Map.entry(0x2018, "'"), // ‘
- Map.entry(0x2019, "'"), // ’
- Map.entry(0x201C, "\""), // “
- Map.entry(0x201D, "\""), // ”
- Map.entry(0x2026, "..."), // … ellipsis
- Map.entry(0x00A0, " ") // NBSP
- );
-
- private TextSanitizer() {}
-
- /** Normalize + replace (no font checks). */
- public static String sanitize(String raw) {
- if (raw == null || raw.isBlank()) return "";
-
- String normalized = Normalizer.normalize(raw, Normalizer.Form.NFKC);
- StringBuilder out = new StringBuilder(normalized.length());
-
- for (int i = 0; i < normalized.length(); ) {
- int cp = normalized.codePointAt(i);
- i += Character.charCount(cp);
-
- // Keep common whitespace, remove other control chars
- if (Character.isISOControl(cp) && cp != '\n' && cp != '\r' && cp != '\t') {
- continue;
- }
-
- String candidate = REPLACEMENTS.getOrDefault(cp, new String(Character.toChars(cp)));
- out.append(candidate); // <-- IMPORTANT: append and continue
- }
-
- return out.toString();
- }
-}
diff --git a/src/test/java/com/demcha/compose/engine/pagination/ContainerExpanderTest.java b/src/test/java/com/demcha/compose/engine/pagination/ContainerExpanderTest.java
deleted file mode 100644
index bc7839f47..000000000
--- a/src/test/java/com/demcha/compose/engine/pagination/ContainerExpanderTest.java
+++ /dev/null
@@ -1,388 +0,0 @@
-package com.demcha.compose.engine.pagination;
-
-import com.demcha.compose.engine.components.core.Component;
-import com.demcha.compose.engine.components.core.Entity;
-import com.demcha.compose.engine.components.geometry.ContentSize;
-import com.demcha.compose.engine.components.geometry.InnerBoxSize;
-import com.demcha.compose.engine.components.geometry.OuterBoxSize;
-import com.demcha.compose.engine.components.style.Margin;
-import com.demcha.compose.engine.core.EntityManager;
-import com.demcha.compose.engine.exceptions.ContentSizeNotFoundException;
-import com.demcha.compose.engine.components.geometry.Expendable;
-import com.demcha.compose.engine.layout.container.ContainerExpander;
-import org.junit.jupiter.api.*;
-import org.junit.jupiter.api.extension.ExtendWith;
-import org.mockito.Mock;
-import org.mockito.MockedStatic;
-import org.mockito.junit.jupiter.MockitoExtension;
-
-import java.util.*;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.within;
-import static org.junit.jupiter.api.Assertions.*;
-import static org.mockito.Mockito.*;
-
-/**
- * Unit tests for the {@link ContainerExpander} class.
- * This class uses Mockito for mocking dependencies and AssertJ for fluent assertions.
- * It assumes that InnerBoxSize and OuterBoxSize are retrieved via static .from() methods.
- */
-@ExtendWith(MockitoExtension.class)
-@DisplayName("ContainerExpander Tests")
-class ContainerExpanderTest {
-
- // A small value for floating-point comparisons.
- private static final double EPSILON = 1e-6;
-
- /** A concrete, named class for testing the Expendable marker interface. */
- private static class TestExpendable implements Expendable, Component {}
-
- /**
- * Helper method to create a child entity.
- * The OuterBoxSize is not added directly but should be mocked in tests.
- *
- * @return A new Entity representing a child.
- */
- private Entity createChild() {
- Entity child = new Entity();
- child.addComponent(Margin.zero()); // Add default margin
- return child;
- }
-
- @Nested
- @DisplayName("expandContentSizeByChildren() Method")
- class ExpandContentSizeByChildrenTests {
-
- private Entity parent;
- private MockedStatic mockedInnerBoxSize;
- private MockedStatic mockedOuterBoxSize;
-
-
- @BeforeEach
- void setUp() {
- // Mock static .from() methods before each test
- mockedInnerBoxSize = mockStatic(InnerBoxSize.class);
- mockedOuterBoxSize = mockStatic(OuterBoxSize.class);
-
- parent = new Entity();
- parent.addComponent(new ContentSize(100, 80));
-
- // Configure the mock for the parent's InnerBoxSize
- mockedInnerBoxSize.when(() -> InnerBoxSize.from(eq(parent)))
- .thenReturn(Optional.of(new InnerBoxSize(100, 80)));
- }
-
- @AfterEach
- void tearDown() {
- // Close static mocks to prevent test pollution
- mockedInnerBoxSize.close();
- mockedOuterBoxSize.close();
- }
-
- @Test
- @DisplayName("Should throw NullPointerException if parent is null")
- void expandContentSize_withNullParent_shouldThrowException() {
- assertThrows(NullPointerException.class,
- () -> ContainerExpander.expandContentSizeByChildren(null, Collections.emptySet()),
- "parent cannot be null");
- }
-
- @Test
- @DisplayName("Should throw NullPointerException if children set is null")
- void expandContentSize_withNullChildren_shouldThrowException() {
- assertThrows(NullPointerException.class,
- () -> ContainerExpander.expandContentSizeByChildren(parent, null),
- "children cannot be null");
- }
-
- @Test
- @DisplayName("Should throw IllegalStateException if parent has no InnerBoxSize")
- void expandContentSize_parentMissingInnerBoxSize_shouldThrowException() {
- Entity parentWithoutInnerBox = new Entity();
- // Mock InnerBoxSize.from() to return empty for this specific entity
- mockedInnerBoxSize.when(() -> InnerBoxSize.from(eq(parentWithoutInnerBox))).thenReturn(Optional.empty());
-
- Set children = Set.of(createChild());
-
- Exception exception = assertThrows(IllegalStateException.class,
- () -> ContainerExpander.expandContentSizeByChildren(parentWithoutInnerBox, children));
-
- assertThat(exception.getMessage()).contains("Parent is missing InnerBoxSize");
- }
-
- @Test
- @DisplayName("Should throw ContentSizeNotFoundException if parent has no ContentSize")
- void expandContentSize_parentMissingContentSize_shouldThrowException() {
- Entity parentWithoutContentSize = new Entity();
- // Configure mock for its InnerBoxSize
- mockedInnerBoxSize.when(() -> InnerBoxSize.from(eq(parentWithoutContentSize)))
- .thenReturn(Optional.of(new InnerBoxSize(50, 50)));
-
- Entity child = createChild();
- mockedOuterBoxSize.when(() -> OuterBoxSize.from(eq(child)))
- .thenReturn(Optional.of(new OuterBoxSize(100, 100)));
-
- Set children = Set.of(child); // Child is larger
-
- assertThrows(ContentSizeNotFoundException.class,
- () -> ContainerExpander.expandContentSizeByChildren(parentWithoutContentSize, children));
- }
-
- @Test
- @DisplayName("Should return false and not expand for an empty set of children")
- void expandContentSize_withEmptyChildren_shouldReturnFalse() {
- boolean result = ContainerExpander.expandContentSizeByChildren(parent, Collections.emptySet());
-
- assertFalse(result);
- ContentSize finalSize = parent.getComponent(ContentSize.class).orElseThrow();
- assertThat(finalSize.width()).isEqualTo(100);
- assertThat(finalSize.height()).isEqualTo(80);
- }
-
- @Test
- @DisplayName("Should not expand if children fit within the parent's inner box")
- void expandContentSize_whenChildrenFit_shouldNotExpand() {
- Entity child1 = createChild();
- Entity child2 = createChild();
- mockedOuterBoxSize.when(() -> OuterBoxSize.from(eq(child1))).thenReturn(Optional.of(new OuterBoxSize(50, 40)));
- mockedOuterBoxSize.when(() -> OuterBoxSize.from(eq(child2))).thenReturn(Optional.of(new OuterBoxSize(99, 79)));
-
- Set children = Set.of(child1, child2);
-
- boolean expanded = ContainerExpander.expandContentSizeByChildren(parent, children);
-
- assertFalse(expanded, "Should not expand as children fit");
- ContentSize finalSize = parent.getComponent(ContentSize.class).orElseThrow();
- assertThat(finalSize.width()).isEqualTo(100);
- assertThat(finalSize.height()).isEqualTo(80);
- }
-
- @Test
- @DisplayName("Should expand width when a child is wider")
- void expandContentSize_whenChildIsWider_shouldExpandWidth() {
- Entity child = createChild();
- mockedOuterBoxSize.when(() -> OuterBoxSize.from(eq(child)))
- .thenReturn(Optional.of(new OuterBoxSize(120, 70)));
- Set children = Set.of(child);
-
- boolean expanded = ContainerExpander.expandContentSizeByChildren(parent, children);
-
- assertTrue(expanded, "Should expand because a child is wider");
- ContentSize finalSize = parent.getComponent(ContentSize.class).orElseThrow();
- assertThat(finalSize.width()).isCloseTo(120, within(EPSILON));
- assertThat(finalSize.height()).isCloseTo(80, within(EPSILON));
- }
-
- @Test
- @DisplayName("Should expand height when a child is taller")
- void expandContentSize_whenChildIsTaller_shouldExpandHeight() {
- Entity child = createChild();
- mockedOuterBoxSize.when(() -> OuterBoxSize.from(eq(child)))
- .thenReturn(Optional.of(new OuterBoxSize(90, 100)));
- Set children = Set.of(child);
-
- boolean expanded = ContainerExpander.expandContentSizeByChildren(parent, children);
-
- assertTrue(expanded, "Should expand because a child is taller");
- ContentSize finalSize = parent.getComponent(ContentSize.class).orElseThrow();
- assertThat(finalSize.width()).isCloseTo(100, within(EPSILON));
- assertThat(finalSize.height()).isCloseTo(100, within(EPSILON));
- }
-
- @Test
- @DisplayName("Should expand both width and height when a child is larger in both dimensions")
- void expandContentSize_whenChildIsLarger_shouldExpandBothDimensions() {
- Entity child = createChild();
- mockedOuterBoxSize.when(() -> OuterBoxSize.from(eq(child)))
- .thenReturn(Optional.of(new OuterBoxSize(150, 120)));
- Set children = Set.of(child);
-
- boolean expanded = ContainerExpander.expandContentSizeByChildren(parent, children);
-
- assertTrue(expanded, "Should expand because a child is larger");
- ContentSize finalSize = parent.getComponent(ContentSize.class).orElseThrow();
- assertThat(finalSize.width()).isCloseTo(150, within(EPSILON));
- assertThat(finalSize.height()).isCloseTo(120, within(EPSILON));
- }
-
- @Test
- @DisplayName("Should expand to max width and height from different children")
- void expandContentSize_withMultipleChildren_shouldExpandToMaxDimensions() {
- Entity widestChild = createChild();
- Entity tallestChild = createChild();
- Entity smallestChild = createChild();
-
- mockedOuterBoxSize.when(() -> OuterBoxSize.from(eq(widestChild))).thenReturn(Optional.of(new OuterBoxSize(140, 70)));
- mockedOuterBoxSize.when(() -> OuterBoxSize.from(eq(tallestChild))).thenReturn(Optional.of(new OuterBoxSize(90, 110)));
- mockedOuterBoxSize.when(() -> OuterBoxSize.from(eq(smallestChild))).thenReturn(Optional.of(new OuterBoxSize(50, 50)));
-
- Set children = Set.of(widestChild, tallestChild, smallestChild);
-
- boolean expanded = ContainerExpander.expandContentSizeByChildren(parent, children);
-
- assertTrue(expanded, "Should expand to max dimensions from different children");
- ContentSize finalSize = parent.getComponent(ContentSize.class).orElseThrow();
- assertThat(finalSize.width()).isCloseTo(140, within(EPSILON));
- assertThat(finalSize.height()).isCloseTo(110, within(EPSILON));
- }
-
- @Test
- @DisplayName("Should ignore children without an OuterBoxSize component")
- void expandContentSize_shouldIgnoreChildWithoutOuterBoxSize() {
- Entity childWithoutSize = createChild();
- Entity childWithSize = createChild();
-
- mockedOuterBoxSize.when(() -> OuterBoxSize.from(eq(childWithoutSize))).thenReturn(Optional.empty());
- mockedOuterBoxSize.when(() -> OuterBoxSize.from(eq(childWithSize))).thenReturn(Optional.of(new OuterBoxSize(50, 50)));
-
- Set children = Set.of(childWithoutSize, childWithSize);
-
- boolean expanded = ContainerExpander.expandContentSizeByChildren(parent, children);
-
- assertFalse(expanded, "Should not expand as the only sized child fits");
- }
- }
-
- @Nested
- @DisplayName("process() Method")
- class ProcessMethodTests {
-
- @Mock
- private EntityManager entityManager;
- private MockedStatic mockedInnerBoxSize;
- private MockedStatic mockedOuterBoxSize;
-
- private UUID parentUuid;
- private Entity parentEntity;
- private Map> childrenByParents;
-
- @BeforeEach
- void setUp() {
- mockedInnerBoxSize = mockStatic(InnerBoxSize.class);
- mockedOuterBoxSize = mockStatic(OuterBoxSize.class);
-
- parentEntity = new Entity();
- // Assuming Entity has getUuid() to retrieve the auto-generated UUID
- parentUuid = parentEntity.getUuid();
-
- parentEntity.addComponent(new ContentSize(100, 100));
- mockedInnerBoxSize.when(() -> InnerBoxSize.from(eq(parentEntity)))
- .thenReturn(Optional.of(new InnerBoxSize(100, 100)));
-
- childrenByParents = new LinkedHashMap<>(); // Use LinkedHashMap for predictable iteration
- }
-
- @AfterEach
- void tearDown() {
- mockedInnerBoxSize.close();
- mockedOuterBoxSize.close();
- }
-
- @Test
- @DisplayName("Should skip processing if parent entity is not found")
- void process_whenParentNotFound_shouldSkip() {
- when(entityManager.getEntity(parentUuid)).thenReturn(Optional.empty());
- childrenByParents.put(parentUuid, Collections.emptySet());
-
- ContainerExpander.process(childrenByParents, entityManager);
-
- verify(entityManager, never()).getSetEntitiesFromUuids(any());
- }
-
- @Test
- @DisplayName("Should skip parent that is not 'Expendable'")
- void process_whenParentIsNotExpendable_shouldSkip() {
- when(entityManager.getEntity(parentUuid)).thenReturn(Optional.of(parentEntity));
- childrenByParents.put(parentUuid, Collections.emptySet());
-
- ContainerExpander.process(childrenByParents, entityManager);
-
- verify(entityManager, never()).getSetEntitiesFromUuids(any());
- ContentSize finalSize = parentEntity.getComponent(ContentSize.class).orElseThrow();
- assertThat(finalSize.width()).as("Size should not change").isEqualTo(100);
- }
-
- @Test
- @DisplayName("Should process and expand an 'Expendable' parent")
- void process_withExpendableParent_shouldExpand() {
- // FIX: Added the Expendable component to ensure the parent is processed.
- parentEntity.addComponent(new TestExpendable());
-
- Entity childEntity1 = createChild();
- UUID childUuid1 = childEntity1.getUuid();
- Set childUuids = Set.of(childUuid1);
- Set childEntities = Set.of(childEntity1);
-
- // FIX: Used the parent's UUID as the key, not the child's.
- childrenByParents.put(parentUuid, childUuids);
-
- when(entityManager.getEntity(parentUuid)).thenReturn(Optional.of(parentEntity));
- when(entityManager.getSetEntitiesFromUuids(childUuids)).thenReturn(childEntities);
- mockedOuterBoxSize.when(() -> OuterBoxSize.from(eq(childEntity1)))
- .thenReturn(Optional.of(new OuterBoxSize(150, 80)));
-
- ContainerExpander.process(childrenByParents, entityManager);
-
- ContentSize finalSize = parentEntity.getComponent(ContentSize.class).orElseThrow();
- assertThat(finalSize.width()).isCloseTo(150, within(EPSILON));
- assertThat(finalSize.height()).isCloseTo(100, within(EPSILON));
- }
-
- @Test
- @DisplayName("Should handle multiple parents correctly")
- void process_withMultipleParents_shouldProcessAll() {
- // --- Parent 1 (Expendable, should expand) ---
- // FIX: Added the Expendable component.
- parentEntity.addComponent(new TestExpendable());
- Entity childEntity1 = createChild();
- UUID childUuid1 = childEntity1.getUuid();
- childrenByParents.put(parentUuid, Set.of(childUuid1));
- when(entityManager.getEntity(parentUuid)).thenReturn(Optional.of(parentEntity));
- when(entityManager.getSetEntitiesFromUuids(Set.of(childUuid1))).thenReturn(Set.of(childEntity1));
- mockedOuterBoxSize.when(() -> OuterBoxSize.from(eq(childEntity1)))
- .thenReturn(Optional.of(new OuterBoxSize(120, 90)));
-
- // --- Parent 2 (Not Expendable, should be skipped) ---
- Entity parent2Entity = new Entity();
- UUID parent2Uuid = parent2Entity.getUuid();
- parent2Entity.addComponent(new ContentSize(50, 50));
- mockedInnerBoxSize.when(() -> InnerBoxSize.from(eq(parent2Entity)))
- .thenReturn(Optional.of(new InnerBoxSize(50, 50)));
- childrenByParents.put(parent2Uuid, Collections.emptySet());
- when(entityManager.getEntity(parent2Uuid)).thenReturn(Optional.of(parent2Entity));
-
- // --- Parent 3 (Expendable, but children fit, should not expand) ---
- Entity parent3Entity = new Entity();
- UUID parent3Uuid = parent3Entity.getUuid();
- // FIX: Added the Expendable component.
- parent3Entity.addComponent(new TestExpendable());
- parent3Entity.addComponent(new ContentSize(200, 200));
- mockedInnerBoxSize.when(() -> InnerBoxSize.from(eq(parent3Entity)))
- .thenReturn(Optional.of(new InnerBoxSize(200, 200)));
- UUID childUuid3 = UUID.randomUUID();
- Entity childEntity3 = createChild();
- childrenByParents.put(parent3Uuid, Set.of(childUuid3));
- when(entityManager.getEntity(parent3Uuid)).thenReturn(Optional.of(parent3Entity));
- when(entityManager.getSetEntitiesFromUuids(Set.of(childUuid3))).thenReturn(Set.of(childEntity3));
- mockedOuterBoxSize.when(() -> OuterBoxSize.from(eq(childEntity3)))
- .thenReturn(Optional.of(new OuterBoxSize(10, 10)));
-
-
- // Execute
- ContainerExpander.process(childrenByParents, entityManager);
-
- // Verify Parent 1 expanded
- ContentSize finalSize1 = parentEntity.getComponent(ContentSize.class).orElseThrow();
- assertThat(finalSize1.width()).isCloseTo(120, within(EPSILON));
-
- // Verify Parent 2 did not change
- ContentSize finalSize2 = parent2Entity.getComponent(ContentSize.class).orElseThrow();
- assertThat(finalSize2.width()).isEqualTo(50);
-
- // Verify Parent 3 did not change
- ContentSize finalSize3 = parent3Entity.getComponent(ContentSize.class).orElseThrow();
- assertThat(finalSize3.width()).isEqualTo(200);
- }
- }
-}
diff --git a/src/test/java/com/demcha/compose/engine/pagination/PageBreakerTest.java b/src/test/java/com/demcha/compose/engine/pagination/PageBreakerTest.java
deleted file mode 100644
index 35be44c69..000000000
--- a/src/test/java/com/demcha/compose/engine/pagination/PageBreakerTest.java
+++ /dev/null
@@ -1,179 +0,0 @@
-package com.demcha.compose.engine.pagination;
-
-import com.demcha.compose.engine.core.EntityManager;
-import com.demcha.compose.engine.pagination.PageLayoutCalculator;
-import com.demcha.compose.engine.pagination.YPositionOnPage;
-import com.demcha.mock.FactoryPresets;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.params.ParameterizedTest;
-import org.junit.jupiter.params.provider.Arguments;
-import org.junit.jupiter.params.provider.MethodSource;
-import org.mockito.InjectMocks;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-import java.util.stream.Stream;
-
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.mockito.Mockito.mock;
-
-
-class PageBreakerTest {
- @Mock
- private EntityManager entityManager;
-
- @InjectMocks
- private PageLayoutCalculator pageLayoutCalculator;
-
- static Stream provideScenarios() {
-
- return Stream.of(
- // Сценарий 1: Стандартный А4, маленький объект
- Arguments.of(
- "Standard A4 Flow", // Название
- 100.0, //Current positionY
- 0,// Current page
- FactoryPresets.OBJ_SMALL_BOX_100_50, // <--- БЕРЕМ ГОТОВЫЙ ПРЕСЕТ
- FactoryPresets.MARGIN_STANDARD_20,
- FactoryPresets.CANVAS_A4_STANDARD, // <--- БЕРЕМ ГОТОВЫЙ ПРЕСЕТ
- FactoryPresets.OFFSET_DATA_ZERO, // Отступы объекта
- false,
- 0.0,
- new YPositionOnPage(100, 0, 0) // Ожидаемый результат
- ),
- Arguments.of(
- "Full Width No Margins", // Название
- 100.0,
- 1,// Current Y
- FactoryPresets.OBJ_FULL_WIDTH_500_100, // <--- БЕРЕМ ГОТОВЫЙ ПРЕСЕТ
- FactoryPresets.MARGIN_ZERO,
- FactoryPresets.CANVAS_A4_NO_MARGINS, // <--- БЕРЕМ ГОТОВЫЙ ПРЕСЕТ
- FactoryPresets.OFFSET_DATA_ALL_2, // Отступы объекта
- false,
- 2,
- new YPositionOnPage(100, 1, 1) // Ожидаемый результат
- ),
- Arguments.of(
- "Full Width No Margins", // Название
- 5.0,
- 1,// Current Y
- FactoryPresets.OBJ_SMALL_BOX_100_50, // <--- БЕРЕМ ГОТОВЫЙ ПРЕСЕТ
- FactoryPresets.MARGIN_ZERO,
- FactoryPresets.CANVAS_A4_NO_MARGINS, // <--- БЕРЕМ ГОТОВЫЙ ПРЕСЕТ
- FactoryPresets.OFFSET_DATA_ZERO, // Отступы объекта
- false,
- 0,
- new YPositionOnPage(5, 1, 1) // Ожидаемый результат
- ),
- Arguments.of(
- "Next page Position under Margin", // Название
- 10.0,
- 0,
- FactoryPresets.OBJ_SMALL_BOX_100_50, // H=50
- FactoryPresets.MARGIN_VERT_10, // T=10, B=10
- FactoryPresets.CANVAS_TEST_100_100_M_T10_B20,
- FactoryPresets.OFFSET_DATA_ZERO, // Отступы объекта
- false,
- -80.0,
- new YPositionOnPage(30, 1, 1) // Ожидаемый результат
- ),
- Arguments.of(
- "Next page Position under Margin 2", // Название
- 10.0,
- 0,
- FactoryPresets.OBJ_SMALL_BOX_100_50, // H=50
- FactoryPresets.MARGIN_VERT_10, // T=10, B=10
- FactoryPresets.CANVAS_TEST_100_100_M_T10_B20,
- FactoryPresets.OFFSET_DATA_ZERO, // Отступы объекта
- false,
- -80,
- new YPositionOnPage(30, 1, 1) // Ожидаемый результат
- ),
-
- Arguments.of(
- "Next page negative position", // Название
- -30,
- 0,
- FactoryPresets.OBJ_SMALL_BOX_100_50, // H=50
- FactoryPresets.MARGIN_VERT_10, // T=10, B=10
- FactoryPresets.CANVAS_TEST_100_100_M_T10_B20,
- FactoryPresets.OFFSET_DATA_ZERO, // Отступы объекта
- false,
- -40,
- new YPositionOnPage(30, 1, 1) // Ожидаемый результат
- )
-
-
-
- );
- }
-
- // --- 1. Data Provider (Сценарии) ---
- static Stream provideDownShiftScenarios() {
- return Stream.of(
- // 1. Early Exit (yPos < MarginBottom)
- Arguments.of(
- "Early Exit",
- 10.0,
- FactoryPresets.OBJ_SMALL_BOX_100_50, // H=50
- FactoryPresets.MARGIN_VERT_10, // T=10, B=10
- FactoryPresets.CANVAS_TEST_100_100_M_T10_B20,
- -80.0
- ),
-
- // 2. Normal Fit
- Arguments.of(
- "Normal Fit",
- 50.0,
- FactoryPresets.OBJ_W100_H20, // H=20
- FactoryPresets.MARGIN_VERT_5, // T=5, B=5
- FactoryPresets.CANVAS_TEST_100_200_M_T10_B20,
- 0.0
- ),
-
- // 3. Edge Case: Fits exactly
- Arguments.of(
- "Fits Exactly",
- 30.0,
- FactoryPresets.OBJ_W100_H60, // H=60
- FactoryPresets.MARGIN_VERT_10,
- FactoryPresets.CANVAS_TEST_100_100_M_10,
- -10.0
- ),
-
- // 4. Overflow with small Y
- Arguments.of(
- "Overflow Small Y",
- 5.0,
- FactoryPresets.OBJ_W100_H40, // H=40
- FactoryPresets.MARGIN_VERT_10,
- FactoryPresets.CANVAS_TEST_100_100_M_T10_B20,
- -65.0
- ),
-
- // 5. Standard A4
- Arguments.of(
- "Standard A4 Zero Shift",
- 100.0,
- FactoryPresets.OBJ_SMALL_BOX_100_50,
- FactoryPresets.MARGIN_STANDARD_20,
- FactoryPresets.CANVAS_A4_STANDARD,
- 0.0
- )
- );
- }
-
- @BeforeEach
- void setUp() {
- MockitoAnnotations.openMocks(this);
- }
-
-
- @MethodSource("provideScenarios")
- @ParameterizedTest(name = "{0}")
- void pageBreaker(){
-
- }
-
-
-}
\ No newline at end of file
diff --git a/src/test/java/com/demcha/compose/engine/pagination/PageLayoutCalculatorTest.java b/src/test/java/com/demcha/compose/engine/pagination/PageLayoutCalculatorTest.java
deleted file mode 100644
index 8e0def1a4..000000000
--- a/src/test/java/com/demcha/compose/engine/pagination/PageLayoutCalculatorTest.java
+++ /dev/null
@@ -1,288 +0,0 @@
-package com.demcha.compose.engine.pagination;
-
-import com.demcha.compose.engine.core.Canvas;
-import com.demcha.compose.engine.components.core.Entity;
-import com.demcha.compose.engine.components.geometry.ContentSize;
-import com.demcha.compose.engine.components.style.Margin;
-import com.demcha.compose.engine.core.EntityManager;
-import com.demcha.compose.engine.exceptions.BigSizeElementException;
-import com.demcha.compose.engine.pagination.Offset;
-import com.demcha.compose.engine.pagination.PageLayoutCalculator;
-import com.demcha.compose.engine.pagination.YPositionOnPage;
-import com.demcha.mock.FactoryClasses;
-import com.demcha.mock.FactoryPresets;
-import com.demcha.mock.data.CanvasData;
-import com.demcha.mock.data.MarginData;
-import com.demcha.mock.data.OffsetData;
-import com.demcha.mock.data.SizeData;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.DisplayName;
-import org.junit.jupiter.api.Test;
-import org.junit.jupiter.params.ParameterizedTest;
-import org.junit.jupiter.params.provider.Arguments;
-import org.junit.jupiter.params.provider.MethodSource;
-import org.mockito.InjectMocks;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-import java.util.stream.Stream;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.assertThatThrownBy;
-import static org.junit.jupiter.api.Assertions.assertAll;
-
-class PageLayoutCalculatorTest {
-
- @Mock
- private EntityManager entityManager;
-
- @InjectMocks
- private PageLayoutCalculator pageLayoutCalculator;
-
- static Stream provideScenarios() {
-
- return Stream.of(
- // Сценарий 1: Стандартный А4, маленький объект
- Arguments.of(
- "Standard A4 Flow", // Название
- 100.0, //Current positionY
- 0,// Current page
- FactoryPresets.OBJ_SMALL_BOX_100_50, // <--- БЕРЕМ ГОТОВЫЙ ПРЕСЕТ
- FactoryPresets.MARGIN_STANDARD_20,
- FactoryPresets.CANVAS_A4_STANDARD, // <--- БЕРЕМ ГОТОВЫЙ ПРЕСЕТ
- FactoryPresets.OFFSET_DATA_ZERO, // Отступы объекта
- false,
- 0.0,
- new YPositionOnPage(100, 0, 0) // Ожидаемый результат
- ),
- Arguments.of(
- "Full Width No Margins", // Название
- 100.0,
- 1,// Current Y
- FactoryPresets.OBJ_FULL_WIDTH_500_100, // <--- БЕРЕМ ГОТОВЫЙ ПРЕСЕТ
- FactoryPresets.MARGIN_ZERO,
- FactoryPresets.CANVAS_A4_NO_MARGINS, // <--- БЕРЕМ ГОТОВЫЙ ПРЕСЕТ
- FactoryPresets.OFFSET_DATA_ALL_2, // Отступы объекта
- false,
- 2,
- new YPositionOnPage(100, 1, 1) // Ожидаемый результат
- ),
- Arguments.of(
- "Full Width No Margins", // Название
- 5.0,
- 1,// Current Y
- FactoryPresets.OBJ_SMALL_BOX_100_50, // <--- БЕРЕМ ГОТОВЫЙ ПРЕСЕТ
- FactoryPresets.MARGIN_ZERO,
- FactoryPresets.CANVAS_A4_NO_MARGINS, // <--- БЕРЕМ ГОТОВЫЙ ПРЕСЕТ
- FactoryPresets.OFFSET_DATA_ZERO, // Отступы объекта
- false,
- 0,
- new YPositionOnPage(5, 1, 1) // Ожидаемый результат
- ),
- Arguments.of(
- "Next page Position under Margin", // Название
- 10.0,
- 0,
- FactoryPresets.OBJ_SMALL_BOX_100_50, // H=50
- FactoryPresets.MARGIN_VERT_10, // T=10, B=10
- FactoryPresets.CANVAS_TEST_100_100_M_T10_B20,
- FactoryPresets.OFFSET_DATA_ZERO, // Отступы объекта
- false,
- -80.0,
- new YPositionOnPage(30, 1, 1) // Ожидаемый результат
- ),
- Arguments.of(
- "Next page Position under Margin 2", // Название
- 10.0,
- 0,
- FactoryPresets.OBJ_SMALL_BOX_100_50, // H=50
- FactoryPresets.MARGIN_VERT_10, // T=10, B=10
- FactoryPresets.CANVAS_TEST_100_100_M_T10_B20,
- FactoryPresets.OFFSET_DATA_ZERO, // Отступы объекта
- false,
- -80,
- new YPositionOnPage(30, 1, 1) // Ожидаемый результат
- ),
-
- Arguments.of(
- "Next page negative position", // Название
- -30,
- 0,
- FactoryPresets.OBJ_SMALL_BOX_100_50, // H=50
- FactoryPresets.MARGIN_VERT_10, // T=10, B=10
- FactoryPresets.CANVAS_TEST_100_100_M_T10_B20,
- FactoryPresets.OFFSET_DATA_ZERO, // Отступы объекта
- false,
- -40,
- new YPositionOnPage(30, 1, 1) // Ожидаемый результат
- )
-
-
-
- );
- }
-
- // --- 1. Data Provider (Сценарии) ---
- static Stream provideDownShiftScenarios() {
- return Stream.of(
- // 1. Early Exit (yPos < MarginBottom)
- Arguments.of(
- "Early Exit",
- 10.0,
- FactoryPresets.OBJ_SMALL_BOX_100_50, // H=50
- FactoryPresets.MARGIN_VERT_10, // T=10, B=10
- FactoryPresets.CANVAS_TEST_100_100_M_T10_B20,
- -80.0
- ),
-
- // 2. Normal Fit
- Arguments.of(
- "Normal Fit",
- 50.0,
- FactoryPresets.OBJ_W100_H20, // H=20
- FactoryPresets.MARGIN_VERT_5, // T=5, B=5
- FactoryPresets.CANVAS_TEST_100_200_M_T10_B20,
- 0.0
- ),
-
- // 3. Edge Case: Fits exactly
- Arguments.of(
- "Fits Exactly",
- 30.0,
- FactoryPresets.OBJ_W100_H60, // H=60
- FactoryPresets.MARGIN_VERT_10,
- FactoryPresets.CANVAS_TEST_100_100_M_10,
- -10.0
- ),
-
- // 4. Overflow with small Y
- Arguments.of(
- "Overflow Small Y",
- 5.0,
- FactoryPresets.OBJ_W100_H40, // H=40
- FactoryPresets.MARGIN_VERT_10,
- FactoryPresets.CANVAS_TEST_100_100_M_T10_B20,
- -65.0
- ),
-
- // 5. Standard A4
- Arguments.of(
- "Standard A4 Zero Shift",
- 100.0,
- FactoryPresets.OBJ_SMALL_BOX_100_50,
- FactoryPresets.MARGIN_STANDARD_20,
- FactoryPresets.CANVAS_A4_STANDARD,
- 0.0
- )
- );
- }
-
- @BeforeEach
- void setUp() {
- MockitoAnnotations.openMocks(this);
- }
-
- @ParameterizedTest(name = "{0}") // Использует первый аргумент (String) как имя
- @MethodSource("provideDownShiftScenarios")
- void testDownShift_Refactored(
- String description,
- double yPosition,
- SizeData elementSize,
- MarginData elementMargin,
- CanvasData canvas,
- double expectedResult
- ) throws BigSizeElementException {
- // Мы "распаковываем" объекты обратно в примитивы для вызова старого метода
- // Если ваш метод downShift принимает объекты, код станет еще чище!
-
- double result = PageLayoutCalculator.downShift(
- yPosition,
- elementSize.height(),
- elementMargin.bottom(),
- elementMargin.top(),
- canvas.height(),
- canvas.margin().bottom(),
- canvas.margin().top()
- );
-
- assertThat(result)
- .as("Scenario: " + description)
- .isEqualTo(expectedResult);
- }
-
- // 💡 Exception Handling Test
- @Test
- @DisplayName("Should throw BigSizeElementException when element is taller than canvas inner height")
- void testDownShift_ThrowsException_WhenElementTooLarge() {
- // Given
- double elementHeight = 100.0;
- double elementMarginBottom = 10.0;
- double elementMarginTop = 10.0;
- // Total Element = 120.0
-
- double canvasHeight = 100.0;
- double canvasMarginBottom = 10.0;
- double canvasMarginTop = 10.0;
- // Canvas Inner = 80.0
-
- // When & Then
-
- assertThatThrownBy(() -> PageLayoutCalculator.downShift(
- 50.0, elementHeight, elementMarginBottom, elementMarginTop,
- canvasHeight, canvasMarginBottom, canvasMarginTop
- ))
- .isInstanceOf(BigSizeElementException.class)
- .hasMessageContaining("Element is too large");
- }
-
- @MethodSource("provideScenarios")
- @ParameterizedTest(name = "{0}")
- void testLayoutCalculation(
- String name,
- double currentY,
- int currentPage,
- SizeData objectSizeData, // Принимаем Record
- MarginData objectMarginData,
- CanvasData canvasData, // Принимаем Record
- OffsetData offsetData,
- boolean isBreakable,
- double expectedOffsetY,
- YPositionOnPage expectedY
- ) {
- // 1. Создаем Моки через Фабрику, передавая Records
- Canvas canvas = FactoryClasses.canvasMock(canvasData);
- ContentSize size = FactoryClasses.mockContentSize(objectSizeData);
- Margin objMargin = FactoryClasses.margin(objectMarginData);
-
- // Остальная подготовка...
-// Entity entity = FactoryClasses.entityMock(size, objMargin);
- Entity entity = FactoryClasses.entityMock(size, objMargin);
- Offset offset = FactoryClasses.offsetReal(offsetData);
-
-
- // 2. Выполняем тест
- var result = pageLayoutCalculator.definePositionOnPage(currentY, entity, currentPage, canvas, offset, isBreakable);
-
- // 3. Проверка
- assertAll("Проверка позиции на странице", // Заголовок
- () -> assertThat(result.yPosition())
- .as("Y Position")
- .isEqualTo(expectedY.yPosition()),
-
- () -> assertThat(result.startPage())
- .as("Start Page")
- .isEqualTo(expectedY.startPage()),
-
- () -> assertThat(result.endPage())
- .as("End Page")
- .isEqualTo(expectedY.endPage())
- ,() -> assertThat(expectedOffsetY)
- .as("Offset")
- .isEqualTo(offset.y())
-
- );
-
- }
-
-
-}
diff --git a/src/test/resources/logback.xml b/src/test/resources/logback.xml
index ea732eb0f..2adb12613 100644
--- a/src/test/resources/logback.xml
+++ b/src/test/resources/logback.xml
@@ -61,13 +61,6 @@