WMF fixes

git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1723898 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Andreas Beeker 2016-01-10 01:12:16 +00:00
parent f065bf6e00
commit 22a9bade84
12 changed files with 698 additions and 251 deletions

View File

@ -25,14 +25,19 @@ import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.WritableRaster;
import java.util.List;
import org.apache.poi.hwmf.record.HwmfBrushStyle;
import org.apache.poi.hwmf.record.HwmfColorRef;
import org.apache.poi.hwmf.record.HwmfFont;
import org.apache.poi.hwmf.record.HwmfFill.WmfSetPolyfillMode.HwmfPolyfillMode;
import org.apache.poi.hwmf.record.HwmfHatchStyle;
import org.apache.poi.hwmf.record.HwmfMapMode;
import org.apache.poi.hwmf.record.HwmfMisc.WmfSetBkMode.HwmfBkMode;
import org.apache.poi.hwmf.record.HwmfPalette.PaletteEntry;
import org.apache.poi.hwmf.record.HwmfPenStyle;
import org.apache.poi.hwmf.record.HwmfText.HwmfTextAlignment;
import org.apache.poi.hwmf.record.HwmfText.HwmfTextVerticalAlignment;
public class HwmfDrawProperties {
private final Rectangle2D window;
@ -51,6 +56,14 @@ public class HwmfDrawProperties {
private HwmfBkMode bkMode = HwmfBkMode.OPAQUE;
private HwmfPolyfillMode polyfillMode = HwmfPolyfillMode.WINDING;
private Shape region = null;
private List<PaletteEntry> palette = null;
private int paletteOffset = 0;
private HwmfFont font = null;
private HwmfColorRef textColor = new HwmfColorRef(Color.BLACK);
private HwmfTextAlignment textAlignLatin = HwmfTextAlignment.LEFT;
private HwmfTextVerticalAlignment textVAlignLatin = HwmfTextVerticalAlignment.TOP;
private HwmfTextAlignment textAlignAsian = HwmfTextAlignment.RIGHT;
private HwmfTextVerticalAlignment textVAlignAsian = HwmfTextVerticalAlignment.TOP;
public HwmfDrawProperties() {
window = new Rectangle2D.Double(0, 0, 1, 1);
@ -84,9 +97,16 @@ public class HwmfDrawProperties {
} else if (other.region instanceof Area) {
this.region = new Area(other.region);
}
this.palette = other.palette;
this.paletteOffset = other.paletteOffset;
this.font = other.font;
this.textColor = (other.textColor == null) ? null : other.textColor.clone();
}
public void setViewportExt(double width, double height) {
if (viewport == null) {
viewport = (Rectangle2D)window.clone();
}
double x = viewport.getX();
double y = viewport.getY();
double w = (width != 0) ? width : viewport.getWidth();
@ -244,4 +264,80 @@ public class HwmfDrawProperties {
public void setRegion(Shape region) {
this.region = region;
}
/**
* Returns the current palette.
* Callers may modify the palette.
*
* @return the current palette or null, if it hasn't been set
*/
public List<PaletteEntry> getPalette() {
return palette;
}
/**
* Sets the current palette.
* It's the callers duty to set a modifiable copy of the palette.
*
* @param palette
*/
public void setPalette(List<PaletteEntry> palette) {
this.palette = palette;
}
public int getPaletteOffset() {
return paletteOffset;
}
public void setPaletteOffset(int paletteOffset) {
this.paletteOffset = paletteOffset;
}
public HwmfColorRef getTextColor() {
return textColor;
}
public void setTextColor(HwmfColorRef textColor) {
this.textColor = textColor;
}
public HwmfFont getFont() {
return font;
}
public void setFont(HwmfFont font) {
this.font = font;
}
public HwmfTextAlignment getTextAlignLatin() {
return textAlignLatin;
}
public void setTextAlignLatin(HwmfTextAlignment textAlignLatin) {
this.textAlignLatin = textAlignLatin;
}
public HwmfTextVerticalAlignment getTextVAlignLatin() {
return textVAlignLatin;
}
public void setTextVAlignLatin(HwmfTextVerticalAlignment textVAlignLatin) {
this.textVAlignLatin = textVAlignLatin;
}
public HwmfTextAlignment getTextAlignAsian() {
return textAlignAsian;
}
public void setTextAlignAsian(HwmfTextAlignment textAlignAsian) {
this.textAlignAsian = textAlignAsian;
}
public HwmfTextVerticalAlignment getTextVAlignAsian() {
return textVAlignAsian;
}
public void setTextVAlignAsian(HwmfTextVerticalAlignment textVAlignAsian) {
this.textVAlignAsian = textVAlignAsian;
}
}

View File

@ -25,10 +25,12 @@ import java.awt.Paint;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.TexturePaint;
import java.awt.font.TextAttribute;
import java.awt.geom.AffineTransform;
import java.awt.geom.GeneralPath;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.text.AttributedString;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
@ -36,13 +38,13 @@ import java.util.ListIterator;
import java.util.NoSuchElementException;
import org.apache.poi.hwmf.record.HwmfBrushStyle;
import org.apache.poi.hwmf.record.HwmfFont;
import org.apache.poi.hwmf.record.HwmfHatchStyle;
import org.apache.poi.hwmf.record.HwmfMapMode;
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.util.Units;
public class HwmfGraphics {
private final Graphics2D graphicsCtx;
@ -118,7 +120,7 @@ public class HwmfGraphics {
boolean dashAlt = ps.isAlternateDash();
// This value is not an integer index into the dash pattern array.
// Instead, it is a floating-point value that specifies a linear distance.
float dashStart = (dashAlt && dashes.length > 1) ? dashes[0] : 0;
float dashStart = (dashAlt && dashes != null && dashes.length > 1) ? dashes[0] : 0;
return new BasicStroke(width, cap, join, miterLimit, dashes, dashStart);
}
@ -243,8 +245,8 @@ public class HwmfGraphics {
/**
* Restores the properties from the stack
*
* @param index if the index is positive, the n-th element from the start is removed and activated.
* If the index is negative, the n-th previous element relative to the current properties element is removed and activated.
* @param index if the index is positive, the n-th element from the start is activated.
* If the index is negative, the n-th previous element relative to the current properties element is activated.
*/
public void restoreProperties(int index) {
if (index == 0) {
@ -253,12 +255,20 @@ public class HwmfGraphics {
int stackIndex = index;
if (stackIndex < 0) {
int curIdx = propStack.indexOf(prop);
assert (curIdx != -1);
if (curIdx == -1) {
// the current element is not pushed to the stacked, i.e. it's the last
curIdx = propStack.size();
}
stackIndex = curIdx + index;
}
prop = propStack.remove(stackIndex);
prop = propStack.get(stackIndex);
}
/**
* After setting various window and viewport related properties,
* the underlying graphics context needs to be adapted.
* This methods gathers and sets the corresponding graphics transformations.
*/
public void updateWindowMapMode() {
GraphicsConfiguration gc = graphicsCtx.getDeviceConfiguration();
Rectangle2D win = prop.getWindow();
@ -268,23 +278,15 @@ public class HwmfGraphics {
switch (mapMode) {
default:
case MM_ANISOTROPIC:
// scale output bounds to image bounds
graphicsCtx.scale(gc.getBounds().getWidth()/bbox.getWidth(), gc.getBounds().getHeight()/bbox.getHeight());
graphicsCtx.translate(-bbox.getX(), -bbox.getY());
// scale window bounds to output bounds
graphicsCtx.translate(win.getCenterX(), win.getCenterY());
graphicsCtx.scale(bbox.getWidth()/win.getWidth(), bbox.getHeight()/win.getHeight());
graphicsCtx.translate(-win.getCenterX(), -win.getCenterY());
graphicsCtx.translate(-win.getX(), -win.getY());
break;
case MM_ISOTROPIC:
// TODO: to be validated ...
// like anisotropic, but use x-axis as reference
graphicsCtx.scale(gc.getBounds().getWidth()/bbox.getWidth(), gc.getBounds().getWidth()/bbox.getWidth());
graphicsCtx.translate(-bbox.getX(), -bbox.getY());
graphicsCtx.translate(win.getCenterX(), win.getCenterY());
graphicsCtx.scale(bbox.getWidth()/win.getWidth(), bbox.getWidth()/win.getWidth());
graphicsCtx.translate(-win.getCenterX(), -win.getCenterY());
graphicsCtx.translate(-win.getX(), -win.getY());
break;
case MM_LOMETRIC:
case MM_HIMETRIC:
@ -294,11 +296,48 @@ public class HwmfGraphics {
// TODO: to be validated ...
graphicsCtx.transform(gc.getNormalizingTransform());
graphicsCtx.scale(1./mapMode.scale, -1./mapMode.scale);
graphicsCtx.translate(-bbox.getX(), -bbox.getY());
graphicsCtx.translate(-win.getX(), -win.getY());
break;
case MM_TEXT:
// TODO: to be validated ...
break;
}
}
public void drawString(String text, Rectangle2D bounds) {
HwmfFont font = prop.getFont();
if (font == null) {
return;
}
AttributedString as = new AttributedString(text);
as.addAttribute(TextAttribute.FAMILY, font.getFacename());
// TODO: fix font height calculation
as.addAttribute(TextAttribute.SIZE, Math.abs(font.getHeight()));
as.addAttribute(TextAttribute.STRIKETHROUGH, font.isStrikeOut());
if (font.isUnderline()) {
as.addAttribute(TextAttribute.UNDERLINE, TextAttribute.UNDERLINE_ON);
}
if (font.isItalic()) {
as.addAttribute(TextAttribute.POSTURE, TextAttribute.POSTURE_OBLIQUE);
}
as.addAttribute(TextAttribute.WEIGHT, font.getWeight());
double angle = Math.toRadians(-font.getEscapement()/10.);
final AffineTransform at = graphicsCtx.getTransform();
try {
graphicsCtx.translate(bounds.getX(), bounds.getY());
graphicsCtx.rotate(angle);
if (prop.getBkMode() == HwmfBkMode.OPAQUE) {
// TODO: validate bounds
graphicsCtx.setBackground(prop.getBackgroundColor().getColor());
graphicsCtx.fill(bounds);
}
graphicsCtx.setColor(prop.getTextColor().getColor());
graphicsCtx.drawString(as.getIterator(), 0, 0); // (float)bounds.getX(), (float)bounds.getY());
} finally {
graphicsCtx.setTransform(at);
}
}
}

View File

@ -17,17 +17,18 @@
package org.apache.poi.hwmf.record;
import java.awt.Polygon;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.geom.Arc2D;
import java.awt.geom.Area;
import java.awt.geom.Ellipse2D;
import java.awt.geom.GeneralPath;
import java.awt.geom.Line2D;
import java.awt.geom.Path2D;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.awt.geom.RoundRectangle2D;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import org.apache.poi.hwmf.draw.HwmfGraphics;
import org.apache.poi.util.LittleEndianConsts;
@ -114,12 +115,7 @@ public class HwmfDraw {
*/
public static class WmfPolygon implements HwmfRecord {
/**
* A 16-bit signed integer that defines the number of points in the array.
*/
private int numberofPoints;
short xPoints[], yPoints[];
private Path2D poly = new Path2D.Double();
@Override
public HwmfRecordType getRecordType() {
@ -128,15 +124,21 @@ public class HwmfDraw {
@Override
public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException {
numberofPoints = leis.readShort();
xPoints = new short[numberofPoints];
yPoints = new short[numberofPoints];
/**
* A 16-bit signed integer that defines the number of points in the array.
*/
int numberofPoints = leis.readShort();
for (int i=0; i<numberofPoints; i++) {
// A 16-bit signed integer that defines the horizontal (x) coordinate of the point.
xPoints[i] = leis.readShort();
int x = leis.readShort();
// A 16-bit signed integer that defines the vertical (y) coordinate of the point.
yPoints[i] = leis.readShort();
int y = leis.readShort();
if (i==0) {
poly.moveTo(x, y);
} else {
poly.lineTo(x, y);
}
}
return LittleEndianConsts.SHORT_SIZE+numberofPoints*LittleEndianConsts.INT_SIZE;
@ -144,15 +146,14 @@ public class HwmfDraw {
@Override
public void draw(HwmfGraphics ctx) {
ctx.fill(getShape());
Path2D p = getShape();
p.closePath();
p.setWindingRule(ctx.getProperties().getPolyfillMode().awtFlag);
ctx.fill(p);
}
protected Polygon getShape() {
Polygon polygon = new Polygon();
for(int i = 0; i < numberofPoints; i++) {
polygon.addPoint(xPoints[i], yPoints[i]);
}
return polygon;
protected Path2D getShape() {
return (Path2D)poly.clone();
}
}
@ -169,7 +170,9 @@ public class HwmfDraw {
@Override
public void draw(HwmfGraphics ctx) {
ctx.draw(getShape());
Path2D p = getShape();
p.setWindingRule(ctx.getProperties().getPolyfillMode().awtFlag);
ctx.draw(p);
}
}
@ -234,12 +237,12 @@ public class HwmfDraw {
* A 16-bit unsigned integer used to index into the WMF Object Table to get
* the region to be framed.
*/
private int region;
private int regionIndex;
/**
* A 16-bit unsigned integer used to index into the WMF Object Table to get the
* Brush to use for filling the region.
*/
private int brush;
private int brushIndex;
/**
* A 16-bit signed integer that defines the height, in logical units, of the
* region frame.
@ -258,8 +261,8 @@ public class HwmfDraw {
@Override
public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException {
region = leis.readUShort();
brush = leis.readUShort();
regionIndex = leis.readUShort();
brushIndex = leis.readUShort();
height = leis.readShort();
width = leis.readShort();
return 4*LittleEndianConsts.SHORT_SIZE;
@ -267,7 +270,17 @@ public class HwmfDraw {
@Override
public void draw(HwmfGraphics ctx) {
ctx.applyObjectTableEntry(brushIndex);
ctx.applyObjectTableEntry(regionIndex);
Rectangle2D inner = ctx.getProperties().getRegion().getBounds();
double x = inner.getX()-width;
double y = inner.getY()-height;
double w = inner.getWidth()+2*width;
double h = inner.getHeight()+2*height;
Rectangle2D outer = new Rectangle2D.Double(x,y,w,h);
Area frame = new Area(outer);
frame.subtract(new Area(inner));
ctx.fill(frame);
}
}
@ -278,27 +291,7 @@ public class HwmfDraw {
*/
public static class WmfPolyPolygon implements HwmfRecord {
/**
* A 16-bit unsigned integer that defines the number of polygons in the object.
*/
private int numberOfPolygons;
/**
* A NumberOfPolygons array of 16-bit unsigned integers that define the number of
* points for each polygon in the object.
*/
private int pointsPerPolygon[];
/**
* An array of 16-bit unsigned integers that define the coordinates of the polygons.
*/
private int xPoints[][];
/**
* An array of 16-bit unsigned integers that define the coordinates of the polygons.
*/
private int yPoints[][];
private List<Path2D> polyList = new ArrayList<Path2D>();
@Override
public HwmfRecordType getRecordType() {
@ -308,10 +301,15 @@ public class HwmfDraw {
@Override
public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException {
// see http://secunia.com/gfx/pdf/SA31675_BA.pdf ;)
numberOfPolygons = leis.readUShort();
pointsPerPolygon = new int[numberOfPolygons];
xPoints = new int[numberOfPolygons][];
yPoints = new int[numberOfPolygons][];
/**
* A 16-bit unsigned integer that defines the number of polygons in the object.
*/
int numberOfPolygons = leis.readUShort();
/**
* A NumberOfPolygons array of 16-bit unsigned integers that define the number of
* points for each polygon in the object.
*/
int[] pointsPerPolygon = new int[numberOfPolygons];
int size = LittleEndianConsts.SHORT_SIZE;
@ -320,16 +318,23 @@ public class HwmfDraw {
size += LittleEndianConsts.SHORT_SIZE;
}
for (int i=0; i<numberOfPolygons; i++) {
xPoints[i] = new int[pointsPerPolygon[i]];
yPoints[i] = new int[pointsPerPolygon[i]];
for (int j=0; j<pointsPerPolygon[i]; j++) {
xPoints[i][j] = leis.readUShort();
yPoints[i][j] = leis.readUShort();
for (int nPoints : pointsPerPolygon) {
/**
* An array of 16-bit unsigned integers that define the coordinates of the polygons.
*/
Path2D poly = new Path2D.Double();
for (int i=0; i<nPoints; i++) {
int x = leis.readUShort();
int y = leis.readUShort();
size += 2*LittleEndianConsts.SHORT_SIZE;
if (i == 0) {
poly.moveTo(x, y);
} else {
poly.lineTo(x, y);
}
}
poly.closePath();
polyList.add(poly);
}
return size;
@ -337,7 +342,14 @@ public class HwmfDraw {
@Override
public void draw(HwmfGraphics ctx) {
int windingRule = ctx.getProperties().getPolyfillMode().awtFlag;
Area area = new Area();
for (Path2D poly : polyList) {
Path2D p = (Path2D)poly.clone();
p.setWindingRule(windingRule);
area.add(new Area(p));
}
ctx.draw(area);
}
}

View File

@ -38,6 +38,42 @@ public class HwmfFill {
BufferedImage getImage();
}
/**
* The ColorUsage Enumeration (a 16-bit unsigned integer) specifies whether a color table
* exists in a device-independent bitmap (DIB) and how to interpret its values,
* i.e. if contains explicit RGB values or indexes into a palette.
*/
public enum ColorUsage {
/**
* The color table contains RGB values
*/
DIB_RGB_COLORS(0x0000),
/**
* The color table contains 16-bit indices into the current logical palette in
* the playback device context.
*/
DIB_PAL_COLORS(0x0001),
/**
* No color table exists. The pixels in the DIB are indices into the current
* logical palette in the playback device context.
*/
DIB_PAL_INDICES(0x0002)
;
int flag;
ColorUsage(int flag) {
this.flag = flag;
}
static ColorUsage valueOf(int flag) {
for (ColorUsage bs : values()) {
if (bs.flag == flag) return bs;
}
return null;
}
}
/**
* The META_FILLREGION record fills a region using a specified brush.
@ -482,12 +518,8 @@ public class HwmfFill {
/**
* A 16-bit unsigned integer that defines whether the Colors field of the
* DIB contains explicit RGB values or indexes into a palette.
* This value MUST be in the ColorUsage Enumeration:
* DIB_RGB_COLORS = 0x0000,
* DIB_PAL_COLORS = 0x0001,
* DIB_PAL_INDICES = 0x0002
*/
private int colorUsage;
private ColorUsage colorUsage;
/**
* A 16-bit signed integer that defines the height, in logical units, of the
* source rectangle.
@ -548,7 +580,7 @@ public class HwmfFill {
rasterOperation = HwmfTernaryRasterOp.valueOf(rasterOpIndex);
assert(rasterOpCode == rasterOperation.opCode);
colorUsage = leis.readUShort();
colorUsage = ColorUsage.valueOf(leis.readUShort());
srcHeight = leis.readShort();
srcWidth = leis.readShort();
ySrc = leis.readShort();
@ -679,12 +711,8 @@ public class HwmfFill {
/**
* A 16-bit unsigned integer that defines whether the Colors field of the
* DIB contains explicit RGB values or indexes into a palette.
* This MUST be one of the values in the ColorUsage Enumeration:
* DIB_RGB_COLORS = 0x0000,
* DIB_PAL_COLORS = 0x0001,
* DIB_PAL_INDICES = 0x0002
*/
private int colorUsage;
private ColorUsage colorUsage;
/**
* A 16-bit unsigned integer that defines the number of scan lines in the source.
*/
@ -736,7 +764,7 @@ public class HwmfFill {
@Override
public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException {
colorUsage = leis.readUShort();
colorUsage = ColorUsage.valueOf(leis.readUShort());
scanCount = leis.readUShort();
startScan = leis.readUShort();
yDib = leis.readUShort();

View File

@ -497,4 +497,64 @@ public class HwmfFont {
return 5*LittleEndianConsts.SHORT_SIZE+8*LittleEndianConsts.BYTE_SIZE+readBytes;
}
public int getHeight() {
return height;
}
public int getWidth() {
return width;
}
public int getEscapement() {
return escapement;
}
public int getOrientation() {
return orientation;
}
public int getWeight() {
return weight;
}
public boolean isItalic() {
return italic;
}
public boolean isUnderline() {
return underline;
}
public boolean isStrikeOut() {
return strikeOut;
}
public WmfCharset getCharSet() {
return charSet;
}
public WmfOutPrecision getOutPrecision() {
return outPrecision;
}
public WmfClipPrecision getClipPrecision() {
return clipPrecision;
}
public WmfFontQuality getQuality() {
return quality;
}
public WmfFontFamilyClass getFamily() {
return family;
}
public WmfFontPitch getPitch() {
return pitch;
}
public String getFacename() {
return facename;
}
}

View File

@ -22,6 +22,7 @@ import java.io.IOException;
import org.apache.poi.hwmf.draw.HwmfDrawProperties;
import org.apache.poi.hwmf.draw.HwmfGraphics;
import org.apache.poi.hwmf.record.HwmfFill.ColorUsage;
import org.apache.poi.hwmf.record.HwmfFill.HwmfImageRecord;
import org.apache.poi.util.LittleEndianConsts;
import org.apache.poi.util.LittleEndianInputStream;
@ -349,12 +350,9 @@ public class HwmfMisc {
* If the Style field specifies BS_PATTERN, a ColorUsage value of DIB_RGB_COLORS MUST be
* used regardless of the contents of this field.
*
* If the Style field specified anything but BS_PATTERN, this field MUST be one of the values:
* DIB_RGB_COLORS = 0x0000,
* DIB_PAL_COLORS = 0x0001,
* DIB_PAL_INDICES = 0x0002
* If the Style field specified anything but BS_PATTERN, this field MUST be one of the ColorUsage values.
*/
private int colorUsage;
private ColorUsage colorUsage;
private HwmfBitmapDib patternDib;
private HwmfBitmap16 pattern16;
@ -367,7 +365,7 @@ public class HwmfMisc {
@Override
public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException {
style = HwmfBrushStyle.valueOf(leis.readUShort());
colorUsage = leis.readUShort();
colorUsage = ColorUsage.valueOf(leis.readUShort());
int size = 2*LittleEndianConsts.SHORT_SIZE;
switch (style) {
case BS_SOLID:

View File

@ -19,68 +19,78 @@ package org.apache.poi.hwmf.record;
import java.awt.Color;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import org.apache.poi.hwmf.draw.HwmfDrawProperties;
import org.apache.poi.hwmf.draw.HwmfGraphics;
import org.apache.poi.util.BitField;
import org.apache.poi.util.BitFieldFactory;
import org.apache.poi.util.LittleEndianConsts;
import org.apache.poi.util.LittleEndianInputStream;
public class HwmfPalette {
public static class PaletteEntry {
enum PaletteEntryFlag {
/**
* Specifies that the logical palette entry be used for palette animation. This value
* prevents other windows from matching colors to the palette entry because the color frequently
* changes. If an unused system-palette entry is available, the color is placed in that entry.
* Otherwise, the color is not available for animation.
*/
PC_RESERVED(0x01),
/**
* Specifies that the low-order word of the logical palette entry designates a hardware
* palette index. This value allows the application to show the contents of the display device palette.
*/
PC_EXPLICIT(0x02),
/**
* Specifies that the color be placed in an unused entry in the system palette
* instead of being matched to an existing color in the system palette. If there are no unused entries
* in the system palette, the color is matched normally. Once this color is in the system palette,
* colors in other logical palettes can be matched to this color.
*/
PC_NOCOLLAPSE(0x04)
;
private static final BitField PC_RESERVED = BitFieldFactory.getInstance(0x01);
private static final BitField PC_EXPLICIT = BitFieldFactory.getInstance(0x02);
private static final BitField PC_NOCOLLAPSE = BitFieldFactory.getInstance(0x04);
int flag;
PaletteEntryFlag(int flag) {
this.flag = flag;
}
static PaletteEntryFlag valueOf(int flag) {
for (PaletteEntryFlag pef : values()) {
if (pef.flag == flag) return pef;
}
return null;
}
}
// Values (1 byte): An 8-bit unsigned integer that defines how the palette entry is to be used.
// The Values field MUST be 0x00 or one of the values in the PaletteEntryFlag Enumeration table.
// Blue (1 byte): An 8-bit unsigned integer that defines the blue intensity value for the palette entry.
// Green (1 byte): An 8-bit unsigned integer that defines the green intensity value for the palette entry.
// Red (1 byte): An 8-bit unsigned integer that defines the red intensity value for the palette entry.
private PaletteEntryFlag values;
private int values;
private Color colorRef;
private PaletteEntry() {
this.values = PC_RESERVED.set(0);
this.colorRef = Color.BLACK;
}
private PaletteEntry(PaletteEntry other) {
this.values = other.values;
this.colorRef = other.colorRef;
}
public int init(LittleEndianInputStream leis) throws IOException {
values = PaletteEntryFlag.valueOf(leis.readUByte());
// Values (1 byte): An 8-bit unsigned integer that defines how the palette entry is to be used.
// The Values field MUST be 0x00 or one of the values in the PaletteEntryFlag Enumeration table.
values = leis.readUByte();
// Blue (1 byte): An 8-bit unsigned integer that defines the blue intensity value for the palette entry.
int blue = leis.readUByte();
// Green (1 byte): An 8-bit unsigned integer that defines the green intensity value for the palette entry.
int green = leis.readUByte();
// Red (1 byte): An 8-bit unsigned integer that defines the red intensity value for the palette entry.
int red = leis.readUByte();
colorRef = new Color(red, green, blue);
return 4*LittleEndianConsts.BYTE_SIZE;
}
/**
* Specifies that the logical palette entry be used for palette animation. This value
* prevents other windows from matching colors to the palette entry because the color frequently
* changes. If an unused system-palette entry is available, the color is placed in that entry.
* Otherwise, the color is not available for animation.
*/
public boolean isReserved() {
return PC_RESERVED.isSet(values);
}
/**
* Specifies that the low-order word of the logical palette entry designates a hardware
* palette index. This value allows the application to show the contents of the display device palette.
*/
public boolean isExplicit() {
return PC_EXPLICIT.isSet(values);
}
/**
* Specifies that the color be placed in an unused entry in the system palette
* instead of being matched to an existing color in the system palette. If there are no unused entries
* in the system palette, the color is matched normally. Once this color is in the system palette,
* colors in other logical palettes can be matched to this color.
*/
public boolean isNoCollapse() {
return PC_NOCOLLAPSE.isSet(values);
}
}
public static abstract class WmfPaletteParent implements HwmfRecord {
@ -92,30 +102,35 @@ public class HwmfPalette {
*/
private int start;
/**
* NumberOfEntries (2 bytes): A 16-bit unsigned integer that defines the number of objects in
* aPaletteEntries.
*/
private int numberOfEntries;
private PaletteEntry entries[];
private List<PaletteEntry> palette = new ArrayList<PaletteEntry>();
@Override
public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException {
start = leis.readUShort();
numberOfEntries = leis.readUShort();
/**
* NumberOfEntries (2 bytes): A 16-bit unsigned integer that defines the number of objects in
* aPaletteEntries.
*/
int numberOfEntries = leis.readUShort();
int size = 2*LittleEndianConsts.SHORT_SIZE;
entries = new PaletteEntry[numberOfEntries];
for (int i=0; i<numberOfEntries; i++) {
entries[i] = new PaletteEntry();
size += entries[i].init(leis);
PaletteEntry pe = new PaletteEntry();
size += pe.init(leis);
palette.add(pe);
}
return size;
}
@Override
public void draw(HwmfGraphics ctx) {
protected List<PaletteEntry> getPaletteCopy() {
List<PaletteEntry> newPalette = new ArrayList<PaletteEntry>();
for (PaletteEntry et : palette) {
newPalette.add(new PaletteEntry(et));
}
return newPalette;
}
protected int getPaletteStart() {
return start;
}
}
@ -135,7 +150,7 @@ public class HwmfPalette {
@Override
public void applyObject(HwmfGraphics ctx) {
ctx.getProperties().setPalette(getPaletteCopy());
}
}
@ -151,7 +166,25 @@ public class HwmfPalette {
@Override
public void draw(HwmfGraphics ctx) {
HwmfDrawProperties props = ctx.getProperties();
List<PaletteEntry> palette = props.getPalette();
if (palette == null) {
palette = new ArrayList<PaletteEntry>();
}
int start = getPaletteStart();
for (int i=palette.size(); i<start; i++) {
palette.add(new PaletteEntry());
}
int index = start;
for (PaletteEntry palCopy : getPaletteCopy()) {
if (palette.size() <= index) {
palette.add(palCopy);
} else {
palette.set(index, palCopy);
}
index++;
}
props.setPalette(palette);
}
}
@ -179,7 +212,16 @@ public class HwmfPalette {
@Override
public void draw(HwmfGraphics ctx) {
HwmfDrawProperties props = ctx.getProperties();
List<PaletteEntry> palette = props.getPalette();
if (palette == null) {
palette = new ArrayList<PaletteEntry>();
}
for (int i=palette.size(); i<numberOfEntries; i++) {
palette.add(new PaletteEntry());
}
palette = palette.subList(0, numberOfEntries);
props.setPalette(palette);
}
}
@ -191,7 +233,7 @@ public class HwmfPalette {
* A 16-bit unsigned integer used to index into the WMF Object Table to get
* the Palette Object to be selected.
*/
private int palette;
private int paletteIndex;
@Override
public HwmfRecordType getRecordType() {
@ -200,13 +242,13 @@ public class HwmfPalette {
@Override
public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException {
palette = leis.readUShort();
paletteIndex = leis.readUShort();
return LittleEndianConsts.SHORT_SIZE;
}
@Override
public void draw(HwmfGraphics ctx) {
ctx.applyObjectTableEntry(paletteIndex);
}
}
@ -251,7 +293,28 @@ public class HwmfPalette {
@Override
public void draw(HwmfGraphics ctx) {
HwmfDrawProperties props = ctx.getProperties();
List<PaletteEntry> dest = props.getPalette();
List<PaletteEntry> src = getPaletteCopy();
int start = getPaletteStart();
if (dest == null) {
dest = new ArrayList<PaletteEntry>();
}
for (int i=dest.size(); i<start; i++) {
dest.add(new PaletteEntry());
}
for (int i=0; i<src.size(); i++) {
PaletteEntry pe = src.get(i);
if (dest.size() <= start+i) {
dest.add(pe);
} else {
PaletteEntry peDst = dest.get(start+i);
if (peDst.isReserved()) {
dest.set(start+i, pe);
}
}
}
props.setPalette(dest);
}
}
}

View File

@ -86,7 +86,7 @@ public class HwmfPenStyle implements Cloneable {
/**
* The pen is solid.
*/
SOLID(0x0000, 10),
SOLID(0x0000, null),
/**
* The pen is dashed. (-----)
*/
@ -106,19 +106,19 @@ public class HwmfPenStyle implements Cloneable {
/**
* The pen is invisible.
*/
NULL(0x0005),
NULL(0x0005, null),
/**
* The pen is solid. When this pen is used in any drawing record that takes a
* bounding rectangle, the dimensions of the figure are shrunk so that it fits
* entirely in the bounding rectangle, taking into account the width of the pen.
*/
INSIDEFRAME(0x0006, 10),
INSIDEFRAME(0x0006, null),
/**
* The pen uses a styling array supplied by the user.
* (this is currently not supported and drawn as solid ... no idea where the user
* styling is supposed to come from ...)
*/
USERSTYLE(0x0007, 10);
USERSTYLE(0x0007, null);
public int wmfFlag;

View File

@ -27,6 +27,7 @@ public class HwmfPlaceableHeader {
public static int WMF_HEADER_MAGIC = 0x9AC6CDD7;
final Rectangle2D bounds;
final int unitsPerInch;
protected HwmfPlaceableHeader(LittleEndianInputStream leis) throws IOException {
/*
@ -53,7 +54,7 @@ public class HwmfPlaceableHeader {
* Thus, a value of 720 specifies that the image SHOULD be rendered at twice its normal size,
* and a value of 2880 specifies that the image SHOULD be rendered at half its normal size.
*/
int inch = leis.readShort();
unitsPerInch = leis.readShort();
/*
* Reserved (4 bytes): A field that is not used and MUST be set to 0x00000000.
@ -82,4 +83,8 @@ public class HwmfPlaceableHeader {
public Rectangle2D getBounds() {
return bounds;
}
public int getUnitsPerInch() {
return unitsPerInch;
}
}

View File

@ -17,10 +17,15 @@
package org.apache.poi.hwmf.record;
import java.awt.geom.Rectangle2D;
import java.io.IOException;
import java.text.AttributedString;
import org.apache.poi.hwmf.draw.HwmfDrawProperties;
import org.apache.poi.hwmf.draw.HwmfGraphics;
import org.apache.poi.hwmf.record.HwmfMisc.WmfSetMapMode;
import org.apache.poi.util.BitField;
import org.apache.poi.util.BitFieldFactory;
import org.apache.poi.util.LittleEndianConsts;
import org.apache.poi.util.LittleEndianInputStream;
import org.apache.poi.util.LocaleUtil;
@ -80,7 +85,7 @@ public class HwmfText {
@Override
public void draw(HwmfGraphics ctx) {
ctx.getProperties().setTextColor(colorRef);
}
}
@ -168,7 +173,8 @@ public class HwmfText {
@Override
public void draw(HwmfGraphics ctx) {
Rectangle2D bounds = new Rectangle2D.Double(xStart, yStart, 0, 0);
ctx.drawString(text, bounds);
}
}
@ -304,73 +310,124 @@ public class HwmfText {
}
}
public enum HwmfTextAlignment {
LEFT,
RIGHT,
CENTER;
}
public enum HwmfTextVerticalAlignment {
TOP,
BOTTOM,
BASELINE;
}
/**
* The META_SETTEXTALIGN record defines text-alignment values in the playback device context.
*/
public static class WmfSetTextAlign implements HwmfRecord {
// ***********************************************************************************
// TextAlignmentMode Flags:
// ***********************************************************************************
/**
* The drawing position in the playback device context MUST NOT be updated after each
* text output call. The reference point MUST be passed to the text output function.
*/
@SuppressWarnings("unused")
private static final BitField TA_NOUPDATECP = BitFieldFactory.getInstance(0x0000);
/**
* The reference point MUST be on the left edge of the bounding rectangle.
*/
@SuppressWarnings("unused")
private static final BitField TA_LEFT = BitFieldFactory.getInstance(0x0000);
/**
* The reference point MUST be on the top edge of the bounding rectangle.
*/
@SuppressWarnings("unused")
private static final BitField TA_TOP = BitFieldFactory.getInstance(0x0000);
/**
* The drawing position in the playback device context MUST be updated after each text
* output call. It MUST be used as the reference point.
*/
@SuppressWarnings("unused")
private static final BitField TA_UPDATECP = BitFieldFactory.getInstance(0x0001);
/**
* The reference point MUST be on the right edge of the bounding rectangle.
*/
private static final BitField TA_RIGHT = BitFieldFactory.getInstance(0x0002);
/**
* The reference point MUST be aligned horizontally with the center of the bounding
* rectangle.
*/
private static final BitField TA_CENTER = BitFieldFactory.getInstance(0x0006);
/**
* The reference point MUST be on the bottom edge of the bounding rectangle.
*/
private static final BitField TA_BOTTOM = BitFieldFactory.getInstance(0x0008);
/**
* The reference point MUST be on the baseline of the text.
*/
private static final BitField TA_BASELINE = BitFieldFactory.getInstance(0x0018);
/**
* The text MUST be laid out in right-to-left reading order, instead of the default
* left-to-right order. This SHOULD be applied only when the font that is defined in the
* playback device context is either Hebrew or Arabic.
*/
@SuppressWarnings("unused")
private static final BitField TA_RTLREADING = BitFieldFactory.getInstance(0x0100);
// ***********************************************************************************
// VerticalTextAlignmentMode Flags (e.g. for Kanji fonts)
// ***********************************************************************************
/**
* The reference point MUST be on the top edge of the bounding rectangle.
*/
@SuppressWarnings("unused")
private static final BitField VTA_TOP = BitFieldFactory.getInstance(0x0000);
/**
* The reference point MUST be on the right edge of the bounding rectangle.
*/
@SuppressWarnings("unused")
private static final BitField VTA_RIGHT = BitFieldFactory.getInstance(0x0000);
/**
* The reference point MUST be on the bottom edge of the bounding rectangle.
*/
private static final BitField VTA_BOTTOM = BitFieldFactory.getInstance(0x0002);
/**
* The reference point MUST be aligned vertically with the center of the bounding
* rectangle.
*/
private static final BitField VTA_CENTER = BitFieldFactory.getInstance(0x0006);
/**
* The reference point MUST be on the left edge of the bounding rectangle.
*/
private static final BitField VTA_LEFT = BitFieldFactory.getInstance(0x0008);
/**
* The reference point MUST be on the baseline of the text.
*/
private static final BitField VTA_BASELINE = BitFieldFactory.getInstance(0x0018);
/**
* A 16-bit unsigned integer that defines text alignment.
* This value MUST be a combination of one or more TextAlignmentMode Flags
* for text with a horizontal baseline, and VerticalTextAlignmentMode Flags
* for text with a vertical baseline.
*
* TextAlignmentMode Flags:
* TA_NOUPDATECP (0x0000):
* The drawing position in the playback device context MUST NOT be updated after each
* text output call. The reference point MUST be passed to the text output function.
*
* TA_LEFT (0x0000):
* The reference point MUST be on the left edge of the bounding rectangle.
*
* TA_TOP (0x0000):
* The reference point MUST be on the top edge of the bounding rectangle.
*
* TA_UPDATECP (0x0001):
* The drawing position in the playback device context MUST be updated after each text
* output call. It MUST be used as the reference point.
*
* TA_RIGHT (0x0002):
* The reference point MUST be on the right edge of the bounding rectangle.
*
* TA_CENTER (0x0006):
* The reference point MUST be aligned horizontally with the center of the bounding
* rectangle.
*
* TA_BOTTOM (0x0008):
* The reference point MUST be on the bottom edge of the bounding rectangle.
*
* TA_BASELINE (0x0018):
* The reference point MUST be on the baseline of the text.
*
* TA_RTLREADING (0x0100):
* The text MUST be laid out in right-to-left reading order, instead of the default
* left-toright order. This SHOULD be applied only when the font that is defined in the
* playback device context is either Hebrew or Arabic.
*
*
* VerticalTextAlignmentMode Flags (e.g. for Kanji fonts)
* VTA_TOP (0x0000):
* The reference point MUST be on the top edge of the bounding rectangle.
*
* VTA_RIGHT (0x0000):
* The reference point MUST be on the right edge of the bounding rectangle.
*
* VTA_BOTTOM (0x0002):
* The reference point MUST be on the bottom edge of the bounding rectangle.
*
* VTA_CENTER (0x0006):
* The reference point MUST be aligned vertically with the center of the bounding
* rectangle.
*
* VTA_LEFT (0x0008):
* The reference point MUST be on the left edge of the bounding rectangle.
*
* VTA_BASELINE (0x0018):
* The reference point MUST be on the baseline of the text.
*/
private int textAlignmentMode;
@ -387,7 +444,38 @@ public class HwmfText {
@Override
public void draw(HwmfGraphics ctx) {
HwmfDrawProperties props = ctx.getProperties();
if (TA_CENTER.isSet(textAlignmentMode)) {
props.setTextAlignLatin(HwmfTextAlignment.CENTER);
} else if (TA_RIGHT.isSet(textAlignmentMode)) {
props.setTextAlignLatin(HwmfTextAlignment.RIGHT);
} else {
props.setTextAlignLatin(HwmfTextAlignment.LEFT);
}
if (VTA_CENTER.isSet(textAlignmentMode)) {
props.setTextAlignAsian(HwmfTextAlignment.CENTER);
} else if (VTA_LEFT.isSet(textAlignmentMode)) {
props.setTextAlignAsian(HwmfTextAlignment.LEFT);
} else {
props.setTextAlignAsian(HwmfTextAlignment.RIGHT);
}
if (TA_BASELINE.isSet(textAlignmentMode)) {
props.setTextVAlignLatin(HwmfTextVerticalAlignment.BASELINE);
} else if (TA_BOTTOM.isSet(textAlignmentMode)) {
props.setTextVAlignLatin(HwmfTextVerticalAlignment.BOTTOM);
} else {
props.setTextVAlignLatin(HwmfTextVerticalAlignment.TOP);
}
if (VTA_BASELINE.isSet(textAlignmentMode)) {
props.setTextVAlignAsian(HwmfTextVerticalAlignment.BASELINE);
} else if (VTA_BOTTOM.isSet(textAlignmentMode)) {
props.setTextVAlignAsian(HwmfTextVerticalAlignment.BOTTOM);
} else {
props.setTextVAlignAsian(HwmfTextVerticalAlignment.TOP);
}
}
}
@ -412,7 +500,7 @@ public class HwmfText {
@Override
public void applyObject(HwmfGraphics ctx) {
ctx.getProperties().setFont(font);
}
}
}

View File

@ -17,9 +17,12 @@
package org.apache.poi.hwmf.usermodel;
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.GraphicsConfiguration;
import java.awt.geom.AffineTransform;
import java.awt.geom.Rectangle2D;
import java.awt.geom.Rectangle2D.Double;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
@ -35,6 +38,7 @@ import org.apache.poi.hwmf.record.HwmfRecordType;
import org.apache.poi.hwmf.record.HwmfWindowing.WmfSetWindowExt;
import org.apache.poi.hwmf.record.HwmfWindowing.WmfSetWindowOrg;
import org.apache.poi.util.LittleEndianInputStream;
import org.apache.poi.util.Units;
public class HwmfPicture {
final List<HwmfRecord> records = new ArrayList<HwmfRecord>();
@ -85,9 +89,23 @@ public class HwmfPicture {
}
public void draw(Graphics2D ctx) {
Dimension dim = getSize();
int width = Units.pointsToPixel(dim.getWidth());
// keep aspect ratio for height
int height = Units.pointsToPixel(dim.getHeight());
Rectangle2D bounds = new Rectangle2D.Double(0,0,width,height);
draw(ctx, bounds);
}
public void draw(Graphics2D ctx, Rectangle2D graphicsBounds) {
AffineTransform at = ctx.getTransform();
try {
HwmfGraphics g = new HwmfGraphics(ctx, getBounds());
Rectangle2D wmfBounds = getBounds();
// scale output bounds to image bounds
ctx.scale(graphicsBounds.getWidth()/wmfBounds.getWidth(), graphicsBounds.getHeight()/wmfBounds.getHeight());
ctx.translate(-wmfBounds.getX(), -wmfBounds.getY());
HwmfGraphics g = new HwmfGraphics(ctx, wmfBounds);
for (HwmfRecord r : records) {
r.draw(g);
}
@ -131,4 +149,18 @@ public class HwmfPicture {
public HwmfHeader getHeader() {
return header;
}
/**
* Return the image size in points
*
* @return the image size in points
*/
public Dimension getSize() {
double inch = (placeableHeader == null) ? 1440 : placeableHeader.getUnitsPerInch();
Rectangle2D bounds = getBounds();
//coefficient to translate from WMF dpi to 72dpi
double coeff = Units.POINT_DPI/inch;
return new Dimension((int)Math.round(bounds.getWidth()*coeff), (int)Math.round(bounds.getHeight()*coeff));
}
}

View File

@ -19,9 +19,9 @@ package org.apache.poi.hwmf;
import static org.junit.Assert.assertEquals;
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileFilter;
@ -45,6 +45,7 @@ import org.apache.poi.sl.usermodel.PictureData;
import org.apache.poi.sl.usermodel.PictureData.PictureType;
import org.apache.poi.sl.usermodel.SlideShow;
import org.apache.poi.sl.usermodel.SlideShowFactory;
import org.apache.poi.util.Units;
import org.junit.Ignore;
import org.junit.Test;
@ -60,17 +61,18 @@ public class TestHwmfParsing {
}
@Test
@Ignore
@Ignore("This is work-in-progress and not a real unit test ...")
public void paint() throws IOException {
File f = POIDataSamples.getSlideShowInstance().getFile("santa.wmf");
// File f = new File("E:\\project\\poi\\misc\\govdocs-ppt", "000133-0001.wmf");
FileInputStream fis = new FileInputStream(f);
HwmfPicture wmf = new HwmfPicture(fis);
fis.close();
Rectangle2D bounds = wmf.getBounds();
int width = 300;
Dimension dim = wmf.getSize();
int width = Units.pointsToPixel(dim.getWidth());
// keep aspect ratio for height
int height = (int)(width*bounds.getHeight()/bounds.getWidth());
int height = Units.pointsToPixel(dim.getHeight());
BufferedImage bufImg = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
Graphics2D g = bufImg.createGraphics();
@ -87,7 +89,7 @@ public class TestHwmfParsing {
}
@Test
@Ignore
@Ignore("This is work-in-progress and not a real unit test ...")
public void fetchWmfFromGovdocs() throws IOException {
URL url = new URL("http://digitalcorpora.org/corpora/files/govdocs1/by_type/ppt.zip");
File outdir = new File("build/ppt");
@ -119,10 +121,14 @@ public class TestHwmfParsing {
}
@Test
@Ignore
@Ignore("This is work-in-progress and not a real unit test ...")
public void parseWmfs() throws IOException {
// parse and render the extracted wmfs from the fetchWmfFromGovdocs step
boolean outputFiles = false;
File indir = new File("build/ppt"), outdir = indir;
boolean renderWmf = true;
File indir = new File("E:\\project\\poi\\misc\\govdocs-ppt");
File outdir = new File("build/wmf");
outdir.mkdirs();
final String startFile = "";
File files[] = indir.listFiles(new FileFilter() {
boolean foundStartFile = false;
@ -149,6 +155,26 @@ public class TestHwmfParsing {
bmpIndex++;
}
}
if (renderWmf) {
Dimension dim = wmf.getSize();
int width = Units.pointsToPixel(dim.getWidth());
// keep aspect ratio for height
int height = Units.pointsToPixel(dim.getHeight());
BufferedImage bufImg = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
Graphics2D g = bufImg.createGraphics();
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
g.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
wmf.draw(g);
g.dispose();
ImageIO.write(bufImg, "PNG", new File(outdir, basename+".png"));
}
} catch (Exception e) {
System.out.println(f.getName()+" ignored.");
}