The figure with no text in it
Your plotting tool probably converted your axis labels into drawings of letters. The picture is identical, and nobody can check it any more.
Open the SVG your plotting script just wrote and search it for one of your own axis labels. On most people’s figures you will find the words, and you will not find any text.
That is not a riddle. Somewhere between your plt.savefig and the file on
disk, each letter stopped being a letter and became a small drawing of one.
The picture is pixel-for-pixel identical. Everything you would want to ask
about the type afterwards, and everything your journal’s production team would
want to ask, has quietly stopped having an answer.
What the two files look like
Here is one ordinary figure: 342 penguins, flipper length against body mass,
three species, drawn in matplotlib 3.11.2 with nothing
customised. It was exported twice from the same code with one setting changed,
svg.fonttype, and nothing else touched.
The two images are indistinguishable. The two files are not.
What is inside the two exports
The same figure, exported twice. The setting named path is what matplotlib ships, and at that setting the file holds no text at all: every letter is a reference to an outline in the file's own <defs>, placed 109 times. Set it to none and the 20 pieces of text come back as text. Neither export looks different on the page.
| Measurement | Default (path) | none |
|---|---|---|
| Text elements | 0 | 20 |
| Glyph outlines placed | 109 | 0 |
| Type sizes the file declares | none | 10 pt |
| Fonts the file names | none | DejaVu Sans, and its fallbacks |
| Where the words “Flipper length (mm)” appear | in a comment only | in the text |
| File size | 84,229 bytes | 65,556 bytes |
The last row of that table is what makes this hard to notice. The default
export does still contain the string Flipper length (mm). matplotlib
writes it into an XML comment beside the outlines it drew it as, as a courtesy.
So if you go looking for your axis label in the file, you will find it, and
conclude that everything is fine.
It is not there as text. It is there as a note explaining what the drawing is of. The check most people would run is the one check that passes either way.
It is not a matplotlib problem
We went looking for figures drawn by tools that are not installed here, on Wikimedia Commons, under licences that allow it. The answer was the same from two of the three, by two different mechanisms neither of which announces itself.
| Tool | Live type? | How the letters got there |
|---|---|---|
| matplotlib 3.11.2, as shipped | No | each glyph defined once in <defs> and placed with <use>, 109 times, from DejaVuSans |
| matplotlib 3.11.2, svg.fonttype: none | Yes | live type |
| Julia, Plots.jl | No | each glyph drawn as a <path> |
| R, the built-in svg() device | No | each glyph defined once as a <symbol> and placed with <use>, which is cairo’s way |
| gnuplot 4.2 | Yes | live type |
gnuplot is the control, and it is the one that behaves: it writes real text and
its labels can be read straight out of the file. Julia’s Plots draws each
letter as a path. R’s built-in svg() device is cairo-backed, so it defines
each letter once and stamps it wherever it is needed. matplotlib works the
same way as R, defining its glyphs as paths rather than as symbols. Different
routes, same outcome, and no warning from any of them.
A caution about that table, since this site has now made the mistake twice. The Julia file is filed on Commons under Category: Created with Matplotlib and is nothing of the kind: the author published the Julia source on the same page. Every attribution above comes from what the author wrote beside the file or from a string inside the file, never from a category. A label applied by a third party is not evidence about what produced a file.
What it costs
Nothing, most of the time. The figure renders correctly, which is exactly why this survives all the way to submission.
It costs something at the two moments that matter. The first is when somebody asks a question about the type, and here is what that looks like on the very figure above.
The type in that figure, drawn and printed
The figure declares itself 132.1 mm wide and sets its type at 10 pt. Reproduced in a 85 mm single column, every size in it is multiplied by 0.643, and that type arrives at 6.4 pt against a floor of 8. The sizes below are shown in proportion to one another; how big they are on your screen depends on your screen, which is the whole difficulty with judging type by eye.
That figure is too small at a single column, and it is too small in both exports, because the outlining changed nothing about the picture. The difference is that one of the two files can be asked and the other cannot. In the default export there is no type size to read, no font to name and nothing for a checker, a copyeditor or you in six months to measure. The problem is still there. The evidence of it has gone.
The second moment is when someone needs to change a word. A typo in a panel label, a gene name the journal wants italicised, a unit that changed in revision: all of these are a two-second edit in a file with text in it, and a redraw-from-the-script in a file without. If the script still runs. If you still have it.
The journal’s own account of this
PLOS says it plainly on its figure guidelines page, under Text within EPS figures, retrieved 2026-09-18:
Embed fonts, or convert them to outlines to prevent missing or improperly rendered text. In those files that are created in software like Matlab, open Illustrator or Inkscape and convert text to outlines. You will not be able to change your text after it has been converted to outlines.
Read that carefully, because it is not on our side of the argument. PLOS recommends outlining for EPS figures, and gives a good reason: a font that is not embedded goes missing in production and the text renders wrong or not at all. Outlining makes a figure immune to that. It is a legitimate and sometimes correct thing to do.
The same page also requires, of every figure, arial, times, or symbol font only in 8-12 point.
Both of those are PLOS’s, and together they describe the trap exactly. Convert your text to outlines and the second requirement becomes uncheckable, by you and by them. The figure may well comply. Nobody can demonstrate that it does.
The difference between PLOS’s advice and your tool’s default is when. Outlining is a finishing step, done deliberately, to a figure that is already the right size with the right type in it, on the way out the door. Your tool does it first, silently, to every figure, including the seventeen drafts you are still revising.
What to do about it
- Set the export to keep your type as type, once, in whatever you plot
with. For matplotlib that is
svg.fonttype: none, in amatplotlibrcor withplt.rcParamsat the top of the script. In R it is a different device rather than a setting:svglite::svglite()in place of the built-insvg(). - Know what that costs, because it is a trade and not a free win.
matplotlib’s own configuration file describes
noneas “assume fonts are installed on the machine where the SVG will be viewed”, and svglite says the same thing from the other side: outlining “does not require the font to be present on the system that displays the SVG but makes it more or less impossible to edit the text after the fact”. Live type is a file that needs its font; outlined type is a file nobody can correct. Choose per stage, not once and for ever. - Check what you actually have. Open the file and search it for
<text, not for your axis label. Your label is in there either way;<textis only in there when the type is real. The figure checker on this site does the same thing and will tell you which kind you have. - Outline last, if at all, and only on the copy you are submitting. Keep the version with text in it, because that is the one you will edit at revision.
- If you are sending EPS or PDF to PLOS, follow PLOS, not us: embed the fonts or outline them, as they ask. Do it to a figure whose type you have already checked.
One thing that is not a problem: this is a vector-export setting and it does not touch your PDF. The same figure saved as a PDF keeps its text either way, which is worth knowing when a journal takes both.
The one-page card
Keep your type as type (PDF, A4). The export setting for matplotlib and for R, each quoted from that tool’s own documentation, the line to paste, and the check to run on a file you already have. Tools whose setting we could not verify are named on the card as unverified rather than guessed at. Also on the site as a page.