Skip to content

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

Description

@nul0m

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.

Activity

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

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions