1
0
mirror of https://github.com/apache/poi.git synced 2025-02-20 08:56:03 +00:00

Bug 63580 - Fix texture paint handling

git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1863600 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Andreas Beeker 2019-07-22 21:29:55 +00:00
parent 21ed5240e7
commit 58eb1a8070
10 changed files with 409 additions and 64 deletions
src
java/org/apache/poi
ooxml
java/org/apache/poi/xslf/usermodel
testcases/org/apache/poi/xslf/usermodel
scratchpad/src/org/apache/poi/hslf/usermodel

@ -18,13 +18,13 @@
package org.apache.poi.sl.draw; package org.apache.poi.sl.draw;
import java.awt.Color; import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics2D; import java.awt.Graphics2D;
import java.awt.LinearGradientPaint; import java.awt.LinearGradientPaint;
import java.awt.Paint; import java.awt.Paint;
import java.awt.RadialGradientPaint; import java.awt.RadialGradientPaint;
import java.awt.Shape; import java.awt.Shape;
import java.awt.geom.AffineTransform; import java.awt.geom.AffineTransform;
import java.awt.geom.Dimension2D;
import java.awt.geom.Point2D; import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D; import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
@ -38,11 +38,13 @@ import java.util.function.BiFunction;
import org.apache.poi.sl.usermodel.AbstractColorStyle; import org.apache.poi.sl.usermodel.AbstractColorStyle;
import org.apache.poi.sl.usermodel.ColorStyle; import org.apache.poi.sl.usermodel.ColorStyle;
import org.apache.poi.sl.usermodel.PaintStyle; import org.apache.poi.sl.usermodel.PaintStyle;
import org.apache.poi.sl.usermodel.PaintStyle.FlipMode;
import org.apache.poi.sl.usermodel.PaintStyle.GradientPaint; import org.apache.poi.sl.usermodel.PaintStyle.GradientPaint;
import org.apache.poi.sl.usermodel.PaintStyle.PaintModifier; import org.apache.poi.sl.usermodel.PaintStyle.PaintModifier;
import org.apache.poi.sl.usermodel.PaintStyle.SolidPaint; import org.apache.poi.sl.usermodel.PaintStyle.SolidPaint;
import org.apache.poi.sl.usermodel.PaintStyle.TexturePaint; import org.apache.poi.sl.usermodel.PaintStyle.TexturePaint;
import org.apache.poi.sl.usermodel.PlaceableShape; import org.apache.poi.sl.usermodel.PlaceableShape;
import org.apache.poi.util.Dimension2DDouble;
import org.apache.poi.util.POILogFactory; import org.apache.poi.util.POILogFactory;
import org.apache.poi.util.POILogger; import org.apache.poi.util.POILogger;
@ -243,11 +245,6 @@ public class DrawPaint {
ImageRenderer renderer = DrawPictureShape.getImageRenderer(graphics, contentType); ImageRenderer renderer = DrawPictureShape.getImageRenderer(graphics, contentType);
int alpha = fill.getAlpha();
if (0 <= alpha && alpha < 100000) {
renderer.setAlpha(alpha/100000.f);
}
// TODO: handle tile settings, currently the pattern is always streched 100% in height/width // TODO: handle tile settings, currently the pattern is always streched 100% in height/width
Rectangle2D textAnchor = shape.getAnchor(); Rectangle2D textAnchor = shape.getAnchor();
@ -258,25 +255,67 @@ public class DrawPaint {
renderer.loadImage(is, contentType); renderer.loadImage(is, contentType);
final BufferedImage image; int alpha = fill.getAlpha();
switch (contentType) { if (0 <= alpha && alpha < 100000) {
case "image/x-wmf": renderer.setAlpha(alpha/100000.f);
case "image/x-emf":
// don't rely on wmf dimensions, use dimension of anchor
// TODO: check pixels vs. points for image dimension
image = renderer.getImage(new Dimension((int)textAnchor.getWidth(), (int)textAnchor.getHeight()));
break;
default:
image = renderer.getImage();
break;
} }
Dimension2D imgDim = renderer.getDimension();
if ("image/x-wmf".contains(contentType)) {
// don't rely on wmf dimensions, use dimension of anchor
// TODO: check pixels vs. points for image dimension
imgDim = new Dimension2DDouble(textAnchor.getWidth(), textAnchor.getHeight());
}
BufferedImage image = renderer.getImage(imgDim);
if(image == null) { if(image == null) {
LOG.log(POILogger.ERROR, "Can't load image data"); LOG.log(POILogger.ERROR, "Can't load image data");
return TRANSPARENT; return TRANSPARENT;
} }
return new java.awt.TexturePaint(image, textAnchor); double flipX = 1, flipY = 1;
final FlipMode flip = fill.getFlipMode();
if (flip != null && flip != FlipMode.NONE) {
final int width = image.getWidth(), height = image.getHeight();
switch (flip) {
case X:
flipX = 2;
break;
case Y:
flipY = 2;
break;
case XY:
flipX = 2;
flipY = 2;
break;
}
final BufferedImage img = new BufferedImage((int)(width*flipX), (int)(height*flipY), BufferedImage.TYPE_INT_ARGB);
Graphics2D g = img.createGraphics();
g.drawImage(image, 0, 0, null);
switch (flip) {
case X:
g.drawImage(image, 2*width, 0, -width, height, null);
break;
case Y:
g.drawImage(image, 0, 2*height, width, -height, null);
break;
case XY:
g.drawImage(image, 2*width, 0, -width, height, null);
g.drawImage(image, 0, 2*height, width, -height, null);
g.drawImage(image, 2*width, 2*height, -width, -height, null);
break;
}
g.dispose();
image = img;
}
Shape s = (Shape)graphics.getRenderingHint(Drawable.GRADIENT_SHAPE);
// TODO: check why original bitmaps scale/behave differently to vector based images
return new DrawTexturePaint(image, s, fill, flipX, flipY, renderer instanceof BitmapImageRenderer);
} catch (IOException e) { } catch (IOException e) {
LOG.log(POILogger.ERROR, "Can't load image data - using transparent color", e); LOG.log(POILogger.ERROR, "Can't load image data - using transparent color", e);
return TRANSPARENT; return TRANSPARENT;

@ -20,6 +20,7 @@ package org.apache.poi.sl.draw;
import java.awt.Dimension; import java.awt.Dimension;
import java.awt.Graphics2D; import java.awt.Graphics2D;
import java.awt.Insets; import java.awt.Insets;
import java.awt.Paint;
import java.awt.geom.Rectangle2D; import java.awt.geom.Rectangle2D;
import java.io.IOException; import java.io.IOException;
@ -114,7 +115,12 @@ public class DrawPictureShape extends DrawSimpleShape {
// falling back to BitmapImageRenderer, at least it gracefully handles invalid images // falling back to BitmapImageRenderer, at least it gracefully handles invalid images
return bir; return bir;
} }
@Override
protected Paint getFillPaint(Graphics2D graphics) {
return null;
}
@Override @Override
protected PictureShape<?,?> getShape() { protected PictureShape<?,?> getShape() {
return (PictureShape<?,?>)shape; return (PictureShape<?,?>)shape;

@ -24,12 +24,14 @@ import java.awt.Color;
import java.awt.Graphics2D; import java.awt.Graphics2D;
import java.awt.Paint; import java.awt.Paint;
import java.awt.geom.AffineTransform; import java.awt.geom.AffineTransform;
import java.awt.geom.Area;
import java.awt.geom.Ellipse2D; import java.awt.geom.Ellipse2D;
import java.awt.geom.Path2D; import java.awt.geom.Path2D;
import java.awt.geom.Rectangle2D; import java.awt.geom.Rectangle2D;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.function.Consumer;
import org.apache.poi.sl.draw.geom.Context; import org.apache.poi.sl.draw.geom.Context;
import org.apache.poi.sl.draw.geom.CustomGeometry; import org.apache.poi.sl.draw.geom.CustomGeometry;
@ -38,6 +40,8 @@ import org.apache.poi.sl.draw.geom.Path;
import org.apache.poi.sl.usermodel.LineDecoration; import org.apache.poi.sl.usermodel.LineDecoration;
import org.apache.poi.sl.usermodel.LineDecoration.DecorationShape; import org.apache.poi.sl.usermodel.LineDecoration.DecorationShape;
import org.apache.poi.sl.usermodel.LineDecoration.DecorationSize; import org.apache.poi.sl.usermodel.LineDecoration.DecorationSize;
import org.apache.poi.sl.usermodel.PaintStyle;
import org.apache.poi.sl.usermodel.PaintStyle.PaintModifier;
import org.apache.poi.sl.usermodel.PaintStyle.SolidPaint; import org.apache.poi.sl.usermodel.PaintStyle.SolidPaint;
import org.apache.poi.sl.usermodel.Shadow; import org.apache.poi.sl.usermodel.Shadow;
import org.apache.poi.sl.usermodel.SimpleShape; import org.apache.poi.sl.usermodel.SimpleShape;
@ -58,9 +62,8 @@ public class DrawSimpleShape extends DrawShape {
return; return;
} }
DrawPaint drawPaint = DrawFactory.getInstance(graphics).getPaint(getShape()); Paint fill = getFillPaint(graphics);
Paint fill = drawPaint.getPaint(graphics, getShape().getFillStyle().getPaint()); Paint line = getLinePaint(graphics);
Paint line = drawPaint.getPaint(graphics, getShape().getStrokeStyle().getPaint());
BasicStroke stroke = getStroke(); // the stroke applies both to the shadow and the shape BasicStroke stroke = getStroke(); // the stroke applies both to the shadow and the shape
graphics.setStroke(stroke); graphics.setStroke(stroke);
@ -71,17 +74,29 @@ public class DrawSimpleShape extends DrawShape {
// then fill the shape interior // then fill the shape interior
if (fill != null) { if (fill != null) {
final Path2D area = new Path2D.Double();
graphics.setRenderingHint(Drawable.GRADIENT_SHAPE, area);
Consumer<PaintModifier> fun = (pm) -> fillArea(graphics, pm, area);
PaintModifier pm = null;
for (Outline o : elems) { for (Outline o : elems) {
if (o.getPath().isFilled()){ Path path = o.getPath();
Paint fillMod = drawPaint.getPaint(graphics, getShape().getFillStyle().getPaint(), o.getPath().getFill()); if (path.isFilled()) {
if (fillMod != null) { PaintModifier pmOld = pm;
graphics.setPaint(fillMod); pm = path.getFill();
java.awt.Shape s = o.getOutline(); if (pmOld != null && pmOld != pm) {
graphics.setRenderingHint(Drawable.GRADIENT_SHAPE, s); fun.accept(pmOld);
fillPaintWorkaround(graphics, s); area.reset();
} else {
area.append(o.getOutline(), false);
} }
} }
} }
if (area.getCurrentPoint() != null) {
fun.accept(pm);
}
} }
// then draw any content within this shape (text, image, etc.) // then draw any content within this shape (text, image, etc.)
@ -104,6 +119,30 @@ public class DrawSimpleShape extends DrawShape {
drawDecoration(graphics, line, stroke); drawDecoration(graphics, line, stroke);
} }
private void fillArea(Graphics2D graphics, PaintModifier pm, Path2D area) {
final SimpleShape<?, ?> ss = getShape();
final PaintStyle ps = ss.getFillStyle().getPaint();
final DrawPaint drawPaint = DrawFactory.getInstance(graphics).getPaint(ss);
final Paint fillMod = drawPaint.getPaint(graphics, ps, pm);
if (fillMod != null) {
graphics.setPaint(fillMod);
fillPaintWorkaround(graphics, area);
}
}
protected Paint getFillPaint(Graphics2D graphics) {
final PaintStyle ps = getShape().getFillStyle().getPaint();
DrawPaint drawPaint = DrawFactory.getInstance(graphics).getPaint(getShape());
return drawPaint.getPaint(graphics, ps);
}
protected Paint getLinePaint(Graphics2D graphics) {
final PaintStyle ps = getShape().getFillStyle().getPaint();
DrawPaint drawPaint = DrawFactory.getInstance(graphics).getPaint(getShape());
return drawPaint.getPaint(graphics, getShape().getStrokeStyle().getPaint());
}
protected void drawDecoration(Graphics2D graphics, Paint line, BasicStroke stroke) { protected void drawDecoration(Graphics2D graphics, Paint line, BasicStroke stroke) {
if(line == null) { if(line == null) {
return; return;

@ -0,0 +1,149 @@
/* ====================================================================
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.sl.draw;
import java.awt.PaintContext;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.Shape;
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 org.apache.poi.sl.usermodel.PaintStyle;
/* package */ class DrawTexturePaint extends java.awt.TexturePaint {
private final PaintStyle.TexturePaint fill;
private final Shape shape;
private final double flipX, flipY;
private final boolean isBitmapSrc;
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()));
this.fill = fill;
this.shape = shape;
this.flipX = flipX;
this.flipY = flipY;
this.isBitmapSrc = isBitmapSrc;
}
@Override
public PaintContext createContext(ColorModel cm, Rectangle deviceBounds, Rectangle2D userBounds, AffineTransform xform, RenderingHints hints) {
final double usr_w, usr_h;
if (fill.isRotatedWithShape() || shape == null) {
usr_w = userBounds.getWidth();
usr_h = userBounds.getHeight();
xform.translate(userBounds.getX(), userBounds.getY());
} else {
AffineTransform transform = new AffineTransform(xform);
// Eliminate any post-translation
transform.preConcatenate(AffineTransform.getTranslateInstance(
-transform.getTranslateX(), -transform.getTranslateY()));
Point2D p1 = new Point2D.Double(1, 0);
p1 = transform.transform(p1,p1);
final double rad = Math.atan2(p1.getY(),p1.getX());
if (rad != 0) {
xform.rotate(-rad, userBounds.getCenterX(), userBounds.getCenterY());
}
// 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();
xform.translate(newBounds.getX(), newBounds.getY());
}
final Dimension2D scale = fill.getScale();
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;
// Alignment happens after the scaling but before any offset.
PaintStyle.TextureAlignment ta = fill.getAlignment();
final double alg_x, alg_y;
switch (ta == null ? PaintStyle.TextureAlignment.TOP_LEFT : ta) {
case BOTTOM:
alg_x = (usr_w-img_w)/2;
alg_y = usr_h-img_h;
break;
case BOTTOM_LEFT:
alg_x = 0;
alg_y = usr_h-img_h;
break;
case BOTTOM_RIGHT:
alg_x = usr_w-img_w;
alg_y = usr_h-img_h;
break;
case CENTER:
alg_x = (usr_w-img_w)/2;
alg_y = (usr_h-img_h)/2;
break;
case LEFT:
alg_x = 0;
alg_y = (usr_h-img_h)/2;
break;
case RIGHT:
alg_x = usr_w-img_w;
alg_y = (usr_h-img_h)/2;
break;
case TOP:
alg_x = (usr_w-img_w)/2;
alg_y = 0;
break;
default:
case TOP_LEFT:
alg_x = 0;
alg_y = 0;
break;
case TOP_RIGHT:
alg_x = usr_w-img_w;
alg_y = 0;
break;
}
xform.translate(alg_x, alg_y);
// Apply additional horizontal/vertical offset after alignment.
// Values are as percentages.
// TODO: apply scaling of drawing context to offset
final Point2D offset = fill.getOffset();
if (offset != null) {
xform.translate(offset.getX(),offset.getY());
}
if (scale != null) {
xform.scale(scale.getWidth()/(isBitmapSrc ? flipX : 1.),scale.getHeight()/(isBitmapSrc ? flipY : 1.));
}
return super.createContext(cm, deviceBounds, userBounds, xform, hints);
}
}

@ -99,7 +99,7 @@ public interface ImageRenderer {
void loadImage(byte[] data, String contentType) throws IOException; void loadImage(byte[] data, String contentType) throws IOException;
/** /**
* @return the dimension of the buffered image * @return the dimension of the buffered image in pixel
*/ */
Dimension2D getDimension(); Dimension2D getDimension();

@ -17,6 +17,8 @@
package org.apache.poi.sl.usermodel; package org.apache.poi.sl.usermodel;
import java.awt.geom.Dimension2D;
import java.awt.geom.Point2D;
import java.io.InputStream; import java.io.InputStream;
@ -41,6 +43,45 @@ public interface PaintStyle {
DARKEN_LESS DARKEN_LESS
} }
enum FlipMode {
/** not flipped/mirrored */
NONE,
/** flipped/mirrored/duplicated along the x axis */
X,
/** flipped/mirrored/duplicated along the y axis */
Y,
/** flipped/mirrored/duplicated along the x and y axis */
XY
}
enum TextureAlignment {
BOTTOM("b"),
BOTTOM_LEFT("bl"),
BOTTOM_RIGHT("br"),
CENTER("ctr"),
LEFT("l"),
RIGHT("r"),
TOP("t"),
TOP_LEFT("tl"),
TOP_RIGHT("tr");
private final String ooxmlId;
TextureAlignment(String ooxmlId) {
this.ooxmlId = ooxmlId;
}
public static TextureAlignment fromOoxmlId(String ooxmlId) {
for (TextureAlignment ta : values()) {
if (ta.ooxmlId.equals(ooxmlId)) {
return ta;
}
}
return null;
}
}
interface SolidPaint extends PaintStyle { interface SolidPaint extends PaintStyle {
ColorStyle getSolidColor(); ColorStyle getSolidColor();
} }
@ -73,5 +114,29 @@ public interface PaintStyle {
* @return the alpha mask in percents [0..100000] * @return the alpha mask in percents [0..100000]
*/ */
int getAlpha(); int getAlpha();
/**
* @return {@code true}, if the rotation of the shape is also applied to the texture paint
*/
default boolean isRotatedWithShape() { return true; }
/**
* @return the dimensions of the tiles in percent of the shape dimensions
* or {@code null} if no scaling is applied
*/
default Dimension2D getScale() { return null; }
/**
* @return the offset of the tiles in points or {@code null} if there's no offset
*/
default Point2D getOffset() { return null; }
/**
* @return the flip/mirroring/duplication mode
*/
default FlipMode getFlipMode() { return FlipMode.NONE; }
default TextureAlignment getAlignment() { return null; }
} }
} }

@ -139,7 +139,7 @@ public class Units {
return (int)Math.rint(points); return (int)Math.rint(points);
} }
public static double pixelToPoints(int pixel) { public static double pixelToPoints(double pixel) {
double points = pixel; double points = pixel;
points *= POINT_DPI; points *= POINT_DPI;
points /= PIXEL_DPI; points /= PIXEL_DPI;
@ -152,6 +152,12 @@ public class Units {
return new Dimension2DDouble(width, height); return new Dimension2DDouble(width, height);
} }
public static Dimension2D pixelToPoints(Dimension2D pointsDim) {
double width = pointsDim.getWidth() * POINT_DPI / PIXEL_DPI;
double height = pointsDim.getHeight() * POINT_DPI / PIXEL_DPI;
return new Dimension2DDouble(width, height);
}
public static int charactersToEMU(double characters) { public static int charactersToEMU(double characters) {
return (int) characters * EMU_PER_CHARACTER; return (int) characters * EMU_PER_CHARACTER;
} }

@ -20,6 +20,8 @@
package org.apache.poi.xslf.usermodel; package org.apache.poi.xslf.usermodel;
import java.awt.Graphics2D; import java.awt.Graphics2D;
import java.awt.geom.Dimension2D;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D; import java.awt.geom.Rectangle2D;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
@ -41,7 +43,9 @@ import org.apache.poi.sl.usermodel.PlaceholderDetails;
import org.apache.poi.sl.usermodel.Shape; import org.apache.poi.sl.usermodel.Shape;
import org.apache.poi.sl.usermodel.SimpleShape; import org.apache.poi.sl.usermodel.SimpleShape;
import org.apache.poi.util.Beta; import org.apache.poi.util.Beta;
import org.apache.poi.util.Dimension2DDouble;
import org.apache.poi.util.Internal; import org.apache.poi.util.Internal;
import org.apache.poi.util.Units;
import org.apache.poi.xslf.model.PropertyFetcher; import org.apache.poi.xslf.model.PropertyFetcher;
import org.apache.poi.xslf.usermodel.XSLFPropertiesDelegate.XSLFFillProperties; import org.apache.poi.xslf.usermodel.XSLFPropertiesDelegate.XSLFFillProperties;
import org.apache.xmlbeans.XmlCursor; import org.apache.xmlbeans.XmlCursor;
@ -58,7 +62,9 @@ import org.openxmlformats.schemas.drawingml.x2006.main.CTShapeStyle;
import org.openxmlformats.schemas.drawingml.x2006.main.CTSolidColorFillProperties; import org.openxmlformats.schemas.drawingml.x2006.main.CTSolidColorFillProperties;
import org.openxmlformats.schemas.drawingml.x2006.main.CTStyleMatrix; import org.openxmlformats.schemas.drawingml.x2006.main.CTStyleMatrix;
import org.openxmlformats.schemas.drawingml.x2006.main.CTStyleMatrixReference; 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.STPathShadeType;
import org.openxmlformats.schemas.drawingml.x2006.main.STTileFlipMode;
import org.openxmlformats.schemas.presentationml.x2006.main.CTBackgroundProperties; import org.openxmlformats.schemas.presentationml.x2006.main.CTBackgroundProperties;
import org.openxmlformats.schemas.presentationml.x2006.main.CTPicture; import org.openxmlformats.schemas.presentationml.x2006.main.CTPicture;
import org.openxmlformats.schemas.presentationml.x2006.main.CTPlaceholder; import org.openxmlformats.schemas.presentationml.x2006.main.CTPlaceholder;
@ -441,6 +447,50 @@ public abstract class XSLFShape implements Shape<XSLFShape,XSLFTextParagraph> {
? blip.getAlphaModFixArray(0).getAmt() ? blip.getAlphaModFixArray(0).getAmt()
: 100000; : 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 ? 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());
}
}; };
} }

@ -22,12 +22,11 @@ package org.apache.poi.xslf.usermodel;
import static org.junit.Assume.assumeFalse; import static org.junit.Assume.assumeFalse;
import java.io.File; import java.io.File;
import java.io.FileFilter;
import java.util.Collection; import java.util.Collection;
import java.util.Locale; import java.util.Locale;
import java.util.Set; import java.util.function.Function;
import java.util.TreeSet; import java.util.stream.Collectors;
import java.util.regex.Pattern; import java.util.stream.Stream;
import org.apache.poi.POIDataSamples; import org.apache.poi.POIDataSamples;
import org.apache.poi.xslf.util.PPTX2PNG; import org.apache.poi.xslf.util.PPTX2PNG;
@ -66,29 +65,15 @@ public class TestPPTX2PNG {
@Parameter(value = 0) @Parameter(value = 0)
public String pptFile; public String pptFile;
@SuppressWarnings("ConstantConditions")
@Parameters(name="{0}") @Parameters(name="{0}")
public static Collection<String> data() { public static Collection<String> data() {
final Set<String> data = new TreeSet<>(); Function<String, Stream<String>> fun = (basedir == null) ? Stream::of :
for (String f : files.split(", ?")) { (f) -> Stream.of(basedir.listFiles(p -> p.getName().matches(f))).map(File::getName);
if (basedir == null) {
data.add(f); return Stream.of(files.split(", ?")).flatMap(fun).collect(Collectors.toList());
} else {
final Pattern p = Pattern.compile(f);
basedir.listFiles(new FileFilter(){
public boolean accept(File pathname) {
String name = pathname.getName();
if (p.matcher(name).matches()) {
data.add(name);
}
return false;
}
});
}
}
return data;
} }
@Test @Test
public void render() throws Exception { public void render() throws Exception {
assumeFalse("ignore HSLF / .ppt files in no-scratchpad run", xslfOnly && pptFile.toLowerCase(Locale.ROOT).endsWith("ppt")); assumeFalse("ignore HSLF / .ppt files in no-scratchpad run", xslfOnly && pptFile.toLowerCase(Locale.ROOT).endsWith("ppt"));
@ -98,6 +83,7 @@ public class TestPPTX2PNG {
"-slide", "-1", // -1 for all "-slide", "-1", // -1 for all
"-outdir", new File("build/tmp/").getCanonicalPath(), "-outdir", new File("build/tmp/").getCanonicalPath(),
"-outpat", "${basename}-${slideno}-${ext}.${format}", "-outpat", "${basename}-${slideno}-${ext}.${format}",
"-scale", "1.333333333",
"-quiet", "-quiet",
(basedir == null ? samples.getFile(pptFile) : new File(basedir, pptFile)).getAbsolutePath() (basedir == null ? samples.getFile(pptFile) : new File(basedir, pptFile)).getAbsolutePath()
}; };

@ -254,20 +254,20 @@ public final class HSLFFill {
}; };
} }
private boolean isRotatedWithShape() {
// NOFILLHITTEST can be in the normal escher opt record but also in the tertiary record
// the extended bit fields seem to be in the second
AbstractEscherOptRecord opt = shape.getEscherChild(RecordTypes.EscherUserDefined);
EscherSimpleProperty p = HSLFShape.getEscherProperty(opt, EscherProperties.FILL__NOFILLHITTEST);
int propVal = (p == null) ? 0 : p.getPropertyValue();
return FILL_USE_USE_SHAPE_ANCHOR.isSet(propVal) && FILL_USE_SHAPE_ANCHOR.isSet(propVal);
}
private GradientPaint getGradientPaint(final GradientType gradientType) { private GradientPaint getGradientPaint(final GradientType gradientType) {
AbstractEscherOptRecord opt = shape.getEscherOptRecord(); AbstractEscherOptRecord opt = shape.getEscherOptRecord();
final EscherArrayProperty ep = HSLFShape.getEscherProperty(opt, EscherProperties.FILL__SHADECOLORS); final EscherArrayProperty ep = HSLFShape.getEscherProperty(opt, EscherProperties.FILL__SHADECOLORS);
final int colorCnt = (ep == null) ? 0 : ep.getNumberOfElementsInArray(); final int colorCnt = (ep == null) ? 0 : ep.getNumberOfElementsInArray();
// NOFILLHITTEST can be in the normal escher opt record but also in the tertiary record
// the extended bit fields seem to be in the second
opt = shape.getEscherChild(RecordTypes.EscherUserDefined);
EscherSimpleProperty p = HSLFShape.getEscherProperty(opt, EscherProperties.FILL__NOFILLHITTEST);
int propVal = (p == null) ? 0 : p.getPropertyValue();
final boolean rotateWithShape = FILL_USE_USE_SHAPE_ANCHOR.isSet(propVal) && FILL_USE_SHAPE_ANCHOR.isSet(propVal);
return new GradientPaint() { return new GradientPaint() {
@Override @Override
public double getGradientAngle() { public double getGradientAngle() {
@ -319,7 +319,7 @@ public final class HSLFFill {
@Override @Override
public boolean isRotatedWithShape() { public boolean isRotatedWithShape() {
return rotateWithShape; return HSLFFill.this.isRotatedWithShape();
} }
@Override @Override
@ -350,6 +350,11 @@ public final class HSLFFill {
public int getAlpha() { public int getAlpha() {
return (int)(shape.getAlpha(EscherProperties.FILL__FILLOPACITY)*100000.0); return (int)(shape.getAlpha(EscherProperties.FILL__FILLOPACITY)*100000.0);
} }
@Override
public boolean isRotatedWithShape() {
return HSLFFill.this.isRotatedWithShape();
}
}; };
} }