diff --git a/src/components/Canvas/KonvaObject.tsx b/src/components/Canvas/KonvaObject.tsx index 40bc691..6b90f33 100644 --- a/src/components/Canvas/KonvaObject.tsx +++ b/src/components/Canvas/KonvaObject.tsx @@ -1,4 +1,5 @@ import { useState, useEffect } from "react"; +import { getFontFamily, useFontCacheVersion } from "../../lib/fontCache"; import { Circle, Ellipse, @@ -370,6 +371,7 @@ function KonvaObjectInner({ onChange, snap, }: Props) { + useFontCacheVersion(); // If the object was imported with ^FT (baseline position), compute display offset. // ^FT positions text at the baseline; ^FO at the top-left corner. // We need to convert FT→FO for canvas rendering only. @@ -462,6 +464,9 @@ function KonvaObjectInner({ if (obj.type === "text") { const p = obj.props; const fontSize = Math.max(dotsToPx(p.fontHeight, scale, dpmm) / 1.3, 6); + const fontFamily = p.printerFontName + ? (getFontFamily(p.printerFontName) ?? "'Roboto Condensed', sans-serif") + : "'Roboto Condensed', sans-serif"; const zplRotationDeg: Record = { N: 0, R: 90, @@ -496,7 +501,7 @@ function KonvaObjectInner({ (); +const listeners = new Set<() => void>(); + +function notify(): void { + listeners.forEach(fn => fn()); +} + +/** Subscribe to cache changes. Returns an unsubscribe function. */ +export function subscribe(fn: () => void): () => void { + listeners.add(fn); + return () => listeners.delete(fn); +} + +/** Hook: returns a version counter that increments whenever the font cache changes. */ +export function useFontCacheVersion(): number { + const [version, setVersion] = useState(0); + useEffect(() => subscribe(() => setVersion(v => v + 1)), []); + return version; +} + +function printerNameToFamily(name: string): string { + // Strip extension, prefix with "zpl-" to avoid collisions with system fonts + return 'zpl-' + name.replace(/\.[^.]+$/, '').toUpperCase(); +} + +async function registerFontFace(entry: CachedFont): Promise { + try { + const face = new FontFace(entry.fontFamily, `url(${entry.dataUrl})`); + await face.load(); + document.fonts.add(face); + } catch { + // Font invalid or API unavailable — canvas will fall back to default font + } +} + +// Hydrate from localStorage on module load +for (let i = 0; i < localStorage.length; i++) { + const key = localStorage.key(i); + if (!key?.startsWith(LS_PREFIX)) continue; + try { + const entry = JSON.parse(localStorage.getItem(key) ?? 'null') as CachedFont; + cache.set(entry.name, entry); + // Re-register fonts asynchronously — canvas renders after React mounts + void registerFontFace(entry); + } catch { + // ignore corrupt entries + } +} + +/** Look up a cached font by printer filename (case-insensitive). */ +export function getFont(printerName: string): CachedFont | undefined { + return cache.get(printerName.toUpperCase()); +} + +/** Return the CSS font-family for a printer font name, or undefined if not loaded. */ +export function getFontFamily(printerName: string): string | undefined { + return cache.get(printerName.toUpperCase())?.fontFamily; +} + +export function getAllFonts(): CachedFont[] { + return [...cache.values()]; +} + +/** Load a TTF/OTF File into the cache under the given printer font name. */ +export async function loadFontFile(file: File, printerName: string): Promise { + return new Promise((resolve, reject) => { + const reader = new FileReader(); + reader.onload = async () => { + const dataUrl = reader.result as string; + const name = printerName.toUpperCase(); + const fontFamily = printerNameToFamily(name); + const entry: CachedFont = { id: crypto.randomUUID(), name, dataUrl, fontFamily }; + cache.set(name, entry); + try { + localStorage.setItem(LS_PREFIX + name, JSON.stringify(entry)); + } catch { + // localStorage full — font stays in memory only + } + await registerFontFace(entry); + notify(); + resolve(entry); + }; + reader.onerror = () => reject(new Error(`Failed to read font: ${file.name}`)); + reader.readAsDataURL(file); + }); +} + +export function removeFont(printerName: string): void { + const name = printerName.toUpperCase(); + cache.delete(name); + localStorage.removeItem(LS_PREFIX + name); + notify(); +} diff --git a/src/lib/zplParser.ts b/src/lib/zplParser.ts index f773c79..b5beb94 100644 --- a/src/lib/zplParser.ts +++ b/src/lib/zplParser.ts @@ -245,6 +245,9 @@ export function parseZPL(zpl: string, dpmm = 8): ParsedZPL { let cbRowHeight = 10; let cbSecurity: CodablockProps['securityLevel'] = 'Y'; + // ^A@ pending printer font name (e.g. "ARIAL.TTF") + let pendingPrinterFontName: string | undefined; + // ^SN / ^SF serialization state let snPending = false; let snIncrement = 1; @@ -286,7 +289,9 @@ export function parseZPL(zpl: string, dpmm = 8): ParsedZPL { fontWidth: textW, rotation: textRot, reverse: (lrActive || frActive) || undefined, + printerFontName: pendingPrinterFontName, }; + pendingPrinterFontName = undefined; if (fbWidth > 0) { textProps.blockWidth = fbWidth; textProps.blockLines = fbLines; @@ -1055,6 +1060,10 @@ export function parseZPL(zpl: string, dpmm = 8): ParsedZPL { textRot = (rest[0] as TextProps['rotation']) ?? fwRotation; textH = int(p[1]) || cfHeight || 30; textW = int(p[2]) || cfWidth || 0; + // Extract font filename from "E:ARIAL.TTF" or "R:FONT.TTF" + const fontRef = p[3] ?? ''; + const colonIdx = fontRef.indexOf(':'); + pendingPrinterFontName = (colonIdx >= 0 ? fontRef.slice(colonIdx + 1) : fontRef) || undefined; partialCmds.add('^A@'); break; } diff --git a/src/locales/ar.ts b/src/locales/ar.ts index 0fd4295..5cbd6bd 100644 --- a/src/locales/ar.ts +++ b/src/locales/ar.ts @@ -109,6 +109,12 @@ const ar = { justifyC: 'C — وسط', justifyR: 'R — يمين', justifyJ: 'J — ضبط', + printerFont: 'Printer font (^A@)', + uploadFont: 'Upload font file', + uploadingFont: 'Uploading…', + replaceFont: 'Replace font', + fontLoaded: 'Font loaded', + fontMissing: 'Font not loaded', }, code128: { content: 'المحتوى', diff --git a/src/locales/bg.ts b/src/locales/bg.ts index a6628b5..db59620 100644 --- a/src/locales/bg.ts +++ b/src/locales/bg.ts @@ -109,6 +109,12 @@ const bg = { justifyC: 'C — Център', justifyR: 'R — Дясно', justifyJ: 'J — Двустранно', + printerFont: 'Printer font (^A@)', + uploadFont: 'Upload font file', + uploadingFont: 'Uploading…', + replaceFont: 'Replace font', + fontLoaded: 'Font loaded', + fontMissing: 'Font not loaded', }, code128: { content: 'Съдържание', diff --git a/src/locales/cs.ts b/src/locales/cs.ts index 84227a5..d7629e1 100644 --- a/src/locales/cs.ts +++ b/src/locales/cs.ts @@ -109,6 +109,12 @@ const cs = { justifyC: 'C — Na střed', justifyR: 'R — Vpravo', justifyJ: 'J — Do bloku', + printerFont: 'Printer font (^A@)', + uploadFont: 'Upload font file', + uploadingFont: 'Uploading…', + replaceFont: 'Replace font', + fontLoaded: 'Font loaded', + fontMissing: 'Font not loaded', }, code128: { content: 'Obsah', diff --git a/src/locales/da.ts b/src/locales/da.ts index dd23a2b..8734198 100644 --- a/src/locales/da.ts +++ b/src/locales/da.ts @@ -109,6 +109,12 @@ const da = { justifyC: 'C — Centreret', justifyR: 'R — Højre', justifyJ: 'J — Lige margener', + printerFont: 'Printer font (^A@)', + uploadFont: 'Upload font file', + uploadingFont: 'Uploading…', + replaceFont: 'Replace font', + fontLoaded: 'Font loaded', + fontMissing: 'Font not loaded', }, code128: { content: 'Indhold', diff --git a/src/locales/de.ts b/src/locales/de.ts index 1cd3f71..d14fc3f 100644 --- a/src/locales/de.ts +++ b/src/locales/de.ts @@ -109,6 +109,12 @@ const de = { justifyC: 'C — Zentriert', justifyR: 'R — Rechts', justifyJ: 'J — Blocksatz', + printerFont: 'Printer font (^A@)', + uploadFont: 'Upload font file', + uploadingFont: 'Uploading…', + replaceFont: 'Replace font', + fontLoaded: 'Font loaded', + fontMissing: 'Font not loaded', }, code128: { content: 'Inhalt', diff --git a/src/locales/el.ts b/src/locales/el.ts index c6e602b..969f1a7 100644 --- a/src/locales/el.ts +++ b/src/locales/el.ts @@ -109,6 +109,12 @@ const el = { justifyC: 'C — Κέντρο', justifyR: 'R — Δεξιά', justifyJ: 'J — Πλήρης', + printerFont: 'Printer font (^A@)', + uploadFont: 'Upload font file', + uploadingFont: 'Uploading…', + replaceFont: 'Replace font', + fontLoaded: 'Font loaded', + fontMissing: 'Font not loaded', }, code128: { content: 'Περιεχόμενο', diff --git a/src/locales/en.ts b/src/locales/en.ts index 2519e82..6c779c8 100644 --- a/src/locales/en.ts +++ b/src/locales/en.ts @@ -109,6 +109,12 @@ const en = { justifyC: 'C — Center', justifyR: 'R — Right', justifyJ: 'J — Justified', + printerFont: 'Printer font (^A@)', + uploadFont: 'Upload font file', + uploadingFont: 'Uploading…', + replaceFont: 'Replace font', + fontLoaded: 'Font loaded', + fontMissing: 'Font not loaded', }, code128: { content: 'Content', diff --git a/src/locales/es.ts b/src/locales/es.ts index cdcc727..deae3bd 100644 --- a/src/locales/es.ts +++ b/src/locales/es.ts @@ -109,6 +109,12 @@ const es = { justifyC: 'C — Centro', justifyR: 'R — Derecha', justifyJ: 'J — Justificado', + printerFont: 'Printer font (^A@)', + uploadFont: 'Upload font file', + uploadingFont: 'Uploading…', + replaceFont: 'Replace font', + fontLoaded: 'Font loaded', + fontMissing: 'Font not loaded', }, code128: { content: 'Contenido', diff --git a/src/locales/et.ts b/src/locales/et.ts index 83bcae6..8b0bf63 100644 --- a/src/locales/et.ts +++ b/src/locales/et.ts @@ -109,6 +109,12 @@ const et = { justifyC: 'C — Keskele', justifyR: 'R — Paremale', justifyJ: 'J — Rööpjoondus', + printerFont: 'Printer font (^A@)', + uploadFont: 'Upload font file', + uploadingFont: 'Uploading…', + replaceFont: 'Replace font', + fontLoaded: 'Font loaded', + fontMissing: 'Font not loaded', }, code128: { content: 'Sisu', diff --git a/src/locales/fa.ts b/src/locales/fa.ts index 6669c41..0b356db 100644 --- a/src/locales/fa.ts +++ b/src/locales/fa.ts @@ -109,6 +109,12 @@ const fa = { justifyC: 'C — وسط', justifyR: 'R — راست', justifyJ: 'J — تنظیم', + printerFont: 'Printer font (^A@)', + uploadFont: 'Upload font file', + uploadingFont: 'Uploading…', + replaceFont: 'Replace font', + fontLoaded: 'Font loaded', + fontMissing: 'Font not loaded', }, code128: { content: 'محتوا', diff --git a/src/locales/fi.ts b/src/locales/fi.ts index 0f678d0..cc5a9d9 100644 --- a/src/locales/fi.ts +++ b/src/locales/fi.ts @@ -109,6 +109,12 @@ const fi = { justifyC: 'C — Keskitetty', justifyR: 'R — Oikea', justifyJ: 'J — Tasattu', + printerFont: 'Printer font (^A@)', + uploadFont: 'Upload font file', + uploadingFont: 'Uploading…', + replaceFont: 'Replace font', + fontLoaded: 'Font loaded', + fontMissing: 'Font not loaded', }, code128: { content: 'Sisältö', diff --git a/src/locales/fr.ts b/src/locales/fr.ts index 51dcfc1..1e31225 100644 --- a/src/locales/fr.ts +++ b/src/locales/fr.ts @@ -109,6 +109,12 @@ const fr = { justifyC: 'C — Centré', justifyR: 'R — Droite', justifyJ: 'J — Justifié', + printerFont: 'Printer font (^A@)', + uploadFont: 'Upload font file', + uploadingFont: 'Uploading…', + replaceFont: 'Replace font', + fontLoaded: 'Font loaded', + fontMissing: 'Font not loaded', }, code128: { content: 'Contenu', diff --git a/src/locales/he.ts b/src/locales/he.ts index 3f5dfb4..a640e42 100644 --- a/src/locales/he.ts +++ b/src/locales/he.ts @@ -109,6 +109,12 @@ const he = { justifyC: 'C — מרכז', justifyR: 'R — ימין', justifyJ: 'J — מיושר', + printerFont: 'Printer font (^A@)', + uploadFont: 'Upload font file', + uploadingFont: 'Uploading…', + replaceFont: 'Replace font', + fontLoaded: 'Font loaded', + fontMissing: 'Font not loaded', }, code128: { content: 'תוכן', diff --git a/src/locales/hr.ts b/src/locales/hr.ts index f9f028c..8b9f0d3 100644 --- a/src/locales/hr.ts +++ b/src/locales/hr.ts @@ -109,6 +109,12 @@ const hr = { justifyC: 'C — Sredina', justifyR: 'R — Desno', justifyJ: 'J — Obostrano', + printerFont: 'Printer font (^A@)', + uploadFont: 'Upload font file', + uploadingFont: 'Uploading…', + replaceFont: 'Replace font', + fontLoaded: 'Font loaded', + fontMissing: 'Font not loaded', }, code128: { content: 'Sadržaj', diff --git a/src/locales/hu.ts b/src/locales/hu.ts index e22aa56..e52b184 100644 --- a/src/locales/hu.ts +++ b/src/locales/hu.ts @@ -109,6 +109,12 @@ const hu = { justifyC: 'C — Középre', justifyR: 'R — Jobbra', justifyJ: 'J — Sorkizárt', + printerFont: 'Printer font (^A@)', + uploadFont: 'Upload font file', + uploadingFont: 'Uploading…', + replaceFont: 'Replace font', + fontLoaded: 'Font loaded', + fontMissing: 'Font not loaded', }, code128: { content: 'Tartalom', diff --git a/src/locales/it.ts b/src/locales/it.ts index 427e2e4..316d681 100644 --- a/src/locales/it.ts +++ b/src/locales/it.ts @@ -109,6 +109,12 @@ const it = { justifyC: 'C — Centro', justifyR: 'R — Destra', justifyJ: 'J — Giustificato', + printerFont: 'Printer font (^A@)', + uploadFont: 'Upload font file', + uploadingFont: 'Uploading…', + replaceFont: 'Replace font', + fontLoaded: 'Font loaded', + fontMissing: 'Font not loaded', }, code128: { content: 'Contenuto', diff --git a/src/locales/ja.ts b/src/locales/ja.ts index 95febb1..0e3f830 100644 --- a/src/locales/ja.ts +++ b/src/locales/ja.ts @@ -109,6 +109,12 @@ const ja = { justifyC: 'C — 中央揃え', justifyR: 'R — 右揃え', justifyJ: 'J — 両端揃え', + printerFont: 'Printer font (^A@)', + uploadFont: 'Upload font file', + uploadingFont: 'Uploading…', + replaceFont: 'Replace font', + fontLoaded: 'Font loaded', + fontMissing: 'Font not loaded', }, code128: { content: 'コンテンツ', diff --git a/src/locales/ko.ts b/src/locales/ko.ts index ab3d57d..c3cc09e 100644 --- a/src/locales/ko.ts +++ b/src/locales/ko.ts @@ -109,6 +109,12 @@ const ko = { justifyC: 'C — 가운데', justifyR: 'R — 오른쪽', justifyJ: 'J — 균등 배분', + printerFont: 'Printer font (^A@)', + uploadFont: 'Upload font file', + uploadingFont: 'Uploading…', + replaceFont: 'Replace font', + fontLoaded: 'Font loaded', + fontMissing: 'Font not loaded', }, code128: { content: '내용', diff --git a/src/locales/lt.ts b/src/locales/lt.ts index d38da50..8007fe9 100644 --- a/src/locales/lt.ts +++ b/src/locales/lt.ts @@ -109,6 +109,12 @@ const lt = { justifyC: 'C — Centras', justifyR: 'R — Dešinė', justifyJ: 'J — Abipusė', + printerFont: 'Printer font (^A@)', + uploadFont: 'Upload font file', + uploadingFont: 'Uploading…', + replaceFont: 'Replace font', + fontLoaded: 'Font loaded', + fontMissing: 'Font not loaded', }, code128: { content: 'Turinys', diff --git a/src/locales/lv.ts b/src/locales/lv.ts index d73ef07..50b2742 100644 --- a/src/locales/lv.ts +++ b/src/locales/lv.ts @@ -109,6 +109,12 @@ const lv = { justifyC: 'C — Centrēts', justifyR: 'R — Pa labi', justifyJ: 'J — Izlīdzināts', + printerFont: 'Printer font (^A@)', + uploadFont: 'Upload font file', + uploadingFont: 'Uploading…', + replaceFont: 'Replace font', + fontLoaded: 'Font loaded', + fontMissing: 'Font not loaded', }, code128: { content: 'Saturs', diff --git a/src/locales/nl.ts b/src/locales/nl.ts index 102c468..0391c85 100644 --- a/src/locales/nl.ts +++ b/src/locales/nl.ts @@ -109,6 +109,12 @@ const nl = { justifyC: 'C — Gecentreerd', justifyR: 'R — Rechts', justifyJ: 'J — Uitgevuld', + printerFont: 'Printer font (^A@)', + uploadFont: 'Upload font file', + uploadingFont: 'Uploading…', + replaceFont: 'Replace font', + fontLoaded: 'Font loaded', + fontMissing: 'Font not loaded', }, code128: { content: 'Inhoud', diff --git a/src/locales/no.ts b/src/locales/no.ts index 6636884..1e80c9b 100644 --- a/src/locales/no.ts +++ b/src/locales/no.ts @@ -109,6 +109,12 @@ const no = { justifyC: 'C — Sentrert', justifyR: 'R — Høyre', justifyJ: 'J — Blokkjustert', + printerFont: 'Printer font (^A@)', + uploadFont: 'Upload font file', + uploadingFont: 'Uploading…', + replaceFont: 'Replace font', + fontLoaded: 'Font loaded', + fontMissing: 'Font not loaded', }, code128: { content: 'Innhold', diff --git a/src/locales/pl.ts b/src/locales/pl.ts index 4a235ba..3c74f2b 100644 --- a/src/locales/pl.ts +++ b/src/locales/pl.ts @@ -109,6 +109,12 @@ const pl = { justifyC: 'C — Środek', justifyR: 'R — Prawo', justifyJ: 'J — Wyjustowane', + printerFont: 'Printer font (^A@)', + uploadFont: 'Upload font file', + uploadingFont: 'Uploading…', + replaceFont: 'Replace font', + fontLoaded: 'Font loaded', + fontMissing: 'Font not loaded', }, code128: { content: 'Zawartość', diff --git a/src/locales/pt.ts b/src/locales/pt.ts index b3c50b4..408dbef 100644 --- a/src/locales/pt.ts +++ b/src/locales/pt.ts @@ -109,6 +109,12 @@ const pt = { justifyC: 'C — Centro', justifyR: 'R — Direita', justifyJ: 'J — Justificado', + printerFont: 'Printer font (^A@)', + uploadFont: 'Upload font file', + uploadingFont: 'Uploading…', + replaceFont: 'Replace font', + fontLoaded: 'Font loaded', + fontMissing: 'Font not loaded', }, code128: { content: 'Conteúdo', diff --git a/src/locales/ro.ts b/src/locales/ro.ts index 628c1f7..2cc6e8a 100644 --- a/src/locales/ro.ts +++ b/src/locales/ro.ts @@ -109,6 +109,12 @@ const ro = { justifyC: 'C — Centru', justifyR: 'R — Dreapta', justifyJ: 'J — Justify', + printerFont: 'Printer font (^A@)', + uploadFont: 'Upload font file', + uploadingFont: 'Uploading…', + replaceFont: 'Replace font', + fontLoaded: 'Font loaded', + fontMissing: 'Font not loaded', }, code128: { content: 'Conținut', diff --git a/src/locales/sk.ts b/src/locales/sk.ts index 6f7e5fc..2cf7166 100644 --- a/src/locales/sk.ts +++ b/src/locales/sk.ts @@ -109,6 +109,12 @@ const sk = { justifyC: 'C — Na stred', justifyR: 'R — Vpravo', justifyJ: 'J — Do bloku', + printerFont: 'Printer font (^A@)', + uploadFont: 'Upload font file', + uploadingFont: 'Uploading…', + replaceFont: 'Replace font', + fontLoaded: 'Font loaded', + fontMissing: 'Font not loaded', }, code128: { content: 'Obsah', diff --git a/src/locales/sl.ts b/src/locales/sl.ts index d88a9c3..357c113 100644 --- a/src/locales/sl.ts +++ b/src/locales/sl.ts @@ -109,6 +109,12 @@ const sl = { justifyC: 'C — Sredina', justifyR: 'R — Desno', justifyJ: 'J — Obojestransko', + printerFont: 'Printer font (^A@)', + uploadFont: 'Upload font file', + uploadingFont: 'Uploading…', + replaceFont: 'Replace font', + fontLoaded: 'Font loaded', + fontMissing: 'Font not loaded', }, code128: { content: 'Vsebina', diff --git a/src/locales/sr.ts b/src/locales/sr.ts index c3e96e8..87bc1f5 100644 --- a/src/locales/sr.ts +++ b/src/locales/sr.ts @@ -109,6 +109,12 @@ const sr = { justifyC: 'C — Центар', justifyR: 'R — Десно', justifyJ: 'J — Обострано', + printerFont: 'Printer font (^A@)', + uploadFont: 'Upload font file', + uploadingFont: 'Uploading…', + replaceFont: 'Replace font', + fontLoaded: 'Font loaded', + fontMissing: 'Font not loaded', }, code128: { content: 'Sadržaj', diff --git a/src/locales/sv.ts b/src/locales/sv.ts index 47ad9e5..4ede245 100644 --- a/src/locales/sv.ts +++ b/src/locales/sv.ts @@ -109,6 +109,12 @@ const sv = { justifyC: 'C — Centrerad', justifyR: 'R — Höger', justifyJ: 'J — Marginaljust.', + printerFont: 'Printer font (^A@)', + uploadFont: 'Upload font file', + uploadingFont: 'Uploading…', + replaceFont: 'Replace font', + fontLoaded: 'Font loaded', + fontMissing: 'Font not loaded', }, code128: { content: 'Innehåll', diff --git a/src/locales/tr.ts b/src/locales/tr.ts index 907fccb..d8f9e98 100644 --- a/src/locales/tr.ts +++ b/src/locales/tr.ts @@ -109,6 +109,12 @@ const tr = { justifyC: 'C — Orta', justifyR: 'R — Sağ', justifyJ: 'J — İki yana', + printerFont: 'Printer font (^A@)', + uploadFont: 'Upload font file', + uploadingFont: 'Uploading…', + replaceFont: 'Replace font', + fontLoaded: 'Font loaded', + fontMissing: 'Font not loaded', }, code128: { content: 'İçerik', diff --git a/src/locales/zh-hans.ts b/src/locales/zh-hans.ts index b662ad2..793ea75 100644 --- a/src/locales/zh-hans.ts +++ b/src/locales/zh-hans.ts @@ -109,6 +109,12 @@ const zhHans = { justifyC: 'C — 居中', justifyR: 'R — 右对齐', justifyJ: 'J — 两端对齐', + printerFont: 'Printer font (^A@)', + uploadFont: 'Upload font file', + uploadingFont: 'Uploading…', + replaceFont: 'Replace font', + fontLoaded: 'Font loaded', + fontMissing: 'Font not loaded', }, code128: { content: '内容', diff --git a/src/locales/zh-hant.ts b/src/locales/zh-hant.ts index ed06785..2aad0b8 100644 --- a/src/locales/zh-hant.ts +++ b/src/locales/zh-hant.ts @@ -109,6 +109,12 @@ const zhHant = { justifyC: 'C — 置中', justifyR: 'R — 靠右', justifyJ: 'J — 左右對齊', + printerFont: 'Printer font (^A@)', + uploadFont: 'Upload font file', + uploadingFont: 'Uploading…', + replaceFont: 'Replace font', + fontLoaded: 'Font loaded', + fontMissing: 'Font not loaded', }, code128: { content: '內容', diff --git a/src/registry/text.tsx b/src/registry/text.tsx index a79ca44..af5be9a 100644 --- a/src/registry/text.tsx +++ b/src/registry/text.tsx @@ -1,7 +1,9 @@ +import { useRef, useState, useCallback } from 'react'; import type { ObjectTypeDefinition } from '../types/ObjectType'; import { useT } from '../lib/useT'; import { inputCls, labelCls } from '../components/Properties/styles'; import { fieldPos } from './zplHelpers'; +import { getFont, loadFontFile, useFontCacheVersion } from '../lib/fontCache'; export interface TextProps { content: string; @@ -9,6 +11,8 @@ export interface TextProps { fontWidth: number; rotation: 'N' | 'R' | 'I' | 'B'; reverse?: boolean; + /** Printer TrueType font filename from ^A@ (e.g. "ARIAL.TTF") */ + printerFontName?: string; /** ^FB field block properties */ blockWidth?: number; blockLines?: number; @@ -30,13 +34,16 @@ export const text: ObjectTypeDefinition = { toZPL: (obj) => { const p = obj.props; + const fontCmd = p.printerFontName + ? `^A@${p.rotation},${p.fontHeight},${p.fontWidth},E:${p.printerFontName}` + : `^A0${p.rotation},${p.fontHeight},${p.fontWidth}`; const fbCmd = p.blockWidth ? `^FB${p.blockWidth},${p.blockLines ?? 1},${p.blockLineSpacing ?? 0},${p.blockJustify ?? 'L'},0` : ''; return [ p.reverse ? '^LRY' : '', fieldPos(obj), - `^A0${p.rotation},${p.fontHeight},${p.fontWidth}`, + fontCmd, fbCmd, `^FD${p.content}^FS`, p.reverse ? '^LRN' : '', @@ -46,8 +53,61 @@ export const text: ObjectTypeDefinition = { PropertiesPanel: ({ obj, onChange }) => { const t = useT(); const p = obj.props; + const fileRef = useRef(null); + const [uploading, setUploading] = useState(false); + useFontCacheVersion(); + + const cachedFont = p.printerFontName ? getFont(p.printerFontName) : undefined; + const fontLoaded = !!cachedFont; + + const handleFontUpload = useCallback(async (file: File) => { + if (!p.printerFontName) return; + setUploading(true); + try { + await loadFontFile(file, p.printerFontName); + } finally { + setUploading(false); + } + }, [p.printerFontName]); + return (
+ {p.printerFontName && ( +
+ + {p.printerFontName} + { + const file = e.target.files?.[0]; + if (file) handleFontUpload(file); + e.target.value = ''; + }} + /> + + {fontLoaded && ( + {t.registry.text.fontLoaded} + )} + {!fontLoaded && ( + {t.registry.text.fontMissing} + )} +
+ )} +