css4j / echosvg

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

EchoSVG on Android #85

Closed tomveich closed 1 year ago

tomveich commented 1 year ago

I opened a new issue for this. If I try to convert any (valid) SVG, I keep getting this error.

FATAL EXCEPTION: main
          Process: com.myapp, PID: 14681
          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)
           ...

The compiler suggests it's being caused by the return statement in SVGSVGElement getRootElement():

public SVGSVGElement getRootElement() {
return (SVGSVGElement) getDocumentElement();
}

File: echosvg/echosvg-anim/src/main/java/io/sf/carte/echosvg/anim/dom/SVGOMDocument.java

Let me know if you need any specific details Thank you

carlosame commented 1 year ago

If I try to convert any (valid) SVG, I keep getting this error.

This implies that your XML parser is not doing namespace handling, so my suggestion would be:

SAXParserFactory factory = SAXParserFactory.newInstance();
factory.setNamespaceAware(true);
SAXParser parser = factory.newSAXParser();
XMLReader reader = parser.getXMLReader();
transcoderInput.setXMLReader(reader);

or alternatively:

SAXParser parser = SAXParserFactory.newSAXParser() 
XMLReader reader = parser.getXMLReader();
reader.setFeature("http://xml.org/sax/features/namespaces", true);
transcoderInput.setXMLReader(reader);

or whatever Android requires to set in order to support namespaces. It is weird, because http://xml.org/sax/features/namespaces is supposed to be true by default. Please let me know what works so I can put it in the FAQ.

That said, I would not expect EchoSVG to work on Android because that OS does not have the java.awt package(s). I know that there is a Harmony-based open source replacement but I would be surprised if it is complete enough. Again, if you manage to run it on Android please let me know so I can put that on the FAQ.

tomveich commented 1 year ago

None of the code you suggested affected the output. The error persists. I found this library in the official android docs: https://developer.android.com/reference/org/xmlpull/v1/package-summary but I couldn't find a reader() method there. Thank you

carlosame commented 1 year ago

None of the code you suggested affected the output. The error persists.

Then I'd suggest debugging what happens inside SAXDocumentFactory.startElement when it is processing the svg element. I mean checking which uri is it reporting when the localName is svg. If uri is null as I say (because it's the only way I can reproduce your issue) then the XML parser is to blame, not EchoSVG.

I found this library in the official android docs: https://developer.android.com/reference/org/xmlpull/v1/package-summary but I couldn't find a reader() method there. Thank you

The XmlPullParser does not use SAX and is therefore incompatible with EchoSVG's DOM, although SAXDocumentFactory could be modified to use it. If you are willing to provide a Pull Request providing that functionality, I could merge it. The XMLPull API is explained here:

https://www.xmlpull.org/v1/download/unpacked/doc/quick_intro.html

tomveich commented 1 year ago

As much as I'd like to, I'm afraid I'm not experienced enough to provide such a PR. For my purpose, I am left to convert the images on backend.

carlosame commented 1 year ago

As much as I'd like to, I'm afraid I'm not experienced enough to provide such a PR.

It is not too complicated once one understands the XmlPull API, but definitely a large amount of work. I don't think it would be worth the result (and a lot of people dislikes the XmlPull parser because it silently removes XML entities).

For my purpose, I am left to convert the images on backend.

There must be a way to get the namespaces with the SAX parser on Android, however even if you managed to solve that you'd probably get stuck on the AWT stuff: https://github.com/css4j/echosvg/wiki/FAQ#does-it-run-on-android

I'm closing this as not planned, feel free to reopen if you make any progress.

carlosame commented 1 year ago

Incidentally, css4j-dom4j does support the XmlPull parser, see the XPP3Test for an example:

https://github.com/css4j/css4j-dom4j/blob/48f903a9509e0884fc1574e2457684d02aa69a59/junit/io/sf/carte/doc/dom4j/XPP3Test.java#L30-L35

So if you load the file with css4j-dom4j and then provide the DOM document to TranscoderInput, that may work:

XHTMLDocumentFactory factory = XHTMLDocumentFactory.getInstance();
XPP3Reader reader = new XPP3Reader(factory);
Reader re = new FileReader(svgFile, "utf-8");
XHTMLDocument document = (XHTMLDocument) reader.read(re);
re.close();

TranscoderInput transcoderInput = new TranscoderInput(document);
PNGTranscoder pngTranscoder = PNGTranscoder()
pngTranscoder.transcode(transcoderInput, transcoderOutput);

And once EchoSVG 0.3.3 is released, you should be able to benefit from the support for namespaceless HTML documents. The idea would be to load the SVG file into a DOM document (from EchoSVG DOM, css4j's DOM or any other DOM, it does not matter), then create an html element, append the document root to it, then append the html element to the document (or just replace the nodes) and provide it to the TranscoderInput:

DocumentFactory df = new SAXDocumentFactory(GenericDOMImplementation.getDOMImplementation());
String uri = svgFile.toURI().toString();
Reader re = new FileReader(svgFile, "utf-8");
Document document = df.createDocument(null, "svg", uri, re);
re.close();

Element html = document.createElement("html");
Element svg = document.getDocumentElement();
document.replaceChild(html, svg);
html.appendChild(svg);
// If the document has a DOCTYPE you'll have to remove it

TranscoderInput transcoderInput = new TranscoderInput(document);
PNGTranscoder pngTranscoder = PNGTranscoder()
pngTranscoder.transcode(transcoderInput, transcoderOutput);

Haven't tested but should work.