diff --git a/ext/_mplcairo.cpp b/ext/_mplcairo.cpp index b9f25ff1..a7bced7f 100644 --- a/ext/_mplcairo.cpp +++ b/ext/_mplcairo.cpp @@ -618,6 +618,14 @@ void GraphicsContextRenderer::_set_size( } } +void GraphicsContextRenderer::_set_init_translation(double x, double y) +{ + auto mtx = cairo_matrix_t{1, 0, 0, 1, x, y}; + cairo_set_matrix(cr_, &mtx); + CAIRO_CHECK_SET_USER_DATA_NEW( + cairo_set_user_data, cr_, &detail::INIT_MATRIX_KEY, mtx); +} + void GraphicsContextRenderer::_show_page() { cairo_show_page(cr_); @@ -1588,10 +1596,12 @@ void GraphicsContextRenderer::draw_text( next_glyphs_pos = glyphs_pos + cluster.num_glyphs; for (auto j = glyphs_pos; j < next_glyphs_pos; ++j) { if (!gac.glyphs[j].index) { - auto missing = - py::cast(s.substr(bytes_pos, cluster.num_bytes)) - .attr("encode")("ascii", "namereplace"); - warn_on_missing_glyph(missing.cast()); + auto missing = py::cast(s.substr(bytes_pos, cluster.num_bytes)); + warn_on_missing_glyph( // Format forced by test_mathtext_ticks. + "{} ({})"_format( + py::module::import("builtins").attr("ord")(missing), + missing.attr("encode")("ascii", "namereplace").attr("decode")()) + .cast()); } } bytes_pos = next_bytes_pos; @@ -2158,6 +2168,7 @@ Only intended for debugging purposes. .def("_set_path", &GraphicsContextRenderer::_set_path) .def("_set_metadata", &GraphicsContextRenderer::_set_metadata) .def("_set_size", &GraphicsContextRenderer::_set_size) + .def("_set_init_translation", &GraphicsContextRenderer::_set_init_translation) .def("_show_page", &GraphicsContextRenderer::_show_page) .def("_get_context", &GraphicsContextRenderer::_get_context) .def("_get_buffer", &GraphicsContextRenderer::_get_buffer) diff --git a/ext/_mplcairo.h b/ext/_mplcairo.h index 5750b9c8..f2c42387 100644 --- a/ext/_mplcairo.h +++ b/ext/_mplcairo.h @@ -77,6 +77,7 @@ class GraphicsContextRenderer { void _set_path(std::optional path); void _set_metadata(std::optional metadata); void _set_size(double width, double height, double dpi); + void _set_init_translation(double x, double y); void _show_page(); py::object _get_context(); py::array _get_buffer(); diff --git a/ext/_util.cpp b/ext/_util.cpp index 13ff7043..e150d6c1 100644 --- a/ext/_util.cpp +++ b/ext/_util.cpp @@ -903,8 +903,8 @@ void warn_on_missing_glyph(std::string s) { PY_CHECK( PyErr_WarnEx, - nullptr, - "Requested glyph ({}) missing from current font."_format(s) + PyExc_UserWarning, + "Glyph {} missing from current font."_format(s) .cast().c_str(), 1); } diff --git a/src/mplcairo/base.py b/src/mplcairo/base.py index b42164c7..c17ffb66 100644 --- a/src/mplcairo/base.py +++ b/src/mplcairo/base.py @@ -264,7 +264,7 @@ def restore_region(self, region): def _print_vector(self, renderer_factory, path_or_stream, *, metadata=None, - _fixed_72dpi=True, + _fixed_72dpi=True, _forced_size=None, _offset=None, **kwargs): _check_print_extra_kwargs(**kwargs) dpi = self.figure.get_dpi() @@ -273,6 +273,10 @@ def _print_vector(self, renderer_factory, draw_raises_done = False with cbook.open_file_cm(path_or_stream, "wb") as stream: renderer = renderer_factory(stream, *self.figure.bbox.size, dpi) + if _forced_size is not None: + renderer._set_size(*_forced_size, dpi) + if _offset is not None: + renderer._set_init_translation(*_offset) try: # Setting invalid metadata can also throw, in which case the # rendered needs to be _finish()ed (to avoid later writing to a @@ -332,21 +336,33 @@ def _print_ps_impl(self, is_eps, path_or_stream, *, f"%%Orientation: {orientation}"] if "Title" in metadata: dsc_comments.append("%%Title: {}".format(metadata.pop("Title"))) + fig_wh = self.figure.get_size_inches() * np.array(72) if not is_eps and papertype != "figure": dsc_comments.append(f"%%DocumentPaperSizes: {papertype}") - print_method = partial(self._print_vector, - GraphicsContextRendererCairo._for_eps_output - if is_eps else - GraphicsContextRendererCairo._for_ps_output) + paper_wh = backend_ps.papersize[papertype] * np.array(72) + if orientation == "landscape": + paper_wh = paper_wh[::-1] + fig_wh = fig_wh[::-1] + offset = (paper_wh - fig_wh) / 2 * [1, -1] + # FIXME: We should set the init transform, including the rotation + # for landscape orientation, instead of just the offset. + else: + paper_wh = offset = None + print_method = partial( + self._print_vector, + GraphicsContextRendererCairo._for_eps_output if is_eps else + GraphicsContextRendererCairo._for_ps_output, + _forced_size=paper_wh, _offset=offset) if mpl.rcParams["ps.usedistiller"]: with TemporaryDirectory() as tmp_dirname: tmp_name = Path(tmp_dirname, "tmp") print_method(tmp_name, metadata=metadata, **kwargs) - # Assume we can get away without passing the bbox. {"ghostscript": backend_ps.gs_distill, "xpdf": backend_ps.xpdf_distill}[ mpl.rcParams["ps.usedistiller"]]( - str(tmp_name), is_eps, ptype=papertype) + str(tmp_name), is_eps, ptype=papertype, + # Assume we can get away with just bbox width/height. + bbox=(None, None, *fig_wh)) # If path_or_stream is *already* a text-mode stream then # tmp_name needs to be opened in text-mode too. with cbook.open_file_cm(path_or_stream, "wb") as stream, \