Source code elsie/render/backends/cairo/svg.py

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
import cairocffi as cairo
from cairosvg.colors import color
from cairosvg.helpers import node_format, size
from cairosvg.parser import Tree
from cairosvg.surface import Surface

from .draw import transform


class CairoSurface(Surface):
    def __init__(
        self,
        surface,
        x,
        y,
        tree,
        dpi,
        output=None,
        parent_surface=None,
        parent_width=None,
        parent_height=None,
        scale=1,
        output_width=None,
        output_height=None,
        background_color=None,
        map_rgba=None,
        map_image=None,
        rotation=None,
    ):
        self.surface = surface
        self.cairo = None
        self.context_width, self.context_height = parent_width, parent_height
        self.cursor_position = [0, 0]
        self.cursor_d_position = [0, 0]
        self.text_path_width = 0
        self.tree_cache = {(tree.url, tree.get("id")): tree}
        if parent_surface:
            self.markers = parent_surface.markers
            self.gradients = parent_surface.gradients
            self.patterns = parent_surface.patterns
            self.masks = parent_surface.masks
            self.paths = parent_surface.paths
            self.filters = parent_surface.filters
            self.images = parent_surface.images
        else:
            self.markers = {}
            self.gradients = {}
            self.patterns = {}
            self.masks = {}
            self.paths = {}
            self.filters = {}
            self.images = {}
        self._old_parent_node = self.parent_node = None
        self.output = output
        self.dpi = dpi
        self.font_size = size(self, "12pt")
        self.stroke_and_fill = True
        width, height, viewbox = node_format(self, tree)
        if viewbox is None:
            viewbox = (0, 0, width, height)

        if output_width and output_height:
            width, height = output_width, output_height
        elif output_width:
            if width:
                # Keep the aspect ratio
                height *= output_width / width
            width = output_width
        elif output_height:
            if height:
                # Keep the aspect ratio
                width *= output_height / height
            height = output_height
        else:
            width *= scale
            height *= scale

        # Actual surface dimensions: may be rounded on raster surfaces types
        self.cairo, self.width, self.height = self._create_surface(
            width * self.device_units_per_user_units,
            height * self.device_units_per_user_units,
        )

        if 0 in (self.width, self.height):
            raise ValueError("The SVG size is undefined")

        self.context = cairo.Context(self.cairo)
        # We must scale the context as the surface size is using physical units
        self.context.scale(
            self.device_units_per_user_units, self.device_units_per_user_units
        )
        center = (x + width / 2, y + height / 2)
        transform(self.context, center, rotation=rotation)
        self.context.translate(x, y)

        # Initial, non-rounded dimensions
        self.set_context_size(width, height, viewbox, tree)

        if background_color:
            self.context.set_source_rgba(*color(background_color))
            self.context.paint()

        self.map_rgba = map_rgba
        self.map_image = map_image

    def _create_surface(self, width, height):
        return self.surface, width, height


def render_svg(
    surface: cairo.Surface, svg: str, x, y, width, height, rotation=None, dpi=96
):
    tree = Tree(bytestring=svg.encode())
    instance = CairoSurface(
        surface=surface,
        rotation=rotation,
        x=x,
        y=y,
        tree=tree,
        dpi=dpi,
        output_width=width,
        output_height=height,
    )
    instance.draw(tree)