cduck / drawsvg

Programmatically generate SVG (vector) images, animations, and interactive Jupyter widgets
MIT License
556 stars 61 forks source link

Text Rendering Issues - Support resvg and/or Inkscape as alternative renderers #102

Open joachimheintz opened 1 year ago

joachimheintz commented 1 year ago

there are some important features of svg text rendering which do not work in the internal jupyter notebook view, but do work when i export an svg file and then render the file with inkscape.

below are examples for some of these important differences. my question is: can text rendering in drawsvg be improved? or can the inkscape svg renderer be added as alternative? (cairosvg which is used by drawsvg states about itself: "Text and features related to fonts are poorly supported" --- which is true unfortunately.)

auto-wrapped text with 'inline-size' property (see W3C)

this is the code:

d = dw.Drawing(300,100,id_prefix='autowrapped')
# simple example for text wrapping
t = 'This text wraps at 200 pixels.'
x,y = 50,30
d.append(dw.Text(t,20,x,y,style='font-style: sans-serif; inline-size: 200px;'))
d.append(dw.Line(x,0,x,100,stroke='gray'))
d.append(dw.Line(x+200,0,x+200,100,stroke='gray'))
# export as svg
d.save_svg('auto-wrapped.svg')
# when exported as png result is wrong
d.save_png('auto-wrapped.png')
# but when the svg is rendered with inkscape it is correct
from os import system
system('inkscape -o auto-wrapped-inkscape.png auto-wrapped.svg')
d

this is the file which is rendered by drawsvg as png (with the same output as in the jupyter notebook): auto-wrapped this is the file which is rendered by inkscape: auto-wrapped-inkscape

the 'shape-inside' property (see W3C)

d = dw.Drawing(300,300,id_prefix='shapeinside')
t = 'Lorem ipsum dolor sit amet, consectetur adipisici elit, sed eiusmod tempor incidunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquid ex ea commodi consequat.'
x,y = 50,30
d.append(dw.Raw('<defs><circle id="wrap" cx="150" cy="150" r="120"/></defs>'))
d.append(dw.Text(t,20,0,0,style="""font-style: sans-serif;
                text-align: center;
                shape-inside: url(#wrap);"""))
d.append(dw.Circle(150,150,120,stroke='gray',fill='none'))
d.save_svg('shape-inside.svg')
d.save_png('shape-inside.png')
from os import system
system('inkscape -o shape-inside-inkscape.png shape-inside.svg')
d

view in the notebook and in the exported png: shape-inside rendered by inkscape: shape-inside-inkscape

white space

white space can be preserved with the white__space='pre' property (see W3C for all options).

d = dw.Drawing(300,100,id_prefix='whitespace')
d.append(dw.Text('white   space   not   preserved',14,30,30))
d.append(dw.Text('white   space   preserved',14,30,60,white_space='pre'))
d.save_svg('white-space.svg')
d.save_png('white-space.png')
from os import system
system('inkscape -o white-space-inkscape.png white-space.svg')
d

rendered in drawsvg: white-space rendered with command-line inkscape: white-space-inkscape

result / question

cairosvg which is used by drawsvg is really missing important features of svg text. is it possible to allow other rendering options? for my case, inkscape is good because it can also render to pdf. for other cases, resvg may also be an option.

my configuration: debian 11, Inkscape 1.2.2 (b0a8486541, 2022-12-01)

cduck commented 1 year ago

Thanks so much for all these examples. I've actually never used these attributes before but they appear to be part of the SVG 2 specification that CairoSVG doesn't really support. Yes, I would definitely support the addition of alternate rendering backends, especially given the limitations of CairoSVG.

I think it would be useful to add an option to Drawing.rasterize and Drawing.save_png and possibly also have a global default backend similar to matplotlib. Here's some code I've previously written to call out to inkscape:


resvg looks like a great project. I'll need to try it out sometime. Have you had good results with it? If it works well, I might try packaging it into a Python wheel for easy use with drawsvg.

cduck commented 8 months ago

Here are two relevant methods where new backends would be added: Raster.from_svg() and Raster.from_svg_to_file()