deeplook / svglib

Read SVG files and convert them to other formats.
GNU Lesser General Public License v3.0
311 stars 80 forks source link

Zero-length line not drawn #319

Closed donkirkby closed 2 years ago

donkirkby commented 2 years ago

Thanks for publishing svglib, it's been really helpful in several of my projects.

The Problem

I know this sounds odd, but drawing a line of length zero with rounded end caps is an easy way to draw a dot in an SVG file. Unfortunately, the dot disappears when I convert it to PNG with svglib.

The same problem occurs with a polyline of length zero. I haven't tested it, but there might be similar problems with other shapes of size zero.

Example

Here's a minimal example:

from pathlib import Path

from reportlab.graphics import renderPM
from svglib.svglib import svg2rlg

svg_text = """\
<svg baseProfile="full" height="200" version="1.1" width="300"
    xmlns="http://www.w3.org/2000/svg"
    xmlns:ev="http://www.w3.org/2001/xml-events"
    xmlns:xlink="http://www.w3.org/1999/xlink">
    <defs />
    <line stroke="#000000" stroke-linecap="round" stroke-width="20"
        x1="150" x2="150" y1="100" y2="100" />
</svg>
"""

svg_path = Path('scratch.svg')
png_path = Path('scratch.png')
svg_path.write_text(svg_text)
rlg = svg2rlg(svg_path)
renderPM.drawToFile(rlg, png_path, fmt="PNG")

When I open the SVG file in a viewer, I see the dot. In the PNG file, I see nothing.

To see the same problem with a polyline, replace the line element with this:

<polyline stroke="#000000" stroke-linecap="round" stroke-width="20" fill="none"
    points="150,100 150,100" />

Workaround

If you change the points in the line just enough that they're not equal, then they'll stop getting filtered out. If the change is small enough, the pixels will look the same as the SVG version.

In the example, change the line's x1 value like this:

<line stroke="#000000" stroke-linecap="round" stroke-width="20"
    x1="150.001" x2="150" y1="100" y2="100" />

Analysis

You could argue that this is a bug in ReportLab, although I can't find any spec on what should happen to lines of length 0. Since svglib is trying to make image files that match the SVG appearance, I suggest you work around ReportLab's behaviour.

I think the best place for a workaround is in Svg2RlgShapeConverter.convertLine() and Svg2RlgShapeConverter.convertPolyline(), as well as the conversion functions for any other shapes with the same problem. I suggest checking whether all the points are identical, and then nudging the first one if they are.

I'll try to put together a pull request with this workaround. Let me know if you don't think the fix belongs in svglib, or if you'd prefer a different approach.

donkirkby commented 2 years ago

Other Shapes

I tried the other shapes in Chrome 97.0.4692.71, Firefox 95.0.1, and the PyCharm 2021.3.2 SVG viewer to see which ones were visible with a thick stroke at size zero.

So in summary, I plan to make a pull request that handles line, polyline, and polygon. Path and rectangle are just confusing, so I'll leave them out for now.

claudep commented 2 years ago

Thanks for the detailed report. Could you please also evaluate what would be a possible fix in reportlab itself?

donkirkby commented 2 years ago

I'm pretty sure the fix would be in _renderPM.c, but I haven't tracked it down yet. If I do track it down, do you know where the reportlab project tracks issues? I couldn't find anything beyond a mailing list.

claudep commented 2 years ago

The reportlab mailing list is the way to report issues and suggest patches.