deeplook / svglib

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

Adding an svglib drawing to a Flowable raises AttributeError #135

Open driscollis opened 6 years ago

driscollis commented 6 years ago

I just tried to add an svglib drawing to a document template in ReportLab and I must be doing something wrong. I am betting the following traceback:

File "/home/mdriscoll/Downloads/svg_demo2.py", line 21, in <module>
  svg_demo('snakehead.svg', 'svg_demo2.pdf')
File "/home/mdriscoll/Downloads/svg_demo2.py", line 18, in svg_demo
  doc.build(story)
File "/usr/local/lib/python2.7/dist-packages/reportlab/platypus/doctemplate.py", line 1213, in build
  BaseDocTemplate.build(self,flowables, canvasmaker=canvasmaker)
File "/usr/local/lib/python2.7/dist-packages/reportlab/platypus/doctemplate.py", line 969, in build
  self.handle_flowable(flowables)
File "/usr/local/lib/python2.7/dist-packages/reportlab/platypus/doctemplate.py", line 810, in handle_flowable
  self.handle_keepWithNext(flowables)
File "/usr/local/lib/python2.7/dist-packages/reportlab/platypus/doctemplate.py", line 777, in handle_keepWithNext
  while i<n and flowables[i].getKeepWithNext() and _ktAllow(flowables[i]): i += 1

AttributeError: 'NoneType' object has no attribute 'getKeepWithNext'

Here is the sample code:

# svg_demo2.py

import os

from reportlab.graphics import renderPDF, renderPM
from reportlab.platypus import SimpleDocTemplate
from svglib.svglib import svg2rlg

def svg_demo(image_path, output_path):
    drawing = svg2rlg(image_path)

    doc = SimpleDocTemplate(output_path)

    story = []
    story.append(drawing)

    doc.build(story)

if __name__ == '__main__':
    svg_demo('snakehead.svg', 'svg_demo2.pdf')

I don't see anything in the tests that actually uses Flowables with svglib, but perhaps I am missing something?

deeplook commented 6 years ago

@driscollis Which version of svglib are you using? I've tried your example with 0.8.0 (a version available on some random machine) and the Flag of Cuba used in the test suite and it works ok for Python 2.7 and 3.5. But flowables introduce a different set of potential issues, as they make assumptions and tests about what fits inside. So the errors one gets from reportlab can potentially depend on the size of your SVG input, and I'm not sure these are always describing the reason very well. I assume @replabrobin would have a longer story to share...

replabrobin commented 6 years ago

The drawing that is returned is a flowable and can be used to render pdf/png/... with a save or can be added to a story. It's possible that the converted svg image that was returned represented something that would not fit into the frame.

driscollis commented 6 years ago

I am using 0.8.1 with ReportLab 3.4 on Python 2.7 on Xubuntu 16.04. I will try using the flag and see if it works on my machine. By the way, I forgot to link to my source code so that you would have access to the SVGs I am using, so here it is: https://github.com/driscollis/reportlabbookcode/tree/master/appendix_a_svg

driscollis commented 6 years ago

@replabrobin Yeah, maybe I should have scaled it first? Or would that have worked?

driscollis commented 6 years ago

I tried the flag of Cuba SVG and it works, even though it is too large and so the right side gets clipped. I also tried using a Python logo SVG from the repo I linked to and it doesn't through an error, but it just creates a blank PDF. I also get this weird message on stdout when I use it:

No handlers could be found for logger "svglib.svglib"

replabrobin commented 6 years ago

It seems that the snakehead example returns None from svg2rlg. I'll try and see what's going wrong.

It says

!!!!! Failed to load input file, 'snakehead.svg'! (not well-formed (invalid token): line 43, column 89)!

Duh looked at the svg and it's actually an html page :(

driscollis commented 6 years ago

Well that doesn't make sense. I just downloaded it from there on my Windows machine and it worked there. Make sure you are downloading the RAW version though.

driscollis commented 6 years ago

Github won't let me attach an SVG, so here it is as a zip file:

snakehead.svg.zip

replabrobin commented 6 years ago

I was hitting the github page and it wasn't at all raw :(; have cloned the repo now though. I do see a drawing coming out in my runs. It's offset to the right, but I suspect that's an issue related to margins etc etc.

replabrobin commented 6 years ago

This works better for me to get the image onto the A4, but the SimpleDoc class isn't really good enough to handle this as the Frame is not controllable (not there until build time).

from svglib.svglib import svg2rlg
from rlextra.thirdparty.svg.svglib import svg2rlg
def svg_demo(image_path, output_path):
    drawing = svg2rlg(image_path)
    doc = SimpleDocTemplate(output_path)
    doc.leftMargin  = 0
    doc.rightMargin = 0
    story = []
    story.append(drawing)
    doc.build(story)
if __name__ == '__main__':
    svg_demo('snakehead.svg', 'svg_demo2.pdf')
driscollis commented 6 years ago

That's cool. I changed my code to just set the margins in the constructor:


from reportlab.platypus import SimpleDocTemplate
from svglib.svglib import svg2rlg

def svg_demo(image_path, output_path):
    drawing = svg2rlg(image_path)

    doc = SimpleDocTemplate(output_path,
                            rightMargin=0,
                            leftMargin=0)

    story = []
    story.append(drawing)

    doc.build(story)

if __name__ == '__main__':
    svg_demo('snakehead.svg', 'svg_demo3.pdf')

This works

driscollis commented 6 years ago

Note that it doesn't fix the oddball python_logo.svg one, but that should probably be a separate issue.

replabrobin commented 6 years ago

Unfortunately there's padding in the Frame that gets built into SimpleDoc so the image is still offset to the right (by 6 points I think).

replabrobin commented 6 years ago

This works pretty good with SimpleDoc

# svg_demo1.py

import os

from reportlab.graphics import renderPDF, renderPM
from reportlab.platypus import SimpleDocTemplate, Image
from svglib.svglib import svg2rlg

def svg_demo(image_path, output_path):
    drawing = svg2rlg(image_path)
    factor = 6*72.0/drawing.width
    doc = SimpleDocTemplate(output_path)
    story = []
    story.append(Image(drawing, factor*drawing.width, height=factor*drawing.height))    
    doc.build(story)

if __name__ == '__main__':
    svg_demo('snakehead.svg', 'svg_demo1.pdf')
driscollis commented 6 years ago

Thanks for that

claudep commented 6 years ago

This code snippet should be adapted and integrated in the docs (README for now).

replabrobin commented 6 years ago

I looked at the python_logo issue; first problem is that the colours are defined as gradients which I don't think the RL graphics api doesn't handle. I suspect the PDF renderer could be easily extended to cope, but the PS, PM etc etc wouldn't like it.

Second the output seems to have lost track of the x & y. If I hack to get the colours OK then the image is still mostly off screen.

driscollis commented 6 years ago

@claudep Feel free to use any of my examples in your documentation as well if any of them are useful for that sort of thing

@replabrobin Thanks for looking into the python_logo issue. You needn't worry about it. I was just trying out a handful of different SVGs