Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
183c787
feat(fonts): apply label-default font in canvas preview
u8array May 20, 2026
153d95e
refactor(fonts): extend schema for preview-only and embedded-font map…
u8array May 20, 2026
8fbd89f
feat(zpl): per-field ^A respects label.defaultFontId
u8array May 20, 2026
3273313
feat(zpl): parser pins ^A{id} on the field as fontId
u8array May 20, 2026
b3a9e04
feat(canvas): resolve per-field fontId for preview rendering
u8array May 20, 2026
b4c4d7a
feat(text): pick font via fontId dropdown, hide ^A@ behind Advanced
u8array May 20, 2026
0324bc3
refactor(fonts): extract stripDrivePrefix and export builtin-letters
u8array May 20, 2026
2c31c7f
feat(zpl): ship embedded fonts via ~DY for 1:1 Labelary parity
u8array May 20, 2026
4a72bd8
feat(fonts): embed-in-ZPL toggle per uploaded font
u8array May 20, 2026
64a52f7
feat(fonts): built-in preview bindings section
u8array May 20, 2026
ab24bf6
fix(fonts): UX polish for built-in alias warning and label dropdown
u8array May 20, 2026
b001565
feat(fonts): discoverability polish for built-in previews
u8array May 20, 2026
9bdf73e
fix(fonts): preserve extra mapping fields on alias / path edits
u8array May 20, 2026
d6d64e3
fix(fonts): empty-alias false positive and Tailwind cascade for warni…
u8array May 20, 2026
8da7e87
feat(fonts): auto-assign next free alias on upload
u8array May 20, 2026
f4fef4e
ux(fonts): clearer embed label, collapse-aware teaser, consistent delete
u8array May 20, 2026
2cea120
fix(fonts): manual-mapping bucket includes empty-path rows
u8array May 20, 2026
204a90e
ux(fonts): auto-open printer-resident section when entries exist
u8array May 20, 2026
70699ae
fix(fonts): manual-mapping delete targets a single row, not all empty…
u8array May 20, 2026
814f1a7
ux(fonts): drop redundant built-in-previews teaser
u8array May 20, 2026
fb575d6
fix(fonts): allow transient edits and clearing of preview bindings
u8array May 20, 2026
693d446
fix(zpl): address PR #78 review nits
u8array May 20, 2026
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
9 changes: 8 additions & 1 deletion src/components/Canvas/KonvaObject.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import type Konva from "konva";
import { dotsToPx, pxToDots } from "../../lib/coordinates";
import { outlineInset } from "../../lib/shapeGeometry";
import { useColorScheme } from "../../lib/useColorScheme";
import { useLabelStore } from "../../store/labelStore";
import { ZPL_FONT_HEIGHT_TO_CSS_RATIO } from "./textPositionTransforms";
import { getTextRenderMetrics } from "./textRenderMetrics";
import { selectionHandlers, type KonvaObjectProps } from "./konvaObjectProps";
Expand Down Expand Up @@ -146,13 +147,19 @@ function KonvaObjectInner({
}: Props) {
const fontVersion = useFontCacheVersion();
const colors = useColorScheme();
// Pass the whole label config so the metrics helper can resolve
// either a per-field `fontId` or the label-wide `defaultFontId` to
// an uploaded preview TTF. ZPL emit/parse intentionally call the
// metrics without `label`, so their ink-width stays PrintLab-ZPL
// based and the round-trip is unaffected.
const label = useLabelStore((s) => s.label);
// obj.x/y is the Konva render position (top-left of the EM bbox) —
// identical to what every other shape stores. The ZPL anchor (^FO
// cap-top / ^FT baseline) lives at obj.x/y + zplAnchorDelta and is
// applied only at the I/O boundary by zplGenerator / zplParser, so
// every in-editor interaction (drag, resize, snap, smart-align) sees
// a shape-agnostic single coordinate system.
const baseMetrics = getTextRenderMetrics(obj);
const baseMetrics = getTextRenderMetrics(obj, undefined, label);
const textMetrics =
baseMetrics && (obj.type === "text" || obj.type === "serial")
? {
Expand Down
39 changes: 34 additions & 5 deletions src/components/Canvas/textRenderMetrics.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { resolvePreviewFontName } from "../../lib/customFonts";
import { getFontFamily } from "../../lib/fontCache";
import type { LabelObject } from "../../types/Group";
import type { LabelConfig } from "../../types/ObjectType";
import { measureInkWidthPx } from "./measureTextDots";
import { ZPL_FONT_HEIGHT_TO_CSS_RATIO } from "./textPositionTransforms";

Expand All @@ -24,14 +26,21 @@ export interface TextMetricsInput {
fontHeight: number;
fontWidth: number;
printerFontName?: string;
/** Canvas-only fallback used when `printerFontName` is empty. Lets
* the renderer apply the label-wide default font (resolved from
* ^CW / ^CF) to text fields that did not pick their own printer
* font. Not passed by emit/parser, so the ZPL round-trip stays
* PrintLab-ZPL based. */
defaultPrinterFontName?: string;
}

/** Compute metrics from raw text parameters. Pure once
* `measureInkWidthPx` and `getFontFamily` are pure. */
export function computeTextRenderMetrics(input: TextMetricsInput): TextRenderMetrics {
const { content, fontHeight, fontWidth, printerFontName } = input;
const fontFamily = printerFontName
? (getFontFamily(printerFontName) ?? "'PrintLab ZPL', sans-serif")
const { content, fontHeight, fontWidth, printerFontName, defaultPrinterFontName } = input;
const effectiveFontName = printerFontName || defaultPrinterFontName;
const fontFamily = effectiveFontName
? (getFontFamily(effectiveFontName) ?? "'PrintLab ZPL', sans-serif")
: "'PrintLab ZPL', sans-serif";
const fontScaleX = fontWidth > 0 ? fontWidth / fontHeight : 1;
const inkWidthDots =
Expand All @@ -45,17 +54,37 @@ export function computeTextRenderMetrics(input: TextMetricsInput): TextRenderMet

/** Object-shaped wrapper used by the renderer and the resize commit
* path. `fontHeightOverride` lets the resize commit see the
* to-be-written fontHeight before it lands in obj.props. */
* to-be-written fontHeight before it lands in obj.props.
*
* `label` is the canvas-only context used to resolve preview fonts.
* Priority order matches the generator's `^A` priority:
* 1. text-level `fontId` → preview TTF for that alias
* 2. text-level `printerFontName` (legacy filename form)
* 3. label `defaultFontId` → preview TTF for the global default
* The emit path (`textFieldPos`) and the parser intentionally call
* this without `label`, so their ink-width measurements stay
* PrintLab-ZPL based and the ZPL round-trip is unaffected. */
export function getTextRenderMetrics(
obj: LabelObject,
fontHeightOverride?: number,
label?: Pick<LabelConfig, "customFonts" | "defaultFontId">,
): TextRenderMetrics | null {
if (obj.type !== "text" && obj.type !== "serial") return null;
const p = obj.props;
const fieldFontId = obj.type === "text" ? obj.props.fontId : undefined;
const fieldPrinterFontName =
obj.type === "text" ? obj.props.printerFontName : undefined;
const printerFontName = label
? (resolvePreviewFontName(label, fieldFontId) ?? fieldPrinterFontName)
: fieldPrinterFontName;
const defaultPrinterFontName = label
? resolvePreviewFontName(label, label.defaultFontId)
: undefined;
return computeTextRenderMetrics({
content: obj.type === "serial" ? `#${p.content}` : p.content,
fontHeight: fontHeightOverride ?? p.fontHeight,
fontWidth: p.fontWidth,
printerFontName: obj.type === "text" ? obj.props.printerFontName : undefined,
printerFontName,
defaultPrinterFontName,
});
}
Loading