Make text in PDF generated matplotlib editable in Illustrator

You draw a figure with Python using matplotlib in PDF, open it in Illustrator to fix one label before the submission deadline, and the text is there but you cannot select it. Click a word and you get a cluster of tiny filled shapes that happen to be letter-shaped. The glyphs are right. The text is gone.

This is not an Illustrator problem and it is not a corrupted file. It is a matplotlib default.

Tested on matplotlib 3.10.8. Backend defaults do occasionally move, so check yours with matplotlib.__version__ if the behavior below does not match.

Type 3 versus Type 42

The PDF specification supports several ways to embed a font. Matplotlib’s PDF backend defaults to Type 3, which defines each glyph as a small PostScript drawing procedure. A Type 3 font is a set of instructions for painting shapes. It carries no reliable mapping back to characters, so a design tool can render it faithfully and still have nothing to hand you when you click.

Type 42 wraps a real TrueType font, outlines and character encoding intact. Illustrator reads it as text because it is text.

Matplotlib picked Type 3 for a defensible reason: Type 3 fonts are smaller and were historically safer for print and LaTeX pipelines. For anyone who intends to touch the figure afterward, the tradeoff runs backwards.

The fix

import matplotlib.pyplot as plt

plt.rcParams.update({
    "pdf.fonttype": 42,        # TrueType in PDF, editable downstream
    "ps.fonttype": 42,         # same for EPS
    "svg.fonttype": "none",    # SVG keeps <text> instead of converting to paths
})

Put it at the top of your plotting script, before you create any figures. That is the whole fix for editability.

Verify it from the command line rather than trusting the setting:

pdffonts figure.pdf

You want CID TrueType in the type column:

name                             type              encoding    emb sub uni
-------------------------------- ----------------- ----------- --- --- ---
FRZNSF+LiberationSans-Bold       CID TrueType      Identity-H  yes yes yes
FRZNSF+LiberationSans-Italic     CID TrueType      Identity-H  yes yes yes
FRZNSF+LiberationSans            CID TrueType      Identity-H  yes yes yes

If it still says Type 3, the rcParam did not take effect. The usual cause is something resetting the params after you set them: a style sheet, a seaborn call, or an import that applies its own theme.

The second problem, which looks like the first

Now the text is editable, and Illustrator opens a dialog complaining about a missing font. Matplotlib’s default family is DejaVu Sans, which ships with matplotlib and is installed on very few design machines. Illustrator substitutes, the substitute has different metrics, and your carefully positioned labels drift.

Switch to a font your design environment actually has. On Linux that usually means Liberation Sans, which is metric-compatible with Arial, so the substitution is dimensionally identical:

plt.rcParams["font.family"] = "Liberation Sans"

Check what you have before committing to one:

import matplotlib.font_manager as fm
print(sorted({f.name for f in fm.fontManager.ttflist}))

Worth being clear about the division of labor here, because the two fixes arrive together and are easy to conflate. pdf.fonttype decides whether you get text at all. font.family decides whether that text keeps its layout. Neither substitutes for the other.

Prefer SVG when you can

For figures headed into a design tool, SVG usually imports more cleanly than PDF. With svg.fonttype set to none, every label stays as a <text> element referencing a font by name, and everything else is ordinary vector geometry.

One caveat: that font name is whatever matplotlib used. If it rendered with Liberation Sans and your machine has Arial, a single find-and-replace fixes it before you ever open Illustrator.

sed -i 's/Liberation Sans/Arial, Helvetica, sans-serif/g' figure.svg

What still will not be editable

Hatch patterns. Matplotlib writes them as clipped groups of individual stroke paths, not as fill patterns, in both PDF and SVG. If you need to restyle a hatched element, change set_hatch in the script and re-export. Fighting the clipped group in Illustrator takes longer than regenerating the figure.

Which is the broader lesson. Three rcParams at the top of a script save you a round of manual repair on every figure you export, and keep the script as the single source of truth for what the figure looks like. The moment a label only exists in an Illustrator file, your figure and your data have quietly come apart.

Comments

Popular posts from this blog

Controlled Sampling in High-Dimensional Latent Spaces for Protein Design

First Impressions: Claude Code as an AI Coding Assistant

Google Colab as a Cloud GPU Platform: Notes and First Impressions