#60656 - Support export file that contains emf and render it correctly

git-svn-id: https://svn.apache.org/repos/asf/poi/branches/hemf@1845291 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Andreas Beeker 2018-10-30 22:35:45 +00:00
parent 29587e78df
commit 0a84f6881f
16 changed files with 410 additions and 231 deletions

View File

@ -26,7 +26,6 @@ public class HemfDrawProperties extends HwmfDrawProperties {
/** Path for path bracket operations */
protected Path2D path = null;
protected Shape clip = null;
protected boolean usePathBracket = false;
@ -67,12 +66,4 @@ public class HemfDrawProperties extends HwmfDrawProperties {
public void setUsePathBracket(boolean usePathBracket) {
this.usePathBracket = usePathBracket;
}
public Shape getClip() {
return clip;
}
public void setClip(Shape shape) {
clip = shape;
}
}

View File

@ -22,16 +22,13 @@ import static org.apache.poi.hwmf.record.HwmfBrushStyle.BS_SOLID;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Shape;
import java.awt.geom.AffineTransform;
import java.awt.geom.Area;
import java.awt.geom.Path2D;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.function.Consumer;
import org.apache.poi.hemf.record.emf.HemfFill;
import org.apache.poi.hemf.record.emf.HemfRecord;
import org.apache.poi.hwmf.draw.HwmfDrawProperties;
import org.apache.poi.hwmf.draw.HwmfGraphics;
import org.apache.poi.hwmf.record.HwmfColorRef;
import org.apache.poi.hwmf.record.HwmfObjectTableEntry;
@ -47,36 +44,22 @@ public class HemfGraphics extends HwmfGraphics {
private static final HwmfColorRef BLACK = new HwmfColorRef(Color.BLACK);
private final AffineTransform initTrans;
public HemfGraphics(Graphics2D graphicsCtx, Rectangle2D bbox) {
super(graphicsCtx,bbox);
// add dummy entry for object ind ex 0, as emf is 1-based
objectIndexes.set(0);
initTrans = new AffineTransform(graphicsCtx.getTransform());
}
@Override
public HemfDrawProperties getProperties() {
if (prop == null) {
prop = new HemfDrawProperties();
}
return (HemfDrawProperties)prop;
return (HemfDrawProperties)super.getProperties();
}
@Override
public void saveProperties() {
final HemfDrawProperties oldProp = getProperties();
oldProp.setClip(graphicsCtx.getClip());
propStack.add(oldProp);
prop = new HemfDrawProperties(oldProp);
}
@Override
public void restoreProperties(int index) {
super.restoreProperties(index);
HemfDrawProperties newProp = getProperties();
graphicsCtx.setClip(newProp.getClip());
protected HemfDrawProperties newProperties(HwmfDrawProperties oldProps) {
return (oldProps == null)
? new HemfDrawProperties()
: new HemfDrawProperties((HemfDrawProperties)oldProps);
}
public void draw(HemfRecord r) {
@ -94,8 +77,12 @@ public class HemfGraphics extends HwmfGraphics {
} else {
path = new Path2D.Double();
path.setWindingRule(prop.getWindingRule());
}
// add dummy move-to at start, to handle invalid emfs not containing that move-to
if (path.getCurrentPoint() == null) {
Point2D pnt = prop.getLocation();
path.moveTo(pnt.getX(),pnt.getY());
path.moveTo(pnt.getX(), pnt.getY());
}
try {
@ -108,7 +95,12 @@ public class HemfGraphics extends HwmfGraphics {
pathConsumer.accept(path);
}
prop.setLocation(path.getCurrentPoint());
Point2D curPnt = path.getCurrentPoint();
if (curPnt == null) {
return;
}
prop.setLocation(curPnt);
if (!useBracket) {
switch (fillDraw) {
case FILL:
@ -264,64 +256,4 @@ public class HemfGraphics extends HwmfGraphics {
break;
}
}
/**
* @return the initial AffineTransform, when this graphics context was created
*/
public AffineTransform getInitTransform() {
return new AffineTransform(initTrans);
}
/**
* @return the current AffineTransform
*/
public AffineTransform getTransform() {
return new AffineTransform(graphicsCtx.getTransform());
}
/**
* Set the current AffineTransform
* @param tx the current AffineTransform
*/
public void setTransform(AffineTransform tx) {
graphicsCtx.setTransform(tx);
}
public void setClip(Shape clip, HemfFill.HemfRegionMode regionMode) {
Shape oldClip = graphicsCtx.getClip();
switch (regionMode) {
case RGN_AND:
graphicsCtx.clip(clip);
break;
case RGN_OR:
if (oldClip == null) {
graphicsCtx.setClip(clip);
} else {
Area area = new Area(oldClip);
area.add(new Area(clip));
graphicsCtx.setClip(area);
}
break;
case RGN_XOR:
if (oldClip == null) {
graphicsCtx.setClip(clip);
} else {
Area area = new Area(oldClip);
area.exclusiveOr(new Area(clip));
graphicsCtx.setClip(area);
}
break;
case RGN_DIFF:
if (oldClip != null) {
Area area = new Area(oldClip);
area.subtract(new Area(clip));
graphicsCtx.setClip(area);
}
break;
case RGN_COPY:
graphicsCtx.setClip(clip);
break;
}
}
}

View File

@ -1051,7 +1051,11 @@ public class HemfDraw {
@Override
public void draw(HemfGraphics ctx) {
final HemfDrawProperties prop = ctx.getProperties();
final Path2D path = (Path2D)prop.getPath().clone();
final Path2D origPath = prop.getPath();
if (origPath.getCurrentPoint() == null) {
return;
}
final Path2D path = (Path2D)origPath.clone();
path.closePath();
path.setWindingRule(ctx.getProperties().getWindingRule());
ctx.fill(path);

View File

@ -20,10 +20,11 @@ package org.apache.poi.hemf.record.emf;
import static org.apache.poi.hemf.record.emf.HemfDraw.readPointL;
import static org.apache.poi.hemf.record.emf.HemfDraw.readRectL;
import static org.apache.poi.hemf.record.emf.HemfRecordIterator.HEADER_SIZE;
import static org.apache.poi.hwmf.record.HwmfDraw.boundsToString;
import java.awt.Shape;
import java.awt.geom.AffineTransform;
import java.awt.geom.Area;
import java.awt.geom.Path2D;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.io.ByteArrayInputStream;
@ -36,11 +37,11 @@ import org.apache.poi.hemf.draw.HemfDrawProperties;
import org.apache.poi.hemf.draw.HemfGraphics;
import org.apache.poi.hwmf.draw.HwmfGraphics;
import org.apache.poi.hwmf.record.HwmfBitmapDib;
import org.apache.poi.hwmf.record.HwmfBrushStyle;
import org.apache.poi.hwmf.record.HwmfColorRef;
import org.apache.poi.hwmf.record.HwmfDraw;
import org.apache.poi.hwmf.record.HwmfFill;
import org.apache.poi.hwmf.record.HwmfFill.ColorUsage;
import org.apache.poi.hwmf.record.HwmfRegionMode;
import org.apache.poi.hwmf.record.HwmfTernaryRasterOp;
import org.apache.poi.util.IOUtils;
import org.apache.poi.util.LittleEndian;
@ -50,47 +51,6 @@ import org.apache.poi.util.LittleEndianInputStream;
public class HemfFill {
private static final int MAX_RECORD_LENGTH = 10_000_000;
public enum HemfRegionMode {
/**
* The new clipping region includes the intersection (overlapping areas)
* of the current clipping region and the current path (or new region).
*/
RGN_AND(0x01),
/**
* The new clipping region includes the union (combined areas)
* of the current clipping region and the current path (or new region).
*/
RGN_OR(0x02),
/**
* The new clipping region includes the union of the current clipping region
* and the current path (or new region) but without the overlapping areas
*/
RGN_XOR(0x03),
/**
* The new clipping region includes the areas of the current clipping region
* with those of the current path (or new region) excluded.
*/
RGN_DIFF(0x04),
/**
* The new clipping region is the current path (or the new region).
*/
RGN_COPY(0x05);
int flag;
HemfRegionMode(int flag) {
this.flag = flag;
}
public static HemfRegionMode valueOf(int flag) {
for (HemfRegionMode rm : values()) {
if (rm.flag == flag) return rm;
}
return null;
}
}
/**
* The EMR_SETPOLYFILLMODE record defines polygon fill mode.
*/
@ -105,7 +65,7 @@ public class HemfFill {
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
// A 32-bit unsigned integer that specifies the polygon fill mode and
// MUST be in the PolygonFillMode enumeration.
polyfillMode = HwmfPolyfillMode.valueOf((int)leis.readUInt());
polyFillMode = HwmfPolyfillMode.valueOf((int)leis.readUInt());
return LittleEndianConsts.INT_SIZE;
}
}
@ -133,7 +93,7 @@ public class HemfFill {
* optionally in combination with a brush pattern, according to a specified raster operation, stretching or
* compressing the output to fit the dimensions of the destination, if necessary.
*/
public static class EmfStretchBlt extends HwmfFill.WmfBitBlt implements HemfRecord {
public static class EmfStretchBlt extends HwmfFill.WmfStretchDib implements HemfRecord {
protected final Rectangle2D bounds = new Rectangle2D.Double();
/** An XForm object that specifies a world-space to page-space transform to apply to the source bitmap. */
@ -142,14 +102,6 @@ public class HemfFill {
/** A WMF ColorRef object that specifies the background color of the source bitmap. */
protected final HwmfColorRef bkColorSrc = new HwmfColorRef();
/**
* A 32-bit unsigned integer that specifies how to interpret values in the color table in
* the source bitmap header. This value MUST be in the DIBColors enumeration
*/
protected int usageSrc;
protected final HwmfBitmapDib bitmap = new HwmfBitmapDib();
@Override
public HemfRecordType getEmfRecordType() {
return HemfRecordType.stretchBlt;
@ -168,7 +120,7 @@ public class HemfFill {
// rectangle and optionally a brush pattern, to achieve the final color.
int rasterOpIndex = (int)leis.readUInt();
rasterOperation = HwmfTernaryRasterOp.valueOf(rasterOpIndex);
rasterOperation = HwmfTernaryRasterOp.valueOf(rasterOpIndex >>> 16);
size += LittleEndianConsts.INT_SIZE;
@ -179,7 +131,7 @@ public class HemfFill {
size += bkColorSrc.init(leis);
usageSrc = (int)leis.readUInt();
colorUsage = ColorUsage.valueOf((int)leis.readUInt());
// A 32-bit unsigned integer that specifies the offset, in bytes, from the
// start of this record to the source bitmap header in the BitmapBuffer field.
@ -188,7 +140,7 @@ public class HemfFill {
// A 32-bit unsigned integer that specifies the size, in bytes, of the source bitmap header.
final int cbBmiSrc = (int)leis.readUInt();
size += 3*LittleEndianConsts.INT_SIZE;
if (size <= recordSize) {
if (size >= recordSize) {
return size;
}
@ -198,9 +150,12 @@ public class HemfFill {
// A 32-bit unsigned integer that specifies the size, in bytes, of the source bitmap bits.
final int cbBitsSrc = (int)leis.readUInt();
size += 2*LittleEndianConsts.INT_SIZE;
if (size >= recordSize) {
return size;
}
if (srcEqualsDstDimension()) {
srcBounds.setRect(srcPnt.getX(), srcPnt.getY(), dstBounds.getWidth(), dstBounds.getHeight());
} else {
@ -219,14 +174,20 @@ public class HemfFill {
return false;
}
@Override
public void draw(HemfGraphics ctx) {
HemfDrawProperties prop = ctx.getProperties();
prop.setBackgroundColor(this.bkColorSrc);
super.draw(ctx);
}
@Override
public String toString() {
return
"{ bounds: { x: "+bounds.getX()+", y: "+bounds.getY()+", w: "+bounds.getWidth()+", h: "+bounds.getHeight()+"}"+
"{ bounds: "+boundsToString(bounds)+
", xFormSrc: { scaleX: "+xFormSrc.getScaleX()+", shearX: "+xFormSrc.getShearX()+", transX: "+xFormSrc.getTranslateX()+", scaleY: "+xFormSrc.getScaleY()+", shearY: "+xFormSrc.getShearY()+", transY: "+xFormSrc.getTranslateY()+" }"+
", bkColorSrc: "+bkColorSrc+
", usageSrc: "+usageSrc+", "
+ super.toString().substring(1);
","+super.toString().substring(1);
}
}
@ -279,7 +240,8 @@ public class HemfFill {
// These codes define how the color data of the source rectangle is to be combined with the color data
// of the destination rectangle and optionally a brush pattern, to achieve the final color.
// The value MUST be in the WMF Ternary Raster Operation enumeration
rasterOperation = HwmfTernaryRasterOp.valueOf(leis.readInt());
int rasterOpIndex = (int)leis.readUInt();
rasterOperation = HwmfTernaryRasterOp.valueOf(rasterOpIndex >>> 16);
// A 32-bit signed integer that specifies the logical width of the destination rectangle.
int cxDest = leis.readInt();
@ -291,7 +253,7 @@ public class HemfFill {
size += 8*LittleEndianConsts.INT_SIZE;
size += readBitmap(leis, dib, startIdx, offBmiSrc, cbBmiSrc, offBitsSrc, cbBitsSrc);
size += readBitmap(leis, bitmap, startIdx, offBmiSrc, cbBmiSrc, offBitsSrc, cbBitsSrc);
return size;
}
@ -346,7 +308,7 @@ public class HemfFill {
ctx.fill(getShape());
}
protected Area getShape() {
protected Shape getShape() {
return getRgnShape(rgnRects);
}
}
@ -371,7 +333,7 @@ public class HemfFill {
return size;
}
protected Area getShape() {
protected Shape getShape() {
return getRgnShape(rgnRects);
}
}
@ -408,13 +370,13 @@ public class HemfFill {
return size;
}
protected Area getShape() {
protected Shape getShape() {
return getRgnShape(rgnRects);
}
}
public static class EmfExtSelectClipRgn implements HemfRecord {
protected HemfRegionMode regionMode;
protected HwmfRegionMode regionMode;
protected final List<Rectangle2D> rgnRects = new ArrayList<>();
@Override
@ -427,20 +389,43 @@ public class HemfFill {
// A 32-bit unsigned integer that specifies the size of region data in bytes
long rgnDataSize = leis.readUInt();
// A 32-bit unsigned integer that specifies the way to use the region.
regionMode = HemfRegionMode.valueOf((int)leis.readUInt());
regionMode = HwmfRegionMode.valueOf((int)leis.readUInt());
long size = 2* LittleEndianConsts.INT_SIZE;
// If RegionMode is RGN_COPY, this data can be omitted and the clip region
// SHOULD be set to the default (NULL) clip region.
if (regionMode != HemfRegionMode.RGN_COPY) {
if (regionMode != HwmfRegionMode.RGN_COPY) {
size += readRgnData(leis, rgnRects);
}
return size;
}
protected Area getShape() {
protected Shape getShape() {
return getRgnShape(rgnRects);
}
@Override
public void draw(HemfGraphics ctx) {
HemfDrawProperties prop = ctx.getProperties();
ctx.setClip(getShape(), regionMode, true);
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("{ regionMode: '"+regionMode+"'");
sb.append(", regions: [");
boolean isFirst = true;
for (Rectangle2D r : rgnRects) {
if (!isFirst) {
sb.append(",");
}
isFirst = false;
sb.append(boundsToString(r));
}
sb.append("]}");
return sb.toString();
}
}
public static class EmfAlphaBlend implements HemfRecord {
@ -717,7 +702,10 @@ public class HemfFill {
return 6 * LittleEndian.INT_SIZE;
}
protected static Area getRgnShape(List<Rectangle2D> rgnRects) {
protected static Shape getRgnShape(List<Rectangle2D> rgnRects) {
if (rgnRects.size() == 1) {
return rgnRects.get(0);
}
final Area frame = new Area();
rgnRects.forEach((rct) -> frame.add(new Area(rct)));
return frame;

View File

@ -20,6 +20,8 @@ package org.apache.poi.hemf.record.emf;
import static org.apache.poi.hemf.record.emf.HemfDraw.readDimensionInt;
import static org.apache.poi.hemf.record.emf.HemfDraw.readRectL;
import static org.apache.poi.hemf.record.emf.HemfRecordIterator.HEADER_SIZE;
import static org.apache.poi.hwmf.record.HwmfDraw.boundsToString;
import static org.apache.poi.hwmf.record.HwmfDraw.dimToString;
import java.awt.geom.Dimension2D;
import java.awt.geom.Rectangle2D;
@ -119,21 +121,21 @@ public class HemfHeader implements HemfRecord {
@Override
public String toString() {
return "HemfHeader{" +
"boundsRectangle=" + boundsRectangle +
", frameRectangle=" + frameRectangle +
", bytes=" + bytes +
", records=" + records +
", handles=" + handles +
", description=" + description +
", nPalEntries=" + nPalEntries +
", hasExtension1=" + hasExtension1 +
", cbPixelFormat=" + cbPixelFormat +
", offPixelFormat=" + offPixelFormat +
", bOpenGL=" + bOpenGL +
", hasExtension2=" + hasExtension2 +
", deviceDimension=" + deviceDimension +
", microDimension=" + microDimension +
", milliDimension=" + milliDimension +
"boundsRectangle: " + boundsToString(boundsRectangle) +
", frameRectangle: " + boundsToString(frameRectangle) +
", bytes: " + bytes +
", records: " + records +
", handles: " + handles +
", description: '" + description + "'" +
", nPalEntries: " + nPalEntries +
", hasExtension1: " + hasExtension1 +
", cbPixelFormat: " + cbPixelFormat +
", offPixelFormat: " + offPixelFormat +
", bOpenGL: " + bOpenGL +
", hasExtension2: " + hasExtension2 +
", deviceDimension: " + dimToString(deviceDimension) +
", microDimension: " + dimToString(microDimension) +
", milliDimension: " + dimToString(milliDimension) +
'}';
}

View File

@ -191,7 +191,7 @@ public class HemfText {
@Override
public void draw(HwmfGraphics ctx) {
ctx.drawString(rawTextBytes, reference, bounds, dx, isUnicode());
ctx.drawString(rawTextBytes, reference, bounds, options, dx, isUnicode());
}
@Override

View File

@ -24,7 +24,7 @@ import java.io.IOException;
import org.apache.poi.hemf.draw.HemfDrawProperties;
import org.apache.poi.hemf.draw.HemfGraphics;
import org.apache.poi.hemf.record.emf.HemfFill.HemfRegionMode;
import org.apache.poi.hwmf.record.HwmfRegionMode;
import org.apache.poi.hwmf.record.HwmfWindowing;
import org.apache.poi.util.LittleEndianConsts;
import org.apache.poi.util.LittleEndianInputStream;
@ -188,7 +188,7 @@ public class HemfWindowing {
* device context, combining the new region with any existing clipping region using the specified mode.
*/
public static class EmfSelectClipPath implements HemfRecord {
protected HemfRegionMode regionMode;
protected HwmfRegionMode regionMode;
@Override
public HemfRecordType getEmfRecordType() {
@ -199,15 +199,15 @@ public class HemfWindowing {
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
// A 32-bit unsigned integer that specifies the way to use the path.
// The value MUST be in the RegionMode enumeration
regionMode = HemfRegionMode.valueOf(leis.readInt());
regionMode = HwmfRegionMode.valueOf(leis.readInt());
return LittleEndianConsts.INT_SIZE;
}
@Override
public void draw(HemfGraphics ctx) {
HemfDrawProperties props = ctx.getProperties();
ctx.setClip(props.getPath(), regionMode);
HemfDrawProperties prop = ctx.getProperties();
ctx.setClip(prop.getPath(), regionMode, false);
}
@Override

View File

@ -113,7 +113,7 @@ public class HemfPicture implements Iterable<HemfRecord> {
height = 100;
}
return new Dimension2DDouble(width*coeff, height*coeff);
return new Dimension2DDouble(Math.abs(width*coeff), Math.abs(height*coeff));
}
public void draw(Graphics2D ctx, Rectangle2D graphicsBounds) {

View File

@ -19,6 +19,7 @@ package org.apache.poi.hwmf.draw;
import java.awt.Color;
import java.awt.Shape;
import java.awt.geom.AffineTransform;
import java.awt.geom.Area;
import java.awt.geom.Path2D;
import java.awt.geom.Point2D;
@ -67,6 +68,8 @@ public class HwmfDrawProperties {
private HwmfTextAlignment textAlignAsian;
private HwmfTextVerticalAlignment textVAlignAsian;
private HwmfTernaryRasterOp rasterOp;
protected Shape clip;
protected final AffineTransform transform = new AffineTransform();
public HwmfDrawProperties() {
window = new Rectangle2D.Double(0, 0, 1, 1);
@ -89,6 +92,7 @@ public class HwmfDrawProperties {
textAlignAsian = HwmfTextAlignment.RIGHT;
textVAlignAsian = HwmfTextVerticalAlignment.TOP;
rasterOp = HwmfTernaryRasterOp.PATCOPY;
clip = null;
}
public HwmfDrawProperties(HwmfDrawProperties other) {
@ -126,6 +130,8 @@ public class HwmfDrawProperties {
this.textAlignAsian = other.textAlignAsian;
this.textVAlignAsian = other.textVAlignAsian;
this.rasterOp = other.rasterOp;
this.transform.setTransform(other.transform);
this.clip = other.clip;
}
public void setViewportExt(double width, double height) {
@ -387,4 +393,20 @@ public class HwmfDrawProperties {
public void setRasterOp(HwmfTernaryRasterOp rasterOp) {
this.rasterOp = rasterOp;
}
public AffineTransform getTransform() {
return transform;
}
public void setTransform(AffineTransform transform) {
this.transform.setTransform(transform);
}
public Shape getClip() {
return clip;
}
public void setClip(Shape clip) {
this.clip = clip;
}
}

View File

@ -29,6 +29,7 @@ import java.awt.font.FontRenderContext;
import java.awt.font.TextAttribute;
import java.awt.font.TextLayout;
import java.awt.geom.AffineTransform;
import java.awt.geom.Area;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
@ -50,7 +51,9 @@ import org.apache.poi.hwmf.record.HwmfMisc.WmfSetBkMode.HwmfBkMode;
import org.apache.poi.hwmf.record.HwmfObjectTableEntry;
import org.apache.poi.hwmf.record.HwmfPenStyle;
import org.apache.poi.hwmf.record.HwmfPenStyle.HwmfLineDash;
import org.apache.poi.hwmf.record.HwmfRegionMode;
import org.apache.poi.hwmf.record.HwmfText;
import org.apache.poi.hwmf.record.HwmfText.WmfExtTextOutOptions;
import org.apache.poi.sl.draw.DrawFactory;
import org.apache.poi.sl.draw.DrawFontManager;
import org.apache.poi.util.LocaleUtil;
@ -66,11 +69,12 @@ public class HwmfGraphics {
protected final Graphics2D graphicsCtx;
protected final BitSet objectIndexes = new BitSet();
protected final TreeMap<Integer,HwmfObjectTableEntry> objectTable = new TreeMap<>();
protected final AffineTransform initialAT = new AffineTransform();
private static final Charset DEFAULT_CHARSET = LocaleUtil.CHARSET_1252;
/** Bounding box from the placeable header */
private final Rectangle2D bbox;
private final AffineTransform initialAT;
/**
* Initialize a graphics context for wmf rendering
@ -81,16 +85,20 @@ public class HwmfGraphics {
public HwmfGraphics(Graphics2D graphicsCtx, Rectangle2D bbox) {
this.graphicsCtx = graphicsCtx;
this.bbox = (Rectangle2D)bbox.clone();
this.initialAT = graphicsCtx.getTransform();
this.initialAT.setTransform(graphicsCtx.getTransform());
}
public HwmfDrawProperties getProperties() {
if (prop == null) {
prop = new HwmfDrawProperties();
prop = newProperties(null);
}
return prop;
}
protected HwmfDrawProperties newProperties(HwmfDrawProperties oldProps) {
return (oldProps == null) ? new HwmfDrawProperties() : new HwmfDrawProperties(oldProps);
}
public void draw(Shape shape) {
HwmfPenStyle ps = getProperties().getPenStyle();
if (ps == null) {
@ -119,14 +127,18 @@ public class HwmfGraphics {
}
public void fill(Shape shape) {
if (getProperties().getBrushStyle() != HwmfBrushStyle.BS_NULL) {
// GeneralPath gp = new GeneralPath(shape);
// gp.setWindingRule(getProperties().getPolyfillMode().awtFlag);
HwmfDrawProperties prop = getProperties();
if (prop.getBrushStyle() != HwmfBrushStyle.BS_NULL) {
if (prop.getBkMode() == HwmfBkMode.OPAQUE) {
graphicsCtx.setPaint(prop.getBackgroundColor().getColor());
graphicsCtx.fill(shape);
}
graphicsCtx.setPaint(getFill());
graphicsCtx.fill(shape);
}
draw(shape);
// draw(shape);
}
protected BasicStroke getStroke() {
@ -264,8 +276,10 @@ public class HwmfGraphics {
public void saveProperties() {
final HwmfDrawProperties p = getProperties();
assert(p != null);
p.setTransform(graphicsCtx.getTransform());
p.setClip(graphicsCtx.getClip());
propStack.add(p);
prop = new HwmfDrawProperties(p);
prop = newProperties(p);
}
/**
@ -291,7 +305,16 @@ public class HwmfGraphics {
// roll to last when curIdx == 0
stackIndex = propStack.size()-1;
}
prop = propStack.get(stackIndex);
// The playback device context is restored by popping state information off a stack that was created by
// prior SAVEDC records
// ... so because being a stack, we will remove all entries having a greater index than the stackIndex
for (int i=propStack.size()-1; i>=stackIndex; i--) {
prop = propStack.remove(i);
}
graphicsCtx.setTransform(prop.getTransform());
graphicsCtx.setClip(prop.getClip());
}
/**
@ -338,13 +361,14 @@ public class HwmfGraphics {
}
}
public void drawString(byte[] text, Point2D reference, Rectangle2D clip) {
drawString(text, reference, clip, null, false);
public void drawString(byte[] text, Point2D reference) {
drawString(text, reference, null, null, null, false);
}
public void drawString(byte[] text, Point2D reference, Rectangle2D clip, List<Integer> dx, boolean isUnicode) {
public void drawString(byte[] text, Point2D reference, Rectangle2D clip, WmfExtTextOutOptions opts, List<Integer> dx, boolean isUnicode) {
final HwmfDrawProperties prop = getProperties();
HwmfFont font = getProperties().getFont();
HwmfFont font = prop.getFont();
if (font == null || text == null || text.length == 0) {
return;
}
@ -446,21 +470,31 @@ public class HwmfGraphics {
Point2D dst = new Point2D.Double();
tx.transform(src, dst);
// TODO: implement clipping on bounds
final Shape clipShape = graphicsCtx.getClip();
final AffineTransform at = graphicsCtx.getTransform();
try {
if (clip != null) {
graphicsCtx.translate(-clip.getCenterX(), -clip.getCenterY());
graphicsCtx.rotate(angle);
graphicsCtx.translate(clip.getCenterX(), clip.getCenterY());
if (prop.getBkMode() == HwmfBkMode.OPAQUE && opts.isOpaque()) {
graphicsCtx.setPaint(prop.getBackgroundColor().getColor());
graphicsCtx.fill(clip);
}
if (opts.isClipped()) {
graphicsCtx.setClip(clip);
}
graphicsCtx.setTransform(at);
}
graphicsCtx.translate(reference.getX(), reference.getY());
graphicsCtx.rotate(angle);
graphicsCtx.translate(dst.getX(), dst.getY());
if (getProperties().getBkMode() == HwmfBkMode.OPAQUE && clip != null) {
// TODO: validate bounds
graphicsCtx.setBackground(getProperties().getBackgroundColor().getColor());
graphicsCtx.fill(new Rectangle2D.Double(0, 0, clip.getWidth(), clip.getHeight()));
}
graphicsCtx.setColor(getProperties().getTextColor().getColor());
graphicsCtx.setColor(prop.getTextColor().getColor());
graphicsCtx.drawString(as.getIterator(), 0, 0);
} finally {
graphicsCtx.setTransform(at);
graphicsCtx.setClip(clipShape);
}
}
@ -498,18 +532,136 @@ public class HwmfGraphics {
}
public void drawImage(BufferedImage img, Rectangle2D srcBounds, Rectangle2D dstBounds) {
// prop.getRasterOp();
graphicsCtx.drawImage(img,
(int)dstBounds.getX(),
(int)dstBounds.getY(),
(int)(dstBounds.getX()+dstBounds.getWidth()),
(int)(dstBounds.getY()+dstBounds.getHeight()),
(int)srcBounds.getX(),
(int)srcBounds.getY(),
(int)(srcBounds.getX()+srcBounds.getWidth()),
(int)(srcBounds.getY()+srcBounds.getHeight()),
null, // getProperties().getBackgroundColor().getColor(),
null
);
HwmfDrawProperties prop = getProperties();
// handle raster op
// currently the raster op as described in https://docs.microsoft.com/en-us/windows/desktop/gdi/ternary-raster-operations
// are not supported, as we would need to extract the destination image area from the underlying buffered image
// and therefore would make it mandatory that the graphics context must be from a buffered image
// furthermore I doubt the purpose of bitwise image operations on non-black/white images
switch (prop.getRasterOp()) {
case D:
// keep destination, i.e. do nothing
break;
case PATCOPY:
graphicsCtx.setPaint(getFill());
graphicsCtx.fill(dstBounds);
break;
case BLACKNESS:
graphicsCtx.setPaint(Color.BLACK);
graphicsCtx.fill(dstBounds);
break;
case WHITENESS:
graphicsCtx.setPaint(Color.WHITE);
graphicsCtx.fill(dstBounds);
break;
default:
case SRCCOPY:
final Shape clip = graphicsCtx.getClip();
// add clipping in case of a source subimage, i.e. a clipped source image
// some dstBounds are horizontal or vertical flipped, so we need to normalize the images
Rectangle2D normalized = new Rectangle2D.Double(
dstBounds.getWidth() >= 0 ? dstBounds.getMinX() : dstBounds.getMaxX(),
dstBounds.getHeight() >= 0 ? dstBounds.getMinY() : dstBounds.getMaxY(),
Math.abs(dstBounds.getWidth()),
Math.abs(dstBounds.getHeight()));
graphicsCtx.clip(normalized);
final AffineTransform at = graphicsCtx.getTransform();
final Rectangle2D imgBounds = new Rectangle2D.Double(0,0,img.getWidth(),img.getHeight());
final boolean isImgBounds = (srcBounds.equals(new Rectangle2D.Double()));
final Rectangle2D srcBounds2 = isImgBounds ? imgBounds : srcBounds;
// TODO: apply emf transform
graphicsCtx.translate(dstBounds.getX(), dstBounds.getY());
graphicsCtx.scale(dstBounds.getWidth()/srcBounds2.getWidth(), dstBounds.getHeight()/srcBounds2.getHeight());
graphicsCtx.translate(-srcBounds2.getX(), -srcBounds2.getY());
graphicsCtx.drawImage(img, 0, 0, prop.getBackgroundColor().getColor(), null);
graphicsCtx.setTransform(at);
graphicsCtx.setClip(clip);
break;
}
}
/**
* @return the initial AffineTransform, when this graphics context was created
*/
public AffineTransform getInitTransform() {
return new AffineTransform(initialAT);
}
/**
* @return the current AffineTransform
*/
public AffineTransform getTransform() {
return new AffineTransform(graphicsCtx.getTransform());
}
/**
* Set the current AffineTransform
* @param tx the current AffineTransform
*/
public void setTransform(AffineTransform tx) {
graphicsCtx.setTransform(tx);
}
private static int clipCnt = 0;
public void setClip(Shape clip, HwmfRegionMode regionMode, boolean useInitialAT) {
final AffineTransform at = graphicsCtx.getTransform();
if (useInitialAT) {
graphicsCtx.setTransform(initialAT);
}
final Shape oldClip = graphicsCtx.getClip();
final boolean isEmpty = clip.getBounds2D().isEmpty();
switch (regionMode) {
case RGN_AND:
if (!isEmpty) {
graphicsCtx.clip(clip);
}
break;
case RGN_OR:
if (!isEmpty) {
if (oldClip == null) {
graphicsCtx.setClip(clip);
} else {
Area area = new Area(oldClip);
area.add(new Area(clip));
graphicsCtx.setClip(area);
}
}
break;
case RGN_XOR:
if (!isEmpty) {
if (oldClip == null) {
graphicsCtx.setClip(clip);
} else {
Area area = new Area(oldClip);
area.exclusiveOr(new Area(clip));
graphicsCtx.setClip(area);
}
}
break;
case RGN_DIFF:
if (!isEmpty) {
if (oldClip != null) {
Area area = new Area(oldClip);
area.subtract(new Area(clip));
graphicsCtx.setClip(area);
}
}
break;
case RGN_COPY: {
graphicsCtx.setClip(isEmpty ? null : clip);
break;
}
}
if (useInitialAT) {
graphicsCtx.setTransform(at);
}
}
}

View File

@ -413,7 +413,7 @@ public class HwmfBitmapDib {
return new ByteArrayInputStream(getBMPData());
}
private byte[] getBMPData() {
public byte[] getBMPData() {
if (imageData == null) {
throw new RecordFormatException("bitmap not initialized ... need to call init() before");
}

View File

@ -20,6 +20,7 @@ package org.apache.poi.hwmf.record;
import java.awt.Shape;
import java.awt.geom.Arc2D;
import java.awt.geom.Area;
import java.awt.geom.Dimension2D;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Line2D;
import java.awt.geom.Path2D;
@ -744,4 +745,11 @@ public class HwmfDraw {
public static String boundsToString(Rectangle2D bounds) {
return "{ x: "+bounds.getX()+", y: "+bounds.getY()+", w: "+bounds.getWidth()+", h: "+bounds.getHeight()+" }";
}
@Internal
public static String dimToString(Dimension2D dim) {
return "{ w: "+dim.getWidth()+", h: "+dim.getHeight()+" }";
}
}

View File

@ -27,6 +27,7 @@ import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.io.IOException;
import org.apache.poi.hwmf.draw.HwmfDrawProperties;
import org.apache.poi.hwmf.draw.HwmfGraphics;
import org.apache.poi.util.LittleEndianConsts;
import org.apache.poi.util.LittleEndianInputStream;
@ -222,7 +223,7 @@ public class HwmfFill {
* An unsigned integer that defines polygon fill mode.
* This MUST be one of the values: ALTERNATE = 0x0001, WINDING = 0x0002
*/
protected HwmfPolyfillMode polyfillMode;
protected HwmfPolyfillMode polyFillMode;
@Override
public HwmfRecordType getWmfRecordType() {
@ -231,13 +232,18 @@ public class HwmfFill {
@Override
public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException {
polyfillMode = HwmfPolyfillMode.valueOf(leis.readUShort() & 3);
polyFillMode = HwmfPolyfillMode.valueOf(leis.readUShort() & 3);
return LittleEndianConsts.SHORT_SIZE;
}
@Override
public void draw(HwmfGraphics ctx) {
ctx.getProperties().setPolyfillMode(polyfillMode);
ctx.getProperties().setPolyfillMode(polyFillMode);
}
@Override
public String toString() {
return "{ polyFillMode: '"+ polyFillMode +"' }";
}
}
@ -458,7 +464,7 @@ public class HwmfFill {
* A variable-sized DeviceIndependentBitmap Object (section 2.2.2.9) that is the
* source of the color data.
*/
protected final HwmfBitmapDib dib = new HwmfBitmapDib();
protected final HwmfBitmapDib bitmap = new HwmfBitmapDib();
@Override
public HwmfRecordType getWmfRecordType() {
@ -481,22 +487,36 @@ public class HwmfFill {
size += readBounds2(leis, srcBounds);
size += readBounds2(leis, dstBounds);
size += dib.init(leis, (int)(recordSize-6-size));
size += bitmap.init(leis, (int)(recordSize-6-size));
return size;
}
@Override
public void draw(HwmfGraphics ctx) {
if (dib.isValid()) {
ctx.getProperties().setRasterOp(rasterOperation);
HwmfDrawProperties prop = ctx.getProperties();
prop.setRasterOp(rasterOperation);
if (bitmap.isValid()) {
ctx.drawImage(getImage(), srcBounds, dstBounds);
} else {
BufferedImage bi = new BufferedImage((int)dstBounds.getWidth(), (int)dstBounds.getHeight(), BufferedImage.TYPE_INT_ARGB);
ctx.drawImage(bi, dstBounds, dstBounds);
}
}
@Override
public BufferedImage getImage() {
return dib.getImage();
return bitmap.getImage();
}
@Override
public String toString() {
return
"{ rasterOperation: '"+rasterOperation+"'"+
", colorUsage: '"+colorUsage+"'"+
", srcBounds: "+boundsToString(srcBounds)+
", dstBounds: "+boundsToString(dstBounds)+
"}";
}
}

View File

@ -0,0 +1,59 @@
/* ====================================================================
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.hwmf.record;
import org.apache.poi.hemf.record.emf.HemfFill;
public enum HwmfRegionMode {
/**
* The new clipping region includes the intersection (overlapping areas)
* of the current clipping region and the current path (or new region).
*/
RGN_AND(0x01),
/**
* The new clipping region includes the union (combined areas)
* of the current clipping region and the current path (or new region).
*/
RGN_OR(0x02),
/**
* The new clipping region includes the union of the current clipping region
* and the current path (or new region) but without the overlapping areas
*/
RGN_XOR(0x03),
/**
* The new clipping region includes the areas of the current clipping region
* with those of the current path (or new region) excluded.
*/
RGN_DIFF(0x04),
/**
* The new clipping region is the current path (or the new region).
*/
RGN_COPY(0x05);
int flag;
HwmfRegionMode(int flag) {
this.flag = flag;
}
public static HwmfRegionMode valueOf(int flag) {
for (HwmfRegionMode rm : values()) {
if (rm.flag == flag) return rm;
}
return null;
}
}

View File

@ -191,7 +191,7 @@ public class HwmfText {
@Override
public void draw(HwmfGraphics ctx) {
ctx.drawString(getTextBytes(), reference, null);
ctx.drawString(getTextBytes(), reference);
}
public String getText(Charset charset) {
@ -396,7 +396,7 @@ public class HwmfText {
@Override
public void draw(HwmfGraphics ctx) {
ctx.drawString(rawTextBytes, reference, bounds, dx, false);
ctx.drawString(rawTextBytes, reference, bounds, options, dx, false);
}
public String getText(Charset charset) throws IOException {

View File

@ -433,6 +433,7 @@ public class HwmfWindowing {
@Override
public void applyObject(HwmfGraphics ctx) {
ctx.setClip(bounds, HwmfRegionMode.RGN_AND, true);
}
@Override