Weasyprint: Add support for inline SVG

Created on 24 Apr 2013  路  19Comments  路  Source: Kozea/WeasyPrint

At the moment, to use SVG within WeasyPrint, you need to embed it (generally via an tag). It'd be really useful if WeasyPrint could handle inline SVG as well.

In other words, one should be able to do the following:

<!doctype html>
<svg style="width: 100px; height: 100px;">
  <rect width="100px" height="100px" style="fill: #c00"/>
</svg>

and get a red square.

In our particular scenario, we're generating HTML for WeasyPrint by performing an XSLT transformation on an XML document -- and in some scenarios, we're hoping to generate SVG graphics (bar charts and the like) dynamically as part of this.

feature

Most helpful comment

The data URL approach definitely works and is indeed trivial. In Django the code is:

Define a template filter:

from base64 import b64encode
@register.filter
def svg2data_url(value):
        return "data:image/svg+xml;charset=utf-8;base64,"+b64encode(value)

Use it in a template:

<img src="{{context.svg|svg2data_url}}">

All 19 comments

Yes, I鈥檇 say this is a missing feature. I wonder if you could get away with WeasyPrint giving a parsed subtree to CairoSVG, but it gets more tricky when document stylesheets apply to inline SVG elements.

In the meantime as a work-around, you could either use data: URLs, or use a custom "URL fetcher" that returns dynamically generated SVG based on an identifier or other parameters in the URL. Flask-WeasyPrint builds on the latter idea.

Hmm, yes, I hadn't thought of document styles... The data: URI works (I just tested it), and will allow us to embed static SVG images directly quite easily. Dynamic, XSLT-generated SVGs would still be a problem in that case -- albeit a problem outside the scope of WeasyPrint!

To be honest, I'd probably be super-happy if WeasyPrint passed the SVG subtree to CairoSVG for rendering, ignoring document styles -- it wouldn't necessarily be conformant, but it'd be a huge step forward in any case. I suspect that inline styles are the norm for SVG images at the moment anyway.

Yes, I鈥檇 say this is a missing feature. I wonder if you could get away with WeasyPrint giving a parsed subtree to CairoSVG, but it gets more tricky when document stylesheets apply to inline SVG elements.

Would it not be possible to pre-caclulate and flatten the CSS onto element style attributes of the inline SVG?

@graingert maybe. This only reinforces my opinion that CairoSVG and WeasyPrint should be one and the same code base :)

Although absolute and fixed positioning would be broken. It does look like CairoSVG and WeasyPrint should be one and the same.

The data URL approach definitely works and is indeed trivial. In Django the code is:

Define a template filter:

from base64 import b64encode
@register.filter
def svg2data_url(value):
        return "data:image/svg+xml;charset=utf-8;base64,"+b64encode(value)

Use it in a template:

<img src="{{context.svg|svg2data_url}}">

Please add inline SVG support. This will make things easier for people who do not have any Python experience and hence do not want to add just another new technology to their stack.

@SimonSapin would it be possible to use premailer to flatten all the CSS directly onto attributes of the SVGs?

@diethardsteiner There is no disagreement that this feature is desired. It鈥檚 mostly a matter of "someone" doing the work.

@graingert I don鈥檛 see why premailer wouldn鈥檛 work, but I don鈥檛 see either how it helps at all with inline SVG not being supported by WeasyPrint. Note that "inline" means different things in the context of premailer (style HTML attributes instead of stylesheets with selectors) and in the context of this issue (<svg> elements in an HTML document instead of <img> or <object> elements referencing an external SVG document).

This only fixes the issue that using passing a subtree would cause.

Oh, I see. Yes, I suppose we could extract a subtree, "flatten" sytelsheets into style attributes, and give that to CairoSVG. Though I鈥檇 rather have the "flattening" done by re-using WeasyPrint鈥檚 existing CSS聽parsing and Selector matching code than using Premailer.

@SimonSapin Ok thanks for your feedback

@SimonSapin where in the code is both the html tree and the cario context available?

@graingert The cairo context for the document (as opposed to contexts for intermediate surfaces used at various points) is created in the write_* methods (such as write_pdf) of the Document class in weayprint/document.py. But by the time that code is called, layout is entirely done and the HTML聽tree is not kept around anymore. We could pass it through so that it鈥檚 still available there, but modifying it at that point wouldn鈥檛 affect already-done layout.

@SimonSapin is it possible to just keep hold of the SVG fragments during the layout processing? So we can at least get it working by pretending the SVG is just an embedded image?

I don鈥檛 have an answer to that right now, sorry. (I鈥檓 not as involved in WeasyPrint as I used to be, so I won鈥檛 be spending a lot of time to work on this or think about how it could be done.)

What I remember is that I thought that the CSS rendering and SVG rendering really should share their style system. (The thing that takes stylesheets and a DOM-like tree, matches selectors, runs the cascade, and gives one computed values for every property for every element.) Right now they鈥檙e completely separate (and somewhat duplicated) in WeasyPrint and CairoSVG.

But last I talked about merging the code bases, @liZe didn鈥檛 like the idea :)

This is the code we are using for our specific use-case to inline the svgs generated by angular-nvd3.

(The selector for svgs was trickier due to namespacing, but I'm sure someone could figure it out). The issue is that it doesnt account for styling information, but got us to what we needed.

+from base64 import b64encode
+from lxml import etree
+
+    def svg_embed(html):
+        """For the child of nvd3 nodes (svg) munge them into b64encoded data
+        as a workaround for https://github.com/Kozea/WeasyPrint/issues/75"""
+        root = html.root_element
+        svgs = root.findall('.//nvd3')
+        for svg in svgs:
+            child = svg.getchildren()[0]
+            encoded = b64encode(etree.tostring(child)).decode()
+            encoded_data = "data:image/svg+xml;charset=utf-8;base64," + encoded
+            encoded_child = etree.fromstring('<img src="%s"/>' % encoded_data)
+            svg.replace(child, encoded_child)
+        return html
+
-    rendered = HTML(string=html, base_url=base_url).render()
+    rendered = svg_embed(HTML(string=html, base_url=base_url)).render()

@liZe is there any possibility for this?

@liZe is there any possibility for this?

Finding svg tags and rendering them as if they were embedded images seems to be somehow possible. But that's just a hack as it wouldn't handle CSS rules correctly (i.e. you would have to link stylesheets in the SVG file, not in the HTML file) and would probably introduce a lot of subtle problems that we can't imagine yet.

If anyone is interested in implementing that, I can give some advice, it's a good way to start hacking WeasyPrint.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

grewn0uille picture grewn0uille  路  4Comments

bjornasm picture bjornasm  路  3Comments

ajakubo1 picture ajakubo1  路  5Comments

ivanprice picture ivanprice  路  3Comments

mjbeyeler picture mjbeyeler  路  4Comments