Bug summary
TextToPath.get_glyphs_tex always runs LaTeX at the fixed FONT_SCALE of 100 pt and ignores the size in prop. TeX fonts have a separate design for each size, so all usetex text drawn as paths (e.g., all SVG output, and text with path_effects on any backend) uses the 17 pt design. At 6 to 12 pt the resulting text is 5-12% narrower than its layout and has lighter strokes. PNG and PDF output are correct.
Code for reproduction
import re
import matplotlib as mpl
import matplotlib.patheffects as pe
import matplotlib.pyplot as plt
mpl.rcParams["text.usetex"] = True
s = "Hamburgefonstiv"
# The same 6 pt text saved as SVG and as PDF.
fig = plt.figure(figsize=(2, 1))
fig.text(0.1, 0.5, s, fontsize=6)
fig.savefig("usetex.svg")
fig.savefig("usetex.pdf")
with open("usetex.svg", encoding="utf-8") as f:
svg = f.read()
with open("usetex.pdf", "rb") as f:
pdf = f.read()
print("SVG fonts:", sorted(set(re.findall(r'id="(CM[A-Z]+\d+)-', svg))))
print("PDF fonts:", sorted({m.decode() for m in
re.findall(rb"/BaseFont\s*/(?:[A-Z]{6}\+)?(\w+)", pdf)}))
# Right-aligned 8 pt text on Agg, without and with path effects.
fig = plt.figure(figsize=(1.6, 0.5), dpi=600)
fig.add_artist(plt.Line2D([0.93, 0.93], [0, 1], lw=0.3, color="tab:red"))
fig.text(0.93, 0.62, s, fontsize=8, ha="right", va="center")
fig.text(0.93, 0.25, s, fontsize=8, ha="right", va="center",
path_effects=[pe.Normal()])
fig.savefig("usetex_path_effects.png")
Actual outcome
SVG fonts: ['CMSS17']
PDF fonts: ['CMSS8']
The same 6 pt text is drawn with the 17 pt design of Computer Modern Sans (CMSS17) in the SVG file, and with CMSS8, the smallest design, in the PDF file.

- both lines are right-aligned on the red line, 8 pt
- the lower line has
path_effects=[pe.Normal()], so it is drawn through get_glyphs_tex.
- the lower line narrower is narrower and lighter, and it ends short of the red line
The table below compares the width matplotlib lays the text out with (TexManager.get_text_width_height_descent) with the ink width as drawn by get_glyphs_tex, and with the ink width when LaTeX runs at the text size.
| Size |
Layout width |
Ink width, drawn (SVG, path effects) |
Ink width, LaTeX at the text size |
| 6 pt |
44.87 pt |
39.56 pt |
44.76 pt |
| 10 pt |
70.43 pt |
65.93 pt |
70.27 pt |
| 24 pt |
158.55 pt |
158.22 pt |
158.22 pt |
For 17 pt and larger CMSS17 is the correct design, so larger text is not affected. The layout width includes the side bearings of the first and last glyph, so it is slightly larger than the ink width even when both use the same design. The bug is the difference between the two ink columns, which closes at 17 pt and above.
Expected outcome
usetex text drawn as paths matches PNG and PDF output, with the design TeX selects for the text's size:
- The SVG file uses CMSS8 for the 6 pt text, as the PDF file does.
In the image, the lower line matches the upper line and ends at the red line.
- In the table, the drawn ink width equals the ink width with LaTeX at the text size, at every size.
Additional information
Likely root cause:
get_glyphs_tex typesets the string with TexManager().make_dvi(s, self.FONT_SCALE), so LaTeX always runs at 100 pt, and its callers scale the result down to the text size. Everywhere else, LaTeX runs at the text's own size. i.e., in the draw_tex methods of the PDF, PS, and PGF backends (prop.get_size_in_points()), in Agg through dvipng, and in the layout measurement (TexManager.get_text_width_height_descent). Because TeX selects a font design by size, only the paths through get_glyphs_tex draw with a different design from the one matplotlib measured.
Affected paths:
The SVG backend does not override draw_tex, so RendererBase.draw_tex draws all usetex text through _draw_text_as_path and get_glyphs_tex, for any svg.fonttype. PathEffectRenderer inherits the same draw_tex, so usetex text with path effects is affected on every backend, including Agg. TextPath(..., usetex=True) also calls get_glyphs_tex. The Cairo backend inherits RendererBase.draw_tex as well.
Possible fix
- dvifile = TexManager().make_dvi(s, self.FONT_SCALE)
+ size = prop.get_size_in_points()
+ scale = self.FONT_SCALE / size
+ dvifile = TexManager().make_dvi(s, size)
...
- xpositions.append(text.x)
- ypositions.append(text.y)
- sizes.append(text.font_size / self.FONT_SCALE)
+ xpositions.append(text.x * scale)
+ ypositions.append(text.y * scale)
+ sizes.append(text.font_size / size)
...
for ox, oy, h, w in page.boxes:
+ ox, oy, h, w = ox * scale, oy * scale, h * scale, w * scale
This is a safe solution because glyph outlines are still loaded at FONT_SCALE, and glyph keys include the font name, so the shared glyph_map of the SVG backend keeps separate entries for CMSS8 and CMSS17 when a figure has mixed sizes. The LaTeX run is the one where the layout measurement is already cached, so the change adds no extra LaTeX runs.
Tested so far as a local patch on 3.11.2:
Limitation for TextPath:
The fix takes the text size from prop, which is correct for the SVG backend and path effects, because they pass the font properties of the text. TextPath accepts its size separately and passes prop to get_text_path unchanged. So TextPath((0, 0), s, size=12, usetex=True) would run LaTeX at the default 10 pt in prop instead of at 12 pt. Passing a copy of prop with its size set to size in TextPath.__init__ would fix that.
Context
I maintain curved-text, a package listed in the third-party registry that draws text along curves from get_glyphs_tex outlines. I found this while adding usetex support to it. For now it works around the bug by setting FONT_SCALE to the text size on its own TextToPath instance, which fixes curved-text but not matplotlib's own SVG and path-effects output. I would be glad to open a PR with the change above and a test, if this direction is acceptable.
AI disclosure
I used an AI assistant (Claude) to trace the code paths, measure the widths, and test the patch. I ran the reproduction and the measurements myself and checked the results, and I reviewed the proposed change line by line.
Operating system
Windows 11
Matplotlib Version
3.11.2
Matplotlib Backend
Agg (image), SVG and PDF via savefig
Python version
3.13.3
Jupyter version
No response
Installation
pip
Bug summary
TextToPath.get_glyphs_texalways runs LaTeX at the fixedFONT_SCALEof 100 pt and ignores the size inprop. TeX fonts have a separate design for each size, so all usetex text drawn as paths (e.g., all SVG output, and text withpath_effectson any backend) uses the 17 pt design. At 6 to 12 pt the resulting text is 5-12% narrower than its layout and has lighter strokes. PNG and PDF output are correct.Code for reproduction
Actual outcome
The same 6 pt text is drawn with the 17 pt design of Computer Modern Sans (CMSS17) in the SVG file, and with CMSS8, the smallest design, in the PDF file.

path_effects=[pe.Normal()], so it is drawn throughget_glyphs_tex.The table below compares the width matplotlib lays the text out with (
TexManager.get_text_width_height_descent) with the ink width as drawn byget_glyphs_tex, and with the ink width when LaTeX runs at the text size.For 17 pt and larger CMSS17 is the correct design, so larger text is not affected. The layout width includes the side bearings of the first and last glyph, so it is slightly larger than the ink width even when both use the same design. The bug is the difference between the two ink columns, which closes at 17 pt and above.
Expected outcome
usetex text drawn as paths matches PNG and PDF output, with the design TeX selects for the text's size:
In the image, the lower line matches the upper line and ends at the red line.
Additional information
Likely root cause:
get_glyphs_textypesets the string withTexManager().make_dvi(s, self.FONT_SCALE), so LaTeX always runs at 100 pt, and its callers scale the result down to the text size. Everywhere else, LaTeX runs at the text's own size. i.e., in thedraw_texmethods of the PDF, PS, and PGF backends (prop.get_size_in_points()), in Agg throughdvipng, and in the layout measurement (TexManager.get_text_width_height_descent). Because TeX selects a font design by size, only the paths throughget_glyphs_texdraw with a different design from the one matplotlib measured.Affected paths:
The SVG backend does not override
draw_tex, soRendererBase.draw_texdraws all usetex text through_draw_text_as_pathandget_glyphs_tex, for anysvg.fonttype. PathEffectRendererinherits the samedraw_tex, so usetex text with path effects is affected on every backend, including Agg.TextPath(..., usetex=True)also callsget_glyphs_tex. The Cairo backend inheritsRendererBase.draw_texas well.Possible fix
This is a safe solution because glyph outlines are still loaded at
FONT_SCALE, and glyph keys include the font name, so the sharedglyph_mapof the SVG backend keeps separate entries for CMSS8 and CMSS17 when a figure has mixed sizes. The LaTeX run is the one where the layout measurement is already cached, so the change adds no extra LaTeX runs.Tested so far as a local patch on 3.11.2:
Limitation for
TextPath:The fix takes the text size from
prop, which is correct for the SVG backend and path effects, because they pass the font properties of the text.TextPathaccepts its size separately and passesproptoget_text_pathunchanged. SoTextPath((0, 0), s, size=12, usetex=True) would run LaTeX at the default 10 pt inpropinstead of at 12 pt. Passing a copy ofpropwith its size set tosizeinTextPath.__init__would fix that.Context
I maintain curved-text, a package listed in the third-party registry that draws text along curves from
get_glyphs_texoutlines. I found this while adding usetex support to it. For now it works around the bug by settingFONT_SCALEto the text size on its ownTextToPathinstance, which fixes curved-text but not matplotlib's own SVG and path-effects output. I would be glad to open a PR with the change above and a test, if this direction is acceptable.AI disclosure
I used an AI assistant (Claude) to trace the code paths, measure the widths, and test the patch. I ran the reproduction and the measurements myself and checked the results, and I reviewed the proposed change line by line.
Operating system
Windows 11
Matplotlib Version
3.11.2
Matplotlib Backend
Agg (image), SVG and PDF via
savefigPython version
3.13.3
Jupyter version
No response
Installation
pip