Skip to content

Commit ffeef1a

Browse files
committed
refactor(canvas): tighten ^GS code per second review
Five small follow-ups from the post-feature review: - `GS_VECTOR_CODES` set + `GsVectorCode` type replace the magic `'A' | 'B' | 'C'` cast at the canvas lookup. Adding D/E to the vector set later is a one-line change rather than a silently- broken cast. - `DEFAULT_GS_SYMBOL_META` switches from positional `GS_SYMBOLS[1]` to a `find()` on the canonical code so reordering the symbol table doesn't break the fallback. - Defensive `rel[p.rotation] ?? rel.N` in the sceneFunc handles malformed JSON imports with an unexpected rotation string without crashing the entire Konva layer. - `gsSymbolPaths.ts` (and the regen-from-cache template) now reuse `ZplRotation` from `registry/rotation.ts` instead of declaring a local duplicate `GsRotation`, and ship an AUTO-GENERATED header so the next maintainer doesn't hand-edit them. - Stale comment ("falls back to the © path") corrected to match the actual null-fallback that routes to the placeholder branch.
1 parent f9b09a6 commit ffeef1a

3 files changed

Lines changed: 30 additions & 9 deletions

File tree

‎src/components/Canvas/KonvaObject.tsx‎

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import { ZPL_FONT_HEIGHT_TO_CSS_RATIO } from "./textPositionTransforms";
1616
import { getTextRenderMetrics } from "./textRenderMetrics";
1717
import { selectionHandlers, type KonvaObjectProps } from "./konvaObjectProps";
1818
import { DEFAULT_GS_SYMBOL_META, GS_SYMBOLS } from "../../registry/symbol";
19-
import { GS_SYMBOL_PATHS } from "../../registry/gsSymbolPaths";
19+
import { GS_SYMBOL_PATHS, GS_VECTOR_CODES, type GsVectorCode } from "../../registry/gsSymbolPaths";
2020

2121
type Props = KonvaObjectProps;
2222

@@ -391,8 +391,11 @@ function KonvaObjectInner({
391391
// to Zebra firmware). D/E are trademarked UL/CSA logos we can't
392392
// ship — render a clearly-placeholder dashed gray box with the
393393
// letters so the user knows the printer will substitute the real
394-
// logo. Falls back to the © path if an unknown code slips through.
395-
const vectorPath = GS_SYMBOL_PATHS[p.symbol as 'A' | 'B' | 'C'] ?? null;
394+
// logo. Unknown codes (D/E or malformed import) take the same
395+
// placeholder branch via the null path here.
396+
const vectorPath = GS_VECTOR_CODES.has(p.symbol)
397+
? GS_SYMBOL_PATHS[p.symbol as GsVectorCode]
398+
: null;
396399
// The field bbox itself doesn't rotate — Zebra keeps the ^FO
397400
// anchor and (w, h) extent fixed and rotates only the glyph
398401
// inside. Skip the outer rotatedGroupTransform; sceneFunc bakes
@@ -427,7 +430,12 @@ function KonvaObjectInner({
427430
// inside the declared bbox, measured directly from
428431
// Labelary so Zebra's per-rotation anchor offsets match
429432
// pixel-for-pixel.
430-
const rel = vectorPath.rel[p.rotation];
433+
// Defensive ?? rel.N for the malformed-import case where
434+
// a JSON-restored object slips a non-N/R/I/B rotation
435+
// string past the type system; falling back to the
436+
// upright entry keeps the canvas from crashing on bad
437+
// data instead of throwing inside sceneFunc.
438+
const rel = vectorPath.rel[p.rotation] ?? vectorPath.rel.N;
431439
const glyphW = vectorPath.bbox.maxX - vectorPath.bbox.minX;
432440
const glyphH = vectorPath.bbox.maxY - vectorPath.bbox.minY;
433441
const renderW = w * rel.w;

‎src/registry/gsSymbolPaths.ts‎

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
11
/**
2+
* AUTO-GENERATED by scripts/regen_gs_from_cache.local.ts.
3+
* Do not edit by hand — re-run the generator after refreshing the
4+
* cached Labelary PNGs under tests/fixtures/labelary_images/_gs_*.
5+
*
26
* Vector paths for the ®/©/™ glyphs Zebra firmware renders via ^GS.
37
* Generated from cached Labelary renders at 8 dpmm. Each code carries
48
* the upright (N) vector plus per-rotation glyph rect within the
@@ -10,7 +14,7 @@
1014
* canvas shows a clearly-labelled placeholder instead.
1115
*/
1216

13-
export type GsRotation = 'N' | 'R' | 'I' | 'B';
17+
import type { ZplRotation } from './rotation';
1418

1519
export interface GsSymbolPath {
1620
readonly d: string;
@@ -19,12 +23,19 @@ export interface GsSymbolPath {
1923
* Canvas picks the entry matching the field's rotation and
2024
* draws the path at (userW * rel.x, userH * rel.y) sized
2125
* (userW * rel.w, userH * rel.h). */
22-
readonly rel: Readonly<Record<GsRotation, {
26+
readonly rel: Readonly<Record<ZplRotation, {
2327
readonly x: number; readonly y: number; readonly w: number; readonly h: number;
2428
}>>;
2529
}
2630

27-
export const GS_SYMBOL_PATHS: Readonly<Record<'A' | 'B' | 'C', GsSymbolPath>> = {
31+
/** Codes for which we ship a vector path. D/E intentionally absent
32+
* (trademarked UL/CSA logos rendered as a clearly-placeholder dashed
33+
* box). Renderers narrow via `GS_VECTOR_CODES.has(code)` instead of
34+
* casting through the literal union. */
35+
export type GsVectorCode = 'A' | 'B' | 'C';
36+
export const GS_VECTOR_CODES: ReadonlySet<string> = new Set<GsVectorCode>(['A', 'B', 'C']);
37+
38+
export const GS_SYMBOL_PATHS: Readonly<Record<GsVectorCode, GsSymbolPath>> = {
2839
A: {
2940
bbox: { minX: 49.602, minY: 50.673, maxX: 169.333, maxY: 170 },
3041
rel: {

‎src/registry/symbol.tsx‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,10 @@ export type SymbolCode = (typeof GS_SYMBOLS)[number]['code'];
2929
export const DEFAULT_GS_SYMBOL: SymbolCode = 'B';
3030
/** Resolved GS_SYMBOLS entry for the DEFAULT_GS_SYMBOL code. Renderers
3131
* use this as a typed fallback when `find` returns undefined, so they
32-
* can avoid non-null assertions on the lookup result. */
33-
export const DEFAULT_GS_SYMBOL_META = GS_SYMBOLS[1]; // 'B' = ©
32+
* can avoid non-null assertions on the lookup result. Looked up by
33+
* code so reordering GS_SYMBOLS doesn't silently break the fallback. */
34+
export const DEFAULT_GS_SYMBOL_META =
35+
GS_SYMBOLS.find((s) => s.code === DEFAULT_GS_SYMBOL) ?? GS_SYMBOLS[0];
3436

3537
/** Set of recognised ^GS codes — used by the parser to validate the
3638
* ^FD payload without re-listing the letters. Single source of truth

0 commit comments

Comments
 (0)