Skip to content

feat: expose resolveFamily in RenderSlideOptions to keep custom font families (textLayout: "svg") #461

Closed

Description

@keurcien

Problem

Our company templates use Lora and Inter. When we preview those decks in the browser with renderSlideToSvg(pres, slide, { textLayout: "svg" }), every text run comes out as font-family="Carlito": in svg mode each run's font goes through substituteFamily, which maps any family other than Calibri/Aptos, Cambria, Arial/Helvetica, Times and Courier/Consolas to Carlito. The original family name is lost before the SVG leaves the renderer, so the host has no way to supply the real font.

We render in svg mode because we sanitize the output and display it as an <img> (blob URL), with foreignObject forbidden. In that context the only fonts that load are @font-face rules embedded as data URIs inside the SVG — which we can do, as long as the family name survives.

Existing paths considered

  • RenderSlideOptions (0.15.0, latest) exposes measureText, textLayout and background. None of them affects family resolution.
  • textLayout: "foreignObject" keeps the name (browserFontFamily → "Lora", <default>), but it needs foreignObject in the output, which our sanitizer removes for security, and it still depends on fonts installed on the viewer's machine.
  • buildFontkitMeasurer({ fonts }) from /node registers real font files, but only for measurement / the Node path; it doesn't change the family written to the SVG in the browser entry.
  • Post-processing the SVG can't recover the name: by then every unknown family is Carlito.

Proposed solution

Expose the existing internal resolveFamily hook on RenderSlideOptions, so a host can keep the families it can supply:

interface RenderSlideOptions {
  // …existing options
  /** Maps a run's font (or theme face) to the family written to the SVG.
   *  Defaults to substituteFamily in svg mode. */
  readonly resolveFamily?: (family: string | null) => string;
}

renderSlideToSvg(pres, slide, {
  textLayout: 'svg',
  resolveFamily: (f) => (f === 'Lora' || f === 'Inter' ? f : substituteFamily(f)),
  measureText, // real metrics for those fonts, so wrapping matches
});

We would then inject the @font-face data URIs ourselves.

Alternatives

  • A fonts: [{ family, src }] option that both keeps those families and emits the @font-face rules into the SVG. Friendlier, but it puts font loading/embedding inside the library; resolveFamily is the smaller change and reuses a hook that already exists.
  • Patching substituteFamily locally (what we'll do in the meantime).

Scope and compatibility

Additive: the option is optional and the default stays substituteFamily, so current output is unchanged.

Confirmation

  • I have read the project's "one way to do one thing" policy and confirmed
    this proposal is not a parallel path to an existing capability.
  • I have searched existing issues (open and closed) and confirmed this is
    not a duplicate.
  • I have a real, specific use case for this — not a hypothetical "could be
    useful."
  • If I used an LLM to draft this issue, I have read and verified every
    claim, including the "existing paths" section, and am willing to defend
    the proposal in follow-up.

Activity

  1. github-actions commented on Oct 12, 2026

    @github-actions
    Contributor

    Hi @keurcien, thanks for the report.

    This issue does not match the project's issue template, so it has been auto-closed. Specifically:

    • The issue is missing the template anchor comment (<!-- issue-template:bug:v1 --> or <!-- issue-template:feature:v1 -->). Please open this issue from the New issue page so the template is preserved.
    • The confirmation checklist (the - [ ] items at the bottom of the template) has been removed. Please restore it.

    How to fix this

    Please edit this issue and restore the template structure. Use the "New issue" page on this repository to start from the official form. When the template is restored, this workflow will reopen the issue automatically.

    Why we enforce this

    Templates exist so that maintainers can triage efficiently. Issues that omit the template — including reports generated by AI without verification — consume scarce maintainer time without producing actionable signal. Repeated low-effort or AI-slop submissions from the same account may result in being blocked from this repository.

    If you believe this auto-close is in error, please leave a comment explaining and a maintainer will take a look.

  2. changed the title [-]preview: expose font-family resolution / custom fonts in RenderSlideOptions (textLayout: "svg")[/-] [+]feat: expose resolveFamily in RenderSlideOptions to keep custom font families (textLayout: "svg")[/+] on Oct 12, 2026
  3. github-actions commented on Oct 12, 2026

    @github-actions
    Contributor

    Thanks for updating this — the template now looks valid. A maintainer will triage from here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions