css4j / echosvg

SVG implementation in the Java™ Language, fork of Apache Batik, supporting level 4 selectors and colors.
Apache License 2.0
39 stars 2 forks source link

Root element namespace does not match that requested #83

Closed tomveich closed 1 year ago

tomveich commented 1 year ago

Hello, I'm working on a Kotlin Android app which generates SVG images and I have problems converting SVG images with EchoSVG. The transcoder is throwing this error:

FATAL EXCEPTION: main
  Process: com.myapp, PID: 15648
  io.sf.carte.echosvg.transcoder.TranscoderException: null
  Enclosed Exception:
  Root element namespace does not match that requested:
  Requested: http://www.w3.org/2000/svg
  Found: null
         at io.sf.carte.echosvg.transcoder.XMLAbstractTranscoder.loadDocument(XMLAbstractTranscoder.java:148)
         at io.sf.carte.echosvg.transcoder.XMLAbstractTranscoder.transcode(XMLAbstractTranscoder.java:87)
         at io.sf.carte.echosvg.transcoder.SVGAbstractTranscoder.transcode(SVGAbstractTranscoder.java:199)
         at com.myapp.parts.ExportKt.exportPNG(Export.kt:117)

I'm confirming that the SVG files are properly formatted, working, SVGs and therefore this must be either an issue with the transcoder or the way the file is read.

The implementation:

fun exportPNG(svgFile: File): File {

    val filename = "converted_${System.currentTimeMillis()/1000}.png"
    val fileDirectory = File(
        Environment.getExternalStoragePublicDirectory(
            Environment.DIRECTORY_PICTURES),
        "My app"
    )
    fileDirectory.mkdirs()

    val convertedFile = File(fileDirectory, filename)
    val convertedFileOutput = FileOutputStream(convertedFile)

    val transcoderInput = TranscoderInput(FileInputStream(svgFile))
    val transcoderOutput = TranscoderOutput(convertedFileOutput)

    val pngTranscoder = PNGTranscoder()
    pngTranscoder.transcode(transcoderInput, transcoderOutput)

    convertedFileOutput.flush()
    convertedFileOutput.close()

    return convertedFile
}

Thank you

carlosame commented 1 year ago

Can you provide a document that triggers the issue?

carlosame commented 1 year ago

Alternatively, and assuming that your document is HTML, can you try with the following hints:

pngTranscoder.addTranscodingHint(XMLAbstractTranscoder.KEY_DOCUMENT_ELEMENT_NAMESPACE_URI,
     "http://www.w3.org/1999/xhtml");
pngTranscoder.addTranscodingHint(XMLAbstractTranscoder.KEY_DOCUMENT_ELEMENT, "html");
carlosame commented 1 year ago

The old code requires the document to explicitly set a namespace, and it seems that your document has none. The current master branch now has a fix for this.

Unless you have more issues to report, I'll release 0.3.2 with the fix. See also https://github.com/css4j/echosvg/wiki/FAQ#can-i-render-an-svg-image-that-is-inlined-inside-an-html-document

tomveich commented 1 year ago

Hi, I've been trying to get it work on this document:

<svg xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg" width="1200" height="800" viewBox="0 0 1200 800" version="1.1" id="Image">
  <g fill="#ffffff">
    <rect width="1200" height="800" stroke-linecap="round" stroke-linejoin="round" stroke-width="5.2917"/>
  </g>
  <g fill="#ff4600">
    <g transform="translate(57.83 -54.636)">
      <g transform="matrix(6.4808 0 0 6.4768 1517.2 -299.47)">
        <rect x="-243.02" y="106.13" width="185.16" height="23.16" stroke-width=".77449"/>
        <rect x="-191.6" y="54.673" width="23.145" height="123.52" stroke-width="1.066"/>
      </g>
    </g>
  </g>
</svg>

Which seems like a valid SVG with a namespace (if it's not, please tell me). I tried to create a buffered reader to read FileInputStream(svgFile) and then iterate through the lines and log them. It logged out the entire svg as shown above. I'm clueless.

carlosame commented 1 year ago

I'm unable to find anything, in the XML 1.0 and 1.1 spec, that would prevent to have the same namespace URI mapped to two different prefixes (including the default in this case). However if I turn on validation, the Xerces parser throws an exception when processing it:

lineNumber: 28; columnNumber: 78; El atributo "xmlns:svg" se debe haber declarado para el tipo de elemento "svg".
    at java.xml/com.sun.org.apache.xerces.internal.util.ErrorHandlerWrapper.createSAXParseException(ErrorHandlerWrapper.java:204)
    at java.xml/com.sun.org.apache.xerces.internal.util.ErrorHandlerWrapper.error(ErrorHandlerWrapper.java:135)
    at java.xml/com.sun.org.apache.xerces.internal.impl.XMLErrorReporter.reportError(XMLErrorReporter.java:396)
    at java.xml/com.sun.org.apache.xerces.internal.impl.XMLErrorReporter.reportError(XMLErrorReporter.java:327)
    at java.xml/com.sun.org.apache.xerces.internal.impl.XMLErrorReporter.reportError(XMLErrorReporter.java:284)
    at java.xml/com.sun.org.apache.xerces.internal.impl.dtd.XMLDTDValidator.addDTDDefaultAttrsAndValidate(XMLDTDValidator.java:1237)
    at java.xml/com.sun.org.apache.xerces.internal.impl.dtd.XMLDTDValidator.handleStartElement(XMLDTDValidator.java:1846)
    at java.xml/com.sun.org.apache.xerces.internal.impl.dtd.XMLDTDValidator.startElement(XMLDTDValidator.java:728)
    at java.xml/com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl.scanStartElement(XMLDocumentFragmentScannerImpl.java:1397)
    at java.xml/com.sun.org.apache.xerces.internal.impl.XMLDocumentScannerImpl$ContentDriver.scanRootElementHook(XMLDocumentScannerImpl.java:1292)
    at java.xml/com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl$FragmentContentDriver.next(XMLDocumentFragmentScannerImpl.java:3063)
    at java.xml/com.sun.org.apache.xerces.internal.impl.XMLDocumentScannerImpl$PrologDriver.next(XMLDocumentScannerImpl.java:836)
    at java.xml/com.sun.org.apache.xerces.internal.impl.XMLDocumentScannerImpl.next(XMLDocumentScannerImpl.java:605)
    at java.xml/com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl.scanDocument(XMLDocumentFragmentScannerImpl.java:534)
    at java.xml/com.sun.org.apache.xerces.internal.parsers.XML11Configuration.parse(XML11Configuration.java:888)
    at java.xml/com.sun.org.apache.xerces.internal.parsers.XML11Configuration.parse(XML11Configuration.java:824)
    at java.xml/com.sun.org.apache.xerces.internal.parsers.XMLParser.parse(XMLParser.java:141)
    at java.xml/com.sun.org.apache.xerces.internal.parsers.AbstractSAXParser.parse(AbstractSAXParser.java:1216)
    at java.xml/com.sun.org.apache.xerces.internal.jaxp.SAXParserImpl$JAXPSAXParser.parse(SAXParserImpl.java:635)
    at io.sf.carte.echosvg.dom.util.SAXDocumentFactory.createDocument(SAXDocumentFactory.java:555)
    ... 16 more

With validation turned off (the default), the current master processes the file just fine.

So unless you have additional issues, I'll proceed to release 0.3.2 tomorrow with the fix.

tomveich commented 1 year ago

The original SVG was generated by Inkscape, so it probably happens that some software includes an extra namespace tag. If I remove the xmlns:svg tag, hovewer, the exact same error persists. "Found: null". I'm not sure what could cause the issue but from the error, I'm guessing it has to be something within the library. It could have problems reading the file, if it says "null". Would you mind looking further into the issue? I isolated this into a blank, new android app project (to remove unnecessary clutter). How could I send it to you?

Thanks

carlosame commented 1 year ago

If I remove the xmlns:svg tag, hovewer, the exact same error persists. "Found: null". I'm not sure what could cause the issue but from the error, I'm guessing it has to be something within the library.

Strange.

It could have problems reading the file, if it says "null".

Don't think so.

Would you mind looking further into the issue? I isolated this into a blank, new android app project (to remove unnecessary clutter). How could I send it to you?

No, all I needed was the problematic file. If you want to be completely sure about the fix, I'd suggest that you grab the latest source from Github, then install to Maven local with

./gradlew publishToMavenLocal -x test

and then running 0.4-SNAPSHOT from your project. You may need to add mavenLocal() as a repository in your Gradle build:

repositories {
    [...]
    mavenLocal()
}
carlosame commented 1 year ago

And FWIW, with the following svg element:

<svg xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg"
    width="450" height="500" viewBox="0 0 1200 800" version="1.1" id="Image">

I get the next image with the current EchoSVG sources: twoPrefixes

tomveich commented 1 year ago

Thank you, I'll try 0.3.2 once it's out

carlosame commented 1 year ago

EchoSVG can render the document that was posted here, so I'm closing this as a previous step to closing the 0.3.2 milestone. If you find further issues with a new document, please reopen or create a new issue.

tomveich commented 1 year ago

The missing namespace problem seems to be solved. Unfortunately it still crashes when converting SVG to PNG.

FATAL EXCEPTION: main
  Process: com.myapp, PID: 17860
  java.lang.ClassCastException: io.sf.carte.echosvg.dom.GenericElement cannot be cast to org.w3c.dom.svg.SVGSVGElement
   at io.sf.carte.echosvg.anim.dom.SVGOMDocument.getRootElement(SVGOMDocument.java:223)
   at io.sf.carte.echosvg.transcoder.SVGAbstractTranscoder.transcode(SVGAbstractTranscoder.java:228)
   at io.sf.carte.echosvg.transcoder.image.ImageTranscoder.transcode(ImageTranscoder.java:97)
   at io.sf.carte.echosvg.transcoder.XMLAbstractTranscoder.transcode(XMLAbstractTranscoder.java:93)
   at io.sf.carte.echosvg.transcoder.SVGAbstractTranscoder.transcode(SVGAbstractTranscoder.java:199)
   at com.myapp.parts.ExportKt.exportPNG(Export.kt:133)

I tried it on following document:

<svg xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg" width="1200" height="800" viewBox="0 0 1200 800" version="1.1" id="Image"><g fill="#ffffff">
<rect width="1200" height="800" stroke-linecap="round" stroke-linejoin="round" stroke-width="5.2917"/>
</g><g fill="#ff0047"><g transform="translate(57.83 -54.636)">
<g transform="matrix(6.4808 0 0 6.4768 1517.2 -299.47)">
<rect x="-243.02" y="106.13" width="185.16" height="23.16" stroke-width=".77449"/>
<rect x="-191.6" y="54.673" width="23.145" height="123.52" stroke-width="1.066"/>
</g></g></g></svg>
carlosame commented 1 year ago

The missing namespace problem seems to be solved. Unfortunately it still crashes when converting SVG to PNG.

Unable to reproduce. So I'd suggest that you do the following, to pass your file through the standard testing infrastructure:

1) Grab the current master branch of EchoSVG, using:

git clone --depth 1 https://github.com/css4j/echosvg.git

2) Apply the following patch to echosvg-test/src/test/java/io/sf/carte/echosvg/test/svg/SamplesRenderingTest.java:

@@ -328,10 +328,18 @@ public class SamplesRenderingTest {
    @Test
    public void testEm() throws TranscoderException, IOException {
        test("samples/tests/spec/coordinates/em.svg");
    }

+   /*
+    * DOM
+    */
+   @Test
+   public void testDomTwoPrefixes() throws TranscoderException, IOException {
+       testNV("samples/tests/spec/dom/twoPrefixes.svg");
+   }
+
    /*
     * Filters
     */
    @Test
    public void testFilterEnableBackground() throws TranscoderException, IOException {

3) Create a directory samples/tests/spec/dom and put your SVG document inside with the twoPrefixes.svg name. You should now have the file:

samples/tests/spec/dom/twoPrefixes.svg

4) At the root project directory, execute:

./gradlew test --tests SamplesRenderingTest.testDomTwoPrefixes

The test will fail, but then you should have the resulting image at:

test-references/samples/tests/spec/dom/candidate-reference/twoPrefixes.png

5) If you move the file to its parent directory, like:

test-references/samples/tests/spec/dom/twoPrefixes.png

you should be able to re-run the test flawlessly. At least I can.

carlosame commented 1 year ago

I just did that for you with the issue-83 branch.

carlosame commented 1 year ago

PR #84

carlosame commented 1 year ago

I'm also successful with the CSSTranscodingHelper, have you tried that?

tomveich commented 1 year ago

I just run the test, here are the results:

> Task :echosvg-test:test

SamplesRenderingTest > testDomTwoPrefixes() FAILED
    java.lang.IllegalArgumentException at SamplesRenderingTest.java:1575

1 test completed, 1 failed

> Task :echosvg-test:test FAILED

FAILURE: Build failed with an exception.

* What went wrong:
Execution failed for task ':echosvg-test:test'.
> There were failing tests. See the report at: file:///home/abc/Downloads/echosvg/echosvg-test/build/reports/tests/test/index.html

* Try:
> Run with --scan to get full insights.

BUILD FAILED in 5s
150 actionable tasks: 49 executed, 101 up-to-date

When I open the index.html file the error points at, I can find this:

java.lang.IllegalArgumentException: Ref. image has width 100 but generated image is 1200
    at io.sf.carte.echosvg.test.image.ImageComparator.createExactDiffImage(ImageComparator.java:628)
    at io.sf.carte.echosvg.test.svg.AbstractRenderingAccuracyTest.saveRangeDiff(AbstractRenderingAccuracyTest.java:450)
    at io.sf.carte.echosvg.test.svg.AbstractRenderingAccuracyTest.runTest(AbstractRenderingAccuracyTest.java:406)
    at io.sf.carte.echosvg.test.svg.SamplesRenderingTest.test(SamplesRenderingTest.java:1575)
    at io.sf.carte.echosvg.test.svg.SamplesRenderingTest.testNV(SamplesRenderingTest.java:1535)
    at io.sf.carte.echosvg.test.svg.SamplesRenderingTest.testDomTwoPrefixes(SamplesRenderingTest.java:339)
    at java.base/java.lang.reflect.Method.invoke(Method.java:568)
    at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
    at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
carlosame commented 1 year ago

When I open the index.html file the error points at, I can find this:

Yes that's expected, please move the file the way I told you.

tomveich commented 1 year ago

Now it works without problems.. at least on my computer. It is so strange.. I can't get it to work on Android under any circumstances. I'm still getting this:

java.lang.ClassCastException: io.sf.carte.echosvg.dom.GenericElement cannot be cast to org.w3c.dom.svg.SVGSVGElement

(on the same file we were just testing)

carlosame commented 1 year ago

I can't get it to work on Android under any circumstances.

Android... just guessing, in EchoSVG/Batik the order of DOM element attributes is undefined, maybe on Android gets a different order and it impacts how it is recognizing the element.

In summary, your problem is that the SAXDocumentFactory does not recognize the svg element as belonging to the http://www.w3.org/2000/svg namespace. I could not reproduce that and I don't know the reason. If you could debug what happens inside SAXDocumentFactory.startElement when it is processing the svg element, that would help a lot.

Once the problem is diagnosed, the solution to the issue should be quite simple.

carlosame commented 1 year ago

FWIW I can reproduce your problem if I remove the xmlns="http://www.w3.org/2000/svg" attribute. Maybe the Android XML parser is somehow removing it?

I'd suggest opening a new issue or a discussion.