diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3355791fb..ff5af3acf 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -36,6 +36,13 @@ for this cycle.
- The unused engine-internal `Font.adjustFontSizeToFit(...)` (and its `PdfFont` /
`WordFont` implementations) has been removed; text auto-sizing is resolved by the
layout compiler.
+- The dormant Entity-Component-System engine internals have been removed: the
+ `EntityManager` / `SystemECS` runtime, the `Entity` component model with its
+ geometry / coordinator / renderable companions, the ECS render pipeline
+ (`engine.render.*` and the guide renderers under `engine.render.guides`), and
+ `LayoutSnapshotExtractor`. None were reachable from the live render path —
+ `DocumentSession` → layout compiler → fixed-layout backend — so document layout,
+ PDF output, and the public `guideLines(...)` overlay are unchanged.
### Public API
diff --git a/qa/src/test/resources/logback.xml b/qa/src/test/resources/logback.xml
index 2adb12613..f33257512 100644
--- a/qa/src/test/resources/logback.xml
+++ b/qa/src/test/resources/logback.xml
@@ -93,24 +93,8 @@
-
- * An {@code Entity} is a lightweight container for components such as text,
- * style, size, layout metadata, and render markers. Builders create entities
- * and
- * attach those components; systems later read and enrich the same entity as the
- * document moves through layout, pagination, and rendering.
- *
- * In other words, builders describe intent on the entity, while systems turn
- * that intent into resolved geometry and final output.
- *
- * The new entity gets its own UUID while reusing the source entity's current
- * component references as the initial snapshot.
- *
- * When the component is an {@link EntityName}, the cached entity name is
- * updated. When it implements {@link Render}, the entity also caches it for
- * fast render checks and dispatch.
- *
- * This is intentionally O(1) and does not rescan the component map.
- * Attach this component to make logs, debugging, and inspector views clearer.
- * The name is not an identifier and does not have to be unique. Examples:
- * > tokenizeInlineRuns(List
> tokenizeInlineRuns(List
Constraints
- *
- *
- *
- * {@code
- * addComponent(entityId, new EntityName("Header → ContactRow"));
- * addComponent(entityId, new EntityName("Button[primary]/#submit"));
- * }
- *
- * Bounds are derived from {@link Placement}, {@link ContentSize}, and optional - * {@link Margin}. This helper keeps geometry reads out of {@code Entity} - * itself. - *
- */ -@UtilityClass -public class EntityBounds { - - /** - * Returns the entity's top outer line including top margin. - * - * @param entity entity with resolved placement and content size - * @return top line in document coordinates - */ - public static double topLine(Entity entity) { - Placement placement = entity.require(Placement.class); - ContentSize size = entity.require(ContentSize.class); - Margin margin = marginOf(entity); - return placement.y() + size.height() + margin.top(); - } - - /** - * Returns the entity's bottom outer line including bottom margin. - * - * @param entity entity with resolved placement - * @return bottom line in document coordinates - */ - public static double bottomLine(Entity entity) { - Placement placement = entity.require(Placement.class); - Margin margin = marginOf(entity); - return placement.y() - margin.bottom(); - } - - /** - * Returns the entity's right outer line including right margin. - * - * @param entity entity with resolved placement and content size - * @return right line in document coordinates - */ - public static double rightLine(Entity entity) { - Placement placement = entity.require(Placement.class); - ContentSize size = entity.require(ContentSize.class); - Margin margin = marginOf(entity); - return placement.x() + size.width() + margin.right(); - } - - /** - * Returns the entity's left outer line including left margin. - * - * @param entity entity with resolved placement - * @return left line in document coordinates - */ - public static double leftLine(Entity entity) { - Placement placement = entity.require(Placement.class); - Margin margin = marginOf(entity); - return placement.x() - margin.left(); - } - - private static Margin marginOf(Entity entity) { - return entity.getComponent(Margin.class).orElse(Margin.zero()); - } -} diff --git a/src/main/java/com/demcha/compose/engine/components/geometry/InnerBoxSize.java b/src/main/java/com/demcha/compose/engine/components/geometry/InnerBoxSize.java deleted file mode 100644 index 286f2f3fc..000000000 --- a/src/main/java/com/demcha/compose/engine/components/geometry/InnerBoxSize.java +++ /dev/null @@ -1,78 +0,0 @@ -package com.demcha.compose.engine.components.geometry; - -import com.demcha.compose.engine.components.core.Entity; -import com.demcha.compose.engine.components.layout.coordinator.Placement; -import com.demcha.compose.engine.components.style.Padding; -import com.demcha.compose.engine.exceptions.ContentSizeNotFoundException; -import lombok.NonNull; -import lombok.extern.slf4j.Slf4j; - -import java.util.Objects; -import java.util.Optional; - -@Slf4j -public record InnerBoxSize(double width, double height) { - - public static Optional- * {@code Anchor} tells the layout system how an entity should be positioned - * inside its available area. Combined with {@code Position}, margin, padding, - * and parent inner size, it produces the entity's {@code ComputedPosition}. - *
- * - *Builders attach anchors while describing the document; layout later - * consumes them to convert relative intent into absolute coordinates.
+ * Horizontal + vertical placement anchor — a plain value pairing an + * {@link HAnchor} and a {@link VAnchor}. Consumers read {@code h()} / {@code v()} + * and the static factories to describe how content aligns within its area. */ -@Slf4j public record Anchor(HAnchor h, VAnchor v) implements Component { public static Anchor topLeft() { return new Anchor(HAnchor.LEFT, VAnchor.TOP); @@ -68,87 +51,5 @@ public static Anchor bottomCenter() { public static Anchor defaultAnchor() { return new Anchor(HAnchor.DEFAULT, VAnchor.DEFAULT); } - - /** - * Converts a computed outer-box position into a rendering origin inside the - * entity's margin box. - */ - public static RenderingPosition renderingPosition(ComputedPosition computedPosition, Margin margin) { - Objects.requireNonNull(computedPosition, "computedPosition cannot be null"); - Objects.requireNonNull(margin, "margin cannot be null"); - - // computedPosition — это левый-нижний угол OUTER-box (учитывает anchor/align). - // Чтобы рисовать контент/рамку внутри margin, просто смещаемся внутрь. - double rx = computedPosition.x() + margin.left(); - double ry = computedPosition.y() + margin.bottom(); - - var renderingPosition = new RenderingPosition(rx, ry); - log.debug("Rendering position computed: {}", renderingPosition); - return renderingPosition; - } - - /** - * Computes a child's anchored position inside a parent inner box. - */ - public ComputedPosition getComputedPosition(Entity child, InnerBoxSize perrentInnerBoxSize) { - log.debug("Starting calculation of computed position for {} ", child); - var position = child.getComponent(Position.class).orElse(Position.zero()); - - var outerBoxSize = OuterBoxSize.from(child).get(); - var childMargin = child.getComponent(Margin.class).orElse(Margin.zero()); - - return getComputedPosition(position, childMargin, outerBoxSize, perrentInnerBoxSize); - } - - private ComputedPosition getComputedPosition(Position position, Margin childMargin, OuterBoxSize outerBoxSize, InnerBoxSize parentInnerBoxSize) { - Objects.requireNonNull(position, () -> { - log.error("Position cannot be null."); - throw new NullPointerException("Position cannot be null."); - }); - Objects.requireNonNull(outerBoxSize, () -> { - log.error("OuterBoxSize cannot be null."); - throw new NullPointerException("OuterBoxSize cannot be null."); - - }); - Objects.requireNonNull(parentInnerBoxSize, () -> { - log.error("ParentInnerBoxSize cannot be null."); - throw new NullPointerException("ParentInnerBoxSize cannot be null."); - - }); - double outerW = outerBoxSize.width(); - double outerH = outerBoxSize.height(); - - double areaW = parentInnerBoxSize.width(); - double areaH = parentInnerBoxSize.height(); - - double childBottomMargin = childMargin.bottom(); - double childLeftMargin = childMargin.left(); - - - return getComputedPosition(position, childBottomMargin, childLeftMargin, areaW, outerW, areaH, outerH); - - } - - private ComputedPosition getComputedPosition(Position position, double childBottomMargin, double childLeftMargin, double areaW, double outerW, double areaH, double outerH) { - double x = switch (this.h()) { - case LEFT -> position.x() + childLeftMargin; // x=0 at left - case CENTER -> (areaW - outerW) / 2.0 + position.x() + childLeftMargin; - case RIGHT -> areaW - outerW + position.x() + childLeftMargin; - case DEFAULT -> position.x(); - }; - - double y = switch (this.v()) { - case BOTTOM -> position.y() + childBottomMargin; // y=0 at bottom - case MIDDLE -> (areaH - outerH) / 2.0 + position.y()+ childBottomMargin; - case TOP -> areaH - outerH + position.y() + childBottomMargin; - case DEFAULT -> position.y(); - }; - - var computed = new ComputedPosition(x, y); - log.debug("Computed position with Anchor has been created: {}", computed); - return computed; - } - - } diff --git a/src/main/java/com/demcha/compose/engine/components/layout/Layer.java b/src/main/java/com/demcha/compose/engine/components/layout/Layer.java deleted file mode 100644 index 5399ff53f..000000000 --- a/src/main/java/com/demcha/compose/engine/components/layout/Layer.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.demcha.compose.engine.components.layout; - -import com.demcha.compose.engine.components.core.Component; - -/** - * Represents an object that exists on a specific layer. - * - *In graphical systems, rendering engines, or UI frameworks, - * a layer is often used to determine the drawing order or - * stacking position of elements. Lower layer numbers are typically - * drawn first (behind others), while higher numbers appear on top.
- */ -public record Layer(int value) implements Component {} - - diff --git a/src/main/java/com/demcha/compose/engine/components/layout/ParentComponent.java b/src/main/java/com/demcha/compose/engine/components/layout/ParentComponent.java deleted file mode 100644 index 056f7be96..000000000 --- a/src/main/java/com/demcha/compose/engine/components/layout/ParentComponent.java +++ /dev/null @@ -1,13 +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 java.util.Objects; -import java.util.UUID; - -public record ParentComponent(UUID uuid) implements Component{ - public ParentComponent(Entity entity) { - this(Objects.requireNonNull(entity.getUuid(), "Entity ID cannot be null.") ); - } -} diff --git a/src/main/java/com/demcha/compose/engine/components/layout/RenderCoordinate.java b/src/main/java/com/demcha/compose/engine/components/layout/RenderCoordinate.java deleted file mode 100644 index 7da482185..000000000 --- a/src/main/java/com/demcha/compose/engine/components/layout/RenderCoordinate.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.demcha.compose.engine.components.layout; - -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 java.util.Optional; - -public interface RenderCoordinate { -- * {@code ComputedPosition} is produced by layout from builder-time metadata such - * as {@link Anchor}, {@code Position}, parent inner size, padding offsets, and - * canvas geometry. It is the bridge between declarative placement intent and the - * fully resolved coordinates later used to build {@link Placement}. - *
- */ -@Slf4j -public record ComputedPosition(double x, double y) implements Component { - /** - * Returns a zero coordinate. - */ - public static ComputedPosition zero() { - return new ComputedPosition(0, 0); - } - - /** - * Resolves a child's position inside a parent inner box and applies the - * parent's padding coordinate offset. - * - * @param child the child entity being placed - * @param parentInnerBoxSize the available inner box of the parent - * @param paddingParentCoordinate the drawing origin inside the parent's padding box - * @return the resolved position for the child - */ - public static ComputedPosition from(@NonNull Entity child, @NonNull InnerBoxSize parentInnerBoxSize, PaddingCoordinate paddingParentCoordinate) { - var anchorOptional = child.getComponent(Anchor.class); - var anchor = anchorOptional.orElseGet(() -> { - log.warn("No Anchor found for {}. Using default Anchor (top-left).", child); - Anchor defaultAnchor = Anchor.defaultAnchor(); - child.addComponent(defaultAnchor); - return defaultAnchor; - }); - ComputedPosition computedPosition = anchor.getComputedPosition(child, parentInnerBoxSize); - double x = computedPosition.x + paddingParentCoordinate.x(); - double y = computedPosition.y + paddingParentCoordinate.y(); - return new ComputedPosition(x, y); - } - - /** - * Resolves a child's position directly from a parent entity. - * - * @param child the child entity - * @param parent the parent entity supplying inner box and padding context - * @return the resolved position - * @throws java.util.NoSuchElementException if the parent has no {@link InnerBoxSize} - */ - public static ComputedPosition from(@NonNull Entity child, @NonNull Entity parent) { - var parentInnerBox = InnerBoxSize.from(parent).orElseThrow(); - var paddingParentCoordinate = PaddingCoordinate.from(parent); - return from(child, parentInnerBox, paddingParentCoordinate); - } - - /** - * Resolves the position of a root-level entity against a document canvas. - * - * @param childEntity the entity being positioned - * @param canvas the document canvas - * @return the resolved position - */ - public static ComputedPosition from(@NonNull Entity childEntity, @NonNull Canvas canvas) { - log.debug("Computing position using default Canvas."); - var margin = canvas.margin() != null ? canvas.margin() : Margin.zero(); - InnerBoxSize innerBoxSize = new InnerBoxSize(canvas.width()- margin.horizontal(), canvas.height() - margin.vertical()); - var paddingParentCoordinate = new PaddingCoordinate(margin.left(), margin.bottom()); - return from(childEntity, innerBoxSize, paddingParentCoordinate); - } - - /** - * Converts this resolved position into a padding-aware coordinate origin. - * - * @param padding the padding to apply - * @return a coordinate shifted inside the padding box - */ - public PaddingCoordinate paddingCoordinate(@NonNull Padding padding) { - - var x = this.x + padding.left(); - var y = this.y + padding.bottom(); - - log.debug("ComputedPosition is {}", this.toString()); - log.debug("Padding coordinate is {}", padding); - - return new PaddingCoordinate(x, y); - } -} - diff --git a/src/main/java/com/demcha/compose/engine/components/layout/coordinator/PaddingCoordinate.java b/src/main/java/com/demcha/compose/engine/components/layout/coordinator/PaddingCoordinate.java deleted file mode 100644 index 051649363..000000000 --- a/src/main/java/com/demcha/compose/engine/components/layout/coordinator/PaddingCoordinate.java +++ /dev/null @@ -1,61 +0,0 @@ -package com.demcha.compose.engine.components.layout.coordinator; - -import com.demcha.compose.engine.components.core.Entity; -import com.demcha.compose.engine.components.style.Padding; -import lombok.NonNull; -import lombok.extern.slf4j.Slf4j; - -/** - * Represents a coordinate adjusted by padding relative to a computed position (x, y). - *- * Typical usage: - *
- * ⚠️ This assumes the entity already has a {@link ComputedPosition} stored. - * If not, compute one first (e.g., {@link ComputedPosition#from(Entity, Entity)}). - * - * @param entity the entity that already stores its {@link ComputedPosition} - * @return padding-adjusted coordinate based on the entity's own padding - * @throws IllegalStateException if {@code entity} has no {@link ComputedPosition} - */ - public static PaddingCoordinate from(@NonNull Entity entity) { - - ComputedPosition position = entity.getComponent(ComputedPosition.class).orElseThrow(); - - var padding = entity.getComponent(Padding.class).orElse(Padding.zero()); - var result = from(position, padding); - log.debug("PaddingCoordinate.from(entity={}) -> {}", entity, result); - return result; - } - - /** - * Low-level factory: apply the given {@link Padding} to a provided {@link ComputedPosition}. - *
- * This is useful when you have already computed the position, and you explicitly - * know which parrentPadding (parent's or entity's) you want to apply. - * - * @param position the base position - * @param parrentPadding the parrentPadding to apply - * @return parrentPadding-adjusted coordinate - */ - public static PaddingCoordinate from(@NonNull ComputedPosition position, @NonNull Padding parrentPadding) { - double px = position.x() + parrentPadding.left() ; - double py = position.y() + parrentPadding.bottom() ; - var result = new PaddingCoordinate(px, py); - log.debug("PaddingCoordinate.from(position={}, parrentPadding={}) -> {}", position, parrentPadding, result); - return result; - } - public static PaddingCoordinate zero(){ - return new PaddingCoordinate(0, 0); - } -} diff --git a/src/main/java/com/demcha/compose/engine/components/layout/coordinator/Placement.java b/src/main/java/com/demcha/compose/engine/components/layout/coordinator/Placement.java deleted file mode 100644 index f72468d91..000000000 --- a/src/main/java/com/demcha/compose/engine/components/layout/coordinator/Placement.java +++ /dev/null @@ -1,97 +0,0 @@ -package com.demcha.compose.engine.components.layout.coordinator; - -import com.demcha.compose.engine.components.content.shape.Stroke; -import com.demcha.compose.engine.components.core.Component; -import com.demcha.compose.engine.components.core.Entity; -import com.demcha.compose.engine.components.geometry.InnerBoxSize; -import com.demcha.compose.engine.components.geometry.OuterBoxSize; -import com.demcha.compose.engine.components.layout.Anchor; -import com.demcha.compose.engine.components.style.Margin; -import com.demcha.compose.engine.components.style.Padding; -import com.demcha.compose.engine.render.RenderingSystemECS; -import lombok.extern.slf4j.Slf4j; - -import java.awt.*; -import java.util.Objects; -import java.util.Optional; - -/** - * Final layout bounding box for an entity. - *
- * {@code Placement} is the layout-phase component closest to what renderers - * consume. It combines resolved position, outer size, and page span information - * into a single record that tells the rendering layer where the entity lives in - * document space. - *
- * - * @param x final horizontal position - * @param y final vertical position - * @param width final outer width - * @param height final outer height - * @param startPage first page containing this entity - * @param endPage last page containing this entity - */ -@Slf4j -public record Placement(double x, double y, double width, double height, int startPage, - int endPage) implements Component { - - - /** - * Creates a single-page placement from an already resolved outer size and position. - */ - private static Placement from(OuterBoxSize outerBoxSize, Position positionWithMargins, int pageNumber) { - Objects.requireNonNull(outerBoxSize); - Objects.requireNonNull(positionWithMargins); - return new Placement(positionWithMargins.x(), positionWithMargins.y(), outerBoxSize.width(), outerBoxSize.height(), pageNumber, pageNumber); - } - - public static Placement from(Entity entity, InnerBoxSize parrentInnerBoxSize, PaddingCoordinate paddingCoordinate, int pageNumber) { - var computedPosition = ComputedPosition.from(entity, parrentInnerBoxSize, paddingCoordinate); - var outBoxSize = OuterBoxSize.from(entity).orElseThrow(); - var padding = entity.getComponent(Padding.class).get(); - return new Placement(computedPosition.x(), computedPosition.y(), outBoxSize.width(), outBoxSize.height(), pageNumber, pageNumber); - - - } - - public static Placement fromWithDefault(Entity entity, InnerBoxSize parrentInnerBoxSize, PaddingCoordinate paddingCoordinate, int pageNumber) { - var position = entity.getComponent(Position.class).orElse(Position.zero()); - entity.addComponent(position); - var margin = entity.getComponent(Margin.class).orElse(Margin.zero()); - entity.addComponent(margin); - var anchor = entity.getComponent(Anchor.class).orElse(Anchor.topLeft()); - entity.addComponent(anchor); - return from(entity, parrentInnerBoxSize, paddingCoordinate, pageNumber); - } - - public static Placement from(Entity child, Entity parent, int pageNumber) { - var parrentInnerBoxSize = InnerBoxSize.from(parent).orElseThrow(); - var paddingCoordinate = PaddingCoordinate.from(parent); - - return from(child, parrentInnerBoxSize, paddingCoordinate, pageNumber); - } - - - /** - * Converts this placement into guideline rendering coordinates for debugging. - */ - publicCoordinates are typically defined in a Cartesian coordinate system, - * where X represents the horizontal axis and Y represents - * the vertical axis.
- * - *Ownership: Owned by the shared engine layout pipeline.
- *Extension rules: Extend with backend-neutral placement metadata only; renderers should consume this state, not define it.
- */ -package com.demcha.compose.engine.components.layout.coordinator; \ No newline at end of file diff --git a/src/main/java/com/demcha/compose/engine/components/renderable/BlockText.java b/src/main/java/com/demcha/compose/engine/components/renderable/BlockText.java deleted file mode 100644 index b077a6475..000000000 --- a/src/main/java/com/demcha/compose/engine/components/renderable/BlockText.java +++ /dev/null @@ -1,64 +0,0 @@ -package com.demcha.compose.engine.components.renderable; - -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.layout.coordinator.Placement; -import com.demcha.compose.engine.render.Render; -import com.demcha.compose.engine.pagination.Breakable; -import com.demcha.compose.engine.text.TextControlSanitizer; -import lombok.Builder; -import lombok.EqualsAndHashCode; -import lombok.NoArgsConstructor; -import lombok.NonNull; -import lombok.extern.slf4j.Slf4j; - -import java.util.Optional; - -/** - * Render marker for breakable multi-line text blocks. - */ -@Slf4j -@Builder -@EqualsAndHashCode -@NoArgsConstructor -public class BlockText implements Render, Breakable { - - public static OptionalOwnership: Owned by the shared engine render seam.
- *Extension rules: Add markers only with matching render-handler dispatch and tests for unsupported backend behavior.
- */ -package com.demcha.compose.engine.components.renderable; \ No newline at end of file diff --git a/src/main/java/com/demcha/compose/engine/components/style/Margin.java b/src/main/java/com/demcha/compose/engine/components/style/Margin.java index 18d6ef6db..49e6e2cbb 100644 --- a/src/main/java/com/demcha/compose/engine/components/style/Margin.java +++ b/src/main/java/com/demcha/compose/engine/components/style/Margin.java @@ -1,20 +1,9 @@ package com.demcha.compose.engine.components.style; -import com.demcha.compose.engine.components.content.shape.Stroke; import com.demcha.compose.engine.components.core.Component; -import com.demcha.compose.engine.components.core.Entity; -import com.demcha.compose.engine.components.layout.RenderCoordinate; -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 lombok.extern.slf4j.Slf4j; -import java.awt.*; -import java.util.Optional; - -@Slf4j -public record Margin(double top, double right, double bottom, double left) implements Component, RenderCoordinate { +public record Margin(double top, double right, double bottom, double left) implements Component { public static Margin of(double value) { return new Margin(value, value, value, value); @@ -40,33 +29,6 @@ public static Margin left(double value) { return new Margin(0, 0, 0, value); } - public- * {@code EntityManager} is the runtime hub of GraphCompose. Builders register - * entities here, systems query and mutate those entities during layout and - * rendering, and composer implementations use the registry to orchestrate the - * full document pipeline. - *
- * - *Besides storing entities, the manager also owns system registration, the - * active font library, layer/depth metadata used by layout, and document-wide - * flags such as markdown and guide-line rendering.
- * - *This helper keeps traversal-specific structure out of {@link EntityManager} so - * the manager can remain the registry of entities and systems, while layout and - * pagination reuse one canonical hierarchy snapshot.
- * - *The context is built from the existing entity graph using two inputs:
- *If those two sources disagree, the context appends the missing child in a - * deterministic fallback position and logs a warning so the inconsistency stays - * visible during maintenance and backend work.
- */ -@Slf4j -public final class LayoutTraversalContext { - private final MapThe resolved hierarchy is recomputed every pass, so stale layers and depth - * metadata must not survive between runs on the same composer.
- */ - public static void resetTraversalState(EntityManager entityManager) { - entityManager.setLayers(new TreeMap<>()); - entityManager.setDepthById(new LinkedHashMap<>()); - } - - /** - * Builds a deterministic hierarchy snapshot from the current entity graph. - */ - public static LayoutTraversalContext from(EntityManager entityManager) { - MapOnly entities that carry a {@link ParentComponent} appear as keys.
- * - * @return child → parent mapping, never {@code null} - */ - public MapThe child order follows {@link Entity#getChildren()} when consistent with - * {@link ParentComponent}. Inconsistencies are resolved deterministically and - * logged as warnings.
- * - * @return parent → ordered children mapping, never {@code null} - */ - public MapIf no roots can be computed (e.g. due to a cycle), the set falls back to all - * known entities so the cycle becomes visible at traversal time.
- * - * @return root entity IDs in insertion order, never {@code null} or empty - */ - public SetThis is not the engine behind the public API. The canonical - * pipeline ({@code GraphCompose.document() -> DocumentSession -> LayoutCompiler - * -> LayoutGraph -> PdfFixedLayoutBackend}) in {@code com.demcha.compose.document.*} - * imports nothing from this package directly, and the former - * {@code GraphCompose.pdf(...)} surface that drove the ECS has been removed. The - * ECS execution engine — the {@code EntityManager.processSystems()} loop - * and the dispatch contracts it drives — is dead code: nothing invokes it, in - * production or in tests.
- * - *The genuinely shared engine packages are elsewhere and are not - * deprecated: {@code engine.components} (value types), {@code engine.measurement} - * (text-measurement contracts), {@code engine.font}, and - * {@code engine.render} (backend-neutral render-pass contracts) are all used by - * the canonical pipeline.
- * - * @deprecated Legacy ECS engine, superseded by the canonical - * {@code com.demcha.compose.document.layout} pipeline. No public entry point - * runs it and it is not on the canonical hot path; it survives only because - * the live {@code Entity} object model and the shared render / pagination / - * debug helpers still compile against these primitives. It goes away when the - * internal {@code Entity} model is retired (see - * {@code docs/roadmaps/post-2.0-engineering.md}). Do not extend it or spend - * optimization effort here. - */ -@Deprecated -package com.demcha.compose.engine.core; diff --git a/src/main/java/com/demcha/compose/engine/debug/LayoutInsetsSnapshot.java b/src/main/java/com/demcha/compose/engine/debug/LayoutInsetsSnapshot.java index ea87d913b..003d796c4 100644 --- a/src/main/java/com/demcha/compose/engine/debug/LayoutInsetsSnapshot.java +++ b/src/main/java/com/demcha/compose/engine/debug/LayoutInsetsSnapshot.java @@ -3,12 +3,14 @@ import com.demcha.compose.engine.components.style.Margin; import com.demcha.compose.engine.components.style.Padding; +import java.math.BigDecimal; +import java.math.RoundingMode; + /** * Snapshot-friendly inset model used for margins and padding. * - *The values are normalized through - * {@link LayoutSnapshotExtractor#normalize(double)} so JSON baselines remain - * stable across insignificant floating-point noise.
+ *The values are normalized through {@link #normalize(double)} so JSON + * baselines remain stable across insignificant floating-point noise.
* * @param top top inset * @param right right inset @@ -26,10 +28,10 @@ public record LayoutInsetsSnapshot(double top, double right, double bottom, doub public static LayoutInsetsSnapshot from(Margin margin) { Margin safeMargin = margin == null ? Margin.zero() : margin; return new LayoutInsetsSnapshot( - LayoutSnapshotExtractor.normalize(safeMargin.top()), - LayoutSnapshotExtractor.normalize(safeMargin.right()), - LayoutSnapshotExtractor.normalize(safeMargin.bottom()), - LayoutSnapshotExtractor.normalize(safeMargin.left())); + normalize(safeMargin.top()), + normalize(safeMargin.right()), + normalize(safeMargin.bottom()), + normalize(safeMargin.left())); } /** @@ -41,9 +43,18 @@ public static LayoutInsetsSnapshot from(Margin margin) { public static LayoutInsetsSnapshot from(Padding padding) { Padding safePadding = padding == null ? Padding.zero() : padding; return new LayoutInsetsSnapshot( - LayoutSnapshotExtractor.normalize(safePadding.top()), - LayoutSnapshotExtractor.normalize(safePadding.right()), - LayoutSnapshotExtractor.normalize(safePadding.bottom()), - LayoutSnapshotExtractor.normalize(safePadding.left())); + normalize(safePadding.top()), + normalize(safePadding.right()), + normalize(safePadding.bottom()), + normalize(safePadding.left())); + } + + private static double normalize(double value) { + if (Math.abs(value) < 0.0005d) { + return 0.0d; + } + return BigDecimal.valueOf(value) + .setScale(3, RoundingMode.HALF_UP) + .doubleValue(); } } diff --git a/src/main/java/com/demcha/compose/engine/debug/LayoutSnapshotExtractor.java b/src/main/java/com/demcha/compose/engine/debug/LayoutSnapshotExtractor.java deleted file mode 100644 index 017b1532e..000000000 --- a/src/main/java/com/demcha/compose/engine/debug/LayoutSnapshotExtractor.java +++ /dev/null @@ -1,264 +0,0 @@ -package com.demcha.compose.engine.debug; - -import com.demcha.compose.engine.components.core.Component; -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.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.style.Margin; -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.core.LayoutTraversalContext; - -import java.math.BigDecimal; -import java.math.RoundingMode; -import java.util.ArrayList; -import java.util.Comparator; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.NoSuchElementException; -import java.util.Objects; -import java.util.Set; -import java.util.UUID; -import java.util.stream.Collectors; -import java.util.regex.Pattern; - -/** - * Extracts deterministic, renderer-agnostic layout snapshots from resolved ECS state. - * - *The extractor walks the entity tree in deterministic depth-first order, - * reads post-layout components such as {@link ComputedPosition} and - * {@link Placement}, and projects them into stable snapshot records that are - * suitable for regression testing and debugging.
- * - *The extractor is intentionally strict: if required post-layout components - * are missing after the layout pass, extraction fails fast with a descriptive - * error rather than silently producing an incomplete snapshot.
- */ -public final class LayoutSnapshotExtractor { - public static final String FORMAT_VERSION = "1.0"; - private static final String PATH_SEPARATOR = "/"; - private static final Pattern GENERATED_ENTITY_NAME = Pattern.compile("^[A-Za-z0-9$]+Builder__[0-9a-fA-F]{5}$"); - - private LayoutSnapshotExtractor() { - } - - /** - * Builds a deterministic snapshot from the current entity manager state. - * - *This method expects layout resolution and pagination to have already - * happened. It does not run rendering and does not mutate renderer-owned - * output state.
- * - * @param entityManager entity graph and resolved post-layout components - * @param canvas resolved canvas used during layout - * @return snapshot of the current resolved layout tree - */ - public static LayoutSnapshot extract(EntityManager entityManager, Canvas canvas) { - Objects.requireNonNull(entityManager, "entityManager"); - Objects.requireNonNull(canvas, "canvas"); - - MapOwnership: Owned by the shared engine foundation.
- *Extension rules: Prefer precise exception types when callers can recover or produce better diagnostics.
- */ -package com.demcha.compose.engine.exceptions; \ No newline at end of file diff --git a/src/main/java/com/demcha/compose/engine/pagination/Breakable.java b/src/main/java/com/demcha/compose/engine/pagination/Breakable.java deleted file mode 100644 index b4cc90a45..000000000 --- a/src/main/java/com/demcha/compose/engine/pagination/Breakable.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.demcha.compose.engine.pagination; - -/** - * Marker for renderables whose content may continue across page boundaries. - * - *This marker is used by the page breaker. A breakable entity may start on one page - * and continue on the next page instead of being moved as a single indivisible block.
- * - *It does not mean that the entity should resize to fit children. - * Parent expansion is handled separately by {@code Expendable}.
- */ -public interface Breakable { -} diff --git a/src/main/java/com/demcha/compose/engine/pagination/Offset.java b/src/main/java/com/demcha/compose/engine/pagination/Offset.java deleted file mode 100644 index 8991f6117..000000000 --- a/src/main/java/com/demcha/compose/engine/pagination/Offset.java +++ /dev/null @@ -1,57 +0,0 @@ -package com.demcha.compose.engine.pagination; - -import lombok.Data; -import lombok.NoArgsConstructor; -import lombok.experimental.Accessors; -import lombok.extern.slf4j.Slf4j; - -import java.util.concurrent.atomic.DoubleAdder; - -@Slf4j -@Data -@Accessors(fluent = true) -@NoArgsConstructor -public class Offset { - // Use DoubleAdder for thread safety and performance - private final DoubleAdder y = new DoubleAdder(); - private final DoubleAdder x = new DoubleAdder(); - - // Custom getters because DoubleAdder doesn't return primitive double automatically - public double y() { - return y.sum(); - } - - public double x() { - return x.sum(); - } - - // --- Y Increments --- - public void incrementY(double increment) { - log.debug("{} increment Y({})", this, increment); - this.y.add(increment); - } - - // Overloads - public void incrementY(float increment) { incrementY((double) increment); } - public void incrementY(int increment) { incrementY((double) increment); } - - public void incrementY(Offset offset) { - log.debug("Increment offset Y from object {}", offset); - incrementY(offset.y()); - } - - // --- X Increments (FIXED BUGS HERE) --- - public void incrementX(double increment) { - log.debug("{} increment X ({})", this, increment); - this.x.add(increment); // Was adding to Y in your code - } - - // Overloads - public void incrementX(float increment) { incrementX((double) increment); } // Fixed call - public void incrementX(int increment) { incrementX((double) increment); } // Fixed call - - public void incrementX(Offset offset) { - log.debug("Increment offset X from object {}", offset); - incrementX(offset.x()); // Fixed call - } -} diff --git a/src/main/java/com/demcha/compose/engine/pagination/PageOutOfBoundException.java b/src/main/java/com/demcha/compose/engine/pagination/PageOutOfBoundException.java deleted file mode 100644 index c7e32a8c8..000000000 --- a/src/main/java/com/demcha/compose/engine/pagination/PageOutOfBoundException.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.demcha.compose.engine.pagination; - -public class PageOutOfBoundException extends RuntimeException { - private static String message = "Page out of bound, current page: %s"; - public PageOutOfBoundException(String s) { - super(s); - } - - public PageOutOfBoundException(String message, Throwable cause) { - super(message, cause); - } - public PageOutOfBoundException(int pageNumber) { - super(String.format(message, pageNumber) ); - } -} diff --git a/src/main/java/com/demcha/compose/engine/pagination/ParentContainerUpdater.java b/src/main/java/com/demcha/compose/engine/pagination/ParentContainerUpdater.java deleted file mode 100644 index 16b231b01..000000000 --- a/src/main/java/com/demcha/compose/engine/pagination/ParentContainerUpdater.java +++ /dev/null @@ -1,181 +0,0 @@ -package com.demcha.compose.engine.pagination; - -import com.demcha.compose.engine.components.core.Entity; -import com.demcha.compose.engine.components.geometry.ContentSize; -import com.demcha.compose.engine.components.layout.ParentComponent; -import com.demcha.compose.engine.components.layout.coordinator.ComputedPosition; -import com.demcha.compose.engine.core.EntityManager; -import lombok.NonNull; -import lombok.experimental.UtilityClass; -import lombok.extern.slf4j.Slf4j; - -/** - * Pagination helper that propagates child-driven size and position changes to - * parent containers. - * - *- * The page breaker uses these routines after a child moves or grows so parent - * boxes can reflect the same change before later layout/render stages inspect - * the tree. - *
- */ -@Slf4j -@UtilityClass -public class ParentContainerUpdater { - - /** - * Propagates a resolved offset from a child entity to its parent container. - * - * @param entity child entity whose parent should be updated - * @param manager entity manager used to resolve parent entities - * @param offset resolved offset object - * @return {@code true} when a parent update was applied - */ - public static boolean updateParentContainer(Entity entity, EntityManager manager, Offset offset) { - if (offset == null) { - log.error("Offset cannot be null"); - return false; - } - return updateParentContainer(entity, manager, offset.y()); - } - - /** - * Propagates a vertical delta from a child entity to its parent container - * chain. - * - * @param entity child entity whose parent should be updated - * @param manager entity manager used to resolve parent entities - * @param offsetY vertical delta to propagate - * @return {@code true} when a parent update was applied - */ - public static boolean updateParentContainer(Entity entity, EntityManager manager, double offsetY) { - if (offsetY == 0) { - if (log.isDebugEnabled()) { - log.debug("Skipping parent container position update because offset is zero for {}", entity.getUuid()); - } - return false; - } - - ParentComponent parentComponent = entity.getComponent(ParentComponent.class).orElse(null); - if (parentComponent == null) { - if (log.isDebugEnabled()) { - log.debug("Parent component missing for entity [{}]; parent position update skipped.", entity.getUuid()); - } - return false; - } - - Entity parent = manager.getEntity(parentComponent.uuid()).orElse(null); - if (parent == null) { - log.error("Parent entity not found in manager for UUID {}", parentComponent.uuid()); - return false; - } - return updateEntitySizeAndPosition(manager, offsetY, parent); - } - - /** - * Propagates a size-only delta from a child entity to its parent container - * chain. - * - * @param entity child entity whose parent size should be updated - * @param manager entity manager used to resolve parent entities - * @param offsetY height delta to propagate - * @return {@code true} when a parent size update was applied - */ - public static boolean updateParentContainerSize(Entity entity, EntityManager manager, double offsetY) { - if (offsetY == 0) { - if (log.isDebugEnabled()) { - log.debug("Skipping parent container size update because offset is zero for {}", entity.getUuid()); - } - return false; - } - - ParentComponent parentComponent = entity.getComponent(ParentComponent.class).orElse(null); - if (parentComponent == null) { - if (log.isDebugEnabled()) { - log.debug("Parent component missing for entity [{}]; parent size update skipped.", entity.getUuid()); - } - return false; - } - - Entity parent = manager.getEntity(parentComponent.uuid()).orElse(null); - if (parent == null) { - log.error("Parent entity not found in manager for UUID {}", parentComponent.uuid()); - return false; - } - return updateEntitySize(manager, offsetY, parent); - } - - /** - * Resizes the target entity and, for negative offsets, also shifts its Y - * position before propagating the change upward. - * - * @param manager entity manager used to resolve parent entities - * @param offsetY height delta or upward shift delta - * @param entity entity to mutate - * @return {@code true} when propagation reached a parent container - */ - public static boolean updateEntitySizeAndPosition(EntityManager manager, double offsetY, @NonNull Entity entity) { - ComputedPosition computedPosition = entity.require(ComputedPosition.class); - ContentSize size = entity.require(ContentSize.class); - - if (offsetY < 0) { - double newY = computedPosition.y() + offsetY; - double newHeight = size.height() + Math.abs(offsetY); - entity.addComponent(new ComputedPosition(computedPosition.x(), newY)); - entity.addComponent(new ContentSize(size.width(), newHeight)); - } else { - double newHeight = size.height() + offsetY; - entity.addComponent(new ContentSize(size.width(), newHeight)); - } - - return updateParentContainer(entity, manager, offsetY); - } - - /** - * Resizes the target entity without changing its own Y coordinate and then - * propagates the size change upward. - * - * @param manager entity manager used to resolve parent entities - * @param offsetY height delta to apply - * @param entity entity to resize - * @return {@code true} when propagation reached a parent container - */ - public static boolean updateEntitySize(EntityManager manager, double offsetY, @NonNull Entity entity) { - entity.require(ComputedPosition.class); - ContentSize size = entity.require(ContentSize.class); - - if (offsetY < 0) { - double newHeight = size.height() + Math.abs(offsetY); - entity.addComponent(new ContentSize(size.width(), newHeight)); - } else { - double newHeight = size.height() + offsetY; - entity.addComponent(new ContentSize(size.width(), newHeight)); - } - - return updateParentContainerSize(entity, manager, offsetY); - } - - /** - * Resizes the current entity and propagates the size-only change to the parent - * container chain. - * - * @param entity current entity to resize - * @param manager entity manager used to resolve parent entities - * @param offsetY height delta to apply - * @return {@code true} when propagation reached a parent container - */ - public static boolean updateCurrentEntitySize(Entity entity, EntityManager manager, double offsetY) { - entity.require(ComputedPosition.class); - ContentSize size = entity.require(ContentSize.class); - - if (offsetY < 0) { - double newHeight = size.height() + Math.abs(offsetY); - entity.addComponent(new ContentSize(size.width(), newHeight)); - } else { - double newHeight = size.height() + offsetY; - entity.addComponent(new ContentSize(size.width(), newHeight)); - } - - return updateParentContainerSize(entity, manager, offsetY); - } -} 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 deleted file mode 100644 index 07d256192..000000000 --- a/src/main/java/com/demcha/compose/engine/pagination/package-info.java +++ /dev/null @@ -1,16 +0,0 @@ -/** - * Pagination and page-placement helpers of the legacy ECS engine. - * - *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 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/Render.java b/src/main/java/com/demcha/compose/engine/render/Render.java deleted file mode 100644 index 8d8472675..000000000 --- a/src/main/java/com/demcha/compose/engine/render/Render.java +++ /dev/null @@ -1,6 +0,0 @@ -package com.demcha.compose.engine.render; - -import com.demcha.compose.engine.components.core.Component; - -public interface Render extends Component { -} diff --git a/src/main/java/com/demcha/compose/engine/render/RenderHandler.java b/src/main/java/com/demcha/compose/engine/render/RenderHandler.java deleted file mode 100644 index d2fc6747a..000000000 --- a/src/main/java/com/demcha/compose/engine/render/RenderHandler.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.demcha.compose.engine.render; - -import com.demcha.compose.engine.components.core.Entity; -import com.demcha.compose.engine.core.EntityManager; -import com.demcha.compose.engine.render.Render; -import com.demcha.compose.engine.render.RenderingSystemECS; - -import java.io.IOException; - -/** - * Backend-owned renderer for one render marker type. - */ -public interface RenderHandlerThe engine uses a session to model "one rendering pass over one document". - * Backends can reuse expensive page-local resources during the pass while the - * engine keeps its contracts PDF-free. Concrete backends remain responsible for - * what a page surface actually is and how it is opened or closed.
- * - *Render handlers must treat the returned surfaces as session-owned. They may - * change graphics/text state for their own draw operation, but they must not - * close the surface directly.
- * - * @paramEntities spanning multiple pages must acquire surfaces explicitly per - * page fragment so their handler can manage page-local draw state.
- * - * @param entity renderable entity with resolved placement - * @return session-owned page surface for the entity's single page - * @throws IOException if the backend cannot provide the surface - */ - default T pageSurface(Entity entity) throws IOException { - Objects.requireNonNull(entity, "entity must not be null"); - Placement placement = entity.getComponent(Placement.class) - .orElseThrow(() -> new IllegalStateException("Entity " + entity + " is missing Placement")); - if (placement.startPage() != placement.endPage()) { - throw new IllegalStateException( - "Entity " + entity + " spans multiple pages and must request page surfaces per fragment"); - } - return pageSurface(placement.startPage()); - } - - @Override - void close() throws IOException; -} diff --git a/src/main/java/com/demcha/compose/engine/render/RenderStream.java b/src/main/java/com/demcha/compose/engine/render/RenderStream.java deleted file mode 100644 index 6913c8fe1..000000000 --- a/src/main/java/com/demcha/compose/engine/render/RenderStream.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.demcha.compose.engine.render; - -import java.io.IOException; - -/** - * Factory for backend-specific render-pass sessions. - * - *The engine does not ask the backend to open a fresh surface per entity. - * Instead it opens one session per render pass, and the backend can decide how - * to reuse page-local resources inside that pass.
- * - * @paramThe shared engine knows how to orchestrate a render pass, but not how a - * concrete backend materializes page surfaces. Implementations expose the - * active {@link RenderPassSession} during {@code process(...)} so handlers can - * reuse backend-local page resources without leaking PDF/DOCX/PPTX details into - * engine/template code.
- */ -public interface RenderingSystemECSThis is only valid while a rendering system is actively processing a - * document. Callers that need ad-hoc rendering outside a normal pass should - * open their own short-lived session from {@link #stream()}.
- */ - default RenderPassSessionThe renderer prefers the currently active render session when guide drawing - * happens during a render pass. If guide rendering is invoked outside an active - * pass, it falls back to a short-lived backend session so the public guide API - * remains usable.
- */ -@Slf4j -@Data -@Accessors(fluent = true) -public abstract class GuidesRendererPrefers the currently active session from the rendering system. If no - * session is active (e.g. guide rendering triggered outside a normal render - * pass), a short-lived session is opened and closed automatically.
- * - * @param action the rendering action to execute within a session - * @return {@code true} if the action rendered any guides - * @throws Exception if the action or session lifecycle fails - */ - private boolean withSession(SessionAction
- * Note: This default implementation relies on {@code resolveCoordinateContext} and {@code renderingSystemECS} methods
- * which are expected to be provided by the implementing class.
- *
- * @param entity The entity for which to create the context.
- * @return An {@link Optional} containing the {@link RenderCoordinateContext} for the padding, or an empty optional if it cannot be resolved.
- */
- default Optional Ownership: Owned by render diagnostics and used only when guide overlays are enabled. Extension rules: Extend per backend without changing document authoring APIs.