mirror of https://github.com/apache/poi.git
#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:
parent
b3f53ff0bc
commit
0b0bcce7ac
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
*
|
||||
|
|
|
@ -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; }
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue