diff options
author | Andreas Beeker <kiwiwings@apache.org> | 2021-05-08 21:56:06 +0000 |
---|---|---|
committer | Andreas Beeker <kiwiwings@apache.org> | 2021-05-08 21:56:06 +0000 |
commit | 0b0bcce7ac60f417c986512dcf90a7b9661fbe7d (patch) | |
tree | f3b3f4f739e0739416d9ff62330b764b10fabe6a /poi-ooxml | |
parent | b3f53ff0bc640cfbed894fbd7865757696d2a944 (diff) | |
download | poi-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')
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; } |