Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

.svg from plotly cannot be parsed - stroke-width with length in px should be allowed #526

Closed
nicholasjin opened this issue Sep 9, 2022 · 6 comments · Fixed by #542
Closed

Comments

@nicholasjin
Copy link

Describe the bug

.svg Plot produced in plotly cannot be parsed.

Error details
If an exception is raised, it is very important that you provide the full error message.
Otherwise members of the fpdf2 won't be able to help you with your problem.

>>> fpdf.svg.SVGObject.from_file("example_output.svg")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/site-packages/fpdf/svg.py", line 875, in from_file
    return cls(svgfile.read(), *args, **kwargs)
  File "/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/site-packages/fpdf/svg.py", line 886, in __init__
    self.convert_graphics(svg_tree)
  File "/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/site-packages/fpdf/svg.py", line 936, in convert_graphics
    self.build_group(root_tag, base_group)
  File "/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/site-packages/fpdf/svg.py", line 1128, in build_group
    pdf_group.add_item(self.build_group(child))
  File "/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/site-packages/fpdf/svg.py", line 1128, in build_group
    pdf_group.add_item(self.build_group(child))
  File "/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/site-packages/fpdf/svg.py", line 1128, in build_group
    pdf_group.add_item(self.build_group(child))
  [Previous line repeated 1 more time]
  File "/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/site-packages/fpdf/svg.py", line 1130, in build_group
    pdf_group.add_item(self.build_path(child))
  File "/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/site-packages/fpdf/svg.py", line 1147, in build_path
    apply_styles(pdf_path, path)
  File "/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/site-packages/fpdf/svg.py", line 321, in apply_styles
    attr_name, value = converter(value)
  File "/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/site-packages/fpdf/svg.py", line 260, in <lambda>
    inheritable(valuestr, convert_stroke_width),
  File "/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/site-packages/fpdf/svg.py", line 232, in inheritable
    return converter(value)
  File "/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/site-packages/fpdf/svg.py", line 196, in convert_stroke_width
    val = float(incoming)
ValueError: could not convert string to float: '1px'

Minimal code
Please include some minimal Python code reproducing your issue:

import fpdf
svg_file = "example_output.svg"
svg = fpdf.svg.SVGObject.from_file(svg_file)

Below is an example plot that produces this error
example_output

If you don't know how to build a minimal reproducible example, please check this tutorial: https://stackoverflow.com/help/minimal-reproducible-example

Environment

  • fpdf v2.5.7
  • Python version 3.8.10 (Databricks) and 3.8.6 (Mac OS X)
@nicholasjin nicholasjin added the bug label Sep 9, 2022
@Lucas-C Lucas-C added the svg label Sep 13, 2022
@Lucas-C
Copy link
Member

Lucas-C commented Sep 13, 2022

Hi @nicholasjin and thank you for reporting this issue. And welcome as a fpdf2 contributor!
Could you please provide a minimal SVG file that causes your problem?
I'll get a look at this bug soon after you provide it.

@allcontributors please add @nicholasjin for bug

@allcontributors
Copy link

@Lucas-C

I've put up a pull request to add @nicholasjin! 🎉

@nicholasjin
Copy link
Author

@Lucas-C Interesting, I attached a plot with the original post but now it's gone O_o

Here it is again

example_output

@nicholasjin
Copy link
Author

Also, on the off-hand chance that this plot fails to render/download again for any reason, here is the .svg file in text format

<svg class="main-svg" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="1000" height="500" style="" viewBox="0 0 1000 500"><rect x="0" y="0" width="1000" height="500" style="fill: rgb(255, 255, 255); fill-opacity: 1;"/><defs id="defs-40d5b4"><g class="clips"><clipPath id="clip40d5b4xyplot" class="plotclip"><rect width="840" height="320"/></clipPath><clipPath class="axesclip" id="clip40d5b4x"><rect x="80" y="0" width="840" height="500"/></clipPath><clipPath class="axesclip" id="clip40d5b4y"><rect x="0" y="100" width="1000" height="320"/></clipPath><clipPath class="axesclip" id="clip40d5b4xy"><rect x="80" y="100" width="840" height="320"/></clipPath></g><g class="gradients"/><g class="patterns"/></defs><g class="bglayer"><rect class="bg" x="80" y="100" width="840" height="320" style="fill: rgb(229, 236, 246); fill-opacity: 1; stroke-width: 0;"/></g><g class="layer-below"><g class="imagelayer"/><g class="shapelayer"/></g><g class="cartesianlayer"><g class="subplot xy"><g class="layer-subplot"><g class="shapelayer"/><g class="imagelayer"/></g><g class="gridlayer"><g class="x"><path class="xgrid crisp" transform="translate(156.36,0)" d="M0,100v320" style="stroke: rgb(255, 255, 255); stroke-opacity: 1; stroke-width: 1px;"/><path class="xgrid crisp" transform="translate(232.73,0)" d="M0,100v320" style="stroke: rgb(255, 255, 255); stroke-opacity: 1; stroke-width: 1px;"/><path class="xgrid crisp" transform="translate(309.09000000000003,0)" d="M0,100v320" style="stroke: rgb(255, 255, 255); stroke-opacity: 1; stroke-width: 1px;"/><path class="xgrid crisp" transform="translate(385.45,0)" d="M0,100v320" style="stroke: rgb(255, 255, 255); stroke-opacity: 1; stroke-width: 1px;"/><path class="xgrid crisp" transform="translate(461.82,0)" d="M0,100v320" style="stroke: rgb(255, 255, 255); stroke-opacity: 1; stroke-width: 1px;"/><path class="xgrid crisp" transform="translate(538.1800000000001,0)" d="M0,100v320" style="stroke: rgb(255, 255, 255); stroke-opacity: 1; stroke-width: 1px;"/><path class="xgrid crisp" transform="translate(614.55,0)" d="M0,100v320" style="stroke: rgb(255, 255, 255); stroke-opacity: 1; stroke-width: 1px;"/><path class="xgrid crisp" transform="translate(690.91,0)" d="M0,100v320" style="stroke: rgb(255, 255, 255); stroke-opacity: 1; stroke-width: 1px;"/><path class="xgrid crisp" transform="translate(767.27,0)" d="M0,100v320" style="stroke: rgb(255, 255, 255); stroke-opacity: 1; stroke-width: 1px;"/><path class="xgrid crisp" transform="translate(843.64,0)" d="M0,100v320" style="stroke: rgb(255, 255, 255); stroke-opacity: 1; stroke-width: 1px;"/></g><g class="y"><path class="ygrid crisp" transform="translate(0,358.59)" d="M80,0h840" style="stroke: rgb(255, 255, 255); stroke-opacity: 1; stroke-width: 1px;"/><path class="ygrid crisp" transform="translate(0,293.94)" d="M80,0h840" style="stroke: rgb(255, 255, 255); stroke-opacity: 1; stroke-width: 1px;"/><path class="ygrid crisp" transform="translate(0,229.29)" d="M80,0h840" style="stroke: rgb(255, 255, 255); stroke-opacity: 1; stroke-width: 1px;"/><path class="ygrid crisp" transform="translate(0,164.65)" d="M80,0h840" style="stroke: rgb(255, 255, 255); stroke-opacity: 1; stroke-width: 1px;"/></g></g><g class="zerolinelayer"/><path class="xlines-below"/><path class="ylines-below"/><g class="overlines-below"/><g class="xaxislayer-below"/><g class="yaxislayer-below"/><g class="overaxes-below"/><g class="plot" transform="translate(80,100)" clip-path="url(#clip40d5b4xyplot)"><g class="scatterlayer mlayer"><g class="trace scatter trace7095a2" style="stroke-miterlimit: 2;"><g class="fills"><g><path class="js-fill" d="M840,323.23L0,323.23L0,323.23L840,323.23" style="fill: rgb(99, 110, 250); fill-opacity: 0.5; stroke-width: 0;"/></g><g><path class="js-fill" d="M0,323.23L76.36,323.23L152.73,251.4L229.09,282.83L305.45,269.36L381.82,258.59L458.18,290.91L534.55,237.04L610.91,298.37L687.27,323.23L763.64,277.06L840,323.23L840,323.23L0,323.23Z" style="fill: rgb(239, 85, 59); fill-opacity: 0.5; stroke-width: 0;"/></g></g><g class="errorbars"/><g class="lines"><path class="js-line" d="M0,323.23L840,323.23" style="vector-effect: non-scaling-stroke; fill: none; stroke: rgb(99, 110, 250); stroke-opacity: 1; stroke-width: 2px; opacity: 1;"/></g><g class="points"/><g class="text"/></g><g class="trace scatter tracede71a4" style="stroke-miterlimit: 2;"><g class="fills"><g><path class="js-fill" d="M0,138.53L76.36,0L152.73,107.74L305.45,53.87L381.82,64.65L458.18,129.29L534.55,150.84L610.91,124.32L687.27,242.42L763.64,92.35L840,215.49L840,323.23L763.64,277.06L687.27,323.23L610.91,298.37L534.55,237.04L458.18,290.91L381.82,258.59L305.45,269.36L229.09,282.83L152.73,251.4L76.36,323.23L0,323.23Z" style="fill: rgb(0, 204, 150); fill-opacity: 0.5; stroke-width: 0;"/></g></g><g class="errorbars"/><g class="lines"><path class="js-line" d="M0,323.23L76.36,323.23L152.73,251.4L229.09,282.83L305.45,269.36L381.82,258.59L458.18,290.91L534.55,237.04L610.91,298.37L687.27,323.23L763.64,277.06L840,323.23" style="vector-effect: non-scaling-stroke; fill: none; stroke: rgb(239, 85, 59); stroke-opacity: 1; stroke-width: 2px; opacity: 1;"/></g><g class="points"/><g class="text"/></g><g class="trace scatter trace18d7e5" style="stroke-miterlimit: 2;"><g class="fills"><g><path class="js-fill" d="M0,0L840,0L840,215.49L763.64,92.35L687.27,242.42L610.91,124.32L534.55,150.84L458.18,129.29L381.82,64.65L305.45,53.87L152.73,107.74L76.36,0L0,138.53Z" style="fill: rgb(171, 99, 250); fill-opacity: 0.5; stroke-width: 0;"/></g></g><g class="errorbars"/><g class="lines"><path class="js-line" d="M0,138.53L76.36,0L152.73,107.74L305.45,53.87L381.82,64.65L458.18,129.29L534.55,150.84L610.91,124.32L687.27,242.42L763.64,92.35L840,215.49" style="vector-effect: non-scaling-stroke; fill: none; stroke: rgb(0, 204, 150); stroke-opacity: 1; stroke-width: 2px; opacity: 1;"/></g><g class="points"/><g class="text"/></g><g class="trace scatter traceed4ef4" style="stroke-miterlimit: 2;"><g class="fills"/><g class="errorbars"/><g class="lines"><path class="js-line" d="M0,0L840,0" style="vector-effect: non-scaling-stroke; fill: none; stroke: rgb(171, 99, 250); stroke-opacity: 1; stroke-width: 2px; opacity: 1;"/></g><g class="points"/><g class="text"/></g></g></g><g class="overplot"/><path class="xlines-above crisp" d="M0,0" style="fill: none;"/><path class="ylines-above crisp" d="M0,0" style="fill: none;"/><g class="overlines-above"/><g class="xaxislayer-above"><g class="xtick"><text text-anchor="middle" x="0" y="433" transform="translate(80,0)" style="font-family: 'Open Sans', verdana, arial, sans-serif; font-size: 12px; fill: rgb(42, 63, 95); fill-opacity: 1; white-space: pre; opacity: 1;">Aug-21</text></g><g class="xtick"><text text-anchor="middle" x="0" y="433" transform="translate(156.36,0)" style="font-family: 'Open Sans', verdana, arial, sans-serif; font-size: 12px; fill: rgb(42, 63, 95); fill-opacity: 1; white-space: pre; opacity: 1;">Sep-21</text></g><g class="xtick"><text text-anchor="middle" x="0" y="433" transform="translate(232.73,0)" style="font-family: 'Open Sans', verdana, arial, sans-serif; font-size: 12px; fill: rgb(42, 63, 95); fill-opacity: 1; white-space: pre; opacity: 1;">Oct-21</text></g><g class="xtick"><text text-anchor="middle" x="0" y="433" transform="translate(309.09000000000003,0)" style="font-family: 'Open Sans', verdana, arial, sans-serif; font-size: 12px; fill: rgb(42, 63, 95); fill-opacity: 1; white-space: pre; opacity: 1;">Nov-21</text></g><g class="xtick"><text text-anchor="middle" x="0" y="433" transform="translate(385.45,0)" style="font-family: 'Open Sans', verdana, arial, sans-serif; font-size: 12px; fill: rgb(42, 63, 95); fill-opacity: 1; white-space: pre; opacity: 1;">Dec-21</text></g><g class="xtick"><text text-anchor="middle" x="0" y="433" transform="translate(461.82,0)" style="font-family: 'Open Sans', verdana, arial, sans-serif; font-size: 12px; fill: rgb(42, 63, 95); fill-opacity: 1; white-space: pre; opacity: 1;">Jan-22</text></g><g class="xtick"><text text-anchor="middle" x="0" y="433" transform="translate(538.1800000000001,0)" style="font-family: 'Open Sans', verdana, arial, sans-serif; font-size: 12px; fill: rgb(42, 63, 95); fill-opacity: 1; white-space: pre; opacity: 1;">Feb-22</text></g><g class="xtick"><text text-anchor="middle" x="0" y="433" transform="translate(614.55,0)" style="font-family: 'Open Sans', verdana, arial, sans-serif; font-size: 12px; fill: rgb(42, 63, 95); fill-opacity: 1; white-space: pre; opacity: 1;">Mar-22</text></g><g class="xtick"><text text-anchor="middle" x="0" y="433" transform="translate(690.91,0)" style="font-family: 'Open Sans', verdana, arial, sans-serif; font-size: 12px; fill: rgb(42, 63, 95); fill-opacity: 1; white-space: pre; opacity: 1;">Apr-22</text></g><g class="xtick"><text text-anchor="middle" x="0" y="433" transform="translate(767.27,0)" style="font-family: 'Open Sans', verdana, arial, sans-serif; font-size: 12px; fill: rgb(42, 63, 95); fill-opacity: 1; white-space: pre; opacity: 1;">May-22</text></g><g class="xtick"><text text-anchor="middle" x="0" y="433" transform="translate(843.64,0)" style="font-family: 'Open Sans', verdana, arial, sans-serif; font-size: 12px; fill: rgb(42, 63, 95); fill-opacity: 1; white-space: pre; opacity: 1;">Jun-22</text></g><g class="xtick"><text text-anchor="middle" x="0" y="433" transform="translate(920,0)" style="font-family: 'Open Sans', verdana, arial, sans-serif; font-size: 12px; fill: rgb(42, 63, 95); fill-opacity: 1; white-space: pre; opacity: 1;">Jul-22</text></g></g><g class="yaxislayer-above"><g class="ytick"><text text-anchor="end" x="79" y="4.199999999999999" transform="translate(0,358.59)" style="font-family: 'Open Sans', verdana, arial, sans-serif; font-size: 12px; fill: rgb(42, 63, 95); fill-opacity: 1; white-space: pre; opacity: 1;">20%</text></g><g class="ytick"><text text-anchor="end" x="79" y="4.199999999999999" style="font-family: 'Open Sans', verdana, arial, sans-serif; font-size: 12px; fill: rgb(42, 63, 95); fill-opacity: 1; white-space: pre; opacity: 1;" transform="translate(0,293.94)">40%</text></g><g class="ytick"><text text-anchor="end" x="79" y="4.199999999999999" style="font-family: 'Open Sans', verdana, arial, sans-serif; font-size: 12px; fill: rgb(42, 63, 95); fill-opacity: 1; white-space: pre; opacity: 1;" transform="translate(0,229.29)">60%</text></g><g class="ytick"><text text-anchor="end" x="79" y="4.199999999999999" style="font-family: 'Open Sans', verdana, arial, sans-serif; font-size: 12px; fill: rgb(42, 63, 95); fill-opacity: 1; white-space: pre; opacity: 1;" transform="translate(0,164.65)">80%</text></g><g class="ytick"><text text-anchor="end" x="79" y="4.199999999999999" style="font-family: 'Open Sans', verdana, arial, sans-serif; font-size: 12px; fill: rgb(42, 63, 95); fill-opacity: 1; white-space: pre; opacity: 1;" transform="translate(0,100)">100%</text></g></g><g class="overaxes-above"/></g></g><g class="polarlayer"/><g class="smithlayer"/><g class="ternarylayer"/><g class="geolayer"/><g class="funnelarealayer"/><g class="pielayer"/><g class="iciclelayer"/><g class="treemaplayer"/><g class="sunburstlayer"/><g class="glimages"/><defs id="topdefs-40d5b4"><g class="clips"/><clipPath id="legend40d5b4"><rect width="414" height="29" x="0" y="0"/></clipPath></defs><g class="layer-above"><g class="imagelayer"/><g class="shapelayer"/></g><g class="infolayer"><g class="legend" pointer-events="all" transform="translate(506,64.6)"><rect class="bg" shape-rendering="crispEdges" style="stroke: rgb(68, 68, 68); stroke-opacity: 1; fill: rgb(255, 255, 255); fill-opacity: 1; stroke-width: 0px;" width="414" height="29" x="0" y="0"/><g class="scrollbox" transform="" clip-path="url(#legend40d5b4)"><g class="groups"><g class="traces" transform="translate(0,14.5)" style="opacity: 1;"><text class="legendtext" text-anchor="start" x="40" y="4.680000000000001" style="font-family: 'Open Sans', verdana, arial, sans-serif; font-size: 12px; fill: rgb(42, 63, 95); fill-opacity: 1; white-space: pre;">Label 1</text><g class="layers"><g class="legendfill"><path class="js-fill" d="M5,-2h30v6h-30z" style="stroke-width: 0; fill: rgb(171, 99, 250); fill-opacity: 0.5;"/></g><g class="legendlines"><path class="js-line" d="M5,-2h30" style="fill: none; stroke: rgb(171, 99, 250); stroke-opacity: 1; stroke-width: 2px;"/></g><g class="legendsymbols"><g class="legendpoints"/></g></g><rect class="legendtoggle" x="0" y="-9.5" width="116.8125" height="19" style="fill: rgb(0, 0, 0); fill-opacity: 0;"/></g><g class="traces" transform="translate(119.3125,14.5)" style="opacity: 1;"><text class="legendtext" text-anchor="start" x="40" y="4.680000000000001" style="font-family: 'Open Sans', verdana, arial, sans-serif; font-size: 12px; fill: rgb(42, 63, 95); fill-opacity: 1; white-space: pre;">Label 2</text><g class="layers"><g class="legendfill"><path class="js-fill" d="M5,-2h30v6h-30z" style="stroke-width: 0; fill: rgb(0, 204, 150); fill-opacity: 0.5;"/></g><g class="legendlines"><path class="js-line" d="M5,-2h30" style="fill: none; stroke: rgb(0, 204, 150); stroke-opacity: 1; stroke-width: 2px;"/></g><g class="legendsymbols"><g class="legendpoints"/></g></g><rect class="legendtoggle" x="0" y="-9.5" width="120.046875" height="19" style="fill: rgb(0, 0, 0); fill-opacity: 0;"/></g><g class="traces" transform="translate(241.859375,14.5)" style="opacity: 1;"><text class="legendtext" text-anchor="start" x="40" y="4.680000000000001" style="font-family: 'Open Sans', verdana, arial, sans-serif; font-size: 12px; fill: rgb(42, 63, 95); fill-opacity: 1; white-space: pre;">Label 3</text><g class="layers"><g class="legendfill"><path class="js-fill" d="M5,-2h30v6h-30z" style="stroke-width: 0; fill: rgb(239, 85, 59); fill-opacity: 0.5;"/></g><g class="legendlines"><path class="js-line" d="M5,-2h30" style="fill: none; stroke: rgb(239, 85, 59); stroke-opacity: 1; stroke-width: 2px;"/></g><g class="legendsymbols"><g class="legendpoints"/></g></g><rect class="legendtoggle" x="0" y="-9.5" width="90.984375" height="19" style="fill: rgb(0, 0, 0); fill-opacity: 0;"/></g><g class="traces" transform="translate(335.34375,14.5)" style="opacity: 1;"><text class="legendtext" text-anchor="start" x="40" y="4.680000000000001" style="font-family: 'Open Sans', verdana, arial, sans-serif; font-size: 12px; fill: rgb(42, 63, 95); fill-opacity: 1; white-space: pre;">Label 4</text><g class="layers"><g class="legendfill"><path class="js-fill" d="M5,-2h30v6h-30z" style="stroke-width: 0; fill: rgb(99, 110, 250); fill-opacity: 0.5;"/></g><g class="legendlines"><path class="js-line" d="M5,-2h30" style="fill: none; stroke: rgb(99, 110, 250); stroke-opacity: 1; stroke-width: 2px;"/></g><g class="legendsymbols"><g class="legendpoints"/></g></g><rect class="legendtoggle" x="0" y="-9.5" width="76.046875" height="19" style="fill: rgb(0, 0, 0); fill-opacity: 0;"/></g></g></g><rect class="scrollbar" rx="20" ry="3" width="0" height="0" style="fill: rgb(128, 139, 164); fill-opacity: 1;" x="0" y="0"/></g><g class="g-gtitle"/><g class="g-xtitle"/><g class="g-ytitle"/></g></svg>

@gmischler
Copy link
Collaborator

here is the .svg file in text format

Your idea of "minimal" doesn't seem to be quite the same as ours... 😉
The problem could easily be demonstrated in about a hundred bytes.

Looks like our code currently neglects that "stroke-width" is technically a length, and can come with an explicit unit.

@Lucas-C Lucas-C changed the title .svg from plotly cannot be parsed .svg from plotly cannot be parsed - stroke-width with length in px should be allowed Sep 16, 2022
@Lucas-C
Copy link
Member

Lucas-C commented Sep 16, 2022

Thank you @nicholasjin for providing the SVG file, and thank you @gmischler for the analysis.

This reminds me of #376, where I did not really address the px problem.

I won't have time to submit a fix for this in the next days, but anyone is welcome to submit a patch-PR!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants