Skip to content

Commit cbb3c1a

Browse files
committed
Re-fix renderers outliving underlying streams.
e.g. `figure(layout="constrained"); imshow([[0, 1]], rasterized=True); savefig("/tmp/test.pdf")`
1 parent 3e725e4 commit cbb3c1a

File tree

4 files changed

+31
-29
lines changed

4 files changed

+31
-29
lines changed

CHANGELOG.rst

+6
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
next
2+
====
3+
4+
- Fix packaging.
5+
- Fix renderers beind finalized too early.
6+
17
v0.6 (2024-11-01)
28
=================
39

ext/_mplcairo.cpp

+13-19
Original file line numberDiff line numberDiff line change
@@ -350,7 +350,7 @@ GraphicsContextRenderer::GraphicsContextRenderer(
350350
{}
351351

352352
cairo_t* GraphicsContextRenderer::cr_from_fileformat_args(
353-
StreamSurfaceType type, std::optional<py::object> file,
353+
StreamSurfaceType type, py::object file,
354354
double width, double height, double dpi)
355355
{
356356
auto surface_create_for_stream =
@@ -384,22 +384,17 @@ cairo_t* GraphicsContextRenderer::cr_from_fileformat_args(
384384
"cairo was built without {.name} support"_format(type)
385385
.cast<std::string>()};
386386
}
387-
auto const& cb = file
388-
? cairo_write_func_t{
389-
[](void* closure, unsigned char const* data, unsigned int length)
387+
auto const& cb =
388+
[](void* closure, unsigned char const* data, unsigned int length)
390389
-> cairo_status_t {
391-
auto const& write =
392-
py::reinterpret_borrow<py::object>(static_cast<PyObject*>(closure));
393-
auto const& written =
394-
write(py::memoryview::from_memory(data, length)).cast<unsigned int>();
395-
return // NOTE: This does not appear to affect the context status.
396-
written == length ? CAIRO_STATUS_SUCCESS : CAIRO_STATUS_WRITE_ERROR;
397-
}}
398-
: nullptr;
399-
py::object write =
400-
// TODO: Why does py::none() not work here?
401-
file ? file->attr("write") : py::reinterpret_borrow<py::object>(Py_None);
402-
390+
auto const& write =
391+
py::reinterpret_borrow<py::object>(static_cast<PyObject*>(closure));
392+
auto const& written =
393+
write(py::memoryview::from_memory(data, length)).cast<unsigned int>();
394+
return // NOTE: This does not appear to affect the context status.
395+
written == length ? CAIRO_STATUS_SUCCESS : CAIRO_STATUS_WRITE_ERROR;
396+
};
397+
auto const& write = file.attr("write");
403398
auto const& surface =
404399
surface_create_for_stream(cb, write.ptr(), width, height);
405400
cairo_surface_set_fallback_resolution(surface, dpi, dpi);
@@ -416,7 +411,7 @@ cairo_t* GraphicsContextRenderer::cr_from_fileformat_args(
416411
}
417412

418413
GraphicsContextRenderer::GraphicsContextRenderer(
419-
StreamSurfaceType type, std::optional<py::object> file,
414+
StreamSurfaceType type, py::object file,
420415
double width, double height, double dpi) :
421416
GraphicsContextRenderer{
422417
cr_from_fileformat_args(type, file, width, height, dpi), width, height,
@@ -2137,8 +2132,7 @@ Only intended for debugging purposes.
21372132
.def(py::init<double, double, double>())
21382133
.def(py::init<
21392134
py::object, double, double, double, std::tuple<double, double>>())
2140-
.def(py::init<
2141-
StreamSurfaceType, std::optional<py::object>, double, double, double>())
2135+
.def(py::init<StreamSurfaceType, py::object, double, double, double>())
21422136
.def(
21432137
py::pickle(
21442138
[](GraphicsContextRenderer const& gcr) -> py::tuple {

ext/_mplcairo.h

+2-2
Original file line numberDiff line numberDiff line change
@@ -63,10 +63,10 @@ class GraphicsContextRenderer {
6363
double width, double height, double dpi,
6464
std::tuple<double, double> device_scales);
6565
static cairo_t* cr_from_fileformat_args(
66-
StreamSurfaceType type, std::optional<py::object> file,
66+
StreamSurfaceType type, py::object file,
6767
double width, double height, double dpi);
6868
GraphicsContextRenderer(
69-
StreamSurfaceType type, std::optional<py::object> file,
69+
StreamSurfaceType type, py::object file,
7070
double width, double height, double dpi);
7171

7272
static GraphicsContextRenderer make_pattern_gcr(cairo_surface_t* cr);

src/mplcairo/base.py

+10-8
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import contextlib
33
from functools import partial, partialmethod
44
from gzip import GzipFile
5+
from io import BytesIO
56
import logging
67
import os
78
from pathlib import Path
@@ -75,7 +76,7 @@ def from_pycairo_ctx(cls, ctx, width, height, dpi, orig_scale):
7576

7677
@classmethod
7778
def _for_fmt_output(cls, fmt, stream, width, height, dpi):
78-
if stream is not None and cbook.file_requires_unicode(stream):
79+
if cbook.file_requires_unicode(stream):
7980
if fmt in [_StreamSurfaceType.PS, _StreamSurfaceType.EPS]:
8081
# PS is (typically) ASCII -- Language Reference, section 3.2.
8182
stream = _BytesWritingWrapper(stream, "ascii")
@@ -286,7 +287,8 @@ def _print_vector(self, renderer_factory,
286287
self.figure.draw(renderer)
287288
except Exception as exc:
288289
draw_raises_done = type(exc).__name__ == "Done"
289-
raise
290+
if not draw_raises_done: # Else, will be re-raised below.
291+
raise
290292
finally:
291293
# _finish() corresponds finalize() in Matplotlib's PDF and SVG
292294
# backends; it is inlined in Matplotlib's PS backend. It must
@@ -295,14 +297,14 @@ def _print_vector(self, renderer_factory,
295297
# to be done before closing the stream.
296298
renderer._finish()
297299
# ... but sometimes, Matplotlib *wants* a renderer to outlast the
298-
# stream's lifetime, specifically to measure text extents. This
299-
# is done on Matplotlib's side by making Figure.draw throw a Done
300-
# exception. We catch that exception and swap in a no-write renderer
301-
# (stream = None) in that case.
300+
# stream's lifetime, specifically to measure text extents. To do so,
301+
# Matplotlib makes Figure.draw throw a Done exception. We catch that
302+
# exception and swap in a dummy renderer (stream = BytesIO()) in that
303+
# case (no actual writing will be performed).
302304
if draw_raises_done:
303-
renderer = renderer_factory(None, *self.figure.bbox.size, dpi)
305+
renderer = renderer_factory(BytesIO(), *self.figure.bbox.size, dpi)
304306
with _LOCK:
305-
self.figure.draw(renderer)
307+
self.figure.draw(renderer) # Should raise Done().
306308

307309
print_pdf = partialmethod(
308310
_print_vector, GraphicsContextRendererCairo._for_pdf_output)

0 commit comments

Comments
 (0)