From 364e6ead33ff2656f34932da6d9fa60e76f8e5d2 Mon Sep 17 00:00:00 2001 From: Mikei386 <44135113+Mikei386@users.noreply.github.com> Date: Mon, 22 Jun 2026 15:01:44 +0200 Subject: [PATCH] Align editor canvas text preview --- app/static/editor.js | 58 +++++++++++++++++++++++++++++++++++--------- 1 file changed, 47 insertions(+), 11 deletions(-) diff --git a/app/static/editor.js b/app/static/editor.js index 5f61b39..48c94f4 100644 --- a/app/static/editor.js +++ b/app/static/editor.js @@ -86,26 +86,62 @@ function pxToMm(v) { return v / scale(); } -function printablePxPerMm() { - const tape = Number(state.design.tapeWidthMm); - return (PRINTABLE_TAPE_PX[tape] || (tape * 180) / 25.4) / tape; -} - function inkColor() { return state.tapeInfo?.textColor?.css || "#000"; } +function objectFont(obj) { + const fontPx = mmToPx(obj.fontSizeMm || 5); + return { + size: fontPx, + value: `${obj.bold ? "700 " : ""}${fontPx}px DejaVu Sans, Arial, system-ui, sans-serif`, + }; +} + function clamp(value, min, max) { return Math.min(max, Math.max(min, value)); } +function wrapCanvasText(text, maxWidthPx) { + const lines = []; + for (const rawLine of String(text || "").split("\n")) { + let current = ""; + for (const word of rawLine.split(" ")) { + const candidate = current ? `${current} ${word}` : word; + if (ctx.measureText(candidate).width <= maxWidthPx) { + current = candidate; + continue; + } + if (current) lines.push(current); + current = word; + } + lines.push(current); + } + return lines.length ? lines : [""]; +} + +function objectContentEndMm(obj) { + if (obj.hidden) return 0; + if (state.design.orientation === "portrait") return obj.y + obj.h; + if (obj.type !== "text" && obj.type !== "symbol") return obj.x + obj.w; + + const { value } = objectFont(obj); + ctx.save(); + ctx.font = value; + const lines = wrapCanvasText(obj.text || "", mmToPx(obj.w)); + const textWidthMm = Math.max(...lines.map((line) => pxToMm(ctx.measureText(line).width)), 0); + ctx.restore(); + + if (obj.align === "center") return obj.x + obj.w / 2 + textWidthMm / 2; + if (obj.align === "right") return obj.x + obj.w; + return obj.x + Math.min(obj.w, textWidthMm); +} + function contentLengthMm() { if (!state.design.objects.length) return Number(controls.lengthMm.value) || state.design.lengthMm || 70; const margin = Number(controls.marginMm.value) || 0; const lengthEnd = state.design.objects.reduce((max, obj) => { - if (obj.hidden) return max; - const end = state.design.orientation === "portrait" ? obj.y + obj.h : obj.x + obj.w; - return Math.max(max, end); + return Math.max(max, objectContentEndMm(obj)); }, margin); return Math.ceil(clamp(lengthEnd + margin, 10, 500)); } @@ -193,11 +229,11 @@ function drawTextObject(obj) { ctx.rect(x, y, w, h); ctx.clip(); ctx.fillStyle = inkColor(); - const fontPx = (obj.fontSizeMm || 5) * printablePxPerMm() * state.zoom; - ctx.font = `${obj.bold ? "700 " : ""}${fontPx}px DejaVu Sans, Arial, system-ui, sans-serif`; + const { size: fontPx, value: font } = objectFont(obj); + ctx.font = font; ctx.textBaseline = "middle"; ctx.textAlign = obj.align || "left"; - const lines = String(obj.text || "").split("\n"); + const lines = wrapCanvasText(obj.text || "", w); const lineHeight = fontPx * 1.15; const total = lineHeight * lines.length; let cy = y + h / 2 - total / 2 + lineHeight / 2;