deeplook / svglib

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

SVG to PDF: Text looses outline/stroke #193

Open girstenbrei opened 5 years ago

girstenbrei commented 5 years ago

So I would like to convert an svg with text to a pdf. In conversion, the text looses its outline ('stroke' in svg speak, so the internet tells me.)

This is an example svg to reproduce the problem, test.pdf is the result I get when running it.

<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="74.28mm" height="108.08mm" viewBox="0 0 210.57 306.36">
  <title>Test</title>
  <text id="txt_Type" transform="translate(100 100)" style="font-size: 24px;fill: #000;stroke: #ff0000;stroke-width: 1px;">Some Text</text>
</svg>

test.pdf

Viewing the svg in Chrome/Firefox/Inkscape/Illustrator shows the outline as expected. Also, I am not sure if this is a problem with svglib or reportlabs rendering of the pdf.

It tested stroke on a circle, that worked. But everything else I tried for text did not (changing font, size, stroke color, stroke width). Am I missing something?

Additional Infos: Running on Windows 10, Python 3.7.3, svglib 0.9.1, reportlab 3.5.23 I'd be happy to provide you with any more information if needed!

Thank you all for developing this and helping me, you are awesome!

claudep commented 5 years ago

Thanks for the well-presented report. I've studied a bit what ReportLab allows us to do with text. Although the pdfgen.PDFTextObject has some advanced rendering capabilities (notable the setTextRenderMode method which would allow to activate the fill and stroke mode), I didn't yet found the method to leverage them from the graphics.String limited set of properties. Maybe @deeplook will have an idea.

girstenbrei commented 5 years ago

@claudep Thank you for your quick reply!

Disclaimer: I have no idea, how svglib works or how reportlab works.

So the except block in svglib.py:1349 hides that the setattr(shape, rlgAttr, meth(svgAttrValue)) one line earlier fails. This seems to be caused by reportlabs.graphics.shapes.String missing the stroke*-attributes, shapes.String:1426. IMHO this should log the error on debug level.

I tried to simply add the missing attributes to shapes.String, and after a little time (copy pasting stuff) the pdf converted, but still was missing the text outline (also checked with Illustrator if the outline may be invisible).

I have no real reason why, but maybe my added attributes get lost between seeting them by svglib and rendering the pdf. And maybe this todo notice in shapes.String:1808, a line above the _attrMap may have something to do with that.

But I just don't know enough to start investigating reportlab and everything that is happening from setting the attribute until rendering the pdf. Could anyone point me in the right direction? Or should I contact reportlabs maintainers for this? @deeplook do you maybe know anything about this?

My next step would be to take svglib out of the equation, just trying to set text on a canvas in reportlab directly, investigating if stroke works when used directly.

In between I had a problem caused by the default of strokeDashArray in svglib.py:1319, the default is set to none. But if used, it seems to be handed down to svglib.py:351 convertLengthList which does not now how to handel none. Is this a separate Issue?

claudep commented 5 years ago

My next step would be to take svglib out of the equation

Good approach, tell us about your pure reportlab findings.

Is this a separate Issue?

Probably., If you can trigger the problem with a minimal SVG, it would be great (and create a new issue for that).

girstenbrei commented 5 years ago

Small experiment with reportlabs:

from reportlab.pdfgen.canvas import Canvas

c = Canvas('hello.pdf')
c.setStrokeColor((1,0,0))
c.setFillColor((0,0,1))
c.setLineWidth(2)
c.setFont('Helvetica',36)
c.drawString(100,700,'Hello World', mode=2)
c.save()

produces a PDF with text outlined by another color (good in Adobe Reader, Illustrator shows that every char is a separate object). This and this source both contain the information, but I could not find anything in the official docs. The first source even talks about this issue. But as mentioned in source 2, this only works having direct access to the canvas.

And after reading some code: the example above works, because reportlabs Canvas.drawString in canvas.py:1561 does set the TextRenderMode to the value of mode. Svglib (or any other drawing for that matter) uses the drawString method in renderPDF:157 which does offer mode settings.

The following produces (semi-good) results:

  1. Add stroke* attributes to _attrMap in shapes.String
  2. switch _PDFRenderer.drawString() from doing its own canvas.beginText(...) to just using canvas.drawString
  3. Infer mode from stroke* attributes
  4. (Do some stuff I did in between without remembering ....)

Critique I have no idea what I broke with this. Especially, as the two drawString methods seem to have completely different intentions (canvas setting text related values like RenderMode, CharSpace, ... and _PDFRenderer beeing able to compute different text alignments but completely ignoring any values set on the text itself).

Semi-good results meaning, opening the pdf in Adobe Reader works fine (with outline). But opening it in Illustrator shows, that the text and outline are completely separate objects and, at least in Illustrator, they are not even close to beeing aligned. And, at the moment, the outline is always dashed. Pro: dashing works. Con: only able to dash the line ... .

Conclusion Svglib seems to do all the right things. Yeah! Reportlab just has a few different and unconnected ways to draw a String. So, unless any one has a better idea, I will open an issue with them.

IMHO, I would leave this issue open until I know how to proceed with reportlab. Then I would add a debug log statement in svglib:1349 and send you a PR. Is that ok for you?

Thanks again for your help, cheers! Chris

claudep commented 5 years ago

Many thanks for this thorough experimentation! Your plan sounds good. If at some point svglib has to subclass a ReportLab object (say shapes.String) to allow for more attributes (waiting a fix in RL itself), we can also try to do that.

girstenbrei commented 5 years ago

@claudep I thought about pushing the functionality from svglib into reportlabs as I noticed the monkeypatch_reportlab - method, something along the lines of subclassing shapes.String, adding the missing attributes and then patching it back into reportlab (additionally forcing the change in the drawString methods). But this gets uggly quick ...

I'll let you know what the reportlab devs say!

Edit: the Issue at reportlab: https://bitbucket.org/rptlab/reportlab/issues/185/unable-to-outline-stroke-a-string-in-a

claudep commented 3 years ago

It looks like some progress has been made on Reportlab's side (from version 3.5.60). See: