diff --git a/imageio/imageio-batik/src/main/java/com/twelvemonkeys/imageio/plugins/svg/SVGImageReader.java b/imageio/imageio-batik/src/main/java/com/twelvemonkeys/imageio/plugins/svg/SVGImageReader.java index 2e82ba35..09ae66e6 100755 --- a/imageio/imageio-batik/src/main/java/com/twelvemonkeys/imageio/plugins/svg/SVGImageReader.java +++ b/imageio/imageio-batik/src/main/java/com/twelvemonkeys/imageio/plugins/svg/SVGImageReader.java @@ -110,24 +110,25 @@ public class SVGImageReader extends ImageReaderBase { public BufferedImage read(int pIndex, ImageReadParam pParam) throws IOException { checkBounds(pIndex); - String baseURI = null; - if (pParam instanceof SVGReadParam) { SVGReadParam svgParam = (SVGReadParam) pParam; - // Set IIOParams as hints + + // Get the base URI + // This must be done before converting the params to hints + String baseURI = svgParam.getBaseURI(); + rasterizer.transcoderInput.setURI(baseURI); + + // Set ImageReadParams as hints // Note: The cast to Map invokes a different method that preserves // unset defaults, DO NOT REMOVE! rasterizer.setTranscodingHints((Map) paramsToHints(svgParam)); - - // Get the base URI (not a hint) - baseURI = svgParam.getBaseURI(); } - Dimension size; - if (pParam != null && (size = pParam.getSourceRenderSize()) != null) { - // Use size... + Dimension size = null; + if (pParam != null) { + size = pParam.getSourceRenderSize(); } - else { + if (size == null) { size = new Dimension(getWidth(pIndex), getHeight(pIndex)); } @@ -137,7 +138,6 @@ public class SVGImageReader extends ImageReaderBase { try { processImageStarted(pIndex); - rasterizer.transcoderInput.setURI(baseURI); BufferedImage image = rasterizer.getImage(); Graphics2D g = destination.createGraphics(); @@ -155,10 +155,16 @@ public class SVGImageReader extends ImageReaderBase { return destination; } catch (TranscoderException e) { - throw new IIOException(e.getMessage(), e); + Throwable cause = unwrapException(e); + throw new IIOException(cause.getMessage(), cause); } } + private static Throwable unwrapException(TranscoderException ex) { + // The TranscoderException is generally useless... + return ex.getException() != null ? ex.getException() : ex; + } + private TranscodingHints paramsToHints(SVGReadParam pParam) throws IOException { TranscodingHints hints = new TranscodingHints(); // Note: We must allow generic ImageReadParams, so converting to @@ -174,8 +180,8 @@ public class SVGImageReader extends ImageReaderBase { } if (size != null) { - hints.put(ImageTranscoder.KEY_WIDTH, new Float(size.getWidth())); - hints.put(ImageTranscoder.KEY_HEIGHT, new Float(size.getHeight())); + hints.put(ImageTranscoder.KEY_WIDTH, (float) size.getWidth()); + hints.put(ImageTranscoder.KEY_HEIGHT, (float) size.getHeight()); } // Set area of interest @@ -185,16 +191,16 @@ public class SVGImageReader extends ImageReaderBase { // Avoid that the batik transcoder scales the AOI up to original image size if (size == null) { - hints.put(ImageTranscoder.KEY_WIDTH, new Float(region.getWidth())); - hints.put(ImageTranscoder.KEY_HEIGHT, new Float(region.getHeight())); + hints.put(ImageTranscoder.KEY_WIDTH, (float) region.getWidth()); + hints.put(ImageTranscoder.KEY_HEIGHT, (float) region.getHeight()); } else { // Need to resize here... double xScale = size.getWidth() / origSize.getWidth(); double yScale = size.getHeight() / origSize.getHeight(); - hints.put(ImageTranscoder.KEY_WIDTH, new Float(region.getWidth() * xScale)); - hints.put(ImageTranscoder.KEY_HEIGHT, new Float(region.getHeight() * yScale)); + hints.put(ImageTranscoder.KEY_WIDTH, (float) (region.getWidth() * xScale)); + hints.put(ImageTranscoder.KEY_HEIGHT, (float) (region.getHeight() * yScale)); } } else if (size != null) { @@ -219,7 +225,7 @@ public class SVGImageReader extends ImageReaderBase { return null; } - public ImageReadParam getDefaultReadParam() { + public SVGReadParam getDefaultReadParam() { return new SVGReadParam(); } @@ -255,7 +261,7 @@ public class SVGImageReader extends ImageReaderBase { */ private class Rasterizer extends SVGAbstractTranscoder /*ImageTranscoder*/ { - BufferedImage image = null; + private BufferedImage image; private TranscoderInput transcoderInput; private float defaultWidth; private float defaultHeight; @@ -266,17 +272,19 @@ public class SVGImageReader extends ImageReaderBase { private TranscoderException exception; private BridgeContext context; - public BufferedImage createImage(final int width, final int height) { - return ImageUtil.createTransparent(width, height);//, BufferedImage.TYPE_INT_ARGB); + private BufferedImage createImage(final int width, final int height) { + return ImageUtil.createTransparent(width, height); // BufferedImage.TYPE_INT_ARGB } // This is cheating... We don't fully transcode after all protected void transcode(Document document, final String uri, final TranscoderOutput output) throws TranscoderException { // Sets up root, curTxf & curAoi // ---- - if ((document != null) && !(document.getImplementation() instanceof SVGDOMImplementation)) { - DOMImplementation impl = (DOMImplementation) hints.get(KEY_DOM_IMPLEMENTATION); - document = DOMUtilities.deepCloneDocument(document, impl); + if (document != null) { + if (!(document.getImplementation() instanceof SVGDOMImplementation)) { + DOMImplementation impl = (DOMImplementation) hints.get(KEY_DOM_IMPLEMENTATION); + document = DOMUtilities.deepCloneDocument(document, impl); + } if (uri != null) { try { @@ -346,8 +354,8 @@ public class SVGImageReader extends ImageReaderBase { processReadAborted(); return null; } - processImageProgress(10f); + processImageProgress(10f); // Hacky workaround below... if (gvtRoot == null) { @@ -369,6 +377,7 @@ public class SVGImageReader extends ImageReaderBase { } ctx = context; // /Hacky + if (abortRequested()) { processReadAborted(); return null; @@ -489,11 +498,9 @@ public class SVGImageReader extends ImageReaderBase { // now we are sure that the aoi is the image size Shape raoi = new Rectangle2D.Float(0, 0, width, height); // Warning: the renderer's AOI must be in user space - renderer.repaint(curTxf.createInverse(). - createTransformedShape(raoi)); + renderer.repaint(curTxf.createInverse().createTransformedShape(raoi)); // NOTE: repaint above cause nullpointer exception with fonts..??? - BufferedImage rend = renderer.getOffScreen(); renderer = null; // We're done with it... @@ -558,18 +565,40 @@ public class SVGImageReader extends ImageReaderBase { return image; } - protected int getDefaultWidth() throws TranscoderException { + int getDefaultWidth() throws TranscoderException { init(); - return (int) (defaultWidth + 0.5); + return (int) Math.ceil(defaultWidth); } - protected int getDefaultHeight() throws TranscoderException { + int getDefaultHeight() throws TranscoderException { init(); - return (int) (defaultHeight + 0.5); + return (int) Math.ceil(defaultHeight); } - public void setInput(final TranscoderInput pInput) { + void setInput(final TranscoderInput pInput) { transcoderInput = pInput; } + + @Override + protected UserAgent createUserAgent() { + return new SVGImageReaderUserAgent(); + } + + private class SVGImageReaderUserAgent extends SVGAbstractTranscoderUserAgent { + @Override + public void displayError(Exception e) { + displayError(e.getMessage()); + } + + @Override + public void displayError(String message) { + displayMessage(message); + } + + @Override + public void displayMessage(String message) { + processWarningOccurred(message.replaceAll("[\\r\\n]+", " ")); + } + } } } diff --git a/imageio/imageio-batik/src/test/java/com/twelvemonkeys/imageio/plugins/svg/SVGImageReaderTest.java b/imageio/imageio-batik/src/test/java/com/twelvemonkeys/imageio/plugins/svg/SVGImageReaderTest.java index f833df80..0f086420 100755 --- a/imageio/imageio-batik/src/test/java/com/twelvemonkeys/imageio/plugins/svg/SVGImageReaderTest.java +++ b/imageio/imageio-batik/src/test/java/com/twelvemonkeys/imageio/plugins/svg/SVGImageReaderTest.java @@ -34,18 +34,26 @@ import com.twelvemonkeys.imageio.util.ImageReaderAbstractTest; import org.junit.Ignore; import org.junit.Test; +import javax.imageio.IIOException; import javax.imageio.ImageReadParam; import javax.imageio.ImageReader; +import javax.imageio.event.IIOReadWarningListener; import javax.imageio.spi.ImageReaderSpi; +import javax.imageio.stream.ImageInputStream; import java.awt.*; import java.awt.image.BufferedImage; import java.awt.image.ImagingOpException; import java.io.IOException; +import java.net.URISyntaxException; +import java.net.URL; import java.util.Arrays; import java.util.Collections; import java.util.List; -import static org.junit.Assert.assertEquals; +import static org.junit.Assert.*; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyString; +import static org.mockito.Mockito.*; /** * SVGImageReaderTest @@ -146,4 +154,97 @@ public class SVGImageReaderTest extends ImageReaderAbstractTest BufferedImage imageBlue = reader.read(0, param); assertEquals(0x0000FF, imageBlue.getRGB(50, 50) & 0xFFFFFF); } + + @Test + public void testEmbeddedBaseURI() throws URISyntaxException, IOException { + URL resource = getClassLoaderResource("/svg/barChart.svg"); + + SVGImageReader reader = createReader(); + + TestData data = new TestData(resource, (Dimension) null); + try (ImageInputStream stream = data.getInputStream()) { + reader.setInput(stream); + + IIOReadWarningListener listener = mock(IIOReadWarningListener.class); + reader.addIIOReadWarningListener(listener); + + SVGReadParam param = reader.getDefaultReadParam(); + param.setBaseURI(resource.toURI().toASCIIString()); + BufferedImage image = reader.read(0, param); + + assertNotNull(image); + assertEquals(450, image.getWidth()); + assertEquals(500, image.getHeight()); + + // CSS and embedded resources all go! + verifyZeroInteractions(listener); + } + finally { + reader.dispose(); + } + } + + @Test + public void testEmbeddedBeforeBaseURI() throws URISyntaxException, IOException { + // Asking for metadata, width, height etc, before attempting to read using a param, + // will cause the document to be parsed without a base URI. + // This will work, but may not use the CSS... + URL resource = getClassLoaderResource("/svg/barChart.svg"); + + SVGImageReader reader = createReader(); + + TestData data = new TestData(resource, (Dimension) null); + try (ImageInputStream stream = data.getInputStream()) { + reader.setInput(stream); + + IIOReadWarningListener listener = mock(IIOReadWarningListener.class); + reader.addIIOReadWarningListener(listener); + + assertEquals(450, reader.getWidth(0)); + assertEquals(500, reader.getHeight(0)); + + // Expect the warning about the missing CSS + verify(listener, atMost(1)).warningOccurred(any(ImageReader.class), anyString()); + reset(listener); + + SVGReadParam param = reader.getDefaultReadParam(); + param.setBaseURI(resource.toURI().toASCIIString()); + BufferedImage image = reader.read(0, param); + + assertNotNull(image); + assertEquals(450, image.getWidth()); + assertEquals(500, image.getHeight()); + + // No more warnings now that the base URI is set + verifyZeroInteractions(listener); + } + finally { + reader.dispose(); + } + } + + @Test + public void testEmbeddedNoBaseURI() throws IOException { + // With no base URI, we will throw an exception, about the missing embedded resource + URL resource = getClassLoaderResource("/svg/barChart.svg"); + + SVGImageReader reader = createReader(); + + TestData data = new TestData(resource, (Dimension) null); + try (ImageInputStream stream = data.getInputStream()) { + reader.setInput(stream); + + BufferedImage image = reader.read(0); + + assertNotNull(image); + assertEquals(450, image.getWidth()); + assertEquals(500, image.getHeight()); + } + catch (IIOException allowed) { + assertTrue(allowed.getMessage().contains("batikLogo.svg")); // The embedded resource we don't find + } + finally { + reader.dispose(); + } + } } \ No newline at end of file diff --git a/imageio/imageio-batik/src/test/resources/svg/barChart.svg b/imageio/imageio-batik/src/test/resources/svg/barChart.svg new file mode 100644 index 00000000..5afd1750 --- /dev/null +++ b/imageio/imageio-batik/src/test/resources/svg/barChart.svg @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + Bar Chart + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Shoe + Car + Travel + Computer + + 0 + 10 + 20 + 30 + 40 + 50 + 60 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/imageio/imageio-batik/src/test/resources/svg/style/test.css b/imageio/imageio-batik/src/test/resources/svg/style/test.css new file mode 100644 index 00000000..bf25007b --- /dev/null +++ b/imageio/imageio-batik/src/test/resources/svg/style/test.css @@ -0,0 +1,29 @@ +/* + + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You 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. + + */ + +.title { + font-family: Arial, Helvetica; + font-size: 16pt; + text-anchor: middle; +} +.legend { + font-family: Arial, Helvetica; + font-size: 10pt; + text-anchor: middle; +}