diff --git a/src/components/Canvas/hooks/useKonvaTransformer.ts b/src/components/Canvas/hooks/useKonvaTransformer.ts index 0007fc30..2f565157 100644 --- a/src/components/Canvas/hooks/useKonvaTransformer.ts +++ b/src/components/Canvas/hooks/useKonvaTransformer.ts @@ -190,7 +190,17 @@ export function useKonvaTransformer({ : ObjectRegistry[singleType]?.heightLocked ? [] : BARCODE_1D_TYPES.has(singleType) - ? ["top-center", "bottom-center"] + ? [ + "top-center", + "bottom-center", + // middle-left / middle-right drag the module-width axis. + // The bar count is fixed by content, so the resulting width + // is rounded to a valid ZPL ^BY moduleWidth (1..10) in + // commitBarcodeWidthHeightTransform on release; during the + // drag the bitmap stretches free-form for visual feedback. + "middle-left", + "middle-right", + ] : isUniformScale ? ["top-left", "top-right", "bottom-left", "bottom-right"] : undefined; @@ -307,9 +317,23 @@ export function useKonvaTransformer({ ); const renderedXDots = pxToDots(topLeft.x - objectsOffsetX, scale, dpmm); const renderedYDots = pxToDots(topLeft.y - labelOffsetY, scale, dpmm); + // For FT-anchored 1D barcodes, model.y is the bar baseline — needs the + // post-resize bar height to convert from the bbox top back. Pipe the + // scaled height through the same snap() commitBarcodeWidthHeight- + // Transform uses so the baseline math sees the *committed* value and + // there's no 1-dot drift between the two pathways. + const newBarHeightDots = + obj.positionType === "FT" && BARCODE_1D_TYPES.has(obj.type) + ? Math.max(1, snap(Math.round((obj.props as { height: number }).height * sy))) + : undefined; // Invert per-type render offsets (e.g. QR's hardcoded +10 dot Y) so the // stored model position matches what BarcodeObject.handleDragEnd produces. - const modelPos = modelPositionFromRenderedTopLeft(obj, renderedXDots, renderedYDots); + const modelPos = modelPositionFromRenderedTopLeft( + obj, + renderedXDots, + renderedYDots, + newBarHeightDots, + ); // Only apply snap when the resize actually moved the position // (e.g. dragging the top-left handle). Anchored-corner drags must keep // the original position so off-grid shapes don't snap as a side-effect. diff --git a/src/components/Canvas/transformPosition.ts b/src/components/Canvas/transformPosition.ts index 5855faba..08b30ffd 100644 --- a/src/components/Canvas/transformPosition.ts +++ b/src/components/Canvas/transformPosition.ts @@ -1,4 +1,4 @@ -import type { LabelObject } from "../../registry"; +import { BARCODE_1D_TYPES, type LabelObject } from "../../registry"; import { QR_FO_Y_OFFSET_DOTS } from "./bwipConstants"; /** @@ -9,20 +9,33 @@ import { QR_FO_Y_OFFSET_DOTS } from "./bwipConstants"; * Currently handles: * - QR (FO): subtracts the hardcoded +10 dot Y-offset that BarcodeObject adds * to compensate for Zebra firmware artifact. + * - 1D barcodes (FT): adds the new bar height back so obj.y stays on the + * bar baseline that ^FT anchors at. Without the correction every resize + * commits the bar TOP as the new obj.y and the next render shifts the + * barcode up by another bar-height. * * Used by onTransformEnd to mirror the rendered→model conversion that * BarcodeObject.handleDragEnd performs for drag. * - * Note: Field-Typeset (FT) corrections for barcode resize are not yet - * implemented here; FT-mode barcode resize still has known position drift. + * Note: QR-FT correction is not yet implemented (the additional firmware + * 3-module offset would need a separate code path); FT resize on QR still + * drifts. */ export function modelPositionFromRenderedTopLeft( obj: LabelObject, renderedXDots: number, renderedYDots: number, + newBarHeightDots?: number, ): { x: number; y: number } { if (obj.type === "qrcode" && obj.positionType !== "FT") { return { x: renderedXDots, y: renderedYDots - QR_FO_Y_OFFSET_DOTS }; } + if ( + obj.positionType === "FT" && + BARCODE_1D_TYPES.has(obj.type) && + newBarHeightDots !== undefined + ) { + return { x: renderedXDots, y: renderedYDots + newBarHeightDots }; + } return { x: renderedXDots, y: renderedYDots }; } diff --git a/src/registry/barcode1d.tsx b/src/registry/barcode1d.tsx index 1f9fcdd1..a04f960c 100644 --- a/src/registry/barcode1d.tsx +++ b/src/registry/barcode1d.tsx @@ -3,7 +3,7 @@ import { useT } from '../lib/useT'; import type { Translations } from '../locales'; import { inputCls, labelCls } from '../components/Properties/styles'; import { fieldPos, fdField } from './zplHelpers'; -import { commitHeightTransform } from './transformHelpers'; +import { commitBarcodeWidthHeightTransform } from './transformHelpers'; import { filterContent, type ContentSpec } from './contentSpec'; import { type ZplRotation } from './rotation'; import { RotationSelect } from '../components/Properties/RotationSelect'; @@ -71,7 +71,13 @@ export function createBarcode1D(config: Barcode1DConfig): ObjectTypeDefinition { // Normalize printInterpretation for symbologies that have no HRI in ZPL diff --git a/src/registry/transformHelpers.ts b/src/registry/transformHelpers.ts index f35a3527..1702e156 100644 --- a/src/registry/transformHelpers.ts +++ b/src/registry/transformHelpers.ts @@ -38,15 +38,23 @@ export function commitWidthHeightTransform

( } as Partial

; } -/** Shared commitTransform for 1D barcodes — only the bar height scales (width - * is determined by content + module width, not by the resize anchor). */ -export function commitHeightTransform

( +/** + * commitTransform for 1D barcodes. Vertical drag scales bar height (sy), + * horizontal drag scales the module width (sx) — the latter is clamped + * to the ZPL `^BY` range [1, 10]. During the drag the bitmap stretches + * free-form for visual feedback; this commit rounds the result to a + * valid integer moduleWidth on release. + */ +export function commitBarcodeWidthHeightTransform< + P extends { height: number; moduleWidth: number }, +>( obj: LabelObjectBase & { props: P }, ctx: TransformContext, ): Partial

{ - const { sy, snap } = ctx; + const { sx, sy, snap } = ctx; return { height: Math.max(1, snap(Math.round(obj.props.height * sy))), + moduleWidth: clamp(1, 10, Math.round(obj.props.moduleWidth * sx)), } as Partial

; }