richard-scryber / scryber.core

Scryber.Core is a dotnet 6 html to pdf engine written entirely in C# for creating beautiful flowing documents from html templates including css styles, object data binding and svg drawing.
Other
194 stars 31 forks source link

SVG Paths with containing dots does not work in Culture de-DE #107

Closed mtren closed 1 year ago

mtren commented 1 year ago

Describe the bug Some Svgs are not rendered properly.

To Reproduce

  1. System.Threading.Thread.CurrentThread.CurrentCulture = System.Globalization.CultureInfo.GetCultureInfo("de-DE");
  2. Load a Template with a svg containing a path containing dots in it.
  3. save as pdf

Expected behavior The svg should be rendered correctly. The coordinates in the paths should be parsed not culture dependend.

example code

            System.Threading.Thread.CurrentThread.CurrentCulture = System.Globalization.CultureInfo.GetCultureInfo("de-DE");
            var doc = (Document)Document.ParseDocument(new System.IO.StringReader(@"<html xmlns='http://www.w3.org/1999/xhtml'><head></head><body>
<svg xmlns=""http://www.w3.org/2000/svg"" height=""24"" width=""24"" fill=""#808080"" class=""canvas""><path d=""M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z""/></svg>
</body></html>"), ParseSourceType.LocalFile);
            doc.SaveAsPDF(@"C:\Temp\a.pdf",FileMode.Create);

Desktop (please complete the following information): Nuget Package Scryber.Core 6.0.2.1-beta

Additional context Add any other context about the problem here. expected.pdf actual.pdf

richard-scryber commented 1 year ago

Hi Firstly, thank you for both raising this, and also the great example.

I have had a couple of discussions and comments on the cultural parsing of content (and bound values) for this library. I will happily make this work as expected, or be as flexible as possible to make it work for everyone, but I'm not absolutely positive what would be the best way.

I can think of a flexible option, but would love a bit of feedback.

  1. Parsing of the content is done in the current culture.

  2. If an element has the lang attribute and/or an xml:lang attribute then the contents within are parsed in that culture. e.g. ... will have all content parsed in the German culture, including numbers and dates.

  3. Inner elements will override the global culture. e.g. .......... will have all the content parsed in German, except content and attributes within the svg element, which will be parsed in English.

  4. Unless the element with the lang attribute also has a translate attribute, and its value is no. e.g. .......... will have all the inner attributes and content parsed in German, including within the svg element.

  5. When binding values, then the global culture of the document will be used, e.g. German in the examples, as binding with multiple nested cultures will be confusing.

I hope this makes sense, and if it seems to work for you, then I can put some unit tests around it, and see if it flies.

bykka commented 1 year ago

Have the same issue with charts generated by apache echart library deployed as aws lambda where do not control a much environment.

In my case the svg is the next:

<svg width="1000" height="400" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" baseProfile="full" style="position:absolute;left:0;top:0;user-select:none">
  <rect width="1000" height="400" x="0" y="0" id="0" fill="none" fill-opacity="1"></rect>
  <g>
    <path d="M503.1463 120.0619L503.7363 105.0735L745 105.0735" fill="none" stroke="#5470c6"></path>
    <path d="M504.3059 279.884L505.1132 294.8623L649 294.8623" fill="none" stroke="#91cc75"></path>
    <path d="M488.3618 120.8511L417.7603 131.2324L291 131.2324" fill="none" stroke="#fac858"></path>
    <path d="M495.7775 120.1115L494.9857 105.1324L315 105.1324" fill="none" stroke="#ee6666"></path>
    <path d="M500 120A80 80 0 0 1 506.2878 120.2475L500 200Z" fill="#5470c6" stroke="#fff" stroke-linejoin="round"></path>
    <path d="M506.2878 120.2475A80 80 0 1 1 485.1759 121.3855L500 200Z" fill="#91cc75" stroke="#fff" stroke-linejoin="round"></path>
    <path d="M485.1759 121.3855A80 80 0 0 1 491.5667 120.4457L500 200Z" fill="#fac858" stroke="#fff" stroke-linejoin="round"></path>
    <path d="M491.5667 120.4457A80 80 0 0 1 500 120L500 200Z" fill="#ee6666" stroke="#fff" stroke-linejoin="round"></path>
    <text dominant-baseline="central" text-anchor="end" style="font-size:12px;font-family:sans-serif;" transform="translate(750 294.8623)" fill="black">Airplane</text>
    <text dominant-baseline="central" text-anchor="start" style="font-size:12px;font-family:sans-serif;" transform="translate(250 131.2324)" fill="black">Car</text>
    <text dominant-baseline="central" text-anchor="start" style="font-size:12px;font-family:sans-serif;" transform="translate(250 105.1324)" fill="black">Train</text>
  </g>
</svg>

But after replacing all dots to comma have got another issue

Scryber.PDFParserException : The XmlParser could not parse the source file 'Dynamic'. Could not read the value d from the Xml source
  ----> Scryber.PDFParserException : Could not read the value d from the Xml source
  ----> Scryber.PDFException : No required argument found for pdf unit in 'L' command
   at Scryber.Generation.XMLParser.Parse(String source, XmlReader reader, ParseSourceType type)
   at Scryber.Generation.XMLParser.Parse(String source, TextReader reader, ParseSourceType type)
   at Scryber.Components.Document.Parse(String source, TextReader textreader, ParseSourceType type, PDFReferenceResolver resolver, ParserSettings settings)
   at Scryber.Components.Document.Parse(String source, TextReader textreader, ParseSourceType type, PDFReferenceResolver resolver)
   at Scryber.Components.Document.Parse(TextReader reader, ParseSourceType type)

Is command L (LineTo) supported by the library?

mtren commented 1 year ago

There is also a parsing problem with in css. In what culture should external or inlined css values be parsed?

richard-scryber commented 1 year ago

Hi Pavlo, I do think this is down to the current thread culture, as I can successfully parse the svg content.

Actually, I'm quite impressed here with how it looks, although the library doesn't currently support the 'transform' and text-anchor attributes so the text was not showing - setting the x and y values resolved this.

I can try at implementing at least the transform attribute over the next couple of days.

ComplexSVG.pdf

What do you think on using the lang attribute to set the parsing culture. Would it work for you?

bykka commented 1 year ago

Check my pull request - that small change helps to parse the svg I guess the issue appears because I have the next locale settings

LANG=""
LC_COLLATE="C"
LC_CTYPE="UTF-8"
LC_MESSAGES="C"
LC_MONETARY="C"
LC_NUMERIC="C"
LC_TIME="C"
LC_ALL=
richard-scryber commented 1 year ago

@mtren , @bykka - Can I just confirm with you, then - You would never expect to write SVG values & CSS values in the local notation - it would always be in invariant format (e.g. 12,345.26px or 0.125%).

Would be good to understand.

bykka commented 1 year ago

I use 3-rd libraries/services/image packs etc., for getting svg images. In other words, I do not control how to format numbers. For today I use echart and this library generates numbers in the invariant format.

Example: https://echarts.apache.org/examples/en/editor.html?c=bump-chart

image
richard-scryber commented 1 year ago

I use 3-rd libraries/services/image packs etc., for getting svg images. In other words, I do not control how to format numbers. For today I use echart and this library generates numbers in the invariant format.

That is fab thank you Pavlo! I had a feeling but good to know. And actually good to know the output can at least be rendered I hope the project was successful for you :-)

richard-scryber commented 1 year ago

I have updated the latest package to 6.0.3.0-beta that includes these changes for formatting. I hope it works for you, either way please let me know so I can either fix or close. Thanks in advance, Richard

bykka commented 1 year ago

First of all thank you for the new version. But you have broke something with those last few commits because now you can't add svg compoent to the document - it fails with exception

Unhandled exception. Scryber.PDFLayoutException: The layout of component 'panl1' failed. Nullable object must have a value.
 ---> System.InvalidOperationException: Nullable object must have a value.
   at System.Nullable`1.get_Value()
   at Scryber.PDF.Layout.LayoutEngineBase.ApplyRelativeTransformations(PDFLayoutRegion positioned, PDFPositionOptions pos)
   at Scryber.PDF.Layout.LayoutEngineBase.DoLayoutAChild(IComponent comp, Style full)
   at Scryber.PDF.Layout.LayoutEngineCanvas.DoLayoutAChild(IComponent comp, Style full)
   at Scryber.PDF.Layout.LayoutEngineBase.DoLayoutAChild(Component comp)
   at Scryber.PDF.Layout.LayoutEngineBase.DoLayoutChildren(ComponentList children)
   at Scryber.PDF.Layout.LayoutEngineBase.DoLayoutChildren()
   at Scryber.PDF.Layout.LayoutEngineCanvas.DoLayoutChildren()
   at Scryber.PDF.Layout.LayoutEnginePanel.DoLayoutBlockComponent(PDFPositionOptions position, PDFColumnOptions columnOptions)
   at Scryber.PDF.Layout.LayoutEnginePanel.DoLayoutComponent()
   at Scryber.PDF.Layout.LayoutEngineBase.Layout(PDFLayoutContext context, Style fullstyle)
   at Scryber.PDF.Layout.LayoutEngineBase.DoLayoutViewPortComponent(IPDFViewPortComponent viewPort, Style style)
   --- End of inner exception stack trace ---
   at Scryber.PDF.Layout.LayoutEngineBase.DoLayoutViewPortComponent(IPDFViewPortComponent viewPort, Style style)
   at Scryber.PDF.Layout.LayoutEngineBase.DoLayoutAChild(IComponent comp, Style full)
   at Scryber.PDF.Layout.LayoutEngineCanvas.DoLayoutAChild(IComponent comp, Style full)
   at Scryber.PDF.Layout.LayoutEngineBase.DoLayoutAChild(Component comp)
   at Scryber.PDF.Layout.LayoutEngineBase.DoLayoutChildren(ComponentList children)
   at Scryber.PDF.Layout.LayoutEngineBase.DoLayoutChildren()
   at Scryber.PDF.Layout.LayoutEngineCanvas.DoLayoutChildren()
   at Scryber.PDF.Layout.LayoutEnginePanel.DoLayoutBlockComponent(PDFPositionOptions position, PDFColumnOptions columnOptions)
   at Scryber.PDF.Layout.LayoutEnginePanel.DoLayoutComponent()
   at Scryber.PDF.Layout.LayoutEngineBase.Layout(PDFLayoutContext context, Style fullstyle)
   at Scryber.PDF.Layout.LayoutEngineBase.DoLayoutViewPortComponent(IPDFViewPortComponent viewPort, Style style)
   at Scryber.PDF.Layout.LayoutEngineBase.DoLayoutAChild(IComponent comp, Style full)
   at Scryber.PDF.Layout.LayoutEngineBase.DoLayoutAChild(Component comp)

A new ticket has been created #113

mtren commented 1 year ago

The current Version does not fix the parsing. if (double.TryParse(valueForParsing, out var d1)) returns true, when the valueForParsing == "503.1463", but it sets d1 to 5031463

richard-scryber commented 1 year ago

Could you let me know in which file you found this code @mtren. Thank you.

richard-scryber commented 1 year ago

I have it. Updated the code in System.Drawing.Unit to try and parse invariant culture first. This should now work for you and I will update the package tonight. (Updated the unit test SVGToComponentWithInvariantUnits to check too).

richard-scryber commented 1 year ago

@mtren - Hoping I can close this on Monday - let me know if there are any issues.

mtren commented 1 year ago

Thank you, it works now for me.

richard-scryber commented 1 year ago

Great - thank you @mtren - I will close it.