Skip to content

Commit

Permalink
transcoder: add the KEY_SVG_SELECTOR transcoding hint
Browse files Browse the repository at this point in the history
If `KEY_SVG_SELECTOR` is set and the document is HTML, its value as a CSS selector
is used to locate the element that will be used as the SVG root.

In the case of the transcoding helper, if the selector argument is null then
`KEY_SVG_SELECTOR` is used, and if also null then the previous behaviour follows.

This new hint is part of the effort to make EchoSVG friendly to embedded SVG images.
See also: #40
  • Loading branch information
carlosame committed Aug 17, 2023
1 parent b6fb173 commit e43d939
Show file tree
Hide file tree
Showing 6 changed files with 119 additions and 20 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,11 @@ class HTMLRenderingAccuracyTest extends XHTMLRenderingAccuracyTest {
private final HtmlParser parser;

public HTMLRenderingAccuracyTest() {
super();
this(null);
}

public HTMLRenderingAccuracyTest(String selector) {
super(selector);
setValidating(false);
parser = new HtmlParser(XmlViolationPolicy.ALTER_INFOSET);
parser.setCommentPolicy(XmlViolationPolicy.ALLOW);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -951,6 +951,11 @@ public void testHTMLEmbed() throws TranscoderException, IOException {
testHTML("samples/tests/spec/styling/css2.html");
}

@Test
public void testHTMLEmbedSelector() throws TranscoderException, IOException {
testHTML("samples/tests/spec/styling/css2.html", "#theSVG");
}

@Test
public void testXHTMLEmbed() throws TranscoderException, IOException {
testXHTML("samples/tests/spec/styling/css2.xhtml");
Expand Down Expand Up @@ -1643,7 +1648,24 @@ private void testXHTMLErrIgnore(String file, String media, int expectedErrorCoun
* @throws IOException
*/
private void testHTML(String file) throws TranscoderException, IOException {
RenderingTest runner = new HTMLRenderingAccuracyTest();
testHTML(file, null);
}

/**
* Test the rendering of a SVG image inside an HTML document.
*
* <p>
* A small percentage of different pixels is allowed during the comparison to a
* reference image.
* </p>
*
* @param file the HTML file to test.
* @param the selector that locates the desired SVG element.
* @throws TranscoderException
* @throws IOException
*/
private void testHTML(String file, String selector) throws TranscoderException, IOException {
RenderingTest runner = new HTMLRenderingAccuracyTest(selector);
runner.setFile(file);
runner.runTest(0.000001f, 0.000001f);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
*/
package io.sf.carte.echosvg.test.svg;

import io.sf.carte.echosvg.transcoder.SVGAbstractTranscoder;
import io.sf.carte.echosvg.transcoder.XMLAbstractTranscoder;
import io.sf.carte.echosvg.transcoder.image.ImageTranscoder;

Expand All @@ -29,8 +30,15 @@
*/
public class XHTMLRenderingAccuracyTest extends RenderingTest {

private final String selector;

public XHTMLRenderingAccuracyTest() {
this(null);
}

public XHTMLRenderingAccuracyTest(String selector) {
super();
this.selector = selector;
setValidating(false);
}

Expand All @@ -43,6 +51,9 @@ ImageTranscoder getTestImageTranscoder() {
t.addTranscodingHint(XMLAbstractTranscoder.KEY_DOCUMENT_ELEMENT_NAMESPACE_URI,
"http://www.w3.org/1999/xhtml");
t.addTranscodingHint(XMLAbstractTranscoder.KEY_DOCUMENT_ELEMENT, "html");
if (selector != null) {
t.addTranscodingHint(SVGAbstractTranscoder.KEY_SVG_SELECTOR, selector);
}
return t;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
import io.sf.carte.echosvg.bridge.UserAgentAdapter;
import io.sf.carte.echosvg.bridge.ViewBox;
import io.sf.carte.echosvg.bridge.svg12.SVG12BridgeContext;
import io.sf.carte.echosvg.dom.AbstractParentNode;
import io.sf.carte.echosvg.dom.util.DocumentFactory;
import io.sf.carte.echosvg.gvt.CanvasGraphicsNode;
import io.sf.carte.echosvg.gvt.CompositeGraphicsNode;
Expand Down Expand Up @@ -338,32 +339,52 @@ protected void transcode(Document document, String uri, TranscoderOutput output)
* @param document the document to import.
* @param uri the document URI.
* @return the imported SVG document.
* @throws TranscoderException if an error occurred while importing the SVG.
*/
private SVGOMDocument importAsSVGDocument(Document document, String uri) {
private SVGOMDocument importAsSVGDocument(Document document, String uri)
throws TranscoderException {
// Obtain the document element and DocumentType
Element docElm = document.getDocumentElement();
// Check whether the document element is a SVG element anyway
if (docElm.getNamespaceURI() != SVGConstants.SVG_NAMESPACE_URI
&& !"svg".equalsIgnoreCase(docElm.getTagName())) {
// Not a SVG document, get the first SVG element
docElm = (Element) document.getElementsByTagNameNS(SVGConstants.SVG_NAMESPACE_URI,
SVGConstants.SVG_SVG_TAG).item(0);
if (docElm == null) {
// If we are we in XHTML instead of plain HTML, we are done
if (document.getDocumentElement().getNamespaceURI() != null) {
return null;
// Not a SVG document, either locate KEY_SVG_SELECTOR
// or get the first SVG element
String selector = (String) hints.get(KEY_SVG_SELECTOR);
if (selector != null && !(selector = selector.trim()).isEmpty()) {
docElm = ((AbstractParentNode) docElm).querySelector(selector);
if (docElm == null || (!"svg".equals(docElm.getLocalName())
&& !"svg".equals(docElm.getTagName()))) {
// No SVG element with that selector
throw new TranscoderException(
"Selector " + selector + " points to no valid SVG element.");
}
// Check for namespaceless <svg> (acceptable inside plain HTML)
docElm = (Element) document.getElementsByTagName(SVGConstants.SVG_SVG_TAG).item(0);
String namespaceURI = docElm.getNamespaceURI();
if (namespaceURI == null) {
// Set the right namespace
docElm = replaceSVGRoot(docElm);
} else if (!SVGConstants.SVG_NAMESPACE_URI.equals(namespaceURI)) {
throw new TranscoderException("Selector " + selector + " points to element in "
+ namespaceURI + " namespace.");
}
} else {
docElm = (Element) document.getElementsByTagNameNS(SVGConstants.SVG_NAMESPACE_URI,
SVGConstants.SVG_SVG_TAG).item(0);
if (docElm == null) {
// No SVG elements at all
return null;
// If we are we in XHTML instead of plain HTML, we are done
if (document.getDocumentElement().getNamespaceURI() != null) {
return null;
}
// Check for namespaceless <svg> (acceptable inside plain HTML)
docElm = (Element) document.getElementsByTagName(SVGConstants.SVG_SVG_TAG)
.item(0);
if (docElm == null) {
// No SVG elements at all
return null;
}
// Set the right namespace
docElm = replaceSVGRoot(docElm);
}
docElm.setAttributeNS(XMLConstants.XMLNS_ATTRIBUTE_NS_URI,
XMLConstants.XMLNS_ATTRIBUTE, SVGConstants.SVG_NAMESPACE_URI);
Element newRoot = replaceNamespace(docElm);
docElm.getParentNode().replaceChild(newRoot, docElm);
docElm = newRoot;
}
}

Expand All @@ -390,6 +411,14 @@ private SVGOMDocument importAsSVGDocument(Document document, String uri) {
return svgDocument;
}

private Element replaceSVGRoot(Element docElm) {
docElm.setAttributeNS(XMLConstants.XMLNS_ATTRIBUTE_NS_URI,
XMLConstants.XMLNS_ATTRIBUTE, SVGConstants.SVG_NAMESPACE_URI);
Element newRoot = replaceNamespace(docElm);
docElm.getParentNode().replaceChild(newRoot, docElm);
return newRoot;
}

private Element replaceNamespace(Element elm) {
Element svgElm;
if (elm.getNamespaceURI() == null) {
Expand Down Expand Up @@ -846,6 +875,35 @@ protected void setImageSize(float docWidth, float docHeight) {
*/
public static final TranscodingHints.Key KEY_MEDIA = new StringKey();

/**
* The CSS selector key.
* <table style="border: 0; border-collapse: collapse; padding: 1px;">
* <caption></caption>
* <tr>
* <th style="text-align: end; vertical-align: top">Key:</th>
* <td style="vertical-align: top">KEY_SVG_SELECTOR</td>
* </tr>
* <tr>
* <th style="text-align: end; vertical-align: top">Value:</th>
* <td style="vertical-align: top">String</td>
* </tr>
* <tr>
* <th style="text-align: end; vertical-align: top">Default:</th>
* <td style="vertical-align: top">null</td>
* </tr>
* <tr>
* <th style="text-align: end; vertical-align: top">Required:</th>
* <td style="vertical-align: top">No</td>
* </tr>
* <tr>
* <th style="text-align: end; vertical-align: top">Description:</th>
* <td style="vertical-align: top">If the document is HTML, use this CSS
* selector to locate the desired SVG element.</td>
* </tr>
* </table>
*/
public static final TranscodingHints.Key KEY_SVG_SELECTOR = new StringKey();

/**
* The default font-family key.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -615,6 +615,10 @@ private void transcodeDOMDocument(DOMDocument document, TranscoderOutput output,

// Determine the SVG root element
DOMElement svgRoot = null;
if (selector == null) {
selector = (String) transcoder.getTranscodingHints()
.get(SVGAbstractTranscoder.KEY_SVG_SELECTOR);
}
if (selector != null && (selector = selector.trim()).length() != 0) {
svgRoot = document.querySelectorAll(selector).item(0);
}
Expand Down
2 changes: 1 addition & 1 deletion samples/tests/spec/styling/css2.html
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
</head>
<body>
<!-- svg elements without a namespace declaration are legal in plain HTML -->
<svg width="500" height="500">
<svg width="500" height="500" id="theSVG">
<style>
/*<![CDATA[*/

Expand Down

0 comments on commit e43d939

Please sign in to comment.