Drawing.get_imagedata with EPS format fails for some drawings

Issue #66 resolved
Former user created an issue

In a large production code base, recently (perhaps due to schemdraw or matplotlib changes) our EPS Drawings now throw an exception when we call get_imagedata() on them with the EPS format.

    return drawing.get_imagedata(drawing_format.value)
py3.9/lib/python3.9/site-packages/schemdraw/schemdraw.py:405: in get_imagedata
    return self.fig.getimage(ext=fmt)  # type: ignore
py3.9/lib/python3.9/site-packages/schemdraw/backends/mpl.py:284: in getimage
    fig.savefig(output, format=ext, bbox_inches='tight')
py3.9/lib/python3.9/site-packages/matplotlib/figure.py:3272: in savefig
    self.canvas.print_figure(fname, **kwargs)
py3.9/lib/python3.9/site-packages/matplotlib/backend_bases.py:2338: in print_figure
    result = print_method(
py3.9/lib/python3.9/site-packages/matplotlib/backend_bases.py:2204: in <lambda>
    print_method = functools.wraps(meth)(lambda *args, **kwargs: meth(
py3.9/lib/python3.9/site-packages/matplotlib/_api/deprecation.py:410: in wrapper
    return func(*inner_args, **inner_kwargs)
py3.9/lib/python3.9/site-packages/matplotlib/backends/backend_ps.py:869: in _print_ps
    printer(fmt, outfile, dpi=dpi, dsc_comments=dsc_comments,
py3.9/lib/python3.9/site-packages/matplotlib/backends/backend_ps.py:927: in _print_figure
    self.figure.draw(renderer)
py3.9/lib/python3.9/site-packages/matplotlib/artist.py:74: in draw_wrapper
    result = draw(artist, renderer, *args, **kwargs)
py3.9/lib/python3.9/site-packages/matplotlib/artist.py:51: in draw_wrapper
    return draw(artist, renderer)
py3.9/lib/python3.9/site-packages/matplotlib/figure.py:3069: in draw
    mimage._draw_list_compositing_images(
py3.9/lib/python3.9/site-packages/matplotlib/image.py:131: in _draw_list_compositing_images
    a.draw(renderer)
py3.9/lib/python3.9/site-packages/matplotlib/artist.py:51: in draw_wrapper
    return draw(artist, renderer)
py3.9/lib/python3.9/site-packages/matplotlib/axes/_base.py:3106: in draw
    mimage._draw_list_compositing_images(
py3.9/lib/python3.9/site-packages/matplotlib/image.py:131: in _draw_list_compositing_images
    a.draw(renderer)
py3.9/lib/python3.9/site-packages/matplotlib/artist.py:51: in draw_wrapper
    return draw(artist, renderer)
py3.9/lib/python3.9/site-packages/matplotlib/text.py:736: in draw
    textrenderer.draw_text(gc, x, y, clean_line,
py3.9/lib/python3.9/site-packages/matplotlib/backends/backend_ps.py:248: in wrapper
    return meth(self, *args, **kwargs)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <matplotlib.backends.backend_ps.RendererPS object at 0x141d65c70>
gc = <matplotlib.backend_bases.GraphicsContextBase object at 0x141d485b0>
x = 317.3922, y = 365.8765624999998, s = ''
prop = <matplotlib.font_manager.FontProperties object at 0x1407cbeb0>
angle = 0.0, ismath = False, mtext = None

    @_log_if_debug_on
    def draw_text(self, gc, x, y, s, prop, angle, ismath=False, mtext=None):
        # docstring inherited

        if self._is_transparent(gc.get_rgb()):
            return  # Special handling for fully transparent.

        if ismath == 'TeX':
            return self.draw_tex(gc, x, y, s, prop, angle)

        if ismath:
            return self.draw_mathtext(gc, x, y, s, prop, angle)

        if mpl.rcParams['ps.useafm']:
            font = self._get_font_afm(prop)
            scale = 0.001 * prop.get_size_in_points()
            stream = []
            thisx = 0
            last_name = None  # kerns returns 0 for None.
            xs_names = []
            for c in s:
                name = uni2type1.get(ord(c), f"uni{ord(c):04X}")
                try:
                    width = font.get_width_from_char_name(name)
                except KeyError:
                    name = 'question'
                    width = font.get_width_char('?')
                kern = font.get_kern_dist_from_name(last_name, name)
                last_name = name
                thisx += kern * scale
                xs_names.append((thisx, name))
                thisx += width * scale
            ps_name = (font.postscript_name
                       .encode("ascii", "replace").decode("ascii"))
            stream.append((ps_name, xs_names))

        else:
            font = self._get_font_ttf(prop)
            self._character_tracker.track(font, s)
            stream = []
            prev_font = curr_stream = None
            for item in _text_helpers.layout(s, font):
                ps_name = (item.ft_object.postscript_name
                           .encode("ascii", "replace").decode("ascii"))
                if item.ft_object is not prev_font:
                    if curr_stream:
                        stream.append(curr_stream)
                    prev_font = item.ft_object
                    curr_stream = [ps_name, []]
                curr_stream[1].append(
                    (item.x, item.ft_object.get_glyph_name(item.glyph_idx))
                )
            # append the last entry
            stream.append(curr_stream)

        self.set_color(*gc.get_rgb())

>       for ps_name, xs_names in stream:
E       TypeError: cannot unpack non-iterable NoneType object

py3.9/lib/python3.9/site-packages/matplotlib/backends/backend_ps.py:673: TypeError

We are using the "headless" mode, which includes

matplotlib.use("Agg")

When we get to this line:

data = d.get_imagedata(ImageFormat.EPS)

The exception above occurs. This happens both on macOS with Python 3.9.13 as well as a python:3.9. In both cases we are running unit tests under pytest.

matplotlib                    3.6.0
schemdraw                     0.15

I tried to produce a minimal reproducible example for this, but was not successful.

I thought this would reproduce it, but it did not. It might require a more complex drawing to trigger this problem?

import matplotlib
from schemdraw import ImageFormat, schemdraw
from schemdraw import elements as elm

matplotlib.use("Agg")
matplotlib.rcParams["svg.fonttype"] = "none"


def main() -> None:
    d = schemdraw.Drawing()
    d += elm.Resistor().right().label('1Ω')
    data = d.get_imagedata(ImageFormat.EPS)

    print(len(data))


if __name__ == "__main__":
    main()

Comments (3)

  1. Log in to comment