import type { SystemLabelTemplate } from './print/types/printer' /** 与 Web 管理端 labelTemplate.ts 保持一致 */ export const LABEL_EDITOR_FONT_FAMILY = 'FreightSans Bold' export const LABEL_EDITOR_FONT_OPTIONS: ReadonlyArray<{ value: string; label: string }> = [ { value: 'FreightSans Bold', label: 'FreightSans Bold' }, { value: 'Roboto', label: 'Roboto' }, { value: 'Open Sans', label: 'Open Sans' }, { value: 'Lato', label: 'Lato' }, { value: 'Tinos', label: 'Tinos' }, { value: 'Roboto Mono', label: 'Roboto Mono' }, ] export const LABEL_EDITOR_BUNDLED_FONT_FAMILIES = new Set( LABEL_EDITOR_FONT_OPTIONS.map((item) => item.value), ) const LEGACY_FONT_ALIASES: Record = { arial: 'Roboto', 'arial, sans-serif': 'Roboto', helvetica: 'Roboto', 'helvetica, sans-serif': 'Roboto', 'times new roman': 'Tinos', 'courier new': 'Roboto Mono', verdana: 'Open Sans', georgia: 'Tinos', } type FontVariantSpec = { file: string weight: 'normal' | 'bold' style: 'normal' | 'italic' } type FontPackageSpec = { family: string dir: string variants: FontVariantSpec[] } /** static/fonts 下已打包字体(与 Web src/assets/fonts 对齐) */ export const LABEL_EDITOR_FONT_PACKAGES: FontPackageSpec[] = [ { family: 'FreightSans Bold', dir: 'freight-sans-bold', variants: [{ file: 'FreightSans-Bold.ttf', weight: 'bold', style: 'normal' }], }, { family: 'Roboto', dir: 'roboto', variants: [ { file: 'roboto-latin-400-normal.woff2', weight: 'normal', style: 'normal' }, { file: 'roboto-latin-700-normal.woff2', weight: 'bold', style: 'normal' }, { file: 'roboto-latin-400-italic.woff2', weight: 'normal', style: 'italic' }, ], }, { family: 'Open Sans', dir: 'open-sans', variants: [ { file: 'open-sans-latin-400-normal.woff2', weight: 'normal', style: 'normal' }, { file: 'open-sans-latin-700-normal.woff2', weight: 'bold', style: 'normal' }, { file: 'open-sans-latin-400-italic.woff2', weight: 'normal', style: 'italic' }, ], }, { family: 'Lato', dir: 'lato', variants: [ { file: 'lato-latin-400-normal.woff2', weight: 'normal', style: 'normal' }, { file: 'lato-latin-700-normal.woff2', weight: 'bold', style: 'normal' }, { file: 'lato-latin-400-italic.woff2', weight: 'normal', style: 'italic' }, ], }, { family: 'Tinos', dir: 'tinos', variants: [ { file: 'tinos-latin-400-normal.woff2', weight: 'normal', style: 'normal' }, { file: 'tinos-latin-700-normal.woff2', weight: 'bold', style: 'normal' }, { file: 'tinos-latin-400-italic.woff2', weight: 'normal', style: 'italic' }, ], }, { family: 'Roboto Mono', dir: 'roboto-mono', variants: [ { file: 'roboto-mono-latin-400-normal.woff2', weight: 'normal', style: 'normal' }, { file: 'roboto-mono-latin-700-normal.woff2', weight: 'bold', style: 'normal' }, { file: 'roboto-mono-latin-400-italic.woff2', weight: 'normal', style: 'italic' }, ], }, ] const loadedFaceKeys = new Set() const loadedFamilies = new Set() let preloadAllPromise: Promise | null = null export function normalizeLabelEditorFontFamily(raw: unknown): string | null { const text = String(raw ?? '').trim() if (!text) return null const alias = LEGACY_FONT_ALIASES[text.toLowerCase()] if (alias) return alias if (LABEL_EDITOR_BUNDLED_FONT_FAMILIES.has(text)) return text return null } export function resolveLabelEditorFontFamily( config: Record | null | undefined, ): string { return ( normalizeLabelEditorFontFamily(config?.fontFamily ?? config?.FontFamily) ?? LABEL_EDITOR_FONT_FAMILY ) } function fontPackageByFamily(family: string): FontPackageSpec | undefined { return LABEL_EDITOR_FONT_PACKAGES.find((item) => item.family === family) } /** static/fonts/{dir}/{file} → 各端可访问的本地 URL */ export function resolveStaticFontFileUrl(dir: string, file: string): string { const rel = `static/fonts/${dir}/${file}`.replace(/\\/g, '/') // #ifdef APP-PLUS try { if (typeof plus !== 'undefined' && plus.io?.convertLocalFileSystemURL) { return plus.io.convertLocalFileSystemURL(`_www/${rel}`) } } catch (_) { /* fall through */ } // #endif return `/${rel}` } /** Android 原生位图文字:Typeface.createFromFile 用的绝对路径 */ export function resolveAndroidFontFilePath( family: string, bold: boolean, italic: boolean, ): string | null { const pkg = fontPackageByFamily(family) if (!pkg) return null const weight = bold ? 'bold' : 'normal' const style = italic ? 'italic' : 'normal' let variant = pkg.variants.find((v) => v.weight === weight && v.style === style) if (!variant && bold) { variant = pkg.variants.find((v) => v.weight === 'bold' && v.style === 'normal') } if (!variant) { variant = pkg.variants.find((v) => v.weight === 'normal' && v.style === 'normal') } if (!variant) return null return resolveStaticFontFileUrl(pkg.dir, variant.file) } function loadFontFaceOnce( family: string, dir: string, variant: FontVariantSpec, ): Promise { const key = `${family}|${variant.weight}|${variant.style}|${variant.file}` if (loadedFaceKeys.has(key)) return Promise.resolve() const url = resolveStaticFontFileUrl(dir, variant.file) return new Promise((resolve) => { uni.loadFontFace({ global: true, family, source: `url("${url}")`, desc: { style: variant.style, weight: variant.weight, }, success: () => { loadedFaceKeys.add(key) loadedFamilies.add(family) if (variant.style === 'italic') loadedItalicFamilies.add(family) resolve() }, fail: (err) => { console.warn('[labelEditorFonts] loadFontFace failed', family, variant, err) resolve() }, }) }) } export function isLabelEditorFontFamilyLoaded(family: string): boolean { return loadedFamilies.has(family) } const loadedItalicFamilies = new Set() export function isLabelEditorFontItalicLoaded(family: string): boolean { return loadedItalicFamilies.has(family) } export async function ensureLabelEditorFontFamilyLoaded(family: string): Promise { const normalized = normalizeLabelEditorFontFamily(family) ?? LABEL_EDITOR_FONT_FAMILY const pkg = fontPackageByFamily(normalized) if (!pkg) return await Promise.all( pkg.variants.map((variant) => loadFontFaceOnce(pkg.family, pkg.dir, variant)), ) } export function collectFontFamiliesFromTemplate(template: SystemLabelTemplate): string[] { const set = new Set([LABEL_EDITOR_FONT_FAMILY]) for (const el of template.elements || []) { const cfg = (el.config || {}) as Record const family = normalizeLabelEditorFontFamily(cfg.fontFamily ?? cfg.FontFamily) if (family) set.add(family) } return [...set] } /** 预览 / 打印 canvas 绘制前调用,加载模板用到的字体 */ export async function ensureLabelEditorFontsForTemplate( template: SystemLabelTemplate, ): Promise { const families = collectFontFamiliesFromTemplate(template) await Promise.all(families.map((family) => ensureLabelEditorFontFamilyLoaded(family))) } /** App 启动后后台预加载全部打包字体,减少首次预览等待 */ export function preloadAllLabelEditorFonts(): Promise { if (!preloadAllPromise) { preloadAllPromise = Promise.all( LABEL_EDITOR_FONT_PACKAGES.map((pkg) => ensureLabelEditorFontFamilyLoaded(pkg.family)), ).then(() => undefined) } return preloadAllPromise }