Kozea / CairoSVG

Convert your vector images
https://courtbouillon.org/cairosvg
GNU Lesser General Public License v3.0
773 stars 151 forks source link

Render to open pyCairo context #260

Open georgesco94 opened 4 years ago

georgesco94 commented 4 years ago

Hello, I am wondering if there is a way to render an svg to an already opened pyCairo context, as opposed to a file. I cannot seem to find anything in the documentation suggesting how to do this. I know rsvg does this, but am looking for an alternative. If not, is this a planned feature for the future ? Thank you for the help

liZe commented 4 years ago

Hello!

CairoSVG doesn’t rely on Pycairo, it relies on CairoCFFI. So, you can quite easily access a CairoCFFI context, but it’s harder to use a Pycairo context (even if it’s possible).

There’s no "official" way to use an already opened context, but it should be possible by creating a class inheriting from Surface and overriding the _create_surface method.

Hope that it helps!

punkdit commented 4 years ago

Overriding _create_surface does not seem to work:

  File "/home/simon/site-packages/cairosvg/surface.py", line 213, in __init__
    self.context = cairo.Context(self.cairo)
  File "/usr/lib/python3/dist-packages/cairocffi/context.py", line 101, in __init__
    self._init_pointer(cairo.cairo_create(target._pointer))
AttributeError: 'cairo.PDFSurface' object has no attribute '_pointer'

But I did manage to hack on __init__ until I got it to work, at least for my specific requirements:

#!/usr/bin/env python3

import sys
import cairo
from cairosvg.parser import Tree
from cairosvg.surface import Surface

class MySurf(Surface):

    def _create_surface(self, width, height): # FAIL
        surface = cairo.PDFSurface("test_out.pdf", width, height)
        return surface, width, height

    def __init__(self, tree, output, dpi):

        W, H = 600., 200. # point == 1/72 inch
        surface = cairo.PDFSurface("test_out.pdf", W, H)
        self.context = cairo.Context(surface)

        self.dpi = dpi

        self._old_parent_node = self.parent_node = None
        self.output = output
        self.font_size = None

        self.context_width = W
        self.context_height = H

        self.cursor_position = [0, 0]
        self.cursor_d_position = [0, 0]
        self.text_path_width = 0
        self.stroke_and_fill = True

        self.tree_cache = {(tree.url, tree.get('id')): tree}

        self.markers = {}
        self.gradients = {}
        self.patterns = {}
        self.masks = {}
        self.paths = {}
        self.filters = {}

        self.map_rgba = None
        self.map_image = None

        self.draw(tree)

        surface.finish()

if __name__ == "__main__":
    name = sys.argv[1]
    s = open(name).read()
    tree = Tree(bytestring=s)
    my = MySurf(tree, None, 72.)
liZe commented 4 years ago

Thanks! We should have this in the documentation.

snoyer commented 3 years ago

I had the same question before I saw this issue and came up with a slightly different solution:

class ContextOnlySurface(cairosvg.surface.Surface):
    def __init__(self, tree, context):
        self.context = context

        # cache stuff
        self.markers = {}
        self.gradients = {}
        self.patterns = {}
        self.masks = {}
        self.paths = {}
        self.filters = {}
        self.images = {}
        self.tree_cache = {(tree.url, tree.get('id')): tree}

        # state stuff
        self.context_width, self.context_height , viewbox = node_format(self, tree) # used in `size` for % units
        self.dpi = 96 # used in `size` for pt units

        self.font_size = size(self, '12pt')
        self.cursor_position = [0, 0]
        self.cursor_d_position = [0, 0]
        self.text_path_width = 0
        self._old_parent_node = self.parent_node = None
        self.stroke_and_fill = True

        self.map_rgba = None
        self.map_image = None

        # self.draw(tree)   # should not be done in init

tree = cairosvg.parser.Tree(file_obj=open(...))
surface = ...
context = cairocffi.Context(surface)
ContextOnlySurface(tree, context).draw(tree)

The Surface class could (should?) be refactored to decouple the rendering to a context from the surface creation.