-
Notifications
You must be signed in to change notification settings - Fork 0
Feat/cutom font #1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,113 @@ | ||
| /** | ||
| * Font cache for printer TrueType fonts referenced by ^A@. | ||
| * Fonts are stored as data-URLs and persisted to localStorage. | ||
| * Each loaded font is registered with the browser's FontFace API | ||
| * so Konva (canvas) can render text using it. | ||
| */ | ||
|
|
||
| import { useState, useEffect } from 'react'; | ||
|
|
||
| export interface CachedFont { | ||
| id: string; | ||
| /** Original printer filename e.g. "ARIAL.TTF" (uppercased for lookup) */ | ||
| name: string; | ||
| /** data-URL — data:font/truetype;base64,... */ | ||
| dataUrl: string; | ||
| /** Registered CSS font-family name e.g. "zpl-ARIAL" */ | ||
| fontFamily: string; | ||
| } | ||
|
|
||
| const LS_PREFIX = 'zpl-font-'; | ||
| const cache = new Map<string, CachedFont>(); | ||
| 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<void> { | ||
| 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<CachedFont> { | ||
| 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)); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Storing font files as data URLs in |
||
| } catch { | ||
| // localStorage full — font stays in memory only | ||
| } | ||
|
Comment on lines
+96
to
+98
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. When |
||
| 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(); | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
crypto.randomUUID()is only available in secure contexts (HTTPS or localhost). If the application is accessed over a standard HTTP connection, this call will throw an error and prevent font uploads. Consider providing a fallback for non-secure contexts or using a different method for generating unique IDs if HTTP support is required.