/* ==================================================================== | |
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 java.awt.Shape; | |
import java.awt.geom.Arc2D; | |
import java.awt.geom.Area; | |
import java.awt.geom.Ellipse2D; | |
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; | |
import org.apache.poi.util.LittleEndianInputStream; | |
public class HwmfDraw { | |
/** | |
* The META_MOVETO record sets the output position in the playback device context to a specified | |
* point. | |
*/ | |
public static class WmfMoveTo implements HwmfRecord { | |
/** | |
* A 16-bit signed integer that defines the y-coordinate, in logical units. | |
*/ | |
private int y; | |
/** | |
* A 16-bit signed integer that defines the x-coordinate, in logical units. | |
*/ | |
private int x; | |
@Override | |
public HwmfRecordType getRecordType() { | |
return HwmfRecordType.moveTo; | |
} | |
@Override | |
public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException { | |
y = leis.readShort(); | |
x = leis.readShort(); | |
return 2*LittleEndianConsts.SHORT_SIZE; | |
} | |
@Override | |
public void draw(HwmfGraphics ctx) { | |
ctx.getProperties().setLocation(x, y); | |
} | |
} | |
/** | |
* The META_LINETO record draws a line from the drawing position that is defined in the playback | |
* device context up to, but not including, the specified point. | |
*/ | |
public static class WmfLineTo implements HwmfRecord { | |
/** | |
* A 16-bit signed integer that defines the vertical component of the drawing | |
* destination position, in logical units. | |
*/ | |
private int y; | |
/** | |
* A 16-bit signed integer that defines the horizontal component of the drawing | |
* destination position, in logical units. | |
*/ | |
private int x; | |
@Override | |
public HwmfRecordType getRecordType() { | |
return HwmfRecordType.lineTo; | |
} | |
@Override | |
public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException { | |
y = leis.readShort(); | |
x = leis.readShort(); | |
return 2*LittleEndianConsts.SHORT_SIZE; | |
} | |
@Override | |
public void draw(HwmfGraphics ctx) { | |
Point2D start = ctx.getProperties().getLocation(); | |
Line2D line = new Line2D.Double(start.getX(), start.getY(), x, y); | |
ctx.draw(line); | |
ctx.getProperties().setLocation(x, y); | |
} | |
} | |
/** | |
* The META_POLYGON record paints a polygon consisting of two or more vertices connected by | |
* straight lines. The polygon is outlined by using the pen and filled by using the brush and polygon fill | |
* mode that are defined in the playback device context. | |
*/ | |
public static class WmfPolygon implements HwmfRecord { | |
private Path2D poly = new Path2D.Double(); | |
@Override | |
public HwmfRecordType getRecordType() { | |
return HwmfRecordType.polygon; | |
} | |
@Override | |
public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException { | |
/** | |
* 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. | |
int x = leis.readShort(); | |
// A 16-bit signed integer that defines the vertical (y) coordinate of the point. | |
int y = leis.readShort(); | |
if (i==0) { | |
poly.moveTo(x, y); | |
} else { | |
poly.lineTo(x, y); | |
} | |
} | |
return LittleEndianConsts.SHORT_SIZE+numberofPoints*LittleEndianConsts.INT_SIZE; | |
} | |
@Override | |
public void draw(HwmfGraphics ctx) { | |
Path2D shape = getShape(); | |
// shape.closePath(); | |
Path2D p = (Path2D)shape.clone(); | |
p.setWindingRule(getWindingRule(ctx)); | |
ctx.fill(p); | |
} | |
protected Path2D getShape() { | |
return (Path2D)poly.clone(); | |
} | |
} | |
/** | |
* The META_POLYLINE record draws a series of line segments by connecting the points in the | |
* specified array. | |
*/ | |
public static class WmfPolyline extends WmfPolygon { | |
@Override | |
public HwmfRecordType getRecordType() { | |
return HwmfRecordType.polyline; | |
} | |
@Override | |
public void draw(HwmfGraphics ctx) { | |
Path2D shape = getShape(); | |
Path2D p = (Path2D)shape.clone(); | |
p.setWindingRule(getWindingRule(ctx)); | |
ctx.draw(p); | |
} | |
} | |
/** | |
* The META_ELLIPSE record draws an ellipse. The center of the ellipse is the center of the specified | |
* bounding rectangle. The ellipse is outlined by using the pen and is filled by using the brush; these | |
* are defined in the playback device context. | |
*/ | |
public static class WmfEllipse implements HwmfRecord { | |
/** | |
* 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.ellipse; | |
} | |
@Override | |
public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException { | |
bottomRect = leis.readShort(); | |
rightRect = leis.readShort(); | |
topRect = leis.readShort(); | |
leftRect = leis.readShort(); | |
return 4*LittleEndianConsts.SHORT_SIZE; | |
} | |
@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); | |
} | |
} | |
/** | |
* The META_FRAMEREGION record draws a border around a specified region using a specified brush. | |
*/ | |
public static class WmfFrameRegion implements HwmfRecord { | |
/** | |
* A 16-bit unsigned integer used to index into the WMF Object Table to get | |
* the region to be framed. | |
*/ | |
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 brushIndex; | |
/** | |
* A 16-bit signed integer that defines the height, in logical units, of the | |
* region frame. | |
*/ | |
private int height; | |
/** | |
* A 16-bit signed integer that defines the width, in logical units, of the | |
* region frame. | |
*/ | |
private int width; | |
@Override | |
public HwmfRecordType getRecordType() { | |
return HwmfRecordType.frameRegion; | |
} | |
@Override | |
public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException { | |
regionIndex = leis.readUShort(); | |
brushIndex = leis.readUShort(); | |
height = leis.readShort(); | |
width = leis.readShort(); | |
return 4*LittleEndianConsts.SHORT_SIZE; | |
} | |
@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); | |
} | |
} | |
/** | |
* The META_POLYPOLYGON record paints a series of closed polygons. Each polygon is outlined by | |
* using the pen and filled by using the brush and polygon fill mode; these are defined in the playback | |
* device context. The polygons drawn by this function can overlap. | |
*/ | |
public static class WmfPolyPolygon implements HwmfRecord { | |
private List<Path2D> polyList = new ArrayList<Path2D>(); | |
@Override | |
public HwmfRecordType getRecordType() { | |
return HwmfRecordType.polyPolygon; | |
} | |
@Override | |
public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException { | |
// see http://secunia.com/gfx/pdf/SA31675_BA.pdf ;) | |
/** | |
* 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; | |
for (int i=0; i<numberOfPolygons; i++) { | |
pointsPerPolygon[i] = leis.readUShort(); | |
size += LittleEndianConsts.SHORT_SIZE; | |
} | |
for (int nPoints : pointsPerPolygon) { | |
/** | |
* An array of 16-bit signed integers that define the coordinates of the polygons. | |
* (Note: MS-WMF wrongly says unsigned integers ...) | |
*/ | |
Path2D poly = new Path2D.Double(); | |
for (int i=0; i<nPoints; i++) { | |
int x = leis.readShort(); | |
int y = leis.readShort(); | |
size += 2*LittleEndianConsts.SHORT_SIZE; | |
if (i == 0) { | |
poly.moveTo(x, y); | |
} else { | |
poly.lineTo(x, y); | |
} | |
} | |
poly.closePath(); | |
polyList.add(poly); | |
} | |
return size; | |
} | |
@Override | |
public void draw(HwmfGraphics ctx) { | |
if (polyList.isEmpty()) { | |
return; | |
} | |
int windingRule = getWindingRule(ctx); | |
Area area = null; | |
for (Path2D poly : polyList) { | |
Path2D p = (Path2D)poly.clone(); | |
p.setWindingRule(windingRule); | |
Area newArea = new Area(p); | |
if (area == null) { | |
area = newArea; | |
} else { | |
area.exclusiveOr(newArea); | |
} | |
} | |
ctx.fill(area); | |
} | |
} | |
/** | |
* The META_RECTANGLE record paints a rectangle. The rectangle is outlined by using the pen and | |
* filled by using the brush that are defined in the playback device context. | |
*/ | |
public static class WmfRectangle implements HwmfRecord { | |
/** | |
* A 16-bit signed integer that defines the y-coordinate, in logical units, of | |
* the lower-right corner of the rectangle. | |
*/ | |
private int bottomRect; | |
/** | |
* A 16-bit signed integer that defines the x-coordinate, in logical units, of | |
* the lower-right corner of the rectangle. | |
*/ | |
private int rightRect; | |
/** | |
* A 16-bit signed integer that defines the y-coordinate, in logical units, of the | |
* upper-left corner of the rectangle. | |
*/ | |
private int topRect; | |
/** | |
* A 16-bit signed integer that defines the x-coordinate, in logical units, of | |
* the upper-left corner of the rectangle. | |
*/ | |
private int leftRect; | |
@Override | |
public HwmfRecordType getRecordType() { | |
return HwmfRecordType.frameRegion; | |
} | |
@Override | |
public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException { | |
bottomRect = leis.readShort(); | |
rightRect = leis.readShort(); | |
topRect = leis.readShort(); | |
leftRect = leis.readShort(); | |
return 4*LittleEndianConsts.SHORT_SIZE; | |
} | |
@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); | |
} | |
} | |
/** | |
* The META_RECTANGLE record paints a rectangle. The rectangle is outlined by using the pen and | |
* filled by using the brush that are defined in the playback device context. | |
*/ | |
public static class WmfSetPixel implements HwmfRecord { | |
/** | |
* A ColorRef Object that defines the color value. | |
*/ | |
HwmfColorRef colorRef; | |
/** | |
* A 16-bit signed integer that defines the y-coordinate, in logical units, of the point | |
* to be set. | |
*/ | |
private int y; | |
/** | |
* A 16-bit signed integer that defines the x-coordinate, in logical units, of the point | |
* to be set. | |
*/ | |
private int x; | |
@Override | |
public HwmfRecordType getRecordType() { | |
return HwmfRecordType.setPixel; | |
} | |
@Override | |
public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException { | |
colorRef = new HwmfColorRef(); | |
int size = colorRef.init(leis); | |
y = leis.readShort(); | |
x = leis.readShort(); | |
return 2*LittleEndianConsts.SHORT_SIZE+size; | |
} | |
@Override | |
public void draw(HwmfGraphics ctx) { | |
Shape s = new Rectangle2D.Double(x, y, 1, 1); | |
ctx.fill(s); | |
} | |
} | |
/** | |
* The META_ROUNDRECT record paints a rectangle with rounded corners. The rectangle is outlined | |
* using the pen and filled using the brush, as defined in the playback device context. | |
*/ | |
public static class WmfRoundRect implements HwmfRecord { | |
/** | |
* A 16-bit signed integer that defines the height, in logical coordinates, of the | |
* ellipse used to draw the rounded corners. | |
*/ | |
private int height; | |
/** | |
* A 16-bit signed integer that defines the width, in logical coordinates, of the | |
* ellipse used to draw the rounded corners. | |
*/ | |
private int width; | |
/** | |
* A 16-bit signed integer that defines the y-coordinate, in logical units, of | |
* the lower-right corner of the rectangle. | |
*/ | |
private int bottomRect; | |
/** | |
* A 16-bit signed integer that defines the x-coordinate, in logical units, of | |
* the lower-right corner of the rectangle. | |
*/ | |
private int rightRect; | |
/** | |
* A 16-bit signed integer that defines the y-coordinate, in logical units, of the | |
* upper-left corner of the rectangle. | |
*/ | |
private int topRect; | |
/** | |
* A 16-bit signed integer that defines the x-coordinate, in logical units, of | |
* the upper-left corner of the rectangle. | |
*/ | |
private int leftRect; | |
@Override | |
public HwmfRecordType getRecordType() { | |
return HwmfRecordType.roundRect; | |
} | |
@Override | |
public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException { | |
height = leis.readShort(); | |
width = leis.readShort(); | |
bottomRect = leis.readShort(); | |
rightRect = leis.readShort(); | |
topRect = leis.readShort(); | |
leftRect = leis.readShort(); | |
return 6*LittleEndianConsts.SHORT_SIZE; | |
} | |
@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_ARC record draws an elliptical arc. | |
*/ | |
public static class WmfArc implements HwmfRecord { | |
/** | |
* A 16-bit signed integer that defines the y-coordinate, in logical units, of | |
* the ending point of the radial line defining the ending point of the arc. | |
*/ | |
private int yEndArc; | |
/** | |
* A 16-bit signed integer that defines the x-coordinate, in logical units, of | |
* the ending point of the radial line defining the ending point of the arc. | |
*/ | |
private int xEndArc; | |
/** | |
* A 16-bit signed integer that defines the y-coordinate, in logical units, of | |
* the ending point of the radial line defining the starting point of the arc. | |
*/ | |
private int yStartArc; | |
/** | |
* A 16-bit signed integer that defines the x-coordinate, in logical units, of | |
* the ending point of the radial line defining the starting point of the arc. | |
*/ | |
private int xStartArc; | |
/** | |
* 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.arc; | |
} | |
@Override | |
public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException { | |
yEndArc = leis.readShort(); | |
xEndArc = leis.readShort(); | |
yStartArc = leis.readShort(); | |
xStartArc = 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) { | |
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; | |
} | |
} | |
/** | |
* The META_CHORD record draws a chord, which is defined by a region bounded by the intersection of | |
* 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 extends WmfArc { | |
@Override | |
public HwmfRecordType getRecordType() { | |
return HwmfRecordType.chord; | |
} | |
} | |
/** | |
* The META_SELECTOBJECT record specifies a graphics object for the playback device context. The | |
* new object replaces the previous object of the same type, unless if the previous object is a palette | |
* object. If the previous object is a palette object, then the META_SELECTPALETTE record must be | |
* used instead of the META_SELECTOBJECT record, as the META_SELECTOBJECT record does not | |
* support replacing the palette object type. | |
*/ | |
public static class WmfSelectObject implements HwmfRecord { | |
/** | |
* A 16-bit unsigned integer used to index into the WMF Object Table to | |
* get the object to be selected. | |
*/ | |
private int objectIndex; | |
@Override | |
public HwmfRecordType getRecordType() { | |
return HwmfRecordType.selectObject; | |
} | |
@Override | |
public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException { | |
objectIndex = leis.readUShort(); | |
return LittleEndianConsts.SHORT_SIZE; | |
} | |
@Override | |
public void draw(HwmfGraphics ctx) { | |
ctx.applyObjectTableEntry(objectIndex); | |
} | |
} | |
private static int getWindingRule(HwmfGraphics ctx) { | |
return ctx.getProperties().getPolyfillMode().awtFlag; | |
} | |
} |