Complete ElkSharp document rendering cleanup and source decomposition
- Fix target-join (edge/4+edge/17): gateway face overflow redirect to left tip - Fix under-node (edge/14,15,20): push-first corridor reroute instead of top corridor - Fix boundary-slots (4->0): snap after gateway polish reordering - Fix gateway corner diagonals (2->0): post-pipeline straightening pass - Fix gateway interior adjacent: polygon-aware IsInsideNodeShapeInterior - Fix gateway source face mismatch (2->0): per-edge redirect with lenient validation - Fix gateway source scoring (5->0): per-edge scoring candidate application - Fix edge-node crossing (1->0): push horizontal segment above blocking node - Decompose 7 oversized files (~20K lines) into 55+ partials under 400 lines each - Archive sprints 004 (document cleanup), 005 (decomposition), 007 (render speed) All 44+ document-processing artifact assertions pass. Hybrid deterministic mode documented as recommended path for LeftToRight layouts. Tests verified: StraightExit 2/2, BoundarySlotOffenders 2/2, HybridDeterministicMode 3/3, DocumentProcessingWorkflow artifact 1/1. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,311 @@
|
||||
namespace StellaOps.ElkSharp;
|
||||
|
||||
internal static partial class ElkEdgePostProcessor
|
||||
{
|
||||
private static List<ElkPoint> TrimTargetApproachBacktracking(
|
||||
IReadOnlyList<ElkPoint> sourcePath,
|
||||
ElkPositionedNode targetNode,
|
||||
string side,
|
||||
ElkPoint explicitEndpoint)
|
||||
{
|
||||
if (sourcePath.Count < 4)
|
||||
{
|
||||
return sourcePath
|
||||
.Select(point => new ElkPoint { X = point.X, Y = point.Y })
|
||||
.ToList();
|
||||
}
|
||||
|
||||
const double tolerance = 0.5d;
|
||||
var startIndex = Math.Max(0, sourcePath.Count - 5);
|
||||
var firstOffendingIndex = -1;
|
||||
for (var i = startIndex; i < sourcePath.Count - 1; i++)
|
||||
{
|
||||
if (IsOnWrongSideOfTarget(sourcePath[i], targetNode, side, tolerance))
|
||||
{
|
||||
firstOffendingIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (firstOffendingIndex < 0)
|
||||
{
|
||||
return sourcePath
|
||||
.Select(point => new ElkPoint { X = point.X, Y = point.Y })
|
||||
.ToList();
|
||||
}
|
||||
|
||||
var trimmed = sourcePath
|
||||
.Take(Math.Max(1, firstOffendingIndex))
|
||||
.Select(point => new ElkPoint { X = point.X, Y = point.Y })
|
||||
.ToList();
|
||||
if (trimmed.Count == 0 || !ElkEdgeRoutingGeometry.PointsEqual(trimmed[^1], explicitEndpoint))
|
||||
{
|
||||
trimmed.Add(explicitEndpoint);
|
||||
}
|
||||
|
||||
return NormalizeEntryPath(trimmed, targetNode, side, explicitEndpoint);
|
||||
}
|
||||
|
||||
private static bool TryNormalizeNonGatewayBacktrackingEntry(
|
||||
IReadOnlyList<ElkPoint> sourcePath,
|
||||
ElkPositionedNode targetNode,
|
||||
out List<ElkPoint> repairedPath)
|
||||
{
|
||||
repairedPath = sourcePath
|
||||
.Select(point => new ElkPoint { X = point.X, Y = point.Y })
|
||||
.ToList();
|
||||
if (sourcePath.Count < 2)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!TryResolveNonGatewayBacktrackingEndpoint(sourcePath, targetNode, out var side, out var endpoint))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var candidate = NormalizeEntryPath(sourcePath, targetNode, side, endpoint);
|
||||
if (HasTargetApproachBacktracking(candidate, targetNode))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
repairedPath = candidate;
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool TryResolveNonGatewayBacktrackingEndpoint(
|
||||
IReadOnlyList<ElkPoint> sourcePath,
|
||||
ElkPositionedNode targetNode,
|
||||
out string side,
|
||||
out ElkPoint endpoint)
|
||||
{
|
||||
side = string.Empty;
|
||||
endpoint = default!;
|
||||
if (sourcePath.Count < 2)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var anchor = sourcePath[^2];
|
||||
var centerX = targetNode.X + (targetNode.Width / 2d);
|
||||
var centerY = targetNode.Y + (targetNode.Height / 2d);
|
||||
var deltaX = anchor.X - centerX;
|
||||
var deltaY = anchor.Y - centerY;
|
||||
var dominantHorizontal = Math.Abs(deltaX) >= Math.Abs(deltaY) * 1.15d;
|
||||
side = dominantHorizontal
|
||||
? (deltaX <= 0d ? "left" : "right")
|
||||
: (deltaY <= 0d ? "top" : "bottom");
|
||||
|
||||
if (side is "left" or "right")
|
||||
{
|
||||
endpoint = new ElkPoint
|
||||
{
|
||||
X = side == "left" ? targetNode.X : targetNode.X + targetNode.Width,
|
||||
Y = Math.Clamp(anchor.Y, targetNode.Y + 4d, targetNode.Y + targetNode.Height - 4d),
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
endpoint = new ElkPoint
|
||||
{
|
||||
X = Math.Clamp(anchor.X, targetNode.X + 4d, targetNode.X + targetNode.Width - 4d),
|
||||
Y = side == "top" ? targetNode.Y : targetNode.Y + targetNode.Height,
|
||||
};
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool HasTargetApproachBacktracking(
|
||||
IReadOnlyList<ElkPoint> path,
|
||||
ElkPositionedNode targetNode)
|
||||
{
|
||||
if (path.Count < 3 || ElkShapeBoundaries.IsGatewayShape(targetNode))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var side = ElkEdgeRoutingGeometry.ResolveBoundarySide(path[^1], targetNode);
|
||||
if (side is not "left" and not "right" and not "top" and not "bottom")
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const double tolerance = 0.5d;
|
||||
if (HasShortOrthogonalTargetHook(path, targetNode, side, tolerance))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var startIndex = Math.Max(0, path.Count - (side is "left" or "right" ? 4 : 3));
|
||||
var axisValues = new List<double>(path.Count - startIndex);
|
||||
for (var i = startIndex; i < path.Count; i++)
|
||||
{
|
||||
var value = side is "left" or "right"
|
||||
? path[i].X
|
||||
: path[i].Y;
|
||||
if (axisValues.Count == 0 || Math.Abs(axisValues[^1] - value) > tolerance)
|
||||
{
|
||||
axisValues.Add(value);
|
||||
}
|
||||
}
|
||||
|
||||
if (axisValues.Count < 3)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var targetAxis = side switch
|
||||
{
|
||||
"left" => targetNode.X,
|
||||
"right" => targetNode.X + targetNode.Width,
|
||||
"top" => targetNode.Y,
|
||||
"bottom" => targetNode.Y + targetNode.Height,
|
||||
_ => double.NaN,
|
||||
};
|
||||
|
||||
var overshootsTargetSide = side switch
|
||||
{
|
||||
"left" or "top" => axisValues.Any(value => value > targetAxis + tolerance),
|
||||
"right" or "bottom" => axisValues.Any(value => value < targetAxis - tolerance),
|
||||
_ => false,
|
||||
};
|
||||
if (overshootsTargetSide)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var expectsIncreasing = side is "left" or "top";
|
||||
var sawProgress = false;
|
||||
for (var i = 1; i < axisValues.Count; i++)
|
||||
{
|
||||
var delta = axisValues[i] - axisValues[i - 1];
|
||||
if (Math.Abs(delta) <= tolerance)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (expectsIncreasing)
|
||||
{
|
||||
if (delta > tolerance)
|
||||
{
|
||||
sawProgress = true;
|
||||
}
|
||||
else if (sawProgress)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (delta < -tolerance)
|
||||
{
|
||||
sawProgress = true;
|
||||
}
|
||||
else if (sawProgress)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool HasShortOrthogonalTargetHook(
|
||||
IReadOnlyList<ElkPoint> path,
|
||||
ElkPositionedNode targetNode,
|
||||
string side,
|
||||
double tolerance)
|
||||
{
|
||||
if (path.Count < 3)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var boundaryPoint = path[^1];
|
||||
var runStartIndex = path.Count - 2;
|
||||
if (side is "left" or "right")
|
||||
{
|
||||
while (runStartIndex > 0 && Math.Abs(path[runStartIndex - 1].Y - boundaryPoint.Y) <= tolerance)
|
||||
{
|
||||
runStartIndex--;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
while (runStartIndex > 0 && Math.Abs(path[runStartIndex - 1].X - boundaryPoint.X) <= tolerance)
|
||||
{
|
||||
runStartIndex--;
|
||||
}
|
||||
}
|
||||
|
||||
if (runStartIndex == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var overallDeltaX = path[^1].X - path[0].X;
|
||||
var overallDeltaY = path[^1].Y - path[0].Y;
|
||||
var overallAbsDx = Math.Abs(overallDeltaX);
|
||||
var overallAbsDy = Math.Abs(overallDeltaY);
|
||||
var sameRowThreshold = Math.Max(24d, targetNode.Height / 3d);
|
||||
var sameColumnThreshold = Math.Max(24d, targetNode.Width / 3d);
|
||||
var looksHorizontal = overallAbsDx >= overallAbsDy * 1.15d
|
||||
&& overallAbsDy <= sameRowThreshold
|
||||
&& Math.Sign(overallDeltaX) != 0;
|
||||
var looksVertical = overallAbsDy >= overallAbsDx * 1.15d
|
||||
&& overallAbsDx <= sameColumnThreshold
|
||||
&& Math.Sign(overallDeltaY) != 0;
|
||||
var contradictsDominantApproach = side switch
|
||||
{
|
||||
"left" or "right" => looksVertical,
|
||||
"top" or "bottom" => looksHorizontal,
|
||||
_ => false,
|
||||
};
|
||||
if (!contradictsDominantApproach)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var runStart = path[runStartIndex];
|
||||
var boundaryDepth = side is "left" or "right"
|
||||
? Math.Abs(boundaryPoint.X - runStart.X)
|
||||
: Math.Abs(boundaryPoint.Y - runStart.Y);
|
||||
var requiredDepth = side is "left" or "right"
|
||||
? targetNode.Width
|
||||
: targetNode.Height;
|
||||
if (boundaryDepth + tolerance >= requiredDepth)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var predecessor = path[runStartIndex - 1];
|
||||
var predecessorDx = Math.Abs(runStart.X - predecessor.X);
|
||||
var predecessorDy = Math.Abs(runStart.Y - predecessor.Y);
|
||||
return side switch
|
||||
{
|
||||
"left" or "right" => predecessorDy > predecessorDx * 3d,
|
||||
"top" or "bottom" => predecessorDx > predecessorDy * 3d,
|
||||
_ => false,
|
||||
};
|
||||
}
|
||||
|
||||
private static bool IsOnWrongSideOfTarget(
|
||||
ElkPoint point,
|
||||
ElkPositionedNode targetNode,
|
||||
string side,
|
||||
double tolerance)
|
||||
{
|
||||
return side switch
|
||||
{
|
||||
"left" => point.X > targetNode.X + tolerance,
|
||||
"right" => point.X < (targetNode.X + targetNode.Width) - tolerance,
|
||||
"top" => point.Y > targetNode.Y + tolerance,
|
||||
"bottom" => point.Y < (targetNode.Y + targetNode.Height) - tolerance,
|
||||
_ => false,
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user