You cannot select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
	
	
		
			530 lines
		
	
	
		
			18 KiB
		
	
	
	
		
			Python
		
	
			
		
		
	
	
			530 lines
		
	
	
		
			18 KiB
		
	
	
	
		
			Python
		
	
"""
 | 
						|
A Cairo backend for Matplotlib
 | 
						|
==============================
 | 
						|
:Author: Steve Chaplin and others
 | 
						|
 | 
						|
This backend depends on cairocffi or pycairo.
 | 
						|
"""
 | 
						|
 | 
						|
import functools
 | 
						|
import gzip
 | 
						|
import math
 | 
						|
 | 
						|
import numpy as np
 | 
						|
 | 
						|
try:
 | 
						|
    import cairo
 | 
						|
    if cairo.version_info < (1, 14, 0):  # Introduced set_device_scale.
 | 
						|
        raise ImportError(f"Cairo backend requires cairo>=1.14.0, "
 | 
						|
                          f"but only {cairo.version_info} is available")
 | 
						|
except ImportError:
 | 
						|
    try:
 | 
						|
        import cairocffi as cairo
 | 
						|
    except ImportError as err:
 | 
						|
        raise ImportError(
 | 
						|
            "cairo backend requires that pycairo>=1.14.0 or cairocffi "
 | 
						|
            "is installed") from err
 | 
						|
 | 
						|
from .. import _api, cbook, font_manager
 | 
						|
from matplotlib.backend_bases import (
 | 
						|
    _Backend, FigureCanvasBase, FigureManagerBase, GraphicsContextBase,
 | 
						|
    RendererBase)
 | 
						|
from matplotlib.font_manager import ttfFontProperty
 | 
						|
from matplotlib.path import Path
 | 
						|
from matplotlib.transforms import Affine2D
 | 
						|
 | 
						|
 | 
						|
def _set_rgba(ctx, color, alpha, forced_alpha):
 | 
						|
    if len(color) == 3 or forced_alpha:
 | 
						|
        ctx.set_source_rgba(*color[:3], alpha)
 | 
						|
    else:
 | 
						|
        ctx.set_source_rgba(*color)
 | 
						|
 | 
						|
 | 
						|
def _append_path(ctx, path, transform, clip=None):
 | 
						|
    for points, code in path.iter_segments(
 | 
						|
            transform, remove_nans=True, clip=clip):
 | 
						|
        if code == Path.MOVETO:
 | 
						|
            ctx.move_to(*points)
 | 
						|
        elif code == Path.CLOSEPOLY:
 | 
						|
            ctx.close_path()
 | 
						|
        elif code == Path.LINETO:
 | 
						|
            ctx.line_to(*points)
 | 
						|
        elif code == Path.CURVE3:
 | 
						|
            cur = np.asarray(ctx.get_current_point())
 | 
						|
            a = points[:2]
 | 
						|
            b = points[-2:]
 | 
						|
            ctx.curve_to(*(cur / 3 + a * 2 / 3), *(a * 2 / 3 + b / 3), *b)
 | 
						|
        elif code == Path.CURVE4:
 | 
						|
            ctx.curve_to(*points)
 | 
						|
 | 
						|
 | 
						|
def _cairo_font_args_from_font_prop(prop):
 | 
						|
    """
 | 
						|
    Convert a `.FontProperties` or a `.FontEntry` to arguments that can be
 | 
						|
    passed to `.Context.select_font_face`.
 | 
						|
    """
 | 
						|
    def attr(field):
 | 
						|
        try:
 | 
						|
            return getattr(prop, f"get_{field}")()
 | 
						|
        except AttributeError:
 | 
						|
            return getattr(prop, field)
 | 
						|
 | 
						|
    name = attr("name")
 | 
						|
    slant = getattr(cairo, f"FONT_SLANT_{attr('style').upper()}")
 | 
						|
    weight = attr("weight")
 | 
						|
    weight = (cairo.FONT_WEIGHT_NORMAL
 | 
						|
              if font_manager.weight_dict.get(weight, weight) < 550
 | 
						|
              else cairo.FONT_WEIGHT_BOLD)
 | 
						|
    return name, slant, weight
 | 
						|
 | 
						|
 | 
						|
class RendererCairo(RendererBase):
 | 
						|
    def __init__(self, dpi):
 | 
						|
        self.dpi = dpi
 | 
						|
        self.gc = GraphicsContextCairo(renderer=self)
 | 
						|
        self.width = None
 | 
						|
        self.height = None
 | 
						|
        self.text_ctx = cairo.Context(
 | 
						|
           cairo.ImageSurface(cairo.FORMAT_ARGB32, 1, 1))
 | 
						|
        super().__init__()
 | 
						|
 | 
						|
    def set_context(self, ctx):
 | 
						|
        surface = ctx.get_target()
 | 
						|
        if hasattr(surface, "get_width") and hasattr(surface, "get_height"):
 | 
						|
            size = surface.get_width(), surface.get_height()
 | 
						|
        elif hasattr(surface, "get_extents"):  # GTK4 RecordingSurface.
 | 
						|
            ext = surface.get_extents()
 | 
						|
            size = ext.width, ext.height
 | 
						|
        else:  # vector surfaces.
 | 
						|
            ctx.save()
 | 
						|
            ctx.reset_clip()
 | 
						|
            rect, *rest = ctx.copy_clip_rectangle_list()
 | 
						|
            if rest:
 | 
						|
                raise TypeError("Cannot infer surface size")
 | 
						|
            _, _, *size = rect
 | 
						|
            ctx.restore()
 | 
						|
        self.gc.ctx = ctx
 | 
						|
        self.width, self.height = size
 | 
						|
 | 
						|
    @staticmethod
 | 
						|
    def _fill_and_stroke(ctx, fill_c, alpha, alpha_overrides):
 | 
						|
        if fill_c is not None:
 | 
						|
            ctx.save()
 | 
						|
            _set_rgba(ctx, fill_c, alpha, alpha_overrides)
 | 
						|
            ctx.fill_preserve()
 | 
						|
            ctx.restore()
 | 
						|
        ctx.stroke()
 | 
						|
 | 
						|
    def draw_path(self, gc, path, transform, rgbFace=None):
 | 
						|
        # docstring inherited
 | 
						|
        ctx = gc.ctx
 | 
						|
        # Clip the path to the actual rendering extents if it isn't filled.
 | 
						|
        clip = (ctx.clip_extents()
 | 
						|
                if rgbFace is None and gc.get_hatch() is None
 | 
						|
                else None)
 | 
						|
        transform = (transform
 | 
						|
                     + Affine2D().scale(1, -1).translate(0, self.height))
 | 
						|
        ctx.new_path()
 | 
						|
        _append_path(ctx, path, transform, clip)
 | 
						|
        if rgbFace is not None:
 | 
						|
            ctx.save()
 | 
						|
            _set_rgba(ctx, rgbFace, gc.get_alpha(), gc.get_forced_alpha())
 | 
						|
            ctx.fill_preserve()
 | 
						|
            ctx.restore()
 | 
						|
        hatch_path = gc.get_hatch_path()
 | 
						|
        if hatch_path:
 | 
						|
            dpi = int(self.dpi)
 | 
						|
            hatch_surface = ctx.get_target().create_similar(
 | 
						|
                cairo.Content.COLOR_ALPHA, dpi, dpi)
 | 
						|
            hatch_ctx = cairo.Context(hatch_surface)
 | 
						|
            _append_path(hatch_ctx, hatch_path,
 | 
						|
                         Affine2D().scale(dpi, -dpi).translate(0, dpi),
 | 
						|
                         None)
 | 
						|
            hatch_ctx.set_line_width(self.points_to_pixels(gc.get_hatch_linewidth()))
 | 
						|
            hatch_ctx.set_source_rgba(*gc.get_hatch_color())
 | 
						|
            hatch_ctx.fill_preserve()
 | 
						|
            hatch_ctx.stroke()
 | 
						|
            hatch_pattern = cairo.SurfacePattern(hatch_surface)
 | 
						|
            hatch_pattern.set_extend(cairo.Extend.REPEAT)
 | 
						|
            ctx.save()
 | 
						|
            ctx.set_source(hatch_pattern)
 | 
						|
            ctx.fill_preserve()
 | 
						|
            ctx.restore()
 | 
						|
        ctx.stroke()
 | 
						|
 | 
						|
    def draw_markers(self, gc, marker_path, marker_trans, path, transform,
 | 
						|
                     rgbFace=None):
 | 
						|
        # docstring inherited
 | 
						|
 | 
						|
        ctx = gc.ctx
 | 
						|
        ctx.new_path()
 | 
						|
        # Create the path for the marker; it needs to be flipped here already!
 | 
						|
        _append_path(ctx, marker_path, marker_trans + Affine2D().scale(1, -1))
 | 
						|
        marker_path = ctx.copy_path_flat()
 | 
						|
 | 
						|
        # Figure out whether the path has a fill
 | 
						|
        x1, y1, x2, y2 = ctx.fill_extents()
 | 
						|
        if x1 == 0 and y1 == 0 and x2 == 0 and y2 == 0:
 | 
						|
            filled = False
 | 
						|
            # No fill, just unset this (so we don't try to fill it later on)
 | 
						|
            rgbFace = None
 | 
						|
        else:
 | 
						|
            filled = True
 | 
						|
 | 
						|
        transform = (transform
 | 
						|
                     + Affine2D().scale(1, -1).translate(0, self.height))
 | 
						|
 | 
						|
        ctx.new_path()
 | 
						|
        for i, (vertices, codes) in enumerate(
 | 
						|
                path.iter_segments(transform, simplify=False)):
 | 
						|
            if len(vertices):
 | 
						|
                x, y = vertices[-2:]
 | 
						|
                ctx.save()
 | 
						|
 | 
						|
                # Translate and apply path
 | 
						|
                ctx.translate(x, y)
 | 
						|
                ctx.append_path(marker_path)
 | 
						|
 | 
						|
                ctx.restore()
 | 
						|
 | 
						|
                # Slower code path if there is a fill; we need to draw
 | 
						|
                # the fill and stroke for each marker at the same time.
 | 
						|
                # Also flush out the drawing every once in a while to
 | 
						|
                # prevent the paths from getting way too long.
 | 
						|
                if filled or i % 1000 == 0:
 | 
						|
                    self._fill_and_stroke(
 | 
						|
                        ctx, rgbFace, gc.get_alpha(), gc.get_forced_alpha())
 | 
						|
 | 
						|
        # Fast path, if there is no fill, draw everything in one step
 | 
						|
        if not filled:
 | 
						|
            self._fill_and_stroke(
 | 
						|
                ctx, rgbFace, gc.get_alpha(), gc.get_forced_alpha())
 | 
						|
 | 
						|
    def draw_image(self, gc, x, y, im):
 | 
						|
        im = cbook._unmultiplied_rgba8888_to_premultiplied_argb32(im[::-1])
 | 
						|
        surface = cairo.ImageSurface.create_for_data(
 | 
						|
            im.ravel().data, cairo.FORMAT_ARGB32,
 | 
						|
            im.shape[1], im.shape[0], im.shape[1] * 4)
 | 
						|
        ctx = gc.ctx
 | 
						|
        y = self.height - y - im.shape[0]
 | 
						|
 | 
						|
        ctx.save()
 | 
						|
        ctx.set_source_surface(surface, float(x), float(y))
 | 
						|
        ctx.paint()
 | 
						|
        ctx.restore()
 | 
						|
 | 
						|
    def draw_text(self, gc, x, y, s, prop, angle, ismath=False, mtext=None):
 | 
						|
        # docstring inherited
 | 
						|
 | 
						|
        # Note: (x, y) are device/display coords, not user-coords, unlike other
 | 
						|
        # draw_* methods
 | 
						|
        if ismath:
 | 
						|
            self._draw_mathtext(gc, x, y, s, prop, angle)
 | 
						|
 | 
						|
        else:
 | 
						|
            ctx = gc.ctx
 | 
						|
            ctx.new_path()
 | 
						|
            ctx.move_to(x, y)
 | 
						|
 | 
						|
            ctx.save()
 | 
						|
            ctx.select_font_face(*_cairo_font_args_from_font_prop(prop))
 | 
						|
            ctx.set_font_size(self.points_to_pixels(prop.get_size_in_points()))
 | 
						|
            opts = cairo.FontOptions()
 | 
						|
            opts.set_antialias(gc.get_antialiased())
 | 
						|
            ctx.set_font_options(opts)
 | 
						|
            if angle:
 | 
						|
                ctx.rotate(np.deg2rad(-angle))
 | 
						|
            ctx.show_text(s)
 | 
						|
            ctx.restore()
 | 
						|
 | 
						|
    def _draw_mathtext(self, gc, x, y, s, prop, angle):
 | 
						|
        ctx = gc.ctx
 | 
						|
        width, height, descent, glyphs, rects = \
 | 
						|
            self._text2path.mathtext_parser.parse(s, self.dpi, prop)
 | 
						|
 | 
						|
        ctx.save()
 | 
						|
        ctx.translate(x, y)
 | 
						|
        if angle:
 | 
						|
            ctx.rotate(np.deg2rad(-angle))
 | 
						|
 | 
						|
        for font, fontsize, idx, ox, oy in glyphs:
 | 
						|
            ctx.new_path()
 | 
						|
            ctx.move_to(ox, -oy)
 | 
						|
            ctx.select_font_face(
 | 
						|
                *_cairo_font_args_from_font_prop(ttfFontProperty(font)))
 | 
						|
            ctx.set_font_size(self.points_to_pixels(fontsize))
 | 
						|
            ctx.show_text(chr(idx))
 | 
						|
 | 
						|
        for ox, oy, w, h in rects:
 | 
						|
            ctx.new_path()
 | 
						|
            ctx.rectangle(ox, -oy, w, -h)
 | 
						|
            ctx.set_source_rgb(0, 0, 0)
 | 
						|
            ctx.fill_preserve()
 | 
						|
 | 
						|
        ctx.restore()
 | 
						|
 | 
						|
    def get_canvas_width_height(self):
 | 
						|
        # docstring inherited
 | 
						|
        return self.width, self.height
 | 
						|
 | 
						|
    def get_text_width_height_descent(self, s, prop, ismath):
 | 
						|
        # docstring inherited
 | 
						|
 | 
						|
        if ismath == 'TeX':
 | 
						|
            return super().get_text_width_height_descent(s, prop, ismath)
 | 
						|
 | 
						|
        if ismath:
 | 
						|
            width, height, descent, *_ = \
 | 
						|
                self._text2path.mathtext_parser.parse(s, self.dpi, prop)
 | 
						|
            return width, height, descent
 | 
						|
 | 
						|
        ctx = self.text_ctx
 | 
						|
        # problem - scale remembers last setting and font can become
 | 
						|
        # enormous causing program to crash
 | 
						|
        # save/restore prevents the problem
 | 
						|
        ctx.save()
 | 
						|
        ctx.select_font_face(*_cairo_font_args_from_font_prop(prop))
 | 
						|
        ctx.set_font_size(self.points_to_pixels(prop.get_size_in_points()))
 | 
						|
 | 
						|
        y_bearing, w, h = ctx.text_extents(s)[1:4]
 | 
						|
        ctx.restore()
 | 
						|
 | 
						|
        return w, h, h + y_bearing
 | 
						|
 | 
						|
    def new_gc(self):
 | 
						|
        # docstring inherited
 | 
						|
        self.gc.ctx.save()
 | 
						|
        # FIXME: The following doesn't properly implement a stack-like behavior
 | 
						|
        # and relies instead on the (non-guaranteed) fact that artists never
 | 
						|
        # rely on nesting gc states, so directly resetting the attributes (IOW
 | 
						|
        # a single-level stack) is enough.
 | 
						|
        self.gc._alpha = 1
 | 
						|
        self.gc._forced_alpha = False  # if True, _alpha overrides A from RGBA
 | 
						|
        self.gc._hatch = None
 | 
						|
        return self.gc
 | 
						|
 | 
						|
    def points_to_pixels(self, points):
 | 
						|
        # docstring inherited
 | 
						|
        return points / 72 * self.dpi
 | 
						|
 | 
						|
 | 
						|
class GraphicsContextCairo(GraphicsContextBase):
 | 
						|
    _joind = {
 | 
						|
        'bevel':  cairo.LINE_JOIN_BEVEL,
 | 
						|
        'miter':  cairo.LINE_JOIN_MITER,
 | 
						|
        'round':  cairo.LINE_JOIN_ROUND,
 | 
						|
    }
 | 
						|
 | 
						|
    _capd = {
 | 
						|
        'butt':        cairo.LINE_CAP_BUTT,
 | 
						|
        'projecting':  cairo.LINE_CAP_SQUARE,
 | 
						|
        'round':       cairo.LINE_CAP_ROUND,
 | 
						|
    }
 | 
						|
 | 
						|
    def __init__(self, renderer):
 | 
						|
        super().__init__()
 | 
						|
        self.renderer = renderer
 | 
						|
 | 
						|
    def restore(self):
 | 
						|
        self.ctx.restore()
 | 
						|
 | 
						|
    def set_alpha(self, alpha):
 | 
						|
        super().set_alpha(alpha)
 | 
						|
        _set_rgba(
 | 
						|
            self.ctx, self._rgb, self.get_alpha(), self.get_forced_alpha())
 | 
						|
 | 
						|
    def set_antialiased(self, b):
 | 
						|
        self.ctx.set_antialias(
 | 
						|
            cairo.ANTIALIAS_DEFAULT if b else cairo.ANTIALIAS_NONE)
 | 
						|
 | 
						|
    def get_antialiased(self):
 | 
						|
        return self.ctx.get_antialias()
 | 
						|
 | 
						|
    def set_capstyle(self, cs):
 | 
						|
        self.ctx.set_line_cap(_api.check_getitem(self._capd, capstyle=cs))
 | 
						|
        self._capstyle = cs
 | 
						|
 | 
						|
    def set_clip_rectangle(self, rectangle):
 | 
						|
        if not rectangle:
 | 
						|
            return
 | 
						|
        x, y, w, h = np.round(rectangle.bounds)
 | 
						|
        ctx = self.ctx
 | 
						|
        ctx.new_path()
 | 
						|
        ctx.rectangle(x, self.renderer.height - h - y, w, h)
 | 
						|
        ctx.clip()
 | 
						|
 | 
						|
    def set_clip_path(self, path):
 | 
						|
        if not path:
 | 
						|
            return
 | 
						|
        tpath, affine = path.get_transformed_path_and_affine()
 | 
						|
        ctx = self.ctx
 | 
						|
        ctx.new_path()
 | 
						|
        affine = (affine
 | 
						|
                  + Affine2D().scale(1, -1).translate(0, self.renderer.height))
 | 
						|
        _append_path(ctx, tpath, affine)
 | 
						|
        ctx.clip()
 | 
						|
 | 
						|
    def set_dashes(self, offset, dashes):
 | 
						|
        self._dashes = offset, dashes
 | 
						|
        if dashes is None:
 | 
						|
            self.ctx.set_dash([], 0)  # switch dashes off
 | 
						|
        else:
 | 
						|
            self.ctx.set_dash(
 | 
						|
                list(self.renderer.points_to_pixels(np.asarray(dashes))),
 | 
						|
                offset)
 | 
						|
 | 
						|
    def set_foreground(self, fg, isRGBA=None):
 | 
						|
        super().set_foreground(fg, isRGBA)
 | 
						|
        if len(self._rgb) == 3:
 | 
						|
            self.ctx.set_source_rgb(*self._rgb)
 | 
						|
        else:
 | 
						|
            self.ctx.set_source_rgba(*self._rgb)
 | 
						|
 | 
						|
    def get_rgb(self):
 | 
						|
        return self.ctx.get_source().get_rgba()[:3]
 | 
						|
 | 
						|
    def set_joinstyle(self, js):
 | 
						|
        self.ctx.set_line_join(_api.check_getitem(self._joind, joinstyle=js))
 | 
						|
        self._joinstyle = js
 | 
						|
 | 
						|
    def set_linewidth(self, w):
 | 
						|
        self._linewidth = float(w)
 | 
						|
        self.ctx.set_line_width(self.renderer.points_to_pixels(w))
 | 
						|
 | 
						|
 | 
						|
class _CairoRegion:
 | 
						|
    def __init__(self, slices, data):
 | 
						|
        self._slices = slices
 | 
						|
        self._data = data
 | 
						|
 | 
						|
 | 
						|
class FigureCanvasCairo(FigureCanvasBase):
 | 
						|
    @property
 | 
						|
    def _renderer(self):
 | 
						|
        # In theory, _renderer should be set in __init__, but GUI canvas
 | 
						|
        # subclasses (FigureCanvasFooCairo) don't always interact well with
 | 
						|
        # multiple inheritance (FigureCanvasFoo inits but doesn't super-init
 | 
						|
        # FigureCanvasCairo), so initialize it in the getter instead.
 | 
						|
        if not hasattr(self, "_cached_renderer"):
 | 
						|
            self._cached_renderer = RendererCairo(self.figure.dpi)
 | 
						|
        return self._cached_renderer
 | 
						|
 | 
						|
    def get_renderer(self):
 | 
						|
        return self._renderer
 | 
						|
 | 
						|
    def copy_from_bbox(self, bbox):
 | 
						|
        surface = self._renderer.gc.ctx.get_target()
 | 
						|
        if not isinstance(surface, cairo.ImageSurface):
 | 
						|
            raise RuntimeError(
 | 
						|
                "copy_from_bbox only works when rendering to an ImageSurface")
 | 
						|
        sw = surface.get_width()
 | 
						|
        sh = surface.get_height()
 | 
						|
        x0 = math.ceil(bbox.x0)
 | 
						|
        x1 = math.floor(bbox.x1)
 | 
						|
        y0 = math.ceil(sh - bbox.y1)
 | 
						|
        y1 = math.floor(sh - bbox.y0)
 | 
						|
        if not (0 <= x0 and x1 <= sw and bbox.x0 <= bbox.x1
 | 
						|
                and 0 <= y0 and y1 <= sh and bbox.y0 <= bbox.y1):
 | 
						|
            raise ValueError("Invalid bbox")
 | 
						|
        sls = slice(y0, y0 + max(y1 - y0, 0)), slice(x0, x0 + max(x1 - x0, 0))
 | 
						|
        data = (np.frombuffer(surface.get_data(), np.uint32)
 | 
						|
                .reshape((sh, sw))[sls].copy())
 | 
						|
        return _CairoRegion(sls, data)
 | 
						|
 | 
						|
    def restore_region(self, region):
 | 
						|
        surface = self._renderer.gc.ctx.get_target()
 | 
						|
        if not isinstance(surface, cairo.ImageSurface):
 | 
						|
            raise RuntimeError(
 | 
						|
                "restore_region only works when rendering to an ImageSurface")
 | 
						|
        surface.flush()
 | 
						|
        sw = surface.get_width()
 | 
						|
        sh = surface.get_height()
 | 
						|
        sly, slx = region._slices
 | 
						|
        (np.frombuffer(surface.get_data(), np.uint32)
 | 
						|
         .reshape((sh, sw))[sly, slx]) = region._data
 | 
						|
        surface.mark_dirty_rectangle(
 | 
						|
            slx.start, sly.start, slx.stop - slx.start, sly.stop - sly.start)
 | 
						|
 | 
						|
    def print_png(self, fobj):
 | 
						|
        self._get_printed_image_surface().write_to_png(fobj)
 | 
						|
 | 
						|
    def print_rgba(self, fobj):
 | 
						|
        width, height = self.get_width_height()
 | 
						|
        buf = self._get_printed_image_surface().get_data()
 | 
						|
        fobj.write(cbook._premultiplied_argb32_to_unmultiplied_rgba8888(
 | 
						|
            np.asarray(buf).reshape((width, height, 4))))
 | 
						|
 | 
						|
    print_raw = print_rgba
 | 
						|
 | 
						|
    def _get_printed_image_surface(self):
 | 
						|
        self._renderer.dpi = self.figure.dpi
 | 
						|
        width, height = self.get_width_height()
 | 
						|
        surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, width, height)
 | 
						|
        self._renderer.set_context(cairo.Context(surface))
 | 
						|
        self.figure.draw(self._renderer)
 | 
						|
        return surface
 | 
						|
 | 
						|
    def _save(self, fmt, fobj, *, orientation='portrait'):
 | 
						|
        # save PDF/PS/SVG
 | 
						|
 | 
						|
        dpi = 72
 | 
						|
        self.figure.dpi = dpi
 | 
						|
        w_in, h_in = self.figure.get_size_inches()
 | 
						|
        width_in_points, height_in_points = w_in * dpi, h_in * dpi
 | 
						|
 | 
						|
        if orientation == 'landscape':
 | 
						|
            width_in_points, height_in_points = (
 | 
						|
                height_in_points, width_in_points)
 | 
						|
 | 
						|
        if fmt == 'ps':
 | 
						|
            if not hasattr(cairo, 'PSSurface'):
 | 
						|
                raise RuntimeError('cairo has not been compiled with PS '
 | 
						|
                                   'support enabled')
 | 
						|
            surface = cairo.PSSurface(fobj, width_in_points, height_in_points)
 | 
						|
        elif fmt == 'pdf':
 | 
						|
            if not hasattr(cairo, 'PDFSurface'):
 | 
						|
                raise RuntimeError('cairo has not been compiled with PDF '
 | 
						|
                                   'support enabled')
 | 
						|
            surface = cairo.PDFSurface(fobj, width_in_points, height_in_points)
 | 
						|
        elif fmt in ('svg', 'svgz'):
 | 
						|
            if not hasattr(cairo, 'SVGSurface'):
 | 
						|
                raise RuntimeError('cairo has not been compiled with SVG '
 | 
						|
                                   'support enabled')
 | 
						|
            if fmt == 'svgz':
 | 
						|
                if isinstance(fobj, str):
 | 
						|
                    fobj = gzip.GzipFile(fobj, 'wb')
 | 
						|
                else:
 | 
						|
                    fobj = gzip.GzipFile(None, 'wb', fileobj=fobj)
 | 
						|
            surface = cairo.SVGSurface(fobj, width_in_points, height_in_points)
 | 
						|
        else:
 | 
						|
            raise ValueError(f"Unknown format: {fmt!r}")
 | 
						|
 | 
						|
        self._renderer.dpi = self.figure.dpi
 | 
						|
        self._renderer.set_context(cairo.Context(surface))
 | 
						|
        ctx = self._renderer.gc.ctx
 | 
						|
 | 
						|
        if orientation == 'landscape':
 | 
						|
            ctx.rotate(np.pi / 2)
 | 
						|
            ctx.translate(0, -height_in_points)
 | 
						|
            # Perhaps add an '%%Orientation: Landscape' comment?
 | 
						|
 | 
						|
        self.figure.draw(self._renderer)
 | 
						|
 | 
						|
        ctx.show_page()
 | 
						|
        surface.finish()
 | 
						|
        if fmt == 'svgz':
 | 
						|
            fobj.close()
 | 
						|
 | 
						|
    print_pdf = functools.partialmethod(_save, "pdf")
 | 
						|
    print_ps = functools.partialmethod(_save, "ps")
 | 
						|
    print_svg = functools.partialmethod(_save, "svg")
 | 
						|
    print_svgz = functools.partialmethod(_save, "svgz")
 | 
						|
 | 
						|
 | 
						|
@_Backend.export
 | 
						|
class _BackendCairo(_Backend):
 | 
						|
    backend_version = cairo.version
 | 
						|
    FigureCanvas = FigureCanvasCairo
 | 
						|
    FigureManager = FigureManagerBase
 |