plotly / plotly/Kaleido

[FEATURE]: Embed custom fonts into vector exports (no system install)

Open
#464 10 comments 0 reactions 1 assignee View on GitHub

@camdecoster is already working on this.

Since Jul 2, 2026.

enhancement P3 size: 3
Dominant language
Python
Stars
498
Forks
62
Avg merge
1d 11h
Merged PRs (30d)
2

Description

Problem

Plotly's SVG output references fonts by name only (e.g. font-family: 'My Font'); it never embeds the font bytes. When Kaleido renders that SVG to a raster or vector format, the font name is resolved against fonts available to the headless browser/OS. If the requested font is not installed system-wide, it silently falls back to a default (e.g. Times New Roman).

For PDF/EPS this is unavoidable today even with workarounds, because Kaleido loads the SVG into an <img> element and prints the page — an <img>-loaded SVG is an isolated document, so it can't see any @font-face defined on the host page.

The practical consequence: a chart authored with a brand/custom font renders correctly on the author's machine (where the font happens to be installed) but differently on CI, in containers, or on a colleague's machine.

Reproduction (current behavior)

Pick any font file (.ttf/.otf/.woff) whose family is not installed system-wide on your machine and set the two variables below. A distinctive display font such as Pacifico is a safe default on most systems:

curl -L -o font.ttf \
  https://github.com/google/fonts/raw/main/ofl/pacifico/Pacifico-Regular.ttf
import asyncio
import kaleido
import plotly.graph_objects as go

FONT_PATH = "font.ttf"   # a font NOT installed on your system
FAMILY    = "Pacifico"   # its family name

fig = go.Figure(go.Scatter(y=[1, 4, 2, 7, 5], mode="lines+markers"))
fig.update_layout(width=700, height=500, font_family=FAMILY,
                  title_text=f"{FAMILY} test")

async def main():
    async with kaleido.Kaleido(n=1, timeout=90) as k:
        await k.write_fig(fig, path="chart.pdf", opts={"format": "pdf"})

asyncio.run(main())

Inspect which font the PDF actually embedded:

from pypdf import PdfReader
fonts = set()
def visit(t, cm, tm, fd, sz):
    if fd and "/BaseFont" in fd: fonts.add(str(fd["/BaseFont"]))
for p in PdfReader("chart.pdf").pages:
    p.extract_text(visitor_text=visit)
print(fonts)

Observed: the embedded font is a system fallback (e.g. a Times/serif face), not FAMILY. The same chart looks different on a machine where FAMILY is installed — output is not reproducible across environments. (If you instead see FAMILY embedded, that font is installed on your machine — pick a different one.)

Desired behavior

A way to tell Kaleido to embed specific font files into the output so figures render with the intended typeface everywhere, without requiring a system font install — e.g.:

await k.write_fig(fig, path="chart.pdf",
                  opts={"format": "pdf", "fonts": [FONT_PATH]})

After which the PDF embeds the actual FAMILY glyphs (a subset such as AAAAAA+Pacifico-Regular) and the SVG carries an inlined base64 @font-face, making vector output self-contained.

A draft implementation is in #463.

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.