From 1794ae97c1cb3f760b7d6d7b4789f3656e31a397 Mon Sep 17 00:00:00 2001 From: Andreas Beeker Date: Mon, 11 Nov 2019 23:06:29 +0000 Subject: [PATCH] #63918 - Fix texture fill - scale stretched images correctly git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1869669 13f79535-47bb-0310-9956-ffa450edef68 --- .../apache/poi/sl/draw/DrawTexturePaint.java | 126 +++++++++++-- .../apache/poi/sl/usermodel/PaintStyle.java | 32 ++++ .../poi/xslf/usermodel/XSLFGradientPaint.java | 107 +++++++++++ .../apache/poi/xslf/usermodel/XSLFShape.java | 169 +----------------- .../poi/xslf/usermodel/XSLFTexturePaint.java | 154 ++++++++++++++++ .../apache/poi/hslf/usermodel/HSLFFill.java | 69 ++++--- 6 files changed, 439 insertions(+), 218 deletions(-) create mode 100644 src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFGradientPaint.java create mode 100644 src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFTexturePaint.java diff --git a/src/java/org/apache/poi/sl/draw/DrawTexturePaint.java b/src/java/org/apache/poi/sl/draw/DrawTexturePaint.java index 3864ec6cee..ce35b835e0 100644 --- a/src/java/org/apache/poi/sl/draw/DrawTexturePaint.java +++ b/src/java/org/apache/poi/sl/draw/DrawTexturePaint.java @@ -17,10 +17,13 @@ package org.apache.poi.sl.draw; +import java.awt.AlphaComposite; +import java.awt.Graphics2D; import java.awt.PaintContext; import java.awt.Rectangle; 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; @@ -28,7 +31,9 @@ import java.awt.geom.Rectangle2D; import java.awt.image.BufferedImage; import java.awt.image.ColorModel; +import org.apache.poi.sl.usermodel.Insets2D; import org.apache.poi.sl.usermodel.PaintStyle; +import org.apache.poi.util.Dimension2DDouble; /* package */ class DrawTexturePaint extends java.awt.TexturePaint { private final PaintStyle.TexturePaint fill; @@ -36,6 +41,9 @@ import org.apache.poi.sl.usermodel.PaintStyle; private final double flipX, flipY; private final boolean isBitmapSrc; + private static final Insets2D INSETS_EMPTY = new Insets2D(0,0,0,0); + + DrawTexturePaint(BufferedImage txtr, Shape shape, PaintStyle.TexturePaint fill, double flipX, double flipY, boolean isBitmapSrc) { // deactivate scaling/translation in super class, by specifying the dimension of the texture super(txtr, new Rectangle2D.Double(0,0,txtr.getWidth(),txtr.getHeight())); @@ -49,13 +57,10 @@ import org.apache.poi.sl.usermodel.PaintStyle; @Override public PaintContext createContext(ColorModel cm, Rectangle deviceBounds, Rectangle2D userBounds, AffineTransform xform, RenderingHints hints) { - final double usr_w, usr_h; - + final Dimension2D userDim = new Dimension2DDouble(); + final Rectangle2D usedBounds; if (fill.isRotatedWithShape() || shape == null) { - usr_w = userBounds.getWidth(); - usr_h = userBounds.getHeight(); - - xform.translate(userBounds.getX(), userBounds.getY()); + usedBounds = userBounds; } else { AffineTransform transform = new AffineTransform(xform); @@ -73,22 +78,111 @@ import org.apache.poi.sl.usermodel.PaintStyle; // TODO: check if approximation via rotating only the bounds (instead of the shape) is sufficient transform = AffineTransform.getRotateInstance(rad, userBounds.getCenterX(), userBounds.getCenterY()); - Rectangle2D newBounds = transform.createTransformedShape(shape).getBounds2D(); - usr_w = newBounds.getWidth(); - usr_h = newBounds.getHeight(); + usedBounds = transform.createTransformedShape(shape).getBounds2D(); + } + userDim.setSize(usedBounds.getWidth(), usedBounds.getHeight()); + xform.translate(usedBounds.getX(), usedBounds.getY()); + + BufferedImage bi = getImage(usedBounds); + + if (fill.getStretch() != null) { + TexturePaint tp = new TexturePaint(bi, new Rectangle2D.Double(0, 0, bi.getWidth(), bi.getHeight())); + return tp.createContext(cm, deviceBounds, usedBounds, xform, hints); + } else if (fill.getScale() != null) { + AffineTransform newXform = getTiledInstance(usedBounds, (AffineTransform) xform.clone()); + TexturePaint tp = new TexturePaint(bi, new Rectangle2D.Double(0, 0, bi.getWidth(), bi.getHeight())); + return tp.createContext(cm, deviceBounds, userBounds, newXform, hints); + } else { + return super.createContext(cm, deviceBounds, userBounds, xform, hints); + } + } + + public BufferedImage getImage(Rectangle2D userBounds) { + BufferedImage bi = super.getImage(); + final Insets2D insets = fill.getInsets(); + final Insets2D stretch = fill.getStretch(); - xform.translate(newBounds.getX(), newBounds.getY()); + if ((insets == null || INSETS_EMPTY.equals(insets)) && (stretch == null)) { + return bi; } - final Dimension2D scale = fill.getScale(); + if (insets != null && !INSETS_EMPTY.equals(insets)) { + final int width = bi.getWidth(); + final int height = bi.getHeight(); + + bi = bi.getSubimage( + (int)(Math.max(insets.left,0)/100_000 * width), + (int)(Math.max(insets.top,0)/100_000 * height), + (int)((100_000-Math.max(insets.left,0)-Math.max(insets.right,0))/100_000 * width), + (int)((100_000-Math.max(insets.top,0)-Math.max(insets.bottom,0))/100_000 * height) + ); + + int addTop = (int)(Math.max(-insets.top, 0)/100_000 * height); + int addLeft = (int)(Math.max(-insets.left, 0)/100_000 * width); + int addBottom = (int)(Math.max(-insets.bottom, 0)/100_000 * height); + int addRight = (int)(Math.max(-insets.right, 0)/100_000 * width); + + // handle outsets + if (addTop > 0 || addLeft > 0 || addBottom > 0 || addRight > 0) { + int[] buf = new int[bi.getWidth()*bi.getHeight()]; + bi.getRGB(0, 0, bi.getWidth(), bi.getHeight(), buf, 0, bi.getWidth()); + BufferedImage borderBi = new BufferedImage(bi.getWidth()+addLeft+addRight, bi.getHeight()+addTop+addBottom, bi.getType()); + borderBi.setRGB(addLeft, addTop, bi.getWidth(), bi.getHeight(), buf, 0, bi.getWidth()); + bi = borderBi; + } + } + + if (stretch != null) { + Rectangle2D srcBounds = new Rectangle2D.Double( + 0, 0, bi.getWidth(), bi.getHeight() + ); + Rectangle2D dstBounds = new Rectangle2D.Double( + stretch.left/100_000 * userBounds.getWidth(), + stretch.top/100_000 * userBounds.getHeight(), + (100_000-stretch.left-stretch.right)/100_000 * userBounds.getWidth(), + (100_000-stretch.top-stretch.bottom)/100_000 * userBounds.getHeight() + ); + + BufferedImage stretchBi = new BufferedImage((int)userBounds.getWidth(), (int)userBounds.getHeight(), BufferedImage.TYPE_INT_ARGB); + Graphics2D g = stretchBi.createGraphics(); + + g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); + g.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_SPEED); + g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC); + g.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON); + + g.setComposite(AlphaComposite.Clear); + g.fillRect(0, 0, stretchBi.getWidth(), stretchBi.getHeight()); + g.setComposite(AlphaComposite.SrcOver); + + AffineTransform at = new AffineTransform(); + at.translate(dstBounds.getCenterX(), dstBounds.getCenterY()); + at.scale(dstBounds.getWidth()/srcBounds.getWidth(), dstBounds.getHeight()/srcBounds.getHeight()); + at.translate(-srcBounds.getCenterX(), -srcBounds.getCenterY()); + + g.drawRenderedImage(bi, at); + + g.dispose(); + + bi = stretchBi; + } + + return bi; + } + + private AffineTransform getTiledInstance(final Rectangle2D usedBounds, final AffineTransform xform) { final BufferedImage bi = getImage(); - final double img_w = bi.getWidth() * (scale == null ? 1 : scale.getWidth())/flipX; - final double img_h = bi.getHeight() * (scale == null ? 1 : scale.getHeight())/flipY; + final Dimension2D scale = fill.getScale(); + assert(scale != null); + final double img_w = bi.getWidth() * (scale.getWidth() == 0 ? 1 : scale.getWidth())/flipX; + final double img_h = bi.getHeight() * (scale.getHeight() == 0 ? 1 : scale.getHeight())/flipY; // Alignment happens after the scaling but before any offset. PaintStyle.TextureAlignment ta = fill.getAlignment(); final double alg_x, alg_y; + final double usr_w = usedBounds.getWidth(), usr_h = usedBounds.getHeight(); switch (ta == null ? PaintStyle.TextureAlignment.TOP_LEFT : ta) { case BOTTOM: alg_x = (usr_w-img_w)/2; @@ -140,10 +234,8 @@ import org.apache.poi.sl.usermodel.PaintStyle; xform.translate(offset.getX(),offset.getY()); } - if (scale != null) { - xform.scale(scale.getWidth()/(isBitmapSrc ? flipX : 1.),scale.getHeight()/(isBitmapSrc ? flipY : 1.)); - } + xform.scale(scale.getWidth()/(isBitmapSrc ? flipX : 1.),scale.getHeight()/(isBitmapSrc ? flipY : 1.)); - return super.createContext(cm, deviceBounds, userBounds, xform, hints); + return xform; } } diff --git a/src/java/org/apache/poi/sl/usermodel/PaintStyle.java b/src/java/org/apache/poi/sl/usermodel/PaintStyle.java index 52bcec7146..92371b68c5 100644 --- a/src/java/org/apache/poi/sl/usermodel/PaintStyle.java +++ b/src/java/org/apache/poi/sl/usermodel/PaintStyle.java @@ -138,5 +138,37 @@ public interface PaintStyle { default TextureAlignment getAlignment() { return null; } + + /** + * Specifies the portion of the blip or image that is used for the fill.

+ * + * Each edge of the image is defined by a percentage offset from the edge of the bounding box. + * A positive percentage specifies an inset and a negative percentage specifies an outset.

+ * + * The percentage are ints based on 100000, so 100% = 100000.

+ * + * So, for example, a left offset of 25% specifies that the left edge of the image is located + * to the right of the bounding box's left edge by 25% of the bounding box's width. + * + * @return the cropping insets of the source image + */ + default Insets2D getInsets() { + return null; + } + + /** + * The stretch specifies the edges of a fill rectangle.

+ * + * Each edge of the fill rectangle is defined by a perentage offset from the corresponding edge + * of the picture's bounding box. A positive percentage specifies an inset and a negative percentage + * specifies an outset.

+ * + * The percentage are ints based on 100000, so 100% = 100000. + * + * @return the stretching in the destination image + */ + default Insets2D getStretch() { + return null; + } } } diff --git a/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFGradientPaint.java b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFGradientPaint.java new file mode 100644 index 0000000000..a39ffd5bcf --- /dev/null +++ b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFGradientPaint.java @@ -0,0 +1,107 @@ +/* ==================================================================== + 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.usermodel; + +import java.util.Arrays; + +import org.apache.poi.sl.usermodel.ColorStyle; +import org.apache.poi.sl.usermodel.PaintStyle; +import org.apache.poi.util.Internal; +import org.openxmlformats.schemas.drawingml.x2006.main.CTGradientFillProperties; +import org.openxmlformats.schemas.drawingml.x2006.main.CTGradientStop; +import org.openxmlformats.schemas.drawingml.x2006.main.CTSchemeColor; +import org.openxmlformats.schemas.drawingml.x2006.main.STPathShadeType; + +@Internal +public class XSLFGradientPaint implements PaintStyle.GradientPaint { + + private final CTGradientFillProperties gradFill; + final ColorStyle[] cs; + final float[] fractions; + + public XSLFGradientPaint(final CTGradientFillProperties gradFill, CTSchemeColor phClr, final XSLFTheme theme) { + this.gradFill = gradFill; + + final CTGradientStop[] gs = gradFill.getGsLst() == null ? + new CTGradientStop[0] : gradFill.getGsLst().getGsArray(); + + Arrays.sort(gs, (o1, o2) -> { + int pos1 = o1.getPos(); + int pos2 = o2.getPos(); + return Integer.compare(pos1, pos2); + }); + + cs = new ColorStyle[gs.length]; + fractions = new float[gs.length]; + + int i=0; + for (CTGradientStop cgs : gs) { + CTSchemeColor phClrCgs = phClr; + if (phClrCgs == null && cgs.isSetSchemeClr()) { + phClrCgs = cgs.getSchemeClr(); + } + cs[i] = new XSLFColor(cgs, theme, phClrCgs).getColorStyle(); + fractions[i] = cgs.getPos() / 100000.f; + i++; + } + + } + + + @Override + public double getGradientAngle() { + return (gradFill.isSetLin()) + ? gradFill.getLin().getAng() / 60000.d + : 0; + } + + @Override + public ColorStyle[] getGradientColors() { + return cs; + } + + @Override + public float[] getGradientFractions() { + return fractions; + } + + @Override + public boolean isRotatedWithShape() { + return gradFill.getRotWithShape(); + } + + @Override + public PaintStyle.GradientPaint.GradientType getGradientType() { + if (gradFill.isSetLin()) { + return PaintStyle.GradientPaint.GradientType.linear; + } + + if (gradFill.isSetPath()) { + /* TODO: handle rect path */ + STPathShadeType.Enum ps = gradFill.getPath().getPath(); + if (ps == STPathShadeType.CIRCLE) { + return PaintStyle.GradientPaint.GradientType.circular; + } else if (ps == STPathShadeType.SHAPE) { + return PaintStyle.GradientPaint.GradientType.shape; + } + } + + return PaintStyle.GradientPaint.GradientType.linear; + } + +} diff --git a/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFShape.java b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFShape.java index 3d4b9cff21..fa8a8ff3db 100644 --- a/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFShape.java +++ b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFShape.java @@ -20,40 +20,26 @@ package org.apache.poi.xslf.usermodel; import java.awt.Graphics2D; -import java.awt.geom.Dimension2D; -import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; -import java.io.IOException; -import java.io.InputStream; -import java.util.Arrays; -import org.apache.poi.openxml4j.exceptions.InvalidFormatException; import org.apache.poi.openxml4j.opc.PackagePart; -import org.apache.poi.openxml4j.opc.PackageRelationship; import org.apache.poi.sl.draw.DrawFactory; import org.apache.poi.sl.draw.DrawPaint; -import org.apache.poi.sl.usermodel.ColorStyle; import org.apache.poi.sl.usermodel.MasterSheet; import org.apache.poi.sl.usermodel.PaintStyle; -import org.apache.poi.sl.usermodel.PaintStyle.GradientPaint; -import org.apache.poi.sl.usermodel.PaintStyle.TexturePaint; import org.apache.poi.sl.usermodel.PlaceableShape; import org.apache.poi.sl.usermodel.Placeholder; import org.apache.poi.sl.usermodel.PlaceholderDetails; import org.apache.poi.sl.usermodel.Shape; import org.apache.poi.sl.usermodel.SimpleShape; import org.apache.poi.util.Beta; -import org.apache.poi.util.Dimension2DDouble; import org.apache.poi.util.Internal; -import org.apache.poi.util.Units; import org.apache.poi.xslf.model.PropertyFetcher; import org.apache.poi.xslf.usermodel.XSLFPropertiesDelegate.XSLFFillProperties; import org.apache.xmlbeans.XmlCursor; import org.apache.xmlbeans.XmlObject; -import org.openxmlformats.schemas.drawingml.x2006.main.CTBlip; import org.openxmlformats.schemas.drawingml.x2006.main.CTBlipFillProperties; import org.openxmlformats.schemas.drawingml.x2006.main.CTGradientFillProperties; -import org.openxmlformats.schemas.drawingml.x2006.main.CTGradientStop; import org.openxmlformats.schemas.drawingml.x2006.main.CTGroupShapeProperties; import org.openxmlformats.schemas.drawingml.x2006.main.CTNonVisualDrawingProps; import org.openxmlformats.schemas.drawingml.x2006.main.CTSchemeColor; @@ -62,9 +48,6 @@ import org.openxmlformats.schemas.drawingml.x2006.main.CTShapeStyle; import org.openxmlformats.schemas.drawingml.x2006.main.CTSolidColorFillProperties; import org.openxmlformats.schemas.drawingml.x2006.main.CTStyleMatrix; import org.openxmlformats.schemas.drawingml.x2006.main.CTStyleMatrixReference; -import org.openxmlformats.schemas.drawingml.x2006.main.CTTileInfoProperties; -import org.openxmlformats.schemas.drawingml.x2006.main.STPathShadeType; -import org.openxmlformats.schemas.drawingml.x2006.main.STTileFlipMode; import org.openxmlformats.schemas.presentationml.x2006.main.CTBackgroundProperties; import org.openxmlformats.schemas.presentationml.x2006.main.CTPicture; import org.openxmlformats.schemas.presentationml.x2006.main.CTPlaceholder; @@ -153,6 +136,7 @@ public abstract class XSLFShape implements Shape { protected PaintStyle getFillPaint() { final XSLFTheme theme = getSheet().getTheme(); final boolean hasPlaceholder = getPlaceholder() != null; + PropertyFetcher fetcher = new PropertyFetcher() { @Override public boolean fetch(XSLFShape shape) { @@ -411,159 +395,12 @@ public abstract class XSLFShape implements Shape { @SuppressWarnings("WeakerAccess") protected static PaintStyle selectPaint(final CTBlipFillProperties blipFill, final PackagePart parentPart) { - final CTBlip blip = blipFill.getBlip(); - return new TexturePaint() { - private PackagePart getPart() { - try { - String blipId = blip.getEmbed(); - PackageRelationship rel = parentPart.getRelationship(blipId); - return parentPart.getRelatedPart(rel); - } catch (InvalidFormatException e) { - throw new RuntimeException(e); - } - } - - @Override - public InputStream getImageData() { - try { - return getPart().getInputStream(); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - @Override - public String getContentType() { - if (blip == null || !blip.isSetEmbed() || blip.getEmbed().isEmpty()) { - return null; - } - /* TOOD: map content-type */ - return getPart().getContentType(); - } - - @Override - public int getAlpha() { - return (blip.sizeOfAlphaModFixArray() > 0) - ? blip.getAlphaModFixArray(0).getAmt() - : 100000; - } - - @Override - public boolean isRotatedWithShape() { - return blipFill.isSetRotWithShape() && blipFill.getRotWithShape(); - } - - @Override - public Dimension2D getScale() { - CTTileInfoProperties tile = blipFill.getTile(); - return (tile == null) ? null : new Dimension2DDouble( - tile.isSetSx() ? tile.getSx()/100_000. : 1, - tile.isSetSy() ? tile.getSy()/100_000. : 1); - } - - @Override - public Point2D getOffset() { - CTTileInfoProperties tile = blipFill.getTile(); - return (tile == null) ? null : new Point2D.Double( - tile.isSetTx() ? Units.toPoints(tile.getTx()) : 0, - tile.isSetTy() ? Units.toPoints(tile.getTy()) : 0); - } - - @Override - public FlipMode getFlipMode() { - CTTileInfoProperties tile = blipFill.getTile(); - switch (tile == null || tile.getFlip() == null ? STTileFlipMode.INT_NONE : tile.getFlip().intValue()) { - default: - case STTileFlipMode.INT_NONE: - return FlipMode.NONE; - case STTileFlipMode.INT_X: - return FlipMode.X; - case STTileFlipMode.INT_Y: - return FlipMode.Y; - case STTileFlipMode.INT_XY: - return FlipMode.XY; - } - } - - @Override - public TextureAlignment getAlignment() { - CTTileInfoProperties tile = blipFill.getTile(); - return (tile == null || !tile.isSetAlgn()) ? null - : TextureAlignment.fromOoxmlId(tile.getAlgn().toString()); - } - }; + return new XSLFTexturePaint(blipFill, parentPart); } @SuppressWarnings("WeakerAccess") protected static PaintStyle selectPaint(final CTGradientFillProperties gradFill, CTSchemeColor phClr, final XSLFTheme theme) { - - @SuppressWarnings("deprecation") - final CTGradientStop[] gs = gradFill.getGsLst() == null ? - new CTGradientStop[0] : gradFill.getGsLst().getGsArray(); - - Arrays.sort(gs, (o1, o2) -> { - int pos1 = o1.getPos(); - int pos2 = o2.getPos(); - return Integer.compare(pos1, pos2); - }); - - final ColorStyle[] cs = new ColorStyle[gs.length]; - final float[] fractions = new float[gs.length]; - - int i=0; - for (CTGradientStop cgs : gs) { - CTSchemeColor phClrCgs = phClr; - if (phClrCgs == null && cgs.isSetSchemeClr()) { - phClrCgs = cgs.getSchemeClr(); - } - cs[i] = new XSLFColor(cgs, theme, phClrCgs).getColorStyle(); - fractions[i] = cgs.getPos() / 100000.f; - i++; - } - - return new GradientPaint() { - - @Override - public double getGradientAngle() { - return (gradFill.isSetLin()) - ? gradFill.getLin().getAng() / 60000.d - : 0; - } - - @Override - public ColorStyle[] getGradientColors() { - return cs; - } - - @Override - public float[] getGradientFractions() { - return fractions; - } - - @Override - public boolean isRotatedWithShape() { - return gradFill.getRotWithShape(); - } - - @Override - public GradientType getGradientType() { - if (gradFill.isSetLin()) { - return GradientType.linear; - } - - if (gradFill.isSetPath()) { - /* TODO: handle rect path */ - STPathShadeType.Enum ps = gradFill.getPath().getPath(); - if (ps == STPathShadeType.CIRCLE) { - return GradientType.circular; - } else if (ps == STPathShadeType.SHAPE) { - return GradientType.shape; - } - } - - return GradientType.linear; - } - }; + return new XSLFGradientPaint(gradFill, phClr, theme); } @SuppressWarnings("WeakerAccess") diff --git a/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFTexturePaint.java b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFTexturePaint.java new file mode 100644 index 0000000000..bc3ac3227c --- /dev/null +++ b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFTexturePaint.java @@ -0,0 +1,154 @@ +/* ==================================================================== + 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.usermodel; + +import java.awt.geom.Dimension2D; +import java.awt.geom.Point2D; +import java.io.IOException; +import java.io.InputStream; +import java.util.function.Supplier; + +import org.apache.poi.openxml4j.exceptions.InvalidFormatException; +import org.apache.poi.openxml4j.opc.PackagePart; +import org.apache.poi.openxml4j.opc.PackageRelationship; +import org.apache.poi.sl.usermodel.Insets2D; +import org.apache.poi.sl.usermodel.PaintStyle; +import org.apache.poi.util.Dimension2DDouble; +import org.apache.poi.util.Internal; +import org.apache.poi.util.Units; +import org.openxmlformats.schemas.drawingml.x2006.main.CTBlip; +import org.openxmlformats.schemas.drawingml.x2006.main.CTBlipFillProperties; +import org.openxmlformats.schemas.drawingml.x2006.main.CTRelativeRect; +import org.openxmlformats.schemas.drawingml.x2006.main.CTTileInfoProperties; +import org.openxmlformats.schemas.drawingml.x2006.main.STTileFlipMode; + +@Internal +public class XSLFTexturePaint implements PaintStyle.TexturePaint { + private final CTBlipFillProperties blipFill; + private final PackagePart parentPart; + private final CTBlip blip; + + public XSLFTexturePaint(final CTBlipFillProperties blipFill, final PackagePart parentPart) { + this.blipFill = blipFill; + this.parentPart = parentPart; + blip = blipFill.getBlip(); + } + + + private PackagePart getPart() { + try { + String blipId = blip.getEmbed(); + PackageRelationship rel = parentPart.getRelationship(blipId); + return parentPart.getRelatedPart(rel); + } catch (InvalidFormatException e) { + throw new RuntimeException(e); + } + } + + @Override + public InputStream getImageData() { + try { + return getPart().getInputStream(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @Override + public String getContentType() { + if (blip == null || !blip.isSetEmbed() || blip.getEmbed().isEmpty()) { + return null; + } + /* TOOD: map content-type */ + return getPart().getContentType(); + } + + @Override + public int getAlpha() { + return (blip.sizeOfAlphaModFixArray() > 0) + ? blip.getAlphaModFixArray(0).getAmt() + : 100000; + } + + @Override + public boolean isRotatedWithShape() { + return blipFill.isSetRotWithShape() && blipFill.getRotWithShape(); + } + + @Override + public Dimension2D getScale() { + CTTileInfoProperties tile = blipFill.getTile(); + return (tile == null) ? null : new Dimension2DDouble( + tile.isSetSx() ? tile.getSx()/100_000. : 1, + tile.isSetSy() ? tile.getSy()/100_000. : 1); + } + + @Override + public Point2D getOffset() { + CTTileInfoProperties tile = blipFill.getTile(); + return (tile == null) ? null : new Point2D.Double( + tile.isSetTx() ? Units.toPoints(tile.getTx()) : 0, + tile.isSetTy() ? Units.toPoints(tile.getTy()) : 0); + } + + @Override + public PaintStyle.FlipMode getFlipMode() { + CTTileInfoProperties tile = blipFill.getTile(); + switch (tile == null || tile.getFlip() == null ? STTileFlipMode.INT_NONE : tile.getFlip().intValue()) { + default: + case STTileFlipMode.INT_NONE: + return PaintStyle.FlipMode.NONE; + case STTileFlipMode.INT_X: + return PaintStyle.FlipMode.X; + case STTileFlipMode.INT_Y: + return PaintStyle.FlipMode.Y; + case STTileFlipMode.INT_XY: + return PaintStyle.FlipMode.XY; + } + } + + @Override + public PaintStyle.TextureAlignment getAlignment() { + CTTileInfoProperties tile = blipFill.getTile(); + return (tile == null || !tile.isSetAlgn()) ? null + : PaintStyle.TextureAlignment.fromOoxmlId(tile.getAlgn().toString()); + } + + @Override + public Insets2D getInsets() { + return getRectVal(blipFill.getSrcRect()); + } + + @Override + public Insets2D getStretch() { + return getRectVal(blipFill.isSetStretch() ? blipFill.getStretch().getFillRect() : null); + } + + private static Insets2D getRectVal(CTRelativeRect rect) { + return rect == null ? null : new Insets2D( + getRectVal(rect::isSetT, rect::getT), + getRectVal(rect::isSetL, rect::getL), + getRectVal(rect::isSetB, rect::getB), + getRectVal(rect::isSetR, rect::getR) + ); + } + + private static int getRectVal(Supplier isSet, Supplier val) { + return isSet.get() ? val.get() : 0; + } +} diff --git a/src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFFill.java b/src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFFill.java index 3b505f703e..30e3dab92c 100644 --- a/src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFFill.java +++ b/src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFFill.java @@ -228,45 +228,44 @@ public final class HSLFFill { public FillStyle getFillStyle() { - return new FillStyle() { - @Override - public PaintStyle getPaint() { - AbstractEscherOptRecord opt = shape.getEscherOptRecord(); + return this::getPaintStyle; + } - EscherSimpleProperty hitProp = HSLFShape.getEscherProperty(opt, EscherPropertyTypes.FILL__NOFILLHITTEST); - int propVal = (hitProp == null) ? 0 : hitProp.getPropertyValue(); + private PaintStyle getPaintStyle() { + AbstractEscherOptRecord opt = shape.getEscherOptRecord(); - EscherSimpleProperty masterProp = HSLFShape.getEscherProperty(opt, EscherPropertyTypes.SHAPE__MASTER); + EscherSimpleProperty hitProp = HSLFShape.getEscherProperty(opt, EscherPropertyTypes.FILL__NOFILLHITTEST); + int propVal = (hitProp == null) ? 0 : hitProp.getPropertyValue(); - if (!FILL_USE_FILLED.isSet(propVal) && masterProp != null) { - int masterId = masterProp.getPropertyValue(); - HSLFShape o = shape.getSheet().getMasterSheet().getShapes().stream().filter(s -> s.getShapeId() == masterId).findFirst().orElse(null); - return o != null ? o.getFillStyle().getPaint() : null; - } + EscherSimpleProperty masterProp = HSLFShape.getEscherProperty(opt, EscherPropertyTypes.SHAPE__MASTER); - final int fillType = getFillType(); - // TODO: fix gradient types, this mismatches with the MS-ODRAW definition ... - // need to handle (not only) the type (radial,rectangular,linear), - // the direction, e.g. top right, and bounds (e.g. for rectangular boxes) - switch (fillType) { - case FILL_SOLID: - return DrawPaint.createSolidPaint(getForegroundColor()); - case FILL_SHADE_SHAPE: - return getGradientPaint(GradientType.shape); - case FILL_SHADE_CENTER: - case FILL_SHADE_TITLE: - return getGradientPaint(GradientType.circular); - case FILL_SHADE: - case FILL_SHADE_SCALE: - return getGradientPaint(GradientType.linear); - case FILL_PICTURE: - return getTexturePaint(); - default: - LOG.log(POILogger.WARN, "unsuported fill type: " + fillType); - return null; - } - } - }; + if (!FILL_USE_FILLED.isSet(propVal) && masterProp != null) { + int masterId = masterProp.getPropertyValue(); + HSLFShape o = shape.getSheet().getMasterSheet().getShapes().stream().filter(s -> s.getShapeId() == masterId).findFirst().orElse(null); + return o != null ? o.getFillStyle().getPaint() : null; + } + + final int fillType = getFillType(); + // TODO: fix gradient types, this mismatches with the MS-ODRAW definition ... + // need to handle (not only) the type (radial,rectangular,linear), + // the direction, e.g. top right, and bounds (e.g. for rectangular boxes) + switch (fillType) { + case FILL_SOLID: + return DrawPaint.createSolidPaint(getForegroundColor()); + case FILL_SHADE_SHAPE: + return getGradientPaint(GradientType.shape); + case FILL_SHADE_CENTER: + case FILL_SHADE_TITLE: + return getGradientPaint(GradientType.circular); + case FILL_SHADE: + case FILL_SHADE_SCALE: + return getGradientPaint(GradientType.linear); + case FILL_PICTURE: + return getTexturePaint(); + default: + LOG.log(POILogger.WARN, "unsuported fill type: " + fillType); + return null; + } } private boolean isRotatedWithShape() { -- 2.39.5