From 2d5936488cfbd055a4bb9900581bdeb96138f652 Mon Sep 17 00:00:00 2001 From: phanex Date: Sun, 30 Aug 2026 11:52:35 +0200 Subject: [PATCH] feat(web): add fit-to-label maximize tool and label-proportional QR/barcode sizing --- .../canvas/elements/barcode-element.tsx | 34 ++++++++++--- .../inspector/sections/transform-section.tsx | 50 ++++++++++++++++++- packages/web/src/lib/keyboard.ts | 28 +++++++---- 3 files changed, 94 insertions(+), 18 deletions(-) diff --git a/packages/web/src/editor/canvas/elements/barcode-element.tsx b/packages/web/src/editor/canvas/elements/barcode-element.tsx index 8188f1e..05549ed 100644 --- a/packages/web/src/editor/canvas/elements/barcode-element.tsx +++ b/packages/web/src/editor/canvas/elements/barcode-element.tsx @@ -23,20 +23,42 @@ export function BarcodeElement({ element, isSelected }: Props) { }; const cacheKey = useMemo( - () => `${p.content || ""}|${p.format || "CODE128"}|${p.displayValue}`, - [p.content, p.format, p.displayValue], + () => `${p.content || ""}|${p.format || "CODE128"}|${p.displayValue}|${element.width}|${element.height}`, + [p.content, p.format, p.displayValue, element.width, element.height], ); const [image, setImage] = useState(null); useEffect(() => { try { + const showVal = p.displayValue ?? true; + const fontSz = showVal + ? Math.max(9, Math.min(18, Math.round(element.height * 0.22))) + : 0; + const barH = Math.max(12, Math.round(element.height - (showVal ? fontSz + 8 : 4))); + + // Measure natural module width using a 1px test render + const testCanvas = document.createElement("canvas"); + JsBarcode(testCanvas, p.content || "1234567890", { + format: p.format || "CODE128", + displayValue: false, + margin: 2, + width: 1, + height: 10, + }); + + const modules = testCanvas.width > 4 ? testCanvas.width - 4 : 100; + const targetBarWidth = Math.max(1, Math.min(6, (element.width - 6) / modules)); + const canvas = document.createElement("canvas"); - JsBarcode(canvas, p.content || "0000", { + JsBarcode(canvas, p.content || "1234567890", { format: p.format || "CODE128", - displayValue: p.displayValue ?? true, + displayValue: showVal, + fontSize: fontSz, + textMargin: 1, margin: 2, - height: 60, + width: targetBarWidth, + height: barH, background: "#ffffff", lineColor: "#000000", }); @@ -46,7 +68,7 @@ export function BarcodeElement({ element, isSelected }: Props) { } catch { // Invalid barcode content for format } - }, [cacheKey, p.content, p.format, p.displayValue]); + }, [cacheKey]); if (!image) { return ( diff --git a/packages/web/src/editor/inspector/sections/transform-section.tsx b/packages/web/src/editor/inspector/sections/transform-section.tsx index 0ba65ca..45757a9 100644 --- a/packages/web/src/editor/inspector/sections/transform-section.tsx +++ b/packages/web/src/editor/inspector/sections/transform-section.tsx @@ -1,6 +1,8 @@ import { AlignHorizontalJustifyCenter, AlignVerticalJustifyCenter, + Focus, + Maximize, } from "lucide-react"; import type { BaseElement } from "../../../store/editor-store.ts"; import { useEditorV2Store } from "../../../store/editor-store.ts"; @@ -27,6 +29,43 @@ export function TransformSection({ element }: Props) { y: Math.round((label.heightPx - element.height) / 2), }); + const fitToLabel = () => { + const p = element.props as { naturalWidth?: number; naturalHeight?: number }; + const marginFactor = 0.9; // 90% of label size (10% padding) + const maxW = label.widthPx * marginFactor; + const maxH = label.heightPx * marginFactor; + + let newW = maxW; + let newH = maxH; + + if (element.type === "qrcode") { + const size = Math.round(Math.min(maxW, maxH)); + newW = size; + newH = size; + } else if (p.naturalWidth && p.naturalHeight) { + const ratio = Math.min(maxW / p.naturalWidth, maxH / p.naturalHeight); + newW = Math.round(p.naturalWidth * ratio); + newH = Math.round(p.naturalHeight * ratio); + } else if (element.type === "barcode") { + newW = Math.round(maxW); + newH = Math.round(Math.min(maxH, maxW * 0.45)); + } else if (element.width && element.height) { + const ratio = Math.min(maxW / element.width, maxH / element.height); + newW = Math.round(element.width * ratio); + newH = Math.round(element.height * ratio); + } else { + newW = Math.round(maxW); + newH = Math.round(maxH); + } + + update({ + width: newW, + height: newH, + x: Math.round((label.widthPx - newW) / 2), + y: Math.round((label.heightPx - newH) / 2), + }); + }; + return (
@@ -64,10 +103,17 @@ export function TransformSection({ element }: Props) { +
diff --git a/packages/web/src/lib/keyboard.ts b/packages/web/src/lib/keyboard.ts index baf071c..69acad3 100644 --- a/packages/web/src/lib/keyboard.ts +++ b/packages/web/src/lib/keyboard.ts @@ -35,28 +35,36 @@ function addTextEl() { } function addQrEl() { - const { addElement } = useEditorV2Store.getState(); + const { label, addElement } = useEditorV2Store.getState(); + const maxDim = Math.round(Math.min(label.widthPx, label.heightPx) * 0.8); + const size = Math.max(24, Math.min(120, maxDim)); + const x = Math.round((label.widthPx - size) / 2); + const y = Math.round((label.heightPx - size) / 2); addElement({ id: uid(), type: "qrcode", - x: 20, - y: 20, - width: 120, - height: 120, + x, + y, + width: size, + height: size, rotation: 0, props: { content: "https://google.com", errorCorrectionLevel: "M" }, }); } function addBarcodeEl() { - const { addElement } = useEditorV2Store.getState(); + const { label, addElement } = useEditorV2Store.getState(); + const w = Math.round(Math.min(220, label.widthPx * 0.85)); + const h = Math.round(Math.min(80, label.heightPx * 0.7)); + const x = Math.round((label.widthPx - w) / 2); + const y = Math.round((label.heightPx - h) / 2); addElement({ id: uid(), type: "barcode", - x: 20, - y: 40, - width: 220, - height: 80, + x, + y, + width: w, + height: h, rotation: 0, props: { content: "1234567890", format: "CODE128", displayValue: true }, });