From 07ace0d3f4536abdf644ff7e7dc1d7f6a8f6b7f7 Mon Sep 17 00:00:00 2001 From: igurielidze Date: Mon, 30 Mar 2026 21:42:36 +0400 Subject: [PATCH] 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) --- svelte-app/src/lib/canvas/renderer.ts | 22 ++++++++++------------ svelte-app/src/lib/export.ts | 6 ++++-- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/svelte-app/src/lib/canvas/renderer.ts b/svelte-app/src/lib/canvas/renderer.ts index 9580fbd..d3eaab1 100644 --- a/svelte-app/src/lib/canvas/renderer.ts +++ b/svelte-app/src/lib/canvas/renderer.ts @@ -556,21 +556,19 @@ function drawConveyanceLabel(ctx: CanvasRenderingContext2D, sym: PlacedSymbol) { let cx: number, cy: number, availW: number, availH: number; if (isSpurType(sym.symbolId)) { const w2 = sym.w2 ?? sym.w; - // Find optimal position: push text down where trapezoid is widest, - // then center horizontally in the available width at that height. - // Trapezoid right edge at height y: w2 + (y/h) * (w - w2) - const fontSize = 14; - const lineCount = lines.length; - const th = fontSize * lineCount; - // Optimal cy: as low as possible (widest area), clamped to fit + // Compute text dimensions at 14px to find optimal placement + ctx.font = 'bold 14px Arial'; + const maxTextW = Math.max(...lines.map(l => ctx.measureText(l).width)); + const th = 14 * lines.length; + // Push text down where trapezoid is wider, but leave room 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 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; + const rightEdge = w2 + (Math.max(0, textTop) / sym.h) * (sym.w - w2); + // Place text so right side of text = rightEdge (hard clamp) + cx = sym.x + Math.min(rightEdge / 2, rightEdge - maxTextW / 2); cy = sym.y + optCy; - availW = Infinity; // never shrink — position handles fit + availW = Infinity; availH = sym.h; } else { cx = sym.x + sym.w / 2; diff --git a/svelte-app/src/lib/export.ts b/svelte-app/src/lib/export.ts index b917654..2398881 100644 --- a/svelte-app/src/lib/export.ts +++ b/svelte-app/src/lib/export.ts @@ -48,8 +48,10 @@ function emitConveyanceLabelInner(lines: string[], sym: PlacedSymbol) { const th = fontSize * textLines.length; 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; + const rightEdge = w2 + (Math.max(0, textTop) / sym.h) * (sym.w - w2); + // 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; availH = sym.h; } else if (isInductionType(sym.symbolId)) {