#64844 - Incorrect sizes of images in SVG

git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1889686 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Andreas Beeker 2021-05-08 21:56:06 +00:00
parent b3f53ff0bc
commit 0b0bcce7ac
12 changed files with 329 additions and 27 deletions

View File

@ -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) {

View File

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

View File

@ -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")

View File

@ -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(

View File

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

View File

@ -44,6 +44,7 @@ import org.apache.poi.sl.usermodel.PaintStyle.GradientPaint;
import org.apache.poi.sl.usermodel.PaintStyle.GradientPaint.GradientType;
import org.apache.poi.sl.usermodel.PaintStyle.TexturePaint;
import org.apache.poi.sl.usermodel.PictureData;
import org.apache.poi.sl.usermodel.Shape;
import org.apache.poi.util.BitField;
import org.apache.poi.util.BitFieldFactory;
import org.apache.poi.util.LittleEndian;
@ -137,28 +138,28 @@ public final class HSLFFill {
* The default value for this property is 0x0.
*/
private static final BitField FILL_USE_HIT_TEST_FILL = BitFieldFactory.getInstance(0x00080000);
/**
* A bit that specifies whether the fillShape bit is set.
* A value of 0x0 specifies that the fillShape MUST be ignored.
* The default value for this property is 0x0.
*/
private static final BitField FILL_USE_FILL_SHAPE = BitFieldFactory.getInstance(0x00040000);
/**
* A bit that specifies whether the fillUseRect bit is set.
* A value of 0x0 specifies that the fillUseRect MUST be ignored.
* The default value for this property is 0x0.
*/
private static final BitField FILL_USE_FILL_USE_RECT = BitFieldFactory.getInstance(0x00020000);
/**
* A bit that specifies whether the fNoFillHitTest bit is set.
* A value of 0x0 specifies that the fNoFillHitTest MUST be ignored.
* The default value for this property is 0x0.
*/
private static final BitField FILL_USE_NO_FILL_HIT_TEST = BitFieldFactory.getInstance(0x00010000);
/**
* A bit that specifies how to recolor a picture fill. If this bit is set to 0x1, the pictureFillCrMod
* property of the picture fill is used for recoloring. If this bit is set to 0x0, the fillCrMod property,
@ -167,14 +168,14 @@ public final class HSLFFill {
* The default value for this property is 0x0.
*/
private static final BitField FILL_RECOLOR_FILL_AS_PICTURE = BitFieldFactory.getInstance(0x00000040);
/**
* A bit that specifies whether the fill is rotated with the shape.
* If UseUseShapeAnchor equals 0x0, this value MUST be ignored.
* The default value for this property is 0x0.
*/
private static final BitField FILL_USE_SHAPE_ANCHOR = BitFieldFactory.getInstance(0x00000020);
/**
* A bit that specifies whether the fill is rendered if the shape is a 2-D shape.
* If this bit is set to 0x1, the fill of this shape is rendered based on the properties of the Fill Style
@ -182,14 +183,14 @@ public final class HSLFFill {
* If UseFilled is 0x0, this value MUST be ignored. The default value for this property is 0x1.
*/
private static final BitField FILL_FILLED = BitFieldFactory.getInstance(0x00000010);
/**
* A bit that specifies whether this fill will be hit tested.
* If UsefHitTestFill equals 0x0, this value MUST be ignored.
* The default value for this property is 0x1.
*/
private static final BitField FILL_HIT_TEST_FILL = BitFieldFactory.getInstance(0x00000008);
/**
* A bit that specifies how the fill is aligned. If this bit is set to 0x1, the fill is
* aligned relative to the shape so that it moves with the shape. If this bit is set to 0x0,
@ -197,7 +198,7 @@ public final class HSLFFill {
* The default value for this property is 0x1.
*/
private static final BitField FILL_FILL_SHAPE = BitFieldFactory.getInstance(0x00000004);
/**
* A bit that specifies whether to use the rectangle specified by the fillRectLeft, fillRectRight,
* fillRectTop, and fillRectBottom properties, rather than the bounding rectangle of the shape,
@ -205,7 +206,7 @@ public final class HSLFFill {
* The default value for this property is 0x0.
*/
private static final BitField FILL_FILL_USE_RECT = BitFieldFactory.getInstance(0x00000002);
/**
* A bit that specifies whether this shape will be hit tested as though it were filled.
* If UsefNoFillHitTest equals 0x0, this value MUST be ignored.
@ -270,7 +271,7 @@ public final class HSLFFill {
return null;
}
}
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
@ -351,11 +352,11 @@ public final class HSLFFill {
public ColorStyle[] getGradientColors() {
return colors.stream().map(this::wrapColor).toArray(ColorStyle[]::new);
}
private ColorStyle wrapColor(Color col) {
return (col == null) ? null : DrawPaint.createSolidPaint(col).getSolidColor();
}
@Override
public float[] getGradientFractions() {
float[] frc = new float[fractions.size()];
@ -364,19 +365,19 @@ public final class HSLFFill {
}
return frc;
}
@Override
public boolean isRotatedWithShape() {
return HSLFFill.this.isRotatedWithShape();
}
@Override
public GradientType getGradientType() {
return gradientType;
}
};
}
private TexturePaint getTexturePaint() {
final HSLFPictureData pd = getPictureData();
if (pd == null) {
@ -403,6 +404,11 @@ public final class HSLFFill {
public boolean isRotatedWithShape() {
return HSLFFill.this.isRotatedWithShape();
}
@Override
public Shape getShape() {
return shape;
}
};
}
@ -503,7 +509,7 @@ public final class HSLFFill {
HSLFShape.setEscherProperty(opt, EscherPropertyTypes.FILL__FILLOPACITY, alphaFP);
}
}
EscherSimpleProperty p = HSLFShape.getEscherProperty(opt, EscherPropertyTypes.FILL__NOFILLHITTEST);
int propVal = (p == null) ? 0 : p.getPropertyValue();
propVal = FILL_FILLED.setBoolean(propVal, color != null);

View File

@ -52,6 +52,9 @@ public class BitmapImageRenderer implements ImageRenderer {
private static final Logger LOG = LogManager.getLogger(BitmapImageRenderer.class);
protected BufferedImage img;
private boolean doCache;
private byte[] cachedImage;
private String cachedContentType;
@Override
public boolean canRender(String contentType) {
@ -68,11 +71,26 @@ public class BitmapImageRenderer implements ImageRenderer {
@Override
public void loadImage(InputStream data, String contentType) throws IOException {
img = readImage(data, contentType);
InputStream in = data;
if (doCache) {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
IOUtils.copy(data, bos);
cachedImage = bos.toByteArray();
cachedContentType = contentType;
in = new ByteArrayInputStream(cachedImage);
}
img = readImage(in, contentType);
}
@Override
public void loadImage(byte[] data, String contentType) throws IOException {
if (data == null) {
return;
}
if (doCache) {
cachedImage = data.clone();
cachedContentType = contentType;
}
img = readImage(new ByteArrayInputStream(data), contentType);
}
@ -331,4 +349,23 @@ public class BitmapImageRenderer implements ImageRenderer {
public Rectangle2D getNativeBounds() {
return new Rectangle2D.Double(0, 0, img.getWidth(), img.getHeight());
}
@Override
public void setCacheInput(boolean enable) {
doCache = enable;
if (!enable) {
cachedContentType = null;
cachedImage = null;
}
}
@Override
public byte[] getCachedImage() {
return cachedImage;
}
@Override
public String getCachedContentType() {
return cachedContentType;
}
}

View File

@ -272,6 +272,8 @@ public class DrawPaint {
return TRANSPARENT;
}
Boolean cacheImage = (Boolean)graphics.getRenderingHint(Drawable.CACHE_IMAGE_SOURCE);
renderer.setCacheInput(cacheImage != null && cacheImage);
renderer.loadImage(is, contentType);
int alpha = fill.getAlpha();
@ -336,7 +338,7 @@ public class DrawPaint {
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);
return new DrawTexturePaint(renderer, image, s, fill, flipX, flipY, renderer instanceof BitmapImageRenderer);
} catch (IOException e) {
LOG.atError().withThrowable(e).log("Can't load image data - using transparent color");
return TRANSPARENT;

View File

@ -34,8 +34,11 @@ 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;
import org.apache.poi.util.Internal;
/* package */ class DrawTexturePaint extends java.awt.TexturePaint {
@Internal
public class DrawTexturePaint extends java.awt.TexturePaint {
private final ImageRenderer imgRdr;
private final PaintStyle.TexturePaint fill;
private final Shape shape;
private final double flipX, flipY;
@ -44,9 +47,10 @@ import org.apache.poi.util.Dimension2DDouble;
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) {
DrawTexturePaint(ImageRenderer imgRdr, 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.imgRdr = imgRdr;
this.fill = fill;
this.shape = shape;
this.flipX = flipX;
@ -238,4 +242,16 @@ import org.apache.poi.util.Dimension2DDouble;
return xform;
}
public ImageRenderer getImageRenderer() {
return imgRdr;
}
public PaintStyle.TexturePaint getFill() {
return fill;
}
public Shape getAwtShape() {
return shape;
}
}

View File

@ -50,6 +50,7 @@ public interface Drawable {
case 13: return "BUFFERED_IMAGE";
case 14: return "DEFAULT_CHARSET";
case 15: return "EMF_FORCE_HEADER_BOUNDS";
case 16: return "CACHE_IMAGE_SOURCE";
default: return "UNKNOWN_ID "+intKey();
}
}
@ -166,6 +167,13 @@ public interface Drawable {
*/
DrawableHint EMF_FORCE_HEADER_BOUNDS = new DrawableHint(15);
/**
* A boolean value to instruct the bitmap image renderer to keep the original image bytes.
* Defaults to {@code false} if unset.
*/
DrawableHint CACHE_IMAGE_SOURCE = new DrawableHint(16);
/**
* Apply 2-D transforms before drawing this shape. This includes rotation and flipping.
*

View File

@ -162,4 +162,23 @@ public interface ImageRenderer {
* @param defaultCharset the default charset
*/
default void setDefaultCharset(Charset defaultCharset) {}
/**
* Dis-/Enables caching of input data for later retrieval.
* Opposed to {@link #getImage()}, which returns a {@link BufferedImage}, the cached image can be later
* used to embedded the original, unmodified data
* @param enable dis-/enables caching - this is an optional operation. {@code false} removes already cached data
*/
default void setCacheInput(boolean enable) {}
/**
* @return the cached image data
*/
default byte[] getCachedImage() { return null; }
/**
* @return the cached content type
*/
default String getCachedContentType() { return null; }
}

View File

@ -163,7 +163,7 @@ public interface PaintStyle {
/**
* The stretch specifies the edges of a fill rectangle.<p>
*
* Each edge of the fill rectangle is defined by a perentage offset from the corresponding edge
* Each edge of the fill rectangle is defined by a percentage offset from the corresponding edge
* of the picture's bounding box. A positive percentage specifies an inset and a negative percentage
* specifies an outset.<p>
*
@ -183,5 +183,9 @@ public interface PaintStyle {
return null;
}
/**
* @return the shape this texture paint is applied to
*/
Shape getShape();
}
}