Spur label: compute optimal position from trapezoid geometry

Position text at 55% height (toward wider area), then center
horizontally based on the actual right edge at the text's top,
minimizing overflow past the angled edge.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
igurielidze 2026-03-30 21:41:24 +04:00
parent f203556082
commit 31aea34361
2 changed files with 22 additions and 9 deletions

View File

@ -556,12 +556,21 @@ function drawConveyanceLabel(ctx: CanvasRenderingContext2D, sym: PlacedSymbol) {
let cx: number, cy: number, availW: number, availH: number; let cx: number, cy: number, availW: number, availH: number;
if (isSpurType(sym.symbolId)) { if (isSpurType(sym.symbolId)) {
const w2 = sym.w2 ?? sym.w; const w2 = sym.w2 ?? sym.w;
// The 90° corners are at x=0 (left edge). The rectangular region // Find optimal position: push text down where trapezoid is widest,
// from x=0 to x=min(w2,w) always fits inside the trapezoid. // then center horizontally in the available width at that height.
const safeW = Math.min(w2, sym.w); // Trapezoid right edge at height y: w2 + (y/h) * (w - w2)
cx = sym.x + safeW / 2; const fontSize = 14;
cy = sym.y + sym.h / 2; const lineCount = lines.length;
availW = Infinity; // never shrink font for spurs — enough visual room const th = fontSize * lineCount;
// Optimal cy: as low as possible (widest area), clamped to fit
const optCy = Math.min(sym.h - th / 2 - 1, sym.h * 0.55);
// Right edge at the TOP of the text block (most constraining)
const textTop = optCy - th / 2;
const rightEdgeAtTop = w2 + (Math.max(0, textTop) / sym.h) * (sym.w - w2);
// Center text in the available width (0 to rightEdgeAtTop)
cx = sym.x + rightEdgeAtTop / 2;
cy = sym.y + optCy;
availW = Infinity; // never shrink — position handles fit
availH = sym.h; availH = sym.h;
} else { } else {
cx = sym.x + sym.w / 2; cx = sym.x + sym.w / 2;

View File

@ -44,9 +44,13 @@ function emitConveyanceLabelInner(lines: string[], sym: PlacedSymbol) {
textRotDeg = (textRotRad * 180) / Math.PI; textRotDeg = (textRotRad * 180) / Math.PI;
} else if (isSpurType(sym.symbolId)) { } else if (isSpurType(sym.symbolId)) {
const w2 = sym.w2 ?? sym.w; const w2 = sym.w2 ?? sym.w;
const safeW = Math.min(w2, sym.w); const fontSize = 14;
labelCx = sym.x + safeW / 2; const th = fontSize * textLines.length;
labelCy = sym.y + sym.h / 2; const optCy = Math.min(sym.h - th / 2 - 1, sym.h * 0.55);
const textTop = optCy - th / 2;
const rightEdgeAtTop = w2 + (Math.max(0, textTop) / sym.h) * (sym.w - w2);
labelCx = sym.x + rightEdgeAtTop / 2;
labelCy = sym.y + optCy;
availH = sym.h; availH = sym.h;
} else if (isInductionType(sym.symbolId)) { } else if (isInductionType(sym.symbolId)) {
const stripTopY = sym.y + sym.h * INDUCTION_CONFIG.stripTopFrac; const stripTopY = sym.y + sym.h * INDUCTION_CONFIG.stripTopFrac;