git-svn-id: https://svn.apache.org/repos/asf/poi/branches/hemf@1845291 13f79535-47bb-0310-9956-ffa450edef68pull/133/head
@@ -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; | |||
} | |||
} |
@@ -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; | |||
} | |||
@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) { | |||
@@ -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; | |||
} | |||
} | |||
} |
@@ -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); |
@@ -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; |
@@ -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) + | |||
'}'; | |||
} | |||
@@ -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 |
@@ -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 |
@@ -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) { |
@@ -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; | |||
} | |||
} |
@@ -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); | |||
} | |||
} | |||
} |
@@ -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"); | |||
} |
@@ -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()+" }"; | |||
} | |||
} |
@@ -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)+ | |||
"}"; | |||
} | |||
} | |||
@@ -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; | |||
} | |||
} |
@@ -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 { |
@@ -433,6 +433,7 @@ public class HwmfWindowing { | |||
@Override | |||
public void applyObject(HwmfGraphics ctx) { | |||
ctx.setClip(bounds, HwmfRegionMode.RGN_AND, true); | |||
} | |||
@Override |