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)) {