import { shallowRef } from 'vue'; import type { ComputedRef, ShallowRef } from 'vue'; import { noop } from '@robonen/stdlib'; import { defaultWindow } from '@/types'; import type { ConfigurableWindow } from '@/types'; import { useSupported } from '@/composables/utilities/useSupported'; import { tryOnMounted } from '@/composables/lifecycle/tryOnMounted'; /** * A single font face exposed by the [Local Font Access API](https://developer.mozilla.org/en-US/docs/Web/API/FontData). */ export interface FontData { /** * The PostScript name of the font (e.g. `"Arial-BoldMT"`) */ readonly postscriptName: string; /** * The full, human-readable name of the font (e.g. `"Arial Bold"`) */ readonly fullName: string; /** * The font family (e.g. `"Arial"`) */ readonly family: string; /** * The font style/subfamily (e.g. `"Bold"`) */ readonly style: string; /** * Resolve the raw font file as a `Blob` (the full SFNT binary) */ blob: () => Promise; } export interface QueryLocalFontsOptions { /** * Restrict the results to the fonts whose PostScript names appear in this list. */ postscriptNames?: string[]; } interface WindowWithLocalFonts { queryLocalFonts: (options?: QueryLocalFontsOptions) => Promise; } export interface UseLocalFontsOptions extends ConfigurableWindow { /** * Query the local fonts immediately on mount. The Local Font Access API * requires the `local-fonts` permission (which may prompt the user), so this * is disabled by default — call `query()` from a user gesture instead. * * @default false */ immediate?: boolean; /** * Called when a query rejects (e.g. the permission is denied) instead of * throwing. The same value is also stored in the returned `error` ref. * * @default noop */ onError?: (error: unknown) => void; } export interface UseLocalFontsReturn { /** * Whether the [Local Font Access API](https://developer.mozilla.org/en-US/docs/Web/API/Local_Font_Access_API) is supported */ isSupported: ComputedRef; /** * The fonts returned by the most recent successful `query()` */ fonts: ShallowRef; /** * The last error thrown by `query()`, or `null` */ error: ShallowRef; /** * Enumerate the locally installed fonts. Resolves with the font list, or * `undefined` when the API is unsupported. Pass `postscriptNames` to filter. */ query: (queryOptions?: QueryLocalFontsOptions) => Promise; } /** * @name useLocalFonts * @category Browser * @description Reactive wrapper around the [Local Font Access API](https://developer.mozilla.org/en-US/docs/Web/API/Local_Font_Access_API) for enumerating the user's locally installed fonts. * * @param {UseLocalFontsOptions} [options={}] Options * @param {boolean} [options.immediate=false] Query immediately on mount (requires the `local-fonts` permission) * @param {Function} [options.onError=noop] Error callback invoked instead of throwing * @param {Window} [options.window=defaultWindow] Custom `window` instance * @returns {UseLocalFontsReturn} `isSupported`, `fonts`, `error`, and `query()` * * @example * const { isSupported, fonts, query } = useLocalFonts(); * // Call from a click handler so the permission prompt is allowed * async function pickFonts() { * await query(); * console.log(fonts.value.map(font => font.fullName)); * } * * @example * // Query only specific fonts by PostScript name * const { fonts, query } = useLocalFonts(); * await query({ postscriptNames: ['Arial-BoldMT'] }); * * @since 0.0.14 */ export function useLocalFonts(options: UseLocalFontsOptions = {}): UseLocalFontsReturn { const { window = defaultWindow, immediate = false, onError = noop, } = options; const isSupported = useSupported(() => !!window && 'queryLocalFonts' in window); const fonts = shallowRef([]); const error = shallowRef(null); async function query(queryOptions?: QueryLocalFontsOptions): Promise { if (!isSupported.value || !window) return undefined; error.value = null; try { const result = await (window as unknown as WindowWithLocalFonts).queryLocalFonts(queryOptions); fonts.value = result; return result; } catch (err) { error.value = err; onError(err); return undefined; } } if (immediate) tryOnMounted(() => query()); return { isSupported, fonts, error, query, }; }