From b9e0f8edb85dda3ba11cf6568b716c761d01e24e Mon Sep 17 00:00:00 2001 From: Andreas Beeker Date: Fri, 20 Aug 2021 16:53:09 +0000 Subject: [PATCH] Bug 65501 - Use viewbox when rendering SVG images git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1892477 13f79535-47bb-0310-9956-ffa450edef68 --- .../poi/xslf/draw/SVGImageRenderer.java | 26 ++-- .../apache/poi/xslf/draw/SVGUserAgent.java | 117 ++++++++++++++++++ 2 files changed, 131 insertions(+), 12 deletions(-) create mode 100644 poi-ooxml/src/main/java/org/apache/poi/xslf/draw/SVGUserAgent.java diff --git a/poi-ooxml/src/main/java/org/apache/poi/xslf/draw/SVGImageRenderer.java b/poi-ooxml/src/main/java/org/apache/poi/xslf/draw/SVGImageRenderer.java index b355e34dd6..5e2939d79f 100644 --- a/poi-ooxml/src/main/java/org/apache/poi/xslf/draw/SVGImageRenderer.java +++ b/poi-ooxml/src/main/java/org/apache/poi/xslf/draw/SVGImageRenderer.java @@ -33,8 +33,6 @@ import org.apache.batik.anim.dom.SAXSVGDocumentFactory; import org.apache.batik.bridge.BridgeContext; import org.apache.batik.bridge.DocumentLoader; import org.apache.batik.bridge.GVTBuilder; -import org.apache.batik.bridge.UserAgent; -import org.apache.batik.bridge.UserAgentAdapter; import org.apache.batik.ext.awt.RenderingHintsKeyExt; import org.apache.batik.ext.awt.image.renderable.ClipRable8Bit; import org.apache.batik.gvt.GraphicsNode; @@ -42,31 +40,30 @@ import org.apache.batik.util.XMLResourceDescriptor; import org.apache.poi.sl.draw.Drawable; import org.apache.poi.sl.draw.ImageRenderer; import org.apache.poi.sl.usermodel.PictureData; -import org.w3c.dom.Document; +import org.w3c.dom.svg.SVGDocument; public class SVGImageRenderer implements ImageRenderer { + private final SAXSVGDocumentFactory svgFact; private final GVTBuilder builder = new GVTBuilder(); private final BridgeContext context; - private final SAXSVGDocumentFactory svgFact; - private GraphicsNode svgRoot; private double alpha = 1.0; public SVGImageRenderer() { String parser = XMLResourceDescriptor.getXMLParserClassName(); // TOOO: tell the batik guys to use secure parsing feature svgFact = new SAXSVGDocumentFactory(parser); + SVGUserAgent agent = new SVGUserAgent(); - UserAgent agent = new UserAgentAdapter(); DocumentLoader loader = new DocumentLoader(agent); context = new BridgeContext(agent, loader); context.setDynamic(true); } - @Override public void loadImage(InputStream data, String contentType) throws IOException { - Document document = svgFact.createDocument("", data); - svgRoot = builder.build(context, document); + SVGDocument document = (SVGDocument)svgFact.createDocument("", data); + ((SVGUserAgent)context.getUserAgent()).initViewbox(document); + builder.build(context, document); } @Override @@ -76,7 +73,7 @@ public class SVGImageRenderer implements ImageRenderer { @Override public Rectangle2D getBounds() { - return svgRoot.getPrimitiveBounds(); + return ((SVGUserAgent)context.getUserAgent()).getViewbox(); } @Override @@ -106,7 +103,7 @@ public class SVGImageRenderer implements ImageRenderer { double scaleY = dim.getHeight() / dimSVG.getHeight(); g2d.scale(scaleX, scaleY); - svgRoot.paint(g2d); + getSVGRoot().paint(g2d); g2d.dispose(); return bi; @@ -121,6 +118,7 @@ public class SVGImageRenderer implements ImageRenderer { public boolean drawImage(Graphics2D graphics, Rectangle2D anchor, Insets clip) { graphics.setRenderingHint(RenderingHintsKeyExt.KEY_BUFFERED_IMAGE, graphics.getRenderingHint(Drawable.BUFFERED_IMAGE)); + GraphicsNode svgRoot = getSVGRoot(); Dimension2D bounds = getDimension(); AffineTransform at = new AffineTransform(); @@ -152,6 +150,10 @@ public class SVGImageRenderer implements ImageRenderer { @Override public Rectangle2D getNativeBounds() { - return svgRoot.getPrimitiveBounds(); + return getSVGRoot().getPrimitiveBounds(); + } + + public GraphicsNode getSVGRoot() { + return context.getGraphicsNode(context.getDocument()); } } diff --git a/poi-ooxml/src/main/java/org/apache/poi/xslf/draw/SVGUserAgent.java b/poi-ooxml/src/main/java/org/apache/poi/xslf/draw/SVGUserAgent.java new file mode 100644 index 0000000000..3b6034198e --- /dev/null +++ b/poi-ooxml/src/main/java/org/apache/poi/xslf/draw/SVGUserAgent.java @@ -0,0 +1,117 @@ +/* ==================================================================== + 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. +==================================================================== */ + +package org.apache.poi.xslf.draw; + +import java.awt.geom.Dimension2D; +import java.awt.geom.Rectangle2D; + +import org.apache.batik.bridge.UserAgentAdapter; +import org.apache.batik.bridge.ViewBox; +import org.apache.batik.parser.DefaultLengthHandler; +import org.apache.batik.parser.LengthHandler; +import org.apache.batik.parser.LengthParser; +import org.apache.batik.parser.ParseException; +import org.apache.batik.util.SVGConstants; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.poi.util.Dimension2DDouble; +import org.apache.poi.util.Internal; +import org.w3c.dom.svg.SVGDocument; +import org.w3c.dom.svg.SVGSVGElement; + +/** + * Helper class to base image calculation on actual viewbox instead of the base box (1,1) + */ +@Internal +public class SVGUserAgent extends UserAgentAdapter { + private static final Logger LOG = LogManager.getLogger(SVGUserAgent.class); + private Rectangle2D viewbox; + + public SVGUserAgent() { + addStdFeatures(); + } + + @Override + public Dimension2D getViewportSize() { + return viewbox != null + ? new Dimension2DDouble(viewbox.getWidth(), viewbox.getHeight()) + : super.getViewportSize(); + } + + public Rectangle2D getViewbox() { + return viewbox != null ? viewbox : new Rectangle2D.Double(0, 0, 1, 1); + } + + public void initViewbox(SVGDocument doc) { + viewbox = null; + SVGSVGElement el = doc.getRootElement(); + if (el == null) { + return; + } + String viewBoxStr = el.getAttributeNS(null, SVGConstants.SVG_VIEW_BOX_ATTRIBUTE); + if (viewBoxStr != null && !viewBoxStr.isEmpty()) { + float[] rect = ViewBox.parseViewBoxAttribute(el, viewBoxStr, null); + viewbox = new Rectangle2D.Float(rect[0], rect[1], rect[2], rect[3]); + return; + } + + float w = parseLength(el, SVGConstants.SVG_WIDTH_ATTRIBUTE); + float h = parseLength(el, SVGConstants.SVG_HEIGHT_ATTRIBUTE); + if (w != 0 && h != 0) { + viewbox = new Rectangle2D.Double(0, 0, w, h); + } + } + + private static float parseLength(SVGSVGElement el, String attr) { + String a = el.getAttributeNS(null, attr); + if (a == null || a.isEmpty()) { + return 0; + } + float[] val = { 0 }; + LengthParser lp = new LengthParser(); + LengthHandler lh = new DefaultLengthHandler() { + @Override + public void lengthValue(float v) throws ParseException { + val[0] = v; + } + }; + lp.setLengthHandler(lh); + lp.parse(a); + return val[0]; + } + + @Override + public void displayMessage(String message) { + LOG.atInfo().log(message); + } + + @Override + public void displayError(String message) { + LOG.atError().log(message); + } + + @Override + public void displayError(Exception e) { + LOG.atError().withThrowable(e).log(e.getMessage()); + } + + @Override + public void showAlert(String message) { + LOG.atWarn().log(message); + } +}