From dffaf9f6028a8e6e12a1f1a76fd3a4dc272d91fa Mon Sep 17 00:00:00 2001 From: DemchaAV Date: Fri, 10 Jul 2026 11:35:11 +0100 Subject: [PATCH] refactor(engine): retire the dormant ECS/Entity runtime MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The render path — DocumentSession → LayoutCompiler → LayoutGraph → the fixed-layout backend SPI → PdfFixedLayoutBackend — never touches the old Entity-Component-System engine. Remove it: the EntityManager / SystemECS runtime, the Entity component model with its geometry / coordinator / renderable companions, the ECS render pipeline (engine.render.* and the engine.render.guides renderers), LayoutSnapshotExtractor, and the unit tests plus com.demcha.mock fixtures that only exercised them. Six live leaves that still compiled against the cluster are decoupled with no change in behavior: - Margin / Padding / Anchor drop their ECS render methods and the RenderCoordinate interface, staying plain value types (still the empty Component marker) with every factory, accessor and equals/hashCode intact. - ParagraphWrapping / TextFlowSupport call TextControlSanitizer.remove(...) directly — the removed BlockText.sanitizeText was a pure delegate to it. - LayoutInsetsSnapshot inlines the snapshot-rounding helper it borrowed from LayoutSnapshotExtractor, byte-for-byte. Output is unchanged: the qa visual/parity suite (643 tests) stays green and the public guideLines(...) overlay is unaffected. --- CHANGELOG.md | 7 + qa/src/test/resources/logback.xml | 16 - .../document/layout/ParagraphWrapping.java | 6 +- .../document/layout/TextFlowSupport.java | 4 +- .../engine/components/core/Entity.java | 505 ------------------ .../engine/components/core/EntityName.java | 26 - .../components/geometry/EntityBounds.java | 74 --- .../components/geometry/InnerBoxSize.java | 78 --- .../components/geometry/OuterBoxSize.java | 88 --- .../engine/components/layout/Anchor.java | 105 +--- .../engine/components/layout/Layer.java | 15 - .../components/layout/ParentComponent.java | 13 - .../components/layout/RenderCoordinate.java | 11 - .../layout/coordinator/ComputedPosition.java | 100 ---- .../layout/coordinator/PaddingCoordinate.java | 61 --- .../layout/coordinator/Placement.java | 97 ---- .../layout/coordinator/Position.java | 23 - .../coordinator/RenderCoordinateContext.java | 13 - .../layout/coordinator/RenderingPosition.java | 41 -- .../layout/coordinator/package-info.java | 7 - .../components/renderable/BlockText.java | 64 --- .../components/renderable/TextComponent.java | 61 --- .../components/renderable/package-info.java | 7 - .../engine/components/style/Margin.java | 40 +- .../engine/components/style/Padding.java | 39 +- .../demcha/compose/engine/core/Canvas.java | 43 -- .../compose/engine/core/EntityManager.java | 296 ---------- .../engine/core/LayoutTraversalContext.java | 216 -------- .../demcha/compose/engine/core/SystemECS.java | 8 - .../compose/engine/core/SystemRegistry.java | 114 ---- .../compose/engine/core/package-info.java | 31 -- .../engine/debug/LayoutInsetsSnapshot.java | 33 +- .../engine/debug/LayoutSnapshotExtractor.java | 264 --------- .../ContentSizeNotFoundException.java | 17 - .../exceptions/RenderGuideLinesException.java | 12 - .../engine/exceptions/package-info.java | 7 - .../compose/engine/pagination/Breakable.java | 13 - .../compose/engine/pagination/Offset.java | 57 -- .../pagination/PageOutOfBoundException.java | 15 - .../pagination/ParentContainerUpdater.java | 181 ------- .../engine/pagination/package-info.java | 16 - .../demcha/compose/engine/render/Render.java | 6 - .../compose/engine/render/RenderHandler.java | 22 - .../engine/render/RenderHandlerRegistry.java | 37 -- .../engine/render/RenderPassSession.java | 65 --- .../compose/engine/render/RenderStream.java | 16 - .../engine/render/RenderingSystemECS.java | 56 -- .../engine/render/guides/BoxRender.java | 37 -- .../render/guides/GuidLineSettings.java | 27 - .../engine/render/guides/GuideCoordinate.java | 156 ------ .../engine/render/guides/GuidesRenderer.java | 287 ---------- .../engine/render/guides/MarginRender.java | 30 -- .../engine/render/guides/PaddingRender.java | 51 -- .../engine/render/guides/package-info.java | 7 - .../components/geometry/EntityBoundsTest.java | 38 -- .../components/geometry/InnerBoxSizeTest.java | 120 ----- .../components/geometry/OuterBoxSizeTest.java | 121 ----- .../layout/ComputedPositionTest.java | 155 ------ .../coordinator/ComputedPositionTest.java | 24 - .../layout/coordinator/PlacementTest.java | 15 - .../coordinator/RenderingPositionTest.java | 44 -- .../java/com/demcha/mock/FactoryClasses.java | 105 ---- .../java/com/demcha/mock/FactoryPresets.java | 73 --- .../java/com/demcha/mock/data/CanvasData.java | 4 - .../java/com/demcha/mock/data/MarginData.java | 7 - .../java/com/demcha/mock/data/OffsetData.java | 10 - .../java/com/demcha/mock/data/SizeData.java | 4 - src/test/resources/logback.xml | 16 - 68 files changed, 39 insertions(+), 4318 deletions(-) delete mode 100644 src/main/java/com/demcha/compose/engine/components/core/Entity.java delete mode 100644 src/main/java/com/demcha/compose/engine/components/core/EntityName.java delete mode 100644 src/main/java/com/demcha/compose/engine/components/geometry/EntityBounds.java delete mode 100644 src/main/java/com/demcha/compose/engine/components/geometry/InnerBoxSize.java delete mode 100644 src/main/java/com/demcha/compose/engine/components/geometry/OuterBoxSize.java delete mode 100644 src/main/java/com/demcha/compose/engine/components/layout/Layer.java delete mode 100644 src/main/java/com/demcha/compose/engine/components/layout/ParentComponent.java delete mode 100644 src/main/java/com/demcha/compose/engine/components/layout/RenderCoordinate.java delete mode 100644 src/main/java/com/demcha/compose/engine/components/layout/coordinator/ComputedPosition.java delete mode 100644 src/main/java/com/demcha/compose/engine/components/layout/coordinator/PaddingCoordinate.java delete mode 100644 src/main/java/com/demcha/compose/engine/components/layout/coordinator/Placement.java delete mode 100644 src/main/java/com/demcha/compose/engine/components/layout/coordinator/Position.java delete mode 100644 src/main/java/com/demcha/compose/engine/components/layout/coordinator/RenderCoordinateContext.java delete mode 100644 src/main/java/com/demcha/compose/engine/components/layout/coordinator/RenderingPosition.java delete mode 100644 src/main/java/com/demcha/compose/engine/components/layout/coordinator/package-info.java delete mode 100644 src/main/java/com/demcha/compose/engine/components/renderable/BlockText.java delete mode 100644 src/main/java/com/demcha/compose/engine/components/renderable/TextComponent.java delete mode 100644 src/main/java/com/demcha/compose/engine/components/renderable/package-info.java delete mode 100644 src/main/java/com/demcha/compose/engine/core/Canvas.java delete mode 100644 src/main/java/com/demcha/compose/engine/core/EntityManager.java delete mode 100644 src/main/java/com/demcha/compose/engine/core/LayoutTraversalContext.java delete mode 100644 src/main/java/com/demcha/compose/engine/core/SystemECS.java delete mode 100644 src/main/java/com/demcha/compose/engine/core/SystemRegistry.java delete mode 100644 src/main/java/com/demcha/compose/engine/core/package-info.java delete mode 100644 src/main/java/com/demcha/compose/engine/debug/LayoutSnapshotExtractor.java delete mode 100644 src/main/java/com/demcha/compose/engine/exceptions/ContentSizeNotFoundException.java delete mode 100644 src/main/java/com/demcha/compose/engine/exceptions/RenderGuideLinesException.java delete mode 100644 src/main/java/com/demcha/compose/engine/exceptions/package-info.java delete mode 100644 src/main/java/com/demcha/compose/engine/pagination/Breakable.java delete mode 100644 src/main/java/com/demcha/compose/engine/pagination/Offset.java delete mode 100644 src/main/java/com/demcha/compose/engine/pagination/PageOutOfBoundException.java delete mode 100644 src/main/java/com/demcha/compose/engine/pagination/ParentContainerUpdater.java delete mode 100644 src/main/java/com/demcha/compose/engine/pagination/package-info.java delete mode 100644 src/main/java/com/demcha/compose/engine/render/Render.java delete mode 100644 src/main/java/com/demcha/compose/engine/render/RenderHandler.java delete mode 100644 src/main/java/com/demcha/compose/engine/render/RenderHandlerRegistry.java delete mode 100644 src/main/java/com/demcha/compose/engine/render/RenderPassSession.java delete mode 100644 src/main/java/com/demcha/compose/engine/render/RenderStream.java delete mode 100644 src/main/java/com/demcha/compose/engine/render/RenderingSystemECS.java delete mode 100644 src/main/java/com/demcha/compose/engine/render/guides/BoxRender.java delete mode 100644 src/main/java/com/demcha/compose/engine/render/guides/GuidLineSettings.java delete mode 100644 src/main/java/com/demcha/compose/engine/render/guides/GuideCoordinate.java delete mode 100644 src/main/java/com/demcha/compose/engine/render/guides/GuidesRenderer.java delete mode 100644 src/main/java/com/demcha/compose/engine/render/guides/MarginRender.java delete mode 100644 src/main/java/com/demcha/compose/engine/render/guides/PaddingRender.java delete mode 100644 src/main/java/com/demcha/compose/engine/render/guides/package-info.java delete mode 100644 src/test/java/com/demcha/compose/engine/components/geometry/EntityBoundsTest.java delete mode 100644 src/test/java/com/demcha/compose/engine/components/geometry/InnerBoxSizeTest.java delete mode 100644 src/test/java/com/demcha/compose/engine/components/geometry/OuterBoxSizeTest.java delete mode 100644 src/test/java/com/demcha/compose/engine/components/layout/ComputedPositionTest.java delete mode 100644 src/test/java/com/demcha/compose/engine/components/layout/coordinator/ComputedPositionTest.java delete mode 100644 src/test/java/com/demcha/compose/engine/components/layout/coordinator/PlacementTest.java delete mode 100644 src/test/java/com/demcha/compose/engine/components/layout/coordinator/RenderingPositionTest.java delete mode 100644 src/test/java/com/demcha/mock/FactoryClasses.java delete mode 100644 src/test/java/com/demcha/mock/FactoryPresets.java delete mode 100644 src/test/java/com/demcha/mock/data/CanvasData.java delete mode 100644 src/test/java/com/demcha/mock/data/MarginData.java delete mode 100644 src/test/java/com/demcha/mock/data/OffsetData.java delete mode 100644 src/test/java/com/demcha/mock/data/SizeData.java 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 @@ - - - - - - - - - - - - - - - - diff --git a/src/main/java/com/demcha/compose/document/layout/ParagraphWrapping.java b/src/main/java/com/demcha/compose/document/layout/ParagraphWrapping.java index fd770fa41..6916fa0dc 100644 --- a/src/main/java/com/demcha/compose/document/layout/ParagraphWrapping.java +++ b/src/main/java/com/demcha/compose/document/layout/ParagraphWrapping.java @@ -7,7 +7,7 @@ import com.demcha.compose.engine.components.content.text.TextDataBody; import com.demcha.compose.engine.components.content.text.TextIndentStrategy; import com.demcha.compose.engine.components.content.text.TextStyle; -import com.demcha.compose.engine.components.renderable.BlockText; +import com.demcha.compose.engine.text.TextControlSanitizer; import com.demcha.compose.engine.measurement.TextMeasurementSystem; import com.demcha.compose.engine.text.markdown.MarkDownParser; @@ -560,7 +560,7 @@ private static List> tokenizeInlineRuns(List continue; } TextStyle style = textRun.textStyle() == null ? defaultStyle : toTextStyle(textRun.textStyle()); - String normalized = BlockText.sanitizeText(textRun.text().replace("\r\n", "\n").replace('\r', '\n')); + String normalized = TextControlSanitizer.remove(textRun.text().replace("\r\n", "\n").replace('\r', '\n')); String[] parts = normalized.split("\n", -1); for (int partIndex = 0; partIndex < parts.length; partIndex++) { if (partIndex > 0) { @@ -592,7 +592,7 @@ private static List> tokenizeInlineRuns(List // run's outer edges — lead pad on the first word, trail pad on the // last — and toInlineParagraphLine coalesces the same-group tokens on // each visual line back into one rounded fill. - String normalized = BlockText.sanitizeText( + String normalized = TextControlSanitizer.remove( highlight.text().replace("\r\n", " ").replace('\r', ' ').replace('\n', ' ')); if (normalized.isEmpty()) { continue; diff --git a/src/main/java/com/demcha/compose/document/layout/TextFlowSupport.java b/src/main/java/com/demcha/compose/document/layout/TextFlowSupport.java index 1db6c3e75..28b3ea341 100644 --- a/src/main/java/com/demcha/compose/document/layout/TextFlowSupport.java +++ b/src/main/java/com/demcha/compose/document/layout/TextFlowSupport.java @@ -8,7 +8,7 @@ import com.demcha.compose.document.style.DocumentTextStyle; import com.demcha.compose.engine.components.content.text.TextIndentStrategy; import com.demcha.compose.engine.components.content.text.TextStyle; -import com.demcha.compose.engine.components.renderable.BlockText; +import com.demcha.compose.engine.text.TextControlSanitizer; import com.demcha.compose.engine.components.style.Padding; import com.demcha.compose.engine.measurement.TextMeasurementSystem; @@ -736,7 +736,7 @@ private static List sanitizeLogicalLines(String rawText) { String[] logicalLines = safeText.split("\n", -1); List sanitized = new ArrayList<>(logicalLines.length); for (String logicalLine : logicalLines) { - sanitized.add(BlockText.sanitizeText(logicalLine)); + sanitized.add(TextControlSanitizer.remove(logicalLine)); } return List.copyOf(sanitized); } diff --git a/src/main/java/com/demcha/compose/engine/components/core/Entity.java b/src/main/java/com/demcha/compose/engine/components/core/Entity.java deleted file mode 100644 index 03e595e85..000000000 --- a/src/main/java/com/demcha/compose/engine/components/core/Entity.java +++ /dev/null @@ -1,505 +0,0 @@ -package com.demcha.compose.engine.components.core; - -import com.demcha.compose.engine.components.geometry.EntityBounds; -import com.demcha.compose.engine.core.EntityManager; -import com.demcha.compose.engine.render.Render; -import com.demcha.compose.engine.pagination.Offset; -import com.demcha.compose.engine.pagination.ParentContainerUpdater; -import lombok.EqualsAndHashCode; -import lombok.Getter; -import lombok.NonNull; -import lombok.Setter; -import lombok.extern.slf4j.Slf4j; - -import java.util.*; - -/** - * Core runtime node in the GraphCompose entity-component model. - *

- * 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. - *

- */ -@Slf4j -@EqualsAndHashCode -public final class Entity { - @Getter - private final UUID uuid; - private final Map, Component> comps = new LinkedHashMap<>(); - @Getter - private final List children = new ArrayList<>(); - private EntityName name; - @Getter - private Render render; - @Setter - @Getter - private boolean guideLines; - - /** - * Creates a new entity with a fresh runtime UUID. - */ - public Entity() { - UUID uuid = UUID.randomUUID(); - log.debug("Creating entity {}", uuid); - this.uuid = uuid; - } - - /** - * Creates a shallow component copy of another entity. - * - *

- * The new entity gets its own UUID while reusing the source entity's current - * component references as the initial snapshot. - *

- * - * @param entity source entity to clone from - * @return new entity with copied component references - */ - public static Entity createFrom(Entity entity) { - log.debug("Creating a copy of entity {}", entity); - var newEntity = new Entity(); - var values = new HashSet<>(entity.comps.values()); - newEntity.populate(values); - return newEntity; - } - - /** - * Returns the current entity name. - * - * @return configured name value - */ - public String name() { - return name.value(); - } - - /** - * Adds a component only when the same concrete component type is not already - * present. - * - * @param c component to add if absent - * @param component type - * @return this entity for fluent chaining - */ - public Entity addComponentIfAbsent(T c) { - if (c == null) { - log.warn("addComponentIfAbsent: component is null"); - return this; - } - Class key = c.getClass().asSubclass(Component.class); - Component prev = comps.putIfAbsent(key, c); - if (prev != null) { - if (log.isDebugEnabled()) { - log.debug("addComponentIfAbsent: component {} already exists on {}", key.getName(), this); - } - return this; - } - if (c instanceof EntityName en) - this.name = en; - if (log.isDebugEnabled()) { - log.debug("Added absent component {} to {}", key.getSimpleName(), this); - } - return this; - } - - /** - * Adds or replaces a component by its concrete runtime class. - * - *

- * 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. - *

- * - * @param c component to add - * @param component type - * @return this entity for fluent chaining - * @throws IllegalStateException if a second render component is attached - * through the strict add path - */ - public Entity addComponent(@NonNull T c) { - Class key = c.getClass().asSubclass(Component.class); - if (log.isDebugEnabled()) { - log.debug("Adding component {} to {}", c, this); - } - Component prev = comps.put(key, c); - if (c instanceof EntityName en) - this.name = en; - if (c instanceof Render) { - if (render == null) { - this.render = (Render) c; - } else { - log.warn("Render Component Already signed in {}", this); - throw new IllegalStateException("%s Render component already assigned in %s".formatted(c, this.render)); - } - - } - debugPutMethod(c, prev, "addComponent"); - return this; - } - - private void debugPutMethod(T c, Component prev, String methodName) { - if (log.isDebugEnabled()) { - if (prev == null) { - log.debug("Added component {} to {} through method {}", c, this, methodName); - } else { - log.debug("Replaced component {} on {} through method {}", c, this, methodName); - } - } - } - - /** - * Adds or replaces a component by its concrete runtime class, including - * overwriting an existing cached render component when necessary. - * - * @param c component to add - * @param component type - * @return this entity for fluent chaining - */ - public Entity forceAddComponent(@NonNull T c) { - Class key = c.getClass().asSubclass(Component.class); - Component prev = comps.put(key, c); - if (c instanceof EntityName en) - this.name = en; - if (c instanceof Render) { - if (render == null) { - this.render = (Render) c; - } else { - log.warn("Rendering Component Already signed in {}", this); - this.render = (Render) c; - } - - } - debugPutMethod(c, prev, "forceAddComponent"); - return this; - } - - /** - * Looks up a component by its exact concrete class key. - * - * @param type concrete component class - * @param component type - * @return the component wrapped in an {@link Optional}, or empty when absent - */ - public Optional getComponent(Class type) { - T value = type.cast(comps.get(type)); - if (value == null) { - return Optional.empty(); - } - return Optional.of(value); - } - - /** - * Looks up a component and fails fast when the entity does not have it. - * - * @param type concrete component class - * @param component type - * @return resolved component - * @throws NoSuchElementException when the component is missing - */ - public T require(Class type) { - T value = type.cast(comps.get(type)); - if (value != null) { - return value; - } - log.error("No component found for type {} for entity [{}]", type.getName(), uuid); - throw new NoSuchElementException("Missing " + type.getName() + " for " + uuid); - } - - /** - * Checks whether any attached component type is assignable to the given base - * class. - * - * @param baseClass base class or marker interface to test - * @return {@code true} when at least one attached component matches by - * assignability - */ - public boolean hasAssignable(Class baseClass) { - return comps.keySet().stream() - .anyMatch(c -> baseClass.isAssignableFrom(c)); - } - - /** - * Checks whether the entity contains a component of the exact given concrete - * type. - * - * @param type component class to check - * @return {@code true} when the component is present - */ - public boolean has(Class type) { - if (log.isDebugEnabled()) { - log.debug("Checking component {} {}", type, this); - } - return comps.containsKey(type); - } - - /** - * Convenience overload that checks the runtime type of the provided component - * instance. - * - * @param component component whose runtime type should be checked - * @param component type - * @return {@code true} when a component of that concrete type is attached - */ - public boolean has(T component) { - var type = component.getClass(); - return has(type); - } - - /** - * Returns whether the entity currently has a cached render marker. - * - *

- * This is intentionally O(1) and does not rescan the component map. - *

- * - * @return {@code true} when a render component is attached - */ - public boolean hasRender() { - return render != null; - } - - /** - * Compatibility wrapper for entity top-bound calculation. - * - * @return top outer line including top margin - * @deprecated use {@link EntityBounds#topLine(Entity)} directly in new code - */ - @Deprecated - public double boundingTopLine() { - return EntityBounds.topLine(this); - } - - /** - * Compatibility wrapper for entity bottom-bound calculation. - * - * @return bottom outer line including bottom margin - * @deprecated use {@link EntityBounds#bottomLine(Entity)} directly in new code - */ - @Deprecated - public double boundingBottomLine() { - return EntityBounds.bottomLine(this); - } - - /** - * Compatibility wrapper for entity right-bound calculation. - * - * @return right outer line including right margin - * @deprecated use {@link EntityBounds#rightLine(Entity)} directly in new code - */ - @Deprecated - public double boundingRightLine() { - return EntityBounds.rightLine(this); - } - - /** - * Compatibility wrapper for entity left-bound calculation. - * - * @return left outer line including left margin - * @deprecated use {@link EntityBounds#leftLine(Entity)} directly in new code - */ - @Deprecated - public double boundingLeftLine() { - return EntityBounds.leftLine(this); - } - - /** - * Populates the entity with the provided component set. - * - * @param components components to attach - * @param component type - * @return this entity for fluent chaining - */ - public Entity populate(Set components) { - if (log.isDebugEnabled()) { - log.debug("Creating and populating entity"); - log.debug("Populating entity UUID [{}] with\nComponents: {}", this, components); - } - for (Component component : components) { - addComponent(component); - } - if (log.isDebugEnabled()) { - log.debug("Created and populated entity {}", this); - } - return this; - - } - - /** - * Returns a read-only view of all components currently attached to the entity. - * - * @return an unmodifiable component map keyed by concrete component class - */ - public Map, Component> view() { - if (log.isDebugEnabled()) { - log.debug("Viewing component {} {}", uuid, this); - } - return Collections.unmodifiableMap(comps); - } - - /** - * Removes a component by its exact concrete class key. - * - * @param type component class to remove - * @return {@code true} when a component was removed - */ - public boolean remove(Class type) { - Component removed = comps.remove(type); - if (removed == null) { - return false; - } - if (removed == render) { - render = null; - } - if (removed == name) { - name = null; - } - return true; - } - - private String printChildren(EntityManager manager) { - StringBuilder childrenInfo = new StringBuilder(); - manager.getSetEntitiesFromUuids(new HashSet<>(children)).forEach(entity -> { - childrenInfo.append(entity.toString()).append("\n"); - }); - return childrenInfo.toString(); - - } - - /** - * Compatibility wrapper that propagates a child offset to the parent container. - * - * @param manager entity manager used to resolve parent entities - * @param offset propagated offset - * @return {@code true} when a parent update was applied - * @deprecated use {@link ParentContainerUpdater#updateParentContainer(Entity, EntityManager, Offset)} - * directly in new code - */ - @Deprecated - public boolean updateParentContainer(EntityManager manager, Offset offset) { - return ParentContainerUpdater.updateParentContainer(this, manager, offset); - } - - /** - * Compatibility wrapper that propagates a vertical delta to the parent - * container chain. - * - * @param manager entity manager used to resolve parent entities - * @param offsetY vertical delta to propagate - * @return {@code true} when a parent update was applied - * @deprecated use - * {@link ParentContainerUpdater#updateParentContainer(Entity, EntityManager, double)} - * directly in new code - */ - @Deprecated - public boolean updateParentContainer(EntityManager manager, double offsetY) { - return ParentContainerUpdater.updateParentContainer(this, manager, offsetY); - } - - /** - * Compatibility wrapper that propagates a size-only delta to the parent - * container chain. - * - * @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 - * @deprecated use - * {@link ParentContainerUpdater#updateParentContainerSize(Entity, EntityManager, double)} - * directly in new code - */ - @Deprecated - public boolean updateParentContainerSize(EntityManager manager, double offsetY) { - return ParentContainerUpdater.updateParentContainerSize(this, manager, offsetY); - } - - /** - * Compatibility wrapper that resizes and, when needed, repositions the target - * entity 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 an update was applied - * @deprecated use - * {@link ParentContainerUpdater#updateEntitySizeAndPosition(EntityManager, double, Entity)} - * directly in new code - */ - @Deprecated - public boolean updateEntitySizeAndPosition(EntityManager manager, double offsetY, @NonNull Entity entity) { - return ParentContainerUpdater.updateEntitySizeAndPosition(manager, offsetY, entity); - } - - /** - * Compatibility wrapper that resizes the target entity and 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 an update was applied - * @deprecated use {@link ParentContainerUpdater#updateEntitySize(EntityManager, double, Entity)} - * directly in new code - */ - @Deprecated - public boolean updateEntitySize(EntityManager manager, double offsetY, @NonNull Entity entity) { - return ParentContainerUpdater.updateEntitySize(manager, offsetY, entity); - } - - /** - * Compatibility wrapper that resizes the current entity and propagates the - * size-only change upward. - * - * @param manager entity manager used to resolve parent entities - * @param offsetY height delta to apply - * @return {@code true} when an update was applied - * @deprecated use - * {@link ParentContainerUpdater#updateCurrentEntitySize(Entity, EntityManager, double)} - * directly in new code - */ - @Deprecated - public boolean updateEntitySize(EntityManager manager, double offsetY) { - return ParentContainerUpdater.updateCurrentEntitySize(this, manager, offsetY); - } - - /** - * Returns a multi-line debug representation of the entity and its attached - * components. - * - * @return debug text for this entity - */ - public String printInfo() { - StringBuilder info = new StringBuilder(this + "\n"); - - comps.forEach((k, e) -> { - info.append(e.toString()).append("\n"); - }); - return info.toString(); - } - - /** - * Returns debug text for this entity plus its currently resolved child - * entities. - * - * @param entityManager entity manager used to resolve child UUIDs - * @return debug text for this entity and its children - */ - public String printInfoWithChildren(EntityManager entityManager) { - return printInfo() + printChildren(entityManager); - } - - @Override - public String toString() { - return "Entity[" + name + - " id: " + uuid + - " renderType: " + (render != null ? render.getClass().getSimpleName() : "null") + - ']'; - } - -} diff --git a/src/main/java/com/demcha/compose/engine/components/core/EntityName.java b/src/main/java/com/demcha/compose/engine/components/core/EntityName.java deleted file mode 100644 index 558e9e8e8..000000000 --- a/src/main/java/com/demcha/compose/engine/components/core/EntityName.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.demcha.compose.engine.components.core; - -/** - * Human-readable name for an entity. - * - *

Attach this component to make logs, debugging, and inspector views clearer. - * The name is not an identifier and does not have to be unique.

- * - *

Constraints

- *
    - *
  • Non-null and non-blank.
  • - *
  • Trimmed; recommended max length is 100 characters.
  • - *
- * - *

Examples: - *

{@code
- * addComponent(entityId, new EntityName("Header → ContactRow"));
- * addComponent(entityId, new EntityName("Button[primary]/#submit"));
- * }
- *

- * - * @param value the human-readable name of the entity; must be non-blank - * @since 1.0 - */ -public record EntityName(String value) implements Component { -} diff --git a/src/main/java/com/demcha/compose/engine/components/geometry/EntityBounds.java b/src/main/java/com/demcha/compose/engine/components/geometry/EntityBounds.java deleted file mode 100644 index 3efff34e5..000000000 --- a/src/main/java/com/demcha/compose/engine/components/geometry/EntityBounds.java +++ /dev/null @@ -1,74 +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.Margin; -import lombok.experimental.UtilityClass; - -/** - * Geometry helper for reading an entity's outer bounds from resolved layout - * state. - * - *

- * 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 from(@NonNull Entity entity) { - log.debug("Starting calculation a OuterBoxSize for entity: {}", entity); - Optional sizeOpt = entity.getComponent(ContentSize.class); - var padding = entity.getComponent(Padding.class).orElse(Padding.zero()); - var size = sizeOpt - .orElseThrow( - () -> { - log.debug("{} has no outer BoxSize.", entity); - return new ContentSizeNotFoundException(); - } - ); - - var innerBoxSizeOpt = from(size, padding); - return innerBoxSizeOpt; - } - - public static Optional from(ContentSize contentSize, Padding padding) { - Objects.requireNonNull(contentSize, () -> { - log.error("Can not calculate InnerBoxSize. All objects must have a ContentSize."); - throw new ContentSizeNotFoundException(); - }); - Objects.requireNonNull(padding, () -> { - log.error("Padding cannot be null."); - throw new NullPointerException("Padding cannot be null."); - }); - - var w = contentSize.width() - padding.horizontal(); - var h = contentSize.height() - padding.vertical(); - - var box = new InnerBoxSize(w, h); - log.debug("{}", box); - return Optional.of(box); - } - - - - private static Placement getPlacement(Entity entity) { - return entity.getComponent(Placement.class).orElseThrow(() -> { - log.error("Placement.class has not been found. {}", entity); - return new NullPointerException("Placement.class has not been found."); - }); - } - - public double getX(Placement placement, Padding padding) { - return placement.x() + padding.left(); - } - - public double getY(Placement placement, Padding padding) { - return placement.y() + padding.bottom(); - } - - public double getInnerX(Entity entity) { - Placement placement = getPlacement(entity); - Padding padding = entity.getComponent(Padding.class).orElse(Padding.zero()); - return getX(placement, padding); - } - - public double getInnerY(Entity entity) { - Placement placement = getPlacement(entity); - Padding padding = entity.getComponent(Padding.class).orElse(Padding.zero()); - return getY(placement, padding); - } -} diff --git a/src/main/java/com/demcha/compose/engine/components/geometry/OuterBoxSize.java b/src/main/java/com/demcha/compose/engine/components/geometry/OuterBoxSize.java deleted file mode 100644 index db0c59933..000000000 --- a/src/main/java/com/demcha/compose/engine/components/geometry/OuterBoxSize.java +++ /dev/null @@ -1,88 +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.Margin; -import com.demcha.compose.engine.exceptions.ContentSizeNotFoundException; -import lombok.Data; -import lombok.NonNull; -import lombok.experimental.Accessors; -import lombok.extern.slf4j.Slf4j; - -import java.util.Objects; -import java.util.Optional; - -/** - * This is size of Box Component which including a Component size + margins - * - */ -@Slf4j -@Accessors(fluent = true) -@Data -public final class OuterBoxSize { - final private double width; - final private double height; - - public static Optional from(@NonNull Entity entity) { - log.debug("Starting calculation a OuterBoxSize for entity: {}", entity); - Optional sizeOpt = entity.getComponent(ContentSize.class); - Margin margin = entity.getComponent(Margin.class).orElse(Margin.zero()); - var size = sizeOpt - .orElseThrow( - () -> { - log.debug("{} has no outer BoxSize.", entity); - return new ContentSizeNotFoundException(entity); - } - ); - Optional outerBoxSizeOpt = from(size, margin); - OuterBoxSize outerBoxSize = outerBoxSizeOpt.orElseThrow(); - log.debug("Calculated outer {} for entity: {}", outerBoxSize, entity); - return outerBoxSizeOpt; - } - - public static Optional from(@NonNull ContentSize contentSize, @NonNull Margin margin) { - Objects.requireNonNull(contentSize, () -> { - log.error("All objects must have a ContentSize."); - throw new ContentSizeNotFoundException(); - }); - - - var w = contentSize.width() + margin.horizontal(); - var h = contentSize.height() + margin.vertical(); - - var box = new OuterBoxSize(w, h); - log.debug("{}", box); - return Optional.of(box); - } - - private static Placement getPlacement(Entity entity) { - return entity.getComponent(Placement.class).orElseThrow(() -> { - log.error("Placement.class has not been found. {}", entity); - return new NullPointerException("Placement.class has not been found."); - }); - } - - public static double getX(Placement placement, @NonNull Margin margin) { - //TODO has to be placement.y() - margin.left() - return placement.x(); - } - - public static double getY(Placement placement, @NonNull Margin margin) { - return placement.y() - margin.bottom(); - } - - public static double getOuterX(Entity entity) { - Placement placement = getPlacement(entity); - Margin margin = entity.getComponent(Margin.class).orElse(Margin.zero()); - return getX(placement, margin); - } - - public static double getOuterY(Entity entity) { - Placement placement = getPlacement(entity); - Margin margin = entity.getComponent(Margin.class).orElse(Margin.zero()); - return getY(placement, margin); - } - - -} - diff --git a/src/main/java/com/demcha/compose/engine/components/layout/Anchor.java b/src/main/java/com/demcha/compose/engine/components/layout/Anchor.java index 09fae1c2f..67516c633 100644 --- a/src/main/java/com/demcha/compose/engine/components/layout/Anchor.java +++ b/src/main/java/com/demcha/compose/engine/components/layout/Anchor.java @@ -1,29 +1,12 @@ 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.InnerBoxSize; -import com.demcha.compose.engine.components.geometry.OuterBoxSize; -import com.demcha.compose.engine.components.layout.coordinator.ComputedPosition; -import com.demcha.compose.engine.components.layout.coordinator.Position; -import com.demcha.compose.engine.components.layout.coordinator.RenderingPosition; -import com.demcha.compose.engine.components.style.Margin; -import lombok.extern.slf4j.Slf4j; - -import java.util.Objects; /** - * Per-entity placement anchor used during layout. - *

- * {@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 { - Optional renderCoordinate(Entity entity, RenderingSystemECS renderingSystem); -} diff --git a/src/main/java/com/demcha/compose/engine/components/layout/coordinator/ComputedPosition.java b/src/main/java/com/demcha/compose/engine/components/layout/coordinator/ComputedPosition.java deleted file mode 100644 index 0f8ebf986..000000000 --- a/src/main/java/com/demcha/compose/engine/components/layout/coordinator/ComputedPosition.java +++ /dev/null @@ -1,100 +0,0 @@ -package com.demcha.compose.engine.components.layout.coordinator; - -import com.demcha.compose.engine.core.Canvas; -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.layout.Anchor; -import com.demcha.compose.engine.components.style.Margin; -import com.demcha.compose.engine.components.style.Padding; -import lombok.NonNull; -import lombok.extern.slf4j.Slf4j; - -/** - * Resolved position of an entity relative to its layout context. - *

- * {@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: - *

    - *
  • Apply the parent's padding to a child's computed position (most common in layout engines).
  • - *
  • Apply an entity's own padding to its computed position (less common, but supported).
  • - *
- */ -@Slf4j -public record PaddingCoordinate(double x, double y) { - - /** - * Creates a {@link PaddingCoordinate} by using the entity's already-present {@link ComputedPosition} - * and applying the entity's own {@link Padding}. - *

- * ⚠️ 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. - */ - public Optional renderCoordinate(Entity entity, RenderingSystemECS renderingSystem) { - double x; - double y; - double width; - double height; - int startPage; - int endPage; - - startPage = startPage(); - endPage = endPage(); - - x = x(); - y = y(); - width = width(); - height = height(); - Color color = renderingSystem.guidLineSettings().BOX_COLOR(); - Stroke stroke = renderingSystem.guidLineSettings().BOX_STROKE(); - return Optional.of(new RenderCoordinateContext(x, y, width, height, startPage, endPage, stroke, color)); - } -} diff --git a/src/main/java/com/demcha/compose/engine/components/layout/coordinator/Position.java b/src/main/java/com/demcha/compose/engine/components/layout/coordinator/Position.java deleted file mode 100644 index 45037b47e..000000000 --- a/src/main/java/com/demcha/compose/engine/components/layout/coordinator/Position.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.demcha.compose.engine.components.layout.coordinator; - -import com.demcha.compose.engine.components.core.Component; - -/** - * Represents an object that has a two-dimensional position. - * - *

Coordinates are typically defined in a Cartesian coordinate system, - * where X represents the horizontal axis and Y represents - * the vertical axis.

- * - *
    - *
  • X – Horizontal position (increasing values move right).
  • - *
  • Y – Vertical position (increasing values move down, unless using - * a mathematical coordinate system where values increase upward).
  • - *
- */ -public record Position(double x, double y) implements Component { - public static Position zero(){ - return new Position(0, 0); - } -} - diff --git a/src/main/java/com/demcha/compose/engine/components/layout/coordinator/RenderCoordinateContext.java b/src/main/java/com/demcha/compose/engine/components/layout/coordinator/RenderCoordinateContext.java deleted file mode 100644 index 7f36a6155..000000000 --- a/src/main/java/com/demcha/compose/engine/components/layout/coordinator/RenderCoordinateContext.java +++ /dev/null @@ -1,13 +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.Entity; - -import java.awt.*; - -public record RenderCoordinateContext(double x, double y, double width, double height, int startPage, int endPage, Stroke stroke, Color color){ -public static RenderCoordinateContext createBox (Entity entity, Stroke stroke, Color color) { - Placement placement = entity.getComponent(Placement.class).orElseThrow(); - return new RenderCoordinateContext(placement.x(), placement.y(), placement.width(), placement.height(), placement.startPage(), placement.endPage(), stroke, color); -} -} diff --git a/src/main/java/com/demcha/compose/engine/components/layout/coordinator/RenderingPosition.java b/src/main/java/com/demcha/compose/engine/components/layout/coordinator/RenderingPosition.java deleted file mode 100644 index 6ba4ab018..000000000 --- a/src/main/java/com/demcha/compose/engine/components/layout/coordinator/RenderingPosition.java +++ /dev/null @@ -1,41 +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.Margin; -import lombok.NonNull; -import lombok.extern.slf4j.Slf4j; - -import java.util.Optional; - -/** - * Position for rendering BoxElement - * - * @param x - * @param y - */ -@Slf4j -public record RenderingPosition(double x, double y) { - - public static RenderingPosition from(@NonNull ComputedPosition computed, @NonNull Margin margin) { - return new RenderingPosition(computed.x() + margin.left(), computed.y() + margin.bottom()); - } - - public static Optional from(Entity entity) { - log.debug("Computation a Rendering position for {}", entity); - var computedPosition = entity.getComponent(ComputedPosition.class); - if (computedPosition.isEmpty()) { - log.warn("TextComponent has no ComputedPosition; skipping: {}", entity); - return Optional.empty(); - } - var margin = entity.getComponent(Margin.class).orElse(Margin.zero()); - RenderingPosition from = from(computedPosition.get(), margin); - log.debug("Rendering position {}", from); - return Optional.of(from); - } - - @Override - public String toString() { - return "RenderingPosition[" + "x: " + x() + ", " + "y: " + y() + "]"; - - } -} diff --git a/src/main/java/com/demcha/compose/engine/components/layout/coordinator/package-info.java b/src/main/java/com/demcha/compose/engine/components/layout/coordinator/package-info.java deleted file mode 100644 index 1e822a6d6..000000000 --- a/src/main/java/com/demcha/compose/engine/components/layout/coordinator/package-info.java +++ /dev/null @@ -1,7 +0,0 @@ -/** - * Layout components that describe anchors, parent links, layers, transforms, and resolved coordinates. - * - *

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 Optional validatedTextData(@NonNull Entity entity) { - var blockTextDataOpt = entity.getComponent(BlockTextData.class); - var styleOpt = entity.getComponent(TextStyle.class); - - if (blockTextDataOpt.isEmpty()) { - log.info("TextComponent has no BlockTextData.class; skipping: {}", entity); - return Optional.empty(); - } - - TextStyle style = styleOpt.orElse(TextStyle.DEFAULT_STYLE); - if (styleOpt.isEmpty()) { - log.info("TextComponent has no TextStyle; skipping: {}", entity); - } - - return Optional.of(new ValidatedTextData(style, blockTextDataOpt.get())); - } - - public static Optional validatedPlacement(Entity entity) { - if (!entity.hasAssignable(BlockTextData.class)) { - log.debug("Entity doesn't have TextComponent; skipping: {}", entity); - return Optional.empty(); - } - - var placementOpt = entity.getComponent(Placement.class); - if (placementOpt.isEmpty()) { - log.warn("TextComponent has no Placement.class Component, skipping: {}", entity); - return Optional.empty(); - } - return placementOpt; - } - - public static String sanitizeText(String rawText) { - return TextControlSanitizer.remove(rawText); - } - - public record ValidatedTextData(TextStyle style, BlockTextData textValue) { - } -} diff --git a/src/main/java/com/demcha/compose/engine/components/renderable/TextComponent.java b/src/main/java/com/demcha/compose/engine/components/renderable/TextComponent.java deleted file mode 100644 index adeac8194..000000000 --- a/src/main/java/com/demcha/compose/engine/components/renderable/TextComponent.java +++ /dev/null @@ -1,61 +0,0 @@ -package com.demcha.compose.engine.components.renderable; - -import com.demcha.compose.engine.components.content.text.Text; -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.core.EntityManager; -import com.demcha.compose.engine.render.Render; -import com.demcha.compose.engine.measurement.TextMeasurementSystem; -import lombok.Builder; -import lombok.EqualsAndHashCode; -import lombok.NoArgsConstructor; -import lombok.extern.slf4j.Slf4j; - -import java.io.IOException; - -@Slf4j -@Builder -@EqualsAndHashCode -@NoArgsConstructor -public class TextComponent implements Render { - - public static ContentSize autoMeasureText(Entity entity, EntityManager entityManager) throws IOException { - Text text = entity.getComponent(Text.class).orElseThrow(); - TextStyle style = entity.getComponent(TextStyle.class).orElseThrow(); - TextMeasurementSystem measurementSystem = entityManager.getSystems() - .textMeasurement() - .orElseThrow(() -> new IllegalStateException("TextMeasurementSystem is required to auto-measure text.")); - ContentSize measured = measurementSystem.measure(style, text.value()); - double width = measured.width(); - double height = measured.height(); - log.debug("Auto-measured single-line text entity {} -> width={} height={} style={}", - entity, width, height, style); - return new ContentSize(width, height); - } - - public static ValidatedTextData validatedTextData(Entity e) { - var textValueOpt = e.getComponent(Text.class); - var styleOpt = e.getComponent(TextStyle.class); - - Text textValue; - TextStyle style; - if (textValueOpt.isEmpty()) { - log.info("TextComponent has no TextValue; skipping: {}", e); - textValue = textValueOpt.orElse(Text.empty()); - } else { - textValue = textValueOpt.get(); - } - if (styleOpt.isEmpty()) { - log.info("TextComponent has no TextStyle; skipping: {}", e); - style = styleOpt.orElse(TextStyle.DEFAULT_STYLE); - } else { - style = styleOpt.get(); - } - - return new ValidatedTextData(style, textValue); - } - - public record ValidatedTextData(TextStyle style, Text textValue) { - } -} diff --git a/src/main/java/com/demcha/compose/engine/components/renderable/package-info.java b/src/main/java/com/demcha/compose/engine/components/renderable/package-info.java deleted file mode 100644 index aeee07fa5..000000000 --- a/src/main/java/com/demcha/compose/engine/components/renderable/package-info.java +++ /dev/null @@ -1,7 +0,0 @@ -/** - * Backend-neutral render marker components for low-level engine entities. - * - *

Ownership: 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 Optional renderCoordinate(Entity entity, RenderingSystemECS renderingSystem) { - if (this.equals(Margin.zero())) { - log.info("Margin is zero, return empty"); - return Optional.empty(); - } - double x; - double y; - double width; - double height; - int startPage; - int endPage; - - var placement = entity.getComponent(Placement.class).orElseThrow(); - startPage = placement.startPage(); - endPage = placement.endPage(); - width = placement.width() + horizontal(); - height = placement.height() + vertical(); - x = placement.x() - left(); - y = placement.y() - bottom(); - Color color = renderingSystem.guidLineSettings().MARGIN_COLOR(); - Stroke stroke = renderingSystem.guidLineSettings().MARGIN_STROKE(); - - - return Optional.of(new RenderCoordinateContext(x, y, width, height, startPage, endPage, stroke, color)); - - } - public double horizontal() { return left + right; } diff --git a/src/main/java/com/demcha/compose/engine/components/style/Padding.java b/src/main/java/com/demcha/compose/engine/components/style/Padding.java index 8d526d802..7062c6284 100644 --- a/src/main/java/com/demcha/compose/engine/components/style/Padding.java +++ b/src/main/java/com/demcha/compose/engine/components/style/Padding.java @@ -1,21 +1,11 @@ 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.geometry.InnerBoxSize; -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 Padding(double top, double right, double bottom, double left) implements Component, RenderCoordinate { +public record Padding(double top, double right, double bottom, double left) implements Component { public static Padding zero() { log.debug("Getting zero padding"); return new Padding(0.0, 0.0, 0.0, 0.0); @@ -49,31 +39,4 @@ public int hashCode() { result = 31 * result + Double.hashCode(left); return result; } - - @Override - public Optional renderCoordinate(Entity entity, RenderingSystemECS renderingSystem) { - if (this.equals(zero())) { - log.info("Padding is zero, return empty"); - return Optional.empty(); - } - var inner = InnerBoxSize.from(entity).orElseThrow(); - var placement = entity.getComponent(Placement.class).orElseThrow(); - double x; - double y; - double width; - double height; - int startPage; - int endPage; - - startPage = placement.startPage(); - endPage = placement.endPage(); - - x = placement.x() + left(); - y = placement.y()+ bottom(); - width = inner.width(); - height = inner.height(); - Color color = renderingSystem.guidLineSettings().PADDING_COLOR(); - Stroke stroke = renderingSystem.guidLineSettings().PADDING_STROKE(); - return Optional.of(new RenderCoordinateContext(x, y, width, height, startPage, endPage,stroke, color)); - } } diff --git a/src/main/java/com/demcha/compose/engine/core/Canvas.java b/src/main/java/com/demcha/compose/engine/core/Canvas.java deleted file mode 100644 index b8b58cbfa..000000000 --- a/src/main/java/com/demcha/compose/engine/core/Canvas.java +++ /dev/null @@ -1,43 +0,0 @@ -package com.demcha.compose.engine.core; - -import com.demcha.compose.engine.components.style.Margin; - -public interface Canvas { - float width(); - - float x(); - - float y(); - - float height(); - - Margin margin(); - - default float boundingTopLine() { - return height() - (float) margin().top(); - } - - default float boundingBottomLine() { - return (float) margin().bottom(); - } - - default float boundingRightLine() { - return width() - (float) margin().right(); - } - - default float boundingLeftLine() { - return (float) margin().left(); - } - - default double innerHeigh() { - return height() - margin().vertical(); - } - - default double innerWidth() { - return width() - margin().horizontal(); - } - - - void addMargin(Margin margin); - -} diff --git a/src/main/java/com/demcha/compose/engine/core/EntityManager.java b/src/main/java/com/demcha/compose/engine/core/EntityManager.java deleted file mode 100644 index 8fb2345fd..000000000 --- a/src/main/java/com/demcha/compose/engine/core/EntityManager.java +++ /dev/null @@ -1,296 +0,0 @@ -package com.demcha.compose.engine.core; - -import com.demcha.compose.font.FontLibrary; -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.layout.ParentComponent; -import com.demcha.compose.engine.core.SystemRegistry; -import com.demcha.compose.engine.render.Render; -import com.demcha.compose.engine.core.SystemECS; -import lombok.Getter; -import lombok.NonNull; -import lombok.Setter; -import lombok.extern.slf4j.Slf4j; - -import java.util.ArrayList; -import java.util.LinkedHashMap; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.Set; -import java.util.TreeMap; -import java.util.UUID; -import java.util.stream.Collectors; - -/** - * Central registry for entities, systems, and document-scoped engine state. - *

- * {@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.

- * - *

Typical role in the pipeline

- *
    - *
  1. builders create entities and register them here
  2. - *
  3. layout systems compute sizes, positions, and pagination metadata
  4. - *
  5. rendering systems read renderable entities and draw the final output
  6. - *
- * - * @author Artem Demchyshyn - * @since 1.0.0 - */ -@Slf4j -@Getter -@Setter -public class EntityManager { - private final Map entities; - private final SystemRegistry systems; - private final FontLibrary fonts; - private Map> layers; - private Map depthById; - private long mutationVersion; - private boolean guideLines; - private boolean markdown = false; - - public EntityManager() { - this(false); - } - - public EntityManager(@NonNull List systems) { - this(systems, new FontLibrary(), true); - } - - public EntityManager(@NonNull List systems, FontLibrary fonts, boolean markdown) { - log.debug("Creating new EntityManager"); - this.fonts = fonts; - this.markdown = markdown; - this.systems = new SystemRegistry(); - this.systems.addAllSystems(systems); - this.entities = new LinkedHashMap<>(); - this.layers = new TreeMap<>(); - this.depthById = new LinkedHashMap<>(); - } - - public EntityManager(boolean markdown) { - this(new ArrayList<>(), new FontLibrary(), markdown); - } - - public EntityManager(FontLibrary fonts, boolean markdown) { - this(new ArrayList<>(), fonts, markdown); - } - - public Entity createEntity() { - return createEntity(null); - } - - /** - * Creates and registers a new entity, optionally assigning an {@link EntityName}. - * - * @param name optional logical name used for diagnostics and debugging - * @return the newly created entity - */ - public Entity createEntity(String name) { - var entity = new Entity(); - - putEntity(entity); - - if (name != null && !name.isBlank()) { - entity.addComponent(new EntityName(name)); - log.trace("Created {}", entity); - } else { - log.trace("Created entity with no EntityName {}", entity.getUuid()); - } - return entity; - } - - public Optional getEntity(UUID id) { - log.trace("Getting Entity id {}", id); - return Optional.ofNullable(entities.get(id)); - } - - public void setGuideLines(boolean guideLines) { - this.guideLines = guideLines; - touchMutation(); - entities.forEach((id, entity) -> { - entity.setGuideLines(guideLines); - }); - } - - public void setMarkdown(boolean markdown) { - this.markdown = markdown; - touchMutation(); - } - - public Entity putEntity(Entity entity) { - UUID uuid = entity.getUuid(); - log.trace("Putting Entity id {}", uuid); - - Optional existing = getEntity(uuid); - - if (existing.isPresent()) { - if (entity.equals(existing)) { - log.trace("Entity already exists and is identical"); - return existing.orElse(null); - } else { - log.warn("Entity conflict detected for id {}. Replacing old entity.", uuid); - touchMutation(); - return entities.put(uuid, entity); - } - } - entity.setGuideLines(guideLines); - touchMutation(); - return entities.put(uuid, entity); - } - - public String displayName(UUID id) { - var entity = getEntity(id); - - log.info("Displaying entity with id {}", id); - String name = this.entities.get(id).name(); - return name == null ? "Entity#" + id.toString().substring(0, 8) : name; - } - - public void addComponent(UUID entityId, Component component) { - Entity entity = entities.get(entityId); - entity.addComponent(component); - } - - public Optional getComponent(UUID entityId, Class componentType) { - Optional entity = getEntity(entityId); - return entity.flatMap(value -> value.getComponent(componentType)); - } - - public Optional> getEntitiesWithComponent(Class componentType) { - log.debug("Getting component with id {}", componentType.getName()); - Set entityIds = null; - for (Map.Entry entry : entities.entrySet()) { - var entityId = entry.getKey(); - log.debug("Checking entity with id {}", entry.getKey()); - var components = entry.getValue(); - if (components.has(componentType)) { - log.debug("Found entity id [{}] with component {}", entityId, componentType.getName()); - if (entityIds == null) { - entityIds = new LinkedHashSet<>(); - } - log.debug("Adding entity [{}], to list with component {}", entityId, componentType.getName()); - entityIds.add(entityId); - } - } - if (entityIds == null || entityIds.isEmpty()) { - log.warn("No component with id {} found", componentType.getName()); - } - log.debug("Found [{}] entities with component {}", entityIds == null ? 0 : entityIds.size(), - componentType.getName()); - return Optional.ofNullable(entityIds); - } - - public Set getEntitiesWithAnyRender() { - Set result = new LinkedHashSet<>(); - for (Map.Entry e : entities.entrySet()) { - boolean hasRenderable = e.getValue().view().values().stream() - .anyMatch(comp -> comp instanceof Render); - if (hasRenderable) { - result.add(e.getKey()); - } - } - return result; - } - - public boolean remove(UUID entityId) { - boolean removed = entities.remove(entityId) != null; - if (removed) { - touchMutation(); - } - return removed; - } - - public boolean remove(Entity entity) { - boolean removed = entities.remove(entity.getUuid()) != null; - if (removed) { - touchMutation(); - } - return removed; - } - - public Set getEntitiesWithRender(Class componentType) { - log.debug("Searching for entities with component type {}", componentType.getName()); - - Set result = new LinkedHashSet<>(); - for (Map.Entry e : entities.entrySet()) { - UUID entityId = e.getKey(); - Entity entity = e.getValue(); - - // перебираем компоненты у сущности - for (Component comp : entity.view().values()) { - // подходит ли по типу? - if (componentType.isInstance(comp)) { - result.add(entityId); - log.debug("Matched entity {} with {}", entityId, componentType.getSimpleName()); - break; // уже добавили — дальше компоненты этой сущности можно не проверять - } - } - } - - if (result.isEmpty()) { - log.warn("No entities found with {}", componentType.getName()); - } else { - log.debug("Found {} entities with {}", result.size(), componentType.getName()); - } - return result; - } - - /** - * Retrive Entities from UUUIDs Set - * - * @param uuids Set will be transformed - * @return Set Entities - */ - public Set getSetEntitiesFromUuids(Set uuids) { - return uuids - .stream() - .map(this::getEntity) - .filter(Optional::isPresent) - .map(Optional::get) - .collect(Collectors.toCollection(LinkedHashSet::new)); - - } - - /** - * SystemECS - */ - public void processSystems() { - log.info("Processing Systems"); - for (SystemECS system : systems.systems().values()) { - log.info("Processing SystemECS {}", system); - system.process(this); // Передаём себя, чтобы система могла получить доступ к компонентам - } - log.info("Processed Systems"); - } - - public void printEntities() { - this.entities.forEach((uuid, entity) -> { - java.lang.System.out.println(entity.toString()); - entity.view().forEach((k, v) -> java.lang.System.out.printf("UUID[%s] : %s \n", uuid, entity.toString())); - }); - } - - public void printEntitiesWithInfo() { - this.entities.forEach((uuid, entity) -> { - java.lang.System.out.println(entity.toString()); - entity.view().forEach((k, v) -> java.lang.System.out.printf("UUID[%s] : %s \n", uuid, entity.printInfo())); - }); - } - - private void touchMutation() { - mutationVersion++; - } - -} diff --git a/src/main/java/com/demcha/compose/engine/core/LayoutTraversalContext.java b/src/main/java/com/demcha/compose/engine/core/LayoutTraversalContext.java deleted file mode 100644 index b46a5c4c2..000000000 --- a/src/main/java/com/demcha/compose/engine/core/LayoutTraversalContext.java +++ /dev/null @@ -1,216 +0,0 @@ -package com.demcha.compose.engine.core; - -import com.demcha.compose.engine.components.core.Entity; -import com.demcha.compose.engine.components.layout.ParentComponent; -import lombok.extern.slf4j.Slf4j; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.LinkedHashMap; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.TreeMap; -import java.util.UUID; - -/** - * Immutable, deterministic view of the current entity hierarchy for one layout pass. - * - *

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:

- *
    - *
  • {@link ParentComponent} as the authoritative parent reference
  • - *
  • {@link Entity#getChildren()} as the canonical sibling order when it is consistent
  • - *
- * - *

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 Map parentById; - private final Map> childrenByParent; - private final Set roots; - - private LayoutTraversalContext(Map parentById, - Map> childrenByParent, - Set roots) { - this.parentById = parentById; - this.childrenByParent = childrenByParent; - this.roots = roots; - } - - /** - * Clears layout-owned traversal state on the manager before a new layout pass. - * - *

The 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) { - Map entities = entityManager.getEntities(); - Map parentById = buildParentById(entities); - Map> childrenByParent = buildChildrenByParent(entities, parentById); - Set roots = buildRoots(entities, parentById); - - return new LayoutTraversalContext( - Collections.unmodifiableMap(parentById), - Collections.unmodifiableMap(childrenByParent), - Collections.unmodifiableSet(roots)); - } - - /** - * Returns an unmodifiable map from each child entity ID to its parent entity ID. - * - *

Only entities that carry a {@link ParentComponent} appear as keys.

- * - * @return child → parent mapping, never {@code null} - */ - public Map parentById() { - return parentById; - } - - /** - * Returns an unmodifiable map from each parent entity ID to its ordered list of - * child entity IDs. - * - *

The 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 Map> childrenByParent() { - return childrenByParent; - } - - /** - * Returns the set of root entity IDs — entities that have no resolved parent. - * - *

If 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 Set roots() { - return roots; - } - - /** - * Walks all entities and indexes child → parent relationships from - * {@link ParentComponent}. Insertion order mirrors the entity map. - */ - private static Map buildParentById(Map entities) { - Map parentById = new LinkedHashMap<>(); - - for (Map.Entry entry : entities.entrySet()) { - entry.getValue() - .getComponent(ParentComponent.class) - .ifPresent(parentComponent -> parentById.put(entry.getKey(), parentComponent.uuid())); - } - - return parentById; - } - - /** - * Builds the ordered children map by reconciling {@link Entity#getChildren()} - * with {@link ParentComponent}. Stale or inconsistent references are logged and - * handled deterministically. - */ - private static Map> buildChildrenByParent(Map entities, - Map parentById) { - Map> childrenByParent = new LinkedHashMap<>(); - Set assignedChildren = new LinkedHashSet<>(); - - for (Entity parent : entities.values()) { - List orderedChildren = new ArrayList<>(); - - for (UUID childId : parent.getChildren()) { - UUID actualParentId = parentById.get(childId); - if (actualParentId == null) { - log.warn("Traversal context skipped stale child reference {} from parent {} because the child has no ParentComponent.", - childId, parent.getUuid()); - continue; - } - if (!actualParentId.equals(parent.getUuid())) { - log.warn("Traversal context skipped stale child reference {} from parent {} because ParentComponent points to {}.", - childId, parent.getUuid(), actualParentId); - continue; - } - if (!entities.containsKey(childId)) { - log.warn("Traversal context skipped missing child {} referenced by parent {}.", childId, parent.getUuid()); - continue; - } - orderedChildren.add(childId); - assignedChildren.add(childId); - } - - if (!orderedChildren.isEmpty()) { - childrenByParent.put(parent.getUuid(), List.copyOf(orderedChildren)); - } - } - - for (Map.Entry entry : parentById.entrySet()) { - UUID childId = entry.getKey(); - UUID parentId = entry.getValue(); - - if (assignedChildren.contains(childId)) { - continue; - } - if (!entities.containsKey(childId) || !entities.containsKey(parentId)) { - continue; - } - - log.warn("Traversal context appended child {} to parent {} because ParentComponent exists but the parent's children list is missing it.", - childId, parentId); - childrenByParent - .computeIfAbsent(parentId, key -> new ArrayList<>()) - .add(childId); - } - - Map> immutableChildren = new LinkedHashMap<>(); - for (Map.Entry> entry : childrenByParent.entrySet()) { - immutableChildren.put(entry.getKey(), List.copyOf(entry.getValue())); - } - return immutableChildren; - } - - /** - * Identifies root entities — those with no parent or whose parent is missing - * from the entity set. Falls back to all entities if no roots are found. - */ - private static Set buildRoots(Map entities, Map parentById) { - Set roots = new LinkedHashSet<>(); - - for (UUID entityId : entities.keySet()) { - UUID parentId = parentById.get(entityId); - if (parentId == null) { - roots.add(entityId); - continue; - } - if (!entities.containsKey(parentId)) { - roots.add(entityId); - log.warn("Traversal context treats entity {} as root because parent {} is missing.", entityId, parentId); - } - } - - if (roots.isEmpty()) { - log.warn("Traversal context computed no roots; falling back to all entities to expose the cycle at traversal time."); - roots.addAll(entities.keySet()); - } - - return new LinkedHashSet<>(roots); - } -} diff --git a/src/main/java/com/demcha/compose/engine/core/SystemECS.java b/src/main/java/com/demcha/compose/engine/core/SystemECS.java deleted file mode 100644 index 9cec4fa55..000000000 --- a/src/main/java/com/demcha/compose/engine/core/SystemECS.java +++ /dev/null @@ -1,8 +0,0 @@ -package com.demcha.compose.engine.core; - -import com.demcha.compose.engine.core.EntityManager; - -public interface SystemECS { - void process(EntityManager entityManager); - -} diff --git a/src/main/java/com/demcha/compose/engine/core/SystemRegistry.java b/src/main/java/com/demcha/compose/engine/core/SystemRegistry.java deleted file mode 100644 index f2e473be5..000000000 --- a/src/main/java/com/demcha/compose/engine/core/SystemRegistry.java +++ /dev/null @@ -1,114 +0,0 @@ -package com.demcha.compose.engine.core; - -import com.demcha.compose.engine.core.SystemECS; -import com.demcha.compose.engine.measurement.TextMeasurementSystem; -import lombok.AccessLevel; -import lombok.Getter; -import lombok.experimental.Accessors; -import lombok.extern.slf4j.Slf4j; - -import java.util.*; -import java.util.stream.Stream; - -/** - * A registry for managing and retrieving Entity Component System (ECS) systems. - * Provides methods to add, retrieve, and stream registered systems. - */ -@Slf4j -@Getter -@Accessors(fluent = true) -public class SystemRegistry { - - /** - * A map storing registered systems, keyed by their class type. - * Uses a LinkedHashMap to maintain the order of insertion. - */ - private final Map, SystemECS> systems = new LinkedHashMap<>(); - - /** - * The text-measurement service provider, held separately from the - * process()-driven {@link #systems} map. Measurement exposes font metrics to - * builders and layout helpers on demand; it is not a {@link SystemECS} and - * never participates in the {@code processSystems()} loop, so it is registered - * out-of-band via {@link #registerTextMeasurement(TextMeasurementSystem)} - * rather than {@link #addSystem(SystemECS)}. - */ - @Getter(AccessLevel.NONE) - private TextMeasurementSystem textMeasurement; - - /** - * Registers the text-measurement service exposed to the legacy engine's text - * components and layout alignment. Unlike {@link #addSystem(SystemECS)} this - * does not enroll the measurement system in the {@code process()} loop — it is - * a service provider, not an ECS system. - * - * @param textMeasurement the measurement service to expose - * @since 1.7.1 - */ - public void registerTextMeasurement(TextMeasurementSystem textMeasurement) { - this.textMeasurement = Objects.requireNonNull(textMeasurement, "textMeasurement"); - } - - /** - * Retrieves the registered text-measurement service, if any. - * - * @return an Optional containing the measurement service, or empty if none has - * been registered - * @since 1.7.1 - */ - public Optional textMeasurement() { - return Optional.ofNullable(textMeasurement); - } - - /** - * Adds a single system to the registry. - * - * @param system the system to add - * @param the type of the system, extending SystemECS - */ - public void addSystem(T system) { - log.info("Adding SystemECS {}", system.getClass().getName()); - systems.put(system.getClass(), system); - } - - /** - * Returns a stream of all registered systems. - * - * @return a stream of SystemECS instances - */ - public Stream getStream() { - return systems.values().stream(); - } - - /** - * Retrieves a system from the registry by its class type. - * If an exact match is not found, it searches for a subclass or implementation. - * - * @param systemClass the class of the system to retrieve - * @param the expected type of the system - * @return an Optional containing the system if found, or an empty Optional otherwise - */ - public Optional getSystem(Class systemClass) { - SystemECS system = systems.get(systemClass); - if (systemClass.isInstance(system)) { - return Optional.of(systemClass.cast(system)); - } - for (SystemECS candidate : systems.values()) { - if (systemClass.isInstance(candidate)) { - return Optional.of(systemClass.cast(candidate)); - } - } - return Optional.empty(); - } - - /** - * Adds a list of systems to the registry. - * - * @param systems the list of systems to add - * @param the type of the systems, extending SystemECS - */ - public void addAllSystems(List systems) { - systems.forEach(this::addSystem); - } - -} diff --git a/src/main/java/com/demcha/compose/engine/core/package-info.java b/src/main/java/com/demcha/compose/engine/core/package-info.java deleted file mode 100644 index 8cdf0263f..000000000 --- a/src/main/java/com/demcha/compose/engine/core/package-info.java +++ /dev/null @@ -1,31 +0,0 @@ -/** - * Core primitives of the legacy ECS engine — entity/component - * graphs and shared traversal state for the original {@code Entity}-based - * layout / pagination / render pipeline. - * - *

This 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"); - - Map entities = entityManager.getEntities(); - if (entities == null || entities.isEmpty()) { - return new LayoutSnapshot( - FORMAT_VERSION, - snapshotCanvas(canvas), - 0, - List.of()); - } - - // Resolved-order features must always traverse through LayoutTraversalContext - // so snapshots, pagination, and renderer ordering agree on the same tree. - LayoutTraversalContext traversalContext = LayoutTraversalContext.from(entityManager); - List roots = resolveRoots(entities, traversalContext); - List nodes = new ArrayList<>(entities.size()); - - for (int rootIndex = 0; rootIndex < roots.size(); rootIndex++) { - visitNode( - entityManager, - entities, - traversalContext, - roots.get(rootIndex), - null, - rootIndex, - nodes, - new HashSet<>()); - } - - int totalPages = nodes.stream() - .mapToInt(node -> Math.max(node.startPage(), node.endPage()) + 1) - .max() - .orElse(0); - - return new LayoutSnapshot( - FORMAT_VERSION, - snapshotCanvas(canvas), - totalPages, - List.copyOf(nodes)); - } - - static double normalize(double value) { - if (Math.abs(value) < 0.0005d) { - return 0.0d; - } - return BigDecimal.valueOf(value) - .setScale(3, RoundingMode.HALF_UP) - .doubleValue(); - } - - private static void visitNode(EntityManager entityManager, - Map entities, - LayoutTraversalContext traversalContext, - Entity entity, - String parentPath, - int childIndex, - List snapshots, - Set activePath) { - if (!activePath.add(entity.getUuid())) { - throw new IllegalStateException("Cycle detected while extracting layout snapshot for entity " + describeEntity(entity)); - } - - String segment = nodeSegment(entity, childIndex); - String path = parentPath == null ? segment : parentPath + PATH_SEPARATOR + segment; - - snapshots.add(toNodeSnapshot(entityManager, entity, parentPath, path, childIndex)); - - List childIds = traversalContext.childrenByParent().getOrDefault(entity.getUuid(), List.of()); - for (int index = 0; index < childIds.size(); index++) { - UUID childId = childIds.get(index); - Entity child = entities.get(childId); - if (child == null) { - throw new IllegalStateException("Missing child entity " + childId + " referenced from " + describeEntity(entity)); - } - visitNode(entityManager, entities, traversalContext, child, path, index, snapshots, activePath); - } - - activePath.remove(entity.getUuid()); - } - - private static LayoutNodeSnapshot toNodeSnapshot(EntityManager entityManager, - Entity entity, - String parentPath, - String path, - int childIndex) { - String label = path + " (" + describeEntity(entity) + ")"; - ComputedPosition computedPosition = requireComponent(entity, ComputedPosition.class, label); - Placement placement = requireComponent(entity, Placement.class, label); - ContentSize contentSize = requireComponent(entity, ContentSize.class, label); - int layer = requireComponent(entity, Layer.class, label).value(); - int depth = entityManager.getDepthById() - .getOrDefault(entity.getUuid(), layer); - - return new LayoutNodeSnapshot( - path, - semanticEntityName(entity), - entityKind(entity), - parentPath, - childIndex, - depth, - layer, - normalize(computedPosition.x()), - normalize(computedPosition.y()), - normalize(placement.x()), - normalize(placement.y()), - normalize(placement.width()), - normalize(placement.height()), - placement.startPage(), - placement.endPage(), - normalize(contentSize.width()), - normalize(contentSize.height()), - LayoutInsetsSnapshot.from(entity.getComponent(Margin.class).orElse(Margin.zero())), - LayoutInsetsSnapshot.from(entity.getComponent(Padding.class).orElse(Padding.zero()))); - } - - private static LayoutCanvasSnapshot snapshotCanvas(Canvas canvas) { - return new LayoutCanvasSnapshot( - normalize(canvas.width()), - normalize(canvas.height()), - normalize(canvas.innerWidth()), - normalize(canvas.innerHeigh()), - LayoutInsetsSnapshot.from(canvas.margin())); - } - - private static List resolveRoots(Map entities, LayoutTraversalContext traversalContext) { - Comparator rootComparator = Comparator - .comparingInt(LayoutSnapshotExtractor::startPage) - .thenComparing(LayoutSnapshotExtractor::topToBottomY) - .thenComparing(LayoutSnapshotExtractor::leftToRightX) - .thenComparing(LayoutSnapshotExtractor::entitySortKey); - - return traversalContext.roots().stream() - .map(entities::get) - .filter(Objects::nonNull) - .sorted(rootComparator) - .collect(Collectors.toList()); - } - - private static int startPage(Entity entity) { - return requireComponent(entity, Placement.class, describeEntity(entity)).startPage(); - } - - private static double topToBottomY(Entity entity) { - return -requireComponent(entity, Placement.class, describeEntity(entity)).y(); - } - - private static double leftToRightX(Entity entity) { - return requireComponent(entity, Placement.class, describeEntity(entity)).x(); - } - - private static String entitySortKey(Entity entity) { - String semanticName = semanticEntityName(entity); - return semanticName == null ? entityKind(entity) : semanticName; - } - - private static String nodeSegment(Entity entity, int childIndex) { - String base = semanticEntityName(entity); - if (base != null) { - base = sanitizeSegment(base); - } - if (base == null || base.isBlank()) { - base = entityKind(entity); - } - - return base + "[" + childIndex + "]"; - } - - private static String sanitizeSegment(String value) { - return value.trim() - .replace('\\', '_') - .replace('/', '_'); - } - - private static String entityKind(Entity entity) { - if (entity.getRender() != null) { - return entity.getRender().getClass().getSimpleName(); - } - return entity.getClass().getSimpleName(); - } - - private static String semanticEntityName(Entity entity) { - return entity.getComponent(EntityName.class) - .map(EntityName::value) - .map(String::trim) - .filter(value -> !value.isBlank()) - .filter(value -> !GENERATED_ENTITY_NAME.matcher(value).matches()) - .orElse(null); - } - - private static String describeEntity(Entity entity) { - String semanticName = semanticEntityName(entity); - return semanticName != null - ? semanticName - : entityKind(entity) + "#" + entity.getUuid().toString().substring(0, 8); - } - - private static T requireComponent(Entity entity, Class componentType, String label) { - return entity.getComponent(componentType) - .orElseThrow(() -> new NoSuchElementException("Layout snapshot requires " + componentType.getSimpleName() - + " for " + label)); - } -} diff --git a/src/main/java/com/demcha/compose/engine/exceptions/ContentSizeNotFoundException.java b/src/main/java/com/demcha/compose/engine/exceptions/ContentSizeNotFoundException.java deleted file mode 100644 index fa280f6fd..000000000 --- a/src/main/java/com/demcha/compose/engine/exceptions/ContentSizeNotFoundException.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.demcha.compose.engine.exceptions; - -import com.demcha.compose.engine.components.core.Entity; - -public class ContentSizeNotFoundException extends RuntimeException { - public ContentSizeNotFoundException() { - super(" All objects must have a ContentSize."); - } - - public ContentSizeNotFoundException(String s) { - super(s); - } - - public ContentSizeNotFoundException(Entity entity) { - super(entity.toString() + " All objects must have a ContentSize."); - } -} diff --git a/src/main/java/com/demcha/compose/engine/exceptions/RenderGuideLinesException.java b/src/main/java/com/demcha/compose/engine/exceptions/RenderGuideLinesException.java deleted file mode 100644 index d0931b49a..000000000 --- a/src/main/java/com/demcha/compose/engine/exceptions/RenderGuideLinesException.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.demcha.compose.engine.exceptions; - -public class RenderGuideLinesException extends RuntimeException { - - public RenderGuideLinesException(String info) { - super(info); - } - - public RenderGuideLinesException(String message, Throwable io) { - super(message, io); - } -} diff --git a/src/main/java/com/demcha/compose/engine/exceptions/package-info.java b/src/main/java/com/demcha/compose/engine/exceptions/package-info.java deleted file mode 100644 index 80ac1a143..000000000 --- a/src/main/java/com/demcha/compose/engine/exceptions/package-info.java +++ /dev/null @@ -1,7 +0,0 @@ -/** - * Shared engine exceptions raised by low-level layout, pagination, and rendering contracts. - * - *

Ownership: 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 RenderHandler> { - - Class renderType(); - - boolean render(EntityManager manager, - Entity entity, - R renderComponent, - RS renderingSystem, - boolean guideLines) throws IOException; -} diff --git a/src/main/java/com/demcha/compose/engine/render/RenderHandlerRegistry.java b/src/main/java/com/demcha/compose/engine/render/RenderHandlerRegistry.java deleted file mode 100644 index 04817c7f9..000000000 --- a/src/main/java/com/demcha/compose/engine/render/RenderHandlerRegistry.java +++ /dev/null @@ -1,37 +0,0 @@ -package com.demcha.compose.engine.render; - -import com.demcha.compose.engine.render.Render; -import com.demcha.compose.engine.render.RenderingSystemECS; - -import java.util.LinkedHashMap; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; - -/** - * Registry for renderer-owned handlers keyed by render marker type. - */ -public final class RenderHandlerRegistry { - private final Map, RenderHandler> handlers = new LinkedHashMap<>(); - - public > void register(RenderHandler handler) { - Objects.requireNonNull(handler, "handler"); - handlers.put(handler.renderType(), handler); - } - - public Optional> find(Render render) { - if (render == null) { - return Optional.empty(); - } - - RenderHandler direct = handlers.get(render.getClass()); - if (direct != null) { - return Optional.of(direct); - } - - return handlers.entrySet().stream() - .filter(entry -> entry.getKey().isInstance(render)) - .map(Map.Entry::getValue) - .findFirst(); - } -} diff --git a/src/main/java/com/demcha/compose/engine/render/RenderPassSession.java b/src/main/java/com/demcha/compose/engine/render/RenderPassSession.java deleted file mode 100644 index 7167dee11..000000000 --- a/src/main/java/com/demcha/compose/engine/render/RenderPassSession.java +++ /dev/null @@ -1,65 +0,0 @@ -package com.demcha.compose.engine.render; - -import com.demcha.compose.engine.components.core.Entity; -import com.demcha.compose.engine.components.layout.coordinator.Placement; - -import java.io.IOException; -import java.util.Objects; - -/** - * Backend-neutral render-pass session. - * - *

The 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.

- * - * @param backend-specific surface type - */ -public interface RenderPassSession extends AutoCloseable { - - /** - * Ensures that the target page exists and is ready to accept draw commands. - * - * @param pageIndex zero-based page index - * @throws IOException if the backend cannot materialize the page - */ - void ensurePage(int pageIndex) throws IOException; - - /** - * Returns the session-owned drawing surface for a page. - * - * @param pageIndex zero-based page index - * @return backend-specific page surface - * @throws IOException if the backend cannot provide the surface - */ - T pageSurface(int pageIndex) throws IOException; - - /** - * Convenience helper for single-page entities. - * - *

Entities 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.

- * - * @param backend-specific surface type - */ -public interface RenderStream { - RenderPassSession openRenderPass() throws IOException; -} diff --git a/src/main/java/com/demcha/compose/engine/render/RenderingSystemECS.java b/src/main/java/com/demcha/compose/engine/render/RenderingSystemECS.java deleted file mode 100644 index fd72fd3a5..000000000 --- a/src/main/java/com/demcha/compose/engine/render/RenderingSystemECS.java +++ /dev/null @@ -1,56 +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.SystemECS; -import com.demcha.compose.engine.render.guides.GuidLineSettings; -import com.demcha.compose.engine.render.guides.GuidesRenderer; - -import java.awt.*; -import java.io.IOException; -import java.util.Optional; -import java.util.Set; - -/** - * Backend-neutral rendering system contract. - * - *

The 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 RenderingSystemECS extends SystemECS { - T canvas(); - - GuidesRenderer guidesRenderer(); - RenderHandlerRegistry renderHandlers(); - - > T stream(); - Optional> activeRenderSession(); - - /** - * Returns the current render-pass session. - * - *

This 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 RenderPassSession renderSession() { - return activeRenderSession() - .orElseThrow(() -> new IllegalStateException("No active render session is available")); - } - - boolean renderBorder(S stream, RenderCoordinateContext context, - boolean lineDash, - Set sides) throws IOException; - - GuidLineSettings guidLineSettings(); - - boolean renderRectangle(S stream, RenderCoordinateContext context, boolean lineDash) throws IOException; - - void fillCircle(S stream, float cx, float cy, float r, Color fill) throws IOException; - -} - diff --git a/src/main/java/com/demcha/compose/engine/render/guides/BoxRender.java b/src/main/java/com/demcha/compose/engine/render/guides/BoxRender.java deleted file mode 100644 index 6c77640da..000000000 --- a/src/main/java/com/demcha/compose/engine/render/guides/BoxRender.java +++ /dev/null @@ -1,37 +0,0 @@ -package com.demcha.compose.engine.render.guides; - -import com.demcha.compose.engine.components.content.shape.Stroke; -import com.demcha.compose.engine.components.core.Entity; -import com.demcha.compose.engine.components.geometry.ContentSize; -import com.demcha.compose.engine.components.layout.coordinator.Placement; -import com.demcha.compose.engine.components.layout.coordinator.RenderCoordinateContext; - -import java.awt.*; -import java.util.Optional; - -/** - * An interface for rendering a box guide for an entity. - * - * @param The type of the stream used for rendering. - */ -public interface BoxRender extends GuideCoordinate { - - /** - * Creates a {@link RenderCoordinateContext} for the bounding box of an entity. - * - * @param entity The entity for which to create the context. - * @return An {@link Optional} containing the {@link RenderCoordinateContext}. - * @throws java.util.NoSuchElementException if the entity does not have a {@link ContentSize} or {@link Placement} component. - */ - default Optional box(Entity entity) { - var boxSize = entity.getComponent(ContentSize.class).orElseThrow(); - var placement = entity.getComponent(Placement.class).orElseThrow(); - double x = placement.x(); - double y = placement.y(); - double width = boxSize.width(); - double height = boxSize.height(); - Color color = renderingSystem().guidLineSettings().BOX_COLOR(); - Stroke stroke = renderingSystem().guidLineSettings().BOX_STROKE(); - return Optional.of(new RenderCoordinateContext(x, y, width, height, placement.startPage(), placement.endPage(), stroke, color)); - } -} diff --git a/src/main/java/com/demcha/compose/engine/render/guides/GuidLineSettings.java b/src/main/java/com/demcha/compose/engine/render/guides/GuidLineSettings.java deleted file mode 100644 index 9879c85a3..000000000 --- a/src/main/java/com/demcha/compose/engine/render/guides/GuidLineSettings.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.demcha.compose.engine.render.guides; - -import com.demcha.compose.engine.components.content.shape.Stroke; -import lombok.Data; -import lombok.experimental.Accessors; - -import java.awt.Color; -@Data -@Accessors(fluent = true) -public class GuidLineSettings { - /** - * The opacity for all guide elements. Value between 0.0f (transparent) and 1.0f (opaque). - */ - float GUIDES_OPACITY = 0.8f; - // --- Margin Guide --- - Color MARGIN_COLOR = new Color(0, 110, 255); - final float MARKER_RADIUS = 3.5f; - Stroke MARGIN_STROKE = new Stroke(0.5); - // --- Padding Guide --- - Color PADDING_COLOR = new Color(255, 140, 0); - Stroke PADDING_STROKE = new Stroke(0.5); - // --- Content Box Guide --- - Color BOX_COLOR = new Color(150, 150, 150); // Using a slightly lighter gray - Stroke BOX_STROKE = new Stroke(1.0); - //viability - boolean showOnlySetGuide = true; -} diff --git a/src/main/java/com/demcha/compose/engine/render/guides/GuideCoordinate.java b/src/main/java/com/demcha/compose/engine/render/guides/GuideCoordinate.java deleted file mode 100644 index cece6434f..000000000 --- a/src/main/java/com/demcha/compose/engine/render/guides/GuideCoordinate.java +++ /dev/null @@ -1,156 +0,0 @@ -package com.demcha.compose.engine.render.guides; - -import com.demcha.compose.engine.components.content.shape.Side; -import com.demcha.compose.engine.components.core.Entity; -import com.demcha.compose.engine.components.layout.coordinator.RenderCoordinateContext; -import com.demcha.compose.engine.exceptions.RenderGuideLinesException; -import com.demcha.compose.engine.render.RenderPassSession; -import com.demcha.compose.engine.render.RenderingSystemECS; -import lombok.NonNull; - -import java.io.IOException; -import java.util.Set; - -/** - * An interface for rendering coordinate guides for broken elements. - * - * @param The type of the stream used for rendering. - */ -public interface GuideCoordinate { - - RenderingSystemECS renderingSystem(); - - // --------------------------------------------------------- - // 2. The "Execute Around" Method (Handles the boilerplate) - // --------------------------------------------------------- - default boolean executeOnStream(@NonNull RenderCoordinateContext context, int pageNumber, StreamRenderer action) throws RenderGuideLinesException { - var activeSession = renderingSystem().activeRenderSession(); - if (activeSession.isPresent()) { - try { - return action.render(context, activeSession.get().pageSurface(pageNumber)); - } catch (RenderGuideLinesException e) { - throw e; - } catch (IOException e) { - throw new RenderGuideLinesException("Error occurred during guide rendering on the active session", e); - } catch (Exception e) { - throw new RenderGuideLinesException("Failed to process the active guide stream", e); - } - } - - try (RenderPassSession session = renderingSystem().stream().openRenderPass()) { - T stream = session.pageSurface(pageNumber); - return action.render(context, stream); - } catch (RenderGuideLinesException e) { - throw e; - } catch (IOException e) { - throw new RenderGuideLinesException("Error occurred during guide stream session handling", e); - } catch (Exception e) { - throw new RenderGuideLinesException("Failed to close or process the stream", e); - } - } - - default boolean start(@NonNull RenderCoordinateContext context, int pageNumber) throws RenderGuideLinesException { - // We pass the method reference 'this::startFromStream' - return executeOnStream(context, pageNumber, this::startFromStream); - } - - // --------------------------------------------------------- - // 3. Public API (Clean and simple) - // --------------------------------------------------------- - - default boolean middle(@NonNull RenderCoordinateContext context, int pageNumber) throws RenderGuideLinesException { - return executeOnStream(context, pageNumber, this::middleFromStream); - } - - default boolean end(@NonNull RenderCoordinateContext context, int pageNumber) throws RenderGuideLinesException { - return executeOnStream(context, pageNumber, this::endFromStream); - } - - default boolean startFromStream(@NonNull RenderCoordinateContext context, @NonNull T stream) throws RenderGuideLinesException, IOException { - var startHeight = context.y(); - RenderCoordinateContext coordinateContext = new RenderCoordinateContext(context.x(), startHeight, context.width(), context.height(), context.startPage(), context.endPage(), context.stroke(), context.color()); - - - Set sides = Set.of(Side.LEFT, Side.RIGHT, Side.BOTTOM); - - boolean lineDash = !context.stroke().equals( - renderingSystem().guidLineSettings().BOX_STROKE() - ); - - return renderingSystem().renderBorder(stream, coordinateContext, lineDash, sides); - } - - default void startMarkers(@NonNull RenderCoordinateContext context, @NonNull T stream) throws IOException { - final float radius = renderingSystem().guidLineSettings().MARKER_RADIUS(); - float cx = (float) context.x(); - float cy = (float) context.y(); - var color = context.color(); - float w = (float) context.width(); - - renderingSystem().fillCircle(stream, cx, cy, radius, color); - renderingSystem().fillCircle(stream, cx + w, cy, radius, color); - } - - // --------------------------------------------------------- - // 4. Logic Implementations (Focus only on drawing) - // --------------------------------------------------------- - - default boolean middleFromStream(@NonNull RenderCoordinateContext context, @NonNull T stream) throws RenderGuideLinesException, IOException { - Set sides = Set.of(Side.LEFT, Side.RIGHT); - boolean lineDash = !context.stroke().equals( - renderingSystem().guidLineSettings().BOX_STROKE() - ); - - renderingSystem().renderBorder(stream, context, lineDash, sides); - - return true; - } - - default boolean endFromStream(@NonNull RenderCoordinateContext context, @NonNull T stream) throws RenderGuideLinesException, IOException { - Set sides = Set.of(Side.LEFT, Side.RIGHT, Side.TOP); - boolean lineDash = !context.stroke().equals( - renderingSystem().guidLineSettings().BOX_STROKE() - ); - renderingSystem().renderBorder(stream, context, lineDash, sides); - return true; - } - - default void endMarkers(@NonNull RenderCoordinateContext context, @NonNull T stream) throws IOException { - final float radius = renderingSystem().guidLineSettings().MARKER_RADIUS(); - float cx = (float) context.x(); - float cy = (float) context.y(); - var color = context.color(); - float w = (float) context.width(); - float h = (float) context.height(); - - renderingSystem().fillCircle(stream, cx, cy + h, radius, color); - renderingSystem().fillCircle(stream, cx + w, cy + h, radius, color); - } - - // Existing methods kept as requested - boolean fromStream(@NonNull Entity entity, T stream); - - default void markers(T stream, RenderCoordinateContext context) throws IOException { - final float radius = renderingSystem().guidLineSettings().MARKER_RADIUS(); - float cx = (float) context.x(); - float cy = (float) context.y(); - var color = context.color(); - float w = (float) context.width(); - float h = (float) context.height(); - - renderingSystem().fillCircle(stream, cx, cy, radius, color); - renderingSystem().fillCircle(stream, cx, cy + h, radius, color); - renderingSystem().fillCircle(stream, cx + w, cy, radius, color); - renderingSystem().fillCircle(stream, cx + w, cy + h, radius, color); - } - - // --------------------------------------------------------- - // 1. The Functional Interface (Contract for the logic) - // --------------------------------------------------------- - @FunctionalInterface - interface StreamRenderer { - boolean render(RenderCoordinateContext context, T stream) throws Exception; - } -} - - diff --git a/src/main/java/com/demcha/compose/engine/render/guides/GuidesRenderer.java b/src/main/java/com/demcha/compose/engine/render/guides/GuidesRenderer.java deleted file mode 100644 index e4450b3ca..000000000 --- a/src/main/java/com/demcha/compose/engine/render/guides/GuidesRenderer.java +++ /dev/null @@ -1,287 +0,0 @@ -package com.demcha.compose.engine.render.guides; - -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.exceptions.RenderGuideLinesException; -import com.demcha.compose.engine.render.guides.GuidLineSettings; -import com.demcha.compose.engine.render.RenderPassSession; -import com.demcha.compose.engine.render.RenderingSystemECS; -import com.demcha.compose.engine.pagination.PageOutOfBoundException; -import lombok.Data; -import lombok.NonNull; -import lombok.ToString; -import lombok.experimental.Accessors; -import lombok.extern.slf4j.Slf4j; - -import java.awt.Color; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.EnumSet; -import java.util.List; -import java.util.Optional; -import java.util.function.Supplier; - -/** - * Renders margin, padding, and box guides for resolved entities. - * - *

The 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 GuidesRenderer { - @ToString.Exclude - protected final RenderingSystemECS renderingSystem; - @ToString.Exclude - protected final BoxRender box; - @ToString.Exclude - protected final MarginRender margin; - @ToString.Exclude - protected final PaddingRender padding; - - private static RenderCoordinateContext update(RenderCoordinateContext source, double y, double height, int page) { - return new RenderCoordinateContext( - source.x(), - y, - source.width(), - height, - page, - page, - source.stroke(), - source.color()); - } - - protected Optional resolveCoordinateContext( - Entity entity, - @NonNull GuidLineSettings guidLineSettings, - Class componentClass, - Supplier defaultSupplier - ) { - if (!renderingSystem.guidLineSettings().showOnlySetGuide()) { - return Optional.empty(); - } - - T context = entity.getComponent(componentClass) - .orElseGet(() -> { - log.info("{} is {} ", componentClass, defaultSupplier.get()); - return defaultSupplier.get(); - }); - - return context.renderCoordinate(entity, renderingSystem()); - } - - public boolean guidesRender(Entity entity, S stream, EnumSet guides) throws RenderGuideLinesException { - boolean any = false; - if (guides.contains(Guide.MARGIN)) { - any |= margin().fromStream(entity, stream); - } - if (guides.contains(Guide.PADDING)) { - any |= padding().fromStream(entity, stream); - } - if (guides.contains(Guide.BOX)) { - any |= box().fromStream(entity, stream); - } - return any; - } - - public boolean guidesRender(Entity entity, EnumSet guides) { - try { - return withSession(session -> guidesRender(entity, guides, session)); - } catch (Exception ex) { - log.error("Failed to render guides for entity {}", entity, ex); - throw new RuntimeException("Rendering failed", ex); - } - } - - private boolean guidesRender(Entity entity, - EnumSet guides, - RenderPassSession session) throws Exception { - Placement placement = entity.getComponent(Placement.class) - .orElseThrow(() -> new IllegalStateException("Entity missing Placement component")); - - int startPage = placement.startPage(); - int endPage = placement.endPage(); - - if (startPage == endPage || startPage == 0 && endPage == -1) { - return guidesRender(entity, session.pageSurface(startPage), guides); - } - - List boxFragments = breakCoordinate(Optional.of(boxCoordinate(entity)), startPage); - List marginFragments = breakCoordinate(margin().margin(entity), startPage); - List paddingFragments = breakCoordinate(padding().padding(entity), startPage); - - log.debug("Rendering spanned multiple pages for entity: {}. Start: {}, End: {}", entity, startPage, endPage); - int boxFragmentIndex = 0; - int marginFragmentIndex = 0; - int paddingFragmentIndex = 0; - - for (int renderingPage = startPage; renderingPage >= endPage; renderingPage--) { - if (renderingPage < 0) { - log.error("Rendering page is out of bound, negative number: {}", renderingPage); - throw new PageOutOfBoundException("Rendering page is out of bound, negative number: " + renderingPage); - } - - RenderCoordinateContext currentBox = - boxFragmentIndex < boxFragments.size() ? boxFragments.get(boxFragmentIndex) : null; - RenderCoordinateContext currentMargin = - marginFragmentIndex < marginFragments.size() ? marginFragments.get(marginFragmentIndex) : null; - RenderCoordinateContext currentPadding = - paddingFragmentIndex < paddingFragments.size() ? paddingFragments.get(paddingFragmentIndex) : null; - - try { - S stream = session.pageSurface(renderingPage); - if (renderingPage == startPage) { - startGuidesFromStream(stream, currentBox, currentMargin, currentPadding); - } else if (renderingPage == endPage) { - endGuidesFromStream(stream, currentBox, currentMargin, currentPadding); - } else { - middleGuidesFromStream(stream, currentBox, currentMargin, currentPadding); - } - } catch (Exception ex) { - log.error("Failed to render guides on multiple page for entity {} \n {}", entity, entity.printInfo(), ex); - throw new RuntimeException( - String.format("Failed to render guides on multiple page %s for entity \n %s ", - renderingPage, - entity.printInfo()), - ex); - } - - boxFragmentIndex++; - marginFragmentIndex++; - paddingFragmentIndex++; - } - - return true; - } - - /** - * Executes a guide-rendering action within a render session. - * - *

Prefers 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 action) throws Exception { - Optional> activeSession = renderingSystem.activeRenderSession(); - if (activeSession.isPresent()) { - return action.render(activeSession.get()); - } - try (RenderPassSession session = renderingSystem.stream().openRenderPass()) { - return action.render(session); - } - } - - private void middleGuidesFromStream(S stream, - RenderCoordinateContext boxContext, - RenderCoordinateContext marginContext, - RenderCoordinateContext paddingContext) throws IOException { - if (marginContext != null) { - margin().middleFromStream(marginContext, stream); - } - if (paddingContext != null) { - padding().middleFromStream(paddingContext, stream); - } - if (boxContext != null) { - box().middleFromStream(boxContext, stream); - } - } - - private void endGuidesFromStream(S stream, - RenderCoordinateContext boxContext, - RenderCoordinateContext marginContext, - RenderCoordinateContext paddingContext) throws IOException { - if (marginContext != null) { - margin().endFromStream(marginContext, stream); - margin().endMarkers(marginContext, stream); - } - if (paddingContext != null) { - padding().endFromStream(paddingContext, stream); - padding().endMarkers(paddingContext, stream); - } - if (boxContext != null) { - box().endFromStream(boxContext, stream); - } - } - - public List breakCoordinate(Optional contextOptional, int pages) { - if (contextOptional.isEmpty()) { - return Collections.emptyList(); - } - - RenderCoordinateContext sourceContext = contextOptional.get(); - List resultSegments = new ArrayList<>(); - float canvasHeight = renderingSystem().canvas().height(); - - double remainingHeight = sourceContext.height(); - double currentY = sourceContext.y(); - int currentPage = pages - 1; - - while (remainingHeight > 0) { - double heightOnThisPage = currentY + remainingHeight <= canvasHeight - ? remainingHeight - : canvasHeight - currentY; - - resultSegments.add(update(sourceContext, currentY, heightOnThisPage, currentPage)); - - remainingHeight -= heightOnThisPage; - currentY = 0; - currentPage--; - } - - return resultSegments; - } - - private void startGuidesFromStream(S stream, - RenderCoordinateContext boxContext, - RenderCoordinateContext marginContext, - RenderCoordinateContext paddingContext) throws IOException { - if (marginContext != null) { - margin().startFromStream(marginContext, stream); - margin().startMarkers(marginContext, stream); - } - if (paddingContext != null) { - padding().startFromStream(paddingContext, stream); - padding().startMarkers(paddingContext, stream); - } - if (boxContext != null) { - box().startFromStream(boxContext, stream); - } - } - - private RenderCoordinateContext boxCoordinate(Entity entity) { - Stroke stroke = renderingSystem().guidLineSettings().BOX_STROKE(); - Color color = renderingSystem().guidLineSettings().BOX_COLOR(); - return RenderCoordinateContext.createBox(entity, stroke, color); - } - - /** - * Functional callback for guide-rendering operations that require a render session. - * - * @param backend-specific surface type - */ - @FunctionalInterface - private interface SessionAction { - boolean render(RenderPassSession session) throws Exception; - } - - /** - * Types of guides that can be rendered around an entity. - */ - public enum Guide { - MARGIN, - PADDING, - BOX - } -} diff --git a/src/main/java/com/demcha/compose/engine/render/guides/MarginRender.java b/src/main/java/com/demcha/compose/engine/render/guides/MarginRender.java deleted file mode 100644 index 605979681..000000000 --- a/src/main/java/com/demcha/compose/engine/render/guides/MarginRender.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.demcha.compose.engine.render.guides; - -import com.demcha.compose.engine.components.core.Entity; -import com.demcha.compose.engine.components.layout.coordinator.RenderCoordinateContext; -import com.demcha.compose.engine.components.style.Margin; - -import java.util.Optional; -import java.util.function.Supplier; - -public interface MarginRender extends GuideCoordinate { - - default Optional margin(Entity entity) { - Optional coordinateOpt = - resolveCoordinateContext(entity, Margin.class, Margin::zero); - return coordinateOpt; - } - - default Optional resolveCoordinateContext(Entity entity, Class componentClass, Supplier zero) { - if (!renderingSystem().guidLineSettings().showOnlySetGuide()) { - return Optional.empty(); - } - - C context = entity.getComponent(componentClass) - .orElseGet(zero); - - return context.renderCoordinate(entity, renderingSystem()); - } - - -} diff --git a/src/main/java/com/demcha/compose/engine/render/guides/PaddingRender.java b/src/main/java/com/demcha/compose/engine/render/guides/PaddingRender.java deleted file mode 100644 index fc7e9790f..000000000 --- a/src/main/java/com/demcha/compose/engine/render/guides/PaddingRender.java +++ /dev/null @@ -1,51 +0,0 @@ -package com.demcha.compose.engine.render.guides; - -import com.demcha.compose.engine.components.core.Entity; -import com.demcha.compose.engine.components.layout.coordinator.RenderCoordinateContext; -import com.demcha.compose.engine.components.style.Padding; - -import java.util.Optional; -import java.util.function.Supplier; - -/** - * An interface for rendering a padding guide for an entity. - * - * @param The type of the stream used for rendering. - */ -public interface PaddingRender extends GuideCoordinate { - - /** - * Creates a {@link RenderCoordinateContext} for the padding area of an entity. - *

- * 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 padding(Entity entity) { - RenderCoordinateContext coordinateContext; - Optional coordinateOpt = - resolveCoordinateContext(entity, Padding.class, Padding::zero); - - return coordinateOpt; - - } - - // The following methods are required by the default 'padding' method. - // They must be implemented by any class that implements this interface. - - default Optional resolveCoordinateContext(Entity entity, Class componentClass, Supplier zero) { - if (!renderingSystem().guidLineSettings().showOnlySetGuide()) { - return Optional.empty(); - } - - // Берём компонент или default (Margin.zero() / Padding.zero()) - C context = entity.getComponent(componentClass) - .orElseGet(zero); - - return context.renderCoordinate(entity, renderingSystem()); - } - - -} diff --git a/src/main/java/com/demcha/compose/engine/render/guides/package-info.java b/src/main/java/com/demcha/compose/engine/render/guides/package-info.java deleted file mode 100644 index 319b4e47e..000000000 --- a/src/main/java/com/demcha/compose/engine/render/guides/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; \ No newline at end of file diff --git a/src/test/java/com/demcha/compose/engine/components/geometry/EntityBoundsTest.java b/src/test/java/com/demcha/compose/engine/components/geometry/EntityBoundsTest.java deleted file mode 100644 index 951b8fb6b..000000000 --- a/src/test/java/com/demcha/compose/engine/components/geometry/EntityBoundsTest.java +++ /dev/null @@ -1,38 +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.Margin; -import org.junit.jupiter.api.Test; - -import static org.assertj.core.api.Assertions.assertThat; - -class EntityBoundsTest { - - @Test - void shouldCalculateBoundsFromPlacementContentSizeAndMargin() { - Entity entity = new Entity(); - entity.addComponent(new Placement(20, 40, 100, 60, 0, 0)); - entity.addComponent(new ContentSize(100, 60)); - entity.addComponent(new Margin(7, 11, 13, 17)); - - assertThat(EntityBounds.topLine(entity)).isEqualTo(107.0); - assertThat(EntityBounds.bottomLine(entity)).isEqualTo(27.0); - assertThat(EntityBounds.rightLine(entity)).isEqualTo(131.0); - assertThat(EntityBounds.leftLine(entity)).isEqualTo(3.0); - } - - @SuppressWarnings("deprecation") - @Test - void shouldExposeSameBoundsThroughDeprecatedEntityWrappers() { - Entity entity = new Entity(); - entity.addComponent(new Placement(12, 25, 40, 30, 0, 0)); - entity.addComponent(new ContentSize(40, 30)); - entity.addComponent(new Margin(2, 3, 5, 7)); - - assertThat(entity.boundingTopLine()).isEqualTo(EntityBounds.topLine(entity)); - assertThat(entity.boundingBottomLine()).isEqualTo(EntityBounds.bottomLine(entity)); - assertThat(entity.boundingRightLine()).isEqualTo(EntityBounds.rightLine(entity)); - assertThat(entity.boundingLeftLine()).isEqualTo(EntityBounds.leftLine(entity)); - } -} diff --git a/src/test/java/com/demcha/compose/engine/components/geometry/InnerBoxSizeTest.java b/src/test/java/com/demcha/compose/engine/components/geometry/InnerBoxSizeTest.java deleted file mode 100644 index c201f3fda..000000000 --- a/src/test/java/com/demcha/compose/engine/components/geometry/InnerBoxSizeTest.java +++ /dev/null @@ -1,120 +0,0 @@ -package com.demcha.compose.engine.components.geometry; - -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.style.Padding; -import com.demcha.compose.engine.exceptions.ContentSizeNotFoundException; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.CsvSource; - -import java.util.Optional; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - - -class InnerBoxSizeTest { - @Nested - @DisplayName("from(ContentSize, Padding)") - class FromContentAndPadding { - - @ParameterizedTest(name = "content=({0},{1}), padding=({2},{3},{4},{5}) → inner=({6},{7})") - @CsvSource({ - // width, height, top, right, bottom, left, expectedInnerW, expectedInnerH - "100, 200, 0, 0, 0, 0, 100, 200", - "100, 200, 5, 10, 15, 20, 70, 180", // W:100-(20+10)=70; H:200-(5+15)=180 - "50, 40, 10, 5, 10, 5, 40, 20", - "50.5, 40.25, 1.5, 2.5, 3.5, 4.5, 43.5, 35.25", - // padding larger than content → negative inner size (current behavior) - "10, 10, 6, 6, 6, 6, -2, -2" - }) - void computesInnerSizeCorrectly(double w, double h, - double top, double right, double bottom, double left, - double expectedInnerW, double expectedInnerH) { - ContentSize content = new ContentSize(w, h); - Padding padding = new Padding(top, right, bottom, left); - - Optional result = InnerBoxSize.from(content, padding); - - assertThat(result).isPresent(); - InnerBoxSize box = result.get(); - assertThat(box.width()).isEqualTo(expectedInnerW); - assertThat(box.height()).isEqualTo(expectedInnerH); - } - - @Test - void nullContent_throwsContentSizeNotFoundException() { - Padding padding = Padding.zero(); - assertThatThrownBy(() -> InnerBoxSize.from(null, padding)) - .isInstanceOf(ContentSizeNotFoundException.class); - } - - @Test - void nullPadding_throwsNullPointerWithMessage() { - ContentSize content = new ContentSize(10, 20); - assertThatThrownBy(() -> InnerBoxSize.from(content, null)) - .isInstanceOf(NullPointerException.class) - .hasMessage("Padding cannot be null."); - } - } - - @Nested - @DisplayName("from(Entity)") - class FromEntity { - - @Test - void returnsInnerSize_whenContentSizeAndPaddingPresent() { - Entity entity = mock(Entity.class); - - ContentSize content = new ContentSize(100, 200); - Padding padding = new Padding(5, 10, 15, 20); // top,right,bottom,left - - when(entity.getComponent(ContentSize.class)).thenReturn(Optional.of(content)); - when(entity.getComponent(Padding.class)).thenReturn(Optional.of(padding)); - - Optional result = InnerBoxSize.from(entity); - - assertThat(result).isPresent(); - InnerBoxSize box = result.get(); - // W = 100 - (left+right) = 100 - (20+10) = 70 - // H = 200 - (top+bottom) = 200 - (5+15) = 180 - assertThat(box.width()).isEqualTo(70.0); - assertThat(box.height()).isEqualTo(180.0); - } - - @Test - void usesZeroPadding_whenPaddingMissing() { - Entity entity = mock(Entity.class); - ContentSize content = new ContentSize(80, 60); - - when(entity.getComponent(ContentSize.class)).thenReturn(Optional.of(content)); - when(entity.getComponent(Padding.class)).thenReturn(Optional.empty()); // Padding.zero() - - Optional result = InnerBoxSize.from(entity); - - assertThat(result).isPresent(); - InnerBoxSize box = result.get(); - assertThat(box.width()).isEqualTo(80.0); - assertThat(box.height()).isEqualTo(60.0); - } - - @Test - void throwsContentSizeNotFound_whenContentSizeMissing() { - Entity entity = mock(Entity.class); - - when(entity.getComponent(ContentSize.class)).thenReturn(Optional.empty()); - when(entity.getComponent(Padding.class)).thenReturn(Optional.of(Padding.zero())); - - assertThatThrownBy(() -> InnerBoxSize.from(entity)) - .isInstanceOf(ContentSizeNotFoundException.class); - } - - - } -} \ No newline at end of file diff --git a/src/test/java/com/demcha/compose/engine/components/geometry/OuterBoxSizeTest.java b/src/test/java/com/demcha/compose/engine/components/geometry/OuterBoxSizeTest.java deleted file mode 100644 index 08ed98e28..000000000 --- a/src/test/java/com/demcha/compose/engine/components/geometry/OuterBoxSizeTest.java +++ /dev/null @@ -1,121 +0,0 @@ -package com.demcha.compose.engine.components.geometry; - - -import com.demcha.compose.engine.components.core.Entity; -import com.demcha.compose.engine.components.geometry.ContentSize; -import com.demcha.compose.engine.components.geometry.OuterBoxSize; -import com.demcha.compose.engine.components.style.Margin; -import com.demcha.compose.engine.exceptions.ContentSizeNotFoundException; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.CsvSource; - -import java.util.Optional; - -import static org.assertj.core.api.Assertions.*; -import static org.mockito.Mockito.*; - -class OuterBoxSizeTest { - - @Nested - @DisplayName("from(ContentSize, Margin)") - class FromContentAndMargin { - - @ParameterizedTest(name = "content=({0},{1}), margin=({2},{3},{4},{5}) → outer=({6},{7})") - @CsvSource({ - // width, height, top, right, bottom, left, expectedW, expectedH - "100, 200, 0, 0, 0, 0, 100, 200", - "100, 200, 5, 10, 15, 20, 130, 220", // w + (left+right) = 100 + (20+10) = 130; h + (top+bottom) = 200 + (5+15) = 220 - "0, 0, 1, 2, 3, 4, 6, 4", - "50.5, 75.25, 0.5, 1.5, 2.5, 3.5, 55.5, 78.25" - }) - void sumsContentAndMarginCorrectly(double w, double h, - double top, double right, double bottom, double left, - double expectedW, double expectedH) { - ContentSize content = new ContentSize(w, h); - Margin margin = new Margin(top, right, bottom, left); - - Optional result = OuterBoxSize.from(content, margin); - - assertThat(result) - .isPresent() - .get() - .satisfies(box -> { - assertThat(box.width()).isEqualTo(expectedW); - assertThat(box.height()).isEqualTo(expectedH); - }); - } - - @Test - void nullContent_throwsNullPointerBecauseOfLombokNonNull() { - Margin margin = Margin.zero(); - assertThatThrownBy(() -> OuterBoxSize.from(null, margin)) - .isInstanceOf(ContentSizeNotFoundException.class); - } - - @Test - void nullMargin_throwsNullPointerBecauseOfLombokNonNull() { - ContentSize content = new ContentSize(10, 20); - assertThatThrownBy(() -> OuterBoxSize.from(content, null)) - .isInstanceOf(NullPointerException.class); - } - } - - @Nested - @DisplayName("from(Entity)") - class FromEntity { - - @Test - void returnsOuterSize_whenContentSizeAndMarginPresent() { - // Arrange - Entity entity = mock(Entity.class); - - ContentSize content = new ContentSize(100, 200); - Margin margin = new Margin(5, 10, 15, 20); // top,right,bottom,left - - when(entity.getComponent(ContentSize.class)).thenReturn(Optional.of(content)); - when(entity.getComponent(Margin.class)).thenReturn(Optional.of(margin)); - - // Act - Optional result = OuterBoxSize.from(entity); - - // Assert - assertThat(result).isPresent(); - OuterBoxSize box = result.get(); - // width = 100 + (left+right) = 100 + (20+10) = 130 - // height = 200 + (top+bottom) = 200 + (5+15) = 220 - assertThat(box.width()).isEqualTo(130.0); - assertThat(box.height()).isEqualTo(220.0); - } - - @Test - void usesZeroMargin_whenMarginMissing() { - Entity entity = mock(Entity.class); - ContentSize content = new ContentSize(80, 60); - - when(entity.getComponent(ContentSize.class)).thenReturn(Optional.of(content)); - when(entity.getComponent(Margin.class)).thenReturn(Optional.empty()); // Margin.zero() - - Optional result = OuterBoxSize.from(entity); - - assertThat(result).isPresent(); - OuterBoxSize box = result.get(); - assertThat(box.width()).isEqualTo(80.0); - assertThat(box.height()).isEqualTo(60.0); - } - - @Test - void throwsContentSizeNotFound_whenContentSizeMissing() { - Entity entity = mock(Entity.class); - - when(entity.getComponent(ContentSize.class)).thenReturn(Optional.empty()); - when(entity.getComponent(Margin.class)).thenReturn(Optional.of(Margin.zero())); - - assertThatThrownBy(() -> OuterBoxSize.from(entity)) - .isInstanceOf(ContentSizeNotFoundException.class); - } - } - -} \ No newline at end of file diff --git a/src/test/java/com/demcha/compose/engine/components/layout/ComputedPositionTest.java b/src/test/java/com/demcha/compose/engine/components/layout/ComputedPositionTest.java deleted file mode 100644 index 47621b345..000000000 --- a/src/test/java/com/demcha/compose/engine/components/layout/ComputedPositionTest.java +++ /dev/null @@ -1,155 +0,0 @@ -package com.demcha.compose.engine.components.layout; - -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.Anchor; -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.core.Canvas; -import com.demcha.compose.engine.components.style.Margin; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.mockito.ArgumentCaptor; -import org.mockito.MockedStatic; - -import java.util.Optional; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.*; - -class ComputedPositionTest { - - @Test - @DisplayName("zero() returns (0,0)") - void zero_returns00() { - ComputedPosition cp = ComputedPosition.zero(); - assertThat(cp.x()).isZero(); - assertThat(cp.y()).isZero(); - } - - @Test - @DisplayName("from(child, innerBox) uses existing Anchor.getComputedPosition(...)") - void from_child_inner_usesExistingAnchor() { - // Arrange - Entity child = mock(Entity.class); - Anchor anchor = mock(Anchor.class); - InnerBoxSize inner = new InnerBoxSize(100, 200); - ComputedPosition expected = new ComputedPosition(12.5, 34.5); - - when(child.getComponent(Anchor.class)).thenReturn(Optional.of(anchor)); - when(anchor.getComputedPosition(child, inner)).thenReturn(expected); - - - // Act - ComputedPosition actual = ComputedPosition.from(child, inner, PaddingCoordinate.zero()); - - // Assert - assertThat(actual).isEqualTo(expected); - verify(child, never()).addComponent(any()); // already had anchor - verify(anchor).getComputedPosition(child, inner); - } - - @Test - @DisplayName("from(child, innerBox) creates and adds default Anchor when missing, then delegates") - void from_child_inner_addsDefaultAnchorWhenMissing() { - // Arrange - Entity child = mock(Entity.class); - InnerBoxSize inner = new InnerBoxSize(80, 60); - Anchor defaultAnchor = mock(Anchor.class); - Position position = mock(Position.class); - ComputedPosition expected = new ComputedPosition(5, 7); - - when(child.getComponent(ContentSize.class)).thenReturn(Optional.of(new ContentSize(80,80))); - - // FIX: Simulate that the Anchor component is MISSING. - when(child.getComponent(Anchor.class)).thenReturn(Optional.empty()); - when (child.getComponent(Position.class)).thenReturn(Optional.of(new Position(5,7))); - - // Mock the static method that is *actually* used for the default anchor. - try (MockedStatic ms = mockStatic(Anchor.class)) { - ms.when(Anchor::defaultAnchor).thenReturn(defaultAnchor); - when(defaultAnchor.getComputedPosition(child, inner)).thenReturn(expected); - - - // Act - - ComputedPosition actual = ComputedPosition.from(child, inner, PaddingCoordinate.zero()); - - // Assert - assertThat(actual).isEqualTo(expected); - verify(child).addComponent(defaultAnchor); // This will now be called - verify(defaultAnchor).getComputedPosition(child, inner); // This will now be called - ms.verify(Anchor::defaultAnchor); // This will now be called - } - } - - @Test - @DisplayName("from(child, parent) calls InnerBoxSize.from(parent) and delegates to anchor") - void from_child_parent_usesInnerBoxFromParent() { - // Arrange - Entity child = mock(Entity.class); - Entity parent = mock(Entity.class); - Anchor anchor = mock(Anchor.class); - InnerBoxSize parentInner = new InnerBoxSize(120, 240); - ComputedPosition expected = new ComputedPosition(10, 20); - - // Anchor present on child - when(child.getComponent(Anchor.class)).thenReturn(Optional.of(anchor)); - when(anchor.getComputedPosition(eq(child), eq(parentInner))).thenReturn(expected); - - // If PaddingCoordinate.from(...) looks up ComputedPosition on the entity, stub it: - when(child.getComponent(ComputedPosition.class)).thenReturn(Optional.of(ComputedPosition.zero())); - - try (MockedStatic innerMs = mockStatic(InnerBoxSize.class); - MockedStatic padMs = mockStatic(PaddingCoordinate.class)) { - - innerMs.when(() -> InnerBoxSize.from(parent)).thenReturn(Optional.of(parentInner)); - - // Return a harmless padding so the code path is satisfied - padMs.when(() -> PaddingCoordinate.from(any())).thenReturn(new PaddingCoordinate(0, 0)); - - // Act - ComputedPosition actual = ComputedPosition.from(child, parent); - - // Assert - assertThat(actual).isEqualTo(expected); - innerMs.verify(() -> InnerBoxSize.from(parent)); - verify(anchor).getComputedPosition(child, parentInner); - } - } - - - @Test - @DisplayName("from(child, pageSize) wraps Canvas in InnerBoxSize(width,height) and delegates") - void from_child_pageSize_wrapsIntoInnerBox() { - // Arrange - Entity child = mock(Entity.class); - Anchor anchor = mock(Anchor.class); - Canvas pageSize = mock(Canvas.class); - when(pageSize.width()).thenReturn(595.0f); - when(pageSize.height()).thenReturn(842.0f); - when(pageSize.margin()).thenReturn(Margin.zero()); - - when(child.getComponent(Anchor.class)).thenReturn(Optional.of(anchor)); - ComputedPosition expected = new ComputedPosition(3, 4); - - // We want to capture the InnerBoxSize passed to anchor.getComputedPosition - ArgumentCaptor innerCaptor = ArgumentCaptor.forClass(InnerBoxSize.class); - when(anchor.getComputedPosition(eq(child), innerCaptor.capture())).thenReturn(expected); - - // Act - ComputedPosition actual = ComputedPosition.from(child, pageSize); - - // Assert - assertThat(actual).isEqualTo(expected); - - InnerBoxSize innerUsed = innerCaptor.getValue(); - assertThat(innerUsed.width()).isEqualTo(595.0); - assertThat(innerUsed.height()).isEqualTo(842.0); - - verify(anchor).getComputedPosition(eq(child), any(InnerBoxSize.class)); - } -} - diff --git a/src/test/java/com/demcha/compose/engine/components/layout/coordinator/ComputedPositionTest.java b/src/test/java/com/demcha/compose/engine/components/layout/coordinator/ComputedPositionTest.java deleted file mode 100644 index 149bf05c0..000000000 --- a/src/test/java/com/demcha/compose/engine/components/layout/coordinator/ComputedPositionTest.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.demcha.compose.engine.components.layout.coordinator; - -import org.junit.jupiter.api.Test; - -import static org.junit.jupiter.api.Assertions.*; - -class ComputedPositionTest { - - @Test - void from() { - } - - @Test - void testFrom() { - } - - @Test - void testFrom1() { - } - - @Test - void paddingCoordinate() { - } -} \ No newline at end of file diff --git a/src/test/java/com/demcha/compose/engine/components/layout/coordinator/PlacementTest.java b/src/test/java/com/demcha/compose/engine/components/layout/coordinator/PlacementTest.java deleted file mode 100644 index 093bf0ad5..000000000 --- a/src/test/java/com/demcha/compose/engine/components/layout/coordinator/PlacementTest.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.demcha.compose.engine.components.layout.coordinator; - -import com.demcha.compose.engine.components.layout.coordinator.Placement; - -import static org.mockito.Mockito.*; - -class PlacementTest { - - public static Placement mockPlacement() { - Placement placement = mock(Placement.class); - when(placement.x()).thenReturn(0.0); - return placement; - } - -} \ No newline at end of file diff --git a/src/test/java/com/demcha/compose/engine/components/layout/coordinator/RenderingPositionTest.java b/src/test/java/com/demcha/compose/engine/components/layout/coordinator/RenderingPositionTest.java deleted file mode 100644 index b15923c29..000000000 --- a/src/test/java/com/demcha/compose/engine/components/layout/coordinator/RenderingPositionTest.java +++ /dev/null @@ -1,44 +0,0 @@ -package com.demcha.compose.engine.components.layout.coordinator; - -import com.demcha.compose.engine.components.core.Component; -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.Position; -import com.demcha.compose.engine.components.layout.coordinator.RenderingPosition; -import org.junit.jupiter.api.Test; - -import java.util.Optional; - -import static org.mockito.Mockito.*; - -class RenderingPositionTest { - - static Entity createEntity(RenderingPosition renderingPosition, ComputedPosition computedPosition) { - Entity e = mock(Entity.class, withSettings().defaultAnswer(CALLS_REAL_METHODS)); - doReturn(Optional.of(computedPosition)).when(e).getComponent(ComputedPosition.class); - doReturn(Optional.of(renderingPosition)).when(e).getComponent(Position.class); - return e; - } - - static Component createComponent(double x, double y) { - ComputedPosition position = mock(ComputedPosition.class); - when(position.x()).thenReturn(x); - when(position.y()).thenReturn(y); - return position; - } - - static RenderingPosition createRenderingPosition(double x, double y) { - RenderingPosition position = mock(RenderingPosition.class); - when(position.x()).thenReturn(x); - when(position.y()).thenReturn(y); - return position; - } - - @Test - void fromEntity() { - - } - - - -} \ No newline at end of file diff --git a/src/test/java/com/demcha/mock/FactoryClasses.java b/src/test/java/com/demcha/mock/FactoryClasses.java deleted file mode 100644 index 9b3790e47..000000000 --- a/src/test/java/com/demcha/mock/FactoryClasses.java +++ /dev/null @@ -1,105 +0,0 @@ -package com.demcha.mock; - -import com.demcha.compose.engine.core.Canvas; -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.style.Margin; -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 com.demcha.compose.engine.pagination.Offset; - -import java.util.Optional; - -import static org.mockito.Mockito.*; - -public class FactoryClasses { - - - @SafeVarargs - public static Entity entityMock(T... components) { - Entity e = mock(Entity.class); - - for (T c : components) { - doReturn(Optional.of(c)) - .when(e) - .getComponent(c.getClass()); - } - - return e; - } - - - public static Offset offsetMock(OffsetData offsetData) { - Offset offset = mock(Offset.class); - when(offset.x()).thenReturn(offsetData.x()); - when(offset.y()).thenReturn(offsetData.y()); - return offset; - } - - public static Offset offsetReal(OffsetData offsetData) { - Offset offset = new Offset(); - offset.incrementX(offsetData.x()); - offset.incrementY(offsetData.y()); - return offset; - } - - - public static ContentSize mockContentSize(SizeData sizeData) { - ContentSize contentSize = mock(ContentSize.class, withSettings().defaultAnswer(CALLS_REAL_METHODS)); - - when(contentSize.width()).thenReturn(sizeData.width()); - when(contentSize.height()).thenReturn(sizeData.height()); - return contentSize; - } - - public static Margin margin(MarginData marginData) { - // It is safer to use standard mock() instead of CALLS_REAL_METHODS - // if you are going to stub all methods anyway. - Margin margin = mock(Margin.class); - - // NOTICE: .when(margin).top() <-- Parenthesis closes after 'margin' - doReturn(marginData.top()).when(margin).top(); - doReturn(marginData.bottom()).when(margin).bottom(); - doReturn(marginData.right()).when(margin).right(); - doReturn(marginData.left()).when(margin).left(); - - return margin; - } - - public static Canvas canvasMock(CanvasData canvasData) { - // 1. Prepare the sub-component (Margin) first - // We reuse your existing 'margin' method to create a fully configured Margin mock - Margin marginMock = margin(canvasData.margin()); - - // 2. Create the Canvas mock - Canvas canvas = mock(Canvas.class); - - // 3. Link the Margin mock to the Canvas mock - // Correct Syntax: .when(mock).method() - doReturn(marginMock).when(canvas).margin(); - - // 4. Stub simple properties - doReturn((float) canvasData.width()).when(canvas).width(); - doReturn((float) canvasData.height()).when(canvas).height(); - - // 5. Stub calculated properties - // Notice the parenthesis: .when(canvas) is closed before calling .innerHeigh() - doReturn(canvasData.height() - canvasData.margin().top() - canvasData.margin().bottom()) - .when(canvas).innerHeigh(); - - doReturn(canvasData.width() - canvasData.margin().left() - canvasData.margin().right()) - .when(canvas).innerWidth(); - - // 6. Stub bounding lines - doReturn((float) canvasData.margin().bottom()).when(canvas).boundingBottomLine(); - doReturn((float) (canvasData.height() - canvasData.margin().top())).when(canvas).boundingTopLine(); - doReturn((float) (canvasData.margin().left())).when(canvas).boundingLeftLine(); - doReturn((float) (canvasData.width() - canvasData.margin().right())).when(canvas).boundingRightLine(); - - return canvas; - } -} - diff --git a/src/test/java/com/demcha/mock/FactoryPresets.java b/src/test/java/com/demcha/mock/FactoryPresets.java deleted file mode 100644 index f86b7a105..000000000 --- a/src/test/java/com/demcha/mock/FactoryPresets.java +++ /dev/null @@ -1,73 +0,0 @@ -package com.demcha.mock; - -import com.demcha.mock.data.CanvasData; -import com.demcha.mock.data.MarginData; -import com.demcha.mock.data.OffsetData; -import com.demcha.mock.data.SizeData; - -public class FactoryPresets { - - // --- Margins (General) --- - public static final MarginData MARGIN_ZERO = MarginData.all(0); - public static final MarginData MARGIN_STANDARD_20 = MarginData.all(20); - public static final MarginData MARGIN_ASYMMETRIC_10_20_30_40 = new MarginData(10, 20, 30, 40); - - // --- Margins (Test / Vertical Specific) --- - // Top=10, Bot=10 (Симметричные вертикальные) - public static final MarginData MARGIN_VERT_10 = new MarginData(10, 10, 0, 0); - public static final MarginData MARGIN_VERT_1 = new MarginData(1, 1, 0, 0); - // Top=5, Bot=5 - public static final MarginData MARGIN_VERT_5 = new MarginData(5, 5, 0, 0); - // Canvas Margins: Top=10, Bot=20 (Несимметричные) - public static final MarginData MARGIN_CANVAS_TOP10_BOT20 = new MarginData(10, 20, 0, 0); - // Canvas Margins: Top=10, Bot=10 - public static final MarginData MARGIN_CANVAS_TOP10_BOT10 = new MarginData(10, 10, 0, 0); - - - // --- Objects (Standard) --- - public static final SizeData OBJ_SMALL_BOX_100_50 = new SizeData(100, 50); - public static final SizeData OBJ_FULL_WIDTH_500_100 = new SizeData(500, 100); - - // --- Objects (Test / Low Height) --- - public static final SizeData OBJ_W100_H20 = new SizeData(100, 20); - public static final SizeData OBJ_W100_H60 = new SizeData(100, 60); - public static final SizeData OBJ_W100_H40 = new SizeData(100, 40); - - - // --- Canvases (Standard A4) --- - public static final CanvasData CANVAS_A4_STANDARD = new CanvasData(595, 842, MARGIN_STANDARD_20); - public static final CanvasData CANVAS_A4_NO_MARGINS = new CanvasData(595, 842, MARGIN_ZERO); - - // --- Canvases (Test / Small Area) --- - // 100x100, Margins: Top 10, Bot 20 - public static final CanvasData CANVAS_TEST_100_100_M_T10_B20 = new CanvasData(100, 100, MARGIN_CANVAS_TOP10_BOT20); - - // 100x200, Margins: Top 10, Bot 20 - public static final CanvasData CANVAS_TEST_100_200_M_T10_B20 = new CanvasData(100, 200, MARGIN_CANVAS_TOP10_BOT20); - - // 100x100, Margins: Top 10, Bot 10 - public static final CanvasData CANVAS_TEST_100_100_M_10 = new CanvasData(100, 100, MARGIN_CANVAS_TOP10_BOT10); - - - // --- Offsets --- - public static final OffsetData OFFSET_DATA_ZERO = OffsetData.zero(); - public static final OffsetData OFFSET_DATA_ALL_2 = OffsetData.all(2); - public static final OffsetData OFFSET_DATA_ALL_NEGATIVE_2 = OffsetData.all(-2); - public static final OffsetData OFFSET_DATA_ALL_15 = OffsetData.all(15); - public static final OffsetData OFFSET_DATA_NEGATIVE_ALL_40 = OffsetData.all(-40); - - // --- Objects (For Splitting Tests) --- - // Height 150: Will require split on a 100px canvas - public static final SizeData OBJ_TALL_100_150 = new SizeData(100, 150); - public static final SizeData OBJ_TALL_5_12 = new SizeData(5, 12); - public static final SizeData OBJ_TALL_5_6 = new SizeData(5, 6); - // Height 250: Might require 3 pages on a 100px canvas - public static final SizeData OBJ_VERY_TALL_100_250 = new SizeData(100, 250); - - - // --- Canvases (For Splitting Tests) --- - // 100x100, Margins: Top 10, Bot 10. Safe Area = 80px. - // Useful for easy math: Total 100, Safe 80. - public static final CanvasData CANVAS_SPLIT_100_M10 = new CanvasData(100, 100, MARGIN_VERT_10); - public static final CanvasData CANVAS_SPLIT_5_M1 = new CanvasData(5, 5, MARGIN_VERT_1); -} \ No newline at end of file diff --git a/src/test/java/com/demcha/mock/data/CanvasData.java b/src/test/java/com/demcha/mock/data/CanvasData.java deleted file mode 100644 index f7696ec1c..000000000 --- a/src/test/java/com/demcha/mock/data/CanvasData.java +++ /dev/null @@ -1,4 +0,0 @@ -package com.demcha.mock.data; - -public record CanvasData(double width, double height, MarginData margin) { -} diff --git a/src/test/java/com/demcha/mock/data/MarginData.java b/src/test/java/com/demcha/mock/data/MarginData.java deleted file mode 100644 index 78b87ae92..000000000 --- a/src/test/java/com/demcha/mock/data/MarginData.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.demcha.mock.data; - -public record MarginData(double top, double bottom, double right, double left) { - public static MarginData all(double i) { - return new MarginData(i, i, i, i); - } -} diff --git a/src/test/java/com/demcha/mock/data/OffsetData.java b/src/test/java/com/demcha/mock/data/OffsetData.java deleted file mode 100644 index fb9842dcf..000000000 --- a/src/test/java/com/demcha/mock/data/OffsetData.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.demcha.mock.data; - -public record OffsetData(double x, double y) { - public static OffsetData zero() { - return new OffsetData(0, 0); - } - public static OffsetData all(double offset) { - return new OffsetData(offset, offset); - } -} diff --git a/src/test/java/com/demcha/mock/data/SizeData.java b/src/test/java/com/demcha/mock/data/SizeData.java deleted file mode 100644 index ac8320e37..000000000 --- a/src/test/java/com/demcha/mock/data/SizeData.java +++ /dev/null @@ -1,4 +0,0 @@ -package com.demcha.mock.data; - -public record SizeData(double width, double height) { -} diff --git a/src/test/resources/logback.xml b/src/test/resources/logback.xml index 2adb12613..f33257512 100644 --- a/src/test/resources/logback.xml +++ b/src/test/resources/logback.xml @@ -93,24 +93,8 @@ - - - - - - - - - - - - - - - -