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
This commit is contained in:
Andreas Beeker 2021-08-20 16:53:09 +00:00
parent 9f48ce8ba6
commit b9e0f8edb8
2 changed files with 131 additions and 12 deletions

View File

@ -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());
}
}

View File

@ -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);
}
}