aboutsummaryrefslogtreecommitdiffstats
path: root/poi-ooxml
diff options
context:
space:
mode:
authorAndreas Beeker <kiwiwings@apache.org>2021-05-08 21:56:06 +0000
committerAndreas Beeker <kiwiwings@apache.org>2021-05-08 21:56:06 +0000
commit0b0bcce7ac60f417c986512dcf90a7b9661fbe7d (patch)
treef3b3f4f739e0739416d9ff62330b764b10fabe6a /poi-ooxml
parentb3f53ff0bc640cfbed894fbd7865757696d2a944 (diff)
downloadpoi-0b0bcce7ac60f417c986512dcf90a7b9661fbe7d.tar.gz
poi-0b0bcce7ac60f417c986512dcf90a7b9661fbe7d.zip
#64844 - Incorrect sizes of images in SVG
git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1889686 13f79535-47bb-0310-9956-ffa450edef68
Diffstat (limited to 'poi-ooxml')
-rw-r--r--poi-ooxml/src/main/java/org/apache/poi/xslf/draw/SVGPOIGraphics2D.java1
-rw-r--r--poi-ooxml/src/main/java/org/apache/poi/xslf/draw/SVGRenderExtension.java204
-rw-r--r--poi-ooxml/src/main/java/org/apache/poi/xslf/usermodel/XSLFShape.java2
-rw-r--r--poi-ooxml/src/main/java/org/apache/poi/xslf/usermodel/XSLFTexturePaint.java11
-rw-r--r--poi-ooxml/src/main/java/org/apache/poi/xslf/util/SVGFormat.java2
5 files changed, 215 insertions, 5 deletions
diff --git a/poi-ooxml/src/main/java/org/apache/poi/xslf/draw/SVGPOIGraphics2D.java b/poi-ooxml/src/main/java/org/apache/poi/xslf/draw/SVGPOIGraphics2D.java
index b2b40f9163..09dfa11492 100644
--- a/poi-ooxml/src/main/java/org/apache/poi/xslf/draw/SVGPOIGraphics2D.java
+++ b/poi-ooxml/src/main/java/org/apache/poi/xslf/draw/SVGPOIGraphics2D.java
@@ -40,6 +40,7 @@ public class SVGPOIGraphics2D extends SVGGraphics2D {
public SVGPOIGraphics2D(Document document, boolean textAsShapes) {
super(getCtx(document), textAsShapes);
hints = getGeneratorContext().getGraphicContextDefaults().getRenderingHints();
+ ((SVGRenderExtension)getGeneratorContext().getExtensionHandler()).setSvgGraphics2D(this);
}
private static SVGGeneratorContext getCtx(Document document) {
diff --git a/poi-ooxml/src/main/java/org/apache/poi/xslf/draw/SVGRenderExtension.java b/poi-ooxml/src/main/java/org/apache/poi/xslf/draw/SVGRenderExtension.java
index 1af8c2d45e..e6e72058b8 100644
--- a/poi-ooxml/src/main/java/org/apache/poi/xslf/draw/SVGRenderExtension.java
+++ b/poi-ooxml/src/main/java/org/apache/poi/xslf/draw/SVGRenderExtension.java
@@ -18,7 +18,15 @@
package org.apache.poi.xslf.draw;
import static java.awt.MultipleGradientPaint.ColorSpaceType.LINEAR_RGB;
+import static org.apache.batik.svggen.SVGSyntax.ID_PREFIX_IMAGE;
+import static org.apache.batik.svggen.SVGSyntax.ID_PREFIX_PATTERN;
+import static org.apache.batik.svggen.SVGSyntax.SIGN_POUND;
+import static org.apache.batik.svggen.SVGSyntax.URL_PREFIX;
+import static org.apache.batik.svggen.SVGSyntax.URL_SUFFIX;
import static org.apache.batik.util.SVGConstants.*;
+import static org.apache.poi.sl.usermodel.PictureData.PictureType.GIF;
+import static org.apache.poi.sl.usermodel.PictureData.PictureType.JPEG;
+import static org.apache.poi.sl.usermodel.PictureData.PictureType.PNG;
import java.awt.Color;
import java.awt.LinearGradientPaint;
@@ -29,20 +37,41 @@ import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.TexturePaint;
import java.awt.geom.AffineTransform;
+import java.awt.geom.Dimension2D;
import java.awt.geom.Point2D;
+import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.WritableRaster;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.lang.ref.WeakReference;
+import java.nio.charset.StandardCharsets;
+import java.util.Base64;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.zip.CRC32;
+
+import javax.imageio.ImageIO;
import org.apache.batik.svggen.DefaultExtensionHandler;
import org.apache.batik.svggen.SVGColor;
import org.apache.batik.svggen.SVGGeneratorContext;
+import org.apache.batik.svggen.SVGGraphics2D;
import org.apache.batik.svggen.SVGPaintDescriptor;
import org.apache.batik.svggen.SVGTexturePaint;
+import org.apache.poi.sl.draw.BitmapImageRenderer;
+import org.apache.poi.sl.draw.DrawTexturePaint;
import org.apache.poi.sl.draw.Drawable;
+import org.apache.poi.sl.draw.ImageRenderer;
import org.apache.poi.sl.draw.PathGradientPaint;
import org.apache.poi.sl.draw.PathGradientPaint.PathGradientContext;
+import org.apache.poi.sl.usermodel.Insets2D;
+import org.apache.poi.sl.usermodel.PaintStyle;
+import org.apache.poi.sl.usermodel.SimpleShape;
+import org.apache.poi.util.Dimension2DDouble;
import org.apache.poi.util.Internal;
+import org.w3c.dom.Document;
import org.w3c.dom.Element;
@@ -57,6 +86,21 @@ import org.w3c.dom.Element;
*/
@Internal
public class SVGRenderExtension extends DefaultExtensionHandler {
+ private static final int LINE_LENGTH = 65;
+ private static final String XLINK_NS = "http://www.w3.org/1999/xlink";
+ private final Map<Long, String> imageMap = new HashMap<>();
+ private WeakReference<SVGGraphics2D> svgGraphics2D = null;
+
+
+ public SVGGraphics2D getSvgGraphics2D() {
+ return (svgGraphics2D != null) ? svgGraphics2D.get() : null;
+ }
+
+ public void setSvgGraphics2D(SVGGraphics2D svgGraphics2D) {
+ this.svgGraphics2D = new WeakReference<>(svgGraphics2D);
+ }
+
+
@Override
public SVGPaintDescriptor handlePaint(Paint paint, SVGGeneratorContext generatorContext) {
if (paint instanceof LinearGradientPaint) {
@@ -71,6 +115,10 @@ public class SVGRenderExtension extends DefaultExtensionHandler {
return getPathDescriptor((PathGradientPaint)paint, generatorContext);
}
+ if (paint instanceof DrawTexturePaint) {
+ return getDtpDescriptor((DrawTexturePaint)paint, generatorContext);
+ }
+
return super.handlePaint(paint, generatorContext);
}
@@ -158,8 +206,8 @@ public class SVGRenderExtension extends DefaultExtensionHandler {
}
// Convert gradient stops
- Color[] colors = gradient.getColors();
- float[] fracs = gradient.getFractions();
+ final Color[] colors = gradient.getColors();
+ final float[] fracs = gradient.getFractions();
for (int i = 0; i < colors.length; i++) {
Element stop = genCtx.getDOMFactory().createElementNS(SVG_NAMESPACE_URI, SVG_STOP_TAG);
@@ -180,4 +228,156 @@ public class SVGRenderExtension extends DefaultExtensionHandler {
gradElem.setAttribute(x, Double.toString(point.getX()));
gradElem.setAttribute(y, Double.toString(point.getY()));
}
+
+ private SVGPaintDescriptor getDtpDescriptor(DrawTexturePaint tdp, SVGGeneratorContext genCtx) {
+ String imgID = getImageID(tdp, genCtx);
+ Document domFactory = genCtx.getDOMFactory();
+
+ Element patternDef = domFactory.createElementNS(SVG_NAMESPACE_URI, SVG_PATTERN_TAG);
+ String patID = genCtx.getIDGenerator().generateID(ID_PREFIX_PATTERN);
+
+ PaintStyle.TexturePaint fill = tdp.getFill();
+
+ Insets2D stretch = fill.getStretch();
+ if (stretch == null) {
+ stretch = new Insets2D(0,0,0,0);
+ }
+
+ Rectangle2D anchorRect = tdp.getAnchorRect();
+ String x = genCtx.doubleString(-stretch.left/100_000 * anchorRect.getWidth());
+ String y = genCtx.doubleString(-stretch.top/100_000 * anchorRect.getHeight());
+ String w = genCtx.doubleString((100_000+stretch.left+stretch.right)/100_000 * anchorRect.getWidth());
+ String h = genCtx.doubleString((100_000+stretch.top+stretch.bottom)/100_000 * anchorRect.getHeight());
+
+ Dimension2D scale = fill.getScale();
+ if (scale == null) {
+ scale = new Dimension2DDouble(1,1);
+ }
+ Point2D offset = fill.getOffset();
+ if (offset == null) {
+ offset = new Point2D.Double(0,0);
+ }
+
+ PaintStyle.FlipMode flipMode = fill.getFlipMode();
+ if (flipMode == null) {
+ flipMode = PaintStyle.FlipMode.NONE;
+ }
+
+ setAttribute(genCtx, patternDef,
+ null, SVG_PATTERN_UNITS_ATTRIBUTE, SVG_OBJECT_BOUNDING_BOX_VALUE,
+ null, SVG_ID_ATTRIBUTE, patID,
+ null, SVG_X_ATTRIBUTE, offset.getX(),
+ null, SVG_Y_ATTRIBUTE, offset.getY(),
+ null, SVG_WIDTH_ATTRIBUTE, genCtx.doubleString(scale.getWidth()*100)+"%",
+ null, SVG_HEIGHT_ATTRIBUTE, genCtx.doubleString(scale.getHeight()*100)+"%",
+ null, SVG_PRESERVE_ASPECT_RATIO_ATTRIBUTE, SVG_NONE_VALUE,
+ null, SVG_VIEW_BOX_ATTRIBUTE, x+" "+ y+" "+ w+" "+h
+ );
+
+ org.apache.poi.sl.usermodel.Shape slShape = fill.getShape();
+
+ // TODO: the rotation handling is incomplete and the scale handling is missing
+ // see DrawTexturePaint on how to do it for AWT
+ if (!fill.isRotatedWithShape() && slShape instanceof SimpleShape) {
+ double rot = ((SimpleShape)slShape).getRotation();
+ if (rot != 0) {
+ setAttribute(genCtx, patternDef,
+ null, SVG_PATTERN_TRANSFORM_ATTRIBUTE, "rotate(" + genCtx.doubleString(-rot) + ")");
+ }
+ }
+
+ Element useImageEl = domFactory.createElementNS(SVG_NAMESPACE_URI, SVG_USE_TAG);
+ useImageEl.setAttributeNS(null, "href", "#"+imgID);
+ patternDef.appendChild(useImageEl);
+
+ String patternAttrBuf = URL_PREFIX + SIGN_POUND + patID + URL_SUFFIX;
+ return new SVGPaintDescriptor(patternAttrBuf, SVG_OPAQUE_VALUE, patternDef);
+ }
+
+ private String getImageID(DrawTexturePaint tdp, SVGGeneratorContext genCtx) {
+ final ImageRenderer imgRdr = tdp.getImageRenderer();
+
+ byte[] imgData = null;
+ String contentType = null;
+ if (imgRdr instanceof BitmapImageRenderer) {
+ BitmapImageRenderer bir = (BitmapImageRenderer)imgRdr;
+ String ct = bir.getCachedContentType();
+ if (PNG.contentType.equals(ct) ||
+ JPEG.contentType.equals(ct) ||
+ GIF.contentType.equals(ct)) {
+ contentType = ct;
+ imgData = bir.getCachedImage();
+ }
+ }
+ if (imgData == null) {
+ BufferedImage bi = imgRdr.getImage();
+ ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ try {
+ ImageIO.write(bi, "PNG", bos);
+ } catch (IOException e) {
+ return null;
+ }
+ imgData = bos.toByteArray();
+ contentType = PNG.contentType;
+ }
+
+ CRC32 crc = new CRC32();
+ crc.update(imgData);
+ Long imageCrc = crc.getValue();
+
+ String imgID = imageMap.get(imageCrc);
+ if (imgID != null) {
+ return imgID;
+ }
+
+ Document domFactory = genCtx.getDOMFactory();
+ Rectangle2D anchorRect = tdp.getAnchorRect();
+
+ imgID = genCtx.getIDGenerator().generateID(ID_PREFIX_IMAGE);
+ imageMap.put(imageCrc, imgID);
+
+ // length of a base64 string
+ int sbLen = ((4 * imgData.length / 3) + 3) & ~3;
+ // add line breaks every 65 chars and a few more padding chars
+ sbLen += sbLen / LINE_LENGTH + 30;
+ StringBuilder sb = new StringBuilder(sbLen);
+ sb.append("data:");
+ sb.append(contentType);
+ sb.append(";base64,\n");
+ sb.append(Base64.getMimeEncoder(LINE_LENGTH, "\n".getBytes(StandardCharsets.US_ASCII)).encodeToString(imgData));
+
+ Element imageEl = domFactory.createElementNS(SVG_NAMESPACE_URI, SVG_IMAGE_TAG);
+ setAttribute(genCtx, imageEl,
+ null, SVG_ID_ATTRIBUTE, imgID,
+ null, SVG_PRESERVE_ASPECT_RATIO_ATTRIBUTE, SVG_NONE_VALUE,
+ null, SVG_X_ATTRIBUTE, anchorRect.getX(),
+ null, SVG_Y_ATTRIBUTE, anchorRect.getY(),
+ null, SVG_WIDTH_ATTRIBUTE, anchorRect.getWidth(),
+ null, SVG_HEIGHT_ATTRIBUTE, anchorRect.getHeight(),
+ XLINK_NS, "xlink:href", sb.toString()
+ );
+
+ getSvgGraphics2D().getDOMTreeManager().addOtherDef(imageEl);
+
+ return imgID;
+ }
+
+ private static void setAttribute(SVGGeneratorContext genCtx, Element el, Object... params) {
+ for (int i=0; i<params.length; i+=3) {
+ String ns = (String)params[i];
+ String name = (String)params[i+1];
+ Object oval = params[i+2];
+ String val;
+ if (oval instanceof String) {
+ val = (String)oval;
+ } else if (oval instanceof Number) {
+ val = genCtx.doubleString(((Number) oval).doubleValue());
+ } else if (oval == null) {
+ val = "";
+ } else {
+ val = oval.toString();
+ }
+ el.setAttributeNS(ns, name, val);
+ }
+ }
}
diff --git a/poi-ooxml/src/main/java/org/apache/poi/xslf/usermodel/XSLFShape.java b/poi-ooxml/src/main/java/org/apache/poi/xslf/usermodel/XSLFShape.java
index 5a267595e2..ba546e8f16 100644
--- a/poi-ooxml/src/main/java/org/apache/poi/xslf/usermodel/XSLFShape.java
+++ b/poi-ooxml/src/main/java/org/apache/poi/xslf/usermodel/XSLFShape.java
@@ -425,7 +425,7 @@ public abstract class XSLFShape implements Shape<XSLFShape,XSLFTextParagraph> {
@SuppressWarnings("WeakerAccess")
protected PaintStyle selectPaint(final CTBlipFillProperties blipFill, final PackagePart parentPart, CTSchemeColor phClr, final XSLFTheme theme) {
- return new XSLFTexturePaint(blipFill, parentPart, phClr, theme, _sheet);
+ return new XSLFTexturePaint(this, blipFill, parentPart, phClr, theme, _sheet);
}
@SuppressWarnings("WeakerAccess")
diff --git a/poi-ooxml/src/main/java/org/apache/poi/xslf/usermodel/XSLFTexturePaint.java b/poi-ooxml/src/main/java/org/apache/poi/xslf/usermodel/XSLFTexturePaint.java
index 655be92305..985109dfb4 100644
--- a/poi-ooxml/src/main/java/org/apache/poi/xslf/usermodel/XSLFTexturePaint.java
+++ b/poi-ooxml/src/main/java/org/apache/poi/xslf/usermodel/XSLFTexturePaint.java
@@ -32,6 +32,7 @@ import org.apache.poi.openxml4j.opc.PackageRelationship;
import org.apache.poi.sl.usermodel.ColorStyle;
import org.apache.poi.sl.usermodel.Insets2D;
import org.apache.poi.sl.usermodel.PaintStyle;
+import org.apache.poi.sl.usermodel.Shape;
import org.apache.poi.util.Dimension2DDouble;
import org.apache.poi.util.Internal;
import org.apache.poi.util.Units;
@@ -46,6 +47,7 @@ import org.openxmlformats.schemas.drawingml.x2006.main.STTileFlipMode;
@Internal
public class XSLFTexturePaint implements PaintStyle.TexturePaint {
+ private final XSLFShape shape;
private final CTBlipFillProperties blipFill;
private final PackagePart parentPart;
private final CTBlip blip;
@@ -53,7 +55,8 @@ public class XSLFTexturePaint implements PaintStyle.TexturePaint {
private final XSLFTheme theme;
private final XSLFSheet sheet;
- public XSLFTexturePaint(final CTBlipFillProperties blipFill, final PackagePart parentPart, CTSchemeColor phClr, final XSLFTheme theme, final XSLFSheet sheet) {
+ public XSLFTexturePaint(final XSLFShape shape, final CTBlipFillProperties blipFill, final PackagePart parentPart, CTSchemeColor phClr, final XSLFTheme theme, final XSLFSheet sheet) {
+ this.shape = shape;
this.blipFill = blipFill;
this.parentPart = parentPart;
blip = blipFill.getBlip();
@@ -100,7 +103,7 @@ public class XSLFTexturePaint implements PaintStyle.TexturePaint {
@Override
public boolean isRotatedWithShape() {
- return blipFill.isSetRotWithShape() && blipFill.getRotWithShape();
+ return !blipFill.isSetRotWithShape() || blipFill.getRotWithShape();
}
@Override
@@ -165,6 +168,10 @@ public class XSLFTexturePaint implements PaintStyle.TexturePaint {
return colors;
}
+ @Override
+ public Shape getShape() {
+ return shape;
+ }
private static Insets2D getRectVal(CTRelativeRect rect) {
return rect == null ? null : new Insets2D(
diff --git a/poi-ooxml/src/main/java/org/apache/poi/xslf/util/SVGFormat.java b/poi-ooxml/src/main/java/org/apache/poi/xslf/util/SVGFormat.java
index 1725aa4f10..3122855bde 100644
--- a/poi-ooxml/src/main/java/org/apache/poi/xslf/util/SVGFormat.java
+++ b/poi-ooxml/src/main/java/org/apache/poi/xslf/util/SVGFormat.java
@@ -29,6 +29,7 @@ import java.nio.charset.StandardCharsets;
import org.apache.batik.dom.GenericDOMImplementation;
import org.apache.batik.svggen.SVGGraphics2D;
+import org.apache.poi.sl.draw.Drawable;
import org.apache.poi.util.Internal;
import org.apache.poi.xslf.draw.SVGPOIGraphics2D;
import org.w3c.dom.DOMImplementation;
@@ -53,6 +54,7 @@ public class SVGFormat implements OutputFormat {
Document document = domImpl.createDocument(svgNS, "svg", null);
svgGenerator = new SVGPOIGraphics2D(document, textAsShapes);
svgGenerator.setSVGCanvasSize(new Dimension((int)width, (int)height));
+ svgGenerator.setRenderingHint(Drawable.CACHE_IMAGE_SOURCE, true);
return svgGenerator;
}