SVG to PNG with custom fonts in Python
Asked Answered
J

5

6

I'm using Cairo/RSVG based solution for rasterizing SVG to PNG. It's already beeb described on StackOverflow in Convert SVG to PNG in Python. However, this solution doesn't seem to work with custom fonts.

I've found this page describing embedding SVG fonts.

I've tried to include them from external SVG via XLink, as described in example. I've tried embedding the font directly in the same SVG file. Having failed with that, I've tried CSS Web Fonts syntax. None of the 3 methods works when rendering with Cairo (nor does it work in the Ubuntu's default viewer Eye of GNOME).

I've tried ImageMagick, with exactly same results as Cairo.

On the other hand with all 3 font embedding methods, fonts are rendered fine in WebKit, either using Google Chrome or webkit2png.py. However, if possible I'd like to avoid using QT WebKit on server, as it's non-trivial setup (including xvfb etc.), and I'm afraid that might not result very efficient nor stable.

Is there any alternative method of rendering SVG to PNG from Python?

Jeremyjerez answered 6/9, 2011 at 13:57 Comment(1)
I used this alternative that works for PNG: https://mcmap.net/q/98890/-render-html-to-an-imageReamer
B
4

I have spent a week researching this very issue and concluded that the best way to handle server-side rendering/rasterizing of SVG with custom fonts is to install those fonts on the server. The tools I tried (rsvg, imagemagick, phantomjs, qtwebkit...) could not handle web fonts and svg fonts.

Google has several hundred fonts (and growing) that one can download and use on a server.

  • Download OTF or TTF fonts
  • Install them on the server and refresh the font cache
  • Replace the CSS definitions of the external fonts with font family names in your SVG docs.

And if you also need to use those same fonts in a web page, you could link to Google CDN directly for the WOFF files to conserve your own server time and network bandwidth.

Brooksbrookshire answered 3/11, 2012 at 23:14 Comment(1)
Note (as far as I know), you can't use fonts that are referenced within the css style tag that are not directly installed on your machine (i.e. font-face with a url referencing a font on a web server). They need to be installed on your machine. After that ImageMagick worked like a charm for me.Aramen
A
1

This is how I got custom font files on AWS s3 to be loaded (but for this example I am using a local file system) - note that they are all woffs.

If all of your fonts can be loaded by the TTFont class from fontTools, you can do something like this - it is possible to convert the different fonts into other fonts with this library as well:

from fontTools.ttLib import TTFont
from pathlib import Path
import xml.etree.ElementTree as ET
from io import BytesIO
from reportlab.graphics import renderPM
import svglib.fonts
from svglib.svglib import svg2rlg


def to_pdf_buffer(d, *args, **kwargs):
    buffer = BytesIO()
    renderPDF.drawToFile(d, buffer, *args, **kwargs)
    return buffer


def to_pil(d, *args, **kwargs):
    pil = renderPM.drawToPIL(d, *args, **kwargs)
    return pil


ET.register_namespace('', "http://www.w3.org/2000/svg")
file_path = Path("my_svg.svg")
root = ET.parse(str(file_path)).getroot()
font_map = svglib.fonts.get_global_font_map()


fonts = []
unloaded_fonts = []
Folder = "./" # folder to where your fonts are
for e in self.root.findall(".//{http://www.w3.org/2000/svg}text"):
    font_family_name = e.get('font-family')
    if font_family_name is not None and font_family_name not in fonts:
        fonts.append(font_family_name)
        path = Path(Folder)
        if path.exists() and path.is_dir():
            file = path.joinpath(Path(f"./{font_family_name}.woff"))
            data = file.read_bytes()
            try:
                TTFont(data)
            except:
                unloaded_fonts.append(font_family_name)
                continue
            else:
                try:
                    font = BytesIO(file.read_bytes())
                    font_map.register_font(font_family_name, font_path=font)
                except:
                    unloaded_fonts.append(font_family_name)



if len(unloaded_fonts) > 0:
    print(f"Could not load fonts: {', '.join(unloaded_fonts)}")

drawing = svg2rlg(BytesIO(ET.tostring(root)), font_map=font_map)
pil = to_pil(drawing, dpi=1200)
pil.save(str(file_path.with_suffix(".png")))

svglib has a font map that is available by calling the svglib.get_global_font_map() function. You can load fonts into by using the font map's method register_fonts which takes a font name and either a path or data (its somewhere in their docs - you can't supply both or something like that).

This uses the ElementTree library to parse for text element font-family tags. It gets their name and finds a font file with the that font family name and loads it, which works for my application, but won't work for others, because typically you load the fonts using css @font-face. In order for the @font-face option to work, you'd have to parse it and get it's data and register those fonts into the font map. It's not terribly difficult, but I am definitely both too lazy and having my attention redirected to a different problem at this time, so I don't have the will power to do it, but this is enough information for a foundation for another builder to come and build upon it.

Just want to clarify once more: to make this work, your text has to have a font-family and your font files must be names of those font-families; also mine were all woffs, so you may have to replace the file extension.

EDIT:

A major draw back (IMO) is that svglib does not support SVG 2, which is part of the reason why I ultimately abandoned this solution. I am still working on something for SVG 2.

Aramen answered 4/11, 2023 at 23:37 Comment(0)
P
0

You can try to use inkscape, perhaps this gives you better results:

 inkscape inputfile.svg --export-png=exportfile.png

Running this from python is described here: Calling an external command in Python

Print answered 6/9, 2011 at 14:29 Comment(3)
Not quite sure that's a software I'd like to install on serversJeremyjerez
@vartec: at least, you will need some third party product. Don't know why inkscape should be more or less trustworthy than any other solution. For Windows, there is also a portable version portableapps.com/apps/graphics_pictures/inkscape_portable if you don't want to install the thing, can be run out-of-the-box, without any admin privileges and without integrating itself into the system.Print
Just tried, doesn't work at all. Not only doesn't render custom font, but also doesn't handle external images inserted using XLink.Jeremyjerez
C
0

With Imagemagick I still struggle with svg rasterizing with fonts that are installed on the server and can be used in certain operations, but fail when using -convert from .svg to .png.... It seems to turn every type of text into arial. I think it may be a bug with ImageMagick or a certain format needed in the .svg

Carthage answered 11/7, 2013 at 13:58 Comment(0)
S
0

A couple of things to check with RSVG:

  • That the font source files are in your system or user font path
  • That font names in the SVG are not quoted, even if they contain spaces. librsvg will assume the quotes are part of the font name and it won't find the source files.
Solomonsolon answered 5/6, 2016 at 20:56 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.