Repository navigation
Open
Labels
Difficulty: Hardhttps://matplotlib.org/devdocs/devel/contribute.html#good-first-issueshttps://matplotlib.org/devdocs/devel/contribute.html#good-first-issuesstatus: upstream fix requiredtopic: text/fontstopic: text/usetex
Description
Hello all,
I seem to run into a problem when I use the (latex) package concmath, and try saving the resultant plot as a pdf (png and jpeg work fine). As suggested in #10272, I've tried installing the "cm-super" package, but this does not solve the issue. I've also played around with the backends, but the problem still persists. A short code that generates this issue:
fig = plt.figure(figsize=(4, 4))
ax = fig.add_axes([0, 0, 1, 1])
plt.rcParams['font.family'] = 'monospace'
plt.rcParams['text.usetex'] = True
rc('text.latex', preamble=r'\usepackage[OT1]{fontenc}\usepackage{concmath}')
plt.plot([10**1,10**9], [1,10], label='Test M$_\star$')
plt.scatter([10**1,10**9], [1,10], label='Test1 M$_\star$ = 10M$_\odot$')
plt.xscale('log')
plt.yscale('log')
plt.xlabel('Test')
plt.ylabel('Test')
plt.xlim(10, 10**9)
plt.legend()
plt.savefig('test.pdf', bbox_inches='tight')
with the following traceback:
KeyError Traceback (most recent call last)
<ipython-input-3-de7a1b0d622a> in <module>
14 plt.legend()
15
---> 16 plt.savefig('test_sm.pdf', bbox_inches='tight')
~/opt/anaconda3/lib/python3.8/site-packages/matplotlib/pyplot.py in savefig(*args, **kwargs)
964 def savefig(*args, **kwargs):
965 fig = gcf()
--> 966 res = fig.savefig(*args, **kwargs)
967 fig.canvas.draw_idle() # need this if 'transparent=True' to reset colors
968 return res
~/opt/anaconda3/lib/python3.8/site-packages/matplotlib/figure.py in savefig(self, fname, transparent, **kwargs)
3003 patch.set_edgecolor('none')
3004
-> 3005 self.canvas.print_figure(fname, **kwargs)
3006
3007 if transparent:
~/opt/anaconda3/lib/python3.8/site-packages/matplotlib/backend_bases.py in print_figure(self, filename, dpi, facecolor, edgecolor, orientation, format, bbox_inches, pad_inches, bbox_extra_artists, backend, **kwargs)
2253 # force the figure dpi to 72), so we need to set it again here.
2254 with cbook._setattr_cm(self.figure, dpi=dpi):
-> 2255 result = print_method(
2256 filename,
2257 facecolor=facecolor,
~/opt/anaconda3/lib/python3.8/site-packages/matplotlib/backend_bases.py in wrapper(*args, **kwargs)
1667 kwargs.pop(arg)
1668
-> 1669 return func(*args, **kwargs)
1670
1671 return wrapper
~/opt/anaconda3/lib/python3.8/site-packages/matplotlib/_api/deprecation.py in wrapper(*inner_args, **inner_kwargs)
429 else deprecation_addendum,
430 **kwargs)
--> 431 return func(*inner_args, **inner_kwargs)
432
433 return wrapper
~/opt/anaconda3/lib/python3.8/site-packages/matplotlib/backends/backend_pdf.py in print_pdf(self, filename, dpi, bbox_inches_restore, metadata)
2723 RendererPdf(file, dpi, height, width),
2724 bbox_inches_restore=bbox_inches_restore)
-> 2725 self.figure.draw(renderer)
2726 renderer.finalize()
2727 if not isinstance(filename, PdfPages):
~/opt/anaconda3/lib/python3.8/site-packages/matplotlib/artist.py in draw_wrapper(artist, renderer, *args, **kwargs)
72 @wraps(draw)
73 def draw_wrapper(artist, renderer, *args, **kwargs):
---> 74 result = draw(artist, renderer, *args, **kwargs)
75 if renderer._rasterizing:
76 renderer.stop_rasterizing()
~/opt/anaconda3/lib/python3.8/site-packages/matplotlib/artist.py in draw_wrapper(artist, renderer, *args, **kwargs)
49 renderer.start_filter()
50
---> 51 return draw(artist, renderer, *args, **kwargs)
52 finally:
53 if artist.get_agg_filter() is not None:
~/opt/anaconda3/lib/python3.8/site-packages/matplotlib/figure.py in draw(self, renderer)
2778
2779 self.patch.draw(renderer)
-> 2780 mimage._draw_list_compositing_images(
2781 renderer, self, artists, self.suppressComposite)
2782
~/opt/anaconda3/lib/python3.8/site-packages/matplotlib/image.py in _draw_list_compositing_images(renderer, parent, artists, suppress_composite)
130 if not_composite or not has_images:
131 for a in artists:
--> 132 a.draw(renderer)
133 else:
134 # Composite any adjacent images together
~/opt/anaconda3/lib/python3.8/site-packages/matplotlib/artist.py in draw_wrapper(artist, renderer, *args, **kwargs)
49 renderer.start_filter()
50
---> 51 return draw(artist, renderer, *args, **kwargs)
52 finally:
53 if artist.get_agg_filter() is not None:
~/opt/anaconda3/lib/python3.8/site-packages/matplotlib/_api/deprecation.py in wrapper(*inner_args, **inner_kwargs)
429 else deprecation_addendum,
430 **kwargs)
--> 431 return func(*inner_args, **inner_kwargs)
432
433 return wrapper
~/opt/anaconda3/lib/python3.8/site-packages/matplotlib/axes/_base.py in draw(self, renderer, inframe)
2919 renderer.stop_rasterizing()
2920
-> 2921 mimage._draw_list_compositing_images(renderer, self, artists)
2922
2923 renderer.close_group('axes')
~/opt/anaconda3/lib/python3.8/site-packages/matplotlib/image.py in _draw_list_compositing_images(renderer, parent, artists, suppress_composite)
130 if not_composite or not has_images:
131 for a in artists:
--> 132 a.draw(renderer)
133 else:
134 # Composite any adjacent images together
~/opt/anaconda3/lib/python3.8/site-packages/matplotlib/artist.py in draw_wrapper(artist, renderer, *args, **kwargs)
49 renderer.start_filter()
50
---> 51 return draw(artist, renderer, *args, **kwargs)
52 finally:
53 if artist.get_agg_filter() is not None:
~/opt/anaconda3/lib/python3.8/site-packages/matplotlib/axis.py in draw(self, renderer, *args, **kwargs)
1139
1140 for tick in ticks_to_draw:
-> 1141 tick.draw(renderer)
1142
1143 # scale up the axis label box to also find the neighbors, not
~/opt/anaconda3/lib/python3.8/site-packages/matplotlib/artist.py in draw_wrapper(artist, renderer, *args, **kwargs)
49 renderer.start_filter()
50
---> 51 return draw(artist, renderer, *args, **kwargs)
52 finally:
53 if artist.get_agg_filter() is not None:
~/opt/anaconda3/lib/python3.8/site-packages/matplotlib/axis.py in draw(self, renderer)
300 for artist in [self.gridline, self.tick1line, self.tick2line,
301 self.label1, self.label2]:
--> 302 artist.draw(renderer)
303 renderer.close_group(self.__name__)
304 self.stale = False
~/opt/anaconda3/lib/python3.8/site-packages/matplotlib/artist.py in draw_wrapper(artist, renderer, *args, **kwargs)
49 renderer.start_filter()
50
---> 51 return draw(artist, renderer, *args, **kwargs)
52 finally:
53 if artist.get_agg_filter() is not None:
~/opt/anaconda3/lib/python3.8/site-packages/matplotlib/text.py in draw(self, renderer)
721
722 if textobj.get_usetex():
--> 723 textrenderer.draw_tex(gc, x, y, clean_line,
724 textobj._fontproperties, angle,
725 mtext=mtext)
~/opt/anaconda3/lib/python3.8/site-packages/matplotlib/_api/deprecation.py in wrapper(*inner_args, **inner_kwargs)
429 else deprecation_addendum,
430 **kwargs)
--> 431 return func(*inner_args, **inner_kwargs)
432
433 return wrapper
~/opt/anaconda3/lib/python3.8/site-packages/matplotlib/backends/backend_pdf.py in draw_tex(self, gc, x, y, s, prop, angle, ismath, mtext)
2190 for x1, y1, dvifont, glyph, width in page.text:
2191 if dvifont != oldfont:
-> 2192 pdfname = self.file.dviFontName(dvifont)
2193 seq += [['font', pdfname, dvifont.size]]
2194 oldfont = dvifont
~/opt/anaconda3/lib/python3.8/site-packages/matplotlib/backends/backend_pdf.py in dviFontName(self, dvifont)
863
864 tex_font_map = dviread.PsfontsMap(dviread.find_tex_file('pdftex.map'))
--> 865 psfont = tex_font_map[dvifont.texname]
866 if psfont.filename is None:
867 raise ValueError(
~/opt/anaconda3/lib/python3.8/site-packages/matplotlib/dviread.py in __getitem__(self, texname)
856 assert isinstance(texname, bytes)
857 try:
--> 858 result = self._font[texname]
859 except KeyError:
860 fmt = ('A PostScript file for the font whose TeX name is "{0}" '
KeyError: b'ccr10'
Please let me know if there's a way to circumvent this issue. Thank you all in advance!
-
Operating system: macOS Big Sur 11.2.3
-
Matplotlib version: 3.4.2
-
Matplotlib backend: module://ipykernel.pylab.backend_inline
-
Python version: 3.8.8
-
Matploblib came pre-installed with anaconda, but upgraded through pip.
Metadata
Metadata
Assignees
Labels
Difficulty: Hardhttps://matplotlib.org/devdocs/devel/contribute.html#good-first-issueshttps://matplotlib.org/devdocs/devel/contribute.html#good-first-issuesstatus: upstream fix requiredtopic: text/fontstopic: text/usetex
Activity
anntzer commentedon Jun 19, 2021
From some research, it looks like this arises because concmath uses fonts in the Metafont format, which is not supported by Matplotlib. If you run either latex+dvips or pdflatex on a single tex file that uses such fonts, you'll see in the log an invocation of
mktexpkandmf-nowinto convert the Metafont font to a bitmap (pk) format (you may need to clear your user-level texlive cache to see that, though). So we'd need to either look into supporting pk fonts as well (likely this will run into FreeType's lack of support, see the last entry at https://www.freetype.org/gsoc.html), or into auto-conversion of Metafont to e.g. Type1 (the usual font supported for usetex), which is not trivial (https://en.wikipedia.org/wiki/Metafont#Producing_PostScript_Type_1_fonts).(I'm tempted to close as "upstream fix required" due to the situation with FreeType.)
oorc06 commentedon Jun 19, 2021
Sorry if this is a naive question, but I'm confused about this statement: "..., which is not supported by Matplotlib.": Everything works fine as long as one doesn't try saving the plot as a pdf. For example, as mentioned earlier, there are no problems if one tries to save the figure as a png or jpeg file (and there are, of course, no issues encountered if one doesn't try to save the file at all). So wouldn't this mean that Matplotlib does support the font, and rather the problem is with saving into a pdf?
anntzer commentedon Jun 19, 2021
No, it's not a naive question at all!
That's because for png/jpeg output, we rely on dvipng to rasterize the text (i.e. transform the text into an image). For pdf we don't do that, as the point of pdf is normally to have selectable text, but then we need to actually know how to embed the font and the glyph infos into the pdf file (which is what's not supported here). I guess yet another solution would be to give up on embedding the font/glyphs in that case and just embed a dvipng-generated image...
@aitikgupta I guess this is actually relevant for your gsoc: switching to parsing dvi even for raster output would mean a small loss of functionality (for agg) in this case, and conversely, perhaps falling back on embedding dvipng rasters would be a relatively simple workaround for vector output?
aitikgupta commentedon Jun 20, 2021
hmm, what if instead we let users decide this?
A parameter while exporting a PDF:
editable(=True by default), but if the user decides that no, they don't want editable PDFs, they could switch the flag (and we will end up using the dvipng route to just embed the text image in the PDF).(edit: This could be a much simpler way of getting around user-end issues, such as this one)
On a different note, non-editable PDFs hardly make sense? If users want to use just 'images' in their PDFs (and not actual text), they could just convert PNG outputs. (or we could provide a flag like mentioned above)
But as a library, I think we should stick to editable PDFs only, and possibly fix the issue of PK fonts.
anntzer commentedon Jun 20, 2021
Just a side point: I realized we don't actually need to figure out the correct mktexpk/mf invocations ourselves, at least that part can be handled transparently by kpathsea (https://tug.org/texinfohtml/kpathsea.html#mktex-scripts). (We need to call something like
kpsewhich -mktex pk ccr10.600pk(where 600 should be replaced by some dpi-like value).)Making things user-configurable seems fine, although as always that means that both branches need to be implemented :-)
After some more investigation, it looks like pdflatex embeds such fonts as type3, taking advantage of the possibility to just specify glyphs as raster images (after all, it can use arbitrary postscript operators to define glyphs). (We'd still need to somehow get the bitmap out of the pk file, though.)
anntzer commentedon Sep 11, 2021
Just for future reference, here's a minimal repro:
Install the
concmathandconcretectan packages, thenanntzer commentedon Sep 19, 2024
I guess if we want to work on this, the first part would be to implement a parser for the pk format, following the description at https://ctan.math.illinois.edu/info/knuth-pdf/mfware/gftopk.pdf (starting at section 21). Probably a good programming exercise ;-)