diff --git a/echosvg-test/src/test/java/io/sf/carte/echosvg/test/svg/ParameterizedRenderingAccuracyTest.java b/echosvg-test/src/test/java/io/sf/carte/echosvg/test/svg/ParameterizedRenderingAccuracyTest.java index 905c33335..b5ba4b52b 100644 --- a/echosvg-test/src/test/java/io/sf/carte/echosvg/test/svg/ParameterizedRenderingAccuracyTest.java +++ b/echosvg-test/src/test/java/io/sf/carte/echosvg/test/svg/ParameterizedRenderingAccuracyTest.java @@ -64,33 +64,36 @@ public void runTest(String uri, String parameter) throws MalformedURLException { /** * Gives a chance to the subclass to control the construction of the reference * PNG file from the svgFile name The refImgURL is built as: getRefImagePrefix() - * + svgDir + getRefImageSuffix() + svgFile + * + svgDir + getRefImageSuffix() + svgFile + parameter + getImageSuffix() + + * PNG_EXTENSION */ @Override protected String buildRefImgURL(String svgDir, String svgFile) { - return getRefImagePrefix() + svgDir + getRefImageSuffix() + svgFile + parameter + PNG_EXTENSION; + return getRefImagePrefix() + svgDir + getRefImageSuffix() + svgFile + parameter + getImageSuffix() + + PNG_EXTENSION; } /** * Gives a chance to the subclass to control the construction of the variation * URL, which is built as: getVariationPrefix() + svgDir + getVariationSuffix() - * + svgFile + parameter + PNG_EXTENSION + * + svgFile + parameter + getImageSuffix() + PNG_EXTENSION */ @Override public String[] buildVariationURLs(String svgDir, String svgFile) { String[] platforms = getVariationPlatforms(); String[] urls = new String[platforms.length + 1]; - urls[0] = getVariationPrefix() + svgDir + getVariationSuffix() + svgFile + parameter + PNG_EXTENSION; + urls[0] = getVariationPrefix() + svgDir + getVariationSuffix() + svgFile + parameter + getImageSuffix() + + PNG_EXTENSION; for (int i = 0; i < platforms.length; i++) { - urls[i + 1] = getVariationPrefix() + svgDir + getVariationSuffix() + svgFile + parameter + '_' - + platforms[i] + PNG_EXTENSION; + urls[i + 1] = getVariationPrefix() + svgDir + getVariationSuffix() + svgFile + parameter + getImageSuffix() + + '_' + platforms[i] + PNG_EXTENSION; } return urls; } @Override protected String buildSaveVariationPath(String svgDir, String svgFile) { - return getSaveVariationPrefix() + svgDir + getSaveVariationSuffix() + svgFile + parameter; + return getSaveVariationPrefix() + svgDir + getSaveVariationSuffix() + svgFile + parameter + getImageSuffix(); } /** @@ -101,7 +104,7 @@ protected String buildSaveVariationPath(String svgDir, String svgFile) { @Override public String buildCandidateReferenceFile(String svgDir, String svgFile) { return getCandidateReferencePrefix() + svgDir + getCandidateReferenceSuffix() + svgFile + parameter - + PNG_EXTENSION; + + getImageSuffix() + PNG_EXTENSION; } } diff --git a/echosvg-test/src/test/java/io/sf/carte/echosvg/test/svg/PreconfiguredRenderingTest.java b/echosvg-test/src/test/java/io/sf/carte/echosvg/test/svg/PreconfiguredRenderingTest.java index 90ff5f897..579f238c1 100644 --- a/echosvg-test/src/test/java/io/sf/carte/echosvg/test/svg/PreconfiguredRenderingTest.java +++ b/echosvg-test/src/test/java/io/sf/carte/echosvg/test/svg/PreconfiguredRenderingTest.java @@ -37,6 +37,8 @@ public abstract class PreconfiguredRenderingTest extends SVGRenderingAccuracyTes public static final String SVG_EXTENSION = ".svg"; public static final String SVGZ_EXTENSION = ".svgz"; + private static final String HTML_EXTENSION = ".html"; + private static final String XHTML_EXTENSION = ".xhtml"; public static final char PATH_SEPARATOR = '/'; @@ -175,6 +177,10 @@ protected String[] breakSVGFile(String svgFile) { ret[2] = SVG_EXTENSION; } else if (svgFile.endsWith(SVGZ_EXTENSION)) { ret[2] = SVGZ_EXTENSION; + } else if (svgFile.endsWith(HTML_EXTENSION)) { + ret[2] = HTML_EXTENSION; + } else if (svgFile.endsWith(XHTML_EXTENSION)) { + ret[2] = XHTML_EXTENSION; } else { throw new IllegalArgumentException(svgFile); } diff --git a/echosvg-test/src/test/java/io/sf/carte/echosvg/test/svg/SamplesRenderingTest.java b/echosvg-test/src/test/java/io/sf/carte/echosvg/test/svg/SamplesRenderingTest.java index 2cfc60a31..3c48edc1d 100644 --- a/echosvg-test/src/test/java/io/sf/carte/echosvg/test/svg/SamplesRenderingTest.java +++ b/echosvg-test/src/test/java/io/sf/carte/echosvg/test/svg/SamplesRenderingTest.java @@ -929,6 +929,11 @@ public void testStyleElement() throws TranscoderException, IOException { test("samples/tests/spec/styling/styleElement.svg"); } + @Test + public void testXHTMLEmbed() throws TranscoderException, IOException { + testXHTML("samples/tests/spec/styling/css2.xhtml"); + } + /* * Text */ @@ -1521,6 +1526,24 @@ private void test(String file, boolean validating) throws TranscoderException, I runner.runTest(0.00001f, 0.00001f); } + /** + * Test the rendering of a SVG image inside an XHTML document. + * + *

+ * A small percentage of different pixels is allowed during the comparison to a + * reference image. + *

+ * + * @param file the HTML file to test. + * @throws TranscoderException + * @throws IOException + */ + private void testXHTML(String file) throws TranscoderException, IOException { + RenderingTest runner = new XHTMLRenderingAccuracyTest(); + runner.setFile(file); + runner.runTest(0.000001f, 0.000001f); + } + /** * Dynamic test of the rendering of a SVG file. * diff --git a/echosvg-test/src/test/java/io/sf/carte/echosvg/test/svg/StyleBypassRenderingTest.java b/echosvg-test/src/test/java/io/sf/carte/echosvg/test/svg/StyleBypassRenderingTest.java index 5b951959f..90441331d 100644 --- a/echosvg-test/src/test/java/io/sf/carte/echosvg/test/svg/StyleBypassRenderingTest.java +++ b/echosvg-test/src/test/java/io/sf/carte/echosvg/test/svg/StyleBypassRenderingTest.java @@ -1437,9 +1437,6 @@ private void test(String file, String medium, boolean darkMode, boolean validati private class BypassRenderingTest extends RenderingTest { - private static final String HTML_EXTENSION = ".html"; - private static final String XHTML_EXTENSION = ".xhtml"; - private final String medium; /** @@ -1488,43 +1485,6 @@ protected void encode(URL srcURL, FileOutputStream fos) throws TranscoderExcepti helper.transcode(re, uri, dst, null); } - @Override - protected String[] breakSVGFile(String svgFile) { - if (svgFile == null) { - throw new IllegalArgumentException(svgFile); - } - - String[] ret = new String[3]; - - if (svgFile.endsWith(SVG_EXTENSION)) { - ret[2] = SVG_EXTENSION; - } else if (svgFile.endsWith(SVGZ_EXTENSION)) { - ret[2] = SVGZ_EXTENSION; - } else if (svgFile.endsWith(HTML_EXTENSION)) { - ret[2] = HTML_EXTENSION; - } else if (svgFile.endsWith(XHTML_EXTENSION)) { - ret[2] = XHTML_EXTENSION; - } else { - throw new IllegalArgumentException(svgFile); - } - - svgFile = svgFile.substring(0, svgFile.length() - ret[2].length()); - - int fileNameStart = svgFile.lastIndexOf(PATH_SEPARATOR); - String svgDir = ""; - if (fileNameStart != -1) { - if (svgFile.length() < fileNameStart + 2) { - // Nothing after PATH_SEPARATOR - throw new IllegalArgumentException(svgFile); - } - svgDir = svgFile.substring(0, fileNameStart + 1); - svgFile = svgFile.substring(fileNameStart + 1); - } - ret[0] = svgDir; - ret[1] = svgFile; - return ret; - } - protected String getImageSuffix() { if (!DEFAULT_MEDIUM.equals(medium)) { return '-' + medium + getDarkModeSuffix(); diff --git a/echosvg-test/src/test/java/io/sf/carte/echosvg/test/svg/XHTMLRenderingAccuracyTest.java b/echosvg-test/src/test/java/io/sf/carte/echosvg/test/svg/XHTMLRenderingAccuracyTest.java new file mode 100644 index 000000000..13deec84a --- /dev/null +++ b/echosvg-test/src/test/java/io/sf/carte/echosvg/test/svg/XHTMLRenderingAccuracyTest.java @@ -0,0 +1,49 @@ +/* + + See the NOTICE file distributed with this work for additional + information regarding copyright ownership. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + */ +package io.sf.carte.echosvg.test.svg; + +import io.sf.carte.echosvg.transcoder.XMLAbstractTranscoder; +import io.sf.carte.echosvg.transcoder.image.ImageTranscoder; + +/** + * Checks for regressions in rendering of SVG inside an XHTML document. + * + * @author See Git history. + * @version $Id$ + */ +public class XHTMLRenderingAccuracyTest extends RenderingTest { + + public XHTMLRenderingAccuracyTest() { + super(); + setValidating(false); + } + + /** + * Returns the ImageTranscoder the Test should use + */ + @Override + ImageTranscoder getTestImageTranscoder() { + ImageTranscoder t = super.getTestImageTranscoder(); + t.addTranscodingHint(XMLAbstractTranscoder.KEY_DOCUMENT_ELEMENT_NAMESPACE_URI, + "http://www.w3.org/1999/xhtml"); + t.addTranscodingHint(XMLAbstractTranscoder.KEY_DOCUMENT_ELEMENT, "html"); + return t; + } + +} diff --git a/echosvg-transcoder/src/main/java/io/sf/carte/echosvg/transcoder/SVGAbstractTranscoder.java b/echosvg-transcoder/src/main/java/io/sf/carte/echosvg/transcoder/SVGAbstractTranscoder.java index 9ccdf4ce9..b58c71f2b 100644 --- a/echosvg-transcoder/src/main/java/io/sf/carte/echosvg/transcoder/SVGAbstractTranscoder.java +++ b/echosvg-transcoder/src/main/java/io/sf/carte/echosvg/transcoder/SVGAbstractTranscoder.java @@ -26,11 +26,17 @@ import java.util.Collections; import java.util.List; +import org.w3c.dom.Attr; import org.w3c.dom.DOMImplementation; import org.w3c.dom.Document; +import org.w3c.dom.DocumentType; +import org.w3c.dom.Element; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; import org.w3c.dom.svg.SVGSVGElement; import io.sf.carte.echosvg.anim.dom.SAXSVGDocumentFactory; +import io.sf.carte.echosvg.anim.dom.SVG12DOMImplementation; import io.sf.carte.echosvg.anim.dom.SVGDOMImplementation; import io.sf.carte.echosvg.anim.dom.SVGOMDocument; import io.sf.carte.echosvg.bridge.BaseScriptingEnvironment; @@ -48,7 +54,6 @@ 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.util.DOMUtilities; import io.sf.carte.echosvg.dom.util.DocumentFactory; import io.sf.carte.echosvg.gvt.CanvasGraphicsNode; import io.sf.carte.echosvg.gvt.CompositeGraphicsNode; @@ -121,7 +126,6 @@ protected SVGAbstractTranscoder() { hints.put(KEY_DOCUMENT_ELEMENT_NAMESPACE_URI, SVGConstants.SVG_NAMESPACE_URI); hints.put(KEY_DOCUMENT_ELEMENT, SVGConstants.SVG_SVG_TAG); - hints.put(KEY_DOM_IMPLEMENTATION, SVGDOMImplementation.getDOMImplementation()); hints.put(KEY_MEDIA, "screen"); hints.put(KEY_DEFAULT_FONT_FAMILY, DEFAULT_DEFAULT_FONT_FAMILY); hints.put(KEY_EXECUTE_ONLOAD, Boolean.FALSE); @@ -200,23 +204,19 @@ public void transcode(TranscoderInput input, TranscoderOutput output) throws Tra /** * Transcodes the specified Document as an image in the specified output. * - * @param document the document to transcode - * @param uri the uri of the document or null if any - * @param output the ouput where to transcode + * @param document the document to transcode. Cannot be {@code null}. + * @param uri the uri of the document or {@code null} if any. + * @param output the ouput where to transcode. * @exception TranscoderException if an error occured while transcoding */ @Override protected void transcode(Document document, String uri, TranscoderOutput output) throws TranscoderException { - - if ((document != null) && !(document.getImplementation() instanceof SVGDOMImplementation)) { - DOMImplementation impl; - impl = (DOMImplementation) hints.get(KEY_DOM_IMPLEMENTATION); - // impl = SVGDOMImplementation.getDOMImplementation(); - document = DOMUtilities.deepCloneDocument(document, impl); - if (uri != null) { - ParsedURL url = new ParsedURL(uri); - ((SVGOMDocument) document).setParsedURL(url); - } + SVGOMDocument svgDoc; + // document is assumed to be non-null here + if (!(document instanceof SVGOMDocument)) { + svgDoc = getSVGDocument(document, uri); + } else { + svgDoc = (SVGOMDocument) document; } if (hints.containsKey(KEY_WIDTH)) @@ -224,7 +224,6 @@ protected void transcode(Document document, String uri, TranscoderOutput output) if (hints.containsKey(KEY_HEIGHT)) height = (Float) hints.get(KEY_HEIGHT); - SVGOMDocument svgDoc = (SVGOMDocument) document; SVGSVGElement root = svgDoc.getRootElement(); ctx = createBridgeContext(svgDoc); @@ -321,6 +320,85 @@ protected void transcode(Document document, String uri, TranscoderOutput output) this.root = gvtRoot; } + private SVGOMDocument getSVGDocument(Document document, String uri) { + // Obtain the document element and DocumentType + DocumentType docType; + Element docElm = document.getDocumentElement(); + // Check whether the document element is a SVG element anyway + if (docElm.getNamespaceURI() != SVGDOMImplementation.SVG_NAMESPACE_URI + && !"SVG".equalsIgnoreCase(docElm.getTagName())) { + // Not a SVG document, get the first SVG element + docElm = (Element) document.getElementsByTagNameNS("*", SVGConstants.SVG_SVG_TAG).item(0); + docType = null; + } else { + docType = document.getDoctype(); + } + + // Obtain a DOM implementation + DOMImplementation impl; + impl = (DOMImplementation) hints.get(KEY_DOM_IMPLEMENTATION); + if (impl == null) { + // Look for the version attribute of the root element + if ("1.2".equals(docElm.getAttribute(SVGConstants.SVG_VERSION_ATTRIBUTE))) { + impl = SVG12DOMImplementation.getDOMImplementation(); + } else { + impl = SVGDOMImplementation.getDOMImplementation(); + } + } + + // Clone the document + SVGOMDocument svgDocument = (SVGOMDocument) deepCloneDocument(document, docElm, docType, impl); + + if (uri != null) { + ParsedURL url = new ParsedURL(uri); + svgDocument.setParsedURL(url); + } + + return svgDocument; + } + + private static Document deepCloneDocument(Document doc, Element root, DocumentType docType, + DOMImplementation impl) { + Document result = impl.createDocument(root.getNamespaceURI(), root.getNodeName(), docType); + Element rroot = result.getDocumentElement(); + + // Let's see if the designed root node is also the origin's document root + boolean before = root.getParentNode().getNodeType() == Node.DOCUMENT_NODE; + Node firstNode; + if (before) { + firstNode = doc.getFirstChild(); + } else { + firstNode = root; + } + + for (Node n = firstNode; n != null; n = n.getNextSibling()) { + if (n == root) { + before = false; + if (root.hasAttributes()) { + NamedNodeMap attr = root.getAttributes(); + int len = attr.getLength(); + for (int i = 0; i < len; i++) { + rroot.setAttributeNode((Attr) result.importNode(attr.item(i), true)); + } + } + for (Node c = root.getFirstChild(); c != null; c = c.getNextSibling()) { + rroot.appendChild(result.importNode(c, true)); + } + } else { + short type = n.getNodeType(); + if (type == Node.COMMENT_NODE || type == Node.PROCESSING_INSTRUCTION_NODE) { + if (before) { + result.insertBefore(result.importNode(n, true), rroot); + } else { + result.appendChild(result.importNode(n, true)); + } + } + } + } + + return result; + } + protected CanvasGraphicsNode getCanvasGraphicsNode(GraphicsNode gn) { if (!(gn instanceof CompositeGraphicsNode)) return null; diff --git a/echosvg-transcoder/src/main/java/io/sf/carte/echosvg/transcoder/XMLAbstractTranscoder.java b/echosvg-transcoder/src/main/java/io/sf/carte/echosvg/transcoder/XMLAbstractTranscoder.java index 2044a0396..f4ffc7699 100644 --- a/echosvg-transcoder/src/main/java/io/sf/carte/echosvg/transcoder/XMLAbstractTranscoder.java +++ b/echosvg-transcoder/src/main/java/io/sf/carte/echosvg/transcoder/XMLAbstractTranscoder.java @@ -25,6 +25,8 @@ import org.w3c.dom.DOMImplementation; import org.w3c.dom.Document; +import io.sf.carte.echosvg.anim.dom.SVGDOMImplementation; +import io.sf.carte.echosvg.dom.GenericDOMImplementation; import io.sf.carte.echosvg.dom.util.DocumentFactory; import io.sf.carte.echosvg.dom.util.SAXDocumentFactory; import io.sf.carte.echosvg.transcoder.keys.BooleanKey; @@ -37,13 +39,20 @@ * advantage of this class, you have to specify the following transcoding hints: * * * * @author Thierry Kormann @@ -73,50 +82,14 @@ protected XMLAbstractTranscoder() { public void transcode(TranscoderInput input, TranscoderOutput output) throws TranscoderException { Document document = null; - String uri = input.getURI(); if (input.getDocument() != null) { document = input.getDocument(); } else { - String namespaceURI = (String) hints.get(KEY_DOCUMENT_ELEMENT_NAMESPACE_URI); - String documentElement = (String) hints.get(KEY_DOCUMENT_ELEMENT); - DOMImplementation domImpl = (DOMImplementation) hints.get(KEY_DOM_IMPLEMENTATION); - - if (domImpl == null) { - handler.fatalError(new TranscoderException("Unspecified transcoding hints: KEY_DOM_IMPLEMENTATION")); - return; - } - if (namespaceURI == null) { - handler.fatalError( - new TranscoderException("Unspecified transcoding hints: KEY_DOCUMENT_ELEMENT_NAMESPACE_URI")); - return; - } - if (documentElement == null) { - handler.fatalError(new TranscoderException("Unspecified transcoding hints: KEY_DOCUMENT_ELEMENT")); - return; - } - // parse the XML document - DocumentFactory f = createDocumentFactory(domImpl); - Object xmlParserValidating = hints.get(KEY_XML_PARSER_VALIDATING); - boolean validating = xmlParserValidating != null && (Boolean) xmlParserValidating; - f.setValidating(validating); - try { - if (input.getInputStream() != null) { - document = f.createDocument(namespaceURI, documentElement, input.getURI(), input.getInputStream()); - } else if (input.getReader() != null) { - document = f.createDocument(namespaceURI, documentElement, input.getURI(), input.getReader()); - } else if (input.getXMLReader() != null) { - document = f.createDocument(namespaceURI, documentElement, input.getURI(), input.getXMLReader()); - } else if (uri != null) { - document = f.createDocument(namespaceURI, documentElement, uri); - } - } catch (DOMException ex) { - handler.fatalError(new TranscoderException(ex)); - } catch (IOException ex) { - handler.fatalError(new TranscoderException(ex)); - } + document = loadDocument(input); } // call the dedicated transcode method if (document != null) { + String uri = input.getURI(); try { transcode(document, uri, output); } catch (TranscoderException ex) { @@ -127,6 +100,65 @@ public void transcode(TranscoderInput input, TranscoderOutput output) throws Tra } } + private Document loadDocument(TranscoderInput input) throws TranscoderException { + String namespaceURI = (String) hints.get(KEY_DOCUMENT_ELEMENT_NAMESPACE_URI); + String documentElement = (String) hints.get(KEY_DOCUMENT_ELEMENT); + + DocumentFactory f = createDocumentFactory(documentElement); + + if (namespaceURI == null) { + handler.fatalError( + new TranscoderException("Unspecified transcoding hints: KEY_DOCUMENT_ELEMENT_NAMESPACE_URI")); + return null; + } + if (documentElement == null) { + handler.fatalError(new TranscoderException("Unspecified transcoding hints: KEY_DOCUMENT_ELEMENT")); + return null; + } + + // parse the XML document + Object xmlParserValidating = hints.get(KEY_XML_PARSER_VALIDATING); + boolean validating = xmlParserValidating != null && (Boolean) xmlParserValidating; + f.setValidating(validating); + String uri = input.getURI(); + + Document document = null; + try { + if (input.getInputStream() != null) { + document = f.createDocument(namespaceURI, documentElement, uri, input.getInputStream()); + } else if (input.getReader() != null) { + document = f.createDocument(namespaceURI, documentElement, uri, input.getReader()); + } else if (input.getXMLReader() != null) { + document = f.createDocument(namespaceURI, documentElement, uri, input.getXMLReader()); + } else if (uri != null) { + document = f.createDocument(namespaceURI, documentElement, uri); + } + } catch (DOMException ex) { + handler.fatalError(new TranscoderException(ex)); + } catch (IOException ex) { + handler.fatalError(new TranscoderException(ex)); + } + + return document; + } + + private DocumentFactory createDocumentFactory(String documentElement) { + DocumentFactory f; + DOMImplementation domImpl = (DOMImplementation) hints.get(KEY_DOM_IMPLEMENTATION); + if (domImpl == null) { + if (documentElement.equals("html")) { + domImpl = GenericDOMImplementation.getDOMImplementation(); + f = new SAXDocumentFactory(domImpl); + } else { + domImpl = SVGDOMImplementation.getDOMImplementation(); + f = createDocumentFactory(domImpl); + } + } else { + f = createDocumentFactory(domImpl); + } + return f; + } + /** * Creates the DocumentFactory used to create the DOM tree. * Override this method if you have to use another implementation of the diff --git a/samples/tests/spec/styling/css2.xhtml b/samples/tests/spec/styling/css2.xhtml new file mode 100644 index 000000000..92586bbd7 --- /dev/null +++ b/samples/tests/spec/styling/css2.xhtml @@ -0,0 +1,89 @@ + + + + + + + + + + + + +SVG Embedded into XHTML + + + + +/**/ + + + + + + + + + + + SVG + HTML + XHTML + SVGZ + + + + diff --git a/test-references/samples/tests/spec/styling/css2.png b/test-references/samples/tests/spec/styling/css2.png new file mode 100644 index 000000000..a5b2a1361 Binary files /dev/null and b/test-references/samples/tests/spec/styling/css2.png differ