WMF fixes

git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1722771 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Andreas Beeker 2016-01-03 22:46:57 +00:00
parent 0fe35af294
commit 6ec54a0177
15 changed files with 601 additions and 260 deletions

View File

@ -18,9 +18,13 @@
package org.apache.poi.hwmf.draw;
import java.awt.Color;
import java.awt.Shape;
import java.awt.geom.Area;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.WritableRaster;
import org.apache.poi.hwmf.record.HwmfBrushStyle;
import org.apache.poi.hwmf.record.HwmfColorRef;
@ -31,9 +35,9 @@ import org.apache.poi.hwmf.record.HwmfMisc.WmfSetBkMode.HwmfBkMode;
import org.apache.poi.hwmf.record.HwmfPenStyle;
public class HwmfDrawProperties {
private Rectangle2D window = new Rectangle2D.Double(0, 0, 1, 1);
private Rectangle2D viewport = new Rectangle2D.Double(0, 0, 1, 1);
private Point2D location = new Point2D.Double(0,0);
private final Rectangle2D window;
private Rectangle2D viewport = null;
private final Point2D location;
private HwmfMapMode mapMode = HwmfMapMode.MM_ANISOTROPIC;
private HwmfColorRef backgroundColor = new HwmfColorRef(Color.BLACK);
private HwmfBrushStyle brushStyle = HwmfBrushStyle.BS_SOLID;
@ -46,6 +50,41 @@ public class HwmfDrawProperties {
private double penMiterLimit = 10;
private HwmfBkMode bkMode = HwmfBkMode.OPAQUE;
private HwmfPolyfillMode polyfillMode = HwmfPolyfillMode.WINDING;
private Shape region = null;
public HwmfDrawProperties() {
window = new Rectangle2D.Double(0, 0, 1, 1);
viewport = null;
location = new Point2D.Double(0,0);
}
public HwmfDrawProperties(HwmfDrawProperties other) {
this.window = (Rectangle2D)other.window.clone();
this.viewport = (other.viewport == null) ? null : (Rectangle2D)other.viewport.clone();
this.location = (Point2D)other.location.clone();
this.mapMode = other.mapMode;
this.backgroundColor = (other.backgroundColor == null) ? null : other.backgroundColor.clone();
this.brushStyle = other.brushStyle;
this.brushColor = other.brushColor.clone();
this.brushHatch = other.brushHatch;
if (other.brushBitmap != null) {
ColorModel cm = other.brushBitmap.getColorModel();
boolean isAlphaPremultiplied = cm.isAlphaPremultiplied();
WritableRaster raster = other.brushBitmap.copyData(null);
this.brushBitmap = new BufferedImage(cm, raster, isAlphaPremultiplied, null);
}
this.penWidth = 1;
this.penStyle = (other.penStyle == null) ? null : other.penStyle.clone();
this.penColor = (other.penColor == null) ? null : other.penColor.clone();
this.penMiterLimit = other.penMiterLimit;
this.bkMode = other.bkMode;
this.polyfillMode = other.polyfillMode;
if (other.region instanceof Rectangle2D) {
this.region = ((Rectangle2D)other.region).getBounds2D();
} else if (other.region instanceof Area) {
this.region = new Area(other.region);
}
}
public void setViewportExt(double width, double height) {
double x = viewport.getX();
@ -62,7 +101,7 @@ public class HwmfDrawProperties {
}
public Rectangle2D getViewport() {
return (Rectangle2D)viewport.clone();
return (viewport == null) ? null : (Rectangle2D)viewport.clone();
}
public void setWindowExt(double width, double height) {
@ -186,4 +225,23 @@ public class HwmfDrawProperties {
public void setBrushBitmap(BufferedImage brushBitmap) {
this.brushBitmap = brushBitmap;
}
/**
* Gets the last stored region
*
* @return the last stored region
*/
public Shape getRegion() {
return region;
}
/**
* Sets a region for further usage
*
* @param region a region object which is usually a rectangle
*/
public void setRegion(Shape region) {
this.region = region;
}
}

View File

@ -28,25 +28,37 @@ import java.awt.geom.AffineTransform;
import java.awt.geom.GeneralPath;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import java.util.NoSuchElementException;
import org.apache.poi.hwmf.record.HwmfBrushStyle;
import org.apache.poi.hwmf.record.HwmfHatchStyle;
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;
private final Deque<HwmfDrawProperties> propStack = new ArrayDeque<HwmfDrawProperties>();
HwmfDrawProperties prop;
private final List<HwmfDrawProperties> propStack = new LinkedList<HwmfDrawProperties>();
private HwmfDrawProperties prop = new HwmfDrawProperties();
private List<HwmfObjectTableEntry> objectTable = new ArrayList<HwmfObjectTableEntry>();
/** Bounding box from the placeable header */
private final Rectangle2D bbox;
public HwmfGraphics(Graphics2D graphicsCtx) {
/**
* Initialize a graphics context for wmf rendering
*
* @param graphicsCtx the graphics context to delegate drawing calls
* @param bbox the bounding box of the wmf (taken from the placeable header)
*/
public HwmfGraphics(Graphics2D graphicsCtx, Rectangle2D bbox) {
this.graphicsCtx = graphicsCtx;
prop = new HwmfDrawProperties();
propStack.push(prop);
this.bbox = (Rectangle2D)bbox.clone();
}
public HwmfDrawProperties getProperties() {
@ -91,7 +103,11 @@ public class HwmfGraphics {
protected Shape fitShapeToView(Shape shape) {
int scaleUnits = prop.getMapMode().scale;
Rectangle2D view = prop.getViewport(), win = prop.getWindow();
Rectangle2D view = prop.getViewport();
Rectangle2D win = prop.getWindow();
if (view == null) {
view = win;
}
double scaleX, scaleY;
switch (scaleUnits) {
case -1:
@ -106,16 +122,20 @@ public class HwmfGraphics {
}
AffineTransform at = new AffineTransform();
at.translate(view.getX(), view.getY());
at.scale(scaleX, scaleY);
at.translate(-win.getX(), -win.getY());
at.translate(-view.getX(), -view.getY());
// at.translate(-view.getX(), -view.getY());
at.translate(bbox.getWidth()/win.getWidth(), bbox.getHeight()/win.getHeight());
return at.createTransformedShape(shape);
Shape tshape = at.createTransformedShape(shape);
return tshape;
}
protected BasicStroke getStroke() {
Rectangle2D view = prop.getViewport(), win = prop.getWindow();
Rectangle2D view = prop.getViewport();
Rectangle2D win = prop.getWindow();
if (view == null) {
view = win;
}
float width = (float)(prop.getPenWidth() * view.getWidth() / win.getWidth());
HwmfPenStyle ps = prop.getPenStyle();
int cap = ps.getLineCap().awtFlag;
@ -182,4 +202,87 @@ public class HwmfGraphics {
return (bi == null) ? null
: new TexturePaint(bi, new Rectangle(0,0,bi.getWidth(),bi.getHeight()));
}
/**
* Adds an record of type {@link HwmfObjectTableEntry} to the object table.
*
* Every object is assigned the lowest available index-that is, the smallest
* numerical value-in the WMF Object Table. This binding happens at object creation,
* not when the object is used.
* Moreover, each object table index uniquely refers to an object.
* Indexes in the WMF Object Table always start at 0.
*
* @param entry
*/
public void addObjectTableEntry(HwmfObjectTableEntry entry) {
ListIterator<HwmfObjectTableEntry> oIter = objectTable.listIterator();
while (oIter.hasNext()) {
HwmfObjectTableEntry tableEntry = oIter.next();
if (tableEntry == null) {
oIter.set(entry);
return;
}
}
objectTable.add(entry);
}
/**
* Applies the object table entry
*
* @param index the index of the object table entry (0-based)
*
* @throws IndexOutOfBoundsException if the index is out of range
* @throws NoSuchElementException if the entry was deleted before
*/
public void applyObjectTableEntry(int index) {
HwmfObjectTableEntry ote = objectTable.get(index);
if (ote == null) {
throw new NoSuchElementException("WMF reference exception - object table entry on index "+index+" was deleted before.");
}
ote.applyObject(this);
}
/**
* Unsets (deletes) the object table entry for further usage
*
* When a META_DELETEOBJECT record (section 2.3.4.7) is received that specifies this
* object's particular index, the object's resources are released, the binding to its
* WMF Object Table index is ended, and the index value is returned to the pool of
* available indexes. The index will be reused, if needed, by a subsequent object
* created by another Object Record Type record.
*
* @param index the index (0-based)
*
* @throws IndexOutOfBoundsException if the index is out of range
*/
public void unsetObjectTableEntry(int index) {
objectTable.set(index, null);
}
/**
* Saves the current properties to the stack
*/
public void saveProperties() {
propStack.add(prop);
prop = new HwmfDrawProperties(prop);
}
/**
* 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.
*/
public void restoreProperties(int index) {
if (index == 0) {
return;
}
int stackIndex = index;
if (stackIndex < 0) {
int curIdx = propStack.indexOf(prop);
assert (curIdx != -1);
stackIndex = curIdx + index;
}
prop = propStack.remove(stackIndex);
}
}

View File

@ -30,7 +30,7 @@ import org.apache.poi.util.LittleEndianInputStream;
* Blue (1 byte): An 8-bit unsigned integer that defines the relative intensity of blue.
* Reserved (1 byte): An 8-bit unsigned integer that MUST be 0x00.
*/
public class HwmfColorRef {
public class HwmfColorRef implements Cloneable {
private Color colorRef = Color.BLACK;
public HwmfColorRef() {}
@ -53,4 +53,21 @@ public class HwmfColorRef {
public Color getColor() {
return colorRef;
}
/**
* Creates a new object of the same class and with the
* same contents as this object.
* @return a clone of this instance.
* @exception OutOfMemoryError if there is not enough memory.
* @see java.lang.Cloneable
*/
@Override
public HwmfColorRef clone() {
try {
return (HwmfColorRef)super.clone();
} catch (CloneNotSupportedException e) {
// this shouldn't happen, since we are Cloneable
throw new InternalError();
}
}
}

View File

@ -18,9 +18,15 @@
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.Ellipse2D;
import java.awt.geom.GeneralPath;
import java.awt.geom.Line2D;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.awt.geom.RoundRectangle2D;
import java.io.IOException;
import org.apache.poi.hwmf.draw.HwmfGraphics;
@ -210,7 +216,12 @@ public class HwmfDraw {
@Override
public void draw(HwmfGraphics ctx) {
int x = Math.min(leftRect, rightRect);
int y = Math.min(topRect, bottomRect);
int w = Math.abs(leftRect - rightRect - 1);
int h = Math.abs(topRect - bottomRect - 1);
Shape s = new Ellipse2D.Double(x, y, w, h);
ctx.fill(s);
}
}
@ -372,7 +383,12 @@ public class HwmfDraw {
@Override
public void draw(HwmfGraphics ctx) {
int x = Math.min(leftRect, rightRect);
int y = Math.min(topRect, bottomRect);
int w = Math.abs(leftRect - rightRect - 1);
int h = Math.abs(topRect - bottomRect - 1);
Shape s = new Rectangle2D.Double(x, y, w, h);
ctx.fill(s);
}
}
@ -415,7 +431,8 @@ public class HwmfDraw {
@Override
public void draw(HwmfGraphics ctx) {
Shape s = new Rectangle2D.Double(x, y, 1, 1);
ctx.fill(s);
}
}
@ -479,84 +496,16 @@ public class HwmfDraw {
@Override
public void draw(HwmfGraphics ctx) {
int x = Math.min(leftRect, rightRect);
int y = Math.min(topRect, bottomRect);
int w = Math.abs(leftRect - rightRect - 1);
int h = Math.abs(topRect - bottomRect - 1);
Shape s = new RoundRectangle2D.Double(x, y, w, h, width, height);
ctx.fill(s);
}
}
/**
* The META_PIE record draws a pie-shaped wedge bounded by the intersection of an ellipse and two
* radials. The pie is outlined by using the pen and filled by using the brush that are defined in the
* playback device context.
*/
public static class WmfPie implements HwmfRecord {
/**
* A 16-bit signed integer that defines the y-coordinate, in logical
* coordinates, of the endpoint of the second radial.
*/
private int yRadial2;
/**
* A 16-bit signed integer that defines the x-coordinate, in logical
* coordinates, of the endpoint of the second radial.
*/
private int xRadial2;
/**
* A 16-bit signed integer that defines the y-coordinate, in logical
* coordinates, of the endpoint of the first radial.
*/
private int yRadial1;
/**
* A 16-bit signed integer that defines the x-coordinate, in logical
* coordinates, of the endpoint of the first radial.
*/
private int xRadial1;
/**
* A 16-bit signed integer that defines the y-coordinate, in logical units, of
* the lower-right corner of the bounding rectangle.
*/
private int bottomRect;
/**
* A 16-bit signed integer that defines the x-coordinate, in logical units, of
* the lower-right corner of the bounding rectangle.
*/
private int rightRect;
/**
* A 16-bit signed integer that defines the y-coordinate, in logical units, of the
* upper-left corner of the bounding rectangle.
*/
private int topRect;
/**
* A 16-bit signed integer that defines the x-coordinate, in logical units, of
* the upper-left corner of the bounding rectangle.
*/
private int leftRect;
@Override
public HwmfRecordType getRecordType() {
return HwmfRecordType.pie;
}
@Override
public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException {
yRadial2 = leis.readShort();
xRadial2 = leis.readShort();
yRadial1 = leis.readShort();
xRadial1 = leis.readShort();
bottomRect = leis.readShort();
rightRect = leis.readShort();
topRect = leis.readShort();
leftRect = leis.readShort();
return 8*LittleEndianConsts.SHORT_SIZE;
}
@Override
public void draw(HwmfGraphics ctx) {
}
}
/**
* The META_ARC record draws an elliptical arc.
*/
@ -622,7 +571,54 @@ public class HwmfDraw {
@Override
public void draw(HwmfGraphics ctx) {
int x = Math.min(leftRect, rightRect);
int y = Math.min(topRect, bottomRect);
int w = Math.abs(leftRect - rightRect - 1);
int h = Math.abs(topRect - bottomRect - 1);
double startAngle = Math.toDegrees(Math.atan2(-(yStartArc - (topRect + h / 2.)), xStartArc - (leftRect + w / 2.)));
double endAngle = Math.toDegrees(Math.atan2(-(yEndArc - (topRect + h / 2.)), xEndArc - (leftRect + w / 2.)));
double arcAngle = (endAngle - startAngle) + (endAngle - startAngle > 0 ? 0 : 360);
if (startAngle < 0) {
startAngle += 360;
}
boolean fillShape;
int arcClosure;
switch (getRecordType()) {
default:
case arc:
arcClosure = Arc2D.OPEN;
fillShape = false;
break;
case chord:
arcClosure = Arc2D.CHORD;
fillShape = true;
break;
case pie:
arcClosure = Arc2D.PIE;
fillShape = true;
break;
}
Shape s = new Arc2D.Double(x, y, w, h, startAngle, arcAngle, arcClosure);
if (fillShape) {
ctx.fill(s);
} else {
ctx.draw(s);
}
}
}
/**
* The META_PIE record draws a pie-shaped wedge bounded by the intersection of an ellipse and two
* radials. The pie is outlined by using the pen and filled by using the brush that are defined in the
* playback device context.
*/
public static class WmfPie extends WmfArc {
@Override
public HwmfRecordType getRecordType() {
return HwmfRecordType.pie;
}
}
@ -631,73 +627,14 @@ public class HwmfDraw {
* an ellipse with a line segment. The chord is outlined using the pen and filled using the brush
* that are defined in the playback device context.
*/
public static class WmfChord implements HwmfRecord {
/**
* A 16-bit signed integer that defines the y-coordinate, in logical
* coordinates, of the endpoint of the second radial.
*/
private int yRadial2;
/**
* A 16-bit signed integer that defines the x-coordinate, in logical
* coordinates, of the endpoint of the second radial.
*/
private int xRadial2;
/**
* A 16-bit signed integer that defines the y-coordinate, in logical
* coordinates, of the endpoint of the first radial.
*/
private int yRadial1;
/**
* A 16-bit signed integer that defines the x-coordinate, in logical
* coordinates, of the endpoint of the first radial.
*/
private int xRadial1;
/**
* A 16-bit signed integer that defines the y-coordinate, in logical units, of
* the lower-right corner of the bounding rectangle.
*/
private int bottomRect;
/**
* A 16-bit signed integer that defines the x-coordinate, in logical units, of
* the lower-right corner of the bounding rectangle.
*/
private int rightRect;
/**
* A 16-bit signed integer that defines the y-coordinate, in logical units, of the
* upper-left corner of the bounding rectangle.
*/
private int topRect;
/**
* A 16-bit signed integer that defines the x-coordinate, in logical units, of
* the upper-left corner of the bounding rectangle.
*/
private int leftRect;
public static class WmfChord extends WmfArc {
@Override
public HwmfRecordType getRecordType() {
return HwmfRecordType.chord;
}
@Override
public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException {
yRadial2 = leis.readShort();
xRadial2 = leis.readShort();
yRadial1 = leis.readShort();
xRadial1 = leis.readShort();
bottomRect = leis.readShort();
rightRect = leis.readShort();
topRect = leis.readShort();
leftRect = leis.readShort();
return 8*LittleEndianConsts.SHORT_SIZE;
}
@Override
public void draw(HwmfGraphics ctx) {
}
}
/**
* The META_SELECTOBJECT record specifies a graphics object for the playback device context. The

View File

@ -17,6 +17,7 @@
package org.apache.poi.hwmf.record;
import java.awt.Shape;
import java.awt.geom.Path2D;
import java.awt.image.BufferedImage;
import java.io.File;
@ -25,6 +26,7 @@ import java.io.IOException;
import javax.imageio.ImageIO;
import org.apache.poi.hwmf.draw.HwmfGraphics;
import org.apache.poi.hwmf.record.HwmfWindowing.WmfCreateRegion;
import org.apache.poi.util.LittleEndianConsts;
import org.apache.poi.util.LittleEndianInputStream;
@ -46,13 +48,13 @@ public class HwmfFill {
* A 16-bit unsigned integer used to index into the WMF Object Table to get
* the region to be filled.
*/
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;
@Override
public HwmfRecordType getRecordType() {
@ -61,13 +63,20 @@ public class HwmfFill {
@Override
public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException {
region = leis.readUShort();
brush = leis.readUShort();
regionIndex = leis.readUShort();
brushIndex = leis.readUShort();
return 2*LittleEndianConsts.SHORT_SIZE;
}
@Override
public void draw(HwmfGraphics ctx) {
ctx.applyObjectTableEntry(regionIndex);
ctx.applyObjectTableEntry(brushIndex);
Shape region = ctx.getProperties().getRegion();
if (region != null) {
ctx.fill(region);
}
}
}
@ -82,20 +91,25 @@ public class HwmfFill {
* A 16-bit unsigned integer used to index into the WMF Object Table to get
* the region to be painted.
*/
int region;
int regionIndex;
public HwmfRecordType getRecordType() {
return HwmfRecordType.paintRegion;
}
public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException {
region = leis.readUShort();
regionIndex = leis.readUShort();
return LittleEndianConsts.SHORT_SIZE;
}
@Override
public void draw(HwmfGraphics ctx) {
ctx.applyObjectTableEntry(regionIndex);
Shape region = ctx.getProperties().getRegion();
if (region != null) {
ctx.fill(region);
}
}
}

View File

@ -44,7 +44,7 @@ public class HwmfMisc {
@Override
public void draw(HwmfGraphics ctx) {
ctx.saveProperties();
}
}
@ -92,7 +92,7 @@ public class HwmfMisc {
@Override
public void draw(HwmfGraphics ctx) {
ctx.restoreProperties(nSavedDC);
}
}
@ -431,11 +431,11 @@ public class HwmfMisc {
@Override
public void draw(HwmfGraphics ctx) {
ctx.unsetObjectTableEntry(objectIndex);
}
}
public static class WmfCreatePatternBrush implements HwmfRecord {
public static class WmfCreatePatternBrush implements HwmfRecord, HwmfObjectTableEntry {
private HwmfBitmap16 pattern;
@ -452,16 +452,23 @@ public class HwmfMisc {
@Override
public void draw(HwmfGraphics ctx) {
ctx.addObjectTableEntry(this);
}
@Override
public void applyObject(HwmfGraphics ctx) {
HwmfDrawProperties dp = ctx.getProperties();
dp.setBrushBitmap(pattern.getImage());
dp.setBrushStyle(HwmfBrushStyle.BS_PATTERN);
}
}
public static class WmfCreatePenIndirect implements HwmfRecord {
public static class WmfCreatePenIndirect implements HwmfRecord, HwmfObjectTableEntry {
private HwmfPenStyle penStyle;
/**
* A 32-bit PointS Object that specifies a point for the object dimensions.
* The xcoordinate is the pen width. The y-coordinate is ignored.
* The x-coordinate is the pen width. The y-coordinate is ignored.
*/
private int xWidth;
@SuppressWarnings("unused")
@ -488,6 +495,11 @@ public class HwmfMisc {
@Override
public void draw(HwmfGraphics ctx) {
ctx.addObjectTableEntry(this);
}
@Override
public void applyObject(HwmfGraphics ctx) {
HwmfDrawProperties p = ctx.getProperties();
p.setPenStyle(penStyle);
p.setPenColor(colorRef);
@ -540,7 +552,7 @@ public class HwmfMisc {
* </tr>
* </table>
*/
public static class WmfCreateBrushIndirect implements HwmfRecord {
public static class WmfCreateBrushIndirect implements HwmfRecord, HwmfObjectTableEntry {
private HwmfBrushStyle brushStyle;
private HwmfColorRef colorRef;
@ -568,6 +580,11 @@ public class HwmfMisc {
@Override
public void draw(HwmfGraphics ctx) {
ctx.addObjectTableEntry(this);
}
@Override
public void applyObject(HwmfGraphics ctx) {
HwmfDrawProperties p = ctx.getProperties();
p.setBrushStyle(brushStyle);
p.setBrushColor(colorRef);

View File

@ -0,0 +1,28 @@
/* ====================================================================
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.hwmf.draw.HwmfGraphics;
/**
* Marker interface for Records, which should be added to the
* WMF object table for further selection
*/
public interface HwmfObjectTableEntry {
public void applyObject(HwmfGraphics ctx);
}

View File

@ -81,7 +81,7 @@ public class HwmfPalette {
/**
* The META_CREATEPALETTE record creates a Palette Object
*/
public static class WmfCreatePalette extends WmfPaletteParent {
public static class WmfCreatePalette extends WmfPaletteParent implements HwmfObjectTableEntry {
@Override
public HwmfRecordType getRecordType() {
return HwmfRecordType.createPalette;
@ -89,6 +89,11 @@ public class HwmfPalette {
@Override
public void draw(HwmfGraphics ctx) {
ctx.addObjectTableEntry(this);
}
@Override
public void applyObject(HwmfGraphics ctx) {
}
}

View File

@ -32,7 +32,7 @@ import org.apache.poi.util.BitFieldFactory;
* The defaults in case the other values of the subsection aren't set are
* solid, round end caps, round joins and cosmetic type.
*/
public class HwmfPenStyle {
public class HwmfPenStyle implements Cloneable {
public enum HwmfLineCap {
/** Rounded ends */
ROUND(0, BasicStroke.CAP_ROUND),
@ -168,4 +168,22 @@ public class HwmfPenStyle {
public boolean isAlternateDash() {
return SUBSECTION_ALTERNATE.isSet(flag);
}
/**
* Creates a new object of the same class and with the
* same contents as this object.
* @return a clone of this instance.
* @exception OutOfMemoryError if there is not enough memory.
* @see java.lang.Cloneable
*/
@Override
public HwmfPenStyle clone() {
try {
return (HwmfPenStyle)super.clone();
} catch (CloneNotSupportedException e) {
// this shouldn't happen, since we are Cloneable
throw new InternalError();
}
}
}

View File

@ -17,6 +17,7 @@
package org.apache.poi.hwmf.record;
import java.awt.geom.Rectangle2D;
import java.io.IOException;
import org.apache.poi.util.LittleEndianConsts;
@ -25,6 +26,8 @@ import org.apache.poi.util.LittleEndianInputStream;
public class HwmfPlaceableHeader {
public static int WMF_HEADER_MAGIC = 0x9AC6CDD7;
final Rectangle2D bounds;
protected HwmfPlaceableHeader(LittleEndianInputStream leis) throws IOException {
/*
* HWmf (2 bytes): The resource handle to the metafile, when the metafile is in memory. When
@ -41,6 +44,7 @@ public class HwmfPlaceableHeader {
int y1 = leis.readShort();
int x2 = leis.readShort();
int y2 = leis.readShort();
bounds = new Rectangle2D.Double(x1, y1, x2-x1, y2-y1);
/*
* Inch (2 bytes): The number of logical units per inch used to represent the image.
@ -74,4 +78,8 @@ public class HwmfPlaceableHeader {
return null;
}
}
public Rectangle2D getBounds() {
return bounds;
}
}

View File

@ -23,76 +23,77 @@ package org.apache.poi.hwmf.record;
* @see <a href="http://www.symantec.com/avcenter/reference/inside.the.windows.meta.file.format.pdf">Inside the Windows Meta File Format</a>
*/
public enum HwmfRecordType {
eof(0x0000, null),
realizePalette(0x0035, HwmfPalette.WmfRealizePalette.class),
setPalEntries(0x0037, HwmfPalette.WmfSetPaletteEntries.class),
setBkMode(0x0102, HwmfMisc.WmfSetBkMode.class),
setMapMode(0x0103, HwmfMisc.WmfSetMapMode.class),
setRop2(0x0104, HwmfMisc.WmfSetRop2.class),
setRelabs(0x0105, HwmfMisc.WmfSetRelabs.class),
setPolyFillMode(0x0106, HwmfFill.WmfSetPolyfillMode.class),
setStretchBltMode(0x0107, HwmfMisc.WmfSetStretchBltMode.class),
setTextCharExtra(0x0108, HwmfText.WmfSetTextCharExtra.class),
restoreDc(0x0127, HwmfMisc.WmfRestoreDc.class),
resizePalette(0x0139, HwmfPalette.WmfResizePalette.class),
dibCreatePatternBrush(0x0142, HwmfMisc.WmfDibCreatePatternBrush.class),
setLayout(0x0149, HwmfMisc.WmfSetLayout.class),
setBkColor(0x0201, HwmfMisc.WmfSetBkColor.class),
setTextColor(0x0209, HwmfText.WmfSetTextColor.class),
offsetViewportOrg(0x0211, HwmfWindowing.WmfOffsetViewportOrg.class),
lineTo(0x0213, HwmfDraw.WmfLineTo.class),
moveTo(0x0214, HwmfDraw.WmfMoveTo.class),
offsetClipRgn(0x0220, HwmfWindowing.WmfOffsetClipRgn.class),
fillRegion(0x0228, HwmfFill.WmfFillRegion.class),
setMapperFlags(0x0231, HwmfMisc.WmfSetMapperFlags.class),
selectPalette(0x0234, HwmfPalette.WmfSelectPalette.class),
polygon(0x0324, HwmfDraw.WmfPolygon.class),
polyline(0x0325, HwmfDraw.WmfPolyline.class),
setTextJustification(0x020a, HwmfText.WmfSetTextJustification.class),
setWindowOrg(0x020b, HwmfWindowing.WmfSetWindowOrg.class),
setWindowExt(0x020c, HwmfWindowing.WmfSetWindowExt.class),
setViewportOrg(0x020d, HwmfWindowing.WmfSetViewportOrg.class),
setViewportExt(0x020e, HwmfWindowing.WmfSetViewportExt.class),
offsetWindowOrg(0x020f, HwmfWindowing.WmfOffsetWindowOrg.class),
scaleWindowExt(0x0410, HwmfWindowing.WmfScaleWindowExt.class),
scaleViewportExt(0x0412, HwmfWindowing.WmfScaleViewportExt.class),
excludeClipRect(0x0415, HwmfWindowing.WmfExcludeClipRect.class),
intersectClipRect(0x0416, HwmfWindowing.WmfIntersectClipRect.class),
ellipse(0x0418, HwmfDraw.WmfEllipse.class),
floodFill(0x0419, HwmfFill.WmfFloodFill.class),
frameRegion(0x0429, HwmfDraw.WmfFrameRegion.class),
animatePalette(0x0436, HwmfPalette.WmfAnimatePalette.class),
textOut(0x0521, HwmfText.WmfTextOut.class),
polyPolygon(0x0538, HwmfDraw.WmfPolyPolygon.class),
extFloodFill(0x0548, HwmfFill.WmfExtFloodFill.class),
rectangle(0x041b, HwmfDraw.WmfRectangle.class),
setPixel(0x041f, HwmfDraw.WmfSetPixel.class),
roundRect(0x061c, HwmfDraw.WmfRoundRect.class),
patBlt(0x061d, HwmfFill.WmfPatBlt.class),
saveDc(0x001e, HwmfMisc.WmfSaveDc.class),
pie(0x081a, HwmfDraw.WmfPie.class),
stretchBlt(0x0b23, HwmfFill.WmfStretchBlt.class),
escape(0x0626, HwmfEscape.class),
invertRegion(0x012a, HwmfFill.WmfInvertRegion.class),
paintRegion(0x012b, HwmfFill.WmfPaintRegion.class),
selectClipRegion(0x012c, HwmfWindowing.WmfSelectClipRegion.class),
selectObject(0x012d, HwmfDraw.WmfSelectObject.class),
setTextAlign(0x012e, HwmfText.WmfSetTextAlign.class),
arc(0x0817, HwmfDraw.WmfArc.class),
chord(0x0830, HwmfDraw.WmfChord.class),
bitBlt(0x0922, HwmfFill.WmfBitBlt.class),
extTextOut(0x0a32, HwmfText.WmfExtTextOut.class),
setDibToDev(0x0d33, HwmfFill.WmfSetDibToDev.class),
dibBitBlt(0x0940, HwmfFill.WmfDibBitBlt.class),
dibStretchBlt(0x0b41, HwmfFill.WmfDibStretchBlt.class),
stretchDib(0x0f43, HwmfFill.WmfStretchDib.class),
deleteObject(0x01f0, HwmfMisc.WmfDeleteObject.class),
createPalette(0x00f7, HwmfPalette.WmfCreatePalette.class),
createPatternBrush(0x01f9, HwmfMisc.WmfCreatePatternBrush.class),
createPenIndirect(0x02fa, HwmfMisc.WmfCreatePenIndirect.class),
createFontIndirect(0x02fb, HwmfText.WmfCreateFontIndirect.class),
createBrushIndirect(0x02fc, HwmfMisc.WmfCreateBrushIndirect.class),
createRegion(0x06ff, HwmfWindowing.WmfCreateRegion.class);
eof(0x0000, null)
,animatePalette(0x0436, HwmfPalette.WmfAnimatePalette.class)
,arc(0x0817, HwmfDraw.WmfArc.class)
,bitBlt(0x0922, HwmfFill.WmfBitBlt.class)
,chord(0x0830, HwmfDraw.WmfChord.class)
,createBrushIndirect(0x02fc, HwmfMisc.WmfCreateBrushIndirect.class)
,createFontIndirect(0x02fb, HwmfText.WmfCreateFontIndirect.class)
,createPalette(0x00f7, HwmfPalette.WmfCreatePalette.class)
,createPatternBrush(0x01f9, HwmfMisc.WmfCreatePatternBrush.class)
,createPenIndirect(0x02fa, HwmfMisc.WmfCreatePenIndirect.class)
,createRegion(0x06ff, HwmfWindowing.WmfCreateRegion.class)
,deleteObject(0x01f0, HwmfMisc.WmfDeleteObject.class)
,dibBitBlt(0x0940, HwmfFill.WmfDibBitBlt.class)
,dibCreatePatternBrush(0x0142, HwmfMisc.WmfDibCreatePatternBrush.class)
,dibStretchBlt(0x0b41, HwmfFill.WmfDibStretchBlt.class)
,ellipse(0x0418, HwmfDraw.WmfEllipse.class)
,escape(0x0626, HwmfEscape.class)
,excludeClipRect(0x0415, HwmfWindowing.WmfExcludeClipRect.class)
,extFloodFill(0x0548, HwmfFill.WmfExtFloodFill.class)
,extTextOut(0x0a32, HwmfText.WmfExtTextOut.class)
,fillRegion(0x0228, HwmfFill.WmfFillRegion.class)
,floodFill(0x0419, HwmfFill.WmfFloodFill.class)
,frameRegion(0x0429, HwmfDraw.WmfFrameRegion.class)
,intersectClipRect(0x0416, HwmfWindowing.WmfIntersectClipRect.class)
,invertRegion(0x012a, HwmfFill.WmfInvertRegion.class)
,lineTo(0x0213, HwmfDraw.WmfLineTo.class)
,moveTo(0x0214, HwmfDraw.WmfMoveTo.class)
,offsetClipRgn(0x0220, HwmfWindowing.WmfOffsetClipRgn.class)
,offsetViewportOrg(0x0211, HwmfWindowing.WmfOffsetViewportOrg.class)
,offsetWindowOrg(0x020f, HwmfWindowing.WmfOffsetWindowOrg.class)
,paintRegion(0x012b, HwmfFill.WmfPaintRegion.class)
,patBlt(0x061d, HwmfFill.WmfPatBlt.class)
,pie(0x081a, HwmfDraw.WmfPie.class)
,polygon(0x0324, HwmfDraw.WmfPolygon.class)
,polyline(0x0325, HwmfDraw.WmfPolyline.class)
,polyPolygon(0x0538, HwmfDraw.WmfPolyPolygon.class)
,realizePalette(0x0035, HwmfPalette.WmfRealizePalette.class)
,rectangle(0x041b, HwmfDraw.WmfRectangle.class)
,resizePalette(0x0139, HwmfPalette.WmfResizePalette.class)
,restoreDc(0x0127, HwmfMisc.WmfRestoreDc.class)
,roundRect(0x061c, HwmfDraw.WmfRoundRect.class)
,saveDc(0x001e, HwmfMisc.WmfSaveDc.class)
,scaleViewportExt(0x0412, HwmfWindowing.WmfScaleViewportExt.class)
,scaleWindowExt(0x0410, HwmfWindowing.WmfScaleWindowExt.class)
,selectClipRegion(0x012c, HwmfWindowing.WmfSelectClipRegion.class)
,selectObject(0x012d, HwmfDraw.WmfSelectObject.class)
,selectPalette(0x0234, HwmfPalette.WmfSelectPalette.class)
,setBkColor(0x0201, HwmfMisc.WmfSetBkColor.class)
,setBkMode(0x0102, HwmfMisc.WmfSetBkMode.class)
,setDibToDev(0x0d33, HwmfFill.WmfSetDibToDev.class)
,setLayout(0x0149, HwmfMisc.WmfSetLayout.class)
,setMapMode(0x0103, HwmfMisc.WmfSetMapMode.class)
,setMapperFlags(0x0231, HwmfMisc.WmfSetMapperFlags.class)
,setPalEntries(0x0037, HwmfPalette.WmfSetPaletteEntries.class)
,setPixel(0x041f, HwmfDraw.WmfSetPixel.class)
,setPolyFillMode(0x0106, HwmfFill.WmfSetPolyfillMode.class)
,setRelabs(0x0105, HwmfMisc.WmfSetRelabs.class)
,setRop2(0x0104, HwmfMisc.WmfSetRop2.class)
,setStretchBltMode(0x0107, HwmfMisc.WmfSetStretchBltMode.class)
,setTextAlign(0x012e, HwmfText.WmfSetTextAlign.class)
,setTextCharExtra(0x0108, HwmfText.WmfSetTextCharExtra.class)
,setTextColor(0x0209, HwmfText.WmfSetTextColor.class)
,setTextJustification(0x020a, HwmfText.WmfSetTextJustification.class)
,setViewportExt(0x020e, HwmfWindowing.WmfSetViewportExt.class)
,setViewportOrg(0x020d, HwmfWindowing.WmfSetViewportOrg.class)
,setWindowExt(0x020c, HwmfWindowing.WmfSetWindowExt.class)
,setWindowOrg(0x020b, HwmfWindowing.WmfSetWindowOrg.class)
,stretchBlt(0x0b23, HwmfFill.WmfStretchBlt.class)
,stretchDib(0x0f43, HwmfFill.WmfStretchDib.class)
,textOut(0x0521, HwmfText.WmfTextOut.class)
;
public int id;
public Class<? extends HwmfRecord> clazz;

View File

@ -391,7 +391,7 @@ public class HwmfText {
}
}
public static class WmfCreateFontIndirect implements HwmfRecord {
public static class WmfCreateFontIndirect implements HwmfRecord, HwmfObjectTableEntry {
private HwmfFont font;
@Override
@ -407,6 +407,11 @@ public class HwmfText {
@Override
public void draw(HwmfGraphics ctx) {
ctx.addObjectTableEntry(this);
}
@Override
public void applyObject(HwmfGraphics ctx) {
}
}

View File

@ -17,7 +17,10 @@
package org.apache.poi.hwmf.record;
import java.awt.Shape;
import java.awt.geom.Area;
import java.awt.geom.Rectangle2D;
import java.awt.geom.Rectangle2D.Double;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
@ -128,7 +131,9 @@ public class HwmfWindowing {
@Override
public void draw(HwmfGraphics ctx) {
Rectangle2D viewport = ctx.getProperties().getViewport();
ctx.getProperties().setViewportOrg(viewport.getX()+xOffset, viewport.getY()+yOffset);
double x = (viewport == null) ? 0 : viewport.getX();
double y = (viewport == null) ? 0 : viewport.getY();
ctx.getProperties().setViewportOrg(x+xOffset, y+yOffset);
}
}
@ -163,6 +168,14 @@ public class HwmfWindowing {
public void draw(HwmfGraphics ctx) {
ctx.getProperties().setWindowOrg(x, y);
}
public int getY() {
return y;
}
public int getX() {
return x;
}
}
/**
@ -199,6 +212,14 @@ public class HwmfWindowing {
public void draw(HwmfGraphics ctx) {
ctx.getProperties().setWindowExt(width, height);
}
public int getHeight() {
return height;
}
public int getWidth() {
return width;
}
}
/**
@ -338,6 +359,9 @@ public class HwmfWindowing {
@Override
public void draw(HwmfGraphics ctx) {
Rectangle2D viewport = ctx.getProperties().getViewport();
if (viewport == null) {
viewport = ctx.getProperties().getWindow();
}
double width = viewport.getWidth() * xNum / xDenom;
double height = viewport.getHeight() * yNum / yDenom;
ctx.getProperties().setViewportExt(width, height);
@ -556,7 +580,7 @@ public class HwmfWindowing {
}
}
public static class WmfCreateRegion implements HwmfRecord {
public static class WmfCreateRegion implements HwmfRecord, HwmfObjectTableEntry {
/**
* A 16-bit signed integer. A value that MUST be ignored.
*/
@ -642,7 +666,32 @@ public class HwmfWindowing {
@Override
public void draw(HwmfGraphics ctx) {
ctx.addObjectTableEntry(this);
}
@Override
public void applyObject(HwmfGraphics ctx) {
Rectangle2D lastRect = null;
Area scanLines = new Area();
int count = 0;
for (WmfScanObject so : scanObjects) {
int y = Math.min(so.top, so.bottom);
int h = Math.abs(so.top - so.bottom - 1);
for (int i=0; i<so.count/2; i++) {
int x = Math.min(so.left_scanline[i], so.right_scanline[i]);
int w = Math.abs(so.right_scanline[i] - so.left_scanline[i] - 1);
lastRect = new Rectangle2D.Double(x,y,w,h);
scanLines.add(new Area(lastRect));
count++;
}
}
Shape region = null;
if (count > 0) {
region = (count == 1) ? lastRect : scanLines;
}
ctx.getProperties().setRegion(region);
}
}
}

View File

@ -17,6 +17,8 @@
package org.apache.poi.hwmf.usermodel;
import java.awt.Graphics2D;
import java.awt.geom.Rectangle2D;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
@ -24,24 +26,25 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.apache.poi.hwmf.draw.HwmfGraphics;
import org.apache.poi.hwmf.record.HwmfHeader;
import org.apache.poi.hwmf.record.HwmfPlaceableHeader;
import org.apache.poi.hwmf.record.HwmfRecord;
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;
public class HwmfPicture {
List<HwmfRecord> records = new ArrayList<HwmfRecord>();
public List<HwmfRecord> getRecords() {
return Collections.unmodifiableList(records);
}
final List<HwmfRecord> records = new ArrayList<HwmfRecord>();
final HwmfPlaceableHeader placeableHeader;
final HwmfHeader header;
public HwmfPicture(InputStream inputStream) throws IOException {
BufferedInputStream bis = new BufferedInputStream(inputStream, 10000);
LittleEndianInputStream leis = new LittleEndianInputStream(bis);
HwmfPlaceableHeader placeableHeader = HwmfPlaceableHeader.readHeader(leis);
HwmfHeader header = new HwmfHeader(leis);
placeableHeader = HwmfPlaceableHeader.readHeader(leis);
header = new HwmfHeader(leis);
for (;;) {
// recordSize in DWORDs
@ -76,5 +79,50 @@ public class HwmfPicture {
}
}
public List<HwmfRecord> getRecords() {
return Collections.unmodifiableList(records);
}
public void draw(Graphics2D ctx) {
HwmfGraphics g = new HwmfGraphics(ctx, getBounds());
for (HwmfRecord r : records) {
r.draw(g);
}
}
/**
* Returns the bounding box in device-independent units. Usually this is taken from the placeable header.
*
* @return the bounding box
*/
public Rectangle2D getBounds() {
if (placeableHeader != null) {
return placeableHeader.getBounds();
} else {
WmfSetWindowOrg wOrg = null;
WmfSetWindowExt wExt = null;
for (HwmfRecord r : getRecords()) {
if (wOrg != null && wExt != null) {
break;
}
if (r instanceof WmfSetWindowOrg) {
wOrg = (WmfSetWindowOrg)r;
} else if (r instanceof WmfSetWindowExt) {
wExt = (WmfSetWindowExt)r;
}
}
if (wOrg == null || wExt == null) {
throw new RuntimeException("invalid wmf file - window records are incomplete.");
}
return new Rectangle2D.Double(wOrg.getX(), wOrg.getY(), wExt.getWidth(), wExt.getHeight());
}
}
public HwmfPlaceableHeader getPlaceableHeader() {
return placeableHeader;
}
public HwmfHeader getHeader() {
return header;
}
}

View File

@ -19,6 +19,9 @@ package org.apache.poi.hwmf;
import static org.junit.Assert.assertEquals;
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;
@ -56,6 +59,36 @@ public class TestHwmfParsing {
assertEquals(581, records.size());
}
@Test
@Ignore
public void paint() throws IOException {
File f = POIDataSamples.getSlideShowInstance().getFile("santa.wmf");
FileInputStream fis = new FileInputStream(f);
HwmfPicture wmf = new HwmfPicture(fis);
fis.close();
Rectangle2D bounds = wmf.getBounds();
int width = 300;
// keep aspect ratio for height
int height = (int)(width*bounds.getHeight()/bounds.getWidth());
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);
g.scale(width/bounds.getWidth(), height/bounds.getHeight());
g.translate(-bounds.getX(), -bounds.getY());
wmf.draw(g);
g.dispose();
ImageIO.write(bufImg, "PNG", new File("bla.png"));
}
@Test
@Ignore
public void fetchWmfFromGovdocs() throws IOException {