From 183c787043eb55b31eb80ee33dcfa54b5bff3422 Mon Sep 17 00:00:00 2001 From: u8array Date: Wed, 20 May 2026 19:09:39 +0200 Subject: [PATCH 01/22] feat(fonts): apply label-default font in canvas preview When label.defaultFontId resolves to a ^CW alias whose mapping points at an uploaded TTF, text and serial fields without their own printerFontName now render in that font. Pure canvas concern: the ZPL emit/parse paths stay PrintLab-ZPL based so the round-trip is unaffected. --- src/components/Canvas/KonvaObject.tsx | 12 +++++- src/components/Canvas/textRenderMetrics.ts | 20 ++++++++-- src/lib/customFonts.test.ts | 44 ++++++++++++++++++++++ src/lib/customFonts.ts | 20 +++++++++- 4 files changed, 90 insertions(+), 6 deletions(-) diff --git a/src/components/Canvas/KonvaObject.tsx b/src/components/Canvas/KonvaObject.tsx index 80cd7ceb..1f2a8f2d 100644 --- a/src/components/Canvas/KonvaObject.tsx +++ b/src/components/Canvas/KonvaObject.tsx @@ -5,8 +5,10 @@ import { LineObject } from "./LineObject"; import { ImageObject } from "./ImageObject"; import type Konva from "konva"; import { dotsToPx, pxToDots } from "../../lib/coordinates"; +import { resolveDefaultPrinterFontName } from "../../lib/customFonts"; 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"; @@ -146,13 +148,21 @@ function KonvaObjectInner({ }: Props) { const fontVersion = useFontCacheVersion(); const colors = useColorScheme(); + // Canvas-only preview of the label-wide default font: if ^CF points + // at a ^CW alias whose path resolves to an uploaded font, text/serial + // fields without their own `printerFontName` render in that font. + // The ZPL emit/parse paths intentionally ignore this fallback so the + // round-trip stays PrintLab-ZPL based. + const defaultPrinterFontName = useLabelStore((s) => + resolveDefaultPrinterFontName(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, defaultPrinterFontName); const textMetrics = baseMetrics && (obj.type === "text" || obj.type === "serial") ? { diff --git a/src/components/Canvas/textRenderMetrics.ts b/src/components/Canvas/textRenderMetrics.ts index be89190a..eee4a6f2 100644 --- a/src/components/Canvas/textRenderMetrics.ts +++ b/src/components/Canvas/textRenderMetrics.ts @@ -24,14 +24,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 = @@ -45,10 +52,14 @@ 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. + * `defaultPrinterFontName` is the canvas-only fallback for text + * objects without their own `printerFontName`; see + * `TextMetricsInput.defaultPrinterFontName`. */ export function getTextRenderMetrics( obj: LabelObject, fontHeightOverride?: number, + defaultPrinterFontName?: string, ): TextRenderMetrics | null { if (obj.type !== "text" && obj.type !== "serial") return null; const p = obj.props; @@ -57,5 +68,6 @@ export function getTextRenderMetrics( fontHeight: fontHeightOverride ?? p.fontHeight, fontWidth: p.fontWidth, printerFontName: obj.type === "text" ? obj.props.printerFontName : undefined, + defaultPrinterFontName, }); } diff --git a/src/lib/customFonts.test.ts b/src/lib/customFonts.test.ts index 64750ca6..03534af7 100644 --- a/src/lib/customFonts.test.ts +++ b/src/lib/customFonts.test.ts @@ -2,6 +2,7 @@ import { describe, it, expect } from "vitest"; import { nextFreeAlias, normalizeAlias, + resolveDefaultPrinterFontName, upsertCustomFontMapping, } from "./customFonts"; @@ -91,3 +92,46 @@ describe("nextFreeAlias", () => { expect(nextFreeAlias(all)).toBe(""); }); }); + +describe("resolveDefaultPrinterFontName", () => { + it("returns the filename for a default alias that maps to a custom font", () => { + expect( + resolveDefaultPrinterFontName({ + defaultFontId: "M", + customFonts: [{ alias: "M", path: "E:MYFONT.TTF" }], + }), + ).toBe("MYFONT.TTF"); + }); + + it("strips any single-letter drive prefix, not just E:", () => { + expect( + resolveDefaultPrinterFontName({ + defaultFontId: "M", + customFonts: [{ alias: "M", path: "R:RAMFONT.TTF" }], + }), + ).toBe("RAMFONT.TTF"); + }); + + it("returns undefined for a built-in font id with no matching mapping", () => { + expect( + resolveDefaultPrinterFontName({ + defaultFontId: "0", + customFonts: [{ alias: "M", path: "E:MYFONT.TTF" }], + }), + ).toBeUndefined(); + }); + + it("returns undefined when defaultFontId is unset", () => { + expect( + resolveDefaultPrinterFontName({ + customFonts: [{ alias: "M", path: "E:MYFONT.TTF" }], + }), + ).toBeUndefined(); + }); + + it("returns undefined when customFonts is missing", () => { + expect( + resolveDefaultPrinterFontName({ defaultFontId: "M" }), + ).toBeUndefined(); + }); +}); diff --git a/src/lib/customFonts.ts b/src/lib/customFonts.ts index 07d1833d..ae52e0b0 100644 --- a/src/lib/customFonts.ts +++ b/src/lib/customFonts.ts @@ -1,4 +1,4 @@ -import type { CustomFontMapping } from "../types/ObjectType"; +import type { CustomFontMapping, LabelConfig } from "../types/ObjectType"; /** Characters NOT allowed in a ^CW alias. Used as a strip-pattern on * user input so a single source of truth feeds both UI surfaces (the @@ -65,6 +65,24 @@ const ALIAS_PREFERRED_ORDER = 'IJKLMNOPQRSTUVWXYZ123456789'; * range is exhausted; assigning one of them is a deliberate override * of the built-in font, which we avoid by default. Returns '' if all * 36 valid alias characters are in use. */ +/** Resolve `label.defaultFontId` to a printer font filename (the part + * after the drive prefix, e.g. "ARIAL.TTF"), using the ^CW mappings on + * the label. Returns undefined when the default font is a built-in + * Zebra font ID (0, A-H) or otherwise unmapped. The filename matches + * the `printerFontName` shape used by text props so it can drop + * straight into the canvas font-cache lookup. Used only by the canvas + * render path; emit and parser stay PrintLab ZPL-based to keep the + * round-trip stable. */ +export function resolveDefaultPrinterFontName( + label: Pick, +): string | undefined { + const id = label.defaultFontId; + if (!id) return undefined; + const entry = label.customFonts?.find((m) => m.alias === id); + if (!entry?.path) return undefined; + return entry.path.replace(/^[A-Z]:/, ""); +} + export function nextFreeAlias(taken: Iterable): string { const used = new Set(taken); for (const c of ALIAS_PREFERRED_ORDER) { From 153d95efe20ca379a42564bd17cf13730e226e80 Mon Sep 17 00:00:00 2001 From: u8array Date: Wed, 20 May 2026 19:17:54 +0200 Subject: [PATCH 02/22] refactor(fonts): extend schema for preview-only and embedded-font mappings customFontMappingSchema now accepts optional previewFontName (canvas-only TTF binding) and embedInZpl (~DY emit flag); path becomes optional so built-in font IDs (0, A-H) can carry a preview binding without inventing a fake printer path. TextProps gains fontId for the short ^A{id} form alongside the existing printerFontName for ^A@ filename references. New helpers isBuiltinFontId, resolvePreviewFontName, getAvailableFontIds back the upcoming UI dropdowns. resolveDefaultPrinterFontName is now a thin wrapper around resolvePreviewFontName so the canvas default-font fallback shares the resolution path with per-field lookups. No behaviour change yet: generator, parser and existing UI still operate on path-based mappings. --- src/components/Fonts/FontManager.tsx | 17 ++- src/components/Properties/PropertiesPanel.tsx | 2 +- src/lib/customFonts.test.ts | 109 ++++++++++++++++++ src/lib/customFonts.ts | 95 ++++++++++++--- src/lib/zplGenerator.ts | 2 +- src/registry/text.tsx | 10 +- src/types/ObjectType.ts | 42 ++++++- 7 files changed, 244 insertions(+), 33 deletions(-) diff --git a/src/components/Fonts/FontManager.tsx b/src/components/Fonts/FontManager.tsx index 6c0b72a0..8b28d782 100644 --- a/src/components/Fonts/FontManager.tsx +++ b/src/components/Fonts/FontManager.tsx @@ -41,6 +41,7 @@ export function FontManager() { const aliasByPath = new Map(); const manualMappings: CustomFontMapping[] = []; for (const m of customFonts ?? []) { + if (!m.path) continue; aliasByPath.set(m.path, m.alias); const isUploadedPath = m.path.startsWith(DEFAULT_FONT_DRIVE) && @@ -278,16 +279,20 @@ function ManualMappingsSection({

{hint}

{mappings.map((m) => { const dup = isDuplicateAlias(m.alias); + // Manual mappings always carry a path (preview-only entries + // live elsewhere). The empty string fallback only keeps TS happy + // and never reaches the user — the row is suppressed upstream. + const path = m.path ?? ''; // Key: stable across edits as long as alias and path don't // change at the same time. For fresh empty rows the auto- // assigned alias from nextFreeAlias is unique per row, which // gives a stable identity until the user types a path. - const rowKey = m.path || `__alias__${m.alias}`; + const rowKey = path || `__alias__${m.alias}`; return (
handleBlur(e, m.path, m.alias)} + onBlur={(e) => handleBlur(e, path, m.alias)} > onUpdate(m.path, { alias: e.target.value })} + onChange={(e) => onUpdate(path, { alias: e.target.value })} /> onUpdate(m.path, { path: e.target.value })} + value={path} + onChange={(e) => onUpdate(path, { path: e.target.value })} /> + {showAdvanced && ( +
+ { - const file = e.target.files?.[0]; - if (file) void handleFontUpload(file); - e.target.value = ""; - }} + type="text" + className={inputCls} + placeholder="ARIAL.TTF" + value={p.printerFontName ?? ""} + onChange={(e) => + onChange({ + printerFontName: e.target.value || undefined, + fontId: e.target.value ? undefined : p.fontId, + }) + } /> - - + {fontLoaded && ( + + {t.registry.text.fontLoaded} + + )} + {fontAssignedButMissing && ( + <> + + {t.registry.text.fontMissing} + + { + const file = e.target.files?.[0]; + if (file) void handleFontUpload(file); + e.target.value = ""; + }} + /> + + + )} +
)}
From 0324bc337f23a87a2949bf5c7c806b58f369e2ca Mon Sep 17 00:00:00 2001 From: u8array Date: Wed, 20 May 2026 20:03:51 +0200 Subject: [PATCH 07/22] refactor(fonts): extract stripDrivePrefix and export builtin-letters MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Pulls the duplicated /^[A-Z]:/ replace into stripDrivePrefix in customFonts.ts; PropertiesPanel and text picker now share the helper along with resolvePreviewFontName. Exports ZPL_BUILTIN_FONT_LETTERS so the parser's ^A handler can branch on the shared constant instead of re-typing '0ABCDEFGH'. Also collapses the font-dropdown labelText into a single template expression (id + suffix) — the previous nested ternary repeated the preview-name format across both built-in and custom branches. --- src/components/Properties/PropertiesPanel.tsx | 4 ++-- src/lib/customFonts.ts | 16 +++++++++++++-- src/lib/zplParser.ts | 6 +++++- src/registry/text.tsx | 20 +++++++++---------- 4 files changed, 31 insertions(+), 15 deletions(-) diff --git a/src/components/Properties/PropertiesPanel.tsx b/src/components/Properties/PropertiesPanel.tsx index 59497eaf..0d1fbd25 100644 --- a/src/components/Properties/PropertiesPanel.tsx +++ b/src/components/Properties/PropertiesPanel.tsx @@ -22,7 +22,7 @@ import { CollapsibleSection } from "../ui/CollapsibleSection"; import { AlignButtons } from "./AlignButtons"; import { inputCls, labelCls } from "./styles"; import type { LabelConfig } from "../../types/ObjectType"; -import { ZPL_BUILTIN_FONT_IDS } from "../../lib/customFonts"; +import { ZPL_BUILTIN_FONT_IDS, stripDrivePrefix } from "../../lib/customFonts"; interface PropertiesPanelProps { /** Imperative handle on the canvas — used for actions that need live render @@ -363,7 +363,7 @@ function LabelConfigPanel({ // emitted verbatim to ZPL. return { value: id, - label: path ? path.replace(/^[A-Z]:/, '') : undefined, + label: path ? stripDrivePrefix(path) : undefined, }; }); diff --git a/src/lib/customFonts.ts b/src/lib/customFonts.ts index 37a5793f..8567bb19 100644 --- a/src/lib/customFonts.ts +++ b/src/lib/customFonts.ts @@ -38,6 +38,15 @@ export function uploadedFontPath(name: string): string { return `${DEFAULT_FONT_DRIVE}${name}`; } +/** Drop the leading Zebra drive prefix (`E:`, `R:`, `A:`, `B:`) from a + * storage path, returning the bare filename. Used by every surface + * that shows a printer path to the user (font dropdowns, mapping + * rows) — the drive letter is implementation detail; the filename is + * what the user picked. */ +export function stripDrivePrefix(path: string): string { + return path.replace(/^[A-Z]:/, ""); +} + /** Normalise raw user input into a valid ^CW alias char (or empty * string if no usable character is present). */ export function normalizeAlias(raw: string): string { @@ -57,7 +66,10 @@ export function upsertCustomFontMapping( return [...withoutPath, { alias, path }]; } -const ZPL_BUILTIN_FONT_LETTERS = '0ABCDEFGH'; +/** The nine built-in Zebra font identifiers as a single string for + * fast `.includes()` checks. Kept lowercase-named (no `_IDS` suffix) + * because consumers iterate over it both as a list and as a set. */ +export const ZPL_BUILTIN_FONT_LETTERS = '0ABCDEFGH'; const ALIAS_PREFERRED_ORDER = 'IJKLMNOPQRSTUVWXYZ123456789'; /** True when the alias is a Zebra built-in font (0, A-H). Built-ins @@ -86,7 +98,7 @@ export function resolvePreviewFontName( const entry = label.customFonts?.find((m) => m.alias === fontId); if (!entry) return undefined; if (entry.previewFontName) return entry.previewFontName; - if (entry.path) return entry.path.replace(/^[A-Z]:/, ""); + if (entry.path) return stripDrivePrefix(entry.path); return undefined; } diff --git a/src/lib/zplParser.ts b/src/lib/zplParser.ts index f5a63e62..77443825 100644 --- a/src/lib/zplParser.ts +++ b/src/lib/zplParser.ts @@ -21,6 +21,7 @@ import type { AztecProps } from "../registry/aztec"; import type { MicroPdf417Props } from "../registry/micropdf417"; import type { CodablockProps } from "../registry/codablock"; import { putImage } from "./imageCache"; +import { ZPL_BUILTIN_FONT_LETTERS } from "./customFonts"; import { GS1_DATABAR_DEFAULT_SEGMENTS } from "./gs1"; export type ImportFindingKind = "partial" | "browserLimit" | "unknown"; @@ -1432,7 +1433,10 @@ export function parseZPL(zpl: string, dpmm = 8): ParsedZPL { } else { pendingFontId = fontChar; } - if (!fontAliases.has(fontChar) && !"0ABCDEFGH".includes(fontChar)) { + if ( + !fontAliases.has(fontChar) && + !ZPL_BUILTIN_FONT_LETTERS.includes(fontChar) + ) { partialCmds.add(`^${cmd}`); } continue; diff --git a/src/registry/text.tsx b/src/registry/text.tsx index b45cbd57..f2b47fe9 100644 --- a/src/registry/text.tsx +++ b/src/registry/text.tsx @@ -5,7 +5,7 @@ import { inputCls, labelCls } from "../components/Properties/styles"; import { textFieldPos, fdField, resolveFontCmd } from "./zplHelpers"; import { effectiveScale } from "./transformHelpers"; import { getFont, loadFontFile } from "../lib/fontCache"; -import { getAvailableFontIds } from "../lib/customFonts"; +import { getAvailableFontIds, stripDrivePrefix } from "../lib/customFonts"; import { useFontCacheVersion } from "../hooks/useFontCacheVersion"; import { useLabelStore } from "../store/labelStore"; import { RotationSelect } from "../components/Properties/RotationSelect"; @@ -131,17 +131,17 @@ export const text: ObjectTypeDefinition = { {fontIdOptions.map((opt) => { const previewName = - opt.previewFontName ?? opt.path?.replace(/^[A-Z]:/, ""); - const labelText = opt.builtin - ? previewName - ? `${opt.id} — ${previewName}` - : `${opt.id} ${t.registry.text.builtinSuffix}` - : previewName - ? `${opt.id} — ${previewName}` - : opt.id; + opt.previewFontName ?? + (opt.path ? stripDrivePrefix(opt.path) : undefined); + const suffix = previewName + ? ` — ${previewName}` + : opt.builtin + ? ` ${t.registry.text.builtinSuffix}` + : ""; return ( ); })} From 2c31c7f553ffcf46459ae2918a9a88e7518f3cae Mon Sep 17 00:00:00 2001 From: u8array Date: Wed, 20 May 2026 20:13:29 +0200 Subject: [PATCH 08/22] feat(zpl): ship embedded fonts via ~DY for 1:1 Labelary parity Generator emits ~DY{path},A,T,{size},,{hex} before ^XA for every customFonts mapping that has embedInZpl=true and matching cached bytes. ASCII-hex format is chosen over Z64 for simplicity (no CRC) and broad firmware/Labelary support; the payload size doubles but stays well inside the existing per-font 4 MB cap. Parser DY handler reverses the encoding: decodes hex back to bytes, registers the font in the cache via loadFontBytesSync (sync wrapper so the parser stays non-async), and records the path. A later ^CW for the same path picks up embedInZpl=true and previewFontName so the round-trip preserves the user's "ship the bytes" intent. fontCache gains getFontBytes / loadFontBytes / loadFontBytesSync via a shared registerBytes core. Only TTF/OTF / ASCII-hex ~DY are imported; non-supported payloads (Z64, compressed, non-font extensions) fall through to the existing browser-limit findings. --- src/lib/fontCache.ts | 69 ++++++++++++++++++++++++++ src/lib/zplGenerator.test.ts | 94 +++++++++++++++++++++++++++++++++++ src/lib/zplGenerator.ts | 45 ++++++++++++++++- src/lib/zplParser.ts | 96 +++++++++++++++++++++++++++++++++++- 4 files changed, 301 insertions(+), 3 deletions(-) diff --git a/src/lib/fontCache.ts b/src/lib/fontCache.ts index cfd85eb5..c1b06051 100644 --- a/src/lib/fontCache.ts +++ b/src/lib/fontCache.ts @@ -74,6 +74,75 @@ export function getAllFonts(): CachedFont[] { return [...cache.values()]; } +/** Return the raw TTF/OTF bytes for a cached font, or undefined when + * the font is unknown. Decoded on demand from the persisted data URL + * so the cache stores only one representation. Used by the `~DY` + * emitter to ship the bytes inside the ZPL stream — both the printer + * and Labelary then resolve the font without a separate upload. */ +export function getFontBytes(printerName: string): Uint8Array | undefined { + const entry = cache.get(printerName.toUpperCase()); + if (!entry) return undefined; + const commaIdx = entry.dataUrl.indexOf(","); + if (commaIdx < 0) return undefined; + const base64 = entry.dataUrl.slice(commaIdx + 1); + const binary = atob(base64); + const bytes = new Uint8Array(binary.length); + for (let i = 0; i < binary.length; i++) { + bytes[i] = binary.charCodeAt(i); + } + return bytes; +} + +/** Load raw TTF/OTF bytes into the cache. Mirrors `loadFontFile` but + * starts from a `Uint8Array` rather than a `File`, which is what the + * ZPL parser hands over after decoding a `~DY` payload. */ +export async function loadFontBytes( + bytes: Uint8Array, + printerName: string, +): Promise { + const entry = registerBytes(bytes, printerName); + await registerFontFace(entry); + notify(); + return entry; +} + +/** Synchronous counterpart of `loadFontBytes` used by the ZPL parser, + * which can't await per-token. Populates the cache immediately so + * subsequent measurement and emit calls see the font, and kicks off + * `FontFace.load()` in the background. The canvas re-renders on the + * next font-version tick once the FontFace resolves. */ +export function loadFontBytesSync( + bytes: Uint8Array, + printerName: string, +): CachedFont { + const entry = registerBytes(bytes, printerName); + void registerFontFace(entry).then(notify); + notify(); + return entry; +} + +function registerBytes(bytes: Uint8Array, printerName: string): CachedFont { + if (bytes.length > MAX_FONT_BYTES) { + throw new Error( + `Font too large: ${printerName} (${bytes.length} bytes, max ${MAX_FONT_BYTES})`, + ); + } + let binary = ""; + for (const b of bytes) binary += String.fromCharCode(b); + const dataUrl = `data:font/truetype;base64,${btoa(binary)}`; + const name = printerName.toUpperCase().replace(/[^A-Z0-9._]/g, "_"); + const fontFamily = printerNameToFamily(name); + const entry: CachedFont = { + id: crypto.randomUUID(), + name, + dataUrl, + fontFamily, + }; + cache.set(name, entry); + safeLocalStorageSet(LS_PREFIX + name, JSON.stringify(entry)); + return entry; +} + /** Load a TTF/OTF File into the cache under the given printer font name. * Rejects on non-TTF/OTF extension or oversized files. */ export async function loadFontFile(file: File, printerName: string): Promise { diff --git a/src/lib/zplGenerator.test.ts b/src/lib/zplGenerator.test.ts index 545bee05..82c9fcc6 100644 --- a/src/lib/zplGenerator.test.ts +++ b/src/lib/zplGenerator.test.ts @@ -219,6 +219,100 @@ describe('generateZPL — printer params', () => { ).not.toContain('^CW'); }); + it('emits ~DY before ^XA when embedInZpl is true and bytes are cached', async () => { + const { loadFontBytes, removeFont } = await import('./fontCache'); + // Tiny fake TTF — content does not need to be valid for the emit + // path to pick up the bytes; the formatter just hex-encodes them. + const bytes = new Uint8Array([0x00, 0x01, 0xff, 0xab]); + await loadFontBytes(bytes, 'EMBED.TTF'); + try { + const zpl = generateZPL( + { + ...BASE_LABEL, + customFonts: [ + { + alias: 'M', + path: 'E:EMBED.TTF', + previewFontName: 'EMBED.TTF', + embedInZpl: true, + }, + ], + }, + [], + ); + const dyIdx = zpl.indexOf('~DY'); + const xaIdx = zpl.indexOf('^XA'); + expect(dyIdx).toBeGreaterThanOrEqual(0); + expect(dyIdx).toBeLessThan(xaIdx); + // ~DYE:EMBED,A,T,4,,0001FFAB — stem strips the extension, ext code + // is T (TTF), bytes count is the original length, hex is uppercase. + expect(zpl).toContain('~DYE:EMBED,A,T,4,,0001FFAB'); + } finally { + removeFont('EMBED.TTF'); + } + }); + + it('skips ~DY when embedInZpl is false', () => { + const zpl = generateZPL( + { + ...BASE_LABEL, + customFonts: [ + { alias: 'M', path: 'E:X.TTF', previewFontName: 'X.TTF' }, + ], + }, + [], + ); + expect(zpl).not.toContain('~DY'); + }); + + it('round-trips embedInZpl: ~DY emit → ~DY parse preserves the flag', async () => { + const { loadFontBytes, removeFont } = await import('./fontCache'); + const bytes = new Uint8Array([0xab, 0xcd, 0xef, 0x12]); + await loadFontBytes(bytes, 'ROUND.TTF'); + try { + const zpl = generateZPL( + { + ...BASE_LABEL, + customFonts: [ + { + alias: 'M', + path: 'E:ROUND.TTF', + previewFontName: 'ROUND.TTF', + embedInZpl: true, + }, + ], + }, + [], + ); + const { labelConfig } = parseZPL(zpl, 8); + const m = labelConfig.customFonts?.[0]; + expect(m?.alias).toBe('M'); + expect(m?.path).toBe('E:ROUND.TTF'); + expect(m?.embedInZpl).toBe(true); + expect(m?.previewFontName).toBe('ROUND.TTF'); + } finally { + removeFont('ROUND.TTF'); + } + }); + + it('skips ~DY when bytes are not cached', () => { + const zpl = generateZPL( + { + ...BASE_LABEL, + customFonts: [ + { + alias: 'M', + path: 'E:MISSING.TTF', + previewFontName: 'MISSING.TTF', + embedInZpl: true, + }, + ], + }, + [], + ); + expect(zpl).not.toContain('~DY'); + }); + it('skips ^CW entries with empty alias or path', () => { const zpl = generateZPL( { diff --git a/src/lib/zplGenerator.ts b/src/lib/zplGenerator.ts index 65d82eff..f9ee6032 100644 --- a/src/lib/zplGenerator.ts +++ b/src/lib/zplGenerator.ts @@ -1,9 +1,42 @@ import { mmToDots } from './coordinates'; import { ObjectRegistry } from '../registry'; import { stripZplCommandChars } from '../registry/zplHelpers'; -import type { LabelConfig } from '../types/ObjectType'; +import type { CustomFontMapping, LabelConfig } from '../types/ObjectType'; import type { Page } from '../store/labelStore'; import { isGroup, type LabelObject } from '../types/Group'; +import { getFontBytes } from './fontCache'; + +/** Format a `~DY` line for one embedded font mapping. The Zebra `~DY` + * command syntax splits the path: `~DY{drive}:{name},{fmt},{ext}, + * {totalBytes},{bytesPerRow},{data}`. For TTF we always use ASCII hex + * (`A`/`T`) — universally supported across firmware revisions and + * Labelary, even though it doubles the payload. `bytesPerRow` is + * irrelevant for fonts, left empty. + * + * Returns undefined when the mapping doesn't qualify (no embed flag, + * no preview TTF, no path, or the font cache has no bytes for the + * referenced TTF). Skipping is preferred over throwing because a + * partial label is still useful — the printer-side path may already + * exist on the device. */ +function formatDownloadObject(m: CustomFontMapping): string | undefined { + if (!m.embedInZpl || !m.path || !m.previewFontName) return undefined; + const bytes = getFontBytes(m.previewFontName); + if (!bytes) return undefined; + // Split "E:NAME.TTF" → drive "E:", stem "NAME", ext "TTF" → ZPL ext code "T". + const colonIdx = m.path.indexOf(':'); + if (colonIdx < 0) return undefined; + const drive = m.path.slice(0, colonIdx + 1); + const filename = m.path.slice(colonIdx + 1); + const dotIdx = filename.lastIndexOf('.'); + const stem = dotIdx >= 0 ? filename.slice(0, dotIdx) : filename; + const ext = dotIdx >= 0 ? filename.slice(dotIdx + 1).toUpperCase() : ''; + // Only TTF/OTF are uploaded today; both map to extension code T + // (TrueType is the only TTF-family identifier in the spec). + if (ext !== 'TTF' && ext !== 'OTF') return undefined; + let hex = ''; + for (const b of bytes) hex += b.toString(16).padStart(2, '0').toUpperCase(); + return `~DY${drive}${stem},A,T,${bytes.length},,${hex}`; +} /** * Concatenates `generateZPL` output for every page. Each page becomes its own @@ -19,6 +52,16 @@ export function generateZPL(label: LabelConfig, objects: LabelObject[]): string const lines: string[] = []; + // ~DY ships embedded font bytes BEFORE the label block. Like ~SD it is + // a tilde-prefix immediate command; the printer (and Labelary) writes + // the file into storage and the following ^XA…^XZ resolves ^CW/^A + // against it. Without ~DY the alias would dangle on Labelary because + // the device has no other channel to receive the TTF. + for (const m of label.customFonts ?? []) { + const line = formatDownloadObject(m); + if (line) lines.push(line); + } + // ~SD is a tilde-prefix command that takes effect immediately on receipt, // independently of the label block. Emit it before ^XA so the darkness // change applies to the label that follows. diff --git a/src/lib/zplParser.ts b/src/lib/zplParser.ts index 77443825..3e6bb71d 100644 --- a/src/lib/zplParser.ts +++ b/src/lib/zplParser.ts @@ -1,4 +1,4 @@ -import type { LabelConfig } from "../types/ObjectType"; +import type { CustomFontMapping, LabelConfig } from "../types/ObjectType"; import { zplAnchorToModel } from "../components/Canvas/textPositionTransforms"; import { computeTextRenderMetrics } from "../components/Canvas/textRenderMetrics"; import type { LabelObject } from "../types/Group"; @@ -21,6 +21,7 @@ import type { AztecProps } from "../registry/aztec"; import type { MicroPdf417Props } from "../registry/micropdf417"; import type { CodablockProps } from "../registry/codablock"; import { putImage } from "./imageCache"; +import { loadFontBytesSync } from "./fontCache"; import { ZPL_BUILTIN_FONT_LETTERS } from "./customFonts"; import { GS1_DATABAR_DEFAULT_SEGMENTS } from "./gs1"; @@ -314,6 +315,12 @@ export function parseZPL(zpl: string, dpmm = 8): ParsedZPL { // as the parser walks the header, consulted on each ^A{X} encounter. const fontAliases = new Map(); + // Paths that arrived via ~DY in this stream. Used so a subsequent + // ^CW for the same path flips the mapping's `embedInZpl` flag — + // round-trip stability: emit then re-parse should preserve the + // user's "ship the bytes" intent. + const downloadedFontPaths = new Set(); + // ^FH state (field hex indicator) let fhActive = false; let fhDelimiter = "_"; @@ -1325,7 +1332,92 @@ export function parseZPL(zpl: string, dpmm = 8): ParsedZPL { const list = (labelConfig.customFonts ?? []).filter( (m) => m.alias !== alias, ); - labelConfig.customFonts = [...list, { alias, path }]; + const entry: CustomFontMapping = { alias, path }; + if (downloadedFontPaths.has(path)) { + // The bytes already shipped via ~DY earlier in the stream; + // surface that intent on the model so re-emit will ~DY again. + entry.embedInZpl = true; + // The fontCache key is the filename portion of the path + // (drive prefix stripped), matching how ~DY registers fonts. + const colonIdx = path.indexOf(":"); + const filename = colonIdx >= 0 ? path.slice(colonIdx + 1) : path; + if (filename) entry.previewFontName = filename; + } + labelConfig.customFonts = [...list, entry]; + }, + + // ── ~DY downloaded TrueType payload ───────────────────────────────────── + // ~DY{drive}:{name},{fmt},{ext},{size},{bpr},{data} + // Decodes ASCII hex (format 'A') TTF/OTF bytes into the font cache + // so the canvas can preview the embedded font without a separate + // upload. The path reconstruction (stem + extension code) round- + // trips the same form the generator emits. Non-TTF extensions and + // non-hex formats are left untouched and fall through to the + // browser-limit bucket so the user sees what was dropped. + DY(_p, rest) { + // Parse manually because the data segment can be hundreds of + // KB of hex; we want to avoid splitting that into the rest of + // the params array. Param layout up to and including bytes-per- + // row is fixed-arity, so we walk commas until we've found 5. + const c: number[] = []; + for (let i = 0; i < rest.length && c.length < 5; i++) { + if (rest[i] === ",") c.push(i); + } + if (c.length < 5) { + browserLimit.push(`~DY${rest}`); + return; + } + const [c0, c1, c2, c3, c4] = c; + if ( + c0 === undefined || + c1 === undefined || + c2 === undefined || + c3 === undefined || + c4 === undefined + ) { + browserLimit.push(`~DY${rest}`); + return; + } + const path = rest.slice(0, c0); + const fmt = rest.slice(c0 + 1, c1).toUpperCase(); + const extCode = rest.slice(c1 + 1, c2).toUpperCase(); + const size = parseInt(rest.slice(c2 + 1, c3), 10); + const data = rest.slice(c4 + 1); + // Only ASCII-hex TTF/OTF imports are supported. Z64 / compressed + // payloads need a CRC-checked decoder and stay out of scope. + if (fmt !== "A" || (extCode !== "T" && extCode !== "B")) { + browserLimit.push(`~DY${rest.slice(0, 80)}…`); + return; + } + if (!path || isNaN(size) || size <= 0 || data.length < size * 2) { + browserLimit.push(`~DY${rest.slice(0, 80)}…`); + return; + } + const bytes = new Uint8Array(size); + for (let i = 0; i < size; i++) { + const byteHex = data.substr(i * 2, 2); + const b = parseInt(byteHex, 16); + if (isNaN(b)) { + browserLimit.push(`~DY${rest.slice(0, 80)}…`); + return; + } + bytes[i] = b; + } + // Reconstruct the full filename with extension so the registered + // name matches what ^CW points at. Generator emits "{stem}" with + // the extension stripped, so we re-attach based on the code. + const ext = extCode === "T" ? ".TTF" : ".BIN"; + const filename = path.includes(".") + ? path.slice(path.lastIndexOf(":") + 1) + : `${path.slice(path.indexOf(":") + 1)}${ext}`; + const fullPath = path.includes(".") ? path : `${path}${ext}`; + try { + loadFontBytesSync(bytes, filename); + downloadedFontPaths.add(fullPath); + } catch { + // Oversized or otherwise unloadable — surface as browser-limit. + browserLimit.push(`~DY${path}`); + } }, // ── Browser-limit: printer-specific features ──────────────────────────── From 4a72bd8cc6f10af8bb3e44f3e6fab9c7b371f1e0 Mon Sep 17 00:00:00 2001 From: u8array Date: Wed, 20 May 2026 20:24:20 +0200 Subject: [PATCH 09/22] feat(fonts): embed-in-ZPL toggle per uploaded font MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit FontEntry rows for uploaded fonts gain a checkbox column that toggles embedInZpl on the matching ^CW mapping. Setting the alias on an uploaded font now also pins previewFontName to the cached TTF — both fields stayed loosely coupled before, but they describe the same binding (canvas + on-printer); pinning both gives the generator a single source of truth and lets the embed toggle skip the additional patching. The checkbox is disabled while no alias is assigned: ~DY without a matching ^CW would push bytes the printer can't reference. Tooltip spells out the ~DY effect so the user can decide whether to ship the font with every label or rely on the device-resident copy. --- src/components/Fonts/FontManager.tsx | 63 +++++++++++++++++++++++++++- src/locales/ar.ts | 2 + src/locales/bg.ts | 2 + src/locales/cs.ts | 2 + src/locales/da.ts | 2 + src/locales/de.ts | 2 + src/locales/el.ts | 2 + src/locales/en.ts | 2 + src/locales/es.ts | 2 + src/locales/et.ts | 2 + src/locales/fa.ts | 2 + src/locales/fi.ts | 2 + src/locales/fr.ts | 2 + src/locales/he.ts | 2 + src/locales/hr.ts | 2 + src/locales/hu.ts | 2 + src/locales/it.ts | 2 + src/locales/ja.ts | 2 + src/locales/ko.ts | 2 + src/locales/lt.ts | 2 + src/locales/lv.ts | 2 + src/locales/nl.ts | 2 + src/locales/no.ts | 2 + src/locales/pl.ts | 2 + src/locales/pt.ts | 2 + src/locales/ro.ts | 2 + src/locales/sk.ts | 2 + src/locales/sl.ts | 2 + src/locales/sr.ts | 2 + src/locales/sv.ts | 2 + src/locales/tr.ts | 2 + src/locales/zh-hans.ts | 2 + src/locales/zh-hant.ts | 2 + 33 files changed, 125 insertions(+), 2 deletions(-) diff --git a/src/components/Fonts/FontManager.tsx b/src/components/Fonts/FontManager.tsx index 8b28d782..ec05795d 100644 --- a/src/components/Fonts/FontManager.tsx +++ b/src/components/Fonts/FontManager.tsx @@ -61,8 +61,40 @@ export function FontManager() { }; const setAliasForPath = (path: string, rawAlias: string) => { + // For uploaded fonts the entry should also bind the local TTF for + // canvas preview: derive `previewFontName` from the path so the + // generator / parser / renderer all share one source of truth. + // upsertCustomFontMapping already handles the alias upsert; we + // augment the resulting entry with the preview-binding here. + const alias = normalizeAlias(rawAlias); + const next = upsertCustomFontMapping(customFonts, path, alias); + if (alias) { + const entry = next.find((m) => m.path === path); + if (entry && uploadedNames.has(path.slice(DEFAULT_FONT_DRIVE.length))) { + entry.previewFontName = path.slice(DEFAULT_FONT_DRIVE.length); + } + } + replaceList(next); + }; + + const toggleEmbedForPath = (path: string, embed: boolean) => { + const list = customFonts ?? []; replaceList( - upsertCustomFontMapping(customFonts, path, normalizeAlias(rawAlias)), + list.map((m) => + m.path === path + ? embed + ? { + ...m, + embedInZpl: true, + // ~DY needs the TTF bytes from fontCache; the upload + // row implies the binding, so pin previewFontName too + // (idempotent when already set). + previewFontName: + m.previewFontName ?? path.slice(DEFAULT_FONT_DRIVE.length), + } + : { ...m, embedInZpl: undefined } + : m, + ), ); }; @@ -114,13 +146,16 @@ export function FontManager() { {fonts.map((font) => { const path = uploadedFontPath(font.name); const alias = aliasByPath.get(path) ?? ''; + const entry = (customFonts ?? []).find((m) => m.path === path); return ( setAliasForPath(path, v)} + onEmbedChange={(v) => toggleEmbedForPath(path, v)} onRequestDelete={() => setPendingDelete(font.name)} /> ); @@ -184,7 +219,9 @@ interface FontEntryProps { name: string; alias: string; duplicate: boolean; + embedInZpl: boolean; onAliasChange: (next: string) => void; + onEmbedChange: (next: boolean) => void; onRequestDelete: () => void; } @@ -192,13 +229,20 @@ function FontEntry({ name, alias, duplicate, + embedInZpl, onAliasChange, + onEmbedChange, onRequestDelete, }: FontEntryProps) { const t = useT(); + // The embed toggle is only meaningful once an alias is in place — + // ~DY without a matching ^CW would dump bytes onto the printer that + // no field can reference. Disable + tooltip when alias is empty so + // the constraint is visible instead of silently failing at emit. + const embedDisabled = !alias; return ( -
+
onAliasChange(e.target.value)} /> + +
+ ); + })} + +
+ ); +} + // ── AddFontForm ──────────────────────────────────────────────────────────────── interface AddFontFormProps { diff --git a/src/locales/ar.ts b/src/locales/ar.ts index bae1804e..b11a309d 100644 --- a/src/locales/ar.ts +++ b/src/locales/ar.ts @@ -461,6 +461,10 @@ const ar = { manualMappingsHeading: 'خطوط الطابعة المقيمة', manualMappingsHint: 'إشارة إلى خطوط موجودة بالفعل على الطابعة لم يتم رفعها هنا.', addManualMapping: 'إضافة خط طابعة', + builtinPreviewsHeading: 'معاينات الخطوط المدمجة', + builtinPreviewsHint: 'اربط ملف TTF محلي بمعرف خط مدمج (0، A-H) لكي يعرض المحرر شكل هذا الخط فعليا. لا يتم إصداره إلى ZPL.', + addBuiltinPreview: 'ربط خط مدمج', + noPreviewFont: 'اختر خطًا…', }, zebraPrint: { heading: 'إرسال إلى طابعة Zebra', diff --git a/src/locales/bg.ts b/src/locales/bg.ts index a14a3f18..6d33dfc1 100644 --- a/src/locales/bg.ts +++ b/src/locales/bg.ts @@ -461,6 +461,10 @@ const bg = { manualMappingsHeading: 'Шрифтове в принтера', manualMappingsHint: 'Препратка към шрифтове, които вече са в принтера, но не са качени тук.', addManualMapping: 'Добави шрифт на принтера', + builtinPreviewsHeading: 'Прегледи на вградени шрифтове', + builtinPreviewsHint: 'Свържете локален TTF с вграден ID на шрифт (0, A-H), за да показва редакторът как изглежда този шрифт. Не се излъчва в ZPL.', + addBuiltinPreview: 'Свържи вграден шрифт', + noPreviewFont: 'Изберете шрифт…', }, zebraPrint: { heading: 'Изпрати към принтер Zebra', diff --git a/src/locales/cs.ts b/src/locales/cs.ts index 592c840d..dcb9bae1 100644 --- a/src/locales/cs.ts +++ b/src/locales/cs.ts @@ -461,6 +461,10 @@ const cs = { manualMappingsHeading: 'Písma uložená v tiskárně', manualMappingsHint: 'Odkaz na písma, která jsou již v tiskárně, ale zde nejsou nahrána.', addManualMapping: 'Přidat písmo tiskárny', + builtinPreviewsHeading: 'Náhledy vestavěných písem', + builtinPreviewsHint: 'Přiřaďte místní TTF k vestavěnému ID písma (0, A-H), aby editor ukazoval, jak písmo skutečně vypadá. Nevysílá se do ZPL.', + addBuiltinPreview: 'Přiřadit vestavěné písmo', + noPreviewFont: 'Vyberte písmo…', }, zebraPrint: { heading: 'Odeslat na tiskárnu Zebra', diff --git a/src/locales/da.ts b/src/locales/da.ts index 82569345..d8d7366f 100644 --- a/src/locales/da.ts +++ b/src/locales/da.ts @@ -461,6 +461,10 @@ const da = { manualMappingsHeading: 'Skrifttyper på printeren', manualMappingsHint: 'Reference til skrifttyper, der allerede er på printeren, men ikke uploadet her.', addManualMapping: 'Tilføj printerskrifttype', + builtinPreviewsHeading: 'Visning af indbyggede skrifttyper', + builtinPreviewsHint: 'Bind en lokal TTF til et indbygget skrift-ID (0, A-H), så editoren viser, hvordan skriften faktisk ser ud. Sendes ikke til ZPL.', + addBuiltinPreview: 'Bind indbygget skrifttype', + noPreviewFont: 'Vælg en skrifttype…', }, zebraPrint: { heading: 'Send til Zebra-printer', diff --git a/src/locales/de.ts b/src/locales/de.ts index 291ba0e8..983b451d 100644 --- a/src/locales/de.ts +++ b/src/locales/de.ts @@ -482,6 +482,10 @@ const de = { manualMappingsHeading: 'Schriften auf dem Drucker', manualMappingsHint: 'Schriften referenzieren, die schon auf dem Drucker liegen, aber hier nicht hochgeladen sind.', addManualMapping: 'Drucker-Schrift hinzufügen', + builtinPreviewsHeading: 'Vorschau für Drucker-Standardschriften', + builtinPreviewsHint: 'Bindet eine lokale TTF an eine eingebaute Schrift-ID (0, A-H), damit der Editor zeigt, wie diese Schrift wirklich aussieht. Wird nicht ins ZPL emittiert.', + addBuiltinPreview: 'Eingebaute Schrift binden', + noPreviewFont: 'Schrift wählen …', }, } as const; diff --git a/src/locales/el.ts b/src/locales/el.ts index abdc4de7..dc583ef9 100644 --- a/src/locales/el.ts +++ b/src/locales/el.ts @@ -461,6 +461,10 @@ const el = { manualMappingsHeading: 'Γραμματοσειρές στον εκτυπωτή', manualMappingsHint: 'Αναφορά σε γραμματοσειρές που βρίσκονται ήδη στον εκτυπωτή αλλά δεν έχουν μεταφορτωθεί εδώ.', addManualMapping: 'Προσθήκη γραμματοσειράς εκτυπωτή', + builtinPreviewsHeading: 'Προεπισκοπήσεις ενσωματωμένων γραμματοσειρών', + builtinPreviewsHint: 'Συνδέστε ένα τοπικό TTF με ένα ενσωματωμένο ID γραμματοσειράς (0, A-H) ώστε ο επεξεργαστής να δείχνει πώς φαίνεται πραγματικά αυτή η γραμματοσειρά. Δεν εκπέμπεται στο ZPL.', + addBuiltinPreview: 'Σύνδεση ενσωματωμένης γραμματοσειράς', + noPreviewFont: 'Επιλέξτε γραμματοσειρά…', }, zebraPrint: { heading: 'Αποστολή σε εκτυπωτή Zebra', diff --git a/src/locales/en.ts b/src/locales/en.ts index 1f66f976..1cd45300 100644 --- a/src/locales/en.ts +++ b/src/locales/en.ts @@ -482,6 +482,10 @@ const en = { manualMappingsHeading: 'Printer-resident fonts', manualMappingsHint: 'Reference fonts already on the printer that are not uploaded here.', addManualMapping: 'Add printer font', + builtinPreviewsHeading: 'Built-in font previews', + builtinPreviewsHint: 'Bind a local TTF to a built-in font ID (0, A-H) so the editor shows what that font actually looks like. Not emitted to ZPL.', + addBuiltinPreview: 'Bind built-in font', + noPreviewFont: 'Pick a font…', }, } as const; diff --git a/src/locales/es.ts b/src/locales/es.ts index 2421817e..5b6c893b 100644 --- a/src/locales/es.ts +++ b/src/locales/es.ts @@ -461,6 +461,10 @@ const es = { manualMappingsHeading: 'Fuentes residentes en la impresora', manualMappingsHint: 'Referencia a fuentes ya presentes en la impresora pero no cargadas aquí.', addManualMapping: 'Añadir fuente de impresora', + builtinPreviewsHeading: 'Previsualizaciones de fuentes integradas', + builtinPreviewsHint: 'Asocia un TTF local a un ID de fuente integrada (0, A-H) para que el editor muestre cómo se ve realmente esa fuente. No se emite a ZPL.', + addBuiltinPreview: 'Vincular fuente integrada', + noPreviewFont: 'Elige una fuente…', }, zebraPrint: { heading: 'Enviar a impresora Zebra', diff --git a/src/locales/et.ts b/src/locales/et.ts index 17342b90..a68b08a7 100644 --- a/src/locales/et.ts +++ b/src/locales/et.ts @@ -461,6 +461,10 @@ const et = { manualMappingsHeading: 'Printeri fondid', manualMappingsHint: 'Viide printeris juba olevatele fontidele, mida pole siia üles laaditud.', addManualMapping: 'Lisa printeri font', + builtinPreviewsHeading: 'Sisseehitatud fontide eelvaated', + builtinPreviewsHint: 'Seo kohalik TTF sisseehitatud fondi ID-ga (0, A-H), et redaktor näitaks, milline see font tegelikult välja näeb. Ei edastata ZPL-i.', + addBuiltinPreview: 'Seo sisseehitatud font', + noPreviewFont: 'Vali font…', }, zebraPrint: { heading: 'Saada Zebra printerile', diff --git a/src/locales/fa.ts b/src/locales/fa.ts index 4a6aef4b..46654212 100644 --- a/src/locales/fa.ts +++ b/src/locales/fa.ts @@ -461,6 +461,10 @@ const fa = { manualMappingsHeading: 'فونت‌های موجود در چاپگر', manualMappingsHint: 'ارجاع به فونت‌هایی که از قبل روی چاپگر هستند و اینجا بارگذاری نشده‌اند.', addManualMapping: 'افزودن فونت چاپگر', + builtinPreviewsHeading: 'پیش‌نمایش قلم‌های داخلی', + builtinPreviewsHint: 'یک فایل TTF محلی را به یک شناسه قلم داخلی (0، A-H) متصل کنید تا ویرایشگر شکل واقعی آن قلم را نشان دهد. به ZPL منتشر نمی‌شود.', + addBuiltinPreview: 'اتصال قلم داخلی', + noPreviewFont: 'یک قلم انتخاب کنید…', }, zebraPrint: { heading: 'ارسال به چاپگر Zebra', diff --git a/src/locales/fi.ts b/src/locales/fi.ts index 7f2fd404..f7b1592d 100644 --- a/src/locales/fi.ts +++ b/src/locales/fi.ts @@ -461,6 +461,10 @@ const fi = { manualMappingsHeading: 'Fontit tulostimessa', manualMappingsHint: 'Viittaa fontteihin, jotka ovat jo tulostimessa mutta joita ei ole ladattu tänne.', addManualMapping: 'Lisää tulostimen fontti', + builtinPreviewsHeading: 'Sisäänrakennettujen fonttien esikatselut', + builtinPreviewsHint: 'Sido paikallinen TTF sisäänrakennettuun fontti-ID:hen (0, A-H), jotta editori näyttää miltä fontti todella näyttää. Ei lähetetä ZPL:ään.', + addBuiltinPreview: 'Sido sisäänrakennettu fontti', + noPreviewFont: 'Valitse fontti…', }, zebraPrint: { heading: 'Lähetä Zebra-tulostimelle', diff --git a/src/locales/fr.ts b/src/locales/fr.ts index 600d1dcb..6f0347bc 100644 --- a/src/locales/fr.ts +++ b/src/locales/fr.ts @@ -461,6 +461,10 @@ const fr = { manualMappingsHeading: 'Polices résidentes', manualMappingsHint: 'Référencer des polices déjà présentes sur l\'imprimante mais non téléversées ici.', addManualMapping: 'Ajouter une police d\'imprimante', + builtinPreviewsHeading: 'Aperçus des polices intégrées', + builtinPreviewsHint: 'Lie un TTF local à un ID de police intégrée (0, A-H) pour que l\'éditeur montre à quoi ressemble vraiment cette police. Non émis dans ZPL.', + addBuiltinPreview: 'Lier police intégrée', + noPreviewFont: 'Choisir une police…', }, zebraPrint: { heading: 'Envoyer à l’imprimante Zebra', diff --git a/src/locales/he.ts b/src/locales/he.ts index 91085ba2..f02097f6 100644 --- a/src/locales/he.ts +++ b/src/locales/he.ts @@ -461,6 +461,10 @@ const he = { manualMappingsHeading: 'גופנים השמורים במדפסת', manualMappingsHint: 'התייחסות לגופנים שכבר נמצאים במדפסת ולא הועלו כאן.', addManualMapping: 'הוסף גופן מדפסת', + builtinPreviewsHeading: 'תצוגות מקדימות של גופנים מובנים', + builtinPreviewsHint: 'קשר קובץ TTF מקומי לזיהוי גופן מובנה (0, A-H) כדי שהעורך יציג כיצד הגופן באמת נראה. לא נפלט ל-ZPL.', + addBuiltinPreview: 'קשר גופן מובנה', + noPreviewFont: 'בחר גופן…', }, zebraPrint: { heading: 'שלח למדפסת Zebra', diff --git a/src/locales/hr.ts b/src/locales/hr.ts index 710d5e70..3b5a9e18 100644 --- a/src/locales/hr.ts +++ b/src/locales/hr.ts @@ -461,6 +461,10 @@ const hr = { manualMappingsHeading: 'Fontovi na pisaču', manualMappingsHint: 'Referenca na fontove koji se već nalaze na pisaču, ali nisu prenijeti ovdje.', addManualMapping: 'Dodaj font pisača', + builtinPreviewsHeading: 'Pregledi ugrađenih fontova', + builtinPreviewsHint: 'Povežite lokalni TTF s ugrađenim ID-om fonta (0, A-H) tako da uređivač prikazuje kako taj font zaista izgleda. Ne emitira se u ZPL.', + addBuiltinPreview: 'Poveži ugrađeni font', + noPreviewFont: 'Odaberite font…', }, zebraPrint: { heading: 'Pošalji na Zebra pisač', diff --git a/src/locales/hu.ts b/src/locales/hu.ts index 13e1cb8e..253e8a0e 100644 --- a/src/locales/hu.ts +++ b/src/locales/hu.ts @@ -461,6 +461,10 @@ const hu = { manualMappingsHeading: 'Betűtípusok a nyomtatón', manualMappingsHint: 'Hivatkozás a nyomtatón már lévő, itt fel nem töltött betűtípusokra.', addManualMapping: 'Nyomtató betűtípus hozzáadása', + builtinPreviewsHeading: 'Beépített betűtípus előnézetek', + builtinPreviewsHint: 'Köss össze egy helyi TTF-et egy beépített betűtípus-azonosítóval (0, A-H), hogy a szerkesztő mutassa, hogyan néz ki valójában az a betűtípus. Nem kerül ZPL-be.', + addBuiltinPreview: 'Beépített betűtípus kötése', + noPreviewFont: 'Válassz betűtípust…', }, zebraPrint: { heading: 'Küldés Zebra nyomtatóra', diff --git a/src/locales/it.ts b/src/locales/it.ts index 991cfd16..f1728f16 100644 --- a/src/locales/it.ts +++ b/src/locales/it.ts @@ -461,6 +461,10 @@ const it = { manualMappingsHeading: 'Font residenti sulla stampante', manualMappingsHint: 'Riferimento a font già presenti sulla stampante ma non caricati qui.', addManualMapping: 'Aggiungi font della stampante', + builtinPreviewsHeading: 'Anteprime caratteri integrati', + builtinPreviewsHint: 'Associa un TTF locale a un ID di carattere integrato (0, A-H) in modo che l\'editor mostri come appare realmente quel carattere. Non emesso in ZPL.', + addBuiltinPreview: 'Associa carattere integrato', + noPreviewFont: 'Scegli un carattere…', }, zebraPrint: { heading: 'Invia a stampante Zebra', diff --git a/src/locales/ja.ts b/src/locales/ja.ts index 949ada5d..2ad1e32b 100644 --- a/src/locales/ja.ts +++ b/src/locales/ja.ts @@ -461,6 +461,10 @@ const ja = { manualMappingsHeading: 'プリンター内蔵フォント', manualMappingsHint: 'プリンターに既にあるがここにアップロードされていないフォントを参照します。', addManualMapping: 'プリンターフォントを追加', + builtinPreviewsHeading: '内蔵フォントのプレビュー', + builtinPreviewsHint: 'ローカルTTFを内蔵フォントID(0、A-H)にバインドして、エディタでそのフォントの実際の見た目を表示します。ZPLには出力されません。', + addBuiltinPreview: '内蔵フォントをバインド', + noPreviewFont: 'フォントを選択…', }, zebraPrint: { heading: 'Zebra プリンターへ送信', diff --git a/src/locales/ko.ts b/src/locales/ko.ts index 9992d94c..4732a067 100644 --- a/src/locales/ko.ts +++ b/src/locales/ko.ts @@ -461,6 +461,10 @@ const ko = { manualMappingsHeading: '프린터 내 글꼴', manualMappingsHint: '프린터에 이미 있지만 여기에 업로드되지 않은 글꼴을 참조합니다.', addManualMapping: '프린터 글꼴 추가', + builtinPreviewsHeading: '내장 글꼴 미리보기', + builtinPreviewsHint: '로컬 TTF를 내장 글꼴 ID(0, A-H)에 바인딩하면 편집기에 해당 글꼴의 실제 모양이 표시됩니다. ZPL에 출력되지 않습니다.', + addBuiltinPreview: '내장 글꼴 바인딩', + noPreviewFont: '글꼴 선택…', }, zebraPrint: { heading: 'Zebra 프린터로 전송', diff --git a/src/locales/lt.ts b/src/locales/lt.ts index d40c333a..c737482e 100644 --- a/src/locales/lt.ts +++ b/src/locales/lt.ts @@ -461,6 +461,10 @@ const lt = { manualMappingsHeading: 'Spausdintuve esantys šriftai', manualMappingsHint: 'Nuoroda į spausdintuve jau esančius šriftus, kurie čia neįkelti.', addManualMapping: 'Pridėti spausdintuvo šriftą', + builtinPreviewsHeading: 'Įmontuotų šriftų peržiūros', + builtinPreviewsHint: 'Susiekite vietinį TTF su įmontuoto šrifto ID (0, A-H), kad redaktorius rodytų, kaip tas šriftas iš tikrųjų atrodo. Neperduodama į ZPL.', + addBuiltinPreview: 'Susieti įmontuotą šriftą', + noPreviewFont: 'Pasirinkite šriftą…', }, zebraPrint: { heading: 'Siųsti į Zebra spausdintuvą', diff --git a/src/locales/lv.ts b/src/locales/lv.ts index 38ac1064..3844bd15 100644 --- a/src/locales/lv.ts +++ b/src/locales/lv.ts @@ -461,6 +461,10 @@ const lv = { manualMappingsHeading: 'Printera fonti', manualMappingsHint: 'Atsauce uz fontiem, kas jau ir printerī, bet nav augšupielādēti šeit.', addManualMapping: 'Pievienot printera fontu', + builtinPreviewsHeading: 'Iebūvēto fontu priekšskatījumi', + builtinPreviewsHint: 'Saistīt vietējo TTF ar iebūvētu fonta ID (0, A-H), lai redaktors parādītu, kā šis fonts patiesībā izskatās. Netiek izvadīts uz ZPL.', + addBuiltinPreview: 'Saistīt iebūvēto fontu', + noPreviewFont: 'Izvēlieties fontu…', }, zebraPrint: { heading: 'Sūtīt uz Zebra printeri', diff --git a/src/locales/nl.ts b/src/locales/nl.ts index 46e24874..7656496f 100644 --- a/src/locales/nl.ts +++ b/src/locales/nl.ts @@ -461,6 +461,10 @@ const nl = { manualMappingsHeading: 'Lettertypen op de printer', manualMappingsHint: 'Verwijs naar lettertypen die al op de printer staan maar hier niet zijn geüpload.', addManualMapping: 'Printerlettertype toevoegen', + builtinPreviewsHeading: 'Voorvertoningen van ingebouwde lettertypen', + builtinPreviewsHint: 'Koppel een lokale TTF aan een ingebouwd lettertype-ID (0, A-H) zodat de editor toont hoe dat lettertype er echt uitziet. Wordt niet naar ZPL gestuurd.', + addBuiltinPreview: 'Ingebouwd lettertype koppelen', + noPreviewFont: 'Kies een lettertype…', }, zebraPrint: { heading: 'Verzenden naar Zebra-printer', diff --git a/src/locales/no.ts b/src/locales/no.ts index d3b913ea..42381aae 100644 --- a/src/locales/no.ts +++ b/src/locales/no.ts @@ -461,6 +461,10 @@ const no = { manualMappingsHeading: 'Skrifter på skriveren', manualMappingsHint: 'Referer til skrifter som allerede ligger på skriveren, men ikke er lastet opp her.', addManualMapping: 'Legg til skriverskrift', + builtinPreviewsHeading: 'Forhåndsvisninger av innebygde skrifter', + builtinPreviewsHint: 'Bind en lokal TTF til en innebygd skrift-ID (0, A-H) slik at editoren viser hvordan skriften faktisk ser ut. Sendes ikke til ZPL.', + addBuiltinPreview: 'Bind innebygd skrift', + noPreviewFont: 'Velg en skrift…', }, zebraPrint: { heading: 'Send til Zebra-skriver', diff --git a/src/locales/pl.ts b/src/locales/pl.ts index 80b8c594..1754bee4 100644 --- a/src/locales/pl.ts +++ b/src/locales/pl.ts @@ -461,6 +461,10 @@ const pl = { manualMappingsHeading: 'Czcionki w pamięci drukarki', manualMappingsHint: 'Odwołuje się do czcionek już w drukarce, niewgranych tutaj.', addManualMapping: 'Dodaj czcionkę drukarki', + builtinPreviewsHeading: 'Podglądy wbudowanych czcionek', + builtinPreviewsHint: 'Powiąż lokalny TTF z wbudowanym ID czcionki (0, A-H), aby edytor pokazywał, jak naprawdę wygląda ta czcionka. Nie jest wysyłane do ZPL.', + addBuiltinPreview: 'Powiąż wbudowaną czcionkę', + noPreviewFont: 'Wybierz czcionkę…', }, zebraPrint: { heading: 'Wyślij do drukarki Zebra', diff --git a/src/locales/pt.ts b/src/locales/pt.ts index 075a276e..6a6a42d3 100644 --- a/src/locales/pt.ts +++ b/src/locales/pt.ts @@ -461,6 +461,10 @@ const pt = { manualMappingsHeading: 'Fontes residentes na impressora', manualMappingsHint: 'Referencia fontes já presentes na impressora mas não enviadas aqui.', addManualMapping: 'Adicionar fonte da impressora', + builtinPreviewsHeading: 'Pré-visualizações de fontes integradas', + builtinPreviewsHint: 'Vincule um TTF local a um ID de fonte integrada (0, A-H) para que o editor mostre como essa fonte realmente parece. Não emitido para ZPL.', + addBuiltinPreview: 'Vincular fonte integrada', + noPreviewFont: 'Escolha uma fonte…', }, zebraPrint: { heading: 'Enviar para impressora Zebra', diff --git a/src/locales/ro.ts b/src/locales/ro.ts index ed38a27b..46b3549a 100644 --- a/src/locales/ro.ts +++ b/src/locales/ro.ts @@ -461,6 +461,10 @@ const ro = { manualMappingsHeading: 'Fonturi din imprimantă', manualMappingsHint: 'Referință la fonturi deja prezente pe imprimantă, neîncărcate aici.', addManualMapping: 'Adaugă font imprimantă', + builtinPreviewsHeading: 'Previzualizări fonturi integrate', + builtinPreviewsHint: 'Leagă un TTF local de un ID de font integrat (0, A-H) pentru ca editorul să arate cum arată cu adevărat acel font. Nu este emis în ZPL.', + addBuiltinPreview: 'Leagă font integrat', + noPreviewFont: 'Alege un font…', }, zebraPrint: { heading: 'Trimite la imprimanta Zebra', diff --git a/src/locales/sk.ts b/src/locales/sk.ts index 4e4290b8..240f191a 100644 --- a/src/locales/sk.ts +++ b/src/locales/sk.ts @@ -461,6 +461,10 @@ const sk = { manualMappingsHeading: 'Písma uložené v tlačiarni', manualMappingsHint: 'Odkaz na písma, ktoré sú už v tlačiarni, ale tu nie sú nahrané.', addManualMapping: 'Pridať písmo tlačiarne', + builtinPreviewsHeading: 'Náhľady vstavaných písiem', + builtinPreviewsHint: 'Priraďte miestny TTF k vstavanému ID písma (0, A-H), aby editor zobrazoval, ako toto písmo skutočne vyzerá. Nevysiela sa do ZPL.', + addBuiltinPreview: 'Priradiť vstavané písmo', + noPreviewFont: 'Vyberte písmo…', }, zebraPrint: { heading: 'Odoslať na tlačiareň Zebra', diff --git a/src/locales/sl.ts b/src/locales/sl.ts index 8832a034..8c094e84 100644 --- a/src/locales/sl.ts +++ b/src/locales/sl.ts @@ -461,6 +461,10 @@ const sl = { manualMappingsHeading: 'Pisave v tiskalniku', manualMappingsHint: 'Sklicevanje na pisave, ki so že v tiskalniku, vendar tukaj niso naložene.', addManualMapping: 'Dodaj pisavo tiskalnika', + builtinPreviewsHeading: 'Predogledi vgrajenih pisav', + builtinPreviewsHint: 'Povežite lokalno TTF datoteko z vgrajenim ID-jem pisave (0, A-H), da urejevalnik prikaže, kako ta pisava dejansko izgleda. Ni oddano v ZPL.', + addBuiltinPreview: 'Poveži vgrajeno pisavo', + noPreviewFont: 'Izberite pisavo…', }, zebraPrint: { heading: 'Pošlji na tiskalnik Zebra', diff --git a/src/locales/sr.ts b/src/locales/sr.ts index c6d31f13..47dd0260 100644 --- a/src/locales/sr.ts +++ b/src/locales/sr.ts @@ -461,6 +461,10 @@ const sr = { manualMappingsHeading: 'Фонтови на штампачу', manualMappingsHint: 'Референца на фонтове који се већ налазе на штампачу, али нису пренети овде.', addManualMapping: 'Додај фонт штампача', + builtinPreviewsHeading: 'Прегледи уграђених фонтова', + builtinPreviewsHint: 'Повежите локални TTF са уграђеним ID-јем фонта (0, A-H) тако да уредник прикаже како тај фонт заиста изгледа. Не емитује се у ZPL.', + addBuiltinPreview: 'Повежи уграђени фонт', + noPreviewFont: 'Изаберите фонт…', }, zebraPrint: { heading: 'Пошаљи на Zebra штампач', diff --git a/src/locales/sv.ts b/src/locales/sv.ts index 724a7281..561be03e 100644 --- a/src/locales/sv.ts +++ b/src/locales/sv.ts @@ -461,6 +461,10 @@ const sv = { manualMappingsHeading: 'Typsnitt i skrivaren', manualMappingsHint: 'Referera till typsnitt som redan finns på skrivaren men inte är uppladdade här.', addManualMapping: 'Lägg till skrivartypsnitt', + builtinPreviewsHeading: 'Förhandsvisningar av inbyggda typsnitt', + builtinPreviewsHint: 'Bind ett lokalt TTF till ett inbyggt typsnitts-ID (0, A-H) så att editorn visar hur det typsnittet faktiskt ser ut. Sänds inte till ZPL.', + addBuiltinPreview: 'Bind inbyggt typsnitt', + noPreviewFont: 'Välj ett typsnitt…', }, zebraPrint: { heading: 'Skicka till Zebra-skrivare', diff --git a/src/locales/tr.ts b/src/locales/tr.ts index 406dc0f3..b0e995f4 100644 --- a/src/locales/tr.ts +++ b/src/locales/tr.ts @@ -461,6 +461,10 @@ const tr = { manualMappingsHeading: 'Yazıcıda kayıtlı yazı tipleri', manualMappingsHint: 'Yazıcıda zaten bulunan ancak buraya yüklenmemiş yazı tiplerine referans verir.', addManualMapping: 'Yazıcı yazı tipi ekle', + builtinPreviewsHeading: 'Yerleşik yazı tipi önizlemeleri', + builtinPreviewsHint: 'Yerel bir TTF dosyasını yerleşik bir yazı tipi kimliğine (0, A-H) bağlayın, böylece editör o yazı tipinin gerçekte nasıl göründüğünü gösterir. ZPL\'ye yayılmaz.', + addBuiltinPreview: 'Yerleşik yazı tipi bağla', + noPreviewFont: 'Bir yazı tipi seç…', }, zebraPrint: { heading: 'Zebra yazıcıya gönder', diff --git a/src/locales/zh-hans.ts b/src/locales/zh-hans.ts index f91de0b1..e79f3d72 100644 --- a/src/locales/zh-hans.ts +++ b/src/locales/zh-hans.ts @@ -461,6 +461,10 @@ const zhHans = { manualMappingsHeading: '打印机内置字体', manualMappingsHint: '引用打印机中已有但未在此上传的字体。', addManualMapping: '添加打印机字体', + builtinPreviewsHeading: '内置字体预览', + builtinPreviewsHint: '将本地TTF绑定到内置字体ID(0、A-H),使编辑器显示该字体的实际外观。不会发送到ZPL。', + addBuiltinPreview: '绑定内置字体', + noPreviewFont: '选择字体…', }, zebraPrint: { heading: '发送到 Zebra 打印机', diff --git a/src/locales/zh-hant.ts b/src/locales/zh-hant.ts index ee165125..21af07a6 100644 --- a/src/locales/zh-hant.ts +++ b/src/locales/zh-hant.ts @@ -461,6 +461,10 @@ const zhHant = { manualMappingsHeading: '印表機內建字型', manualMappingsHint: '參照印表機中已有但未在此上傳的字型。', addManualMapping: '新增印表機字型', + builtinPreviewsHeading: '內建字型預覽', + builtinPreviewsHint: '將本地TTF綁定到內建字型ID(0、A-H),使編輯器顯示該字型的實際外觀。不會發送到ZPL。', + addBuiltinPreview: '綁定內建字型', + noPreviewFont: '選擇字型…', }, zebraPrint: { heading: '傳送至 Zebra 印表機', From ab24bf64a6b91a80a830f8276bb4c73becc07e09 Mon Sep 17 00:00:00 2001 From: u8array Date: Wed, 20 May 2026 20:50:54 +0200 Subject: [PATCH 11/22] fix(fonts): UX polish for built-in alias warning and label dropdown MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Surfaces an amber inline warning on the uploaded-font row when the user assigns a built-in letter (0, A-H) as the alias — that emits a ^CW which overrides the printer's factory font, almost never the user's intent. Points them at the "Built-in font previews" section for an editor-only binding. Label-properties default font datalist now goes through the same getAvailableFontIds helper the per-text dropdown uses, so a built-in preview binding surfaces in the global selector with the same filename suffix the user sees in text fields. --- src/components/Fonts/FontManager.tsx | 111 +++++++++++------- src/components/Properties/PropertiesPanel.tsx | 38 +++--- src/locales/ar.ts | 1 + src/locales/bg.ts | 1 + src/locales/cs.ts | 1 + src/locales/da.ts | 1 + src/locales/de.ts | 1 + src/locales/el.ts | 1 + src/locales/en.ts | 1 + src/locales/es.ts | 1 + src/locales/et.ts | 1 + src/locales/fa.ts | 1 + src/locales/fi.ts | 1 + src/locales/fr.ts | 1 + src/locales/he.ts | 1 + src/locales/hr.ts | 1 + src/locales/hu.ts | 1 + src/locales/it.ts | 1 + src/locales/ja.ts | 1 + src/locales/ko.ts | 1 + src/locales/lt.ts | 1 + src/locales/lv.ts | 1 + src/locales/nl.ts | 1 + src/locales/no.ts | 1 + src/locales/pl.ts | 1 + src/locales/pt.ts | 1 + src/locales/ro.ts | 1 + src/locales/sk.ts | 1 + src/locales/sl.ts | 1 + src/locales/sr.ts | 1 + src/locales/sv.ts | 1 + src/locales/tr.ts | 1 + src/locales/zh-hans.ts | 1 + src/locales/zh-hant.ts | 1 + 34 files changed, 113 insertions(+), 68 deletions(-) diff --git a/src/components/Fonts/FontManager.tsx b/src/components/Fonts/FontManager.tsx index 186feb88..7e49faa6 100644 --- a/src/components/Fonts/FontManager.tsx +++ b/src/components/Fonts/FontManager.tsx @@ -8,6 +8,7 @@ import { DEFAULT_FONT_DRIVE, ZPL_BUILTIN_FONT_IDS, ZPL_DRIVE_PREFIXES, + isBuiltinFontId, nextFreeAlias, normalizeAlias, uploadedFontPath, @@ -305,55 +306,75 @@ function FontEntry({ // no field can reference. Disable + tooltip when alias is empty so // the constraint is visible instead of silently failing at emit. const embedDisabled = !alias; + // Heads-up when the user picks a built-in letter (0, A-H): ^CW with + // a built-in alias overrides the factory font on the printer, which + // is rarely what the user wants — the "Built-in font previews" + // section is the right place for an editor-only binding. + const overridesBuiltin = isBuiltinFontId(alias); return ( -
- - {name} - - onAliasChange(e.target.value)} - /> -
); } diff --git a/src/locales/ar.ts b/src/locales/ar.ts index 3d87e396..01c26b3e 100644 --- a/src/locales/ar.ts +++ b/src/locales/ar.ts @@ -465,6 +465,8 @@ const ar = { builtinPreviewsHeading: 'معاينات الخطوط المدمجة', builtinPreviewsHint: 'اربط ملف TTF محلي بمعرف خط مدمج (0، A-H) لكي يعرض المحرر شكل هذا الخط فعليا. لا يتم إصداره إلى ZPL.', addBuiltinPreview: 'ربط خط مدمج', + builtinPreviewsTeaser: 'تريد رؤية كيف تبدو الخطوط المدمجة؟ راجع القسم أدناه.', + usedAsPreview: 'معاينة لـ', noPreviewFont: 'اختر خطًا…', }, zebraPrint: { diff --git a/src/locales/bg.ts b/src/locales/bg.ts index dc78dbf7..90204a24 100644 --- a/src/locales/bg.ts +++ b/src/locales/bg.ts @@ -465,6 +465,8 @@ const bg = { builtinPreviewsHeading: 'Прегледи на вградени шрифтове', builtinPreviewsHint: 'Свържете локален TTF с вграден ID на шрифт (0, A-H), за да показва редакторът как изглежда този шрифт. Не се излъчва в ZPL.', addBuiltinPreview: 'Свържи вграден шрифт', + builtinPreviewsTeaser: 'Искате да видите как изглеждат вградените шрифтове? Виж секцията по-долу.', + usedAsPreview: 'преглед за', noPreviewFont: 'Изберете шрифт…', }, zebraPrint: { diff --git a/src/locales/cs.ts b/src/locales/cs.ts index c5ada8f6..bf9ec1d9 100644 --- a/src/locales/cs.ts +++ b/src/locales/cs.ts @@ -465,6 +465,8 @@ const cs = { builtinPreviewsHeading: 'Náhledy vestavěných písem', builtinPreviewsHint: 'Přiřaďte místní TTF k vestavěnému ID písma (0, A-H), aby editor ukazoval, jak písmo skutečně vypadá. Nevysílá se do ZPL.', addBuiltinPreview: 'Přiřadit vestavěné písmo', + builtinPreviewsTeaser: 'Chcete vidět, jak vypadají vestavěná písma? Viz sekce níže.', + usedAsPreview: 'náhled pro', noPreviewFont: 'Vyberte písmo…', }, zebraPrint: { diff --git a/src/locales/da.ts b/src/locales/da.ts index dcadccd1..c1049631 100644 --- a/src/locales/da.ts +++ b/src/locales/da.ts @@ -465,6 +465,8 @@ const da = { builtinPreviewsHeading: 'Visning af indbyggede skrifttyper', builtinPreviewsHint: 'Bind en lokal TTF til et indbygget skrift-ID (0, A-H), så editoren viser, hvordan skriften faktisk ser ud. Sendes ikke til ZPL.', addBuiltinPreview: 'Bind indbygget skrifttype', + builtinPreviewsTeaser: 'Vil du se, hvordan indbyggede skrifttyper ser ud? Se afsnittet nedenfor.', + usedAsPreview: 'visning for', noPreviewFont: 'Vælg en skrifttype…', }, zebraPrint: { diff --git a/src/locales/de.ts b/src/locales/de.ts index 80c77173..803b9ed9 100644 --- a/src/locales/de.ts +++ b/src/locales/de.ts @@ -486,6 +486,8 @@ const de = { builtinPreviewsHeading: 'Vorschau für Drucker-Standardschriften', builtinPreviewsHint: 'Bindet eine lokale TTF an eine eingebaute Schrift-ID (0, A-H), damit der Editor zeigt, wie diese Schrift wirklich aussieht. Wird nicht ins ZPL emittiert.', addBuiltinPreview: 'Eingebaute Schrift binden', + builtinPreviewsTeaser: 'Sehen wie eingebaute Schriften aussehen? Siehe Sektion unten.', + usedAsPreview: 'Vorschau für', noPreviewFont: 'Schrift wählen …', }, } as const; diff --git a/src/locales/el.ts b/src/locales/el.ts index f445933a..371c7e1b 100644 --- a/src/locales/el.ts +++ b/src/locales/el.ts @@ -465,6 +465,8 @@ const el = { builtinPreviewsHeading: 'Προεπισκοπήσεις ενσωματωμένων γραμματοσειρών', builtinPreviewsHint: 'Συνδέστε ένα τοπικό TTF με ένα ενσωματωμένο ID γραμματοσειράς (0, A-H) ώστε ο επεξεργαστής να δείχνει πώς φαίνεται πραγματικά αυτή η γραμματοσειρά. Δεν εκπέμπεται στο ZPL.', addBuiltinPreview: 'Σύνδεση ενσωματωμένης γραμματοσειράς', + builtinPreviewsTeaser: 'Θέλετε να δείτε πώς εμφανίζονται οι ενσωματωμένες γραμματοσειρές; Δείτε την ενότητα παρακάτω.', + usedAsPreview: 'προεπισκόπηση για', noPreviewFont: 'Επιλέξτε γραμματοσειρά…', }, zebraPrint: { diff --git a/src/locales/en.ts b/src/locales/en.ts index 3e99cdcf..c7698f69 100644 --- a/src/locales/en.ts +++ b/src/locales/en.ts @@ -486,6 +486,8 @@ const en = { builtinPreviewsHeading: 'Built-in font previews', builtinPreviewsHint: 'Bind a local TTF to a built-in font ID (0, A-H) so the editor shows what that font actually looks like. Not emitted to ZPL.', addBuiltinPreview: 'Bind built-in font', + builtinPreviewsTeaser: 'Want to see what built-in fonts render like? See the section below.', + usedAsPreview: 'preview for', noPreviewFont: 'Pick a font…', }, } as const; diff --git a/src/locales/es.ts b/src/locales/es.ts index 611fe3ee..549ea1d4 100644 --- a/src/locales/es.ts +++ b/src/locales/es.ts @@ -465,6 +465,8 @@ const es = { builtinPreviewsHeading: 'Previsualizaciones de fuentes integradas', builtinPreviewsHint: 'Asocia un TTF local a un ID de fuente integrada (0, A-H) para que el editor muestre cómo se ve realmente esa fuente. No se emite a ZPL.', addBuiltinPreview: 'Vincular fuente integrada', + builtinPreviewsTeaser: '¿Quieres ver cómo se ven las fuentes integradas? Mira la sección de abajo.', + usedAsPreview: 'vista previa para', noPreviewFont: 'Elige una fuente…', }, zebraPrint: { diff --git a/src/locales/et.ts b/src/locales/et.ts index a30b9861..a93b660b 100644 --- a/src/locales/et.ts +++ b/src/locales/et.ts @@ -465,6 +465,8 @@ const et = { builtinPreviewsHeading: 'Sisseehitatud fontide eelvaated', builtinPreviewsHint: 'Seo kohalik TTF sisseehitatud fondi ID-ga (0, A-H), et redaktor näitaks, milline see font tegelikult välja näeb. Ei edastata ZPL-i.', addBuiltinPreview: 'Seo sisseehitatud font', + builtinPreviewsTeaser: 'Kas tahad näha, kuidas sisseehitatud fondid välja näevad? Vaata allolevat jaotist.', + usedAsPreview: 'eelvaade', noPreviewFont: 'Vali font…', }, zebraPrint: { diff --git a/src/locales/fa.ts b/src/locales/fa.ts index 291620a0..e86eee7e 100644 --- a/src/locales/fa.ts +++ b/src/locales/fa.ts @@ -465,6 +465,8 @@ const fa = { builtinPreviewsHeading: 'پیش‌نمایش قلم‌های داخلی', builtinPreviewsHint: 'یک فایل TTF محلی را به یک شناسه قلم داخلی (0، A-H) متصل کنید تا ویرایشگر شکل واقعی آن قلم را نشان دهد. به ZPL منتشر نمی‌شود.', addBuiltinPreview: 'اتصال قلم داخلی', + builtinPreviewsTeaser: 'می‌خواهید ببینید قلم‌های داخلی چگونه به نظر می‌رسند؟ به بخش زیر مراجعه کنید.', + usedAsPreview: 'پیش‌نمایش برای', noPreviewFont: 'یک قلم انتخاب کنید…', }, zebraPrint: { diff --git a/src/locales/fi.ts b/src/locales/fi.ts index 65b4d805..6ab8b888 100644 --- a/src/locales/fi.ts +++ b/src/locales/fi.ts @@ -465,6 +465,8 @@ const fi = { builtinPreviewsHeading: 'Sisäänrakennettujen fonttien esikatselut', builtinPreviewsHint: 'Sido paikallinen TTF sisäänrakennettuun fontti-ID:hen (0, A-H), jotta editori näyttää miltä fontti todella näyttää. Ei lähetetä ZPL:ään.', addBuiltinPreview: 'Sido sisäänrakennettu fontti', + builtinPreviewsTeaser: 'Haluatko nähdä, miltä sisäänrakennetut fontit näyttävät? Katso alla oleva osio.', + usedAsPreview: 'esikatselu', noPreviewFont: 'Valitse fontti…', }, zebraPrint: { diff --git a/src/locales/fr.ts b/src/locales/fr.ts index ff8b97e3..1e6464a0 100644 --- a/src/locales/fr.ts +++ b/src/locales/fr.ts @@ -465,6 +465,8 @@ const fr = { builtinPreviewsHeading: 'Aperçus des polices intégrées', builtinPreviewsHint: 'Lie un TTF local à un ID de police intégrée (0, A-H) pour que l\'éditeur montre à quoi ressemble vraiment cette police. Non émis dans ZPL.', addBuiltinPreview: 'Lier police intégrée', + builtinPreviewsTeaser: 'Voir à quoi ressemblent les polices intégrées ? Voir la section ci-dessous.', + usedAsPreview: 'aperçu pour', noPreviewFont: 'Choisir une police…', }, zebraPrint: { diff --git a/src/locales/he.ts b/src/locales/he.ts index 52974067..cd72d2a4 100644 --- a/src/locales/he.ts +++ b/src/locales/he.ts @@ -465,6 +465,8 @@ const he = { builtinPreviewsHeading: 'תצוגות מקדימות של גופנים מובנים', builtinPreviewsHint: 'קשר קובץ TTF מקומי לזיהוי גופן מובנה (0, A-H) כדי שהעורך יציג כיצד הגופן באמת נראה. לא נפלט ל-ZPL.', addBuiltinPreview: 'קשר גופן מובנה', + builtinPreviewsTeaser: 'רוצה לראות איך נראים גופנים מובנים? ראה את הסעיף למטה.', + usedAsPreview: 'תצוגה עבור', noPreviewFont: 'בחר גופן…', }, zebraPrint: { diff --git a/src/locales/hr.ts b/src/locales/hr.ts index 4045178a..22754f71 100644 --- a/src/locales/hr.ts +++ b/src/locales/hr.ts @@ -465,6 +465,8 @@ const hr = { builtinPreviewsHeading: 'Pregledi ugrađenih fontova', builtinPreviewsHint: 'Povežite lokalni TTF s ugrađenim ID-om fonta (0, A-H) tako da uređivač prikazuje kako taj font zaista izgleda. Ne emitira se u ZPL.', addBuiltinPreview: 'Poveži ugrađeni font', + builtinPreviewsTeaser: 'Želite vidjeti kako izgledaju ugrađeni fontovi? Pogledajte odjeljak ispod.', + usedAsPreview: 'pregled za', noPreviewFont: 'Odaberite font…', }, zebraPrint: { diff --git a/src/locales/hu.ts b/src/locales/hu.ts index 60d6574d..1f5d7240 100644 --- a/src/locales/hu.ts +++ b/src/locales/hu.ts @@ -465,6 +465,8 @@ const hu = { builtinPreviewsHeading: 'Beépített betűtípus előnézetek', builtinPreviewsHint: 'Köss össze egy helyi TTF-et egy beépített betűtípus-azonosítóval (0, A-H), hogy a szerkesztő mutassa, hogyan néz ki valójában az a betűtípus. Nem kerül ZPL-be.', addBuiltinPreview: 'Beépített betűtípus kötése', + builtinPreviewsTeaser: 'Szeretnéd látni, hogyan néznek ki a beépített betűtípusok? Lásd az alábbi szakaszt.', + usedAsPreview: 'előnézet', noPreviewFont: 'Válassz betűtípust…', }, zebraPrint: { diff --git a/src/locales/it.ts b/src/locales/it.ts index 3cddee26..62254639 100644 --- a/src/locales/it.ts +++ b/src/locales/it.ts @@ -465,6 +465,8 @@ const it = { builtinPreviewsHeading: 'Anteprime caratteri integrati', builtinPreviewsHint: 'Associa un TTF locale a un ID di carattere integrato (0, A-H) in modo che l\'editor mostri come appare realmente quel carattere. Non emesso in ZPL.', addBuiltinPreview: 'Associa carattere integrato', + builtinPreviewsTeaser: 'Vuoi vedere come appaiono i caratteri integrati? Vedi sezione sotto.', + usedAsPreview: 'anteprima per', noPreviewFont: 'Scegli un carattere…', }, zebraPrint: { diff --git a/src/locales/ja.ts b/src/locales/ja.ts index a4e3aa0a..6b91ba07 100644 --- a/src/locales/ja.ts +++ b/src/locales/ja.ts @@ -465,6 +465,8 @@ const ja = { builtinPreviewsHeading: '内蔵フォントのプレビュー', builtinPreviewsHint: 'ローカルTTFを内蔵フォントID(0、A-H)にバインドして、エディタでそのフォントの実際の見た目を表示します。ZPLには出力されません。', addBuiltinPreview: '内蔵フォントをバインド', + builtinPreviewsTeaser: '内蔵フォントの表示を確認しますか?下のセクションを参照してください。', + usedAsPreview: 'プレビュー', noPreviewFont: 'フォントを選択…', }, zebraPrint: { diff --git a/src/locales/ko.ts b/src/locales/ko.ts index 56a77e43..e8b5f80b 100644 --- a/src/locales/ko.ts +++ b/src/locales/ko.ts @@ -465,6 +465,8 @@ const ko = { builtinPreviewsHeading: '내장 글꼴 미리보기', builtinPreviewsHint: '로컬 TTF를 내장 글꼴 ID(0, A-H)에 바인딩하면 편집기에 해당 글꼴의 실제 모양이 표시됩니다. ZPL에 출력되지 않습니다.', addBuiltinPreview: '내장 글꼴 바인딩', + builtinPreviewsTeaser: '내장 글꼴이 어떻게 보이는지 확인하시겠습니까? 아래 섹션을 참조하세요.', + usedAsPreview: '미리보기', noPreviewFont: '글꼴 선택…', }, zebraPrint: { diff --git a/src/locales/lt.ts b/src/locales/lt.ts index 44d1f0a0..e448d08c 100644 --- a/src/locales/lt.ts +++ b/src/locales/lt.ts @@ -465,6 +465,8 @@ const lt = { builtinPreviewsHeading: 'Įmontuotų šriftų peržiūros', builtinPreviewsHint: 'Susiekite vietinį TTF su įmontuoto šrifto ID (0, A-H), kad redaktorius rodytų, kaip tas šriftas iš tikrųjų atrodo. Neperduodama į ZPL.', addBuiltinPreview: 'Susieti įmontuotą šriftą', + builtinPreviewsTeaser: 'Norite pamatyti, kaip atrodo įmontuoti šriftai? Žiūrėkite skyrių žemiau.', + usedAsPreview: 'peržiūra', noPreviewFont: 'Pasirinkite šriftą…', }, zebraPrint: { diff --git a/src/locales/lv.ts b/src/locales/lv.ts index c6fe214d..d9163b93 100644 --- a/src/locales/lv.ts +++ b/src/locales/lv.ts @@ -465,6 +465,8 @@ const lv = { builtinPreviewsHeading: 'Iebūvēto fontu priekšskatījumi', builtinPreviewsHint: 'Saistīt vietējo TTF ar iebūvētu fonta ID (0, A-H), lai redaktors parādītu, kā šis fonts patiesībā izskatās. Netiek izvadīts uz ZPL.', addBuiltinPreview: 'Saistīt iebūvēto fontu', + builtinPreviewsTeaser: 'Vēlaties redzēt, kā izskatās iebūvētie fonti? Skatiet sadaļu zemāk.', + usedAsPreview: 'priekšskatījums', noPreviewFont: 'Izvēlieties fontu…', }, zebraPrint: { diff --git a/src/locales/nl.ts b/src/locales/nl.ts index 8080929c..2a4bfa7f 100644 --- a/src/locales/nl.ts +++ b/src/locales/nl.ts @@ -465,6 +465,8 @@ const nl = { builtinPreviewsHeading: 'Voorvertoningen van ingebouwde lettertypen', builtinPreviewsHint: 'Koppel een lokale TTF aan een ingebouwd lettertype-ID (0, A-H) zodat de editor toont hoe dat lettertype er echt uitziet. Wordt niet naar ZPL gestuurd.', addBuiltinPreview: 'Ingebouwd lettertype koppelen', + builtinPreviewsTeaser: 'Wil je zien hoe ingebouwde lettertypen eruitzien? Zie de sectie hieronder.', + usedAsPreview: 'voorvertoning voor', noPreviewFont: 'Kies een lettertype…', }, zebraPrint: { diff --git a/src/locales/no.ts b/src/locales/no.ts index 48fea91c..eff09f32 100644 --- a/src/locales/no.ts +++ b/src/locales/no.ts @@ -465,6 +465,8 @@ const no = { builtinPreviewsHeading: 'Forhåndsvisninger av innebygde skrifter', builtinPreviewsHint: 'Bind en lokal TTF til en innebygd skrift-ID (0, A-H) slik at editoren viser hvordan skriften faktisk ser ut. Sendes ikke til ZPL.', addBuiltinPreview: 'Bind innebygd skrift', + builtinPreviewsTeaser: 'Vil du se hvordan innebygde skrifter ser ut? Se seksjonen nedenfor.', + usedAsPreview: 'visning for', noPreviewFont: 'Velg en skrift…', }, zebraPrint: { diff --git a/src/locales/pl.ts b/src/locales/pl.ts index e7dd4629..df47100d 100644 --- a/src/locales/pl.ts +++ b/src/locales/pl.ts @@ -465,6 +465,8 @@ const pl = { builtinPreviewsHeading: 'Podglądy wbudowanych czcionek', builtinPreviewsHint: 'Powiąż lokalny TTF z wbudowanym ID czcionki (0, A-H), aby edytor pokazywał, jak naprawdę wygląda ta czcionka. Nie jest wysyłane do ZPL.', addBuiltinPreview: 'Powiąż wbudowaną czcionkę', + builtinPreviewsTeaser: 'Chcesz zobaczyć, jak wyglądają wbudowane czcionki? Zobacz sekcję poniżej.', + usedAsPreview: 'podgląd dla', noPreviewFont: 'Wybierz czcionkę…', }, zebraPrint: { diff --git a/src/locales/pt.ts b/src/locales/pt.ts index 586d242a..0977cb5a 100644 --- a/src/locales/pt.ts +++ b/src/locales/pt.ts @@ -465,6 +465,8 @@ const pt = { builtinPreviewsHeading: 'Pré-visualizações de fontes integradas', builtinPreviewsHint: 'Vincule um TTF local a um ID de fonte integrada (0, A-H) para que o editor mostre como essa fonte realmente parece. Não emitido para ZPL.', addBuiltinPreview: 'Vincular fonte integrada', + builtinPreviewsTeaser: 'Quer ver como as fontes integradas aparecem? Veja a seção abaixo.', + usedAsPreview: 'pré-visualização para', noPreviewFont: 'Escolha uma fonte…', }, zebraPrint: { diff --git a/src/locales/ro.ts b/src/locales/ro.ts index 85bcf35a..b0b40839 100644 --- a/src/locales/ro.ts +++ b/src/locales/ro.ts @@ -465,6 +465,8 @@ const ro = { builtinPreviewsHeading: 'Previzualizări fonturi integrate', builtinPreviewsHint: 'Leagă un TTF local de un ID de font integrat (0, A-H) pentru ca editorul să arate cum arată cu adevărat acel font. Nu este emis în ZPL.', addBuiltinPreview: 'Leagă font integrat', + builtinPreviewsTeaser: 'Vrei să vezi cum arată fonturile integrate? Vezi secțiunea de mai jos.', + usedAsPreview: 'previzualizare', noPreviewFont: 'Alege un font…', }, zebraPrint: { diff --git a/src/locales/sk.ts b/src/locales/sk.ts index af6e084b..524667e8 100644 --- a/src/locales/sk.ts +++ b/src/locales/sk.ts @@ -465,6 +465,8 @@ const sk = { builtinPreviewsHeading: 'Náhľady vstavaných písiem', builtinPreviewsHint: 'Priraďte miestny TTF k vstavanému ID písma (0, A-H), aby editor zobrazoval, ako toto písmo skutočne vyzerá. Nevysiela sa do ZPL.', addBuiltinPreview: 'Priradiť vstavané písmo', + builtinPreviewsTeaser: 'Chcete vidieť, ako vyzerajú vstavané písma? Pozrite si sekciu nižšie.', + usedAsPreview: 'náhľad pre', noPreviewFont: 'Vyberte písmo…', }, zebraPrint: { diff --git a/src/locales/sl.ts b/src/locales/sl.ts index a928591e..ca9e9b2b 100644 --- a/src/locales/sl.ts +++ b/src/locales/sl.ts @@ -465,6 +465,8 @@ const sl = { builtinPreviewsHeading: 'Predogledi vgrajenih pisav', builtinPreviewsHint: 'Povežite lokalno TTF datoteko z vgrajenim ID-jem pisave (0, A-H), da urejevalnik prikaže, kako ta pisava dejansko izgleda. Ni oddano v ZPL.', addBuiltinPreview: 'Poveži vgrajeno pisavo', + builtinPreviewsTeaser: 'Želite videti, kako izgledajo vgrajene pisave? Glejte spodnji razdelek.', + usedAsPreview: 'predogled za', noPreviewFont: 'Izberite pisavo…', }, zebraPrint: { diff --git a/src/locales/sr.ts b/src/locales/sr.ts index 5be0dc5d..8e9821c0 100644 --- a/src/locales/sr.ts +++ b/src/locales/sr.ts @@ -465,6 +465,8 @@ const sr = { builtinPreviewsHeading: 'Прегледи уграђених фонтова', builtinPreviewsHint: 'Повежите локални TTF са уграђеним ID-јем фонта (0, A-H) тако да уредник прикаже како тај фонт заиста изгледа. Не емитује се у ZPL.', addBuiltinPreview: 'Повежи уграђени фонт', + builtinPreviewsTeaser: 'Желите да видите како изгледају уграђени фонтови? Погледајте секцију испод.', + usedAsPreview: 'преглед за', noPreviewFont: 'Изаберите фонт…', }, zebraPrint: { diff --git a/src/locales/sv.ts b/src/locales/sv.ts index 4c0edf33..435f0caa 100644 --- a/src/locales/sv.ts +++ b/src/locales/sv.ts @@ -465,6 +465,8 @@ const sv = { builtinPreviewsHeading: 'Förhandsvisningar av inbyggda typsnitt', builtinPreviewsHint: 'Bind ett lokalt TTF till ett inbyggt typsnitts-ID (0, A-H) så att editorn visar hur det typsnittet faktiskt ser ut. Sänds inte till ZPL.', addBuiltinPreview: 'Bind inbyggt typsnitt', + builtinPreviewsTeaser: 'Vill du se hur inbyggda typsnitt ser ut? Se avsnittet nedan.', + usedAsPreview: 'visning för', noPreviewFont: 'Välj ett typsnitt…', }, zebraPrint: { diff --git a/src/locales/tr.ts b/src/locales/tr.ts index c37e23e6..e738cb55 100644 --- a/src/locales/tr.ts +++ b/src/locales/tr.ts @@ -465,6 +465,8 @@ const tr = { builtinPreviewsHeading: 'Yerleşik yazı tipi önizlemeleri', builtinPreviewsHint: 'Yerel bir TTF dosyasını yerleşik bir yazı tipi kimliğine (0, A-H) bağlayın, böylece editör o yazı tipinin gerçekte nasıl göründüğünü gösterir. ZPL\'ye yayılmaz.', addBuiltinPreview: 'Yerleşik yazı tipi bağla', + builtinPreviewsTeaser: 'Yerleşik yazı tiplerinin nasıl göründüğünü görmek ister misiniz? Aşağıdaki bölüme bakın.', + usedAsPreview: 'önizleme', noPreviewFont: 'Bir yazı tipi seç…', }, zebraPrint: { diff --git a/src/locales/zh-hans.ts b/src/locales/zh-hans.ts index c0e9c62d..9b560ebc 100644 --- a/src/locales/zh-hans.ts +++ b/src/locales/zh-hans.ts @@ -465,6 +465,8 @@ const zhHans = { builtinPreviewsHeading: '内置字体预览', builtinPreviewsHint: '将本地TTF绑定到内置字体ID(0、A-H),使编辑器显示该字体的实际外观。不会发送到ZPL。', addBuiltinPreview: '绑定内置字体', + builtinPreviewsTeaser: '想看内置字体的样子?请参见下方区块。', + usedAsPreview: '预览', noPreviewFont: '选择字体…', }, zebraPrint: { diff --git a/src/locales/zh-hant.ts b/src/locales/zh-hant.ts index c19271a5..98be9674 100644 --- a/src/locales/zh-hant.ts +++ b/src/locales/zh-hant.ts @@ -465,6 +465,8 @@ const zhHant = { builtinPreviewsHeading: '內建字型預覽', builtinPreviewsHint: '將本地TTF綁定到內建字型ID(0、A-H),使編輯器顯示該字型的實際外觀。不會發送到ZPL。', addBuiltinPreview: '綁定內建字型', + builtinPreviewsTeaser: '想看內建字型的樣子?請參見下方區塊。', + usedAsPreview: '預覽', noPreviewFont: '選擇字型…', }, zebraPrint: { From 9bdf73e7fff595c15e5421d5b77d0ded3a6f8e97 Mon Sep 17 00:00:00 2001 From: u8array Date: Wed, 20 May 2026 21:29:23 +0200 Subject: [PATCH 13/22] fix(fonts): preserve extra mapping fields on alias / path edits MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updateManualAt and updateBuiltinPreview rebuilt the mapping object from scratch on every edit, dropping previewFontName or embedInZpl if they happened to be set. The UI doesn't construct such hybrid entries today, but a round-trip-imported label or future composition can — spreading the source object first keeps untouched fields intact and makes the partial-patch contract honest. --- src/components/Fonts/FontManager.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/components/Fonts/FontManager.tsx b/src/components/Fonts/FontManager.tsx index cff0704a..70065fc1 100644 --- a/src/components/Fonts/FontManager.tsx +++ b/src/components/Fonts/FontManager.tsx @@ -113,6 +113,7 @@ export function FontManager() { list.map((m) => m.path === path ? { + ...m, alias: patch.alias !== undefined ? normalizeAlias(patch.alias) @@ -163,6 +164,7 @@ export function FontManager() { list.map((m) => m.alias === currentAlias && !m.path ? { + ...m, alias: patch.alias !== undefined ? normalizeAlias(patch.alias) || m.alias : m.alias, From d6d64e3b772a8058db054f796ac6f38913013174 Mon Sep 17 00:00:00 2001 From: u8array Date: Wed, 20 May 2026 21:43:23 +0200 Subject: [PATCH 14/22] fix(fonts): empty-alias false positive and Tailwind cascade for warning border MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit isBuiltinFontId("") returned true because String.includes("") is always true — every FontEntry with a blank alias surfaced the "overrides built-in" warning. Guards with an explicit length check and a regression test. The amber/red warning borders on alias inputs were drawn the same dark gray as the unmarked state because Tailwind's CSS cascade put border-border (from inputCls) after border-amber-500 / border-red-500 in source order. Prefixing the conditional classes with ! (important) makes the warning state visible regardless of utility ordering. --- src/components/Fonts/FontManager.tsx | 8 ++++---- src/lib/customFonts.test.ts | 4 ++++ src/lib/customFonts.ts | 6 ++++-- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/components/Fonts/FontManager.tsx b/src/components/Fonts/FontManager.tsx index 70065fc1..bf7f0265 100644 --- a/src/components/Fonts/FontManager.tsx +++ b/src/components/Fonts/FontManager.tsx @@ -343,9 +343,9 @@ function FontEntry({ type="text" className={`${inputCls} text-center ${ duplicate - ? 'border-red-500' + ? '!border-red-500' : overridesBuiltin - ? 'border-amber-500' + ? '!border-amber-500' : '' }`} maxLength={1} @@ -468,7 +468,7 @@ function ManualMappingsSection({ > onUpdate(path, { alias: e.target.value })} + onChange={(e) => onUpdate(index, { alias: e.target.value })} /> onUpdate(path, { path: e.target.value })} + onChange={(e) => onUpdate(index, { path: e.target.value })} /> - {!open && teaser && ( -

{teaser}

- )} {open && (
{children} diff --git a/src/locales/ar.ts b/src/locales/ar.ts index 2b8b3496..0d29468d 100644 --- a/src/locales/ar.ts +++ b/src/locales/ar.ts @@ -465,7 +465,6 @@ const ar = { builtinPreviewsHeading: 'معاينات الخطوط المدمجة', builtinPreviewsHint: 'اربط ملف TTF محلي بمعرف خط مدمج (0، A-H) لكي يعرض المحرر شكل هذا الخط فعليا. لا يتم إصداره إلى ZPL.', addBuiltinPreview: 'ربط خط مدمج', - builtinPreviewsTeaser: 'تريد رؤية كيف تبدو الخطوط المدمجة؟ راجع القسم أدناه.', usedAsPreview: 'معاينة لـ', noPreviewFont: 'اختر خطًا…', }, diff --git a/src/locales/bg.ts b/src/locales/bg.ts index a13819fc..3d92f752 100644 --- a/src/locales/bg.ts +++ b/src/locales/bg.ts @@ -465,7 +465,6 @@ const bg = { builtinPreviewsHeading: 'Прегледи на вградени шрифтове', builtinPreviewsHint: 'Свържете локален TTF с вграден ID на шрифт (0, A-H), за да показва редакторът как изглежда този шрифт. Не се излъчва в ZPL.', addBuiltinPreview: 'Свържи вграден шрифт', - builtinPreviewsTeaser: 'Искате да видите как изглеждат вградените шрифтове? Виж секцията по-долу.', usedAsPreview: 'преглед за', noPreviewFont: 'Изберете шрифт…', }, diff --git a/src/locales/cs.ts b/src/locales/cs.ts index 023d4c80..d85150be 100644 --- a/src/locales/cs.ts +++ b/src/locales/cs.ts @@ -465,7 +465,6 @@ const cs = { builtinPreviewsHeading: 'Náhledy vestavěných písem', builtinPreviewsHint: 'Přiřaďte místní TTF k vestavěnému ID písma (0, A-H), aby editor ukazoval, jak písmo skutečně vypadá. Nevysílá se do ZPL.', addBuiltinPreview: 'Přiřadit vestavěné písmo', - builtinPreviewsTeaser: 'Chcete vidět, jak vypadají vestavěná písma? Viz sekce níže.', usedAsPreview: 'náhled pro', noPreviewFont: 'Vyberte písmo…', }, diff --git a/src/locales/da.ts b/src/locales/da.ts index bcfe1e91..070c6ae0 100644 --- a/src/locales/da.ts +++ b/src/locales/da.ts @@ -465,7 +465,6 @@ const da = { builtinPreviewsHeading: 'Visning af indbyggede skrifttyper', builtinPreviewsHint: 'Bind en lokal TTF til et indbygget skrift-ID (0, A-H), så editoren viser, hvordan skriften faktisk ser ud. Sendes ikke til ZPL.', addBuiltinPreview: 'Bind indbygget skrifttype', - builtinPreviewsTeaser: 'Vil du se, hvordan indbyggede skrifttyper ser ud? Se afsnittet nedenfor.', usedAsPreview: 'visning for', noPreviewFont: 'Vælg en skrifttype…', }, diff --git a/src/locales/de.ts b/src/locales/de.ts index 9a05b95f..4f93f7ea 100644 --- a/src/locales/de.ts +++ b/src/locales/de.ts @@ -486,7 +486,6 @@ const de = { builtinPreviewsHeading: 'Vorschau für Drucker-Standardschriften', builtinPreviewsHint: 'Bindet eine lokale TTF an eine eingebaute Schrift-ID (0, A-H), damit der Editor zeigt, wie diese Schrift wirklich aussieht. Wird nicht ins ZPL emittiert.', addBuiltinPreview: 'Eingebaute Schrift binden', - builtinPreviewsTeaser: 'Sehen wie eingebaute Schriften aussehen? Siehe Sektion unten.', usedAsPreview: 'Vorschau für', noPreviewFont: 'Schrift wählen …', }, diff --git a/src/locales/el.ts b/src/locales/el.ts index 6bbe14bb..7c1af2a3 100644 --- a/src/locales/el.ts +++ b/src/locales/el.ts @@ -465,7 +465,6 @@ const el = { builtinPreviewsHeading: 'Προεπισκοπήσεις ενσωματωμένων γραμματοσειρών', builtinPreviewsHint: 'Συνδέστε ένα τοπικό TTF με ένα ενσωματωμένο ID γραμματοσειράς (0, A-H) ώστε ο επεξεργαστής να δείχνει πώς φαίνεται πραγματικά αυτή η γραμματοσειρά. Δεν εκπέμπεται στο ZPL.', addBuiltinPreview: 'Σύνδεση ενσωματωμένης γραμματοσειράς', - builtinPreviewsTeaser: 'Θέλετε να δείτε πώς εμφανίζονται οι ενσωματωμένες γραμματοσειρές; Δείτε την ενότητα παρακάτω.', usedAsPreview: 'προεπισκόπηση για', noPreviewFont: 'Επιλέξτε γραμματοσειρά…', }, diff --git a/src/locales/en.ts b/src/locales/en.ts index 70b127a5..c801945d 100644 --- a/src/locales/en.ts +++ b/src/locales/en.ts @@ -486,7 +486,6 @@ const en = { builtinPreviewsHeading: 'Built-in font previews', builtinPreviewsHint: 'Bind a local TTF to a built-in font ID (0, A-H) so the editor shows what that font actually looks like. Not emitted to ZPL.', addBuiltinPreview: 'Bind built-in font', - builtinPreviewsTeaser: 'Want to see what built-in fonts render like? See the section below.', usedAsPreview: 'preview for', noPreviewFont: 'Pick a font…', }, diff --git a/src/locales/es.ts b/src/locales/es.ts index fc49f25a..eefc0492 100644 --- a/src/locales/es.ts +++ b/src/locales/es.ts @@ -465,7 +465,6 @@ const es = { builtinPreviewsHeading: 'Previsualizaciones de fuentes integradas', builtinPreviewsHint: 'Asocia un TTF local a un ID de fuente integrada (0, A-H) para que el editor muestre cómo se ve realmente esa fuente. No se emite a ZPL.', addBuiltinPreview: 'Vincular fuente integrada', - builtinPreviewsTeaser: '¿Quieres ver cómo se ven las fuentes integradas? Mira la sección de abajo.', usedAsPreview: 'vista previa para', noPreviewFont: 'Elige una fuente…', }, diff --git a/src/locales/et.ts b/src/locales/et.ts index 409f8048..9abebbb4 100644 --- a/src/locales/et.ts +++ b/src/locales/et.ts @@ -465,7 +465,6 @@ const et = { builtinPreviewsHeading: 'Sisseehitatud fontide eelvaated', builtinPreviewsHint: 'Seo kohalik TTF sisseehitatud fondi ID-ga (0, A-H), et redaktor näitaks, milline see font tegelikult välja näeb. Ei edastata ZPL-i.', addBuiltinPreview: 'Seo sisseehitatud font', - builtinPreviewsTeaser: 'Kas tahad näha, kuidas sisseehitatud fondid välja näevad? Vaata allolevat jaotist.', usedAsPreview: 'eelvaade', noPreviewFont: 'Vali font…', }, diff --git a/src/locales/fa.ts b/src/locales/fa.ts index b9fa3efe..ffd5b2fa 100644 --- a/src/locales/fa.ts +++ b/src/locales/fa.ts @@ -465,7 +465,6 @@ const fa = { builtinPreviewsHeading: 'پیش‌نمایش قلم‌های داخلی', builtinPreviewsHint: 'یک فایل TTF محلی را به یک شناسه قلم داخلی (0، A-H) متصل کنید تا ویرایشگر شکل واقعی آن قلم را نشان دهد. به ZPL منتشر نمی‌شود.', addBuiltinPreview: 'اتصال قلم داخلی', - builtinPreviewsTeaser: 'می‌خواهید ببینید قلم‌های داخلی چگونه به نظر می‌رسند؟ به بخش زیر مراجعه کنید.', usedAsPreview: 'پیش‌نمایش برای', noPreviewFont: 'یک قلم انتخاب کنید…', }, diff --git a/src/locales/fi.ts b/src/locales/fi.ts index 5e921be9..435ef73d 100644 --- a/src/locales/fi.ts +++ b/src/locales/fi.ts @@ -465,7 +465,6 @@ const fi = { builtinPreviewsHeading: 'Sisäänrakennettujen fonttien esikatselut', builtinPreviewsHint: 'Sido paikallinen TTF sisäänrakennettuun fontti-ID:hen (0, A-H), jotta editori näyttää miltä fontti todella näyttää. Ei lähetetä ZPL:ään.', addBuiltinPreview: 'Sido sisäänrakennettu fontti', - builtinPreviewsTeaser: 'Haluatko nähdä, miltä sisäänrakennetut fontit näyttävät? Katso alla oleva osio.', usedAsPreview: 'esikatselu', noPreviewFont: 'Valitse fontti…', }, diff --git a/src/locales/fr.ts b/src/locales/fr.ts index fa398ad7..52f1736b 100644 --- a/src/locales/fr.ts +++ b/src/locales/fr.ts @@ -465,7 +465,6 @@ const fr = { builtinPreviewsHeading: 'Aperçus des polices intégrées', builtinPreviewsHint: 'Lie un TTF local à un ID de police intégrée (0, A-H) pour que l\'éditeur montre à quoi ressemble vraiment cette police. Non émis dans ZPL.', addBuiltinPreview: 'Lier police intégrée', - builtinPreviewsTeaser: 'Voir à quoi ressemblent les polices intégrées ? Voir la section ci-dessous.', usedAsPreview: 'aperçu pour', noPreviewFont: 'Choisir une police…', }, diff --git a/src/locales/he.ts b/src/locales/he.ts index 09d1e9d6..5f2388e6 100644 --- a/src/locales/he.ts +++ b/src/locales/he.ts @@ -465,7 +465,6 @@ const he = { builtinPreviewsHeading: 'תצוגות מקדימות של גופנים מובנים', builtinPreviewsHint: 'קשר קובץ TTF מקומי לזיהוי גופן מובנה (0, A-H) כדי שהעורך יציג כיצד הגופן באמת נראה. לא נפלט ל-ZPL.', addBuiltinPreview: 'קשר גופן מובנה', - builtinPreviewsTeaser: 'רוצה לראות איך נראים גופנים מובנים? ראה את הסעיף למטה.', usedAsPreview: 'תצוגה עבור', noPreviewFont: 'בחר גופן…', }, diff --git a/src/locales/hr.ts b/src/locales/hr.ts index d4781067..71db443a 100644 --- a/src/locales/hr.ts +++ b/src/locales/hr.ts @@ -465,7 +465,6 @@ const hr = { builtinPreviewsHeading: 'Pregledi ugrađenih fontova', builtinPreviewsHint: 'Povežite lokalni TTF s ugrađenim ID-om fonta (0, A-H) tako da uređivač prikazuje kako taj font zaista izgleda. Ne emitira se u ZPL.', addBuiltinPreview: 'Poveži ugrađeni font', - builtinPreviewsTeaser: 'Želite vidjeti kako izgledaju ugrađeni fontovi? Pogledajte odjeljak ispod.', usedAsPreview: 'pregled za', noPreviewFont: 'Odaberite font…', }, diff --git a/src/locales/hu.ts b/src/locales/hu.ts index 783efa9e..857de47f 100644 --- a/src/locales/hu.ts +++ b/src/locales/hu.ts @@ -465,7 +465,6 @@ const hu = { builtinPreviewsHeading: 'Beépített betűtípus előnézetek', builtinPreviewsHint: 'Köss össze egy helyi TTF-et egy beépített betűtípus-azonosítóval (0, A-H), hogy a szerkesztő mutassa, hogyan néz ki valójában az a betűtípus. Nem kerül ZPL-be.', addBuiltinPreview: 'Beépített betűtípus kötése', - builtinPreviewsTeaser: 'Szeretnéd látni, hogyan néznek ki a beépített betűtípusok? Lásd az alábbi szakaszt.', usedAsPreview: 'előnézet', noPreviewFont: 'Válassz betűtípust…', }, diff --git a/src/locales/it.ts b/src/locales/it.ts index a4ab2ba1..5bf09103 100644 --- a/src/locales/it.ts +++ b/src/locales/it.ts @@ -465,7 +465,6 @@ const it = { builtinPreviewsHeading: 'Anteprime caratteri integrati', builtinPreviewsHint: 'Associa un TTF locale a un ID di carattere integrato (0, A-H) in modo che l\'editor mostri come appare realmente quel carattere. Non emesso in ZPL.', addBuiltinPreview: 'Associa carattere integrato', - builtinPreviewsTeaser: 'Vuoi vedere come appaiono i caratteri integrati? Vedi sezione sotto.', usedAsPreview: 'anteprima per', noPreviewFont: 'Scegli un carattere…', }, diff --git a/src/locales/ja.ts b/src/locales/ja.ts index e7dc7a81..0eb26d0e 100644 --- a/src/locales/ja.ts +++ b/src/locales/ja.ts @@ -465,7 +465,6 @@ const ja = { builtinPreviewsHeading: '内蔵フォントのプレビュー', builtinPreviewsHint: 'ローカルTTFを内蔵フォントID(0、A-H)にバインドして、エディタでそのフォントの実際の見た目を表示します。ZPLには出力されません。', addBuiltinPreview: '内蔵フォントをバインド', - builtinPreviewsTeaser: '内蔵フォントの表示を確認しますか?下のセクションを参照してください。', usedAsPreview: 'プレビュー', noPreviewFont: 'フォントを選択…', }, diff --git a/src/locales/ko.ts b/src/locales/ko.ts index e23d8ed2..9880b511 100644 --- a/src/locales/ko.ts +++ b/src/locales/ko.ts @@ -465,7 +465,6 @@ const ko = { builtinPreviewsHeading: '내장 글꼴 미리보기', builtinPreviewsHint: '로컬 TTF를 내장 글꼴 ID(0, A-H)에 바인딩하면 편집기에 해당 글꼴의 실제 모양이 표시됩니다. ZPL에 출력되지 않습니다.', addBuiltinPreview: '내장 글꼴 바인딩', - builtinPreviewsTeaser: '내장 글꼴이 어떻게 보이는지 확인하시겠습니까? 아래 섹션을 참조하세요.', usedAsPreview: '미리보기', noPreviewFont: '글꼴 선택…', }, diff --git a/src/locales/lt.ts b/src/locales/lt.ts index c322e0c1..a7196bed 100644 --- a/src/locales/lt.ts +++ b/src/locales/lt.ts @@ -465,7 +465,6 @@ const lt = { builtinPreviewsHeading: 'Įmontuotų šriftų peržiūros', builtinPreviewsHint: 'Susiekite vietinį TTF su įmontuoto šrifto ID (0, A-H), kad redaktorius rodytų, kaip tas šriftas iš tikrųjų atrodo. Neperduodama į ZPL.', addBuiltinPreview: 'Susieti įmontuotą šriftą', - builtinPreviewsTeaser: 'Norite pamatyti, kaip atrodo įmontuoti šriftai? Žiūrėkite skyrių žemiau.', usedAsPreview: 'peržiūra', noPreviewFont: 'Pasirinkite šriftą…', }, diff --git a/src/locales/lv.ts b/src/locales/lv.ts index 13c00312..c930c049 100644 --- a/src/locales/lv.ts +++ b/src/locales/lv.ts @@ -465,7 +465,6 @@ const lv = { builtinPreviewsHeading: 'Iebūvēto fontu priekšskatījumi', builtinPreviewsHint: 'Saistīt vietējo TTF ar iebūvētu fonta ID (0, A-H), lai redaktors parādītu, kā šis fonts patiesībā izskatās. Netiek izvadīts uz ZPL.', addBuiltinPreview: 'Saistīt iebūvēto fontu', - builtinPreviewsTeaser: 'Vēlaties redzēt, kā izskatās iebūvētie fonti? Skatiet sadaļu zemāk.', usedAsPreview: 'priekšskatījums', noPreviewFont: 'Izvēlieties fontu…', }, diff --git a/src/locales/nl.ts b/src/locales/nl.ts index 3cd9ac88..da274dbf 100644 --- a/src/locales/nl.ts +++ b/src/locales/nl.ts @@ -465,7 +465,6 @@ const nl = { builtinPreviewsHeading: 'Voorvertoningen van ingebouwde lettertypen', builtinPreviewsHint: 'Koppel een lokale TTF aan een ingebouwd lettertype-ID (0, A-H) zodat de editor toont hoe dat lettertype er echt uitziet. Wordt niet naar ZPL gestuurd.', addBuiltinPreview: 'Ingebouwd lettertype koppelen', - builtinPreviewsTeaser: 'Wil je zien hoe ingebouwde lettertypen eruitzien? Zie de sectie hieronder.', usedAsPreview: 'voorvertoning voor', noPreviewFont: 'Kies een lettertype…', }, diff --git a/src/locales/no.ts b/src/locales/no.ts index 0a589c86..f5fb89ca 100644 --- a/src/locales/no.ts +++ b/src/locales/no.ts @@ -465,7 +465,6 @@ const no = { builtinPreviewsHeading: 'Forhåndsvisninger av innebygde skrifter', builtinPreviewsHint: 'Bind en lokal TTF til en innebygd skrift-ID (0, A-H) slik at editoren viser hvordan skriften faktisk ser ut. Sendes ikke til ZPL.', addBuiltinPreview: 'Bind innebygd skrift', - builtinPreviewsTeaser: 'Vil du se hvordan innebygde skrifter ser ut? Se seksjonen nedenfor.', usedAsPreview: 'visning for', noPreviewFont: 'Velg en skrift…', }, diff --git a/src/locales/pl.ts b/src/locales/pl.ts index fe697bd5..cc75ff95 100644 --- a/src/locales/pl.ts +++ b/src/locales/pl.ts @@ -465,7 +465,6 @@ const pl = { builtinPreviewsHeading: 'Podglądy wbudowanych czcionek', builtinPreviewsHint: 'Powiąż lokalny TTF z wbudowanym ID czcionki (0, A-H), aby edytor pokazywał, jak naprawdę wygląda ta czcionka. Nie jest wysyłane do ZPL.', addBuiltinPreview: 'Powiąż wbudowaną czcionkę', - builtinPreviewsTeaser: 'Chcesz zobaczyć, jak wyglądają wbudowane czcionki? Zobacz sekcję poniżej.', usedAsPreview: 'podgląd dla', noPreviewFont: 'Wybierz czcionkę…', }, diff --git a/src/locales/pt.ts b/src/locales/pt.ts index 8aa205a4..17a9cddc 100644 --- a/src/locales/pt.ts +++ b/src/locales/pt.ts @@ -465,7 +465,6 @@ const pt = { builtinPreviewsHeading: 'Pré-visualizações de fontes integradas', builtinPreviewsHint: 'Vincule um TTF local a um ID de fonte integrada (0, A-H) para que o editor mostre como essa fonte realmente parece. Não emitido para ZPL.', addBuiltinPreview: 'Vincular fonte integrada', - builtinPreviewsTeaser: 'Quer ver como as fontes integradas aparecem? Veja a seção abaixo.', usedAsPreview: 'pré-visualização para', noPreviewFont: 'Escolha uma fonte…', }, diff --git a/src/locales/ro.ts b/src/locales/ro.ts index 8c256fdb..c3f1932c 100644 --- a/src/locales/ro.ts +++ b/src/locales/ro.ts @@ -465,7 +465,6 @@ const ro = { builtinPreviewsHeading: 'Previzualizări fonturi integrate', builtinPreviewsHint: 'Leagă un TTF local de un ID de font integrat (0, A-H) pentru ca editorul să arate cum arată cu adevărat acel font. Nu este emis în ZPL.', addBuiltinPreview: 'Leagă font integrat', - builtinPreviewsTeaser: 'Vrei să vezi cum arată fonturile integrate? Vezi secțiunea de mai jos.', usedAsPreview: 'previzualizare', noPreviewFont: 'Alege un font…', }, diff --git a/src/locales/sk.ts b/src/locales/sk.ts index 4f5bbae2..78510bbd 100644 --- a/src/locales/sk.ts +++ b/src/locales/sk.ts @@ -465,7 +465,6 @@ const sk = { builtinPreviewsHeading: 'Náhľady vstavaných písiem', builtinPreviewsHint: 'Priraďte miestny TTF k vstavanému ID písma (0, A-H), aby editor zobrazoval, ako toto písmo skutočne vyzerá. Nevysiela sa do ZPL.', addBuiltinPreview: 'Priradiť vstavané písmo', - builtinPreviewsTeaser: 'Chcete vidieť, ako vyzerajú vstavané písma? Pozrite si sekciu nižšie.', usedAsPreview: 'náhľad pre', noPreviewFont: 'Vyberte písmo…', }, diff --git a/src/locales/sl.ts b/src/locales/sl.ts index 768d8837..c9c83298 100644 --- a/src/locales/sl.ts +++ b/src/locales/sl.ts @@ -465,7 +465,6 @@ const sl = { builtinPreviewsHeading: 'Predogledi vgrajenih pisav', builtinPreviewsHint: 'Povežite lokalno TTF datoteko z vgrajenim ID-jem pisave (0, A-H), da urejevalnik prikaže, kako ta pisava dejansko izgleda. Ni oddano v ZPL.', addBuiltinPreview: 'Poveži vgrajeno pisavo', - builtinPreviewsTeaser: 'Želite videti, kako izgledajo vgrajene pisave? Glejte spodnji razdelek.', usedAsPreview: 'predogled za', noPreviewFont: 'Izberite pisavo…', }, diff --git a/src/locales/sr.ts b/src/locales/sr.ts index 55c19666..4c131f76 100644 --- a/src/locales/sr.ts +++ b/src/locales/sr.ts @@ -465,7 +465,6 @@ const sr = { builtinPreviewsHeading: 'Прегледи уграђених фонтова', builtinPreviewsHint: 'Повежите локални TTF са уграђеним ID-јем фонта (0, A-H) тако да уредник прикаже како тај фонт заиста изгледа. Не емитује се у ZPL.', addBuiltinPreview: 'Повежи уграђени фонт', - builtinPreviewsTeaser: 'Желите да видите како изгледају уграђени фонтови? Погледајте секцију испод.', usedAsPreview: 'преглед за', noPreviewFont: 'Изаберите фонт…', }, diff --git a/src/locales/sv.ts b/src/locales/sv.ts index d9272b05..bd95a933 100644 --- a/src/locales/sv.ts +++ b/src/locales/sv.ts @@ -465,7 +465,6 @@ const sv = { builtinPreviewsHeading: 'Förhandsvisningar av inbyggda typsnitt', builtinPreviewsHint: 'Bind ett lokalt TTF till ett inbyggt typsnitts-ID (0, A-H) så att editorn visar hur det typsnittet faktiskt ser ut. Sänds inte till ZPL.', addBuiltinPreview: 'Bind inbyggt typsnitt', - builtinPreviewsTeaser: 'Vill du se hur inbyggda typsnitt ser ut? Se avsnittet nedan.', usedAsPreview: 'visning för', noPreviewFont: 'Välj ett typsnitt…', }, diff --git a/src/locales/tr.ts b/src/locales/tr.ts index a314e074..a9cd7fe6 100644 --- a/src/locales/tr.ts +++ b/src/locales/tr.ts @@ -465,7 +465,6 @@ const tr = { builtinPreviewsHeading: 'Yerleşik yazı tipi önizlemeleri', builtinPreviewsHint: 'Yerel bir TTF dosyasını yerleşik bir yazı tipi kimliğine (0, A-H) bağlayın, böylece editör o yazı tipinin gerçekte nasıl göründüğünü gösterir. ZPL\'ye yayılmaz.', addBuiltinPreview: 'Yerleşik yazı tipi bağla', - builtinPreviewsTeaser: 'Yerleşik yazı tiplerinin nasıl göründüğünü görmek ister misiniz? Aşağıdaki bölüme bakın.', usedAsPreview: 'önizleme', noPreviewFont: 'Bir yazı tipi seç…', }, diff --git a/src/locales/zh-hans.ts b/src/locales/zh-hans.ts index fb398e82..ff637fca 100644 --- a/src/locales/zh-hans.ts +++ b/src/locales/zh-hans.ts @@ -465,7 +465,6 @@ const zhHans = { builtinPreviewsHeading: '内置字体预览', builtinPreviewsHint: '将本地TTF绑定到内置字体ID(0、A-H),使编辑器显示该字体的实际外观。不会发送到ZPL。', addBuiltinPreview: '绑定内置字体', - builtinPreviewsTeaser: '想看内置字体的样子?请参见下方区块。', usedAsPreview: '预览', noPreviewFont: '选择字体…', }, diff --git a/src/locales/zh-hant.ts b/src/locales/zh-hant.ts index 63f1c926..b0a47096 100644 --- a/src/locales/zh-hant.ts +++ b/src/locales/zh-hant.ts @@ -465,7 +465,6 @@ const zhHant = { builtinPreviewsHeading: '內建字型預覽', builtinPreviewsHint: '將本地TTF綁定到內建字型ID(0、A-H),使編輯器顯示該字型的實際外觀。不會發送到ZPL。', addBuiltinPreview: '綁定內建字型', - builtinPreviewsTeaser: '想看內建字型的樣子?請參見下方區塊。', usedAsPreview: '預覽', noPreviewFont: '選擇字型…', }, From fb575d6ec2f8c633b787906ee49eb250a6d56f0d Mon Sep 17 00:00:00 2001 From: u8array Date: Wed, 20 May 2026 22:16:47 +0200 Subject: [PATCH 21/22] fix(fonts): allow transient edits and clearing of preview bindings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two related correctness fixes flagged in the final smell sweep: - The schema rejected the rows the UI produced while a user was typing. addManual creates {alias, path: ''} and addBuiltinPreview creates {alias, previewFontName: ''} so the new row renders before the user supplies a value; the old .min(1) on each field plus the "at least one" refine would have wiped both on rehydrate. Drops the per-field min and the at-least-one refine, keeping only the embedInZpl refine ("~DY needs both sides"). The emit guards in zplGenerator already skip empty-alias / empty-path rows, so loosening the schema does not produce invalid ZPL. - updateBuiltinPreview merged the patch with , which made the "Pick a font…" option a no-op because Nullish coalescing ignores undefined patches. Switched to a spread so an explicit actually clears the binding. --- src/components/Fonts/FontManager.tsx | 23 ++++++++++++----------- src/types/ObjectType.ts | 20 ++++++++++---------- 2 files changed, 22 insertions(+), 21 deletions(-) diff --git a/src/components/Fonts/FontManager.tsx b/src/components/Fonts/FontManager.tsx index c7e5ac90..8855e2d8 100644 --- a/src/components/Fonts/FontManager.tsx +++ b/src/components/Fonts/FontManager.tsx @@ -160,23 +160,24 @@ export function FontManager() { }; // Built-in-preview rows are keyed by alias (one binding per ID). + // Patch is applied via spread so an explicit `undefined` actually + // clears the field — the previous `?? m.previewFontName` fallback + // turned "Pick a font…" into a no-op because Nullish-coalescing + // ignores undefined patches. const updateBuiltinPreview = ( currentAlias: string, patch: Partial, ) => { const list = customFonts ?? []; replaceList( - list.map((m) => - m.alias === currentAlias && m.path === undefined - ? { - ...m, - alias: patch.alias !== undefined - ? normalizeAlias(patch.alias) || m.alias - : m.alias, - previewFontName: patch.previewFontName ?? m.previewFontName, - } - : m, - ), + list.map((m) => { + if (m.alias !== currentAlias || m.path !== undefined) return m; + const next: CustomFontMapping = { ...m, ...patch }; + if (patch.alias !== undefined) { + next.alias = normalizeAlias(patch.alias) || m.alias; + } + return next; + }), ); }; diff --git a/src/types/ObjectType.ts b/src/types/ObjectType.ts index 7048915d..f969d3e6 100644 --- a/src/types/ObjectType.ts +++ b/src/types/ObjectType.ts @@ -18,21 +18,21 @@ import { z } from 'zod'; * declares "this alias maps to a file already on the printer"; * canvas falls back to PrintLab ZPL because it has no bytes. * - * `embedInZpl` toggles a `~DY{path}` upload of the local TTF bytes so - * the printer (and Labelary, if it honours `~DY`) renders with the - * uploaded font even when the file is not yet on the device. Requires - * both `path` and `previewFontName`. */ + * Both `path` and `previewFontName` allow empty strings: while the user + * is editing a fresh row the value may transiently be blank, and we + * want that state to survive a persist/rehydrate round-trip so the + * reload lands on the same row instead of dropping it. Completeness + * ("at least one of the two is non-empty") is enforced at emit time + * via the existing `if (m.alias && m.path)` guards in zplGenerator — + * not as a schema-level refine, because the schema fronts the + * persisted store and the store has to allow in-progress edits. */ export const customFontMappingSchema = z .object({ alias: z.string().regex(/^[A-Z0-9]$/), - path: z.string().min(1).optional(), - previewFontName: z.string().min(1).optional(), + path: z.string().optional(), + previewFontName: z.string().optional(), embedInZpl: z.boolean().optional(), }) - .refine((m) => !!m.path || !!m.previewFontName, { - message: - "Custom font mapping needs at least a printer path or a preview TTF", - }) .refine((m) => !m.embedInZpl || (!!m.path && !!m.previewFontName), { message: "embedInZpl requires both a printer path (~DY target) and a preview TTF (~DY bytes)", From 693d44604c473431e35e578de333a90343f6a0ba Mon Sep 17 00:00:00 2001 From: u8array Date: Wed, 20 May 2026 22:34:18 +0200 Subject: [PATCH 22/22] fix(zpl): address PR #78 review nits MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Hex-encode the ~DY payload via Array.from + map + join. The previous string-concat loop is fine functionally but allocates a fresh string on every byte; the declarative form scales better for the larger fonts (the cap is 4 MB, ~8 MB hex). - Swap data.substr() for data.slice(i*2, i*2+2) in the parser's ~DY decoder. substr is legacy and discouraged in modern lint configs. Both are stylistic — no behaviour change, generator output and parser acceptance are byte-identical to the previous form. --- src/lib/zplGenerator.ts | 5 +++-- src/lib/zplParser.ts | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/lib/zplGenerator.ts b/src/lib/zplGenerator.ts index f9ee6032..b0183491 100644 --- a/src/lib/zplGenerator.ts +++ b/src/lib/zplGenerator.ts @@ -33,8 +33,9 @@ function formatDownloadObject(m: CustomFontMapping): string | undefined { // Only TTF/OTF are uploaded today; both map to extension code T // (TrueType is the only TTF-family identifier in the spec). if (ext !== 'TTF' && ext !== 'OTF') return undefined; - let hex = ''; - for (const b of bytes) hex += b.toString(16).padStart(2, '0').toUpperCase(); + const hex = Array.from(bytes, (b) => b.toString(16).padStart(2, '0')) + .join('') + .toUpperCase(); return `~DY${drive}${stem},A,T,${bytes.length},,${hex}`; } diff --git a/src/lib/zplParser.ts b/src/lib/zplParser.ts index 3e6bb71d..7872806c 100644 --- a/src/lib/zplParser.ts +++ b/src/lib/zplParser.ts @@ -1395,7 +1395,7 @@ export function parseZPL(zpl: string, dpmm = 8): ParsedZPL { } const bytes = new Uint8Array(size); for (let i = 0; i < size; i++) { - const byteHex = data.substr(i * 2, 2); + const byteHex = data.slice(i * 2, i * 2 + 2); const b = parseInt(byteHex, 16); if (isNaN(b)) { browserLimit.push(`~DY${rest.slice(0, 80)}…`);