/** Path for path bracket operations */
protected Path2D path = null;
- protected Shape clip = null;
protected boolean usePathBracket = false;
public void setUsePathBracket(boolean usePathBracket) {
this.usePathBracket = usePathBracket;
}
-
- public Shape getClip() {
- return clip;
- }
-
- public void setClip(Shape shape) {
- clip = shape;
- }
}
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;
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;
- }
-
- @Override
- public void saveProperties() {
- final HemfDrawProperties oldProp = getProperties();
- oldProp.setClip(graphicsCtx.getClip());
- propStack.add(oldProp);
- prop = new HemfDrawProperties(oldProp);
+ return (HemfDrawProperties)super.getProperties();
}
@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) {
} 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 {
pathConsumer.accept(path);
}
- prop.setLocation(path.getCurrentPoint());
+ Point2D curPnt = path.getCurrentPoint();
+ if (curPnt == null) {
+ return;
+ }
+
+ prop.setLocation(curPnt);
if (!useBracket) {
switch (fillDraw) {
case FILL:
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;
- }
- }
}
@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);
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;
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;
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.
*/
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;
}
}
* 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. */
/** 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;
// 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;
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.
// 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;
}
// 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 {
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);
}
}
// 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();
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;
}
ctx.fill(getShape());
}
- protected Area getShape() {
+ protected Shape getShape() {
return getRgnShape(rgnRects);
}
}
return size;
}
- protected Area getShape() {
+ protected Shape getShape() {
return getRgnShape(rgnRects);
}
}
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
// 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 {
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;
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;
@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) +
'}';
}
@Override
public void draw(HwmfGraphics ctx) {
- ctx.drawString(rawTextBytes, reference, bounds, dx, isUnicode());
+ ctx.drawString(rawTextBytes, reference, bounds, options, dx, isUnicode());
}
@Override
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;
* 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() {
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
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) {
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;
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);
textAlignAsian = HwmfTextAlignment.RIGHT;
textVAlignAsian = HwmfTextVerticalAlignment.TOP;
rasterOp = HwmfTernaryRasterOp.PATCOPY;
+ clip = null;
}
public HwmfDrawProperties(HwmfDrawProperties other) {
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) {
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;
+ }
}
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;
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;
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
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) {
}
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() {
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);
}
/**
// 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());
}
/**
}
}
- 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;
}
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);
}
}
}
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);
+ }
}
}
return new ByteArrayInputStream(getBMPData());
}
- private byte[] getBMPData() {
+ public byte[] getBMPData() {
if (imageData == null) {
throw new RecordFormatException("bitmap not initialized ... need to call init() before");
}
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;
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()+" }";
+ }
+
+
}
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;
* 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() {
@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 +"' }";
}
}
* 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() {
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)+
+ "}";
}
}
--- /dev/null
+/* ====================================================================
+ 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;
+ }
+}
@Override
public void draw(HwmfGraphics ctx) {
- ctx.drawString(getTextBytes(), reference, null);
+ ctx.drawString(getTextBytes(), reference);
}
public String getText(Charset charset) {
@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 {
@Override
public void applyObject(HwmfGraphics ctx) {
+ ctx.setClip(bounds, HwmfRegionMode.RGN_AND, true);
}
@Override