Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 17 additions & 46 deletions src/components/Canvas/KonvaObject.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { ImageObject } from "./ImageObject";
import type Konva from "konva";
import { dotsToPx, pxToDots } from "../../lib/coordinates";
import { outlineInset } from "../../lib/shapeGeometry";
import { reverseShapeStyle } from "./reverseShapeStyle";
import { useColorScheme } from "../../lib/useColorScheme";
import { useLabelStore } from "../../store/labelStore";
import { ZPL_FONT_HEIGHT_TO_CSS_RATIO } from "./textPositionTransforms";
Expand Down Expand Up @@ -312,42 +313,15 @@ function KonvaObjectInner({
? cornerRadius
: Math.max(0, cornerRadius - strokeWidth / 2);

// Inverted (^LRY) regions print as a knockout. The difference-blend
// body renders print-correctly: on the white label it produces black
// (white-on-white inverted = black ink in print), and over darker
// shapes it inverts those pixels — matching what Zebra firmware
// actually prints. The body keeps that mode even while selected so
// the inversion visualisation doesn't disappear and hide whatever
// is layered behind. The selection outline is rendered as a separate
// overlay rect with normal blending.
//
// Special-cases:
// - reverse + filled drops the body stroke. Konva renders fill then
// stroke; with the difference blend the fill flips the destination
// to black and the (white) stroke then flips back to white inside
// the rect, producing a b/w/b banding artefact. The stroke and
// fill carry the same colour anyway so dropping it is visually
// identical without the artefact.
// - colour W filled (non-reverse) uses the light-grey shape colour
// for the fill too, otherwise white-on-white would make filled
// and outlined indistinguishable on canvas.
const isReverse = !!p.reverse;
const shapeColor = p.color === "B" ? "#000000" : "#cccccc";
// `renderFilled` includes the firmware clamp-to-solid case, so a
// very-thick outline picks the filled fill/stroke pair instead of
// collapsing into a degenerate inset rect.
const stroke = isReverse
? renderFilled
? "transparent"
: "#ffffff"
: shapeColor;
const fill = isReverse
? renderFilled
? "#ffffff"
: "transparent"
: renderFilled
? shapeColor
: "transparent";
// Inverted (^LRY) paint is delegated to reverseShapeStyle so box
// and ellipse share the same colour rules — the helper covers the
// stroke/fill swap for filled outlines (banding workaround) and
// the difference-blend that produces the print-correct knockout.
const { stroke, fill, globalCompositeOperation } = reverseShapeStyle(
p.reverse,
p.color,
renderFilled,
);
// Wrap body + selection overlay in a draggable Group so both move
// together during a drag — without this the selection-stroke rect
// stays at the start position while the body translates, leaving a
Expand Down Expand Up @@ -379,7 +353,7 @@ function KonvaObjectInner({
strokeScaleEnabled={false}
fill={fill}
cornerRadius={insetCornerRadius}
globalCompositeOperation={isReverse ? "difference" : "source-over"}
globalCompositeOperation={globalCompositeOperation}
/>
{isSelected && (
<SelectionOverlay
Expand All @@ -403,7 +377,6 @@ function KonvaObjectInner({
const h = dotsToPx(p.height, scale, dpmm);
const rx = w / 2;
const ry = h / 2;
const stroke = p.color === "B" ? "#000000" : "#cccccc";
const strokeWidth = Math.max(dotsToPx(p.thickness, scale, dpmm), 0.5);
// Option-A geometry — same outlineInset() definition as the box
// path so the firmware's clamp-to-solid rule stays consistent
Expand All @@ -412,14 +385,11 @@ function KonvaObjectInner({
const renderFilled = insetGeom.renderFilled;
const insetRx = insetGeom.width / 2;
const insetRy = insetGeom.height / 2;
const fill = renderFilled
? p.color === "B"
? "#000000"
: "#ffffff"
: "transparent";
// Group-at-top-left wrapper mirrors the box path: keeps the body's
// own stroke + thickness while a separate EllipseSelectionOverlay
// traces the outer bbox in the selection colour.
const { stroke, fill, globalCompositeOperation } = reverseShapeStyle(
p.reverse,
p.color,
renderFilled,
);
return (
<Group
id={obj.id}
Expand All @@ -439,6 +409,7 @@ function KonvaObjectInner({
strokeWidth={renderFilled ? 0 : strokeWidth}
strokeScaleEnabled={false}
fill={fill}
globalCompositeOperation={globalCompositeOperation}
/>
{isSelected && (
<EllipseSelectionOverlay rx={rx} ry={ry} color={colors.selection} />
Expand Down
47 changes: 47 additions & 0 deletions src/components/Canvas/reverseShapeStyle.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/**
* Per-shape paint values for the field-level `^LR` (reverse) state.
*
* Box and ellipse share the same paint rules — black/white knockout via
* a `difference` blend, with a fill/stroke swap so a filled outline
* doesn't band when the stroke flips to white and the fill flips back
* to black. The two render paths agreed in the editor by coincidence;
* pulling the colour table out of both call sites makes that agreement
* explicit and stops the two from drifting apart on the next colour
* tweak.
*
* Line has its own simpler variant (stroke-only, no fill) and stays
* separate.
*/

export interface ReverseShapeStyle {
stroke: string;
fill: string;
/** Konva's `globalCompositeOperation`. `difference` produces the
* print-correct knockout on the white label and inverts darker
* shapes underneath; `source-over` is the default upright paint. */
globalCompositeOperation: "difference" | "source-over";
}

export function reverseShapeStyle(
reverse: boolean | undefined,
color: "B" | "W",
renderFilled: boolean,
): ReverseShapeStyle {
const isReverse = !!reverse;
const shapeColor = color === "B" ? "#000000" : "#cccccc";
return {
stroke: isReverse
? renderFilled
? "transparent"
: "#ffffff"
: shapeColor,
fill: isReverse
? renderFilled
? "#ffffff"
: "transparent"
: renderFilled
? shapeColor
: "transparent",
globalCompositeOperation: isReverse ? "difference" : "source-over",
};
}
14 changes: 1 addition & 13 deletions src/components/Palette/ObjectPalette.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { useDraggable } from '@dnd-kit/core';
import { ObjectRegistry } from '../../registry';
import { PALETTE_GROUPS } from './paletteGroups';
import { VIRTUAL_PALETTE_ENTRIES, type VirtualPaletteEntry } from './virtualEntries';
import type { ObjectGroup } from '../../types/ObjectType';
import { useT } from '../../lib/useT';
import { useLabelStore } from '../../store/labelStore';
Expand Down Expand Up @@ -76,7 +75,7 @@ function resolveEntries(
group: ObjectGroup,
types: Record<string, string>,
): ResolvedEntry[] {
const registry = Object.entries(ObjectRegistry)
return Object.entries(ObjectRegistry)
.filter(([, def]) => def.group === group)
.map(([type, def]): ResolvedEntry => ({
id: type,
Expand All @@ -85,17 +84,6 @@ function resolveEntries(
label: types[type] ?? def.label,
defaultSize: def.defaultSize,
}));
const virtual = VIRTUAL_PALETTE_ENTRIES
.filter((v) => v.group === group)
.map((v: VirtualPaletteEntry): ResolvedEntry => ({
id: v.id,
type: v.type,
icon: v.icon,
label: types[v.labelKey] ?? v.fallbackLabel,
defaultSize: v.defaultSize,
propsOverride: v.propsOverride,
}));
return [...registry, ...virtual];
}

export function ObjectPalette() {
Expand Down
44 changes: 0 additions & 44 deletions src/components/Palette/virtualEntries.ts

This file was deleted.

2 changes: 2 additions & 0 deletions src/lib/zplParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1220,6 +1220,7 @@ export function parseZPL(zpl: string, dpmm = 8): ParsedZPL {
thickness: t,
filled,
color,
reverse: getReverseFlag(),
} satisfies EllipseProps,
undefined,
takeComment(),
Expand All @@ -1244,6 +1245,7 @@ export function parseZPL(zpl: string, dpmm = 8): ParsedZPL {
filled,
color,
lockAspect: true,
reverse: getReverseFlag(),
} satisfies EllipseProps,
undefined,
takeComment(),
Expand Down
3 changes: 2 additions & 1 deletion src/locales/ar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ const ar = {
datamatrix: 'DataMatrix',
box: 'مستطيل',
ellipse: 'قطع ناقص',
circle: 'دائرة',
line: 'خط',
serial: 'رقم تسلسلي',
image: 'صورة',
Expand Down Expand Up @@ -263,9 +262,11 @@ const ar = {
height: 'الارتفاع (نقطة)',
thickness: 'الحدود (نقطة)',
filled: 'ممتلئ',
lockAspect: 'دائرة (تثبيت نسبة العرض إلى الارتفاع)',
color: 'اللون',
colorB: 'B — أسود',
colorW: 'W — أبيض',
reverse: 'عكس',
},
circle: {
diameter: 'القطر (نقاط)',
Expand Down
3 changes: 2 additions & 1 deletion src/locales/bg.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ const bg = {
datamatrix: 'DataMatrix',
box: 'Правоъгълник',
ellipse: 'Елипса',
circle: 'Кръг',
line: 'Линия',
serial: 'Сериен №',
image: 'Изображение',
Expand Down Expand Up @@ -263,9 +262,11 @@ const bg = {
height: 'Височина (точки)',
thickness: 'Рамка (точки)',
filled: 'Запълнен',
lockAspect: 'Кръг (заключи съотношението)',
color: 'Цвят',
colorB: 'B — Черен',
colorW: 'W — Бял',
reverse: 'Обърни',
},
circle: {
diameter: 'Диаметър (точки)',
Expand Down
3 changes: 2 additions & 1 deletion src/locales/cs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ const cs = {
datamatrix: 'DataMatrix',
box: 'Obdélník',
ellipse: 'Elipsa',
circle: 'Kruh',
line: 'Čára',
serial: 'Sériové číslo',
image: 'Obrázek',
Expand Down Expand Up @@ -263,9 +262,11 @@ const cs = {
height: 'Výška (body)',
thickness: 'Rámeček (body)',
filled: 'Vyplněný',
lockAspect: 'Kruh (uzamknout poměr stran)',
color: 'Barva',
colorB: 'B — Černá',
colorW: 'W — Bílá',
reverse: 'Invertovat',
},
circle: {
diameter: 'Průměr (body)',
Expand Down
3 changes: 2 additions & 1 deletion src/locales/da.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ const da = {
datamatrix: 'DataMatrix',
box: 'Rektangel',
ellipse: 'Ellipse',
circle: 'Cirkel',
line: 'Linje',
serial: 'Serienr.',
image: 'Billede',
Expand Down Expand Up @@ -263,9 +262,11 @@ const da = {
height: 'Højde (punkter)',
thickness: 'Kant (punkter)',
filled: 'Udfyldt',
lockAspect: 'Cirkel (lås formatforhold)',
color: 'Farve',
colorB: 'B — Sort',
colorW: 'W — Hvid',
reverse: 'Invertere',
},
circle: {
diameter: 'Diameter (punkter)',
Expand Down
3 changes: 2 additions & 1 deletion src/locales/de.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ const de = {
datamatrix: 'DataMatrix',
box: 'Box',
ellipse: 'Ellipse',
circle: 'Kreis',
line: 'Linie',
serial: 'Seriennummer',
image: 'Bild',
Expand Down Expand Up @@ -284,9 +283,11 @@ const de = {
height: 'Höhe (Punkte)',
thickness: 'Rahmen (Punkte)',
filled: 'Gefüllt',
lockAspect: 'Kreis (Seitenverhältnis sperren)',
color: 'Farbe',
colorB: 'B — Schwarz',
colorW: 'W — Weiß',
reverse: 'Invertieren',
},
circle: {
diameter: 'Durchmesser (Punkte)',
Expand Down
3 changes: 2 additions & 1 deletion src/locales/el.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ const el = {
datamatrix: 'DataMatrix',
box: 'Ορθογώνιο',
ellipse: 'Έλλειψη',
circle: 'Κύκλος',
line: 'Γραμμή',
serial: 'Σειριακός αρ.',
image: 'Εικόνα',
Expand Down Expand Up @@ -263,9 +262,11 @@ const el = {
height: 'Ύψος (κουκκίδες)',
thickness: 'Πλαίσιο (κουκκίδες)',
filled: 'Γεμισμένο',
lockAspect: 'Κύκλος (κλείδωμα αναλογίας διαστάσεων)',
color: 'Χρώμα',
colorB: 'B — Μαύρο',
colorW: 'W — Λευκό',
reverse: 'Αντιστροφή',
},
circle: {
diameter: 'Διάμετρος (κουκκίδες)',
Expand Down
3 changes: 2 additions & 1 deletion src/locales/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ const en = {
datamatrix: 'DataMatrix',
box: 'Box',
ellipse: 'Ellipse',
circle: 'Circle',
line: 'Line',
serial: 'Serial',
image: 'Image',
Expand Down Expand Up @@ -284,9 +283,11 @@ const en = {
height: 'Height (dots)',
thickness: 'Border (dots)',
filled: 'Filled',
lockAspect: 'Circle (lock aspect ratio)',
color: 'Color',
colorB: 'B — Black',
colorW: 'W — White',
reverse: 'Invert',
},
circle: {
diameter: 'Diameter (dots)',
Expand Down
Loading