Spur label: clamp text right edge to trapezoid boundary

Measure actual text width and position so the right side of the text
aligns with the angled edge of the spur, preventing overflow onto
adjacent conveyors.

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

View File

@ -556,21 +556,19 @@ 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;
// Find optimal position: push text down where trapezoid is widest, // Compute text dimensions at 14px to find optimal placement
// then center horizontally in the available width at that height. ctx.font = 'bold 14px Arial';
// Trapezoid right edge at height y: w2 + (y/h) * (w - w2) const maxTextW = Math.max(...lines.map(l => ctx.measureText(l).width));
const fontSize = 14; const th = 14 * lines.length;
const lineCount = lines.length; // Push text down where trapezoid is wider, but leave 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); const optCy = Math.min(sym.h - th / 2 - 1, sym.h * 0.55);
// Right edge at the TOP of the text block (most constraining) // Right edge at top of text — text must not exceed this
const textTop = optCy - th / 2; const textTop = optCy - th / 2;
const rightEdgeAtTop = w2 + (Math.max(0, textTop) / sym.h) * (sym.w - w2); const rightEdge = w2 + (Math.max(0, textTop) / sym.h) * (sym.w - w2);
// Center text in the available width (0 to rightEdgeAtTop) // Place text so right side of text = rightEdge (hard clamp)
cx = sym.x + rightEdgeAtTop / 2; cx = sym.x + Math.min(rightEdge / 2, rightEdge - maxTextW / 2);
cy = sym.y + optCy; cy = sym.y + optCy;
availW = Infinity; // never shrink — position handles fit availW = Infinity;
availH = sym.h; availH = sym.h;
} else { } else {
cx = sym.x + sym.w / 2; cx = sym.x + sym.w / 2;

View File

@ -48,8 +48,10 @@ function emitConveyanceLabelInner(lines: string[], sym: PlacedSymbol) {
const th = fontSize * textLines.length; const th = fontSize * textLines.length;
const optCy = Math.min(sym.h - th / 2 - 1, sym.h * 0.55); const optCy = Math.min(sym.h - th / 2 - 1, sym.h * 0.55);
const textTop = optCy - th / 2; const textTop = optCy - th / 2;
const rightEdgeAtTop = w2 + (Math.max(0, textTop) / sym.h) * (sym.w - w2); const rightEdge = w2 + (Math.max(0, textTop) / sym.h) * (sym.w - w2);
labelCx = sym.x + rightEdgeAtTop / 2; // Estimate text width (~8px per char at 14px bold)
const estTextW = Math.max(...textLines.map(l => l.length * 8));
labelCx = sym.x + Math.min(rightEdge / 2, rightEdge - estTextW / 2);
labelCy = sym.y + optCy; labelCy = sym.y + optCy;
availH = sym.h; availH = sym.h;
} else if (isInductionType(sym.symbolId)) { } else if (isInductionType(sym.symbolId)) {