@crazyhappyone/auto-graph 0.2.2 → 0.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.cts CHANGED
@@ -448,6 +448,19 @@ interface ConstraintSolverResult {
448
448
 
449
449
  declare function applyLayoutConstraints(input: ConstraintSolverInput): ConstraintSolverResult;
450
450
 
451
+ interface QualityMetric {
452
+ readonly kind: QualityMetricKind;
453
+ readonly value: number;
454
+ readonly label: string;
455
+ }
456
+ type QualityMetricKind = "node-overlap" | "edge-crossing" | "bend-count" | "route-backtrack" | "label-collision";
457
+ interface QualityReport {
458
+ readonly metrics: QualityMetric[];
459
+ /** Aggregate score 0–100 (higher = better). */
460
+ readonly score: number;
461
+ readonly diagnostics: Diagnostic[];
462
+ }
463
+
451
464
  type DiagramStage = "intent" | "normalized" | "coordinated";
452
465
  type DiagramMetadata = JsonObject;
453
466
  interface IntentDiagram {
@@ -499,6 +512,7 @@ interface CoordinatedDiagram {
499
512
  bounds: Box;
500
513
  frame?: CoordinatedFrame;
501
514
  metadata?: DiagramMetadata;
515
+ qualityReport?: QualityReport;
502
516
  }
503
517
 
504
518
  type DslDiagnosticLayer = "parse" | "validate" | "solve" | "export" | "io";
@@ -740,6 +754,8 @@ interface SolveDiagramOptions {
740
754
  initialLayout?: InitialLayoutMode;
741
755
  routeKind?: RouteKind;
742
756
  obstacleMargin?: number | Insets;
757
+ /** When true, compute quality score after solving (Issue #54, 方案 E). */
758
+ qualityScore?: boolean;
743
759
  /** Extra horizontal/vertical clearance reserved around nodes for edge corridors. */
744
760
  routingGutter?: number;
745
761
  overlapSpacing?: number;
@@ -781,5 +797,73 @@ declare function solveDiagram(diagram: NormalizedDiagram, options?: SolveDiagram
781
797
  * @see SolveDiagramOptions.prefitLabelSize
782
798
  */
783
799
  declare function solveDiagramSafe(diagram: NormalizedDiagram, options?: SolveDiagramOptions): CoordinatedDiagram;
800
+ /**
801
+ * Build the default layout pipeline. Currently wraps `solveDiagram` in
802
+ * a single mega-phase so custom callers can replace individual phases
803
+ * (e.g. "initial-layout", "route-edges") without touching the rest.
804
+ *
805
+ * Individual phases will be extracted from the mega-phase in follow-up
806
+ * PRs for 方案 A (recursive layout) and 方案 B (corner-graph A*).
807
+ */
808
+ declare function createDefaultPipeline(): LayoutPipeline;
809
+
810
+ /** Shared mutable state flowing through every pipeline phase. */
811
+ interface LayoutState {
812
+ diagram: NormalizedDiagram;
813
+ options: SolveDiagramOptions;
814
+ nodes: NormalizedNode[];
815
+ edges: NormalizedEdge[];
816
+ groups: NormalizedGroup[];
817
+ swimlanes: Swimlane[];
818
+ constraints: Constraint[];
819
+ boxes: Map<string, Box>;
820
+ locks: Map<string, LayoutLock>;
821
+ nodeGeometry: Map<string, ShapeGeometry>;
822
+ coordinatedNodes: CoordinatedNode[];
823
+ coordinatedEdges: CoordinatedEdge[];
824
+ coordinatedGroups: CoordinatedGroup[];
825
+ coordinatedMatrices: CoordinatedMatrixBlock[];
826
+ coordinatedTables: CoordinatedTableBlock[];
827
+ coordinatedEvidencePanels: CoordinatedEvidencePanel[];
828
+ frame?: CoordinatedFrame;
829
+ baseTextAnnotations: SolvedTextAnnotation[];
830
+ edgeTextAnnotations: SolvedTextAnnotation[];
831
+ contentBounds: Box;
832
+ bounds: Box;
833
+ degraded: boolean;
834
+ diagnostics: Diagnostic[];
835
+ qualityReport?: QualityReport;
836
+ phaseTrace: PhaseTraceEntry[];
837
+ }
838
+ interface PhaseTraceEntry {
839
+ phase: string;
840
+ durationMs: number;
841
+ diagnosticsAdded: number;
842
+ }
843
+ /** A single pipeline phase — mutates `state` in-place. */
844
+ interface LayoutPhase {
845
+ readonly name: string;
846
+ run(state: LayoutState): void;
847
+ }
848
+
849
+ /**
850
+ * Pluggable layout pipeline (Issue #54, 方案 D).
851
+ *
852
+ * Phases run sequentially, each mutating the shared LayoutState.
853
+ * Custom pipelines are built with the fluent builder API:
854
+ *
855
+ * new LayoutPipeline()
856
+ * .addPhase(myPhase)
857
+ * .addBefore("apply-constraints", prePhase)
858
+ * .run(diagram, options);
859
+ */
860
+ declare class LayoutPipeline {
861
+ private phases;
862
+ addPhase(phase: LayoutPhase): this;
863
+ addBefore(refName: string, phase: LayoutPhase): this;
864
+ addAfter(refName: string, phase: LayoutPhase): this;
865
+ replacePhase(name: string, phase: LayoutPhase): this;
866
+ run(state: LayoutState): void;
867
+ }
784
868
 
785
- export { type AlignConstraint, type AlignmentAxis, type AnchorName, type AnchorPoint, type Arrowhead, type Box, type BoxSpatialIndex, type BoxSpatialIndexEntry, type CanonicalJson, type CanonicalizeOptions, type Constraint, type ConstraintBase, type ConstraintSolverInput, type ConstraintSolverResult, type ConstraintTarget, type ConstraintTargetKind, type ContainerGeometry, type ContainerGeometryInput, type ContainmentConstraint, type CoordinatedDiagram, type CoordinatedEdge, type CoordinatedEvidencePanel, type CoordinatedFrame, type CoordinatedGroup, type CoordinatedMatrixBlock, type CoordinatedNode, type CoordinatedPort, type CoordinatedTableBlock, DEFAULT_CANONICAL_PRECISION, DEFAULT_DSL_MAX_BYTES, DELIVERABILITY_DIAGNOSTIC_CODES, type DagreLayoutEdge, type DagreLayoutInput, type DagreLayoutNode, type DagreLayoutOptions, type DefaultTextMeasurerOptions, DeterministicTextMeasurer, type Diagnostic, type DiagnosticPathSegment, type DiagnosticSeverity, type DiagramDirection, type DiagramFrame, type DiagramMetadata, type DiagramStage, type DistributeConstraint, type DistributionAxis, type DslDiagnostic, type DslDiagnosticLayer, type DslOutputFormat, type EdgeArrowhead, type EdgeEndpoint, type EdgeStrokeStyle, type EvidenceCell, type EvidencePanel, type EvidencePanelItem, type EvidencePanelKind, type EvidenceTextLayout, type ExactPositionConstraint, type ExportFormat, type ExportOptions, type ExportResult, type InitialLayoutMode, type InitialLayoutResult, type Insets, type IntentDiagram, type IntentEdge, type IntentGroup, type IntentNode, type JsonObject, type JsonPrimitive, type JsonValue, type Label, type LabelFitOptions, LabelFitter, type LabelLayout, type LabelLineLayout, type LayoutLock, type LayoutLockSource, type MatrixBlock, type NodeBase, type NodeCompartments, type NodePort, type NodeShape, type NormalizeDiagramDslOptions, type NormalizeDiagramDslResult, type NormalizedDiagram, type NormalizedEdge, type NormalizedGroup, type NormalizedNode, type ParseDiagramDslOptions, type ParseDiagramDslResult, type ParsedEdgeShorthand, type Point, type PortKind, type PortShiftingOptions, type PortSide, type PreparedText, PretextTextMeasurer, type RelativePositionConstraint, type RelativePositionRelation, type RenderDiagramDslOptions, type RenderDiagramDslResult, type RouteEdgeInput, type RouteEdgeResult, type RouteKind, type ShapeGeometry, type ShapeGeometryInput, type Size, type SolveDiagramOptions, type SolvedTextAnnotation, type Swimlane, type SwimlaneLane, type SwimlaneLayout, type SwimlaneOrientation, type TableBlock, type TableColumn, type TableRow, type TextCursor, type TextLayout, type TextLayoutLine, type TextMeasurementBackend, type TextMeasurer, type TextStyleOptions, type TextSurfaceKind, type VisualStyle, applyLayoutConstraints, assertFiniteNonNegative, assertFinitePositive, boxCenter, canonicalize, computeArrowhead, computeContainerGeometry, computeShapeGeometry, createBoxSpatialIndex, createDefaultTextMeasurer, expandBox, expandBoxForQuery, exportExcalidraw, exportSvg, fitLabel, getEdgePort, installNodeCanvasRuntime, intersectsAabb, isPretextRuntimeAvailable, normalizeDiagramDsl, normalizeInsets, overlapArea, parseDiagramDsl, parseEdgeShorthand, queryBoxSpatialIndex, querySegmentSpatialIndex, renderDiagramDsl, resolveLineHeight, resolveOutputFormat, routeEdge, runComponentAwareDagreInitialLayout, runDagreInitialLayout, simplifyRoute, solveDiagram, solveDiagramSafe, sortDslDiagnostics, stringifyCanonical, toCanvasFont, unionBoxes, validateBox, validateTextStyle };
869
+ export { type AlignConstraint, type AlignmentAxis, type AnchorName, type AnchorPoint, type Arrowhead, type Box, type BoxSpatialIndex, type BoxSpatialIndexEntry, type CanonicalJson, type CanonicalizeOptions, type Constraint, type ConstraintBase, type ConstraintSolverInput, type ConstraintSolverResult, type ConstraintTarget, type ConstraintTargetKind, type ContainerGeometry, type ContainerGeometryInput, type ContainmentConstraint, type CoordinatedDiagram, type CoordinatedEdge, type CoordinatedEvidencePanel, type CoordinatedFrame, type CoordinatedGroup, type CoordinatedMatrixBlock, type CoordinatedNode, type CoordinatedPort, type CoordinatedTableBlock, DEFAULT_CANONICAL_PRECISION, DEFAULT_DSL_MAX_BYTES, DELIVERABILITY_DIAGNOSTIC_CODES, type DagreLayoutEdge, type DagreLayoutInput, type DagreLayoutNode, type DagreLayoutOptions, type DefaultTextMeasurerOptions, DeterministicTextMeasurer, type Diagnostic, type DiagnosticPathSegment, type DiagnosticSeverity, type DiagramDirection, type DiagramFrame, type DiagramMetadata, type DiagramStage, type DistributeConstraint, type DistributionAxis, type DslDiagnostic, type DslDiagnosticLayer, type DslOutputFormat, type EdgeArrowhead, type EdgeEndpoint, type EdgeStrokeStyle, type EvidenceCell, type EvidencePanel, type EvidencePanelItem, type EvidencePanelKind, type EvidenceTextLayout, type ExactPositionConstraint, type ExportFormat, type ExportOptions, type ExportResult, type InitialLayoutMode, type InitialLayoutResult, type Insets, type IntentDiagram, type IntentEdge, type IntentGroup, type IntentNode, type JsonObject, type JsonPrimitive, type JsonValue, type Label, type LabelFitOptions, LabelFitter, type LabelLayout, type LabelLineLayout, type LayoutLock, type LayoutLockSource, type LayoutPhase, LayoutPipeline, type LayoutState, type MatrixBlock, type NodeBase, type NodeCompartments, type NodePort, type NodeShape, type NormalizeDiagramDslOptions, type NormalizeDiagramDslResult, type NormalizedDiagram, type NormalizedEdge, type NormalizedGroup, type NormalizedNode, type ParseDiagramDslOptions, type ParseDiagramDslResult, type ParsedEdgeShorthand, type PhaseTraceEntry, type Point, type PortKind, type PortShiftingOptions, type PortSide, type PreparedText, PretextTextMeasurer, type RelativePositionConstraint, type RelativePositionRelation, type RenderDiagramDslOptions, type RenderDiagramDslResult, type RouteEdgeInput, type RouteEdgeResult, type RouteKind, type ShapeGeometry, type ShapeGeometryInput, type Size, type SolveDiagramOptions, type SolvedTextAnnotation, type Swimlane, type SwimlaneLane, type SwimlaneLayout, type SwimlaneOrientation, type TableBlock, type TableColumn, type TableRow, type TextCursor, type TextLayout, type TextLayoutLine, type TextMeasurementBackend, type TextMeasurer, type TextStyleOptions, type TextSurfaceKind, type VisualStyle, applyLayoutConstraints, assertFiniteNonNegative, assertFinitePositive, boxCenter, canonicalize, computeArrowhead, computeContainerGeometry, computeShapeGeometry, createBoxSpatialIndex, createDefaultPipeline, createDefaultTextMeasurer, expandBox, expandBoxForQuery, exportExcalidraw, exportSvg, fitLabel, getEdgePort, installNodeCanvasRuntime, intersectsAabb, isPretextRuntimeAvailable, normalizeDiagramDsl, normalizeInsets, overlapArea, parseDiagramDsl, parseEdgeShorthand, queryBoxSpatialIndex, querySegmentSpatialIndex, renderDiagramDsl, resolveLineHeight, resolveOutputFormat, routeEdge, runComponentAwareDagreInitialLayout, runDagreInitialLayout, simplifyRoute, solveDiagram, solveDiagramSafe, sortDslDiagnostics, stringifyCanonical, toCanvasFont, unionBoxes, validateBox, validateTextStyle };
package/dist/index.d.ts CHANGED
@@ -448,6 +448,19 @@ interface ConstraintSolverResult {
448
448
 
449
449
  declare function applyLayoutConstraints(input: ConstraintSolverInput): ConstraintSolverResult;
450
450
 
451
+ interface QualityMetric {
452
+ readonly kind: QualityMetricKind;
453
+ readonly value: number;
454
+ readonly label: string;
455
+ }
456
+ type QualityMetricKind = "node-overlap" | "edge-crossing" | "bend-count" | "route-backtrack" | "label-collision";
457
+ interface QualityReport {
458
+ readonly metrics: QualityMetric[];
459
+ /** Aggregate score 0–100 (higher = better). */
460
+ readonly score: number;
461
+ readonly diagnostics: Diagnostic[];
462
+ }
463
+
451
464
  type DiagramStage = "intent" | "normalized" | "coordinated";
452
465
  type DiagramMetadata = JsonObject;
453
466
  interface IntentDiagram {
@@ -499,6 +512,7 @@ interface CoordinatedDiagram {
499
512
  bounds: Box;
500
513
  frame?: CoordinatedFrame;
501
514
  metadata?: DiagramMetadata;
515
+ qualityReport?: QualityReport;
502
516
  }
503
517
 
504
518
  type DslDiagnosticLayer = "parse" | "validate" | "solve" | "export" | "io";
@@ -740,6 +754,8 @@ interface SolveDiagramOptions {
740
754
  initialLayout?: InitialLayoutMode;
741
755
  routeKind?: RouteKind;
742
756
  obstacleMargin?: number | Insets;
757
+ /** When true, compute quality score after solving (Issue #54, 方案 E). */
758
+ qualityScore?: boolean;
743
759
  /** Extra horizontal/vertical clearance reserved around nodes for edge corridors. */
744
760
  routingGutter?: number;
745
761
  overlapSpacing?: number;
@@ -781,5 +797,73 @@ declare function solveDiagram(diagram: NormalizedDiagram, options?: SolveDiagram
781
797
  * @see SolveDiagramOptions.prefitLabelSize
782
798
  */
783
799
  declare function solveDiagramSafe(diagram: NormalizedDiagram, options?: SolveDiagramOptions): CoordinatedDiagram;
800
+ /**
801
+ * Build the default layout pipeline. Currently wraps `solveDiagram` in
802
+ * a single mega-phase so custom callers can replace individual phases
803
+ * (e.g. "initial-layout", "route-edges") without touching the rest.
804
+ *
805
+ * Individual phases will be extracted from the mega-phase in follow-up
806
+ * PRs for 方案 A (recursive layout) and 方案 B (corner-graph A*).
807
+ */
808
+ declare function createDefaultPipeline(): LayoutPipeline;
809
+
810
+ /** Shared mutable state flowing through every pipeline phase. */
811
+ interface LayoutState {
812
+ diagram: NormalizedDiagram;
813
+ options: SolveDiagramOptions;
814
+ nodes: NormalizedNode[];
815
+ edges: NormalizedEdge[];
816
+ groups: NormalizedGroup[];
817
+ swimlanes: Swimlane[];
818
+ constraints: Constraint[];
819
+ boxes: Map<string, Box>;
820
+ locks: Map<string, LayoutLock>;
821
+ nodeGeometry: Map<string, ShapeGeometry>;
822
+ coordinatedNodes: CoordinatedNode[];
823
+ coordinatedEdges: CoordinatedEdge[];
824
+ coordinatedGroups: CoordinatedGroup[];
825
+ coordinatedMatrices: CoordinatedMatrixBlock[];
826
+ coordinatedTables: CoordinatedTableBlock[];
827
+ coordinatedEvidencePanels: CoordinatedEvidencePanel[];
828
+ frame?: CoordinatedFrame;
829
+ baseTextAnnotations: SolvedTextAnnotation[];
830
+ edgeTextAnnotations: SolvedTextAnnotation[];
831
+ contentBounds: Box;
832
+ bounds: Box;
833
+ degraded: boolean;
834
+ diagnostics: Diagnostic[];
835
+ qualityReport?: QualityReport;
836
+ phaseTrace: PhaseTraceEntry[];
837
+ }
838
+ interface PhaseTraceEntry {
839
+ phase: string;
840
+ durationMs: number;
841
+ diagnosticsAdded: number;
842
+ }
843
+ /** A single pipeline phase — mutates `state` in-place. */
844
+ interface LayoutPhase {
845
+ readonly name: string;
846
+ run(state: LayoutState): void;
847
+ }
848
+
849
+ /**
850
+ * Pluggable layout pipeline (Issue #54, 方案 D).
851
+ *
852
+ * Phases run sequentially, each mutating the shared LayoutState.
853
+ * Custom pipelines are built with the fluent builder API:
854
+ *
855
+ * new LayoutPipeline()
856
+ * .addPhase(myPhase)
857
+ * .addBefore("apply-constraints", prePhase)
858
+ * .run(diagram, options);
859
+ */
860
+ declare class LayoutPipeline {
861
+ private phases;
862
+ addPhase(phase: LayoutPhase): this;
863
+ addBefore(refName: string, phase: LayoutPhase): this;
864
+ addAfter(refName: string, phase: LayoutPhase): this;
865
+ replacePhase(name: string, phase: LayoutPhase): this;
866
+ run(state: LayoutState): void;
867
+ }
784
868
 
785
- export { type AlignConstraint, type AlignmentAxis, type AnchorName, type AnchorPoint, type Arrowhead, type Box, type BoxSpatialIndex, type BoxSpatialIndexEntry, type CanonicalJson, type CanonicalizeOptions, type Constraint, type ConstraintBase, type ConstraintSolverInput, type ConstraintSolverResult, type ConstraintTarget, type ConstraintTargetKind, type ContainerGeometry, type ContainerGeometryInput, type ContainmentConstraint, type CoordinatedDiagram, type CoordinatedEdge, type CoordinatedEvidencePanel, type CoordinatedFrame, type CoordinatedGroup, type CoordinatedMatrixBlock, type CoordinatedNode, type CoordinatedPort, type CoordinatedTableBlock, DEFAULT_CANONICAL_PRECISION, DEFAULT_DSL_MAX_BYTES, DELIVERABILITY_DIAGNOSTIC_CODES, type DagreLayoutEdge, type DagreLayoutInput, type DagreLayoutNode, type DagreLayoutOptions, type DefaultTextMeasurerOptions, DeterministicTextMeasurer, type Diagnostic, type DiagnosticPathSegment, type DiagnosticSeverity, type DiagramDirection, type DiagramFrame, type DiagramMetadata, type DiagramStage, type DistributeConstraint, type DistributionAxis, type DslDiagnostic, type DslDiagnosticLayer, type DslOutputFormat, type EdgeArrowhead, type EdgeEndpoint, type EdgeStrokeStyle, type EvidenceCell, type EvidencePanel, type EvidencePanelItem, type EvidencePanelKind, type EvidenceTextLayout, type ExactPositionConstraint, type ExportFormat, type ExportOptions, type ExportResult, type InitialLayoutMode, type InitialLayoutResult, type Insets, type IntentDiagram, type IntentEdge, type IntentGroup, type IntentNode, type JsonObject, type JsonPrimitive, type JsonValue, type Label, type LabelFitOptions, LabelFitter, type LabelLayout, type LabelLineLayout, type LayoutLock, type LayoutLockSource, type MatrixBlock, type NodeBase, type NodeCompartments, type NodePort, type NodeShape, type NormalizeDiagramDslOptions, type NormalizeDiagramDslResult, type NormalizedDiagram, type NormalizedEdge, type NormalizedGroup, type NormalizedNode, type ParseDiagramDslOptions, type ParseDiagramDslResult, type ParsedEdgeShorthand, type Point, type PortKind, type PortShiftingOptions, type PortSide, type PreparedText, PretextTextMeasurer, type RelativePositionConstraint, type RelativePositionRelation, type RenderDiagramDslOptions, type RenderDiagramDslResult, type RouteEdgeInput, type RouteEdgeResult, type RouteKind, type ShapeGeometry, type ShapeGeometryInput, type Size, type SolveDiagramOptions, type SolvedTextAnnotation, type Swimlane, type SwimlaneLane, type SwimlaneLayout, type SwimlaneOrientation, type TableBlock, type TableColumn, type TableRow, type TextCursor, type TextLayout, type TextLayoutLine, type TextMeasurementBackend, type TextMeasurer, type TextStyleOptions, type TextSurfaceKind, type VisualStyle, applyLayoutConstraints, assertFiniteNonNegative, assertFinitePositive, boxCenter, canonicalize, computeArrowhead, computeContainerGeometry, computeShapeGeometry, createBoxSpatialIndex, createDefaultTextMeasurer, expandBox, expandBoxForQuery, exportExcalidraw, exportSvg, fitLabel, getEdgePort, installNodeCanvasRuntime, intersectsAabb, isPretextRuntimeAvailable, normalizeDiagramDsl, normalizeInsets, overlapArea, parseDiagramDsl, parseEdgeShorthand, queryBoxSpatialIndex, querySegmentSpatialIndex, renderDiagramDsl, resolveLineHeight, resolveOutputFormat, routeEdge, runComponentAwareDagreInitialLayout, runDagreInitialLayout, simplifyRoute, solveDiagram, solveDiagramSafe, sortDslDiagnostics, stringifyCanonical, toCanvasFont, unionBoxes, validateBox, validateTextStyle };
869
+ export { type AlignConstraint, type AlignmentAxis, type AnchorName, type AnchorPoint, type Arrowhead, type Box, type BoxSpatialIndex, type BoxSpatialIndexEntry, type CanonicalJson, type CanonicalizeOptions, type Constraint, type ConstraintBase, type ConstraintSolverInput, type ConstraintSolverResult, type ConstraintTarget, type ConstraintTargetKind, type ContainerGeometry, type ContainerGeometryInput, type ContainmentConstraint, type CoordinatedDiagram, type CoordinatedEdge, type CoordinatedEvidencePanel, type CoordinatedFrame, type CoordinatedGroup, type CoordinatedMatrixBlock, type CoordinatedNode, type CoordinatedPort, type CoordinatedTableBlock, DEFAULT_CANONICAL_PRECISION, DEFAULT_DSL_MAX_BYTES, DELIVERABILITY_DIAGNOSTIC_CODES, type DagreLayoutEdge, type DagreLayoutInput, type DagreLayoutNode, type DagreLayoutOptions, type DefaultTextMeasurerOptions, DeterministicTextMeasurer, type Diagnostic, type DiagnosticPathSegment, type DiagnosticSeverity, type DiagramDirection, type DiagramFrame, type DiagramMetadata, type DiagramStage, type DistributeConstraint, type DistributionAxis, type DslDiagnostic, type DslDiagnosticLayer, type DslOutputFormat, type EdgeArrowhead, type EdgeEndpoint, type EdgeStrokeStyle, type EvidenceCell, type EvidencePanel, type EvidencePanelItem, type EvidencePanelKind, type EvidenceTextLayout, type ExactPositionConstraint, type ExportFormat, type ExportOptions, type ExportResult, type InitialLayoutMode, type InitialLayoutResult, type Insets, type IntentDiagram, type IntentEdge, type IntentGroup, type IntentNode, type JsonObject, type JsonPrimitive, type JsonValue, type Label, type LabelFitOptions, LabelFitter, type LabelLayout, type LabelLineLayout, type LayoutLock, type LayoutLockSource, type LayoutPhase, LayoutPipeline, type LayoutState, type MatrixBlock, type NodeBase, type NodeCompartments, type NodePort, type NodeShape, type NormalizeDiagramDslOptions, type NormalizeDiagramDslResult, type NormalizedDiagram, type NormalizedEdge, type NormalizedGroup, type NormalizedNode, type ParseDiagramDslOptions, type ParseDiagramDslResult, type ParsedEdgeShorthand, type PhaseTraceEntry, type Point, type PortKind, type PortShiftingOptions, type PortSide, type PreparedText, PretextTextMeasurer, type RelativePositionConstraint, type RelativePositionRelation, type RenderDiagramDslOptions, type RenderDiagramDslResult, type RouteEdgeInput, type RouteEdgeResult, type RouteKind, type ShapeGeometry, type ShapeGeometryInput, type Size, type SolveDiagramOptions, type SolvedTextAnnotation, type Swimlane, type SwimlaneLane, type SwimlaneLayout, type SwimlaneOrientation, type TableBlock, type TableColumn, type TableRow, type TextCursor, type TextLayout, type TextLayoutLine, type TextMeasurementBackend, type TextMeasurer, type TextStyleOptions, type TextSurfaceKind, type VisualStyle, applyLayoutConstraints, assertFiniteNonNegative, assertFinitePositive, boxCenter, canonicalize, computeArrowhead, computeContainerGeometry, computeShapeGeometry, createBoxSpatialIndex, createDefaultPipeline, createDefaultTextMeasurer, expandBox, expandBoxForQuery, exportExcalidraw, exportSvg, fitLabel, getEdgePort, installNodeCanvasRuntime, intersectsAabb, isPretextRuntimeAvailable, normalizeDiagramDsl, normalizeInsets, overlapArea, parseDiagramDsl, parseEdgeShorthand, queryBoxSpatialIndex, querySegmentSpatialIndex, renderDiagramDsl, resolveLineHeight, resolveOutputFormat, routeEdge, runComponentAwareDagreInitialLayout, runDagreInitialLayout, simplifyRoute, solveDiagram, solveDiagramSafe, sortDslDiagnostics, stringifyCanonical, toCanvasFont, unionBoxes, validateBox, validateTextStyle };
package/dist/index.js CHANGED
@@ -4051,6 +4051,45 @@ function indentLines(values) {
4051
4051
  return values.map(indent);
4052
4052
  }
4053
4053
 
4054
+ // src/solver/pipeline/pipeline.ts
4055
+ var LayoutPipeline = class {
4056
+ phases = [];
4057
+ addPhase(phase) {
4058
+ this.phases.push(phase);
4059
+ return this;
4060
+ }
4061
+ addBefore(refName, phase) {
4062
+ const idx = this.phases.findIndex((p) => p.name === refName);
4063
+ if (idx === -1) throw new Error(`Phase "${refName}" not found`);
4064
+ this.phases.splice(idx, 0, phase);
4065
+ return this;
4066
+ }
4067
+ addAfter(refName, phase) {
4068
+ const idx = this.phases.findIndex((p) => p.name === refName);
4069
+ if (idx === -1) throw new Error(`Phase "${refName}" not found`);
4070
+ this.phases.splice(idx + 1, 0, phase);
4071
+ return this;
4072
+ }
4073
+ replacePhase(name, phase) {
4074
+ const idx = this.phases.findIndex((p) => p.name === name);
4075
+ if (idx === -1) throw new Error(`Phase "${name}" not found`);
4076
+ this.phases[idx] = phase;
4077
+ return this;
4078
+ }
4079
+ run(state) {
4080
+ for (const phase of this.phases) {
4081
+ const before = state.diagnostics.length;
4082
+ const start = performance.now();
4083
+ phase.run(state);
4084
+ state.phaseTrace.push({
4085
+ phase: phase.name,
4086
+ durationMs: performance.now() - start,
4087
+ diagnosticsAdded: state.diagnostics.length - before
4088
+ });
4089
+ }
4090
+ }
4091
+ };
4092
+
4054
4093
  // src/ir/diagnostics.ts
4055
4094
  var DELIVERABILITY_DIAGNOSTIC_CODES = /* @__PURE__ */ new Set([
4056
4095
  "constraints.locked-target-not-moved",
@@ -5436,6 +5475,217 @@ function areCollinear2(a, b, c) {
5436
5475
  return a.x === b.x && b.x === c.x || a.y === b.y && b.y === c.y;
5437
5476
  }
5438
5477
 
5478
+ // src/solver/pipeline/quality.ts
5479
+ function scoreLayoutQuality(nodes, edges) {
5480
+ const diagnostics = [];
5481
+ const metrics = [];
5482
+ const overlapCount = countNodeOverlaps(nodes);
5483
+ const overlapScore = Math.max(0, 20 - overlapCount * 5);
5484
+ metrics.push({
5485
+ kind: "node-overlap",
5486
+ value: overlapCount,
5487
+ label: `${overlapCount} overlaps`
5488
+ });
5489
+ if (overlapCount > 0) {
5490
+ diagnostics.push({
5491
+ severity: "warning",
5492
+ code: "quality.node_overlap",
5493
+ message: `${overlapCount} node pair(s) overlap.`,
5494
+ detail: { overlapCount }
5495
+ });
5496
+ }
5497
+ const crossingCount = countEdgeCrossings(edges);
5498
+ const crossingScore = Math.max(0, 20 - crossingCount * 2);
5499
+ metrics.push({
5500
+ kind: "edge-crossing",
5501
+ value: crossingCount,
5502
+ label: `${crossingCount} crossings`
5503
+ });
5504
+ if (crossingCount > 0) {
5505
+ diagnostics.push({
5506
+ severity: "warning",
5507
+ code: "quality.edge_crossing",
5508
+ message: `${crossingCount} edge segment pair(s) cross.`,
5509
+ detail: { crossingCount }
5510
+ });
5511
+ }
5512
+ const totalBends = countTotalBends(edges);
5513
+ const bendScore = Math.max(0, 20 - totalBends * 0.5);
5514
+ metrics.push({
5515
+ kind: "bend-count",
5516
+ value: totalBends,
5517
+ label: `${totalBends} bends`
5518
+ });
5519
+ const backtrackCount = countBacktrackingEdges(edges);
5520
+ const backtrackScore = Math.max(0, 20 - backtrackCount * 5);
5521
+ metrics.push({
5522
+ kind: "route-backtrack",
5523
+ value: backtrackCount,
5524
+ label: `${backtrackCount} backtracking`
5525
+ });
5526
+ if (backtrackCount > 0) {
5527
+ diagnostics.push({
5528
+ severity: "warning",
5529
+ code: "quality.route_backtrack",
5530
+ message: `${backtrackCount} edge(s) are excessively long (>3\xD7 direct).`,
5531
+ detail: { backtrackCount }
5532
+ });
5533
+ }
5534
+ const labelCollisions = countLabelCollisions(nodes, edges);
5535
+ const labelScore = Math.max(0, 20 - labelCollisions * 3);
5536
+ metrics.push({
5537
+ kind: "label-collision",
5538
+ value: labelCollisions,
5539
+ label: `${labelCollisions} label collisions`
5540
+ });
5541
+ const score = Math.max(
5542
+ 0,
5543
+ Math.min(
5544
+ 100,
5545
+ overlapScore + crossingScore + bendScore + backtrackScore + labelScore
5546
+ )
5547
+ );
5548
+ return { metrics, score, diagnostics };
5549
+ }
5550
+ function countNodeOverlaps(nodes) {
5551
+ let count = 0;
5552
+ for (let i = 0; i < nodes.length; i++) {
5553
+ for (let j = i + 1; j < nodes.length; j++) {
5554
+ if (intersectsAabb(nodes[i].box, nodes[j].box)) {
5555
+ count++;
5556
+ }
5557
+ }
5558
+ }
5559
+ return count;
5560
+ }
5561
+ function countEdgeCrossings(edges) {
5562
+ let count = 0;
5563
+ for (let i = 0; i < edges.length; i++) {
5564
+ const aPts = edges[i].points;
5565
+ for (let j = i + 1; j < edges.length; j++) {
5566
+ const bPts = edges[j].points;
5567
+ for (let ai = 0; ai < aPts.length - 1; ai++) {
5568
+ for (let bi = 0; bi < bPts.length - 1; bi++) {
5569
+ if (segmentsIntersect(
5570
+ aPts[ai],
5571
+ aPts[ai + 1],
5572
+ bPts[bi],
5573
+ bPts[bi + 1]
5574
+ )) {
5575
+ count++;
5576
+ }
5577
+ }
5578
+ }
5579
+ }
5580
+ }
5581
+ return count;
5582
+ }
5583
+ function segmentsIntersect(a, b, c, d) {
5584
+ const d1 = cross(c, d, a);
5585
+ const d2 = cross(c, d, b);
5586
+ const d3 = cross(a, b, c);
5587
+ const d4 = cross(a, b, d);
5588
+ return (d1 > 0 && d2 < 0 || d1 < 0 && d2 > 0) && (d3 > 0 && d4 < 0 || d3 < 0 && d4 > 0);
5589
+ }
5590
+ function cross(o, a, b) {
5591
+ return (a.x - o.x) * (b.y - o.y) - (a.y - o.y) * (b.x - o.x);
5592
+ }
5593
+ function countTotalBends(edges) {
5594
+ const sign = (n) => n > 0 ? 1 : n < 0 ? -1 : 0;
5595
+ let bends = 0;
5596
+ for (const e of edges) {
5597
+ if (e.points.length < 3) continue;
5598
+ for (let i = 1; i < e.points.length - 1; i++) {
5599
+ const prev = e.points[i - 1];
5600
+ const curr = e.points[i];
5601
+ const next = e.points[i + 1];
5602
+ const dx1 = curr.x - prev.x;
5603
+ const dy1 = curr.y - prev.y;
5604
+ const dx2 = next.x - curr.x;
5605
+ const dy2 = next.y - curr.y;
5606
+ if (sign(dx1) !== sign(dx2) || sign(dy1) !== sign(dy2)) {
5607
+ bends++;
5608
+ }
5609
+ }
5610
+ }
5611
+ return bends;
5612
+ }
5613
+ function countBacktrackingEdges(edges) {
5614
+ let count = 0;
5615
+ for (const e of edges) {
5616
+ if (e.points.length < 2) continue;
5617
+ const first = e.points[0];
5618
+ const last = e.points[e.points.length - 1];
5619
+ const direct = Math.hypot(last.x - first.x, last.y - first.y);
5620
+ if (direct <= 0) continue;
5621
+ let routeLen = 0;
5622
+ for (let i = 0; i < e.points.length - 1; i++) {
5623
+ routeLen += Math.hypot(
5624
+ e.points[i + 1].x - e.points[i].x,
5625
+ e.points[i + 1].y - e.points[i].y
5626
+ );
5627
+ }
5628
+ if (routeLen > direct * 3) count++;
5629
+ }
5630
+ return count;
5631
+ }
5632
+ function countLabelCollisions(nodes, edges) {
5633
+ let count = 0;
5634
+ const nodeBoxes = /* @__PURE__ */ new Map();
5635
+ for (const n of nodes) {
5636
+ nodeBoxes.set(n.id, n.box);
5637
+ if (n.label?.text !== void 0) {
5638
+ const lw = n.label.text.length * 8;
5639
+ const labelBox = {
5640
+ x: n.box.x + n.box.width / 2 - lw / 2,
5641
+ y: n.box.y - 8,
5642
+ width: lw,
5643
+ height: 14
5644
+ };
5645
+ for (const [id, box] of nodeBoxes) {
5646
+ if (id === n.id) continue;
5647
+ if (intersectsAabb(labelBox, box)) count++;
5648
+ }
5649
+ for (const e of edges) {
5650
+ for (let i = 0; i < e.points.length - 1; i++) {
5651
+ if (segmentIntersectsBox2(e.points[i], e.points[i + 1], labelBox)) {
5652
+ count++;
5653
+ break;
5654
+ }
5655
+ }
5656
+ }
5657
+ }
5658
+ }
5659
+ return count;
5660
+ }
5661
+ function segmentIntersectsBox2(start, end, box) {
5662
+ const left = box.x;
5663
+ const right = box.x + box.width;
5664
+ const top = box.y;
5665
+ const bottom = box.y + box.height;
5666
+ if (start.x > left && start.x < right && start.y > top && start.y < bottom || end.x > left && end.x < right && end.y > top && end.y < bottom)
5667
+ return true;
5668
+ if (start.x === end.x) {
5669
+ return start.x > left && start.x < right && rangesOverlap3(start.y, end.y, top, bottom);
5670
+ }
5671
+ if (start.y === end.y) {
5672
+ return start.y > top && start.y < bottom && rangesOverlap3(start.x, end.x, left, right);
5673
+ }
5674
+ return edgeIntersect(start, end, left, top, right, top) || edgeIntersect(start, end, right, top, right, bottom) || edgeIntersect(start, end, right, bottom, left, bottom) || edgeIntersect(start, end, left, bottom, left, top);
5675
+ }
5676
+ function rangesOverlap3(a, b, min, max) {
5677
+ const lo = Math.min(a, b);
5678
+ const hi = Math.max(a, b);
5679
+ return hi > min && lo < max;
5680
+ }
5681
+ function edgeIntersect(start, end, x1, y1, x2, y2) {
5682
+ const denom = (end.x - start.x) * (y2 - y1) - (end.y - start.y) * (x2 - x1);
5683
+ if (denom === 0) return false;
5684
+ const t = ((start.x - x1) * (y2 - y1) - (start.y - y1) * (x2 - x1)) / denom;
5685
+ const u = ((start.x - x1) * (end.y - start.y) - (start.y - y1) * (end.x - start.x)) / denom;
5686
+ return t > 0 && t < 1 && u > 0 && u < 1;
5687
+ }
5688
+
5439
5689
  // src/solver/solve.ts
5440
5690
  var DEFAULT_MATRIX_CELL_SIZE2 = { width: 120, height: 36 };
5441
5691
  var DEFAULT_TABLE_CELL_SIZE2 = { width: 128, height: 34 };
@@ -8123,13 +8373,13 @@ function routeIntersectsTextBox(points, box) {
8123
8373
  if (start === void 0 || end === void 0) {
8124
8374
  continue;
8125
8375
  }
8126
- if (segmentIntersectsBox2(start, end, box)) {
8376
+ if (segmentIntersectsBox3(start, end, box)) {
8127
8377
  return true;
8128
8378
  }
8129
8379
  }
8130
8380
  return false;
8131
8381
  }
8132
- function segmentIntersectsBox2(start, end, box) {
8382
+ function segmentIntersectsBox3(start, end, box) {
8133
8383
  const left = box.x;
8134
8384
  const right = box.x + box.width;
8135
8385
  const top = box.y;
@@ -8138,17 +8388,17 @@ function segmentIntersectsBox2(start, end, box) {
8138
8388
  return true;
8139
8389
  }
8140
8390
  if (start.x === end.x) {
8141
- return start.x > left && start.x < right && rangesOverlap3(start.y, end.y, top, bottom);
8391
+ return start.x > left && start.x < right && rangesOverlap4(start.y, end.y, top, bottom);
8142
8392
  }
8143
8393
  if (start.y === end.y) {
8144
- return start.y > top && start.y < bottom && rangesOverlap3(start.x, end.x, left, right);
8394
+ return start.y > top && start.y < bottom && rangesOverlap4(start.x, end.x, left, right);
8145
8395
  }
8146
8396
  return segmentIntersectsBoxEdge2(start, end, left, top, right, top) || segmentIntersectsBoxEdge2(start, end, right, top, right, bottom) || segmentIntersectsBoxEdge2(start, end, right, bottom, left, bottom) || segmentIntersectsBoxEdge2(start, end, left, bottom, left, top);
8147
8397
  }
8148
8398
  function pointInsideBox2(point2, box) {
8149
8399
  return point2.x > box.x && point2.x < box.x + box.width && point2.y > box.y && point2.y < box.y + box.height;
8150
8400
  }
8151
- function rangesOverlap3(a, b, min, max) {
8401
+ function rangesOverlap4(a, b, min, max) {
8152
8402
  const low = Math.min(a, b);
8153
8403
  const high = Math.max(a, b);
8154
8404
  return high > min && low < max;
@@ -8511,6 +8761,30 @@ function groupReferenceMissing(groupId, referenceKind, id) {
8511
8761
  detail: id === void 0 ? { groupId } : { groupId, id }
8512
8762
  };
8513
8763
  }
8764
+ function createDefaultPipeline() {
8765
+ return new LayoutPipeline().addPhase({
8766
+ name: "solve-diagram",
8767
+ run(state) {
8768
+ const result = solveDiagram(state.diagram, state.options);
8769
+ state.diagnostics.push(...result.diagnostics);
8770
+ state.bounds = result.bounds;
8771
+ state.degraded = result.degraded ?? false;
8772
+ state.coordinatedNodes = result.nodes;
8773
+ state.coordinatedEdges = result.edges;
8774
+ }
8775
+ }).addPhase({
8776
+ name: "quality-score",
8777
+ run(state) {
8778
+ if (!state.options.qualityScore) return;
8779
+ const report = scoreLayoutQuality(
8780
+ state.coordinatedNodes,
8781
+ state.coordinatedEdges
8782
+ );
8783
+ state.qualityReport = report;
8784
+ state.diagnostics.push(...report.diagnostics);
8785
+ }
8786
+ });
8787
+ }
8514
8788
 
8515
8789
  // src/dsl/render.ts
8516
8790
  function resolveOutputFormat(cliFormat, dslFormat) {
@@ -8748,6 +9022,6 @@ function isPointLikeRecord(value) {
8748
9022
  return isPlainObject(value) && typeof value.x === "number" && typeof value.y === "number";
8749
9023
  }
8750
9024
 
8751
- export { DEFAULT_CANONICAL_PRECISION, DEFAULT_DSL_MAX_BYTES, DELIVERABILITY_DIAGNOSTIC_CODES, DeterministicTextMeasurer, LabelFitter, PretextTextMeasurer, applyLayoutConstraints, assertFiniteNonNegative, assertFinitePositive, boxCenter, canonicalize, computeArrowhead, computeContainerGeometry, computeShapeGeometry, createBoxSpatialIndex, createDefaultTextMeasurer, expandBox, expandBoxForQuery, exportExcalidraw, exportSvg, fitLabel, getEdgePort, installNodeCanvasRuntime, intersectsAabb, isPretextRuntimeAvailable, normalizeDiagramDsl, normalizeInsets, overlapArea, parseDiagramDsl, parseEdgeShorthand, queryBoxSpatialIndex, querySegmentSpatialIndex, renderDiagramDsl, resolveLineHeight, resolveOutputFormat, routeEdge, runComponentAwareDagreInitialLayout, runDagreInitialLayout, simplifyRoute2 as simplifyRoute, solveDiagram, solveDiagramSafe, sortDslDiagnostics, stringifyCanonical, toCanvasFont, unionBoxes, validateBox, validateTextStyle };
9025
+ export { DEFAULT_CANONICAL_PRECISION, DEFAULT_DSL_MAX_BYTES, DELIVERABILITY_DIAGNOSTIC_CODES, DeterministicTextMeasurer, LabelFitter, LayoutPipeline, PretextTextMeasurer, applyLayoutConstraints, assertFiniteNonNegative, assertFinitePositive, boxCenter, canonicalize, computeArrowhead, computeContainerGeometry, computeShapeGeometry, createBoxSpatialIndex, createDefaultPipeline, createDefaultTextMeasurer, expandBox, expandBoxForQuery, exportExcalidraw, exportSvg, fitLabel, getEdgePort, installNodeCanvasRuntime, intersectsAabb, isPretextRuntimeAvailable, normalizeDiagramDsl, normalizeInsets, overlapArea, parseDiagramDsl, parseEdgeShorthand, queryBoxSpatialIndex, querySegmentSpatialIndex, renderDiagramDsl, resolveLineHeight, resolveOutputFormat, routeEdge, runComponentAwareDagreInitialLayout, runDagreInitialLayout, simplifyRoute2 as simplifyRoute, solveDiagram, solveDiagramSafe, sortDslDiagnostics, stringifyCanonical, toCanvasFont, unionBoxes, validateBox, validateTextStyle };
8752
9026
  //# sourceMappingURL=index.js.map
8753
9027
  //# sourceMappingURL=index.js.map