Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .cspell.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ words:
- collinear
- collinearity
- collinearly
- pathfinding
# Non-ASCII terms retained verbatim in extracted source comments
- façade
- Köpf
Expand Down
9 changes: 7 additions & 2 deletions docs/design/rendering-layout/connector-router.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,13 @@ implementations are:
the margin. The result is then clamped into the chosen face's usable connectable extents (again
applying the same clearance inset when an extent is long enough) and projected inward to the real
outline. The chosen `PortSide` is retained so the route exits and enters perpendicular to the face.
2. **Obstacle set.** Builds a `Rect` per box, excluding the connection's two endpoint boxes matched
by reference identity, so the connector is free to leave and enter the boxes it joins.
2. **Obstacle set.** Builds a `Rect` per box, including the connection's own two endpoint boxes. The
connector remains free to leave and enter the boxes it joins because the underlying
`OrthogonalEdgeRouter` steps each anchor off its face by a perpendicular stub longer than any
clearance level it tries before pathfinding, then reattaches the true anchor when assembling the
final path; treating endpoints as ordinary obstacles closes the gap through which an unrelated,
already-routed connector's soft obstacles could otherwise squeeze a later connector into its own
target box's interior.
3. **Dispatch.** Routes through the router realizing `options.EdgeRouting`. Today `Orthogonal` maps to
the internal `OrthogonalEdgeRouter.RouteWithStatus`, which is the implementation behind the enum
value and remains internal to the Layout system. The dispatch is a single-arm switch structured so
Expand Down
7 changes: 7 additions & 0 deletions docs/gallery/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,13 @@ layer with a dummy bend point created by an unrelated long edge (LongSource to L
clusters isolated nodes at the end of the layer's order, and coordinate assignment squeezes any resulting gap down to
the standard node spacing, instead of inheriting the dummy's unrelated port-alignment floor as an inflated gap.

![Nine edges docking on a compartment box without crossing its interior](parallel-edges-into-compartment-box.svg)

Regression coverage for the parallel-edges-into-compartment-box fix: nine unmerged edges from a small Source box
converge on a taller Target box's nine-row compartment. ConnectorRouter now treats every box, including a connection's
own endpoints, as a hard obstacle for the whole route (not just the final docking stub), so a connector squeezed by
other already-routed connectors can no longer detour straight through its own target box's interior.

## Flow direction

The same directed graph laid out in two flow directions, selected with the direction option, plus a nested container
Expand Down
57 changes: 57 additions & 0 deletions docs/gallery/parallel-edges-into-compartment-box.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
15 changes: 9 additions & 6 deletions docs/reqstream/rendering-layout/connector-router.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -63,14 +63,17 @@ sections:

- id: Rendering-Layout-ConnectorRouter-ExcludesEndpoints
title: >-
ConnectorRouter shall exclude a connection's two endpoint boxes, matched by instance
identity, from the obstacle set used to route that connection.
ConnectorRouter shall treat a connection's own endpoint boxes as ordinary obstacles while
still letting the connector leave and enter their faces, by routing through a perpendicular
approach stub that clears each endpoint before the true anchor is reattached.
justification: |
A connector must be free to leave and enter the boxes it joins; treating the endpoint boxes
as obstacles would block the connector's own anchors. Endpoints are matched by reference so
no domain concept enters the routing.
Excluding endpoint boxes entirely left a gap through which an unrelated, already-routed
connector's soft obstacles could squeeze a later connector into its own target box's
interior. Treating endpoints as solid obstacles and relying on the router's existing
stub-then-dock approach keeps the connector's own docking free while closing that gap.
tests:
- Route_EndpointBoxes_AreExcludedFromObstacles
- Route_EndpointBoxes_ReachBoundaryAnchorsViaApproachStub
- Route_NineParallelEdgesIntoCompartmentBox_DoNotCrossTargetInterior

- id: Rendering-Layout-ConnectorRouter-CarriesStyling
title: >-
Expand Down
14 changes: 9 additions & 5 deletions docs/verification/rendering-layout/connector-router.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ obstacle avoidance, and produced `LayoutLine` styling are all observed on real o

A verification run passes when every named scenario below asserts without unexpected exception, and
the referenced tests cover each `Rendering-Layout-ConnectorRouter-*` requirement. Any wrong anchor
face, waypoint that enters a non-endpoint obstacle interior, incorrect line styling passthrough,
face, waypoint that enters an obstacle's strict interior, incorrect line styling passthrough,
out-of-order batch result, or non-argument-null exception for invalid input constitutes a failure.

### Test Scenarios
Expand All @@ -51,9 +51,12 @@ out-of-order batch result, or non-argument-null exception for invalid input cons
- **Obstacle avoidance** (`Rendering-Layout-ConnectorRouter-AvoidsObstacles`):
`Route_ObstacleBetweenEndpoints_RoutesAroundInterior` confirms the route is orthogonal and never
enters an intervening box's interior.
- **Endpoint exclusion** (`Rendering-Layout-ConnectorRouter-ExcludesEndpoints`):
`Route_EndpointBoxes_AreExcludedFromObstacles` confirms the connector reaches the endpoints' boundary
anchors even though both boxes appear in the box list.
- **Endpoint boxes as ordinary obstacles** (`Rendering-Layout-ConnectorRouter-ExcludesEndpoints`):
`Route_EndpointBoxes_ReachBoundaryAnchorsViaApproachStub` confirms the connector still reaches the
endpoints' boundary anchors even though both boxes are treated as obstacles, and
`Route_NineParallelEdgesIntoCompartmentBox_DoNotCrossTargetInterior` confirms a batch of parallel
connectors converging on one box face never cuts through that box's (or the source box's) interior,
reproducing a previously reported defect.
- **Styling carried** (`Rendering-Layout-ConnectorRouter-CarriesStyling`):
`Route_Connection_CarriesRequestedStyling` confirms the requested target marker, line style, and
label flow onto the produced line while the source end stays unmarked.
Expand Down Expand Up @@ -94,7 +97,8 @@ level rather than introducing a new `ConnectorRouter`-level responsibility:
- **`Rendering-Layout-ConnectorRouter-AvoidsObstacles`**:
Route_ObstacleBetweenEndpoints_RoutesAroundInterior
- **`Rendering-Layout-ConnectorRouter-ExcludesEndpoints`**:
Route_EndpointBoxes_AreExcludedFromObstacles
Route_EndpointBoxes_ReachBoundaryAnchorsViaApproachStub,
Route_NineParallelEdgesIntoCompartmentBox_DoNotCrossTargetInterior
- **`Rendering-Layout-ConnectorRouter-CarriesStyling`**:
Route_Connection_CarriesRequestedStyling
- **`Rendering-Layout-ConnectorRouter-BatchOrder`**:
Expand Down
19 changes: 19 additions & 0 deletions src/DemaConsulting.Rendering.Abstractions/BoxMetrics.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,25 @@ public static double FolderTabHeight(Theme theme)
return theme.FontSizeBody + 2.0 * theme.LabelPadding;
}

/// <summary>
/// Computes the size of the folded-corner cutout drawn at the top-right of a
/// <see cref="BoxShape.Note"/> box: a fraction of the box's shorter side
/// (<see cref="NotationMetrics.NoteFoldFraction"/>), capped at
/// <see cref="NotationMetrics.NoteFoldMaxSize"/>. The same value is the horizontal inset from
/// the right edge and the vertical inset from the top edge, since the fold cutout is a
/// right-angle triangle. Both renderers, and anything reasoning about how close a horizontal
/// divider may safely approach a Note box's top edge, must call this so the fold's drawn
/// geometry and any content clearance never disagree.
/// </summary>
/// <param name="box">The Note-shaped box whose fold size is resolved.</param>
/// <returns>The fold size in logical pixels.</returns>
/// <exception cref="ArgumentNullException"><paramref name="box"/> is <see langword="null"/>.</exception>
public static double NoteFoldSize(LayoutBox box)
{
ArgumentNullException.ThrowIfNull(box);
return Math.Min(Math.Min(box.Width, box.Height) * NotationMetrics.NoteFoldFraction, NotationMetrics.NoteFoldMaxSize);
}

/// <summary>
/// Computes the height of the title area of a box: the vertical space reserved at the top
/// for the optional keyword line and the bold name line.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@

<!-- Polyfill Configuration -->
<PolyArgumentExceptions>true</PolyArgumentExceptions>
<PolyUseEmbeddedAttribute>true</PolyUseEmbeddedAttribute>

<!-- SBOM Configuration -->
<GenerateSBOM>true</GenerateSBOM>
Expand Down Expand Up @@ -74,6 +75,8 @@

<ItemGroup>
<InternalsVisibleTo Include="DemaConsulting.Rendering.Abstractions.Tests" />
<InternalsVisibleTo Include="DemaConsulting.Rendering.Layout" />
<InternalsVisibleTo Include="DemaConsulting.Rendering.Layout.Tests" />
</ItemGroup>

<ItemGroup>
Expand Down
37 changes: 0 additions & 37 deletions src/DemaConsulting.Rendering.Abstractions/ILayoutAlgorithm.cs

This file was deleted.

Loading
Loading