Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ The **Variables** tab adds a variable with a name, an `^FN` slot and a default v

### Fonts

The **Fonts** tab uploads TrueType fonts and assigns their `^CW` alias.
The **Fonts** tab uploads TrueType fonts and assigns their `^CW` alias. On the desktop the tab also offers the fonts installed on this computer.

### Pages

Expand Down
22 changes: 13 additions & 9 deletions packages/core/src/lib/customFonts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ export const ZPL_BUILTIN_FONT_IDS = [
] as const;

/** The printer path for a locally picked file: a real device, up to 8 name chars, and an extension
* ^A@ and ^CW can reference (spec p.63, p.168): .TTE stays, everything else is .TTF. */
* ^A@ and ^CW can reference (spec p.63, p.168). .TTE stays, .OTF and the rest become .TTF (p.184). */
export function printerFontFileName(fileName: string): string | undefined {
const first = fileName[0]?.toUpperCase() ?? "";
const typedDevice = fileName[1] === ":" && (STORAGE_DEVICES as readonly string[]).includes(first) ? `${first}:` : undefined;
Expand All @@ -77,16 +77,20 @@ export function printerFontFileName(fileName: string): string | undefined {

export type FontUploadIssue = "notAFont" | "nameUnusable" | "nameTaken";

/** The gate a picked file passes before its bytes enter the cache; reads the file once. */
export async function prepareFontUpload(
file: File,
typedName = "",
): Promise<{ ok: true; path: string; bytes: Uint8Array } | { ok: false; reason: FontUploadIssue }> {
export type FontNameIssue = Exclude<FontUploadIssue, "notAFont">;
export type PreparedFont = { ok: true; path: string; bytes: Uint8Array } | { ok: false; reason: FontNameIssue };

/** The gate a picked file passes before its bytes enter the cache. */
export async function prepareFontUpload(file: File, typedName = ""): Promise<PreparedFont | { ok: false; reason: "notAFont" }> {
if (!isFontFile(file)) return { ok: false, reason: "notAFont" };
const path = printerFontFileName(typedName.trim() || file.name);
return prepareFontBytes(file.name, new Uint8Array(await file.arrayBuffer()), typedName);
}

/** The name and collision half of the gate, for bytes that arrive without a File. */
export function prepareFontBytes(fileName: string, bytes: Uint8Array, typedName = ""): PreparedFont {
const path = printerFontFileName(typedName.trim() || fileName);
if (!path) return { ok: false, reason: "nameUnusable" };
const bytes = new Uint8Array(await file.arrayBuffer());
// Two picked files can fold onto one printer name; a different file must not take the first one's row.
// Two picked files can fold onto one printer name. A different file must not take the first one's row.
if (holdsOtherBytes(path, bytes)) return { ok: false, reason: "nameTaken" };
return { ok: true, path, bytes };
}
Expand Down
5 changes: 4 additions & 1 deletion packages/core/src/lib/fontCache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,10 @@ export const MAX_FONT_BYTES = 4 * 1024 * 1024;
* grows, and the live ZPL view rebuilds the payload on every edit. */
export const EMBED_WARN_FONT_BYTES = 1024 * 1024;

const FONT_EXT_RE = /\.(ttf|otf|tte)$/i;
const FONT_EXTENSIONS = ["ttf", "otf", "tte"];
const FONT_EXT_RE = new RegExp(`\\.(${FONT_EXTENSIONS.join("|")})$`, "i");
/** The `accept` list of a font picker, so a picker never offers what the gate refuses. */
export const FONT_FILE_ACCEPT = FONT_EXTENSIONS.flatMap((ext) => [`.${ext}`, `.${ext.toUpperCase()}`]).join(",");

/** What `loadFontFile` will take, answerable before a byte is read. */
export const isFontFile = (file: File): boolean => FONT_EXT_RE.test(file.name) && file.size <= MAX_FONT_BYTES;
Expand Down
4 changes: 4 additions & 0 deletions src-tauri/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ mod package;
mod preview;
mod print;
mod scope;
mod system_fonts;
mod transport;
mod updates;
mod usb;
Expand All @@ -17,6 +18,7 @@ use tauri::Manager;
pub fn run() {
let builder = tauri::Builder::default()
.manage(mcp::McpState::default())
.manage(system_fonts::SystemFonts::default())
// In setup, not manage(): loading the persisted grants needs the app paths.
.setup(|app| {
let grants = scope::PathGrants::load(app.handle());
Expand All @@ -32,6 +34,8 @@ pub fn run() {
usb::send_zpl_usb,
usb::query_zpl_usb,
usb::setup_usb_access,
system_fonts::list_system_fonts,
system_fonts::read_system_font,
db::db_list_tables,
db::db_fetch,
db::db_set_password,
Expand Down
Loading
Loading