import type { FontFaceDescriptor, FontFaceLoadResult, FontFaceRequest, FontLoadResult, FontLoadStatus, RegisteredFace, RegisterFaceResult, RequiredFace } from './types.js'; /** * Default per-font budget the gate waits before treating a face as `timed_out` * and proceeding with a fallback. Generous enough for a cold cache on a slow * connection, short enough that a missing font never blocks first paint forever. */ export declare const DEFAULT_FONT_LOAD_TIMEOUT_MS = 3000; /** * Structural slice of the CSS Font Loading API (`FontFaceSet`) the registry * depends on. Declared as an interface (not the DOM type) so the registry is * unit-testable with a fake and degrades cleanly where there is no DOM. */ export interface FontSetLike { add(face: FontFaceLike): void; /** Remove a managed face (DOM `FontFaceSet.delete`). Optional: some sets (domless/tests) omit it. */ delete?(face: FontFaceLike): boolean; load(font: string, text?: string): Promise; check(font: string, text?: string): boolean; } /** Structural slice of a `FontFace`. */ export interface FontFaceLike { readonly family: string; load(): Promise; readonly status?: string; } /** Structural slice of the global `FontFace` constructor. */ export type FontFaceCtor = new (family: string, source: string | ArrayBuffer | ArrayBufferView, descriptors?: FontFaceDescriptors) => FontFaceLike; /** * A document-owned font face registered from raw bytes (a deobfuscated DOCX-embedded font), the input * to {@link FontRegistry.registerOwnedFace}. Carries the bytes plus the OS/2-derived face axis so the * face registers under the document's family name with the correct weight/style and flows through the * resolver's `registered_face` rung. `weight`/`style` are already canonical (no normalization needed). */ export interface OwnedFaceDescriptor { family: string; source: ArrayBuffer | ArrayBufferView; weight: '400' | '700'; style: 'normal' | 'italic'; } export interface FontRegistryOptions { /** The font set to drive. Defaults to the ambient `document.fonts`. */ fontSet?: FontSetLike | null; /** Constructor for new managed faces. Defaults to the global `FontFace`. */ FontFaceCtor?: FontFaceCtor | null; /** Font size used in probe strings. Only affects the `font` shorthand syntax. */ probeSize?: string; /** Timer hooks, injectable for deterministic tests. Default to the globals. */ scheduleTimeout?: (cb: () => void, ms: number) => unknown; cancelTimeout?: (handle: unknown) => void; } /** * Runtime registry of font faces and their load state. * * Two jobs: * 1. **Register** managed faces (bundled substitutes, customer BYO fonts) so the * browser knows how to load them. Registration is lazy - it adds the face to * the set but does not download until something awaits it. * 2. **Await** the specific faces a layout pass needs, with a per-font timeout, * reporting `loaded | failed | timed_out | fallback_used`. This is the * contract the load-before-measure gate consumes; it works for *any* family * in the set, whether the registry created the face (managed) or it was * injected elsewhere (embedded `@font-face`, a system font). * * The registry intentionally does not subscribe to font-loading events or touch * measurement caches - those belong to the gate, which owns the editor lifecycle. */ export declare class FontRegistry { #private; constructor(options?: FontRegistryOptions); /** * Register a managed face. Adds it to the font set so the browser can load it * on demand, but does not download yet. Safe to call where there is no DOM / * constructor: the family is recorded as `unloaded` and will resolve to * `fallback_used` when awaited. */ register(descriptor: FontFaceDescriptor): RegisterFaceResult; /** True if this registry created a managed face for the family. */ isManaged(family: string): boolean; /** * Register a DOCUMENT-OWNED face from raw bytes (a deobfuscated DOCX-embedded font) and return a * disposer that releases EXACTLY this face. Unlike {@link register} - the shared registrar for * bundled and `fonts.add` faces, idempotent by URL source - every call here creates a distinct * managed face, because the registry is shared per FontFaceSet across editors and two documents can * embed the same family|weight|style with different (subset) bytes. Calling the returned disposer * (on a document swap or editor teardown) removes only this face; the key's `hasFace` stays true * while any other owner's face remains, so one document's cleanup never drops another document's or * a customer's face. The face participates in `hasFace`/await like any managed face while it lives. * * Returns null where there is no DOM `FontFace` constructor / font set: embedded bytes cannot render * without one, so the resolver correctly falls through to the bundled substitute. The disposer is * idempotent (a second call returns false). */ registerOwnedFace(descriptor: OwnedFaceDescriptor): (() => boolean) | null; /** * Last known status for a family, rolled up from its faces (and any legacy family-path * load). Used by declared-font diagnostics (`buildFontReport`). * * A FAILED/TIMED_OUT/FALLBACK_USED face surfaces OVER a loaded sibling: if a document * uses Arial regular (Liberation Sans loads) and Arial bold (its face 404s), the family * reports the failure (`missing: true`), not a misleadingly-clean `loaded`. This is sound * because the gate only awaits USED faces - an unused face stays `unloaded` (lowest * priority), so a declared-but-unused family stays `unloaded` (not settled => not missing) * and a used-but-failed face is never masked. Per-face detail is in `getFaceStatus` and * the load summary's per-face counts. */ getStatus(family: string): FontLoadStatus; /** * Is a face (family + weight + style) provided by a REGISTERED face that has not terminally failed * to load? The face-availability oracle the face-aware resolver consults to answer "does the * physical family actually provide this face?" - so a single-face substitute is never mapped onto a * weight/style it lacks (which the painter would faux-synthesize). Covers bundled faces (registered * by `installBundledSubstitutes`) and customer `fonts.add()` faces alike, because both register * through {@link register}. * * Two deliberate exclusions: * - A merely-AWAITED face is not a provider. The oracle reads {@link #providerFaceKeys} (registration * only), NOT `#facesByFamily` (which the await path also populates for the status rollup), so an * `as_requested` family the gate once awaited does not masquerade as a registered face on the next * resolve. * - A face whose asset terminally FAILED to load is dropped, so the ladder steps down to the bundled * clone instead of committing forever to a broken registered face. `timed_out` is NOT excluded - * the late-load reflow still recovers a slow face, and demoting it would strand the real font. * * Distinct from {@link isAvailable}, which asks whether a face is LOADED right now. */ hasFace(family: string, weight: '400' | '700', style: 'normal' | 'italic'): boolean; /** * Synchronous availability check: is a real face for this family loaded and * usable in the set *right now*? Used by the gate's late-load handler to detect * a face that finished after the first measure. Returns false where there is no * font set. */ isAvailable(family: string): boolean; /** * Await one family with a per-font timeout. Concurrent calls for the same * family share the in-flight probe. A family already known to be `loaded` * resolves immediately. */ awaitFace(family: string, timeoutMs?: number): Promise; /** * Await many families. The result preserves input order after de-duplication. * This is the gate's primary entry point before a measurement pass. */ awaitFaces(families: Iterable, options?: { timeoutMs?: number; }): Promise; /** * Required families as `{ family, status, ready }` handles. Reading `status` is * synchronous; `ready` settles when the load resolves. Calling this starts the * loads (idempotently). */ getRequiredFaces(families: Iterable, timeoutMs?: number): RequiredFace[]; /** Snapshot of every family the registry has seen and its status (diagnostics). */ getStates(): RegisteredFace[]; /** Last known status for a specific face (`unloaded` if never seen). */ getFaceStatus(request: FontFaceRequest): FontLoadStatus; /** * Await one specific face (family + weight + style) with a per-font timeout. Uses a * weight/style-specific probe (`italic 700 16px "Carlito"`), so unlike {@link awaitFace} * it loads the EXACT face the run needs, not just the regular one. Concurrent awaits of * the same face share one probe; an already-`loaded` face resolves immediately. */ awaitFaceRequest(request: FontFaceRequest, timeoutMs?: number): Promise; /** Await many faces; result preserves input order after de-duplication by face key. */ awaitFaceRequests(requests: Iterable, options?: { timeoutMs?: number; }): Promise; } /** * The single registry bound to a given font set. There is exactly one registry * per `FontFaceSet`, so a document's load gate, its bundled faces, and the public * `superdoc.fonts.*` surface all share load state. * * Callers MUST pass the same font set they watch for load events. A consumer that * awaits one set but listens to another would never observe its fonts arriving - * the exact failure mode for an editor inside an iframe whose `document.fonts` * differs from the top window's. Pass `null` (no DOM) to get a shared DOM-less * registry whose every await resolves to `fallback_used`. */ export declare function getFontRegistryFor(fontSet: FontSetLike | null, FontFaceCtor: FontFaceCtor | null): FontRegistry; /** * The registry for the ambient `document.fonts`. Convenience for the common * single-document case; iframe/embedded callers should use {@link getFontRegistryFor} * with their own document's font set so the registry and the watched set match. */ export declare function getDefaultFontRegistry(): FontRegistry; /** Reset the cached DOM-less registry. Test-only; not part of the public surface. */ export declare function __resetDefaultFontRegistry(): void;