Fix text readability: always right-side up on all conveyance types
Canvas renderer: - Straight/spur: flip text 180 when rotation is 91-269 deg - Mirror: counter-scale text so it doesn't read backwards - Curved: compute world angle (sym rotation + tangent), flip if upside-down SVG export: - Curved text now includes rotation transform along the arc tangent - Straight text includes rotation correction for readability - All text stays grouped with its shape for proper transform inheritance Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
ea367df42a
commit
533465be3c
@ -568,6 +568,14 @@ function drawConveyanceLabel(ctx: CanvasRenderingContext2D, sym: PlacedSymbol) {
|
||||
availH = sym.h - pad * 2;
|
||||
}
|
||||
|
||||
// Compute text correction so text is always readable:
|
||||
// - Counter-rotate if symbol rotation makes text upside-down (91-269 deg)
|
||||
// - Counter-mirror if symbol is mirrored (so text doesn't read backwards)
|
||||
const rot = ((sym.rotation || 0) % 360 + 360) % 360;
|
||||
const needsFlip = rot > 90 && rot < 270;
|
||||
const needsMirrorFix = sym.mirrored;
|
||||
const hasCorrection = needsFlip || needsMirrorFix;
|
||||
|
||||
ctx.fillStyle = '#000000';
|
||||
ctx.textAlign = 'center';
|
||||
ctx.textBaseline = 'middle';
|
||||
@ -590,9 +598,22 @@ function drawConveyanceLabel(ctx: CanvasRenderingContext2D, sym: PlacedSymbol) {
|
||||
if (fontSize >= 4) {
|
||||
ctx.font = `bold ${fontSize}px Arial`;
|
||||
const lineH = fontSize;
|
||||
const startY = cy - (lineCount - 1) * lineH / 2;
|
||||
for (let i = 0; i < tryLines.length; i++) {
|
||||
ctx.fillText(tryLines[i], cx, startY + i * lineH);
|
||||
|
||||
if (hasCorrection) {
|
||||
ctx.save();
|
||||
ctx.translate(cx, cy);
|
||||
if (needsFlip) ctx.rotate(Math.PI);
|
||||
if (needsMirrorFix) ctx.scale(-1, 1);
|
||||
for (let i = 0; i < tryLines.length; i++) {
|
||||
const dy = -(lineCount - 1) * lineH / 2 + i * lineH;
|
||||
ctx.fillText(tryLines[i], 0, dy);
|
||||
}
|
||||
ctx.restore();
|
||||
} else {
|
||||
const startY = cy - (lineCount - 1) * lineH / 2;
|
||||
for (let i = 0; i < tryLines.length; i++) {
|
||||
ctx.fillText(tryLines[i], cx, startY + i * lineH);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
@ -611,8 +632,19 @@ function drawCurvedLabel(ctx: CanvasRenderingContext2D, sym: PlacedSymbol) {
|
||||
// Position at arc midpoint
|
||||
const textX = arcCx + midR * Math.cos(midAngleRad);
|
||||
const textY = arcCy - midR * Math.sin(midAngleRad);
|
||||
// Rotation: perpendicular to radius = tangent to arc
|
||||
const textRot = -midAngleRad + Math.PI / 2;
|
||||
// Rotation: tangent to arc
|
||||
let textRot = -midAngleRad + Math.PI / 2;
|
||||
|
||||
// Add symbol rotation to check readability
|
||||
const symRotRad = ((sym.rotation || 0) * Math.PI) / 180;
|
||||
let worldAngle = (textRot + symRotRad) % (2 * Math.PI);
|
||||
if (worldAngle < 0) worldAngle += 2 * Math.PI;
|
||||
// Flip if text would be upside-down
|
||||
if (worldAngle > Math.PI / 2 && worldAngle < Math.PI * 3 / 2) {
|
||||
textRot += Math.PI;
|
||||
}
|
||||
// Handle mirror
|
||||
if (sym.mirrored) textRot = -textRot;
|
||||
|
||||
const availW = bandW - 4;
|
||||
|
||||
|
||||
@ -17,13 +17,13 @@ function parseConveyanceLabel(label: string): { lines: string[]; stripped: strin
|
||||
return { lines: [core], stripped: [core] };
|
||||
}
|
||||
|
||||
/** Emit conveyance label text inside a <g> — no transform needed (inherits from group) */
|
||||
/** Emit conveyance label text inside a <g> — inherits outer transform from group */
|
||||
function emitConveyanceLabelInner(lines: string[], sym: PlacedSymbol) {
|
||||
if (!sym.label) return;
|
||||
const { lines: textLines } = parseConveyanceLabel(sym.label);
|
||||
|
||||
// Compute label center position based on shape type
|
||||
let labelCx: number, labelCy: number, availH: number;
|
||||
let textRotDeg = 0; // additional rotation for the text
|
||||
|
||||
if (isCurvedType(sym.symbolId)) {
|
||||
const angle = sym.curveAngle || 90;
|
||||
@ -33,6 +33,15 @@ function emitConveyanceLabelInner(lines: string[], sym: PlacedSymbol) {
|
||||
labelCx = arcCx + midR * Math.cos(midAngleRad);
|
||||
labelCy = arcCy - midR * Math.sin(midAngleRad);
|
||||
availH = bandW - 4;
|
||||
// Tangent rotation (same as canvas renderer)
|
||||
let textRotRad = -midAngleRad + Math.PI / 2;
|
||||
// Check readability with outer rotation
|
||||
const symRotRad = ((sym.rotation || 0) * Math.PI) / 180;
|
||||
let worldAngle = (textRotRad + symRotRad) % (2 * Math.PI);
|
||||
if (worldAngle < 0) worldAngle += 2 * Math.PI;
|
||||
if (worldAngle > Math.PI / 2 && worldAngle < Math.PI * 3 / 2) textRotRad += Math.PI;
|
||||
if (sym.mirrored) textRotRad = -textRotRad;
|
||||
textRotDeg = (textRotRad * 180) / Math.PI;
|
||||
} else if (isSpurType(sym.symbolId)) {
|
||||
const w2 = sym.w2 ?? sym.w;
|
||||
labelCx = sym.x + (w2 + sym.w) / 4;
|
||||
@ -50,14 +59,22 @@ function emitConveyanceLabelInner(lines: string[], sym: PlacedSymbol) {
|
||||
availH = sym.h - 4;
|
||||
}
|
||||
|
||||
// For non-curved: check readability and flip if needed
|
||||
if (!isCurvedType(sym.symbolId)) {
|
||||
const rot = ((sym.rotation || 0) % 360 + 360) % 360;
|
||||
if (rot > 90 && rot < 270) textRotDeg = 180;
|
||||
// Mirror fix: text inside group inherits mirror, counter it
|
||||
if (sym.mirrored && textRotDeg === 0) textRotDeg = 0; // mirrored handled by scale in group
|
||||
}
|
||||
|
||||
const fontSize = Math.min(14, availH / textLines.length);
|
||||
if (fontSize < 4) return;
|
||||
const lineH = fontSize;
|
||||
// Emit each line at absolute position — y is baseline, offset by 0.35*fontSize to center visually
|
||||
const rotAttr = textRotDeg ? ` transform="rotate(${textRotDeg.toFixed(1)},${labelCx},${labelCy})"` : '';
|
||||
for (let i = 0; i < textLines.length; i++) {
|
||||
const dy = -(textLines.length - 1) * lineH / 2 + i * lineH;
|
||||
const y = labelCy + dy + fontSize * 0.35;
|
||||
lines.push(` <text x="${labelCx}" y="${y}" text-anchor="middle" font-family="Arial" font-weight="bold" font-size="${fontSize}px" fill="#000000">${textLines[i]}</text>`);
|
||||
lines.push(` <text x="${labelCx}" y="${y}" text-anchor="middle" font-family="Arial" font-weight="bold" font-size="${fontSize}px" fill="#000000"${rotAttr}>${textLines[i]}</text>`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user