Browse Source

#60656 - Support export file that contains emf and render it correctly

git-svn-id: https://svn.apache.org/repos/asf/poi/branches/hemf@1844931 13f79535-47bb-0310-9956-ffa450edef68
tags/REL_4_1_0
Andreas Beeker 5 years ago
parent
commit
29587e78df

+ 19
- 2
src/scratchpad/src/org/apache/poi/hemf/draw/HemfDrawProperties.java View File

@@ -17,6 +17,7 @@

package org.apache.poi.hemf.draw;

import java.awt.Shape;
import java.awt.geom.Path2D;

import org.apache.poi.hwmf.draw.HwmfDrawProperties;
@@ -25,6 +26,8 @@ public class HemfDrawProperties extends HwmfDrawProperties {

/** Path for path bracket operations */
protected Path2D path = null;
protected Shape clip = null;
protected boolean usePathBracket = false;


public HemfDrawProperties() {
@@ -33,6 +36,8 @@ public class HemfDrawProperties extends HwmfDrawProperties {
public HemfDrawProperties(HemfDrawProperties other) {
super(other);
path = (other.path != null) ? (Path2D)other.path.clone() : null;
// TODO: check how to clone
clip = other.clip;
}

/**
@@ -55,7 +60,19 @@ public class HemfDrawProperties extends HwmfDrawProperties {
* @return {@code true}, if the drawing should go to the path bracket,
* if {@code false} draw directly to the graphics context
*/
public boolean usePathBracket() {
return path != null;
public boolean getUsePathBracket() {
return usePathBracket;
}

public void setUsePathBracket(boolean usePathBracket) {
this.usePathBracket = usePathBracket;
}

public Shape getClip() {
return clip;
}

public void setClip(Shape shape) {
clip = shape;
}
}

+ 65
- 41
src/scratchpad/src/org/apache/poi/hemf/draw/HemfGraphics.java View File

@@ -22,15 +22,15 @@ import static org.apache.poi.hwmf.record.HwmfBrushStyle.BS_SOLID;

import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Shape;
import java.awt.geom.AffineTransform;
import java.awt.geom.Area;
import java.awt.geom.Path2D;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.function.Consumer;

import org.apache.poi.hemf.record.emf.HemfBounded;
import org.apache.poi.hemf.record.emf.HemfFill;
import org.apache.poi.hemf.record.emf.HemfRecord;
import org.apache.poi.hwmf.draw.HwmfGraphics;
import org.apache.poi.hwmf.record.HwmfColorRef;
@@ -47,13 +47,13 @@ public class HemfGraphics extends HwmfGraphics {
private static final HwmfColorRef BLACK = new HwmfColorRef(Color.BLACK);


private final Deque<AffineTransform> transforms = new ArrayDeque<>();
private final AffineTransform initTrans;

public HemfGraphics(Graphics2D graphicsCtx, Rectangle2D bbox) {
super(graphicsCtx,bbox);
// add dummy entry for object index 0, as emf is 1-based
// add dummy entry for object ind ex 0, as emf is 1-based
objectIndexes.set(0);
saveTransform();
initTrans = new AffineTransform(graphicsCtx.getTransform());
}

@Override
@@ -66,48 +66,34 @@ public class HemfGraphics extends HwmfGraphics {

@Override
public void saveProperties() {
propStack.add(getProperties());
prop = new HemfDrawProperties((HemfDrawProperties)prop);
final HemfDrawProperties oldProp = getProperties();
oldProp.setClip(graphicsCtx.getClip());
propStack.add(oldProp);
prop = new HemfDrawProperties(oldProp);
}

@Override
public void updateWindowMapMode() {
// ignore window settings
public void restoreProperties(int index) {
super.restoreProperties(index);
HemfDrawProperties newProp = getProperties();
graphicsCtx.setClip(newProp.getClip());
}

public void draw(HemfRecord r) {
if (r instanceof HemfBounded) {
saveTransform();
final HemfBounded bounded = (HemfBounded)r;
final Rectangle2D tgt = bounded.getRecordBounds();
if (tgt != null && !tgt.isEmpty()) {
final Rectangle2D src = bounded.getShapeBounds(this);
if (src != null && !src.isEmpty()) {
// graphicsCtx.translate(tgt.getCenterX() - src.getCenterX(), tgt.getCenterY() - src.getCenterY());
// graphicsCtx.translate(src.getCenterX(), src.getCenterY());
// graphicsCtx.scale(tgt.getWidth() / src.getWidth(), tgt.getHeight() / src.getHeight());
// graphicsCtx.translate(-src.getCenterX(), -src.getCenterY());
}
}
}

r.draw(this);

if (r instanceof HemfBounded) {
restoreTransform();
}
}

@Internal
public void draw(Consumer<Path2D> pathConsumer) {
public void draw(Consumer<Path2D> pathConsumer, FillDrawStyle fillDraw) {
final HemfDrawProperties prop = getProperties();
final boolean useBracket = prop.usePathBracket();
final boolean useBracket = prop.getUsePathBracket();

final Path2D path;
if (useBracket) {
path = prop.getPath();
} else {
path = new Path2D.Double();
path.setWindingRule(prop.getWindingRule());
Point2D pnt = prop.getLocation();
path.moveTo(pnt.getX(),pnt.getY());
}
@@ -124,8 +110,18 @@ public class HemfGraphics extends HwmfGraphics {

prop.setLocation(path.getCurrentPoint());
if (!useBracket) {
// TODO: when to use draw vs. fill?
super.draw(path);
switch (fillDraw) {
case FILL:
super.fill(path);
break;
case DRAW:
super.draw(path);
break;
case FILL_DRAW:
super.fill(path);
super.draw(path);
break;
}
}

}
@@ -273,7 +269,7 @@ public class HemfGraphics extends HwmfGraphics {
* @return the initial AffineTransform, when this graphics context was created
*/
public AffineTransform getInitTransform() {
return new AffineTransform(transforms.peekFirst());
return new AffineTransform(initTrans);
}

/**
@@ -291,13 +287,41 @@ public class HemfGraphics extends HwmfGraphics {
graphicsCtx.setTransform(tx);
}

/** saves the current affine transform on the stack */
private void saveTransform() {
transforms.push(graphicsCtx.getTransform());
}
public void setClip(Shape clip, HemfFill.HemfRegionMode regionMode) {
Shape oldClip = graphicsCtx.getClip();

/** restore the last saved affine transform */
private void restoreTransform() {
graphicsCtx.setTransform(transforms.pop());
switch (regionMode) {
case RGN_AND:
graphicsCtx.clip(clip);
break;
case RGN_OR:
if (oldClip == null) {
graphicsCtx.setClip(clip);
} else {
Area area = new Area(oldClip);
area.add(new Area(clip));
graphicsCtx.setClip(area);
}
break;
case RGN_XOR:
if (oldClip == null) {
graphicsCtx.setClip(clip);
} else {
Area area = new Area(oldClip);
area.exclusiveOr(new Area(clip));
graphicsCtx.setClip(area);
}
break;
case RGN_DIFF:
if (oldClip != null) {
Area area = new Area(oldClip);
area.subtract(new Area(clip));
graphicsCtx.setClip(area);
}
break;
case RGN_COPY:
graphicsCtx.setClip(clip);
break;
}
}
}

+ 0
- 45
src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfBounded.java View File

@@ -1,45 +0,0 @@
/* ====================================================================
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.hemf.record.emf;

import java.awt.geom.Rectangle2D;

import org.apache.poi.hemf.draw.HemfGraphics;

/**
* In EMF, shape records bring their own bounding.
* The record bounding is in the same space as the global drawing context,
* but the specified shape points can have a different space and therefore
* need to be translated/normalized
*/
public interface HemfBounded {
/**
* Getter for the outer bounds which are given in the record
*
* @return the bounds specified in the record
*/
Rectangle2D getRecordBounds();

/**
* Getter for the inner bounds which are calculated by the shape points
*
* @param ctx the graphics context
* @return the bounds of the shape points
*/
Rectangle2D getShapeBounds(HemfGraphics ctx);
}

+ 138
- 89
src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfDraw.java View File

@@ -17,8 +17,10 @@

package org.apache.poi.hemf.record.emf;

import static org.apache.poi.hwmf.record.HwmfDraw.boundsToString;

import java.awt.Shape;
import java.awt.geom.Arc2D;
import java.awt.geom.Area;
import java.awt.geom.Dimension2D;
import java.awt.geom.Path2D;
import java.awt.geom.PathIterator;
@@ -28,6 +30,7 @@ import java.io.IOException;

import org.apache.poi.hemf.draw.HemfDrawProperties;
import org.apache.poi.hemf.draw.HemfGraphics;
import org.apache.poi.hwmf.draw.HwmfGraphics.FillDrawStyle;
import org.apache.poi.hwmf.record.HwmfDraw;
import org.apache.poi.hwmf.record.HwmfDraw.WmfSelectObject;
import org.apache.poi.util.LittleEndianConsts;
@@ -88,7 +91,7 @@ public class HemfDraw {


/** The EMR_POLYBEZIER record specifies one or more Bezier curves. */
public static class EmfPolyBezier extends HwmfDraw.WmfPolygon implements HemfRecord, HemfBounded {
public static class EmfPolyBezier extends HwmfDraw.WmfPolygon implements HemfRecord {
private final Rectangle2D bounds = new Rectangle2D.Double();

@Override
@@ -120,7 +123,7 @@ public class HemfDraw {
final int points = Math.min(count, 16384);
size += LittleEndianConsts.INT_SIZE;

poly = new Path2D.Double(Path2D.WIND_EVEN_ODD, points);
poly = new Path2D.Double(Path2D.WIND_EVEN_ODD, points+2);

/* Cubic Bezier curves are defined using the endpoints and control points
* specified by the points field. The first curve is drawn from the first
@@ -135,8 +138,8 @@ public class HemfDraw {
Point2D pnt[] = { new Point2D.Double(), new Point2D.Double(), new Point2D.Double() };

// points-1 because of the first point
final int pointCnt = hasStartPoint() ? points-1 : points;
for (int i=0; i+3<pointCnt; i+=3) {
final int pointCnt = hasStartPoint() ? points-2 : points;
for (int i=0; i+2<pointCnt; i+=3) {
// x (4 bytes): A 32-bit signed integer that defines the horizontal (x) coordinate of the point.
// y (4 bytes): A 32-bit signed integer that defines the vertical (y) coordinate of the point.
if (i==0) {
@@ -162,25 +165,23 @@ public class HemfDraw {
return size;
}

@Override
public Rectangle2D getRecordBounds() {
return bounds;
}

@Override
public Rectangle2D getShapeBounds(HemfGraphics ctx) {
if (!hasStartPoint()) {
throw new IllegalStateException("shape bounds not valid for path bracket based record: "+getClass().getName());
}
return poly.getBounds2D();
}

/**
* @return true, if start point is in the list of points. false, if start point is taken from the context
*/
protected boolean hasStartPoint() {
return true;
}

@Override
protected FillDrawStyle getFillDrawStyle() {
// The cubic Bezier curves SHOULD be drawn using the current pen.
return FillDrawStyle.DRAW;
}

@Override
public void draw(HemfGraphics ctx) {
ctx.draw(path -> path.append(poly, !hasStartPoint()), getFillDrawStyle());
}
}

/**
@@ -203,7 +204,7 @@ public class HemfDraw {
* The EMR_POLYGON record specifies a polygon consisting of two or more vertexes connected by
* straight lines.
*/
public static class EmfPolygon extends HwmfDraw.WmfPolygon implements HemfRecord, HemfBounded {
public static class EmfPolygon extends HwmfDraw.WmfPolygon implements HemfRecord {
private final Rectangle2D bounds = new Rectangle2D.Double();

@Override
@@ -246,25 +247,25 @@ public class HemfDraw {
return size;
}

@Override
public Rectangle2D getRecordBounds() {
return bounds;
}

@Override
public Rectangle2D getShapeBounds(HemfGraphics ctx) {
if (!hasStartPoint()) {
throw new IllegalStateException("shape bounds not valid for path bracket based record: "+getClass().getName());
}
return poly.getBounds2D();
}

/**
* @return true, if start point is in the list of points. false, if start point is taken from the context
*/
protected boolean hasStartPoint() {
return true;
}

@Override
protected FillDrawStyle getFillDrawStyle() {
// The polygon SHOULD be outlined using the current pen and filled using the current brush and
// polygon fill mode. The polygon SHOULD be closed automatically by drawing a line from the last
// vertex to the first.
return FillDrawStyle.FILL_DRAW;
}

@Override
public void draw(HemfGraphics ctx) {
ctx.draw(path -> path.append(poly, false), getFillDrawStyle());
}
}

/**
@@ -295,8 +296,9 @@ public class HemfDraw {
}

@Override
protected boolean isFill() {
return false;
protected FillDrawStyle getFillDrawStyle() {
// The line segments SHOULD be drawn using the current pen.
return FillDrawStyle.DRAW;
}
}

@@ -333,14 +335,7 @@ public class HemfDraw {

@Override
public void draw(HemfGraphics ctx) {
polyTo(ctx, poly);
}

@Override
public Rectangle2D getShapeBounds(HemfGraphics ctx) {
// should be called in a beginPath/endPath bracket, so the shape bounds
// of this path segment are irrelevant
return null;
polyTo(ctx, poly, getFillDrawStyle());
}
}

@@ -374,14 +369,7 @@ public class HemfDraw {

@Override
public void draw(HemfGraphics ctx) {
polyTo(ctx, poly);
}

@Override
public Rectangle2D getShapeBounds(HemfGraphics ctx) {
// should be called in a beginPath/endPath bracket, so the shape bounds
// of this path segment are irrelevant
return null;
polyTo(ctx, poly, getFillDrawStyle());
}
}

@@ -406,7 +394,7 @@ public class HemfDraw {
/**
* The EMR_POLYPOLYGON record specifies a series of closed polygons.
*/
public static class EmfPolyPolygon extends HwmfDraw.WmfPolyPolygon implements HemfRecord, HemfBounded {
public static class EmfPolyPolygon extends HwmfDraw.WmfPolyPolygon implements HemfRecord {
private final Rectangle2D bounds = new Rectangle2D.Double();

@Override
@@ -461,22 +449,15 @@ public class HemfDraw {
return size;
}

/**
* @return true, if a polyline should be closed, i.e. is a polygon
*/
protected boolean isClosed() {
return true;
}

@Override
public Rectangle2D getRecordBounds() {
return bounds;
}
public void draw(HemfGraphics ctx) {
Shape shape = getShape(ctx);
if (shape == null) {
return;
}

@Override
public Rectangle2D getShapeBounds(HemfGraphics ctx) {
Area area = getShape(ctx);
return area == null ? bounds : area.getBounds2D();
ctx.draw(path -> path.append(shape, false), getFillDrawStyle());
}
}

@@ -512,8 +493,8 @@ public class HemfDraw {
}

@Override
protected boolean isFill() {
return false;
protected FillDrawStyle getFillDrawStyle() {
return FillDrawStyle.DRAW;
}
}

@@ -563,12 +544,12 @@ public class HemfDraw {

@Override
public void draw(final HemfGraphics ctx) {
ctx.draw((path) -> path.moveTo(point.getX(), point.getY()));
ctx.draw((path) -> path.moveTo(point.getX(), point.getY()), FillDrawStyle.NONE);
}
}

/**
* The EMR_ARCTO record specifies an elliptical arc.
* The EMR_ARC record specifies an elliptical arc.
* It resets the current position to the end point of the arc.
*/
public static class EmfArc extends HwmfDraw.WmfArc implements HemfRecord {
@@ -587,8 +568,7 @@ public class HemfDraw {

@Override
public void draw(HemfGraphics ctx) {
super.draw(ctx);
ctx.getProperties().setLocation(endPoint);
ctx.draw(path -> path.append(getShape(), false), getFillDrawStyle());
}
}

@@ -610,6 +590,11 @@ public class HemfDraw {
size += readPointL(leis, endPoint);
return size;
}

@Override
public void draw(HemfGraphics ctx) {
ctx.draw(path -> path.append(getShape(), false), getFillDrawStyle());
}
}

/**
@@ -629,6 +614,11 @@ public class HemfDraw {
size += readPointL(leis, endPoint);
return size;
}

@Override
public void draw(HemfGraphics ctx) {
ctx.draw(path -> path.append(getShape(), false), getFillDrawStyle());
}
}

/**
@@ -646,6 +636,11 @@ public class HemfDraw {
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
return readRectL(leis, bounds);
}

@Override
public void draw(HemfGraphics ctx) {
ctx.draw(path -> path.append(getShape(), false), FillDrawStyle.FILL_DRAW);
}
}

/**
@@ -662,6 +657,11 @@ public class HemfDraw {
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
return readRectL(leis, bounds);
}

@Override
public void draw(HemfGraphics ctx) {
ctx.draw(path -> path.append(bounds, false), FillDrawStyle.FILL_DRAW);
}
}

/**
@@ -684,6 +684,11 @@ public class HemfDraw {

return size + 2*LittleEndianConsts.INT_SIZE;
}

@Override
public void draw(HemfGraphics ctx) {
ctx.draw(path -> path.append(getShape(), false), FillDrawStyle.FILL_DRAW);
}
}

/**
@@ -703,7 +708,7 @@ public class HemfDraw {

@Override
public void draw(final HemfGraphics ctx) {
ctx.draw((path) -> path.lineTo(point.getX(), point.getY()));
ctx.draw((path) -> path.lineTo(point.getX(), point.getY()), FillDrawStyle.DRAW);
}
}

@@ -728,12 +733,12 @@ public class HemfDraw {
@Override
public void draw(final HemfGraphics ctx) {
final Arc2D arc = getShape();
ctx.draw((path) -> path.append(arc, true));
ctx.draw((path) -> path.append(arc, true), getFillDrawStyle());
}
}

/** The EMR_POLYDRAW record specifies a set of line segments and Bezier curves. */
public static class EmfPolyDraw extends HwmfDraw.WmfPolygon implements HemfRecord, HemfBounded {
public static class EmfPolyDraw extends HwmfDraw.WmfPolygon implements HemfRecord {
private final Rectangle2D bounds = new Rectangle2D.Double();

@Override
@@ -808,13 +813,14 @@ public class HemfDraw {
}

@Override
public Rectangle2D getRecordBounds() {
return bounds;
protected FillDrawStyle getFillDrawStyle() {
// Draws a set of line segments and Bezier curves.
return FillDrawStyle.DRAW;
}

@Override
public Rectangle2D getShapeBounds(HemfGraphics ctx) {
return poly.getBounds2D();
public void draw(HemfGraphics ctx) {
ctx.draw(path -> path.append(poly, false), getFillDrawStyle());
}
}

@@ -854,6 +860,7 @@ public class HemfDraw {
public void draw(HemfGraphics ctx) {
final HemfDrawProperties prop = ctx.getProperties();
prop.setPath(new Path2D.Double());
prop.setUsePathBracket(true);
}

@Override
@@ -877,6 +884,12 @@ public class HemfDraw {
return 0;
}

@Override
public void draw(HemfGraphics ctx) {
final HemfDrawProperties prop = ctx.getProperties();
prop.setUsePathBracket(false);
}

@Override
public String toString() {
return "{}";
@@ -901,6 +914,7 @@ public class HemfDraw {
public void draw(HemfGraphics ctx) {
final HemfDrawProperties prop = ctx.getProperties();
prop.setPath(null);
prop.setUsePathBracket(false);
}

@Override
@@ -995,7 +1009,7 @@ public class HemfDraw {
/**
* The EMR_STROKEPATH record renders the specified path by using the current pen.
*/
public static class EmfStrokePath implements HemfRecord, HemfBounded {
public static class EmfStrokePath implements HemfRecord {
protected final Rectangle2D bounds = new Rectangle2D.Double();

@Override
@@ -1006,29 +1020,64 @@ public class HemfDraw {
@Override
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
// A 128-bit WMF RectL object, which specifies bounding rectangle, in device units
return readRectL(leis, bounds);
return (recordSize == 0) ? 0 : readRectL(leis, bounds);
}

@Override
public void draw(HemfGraphics ctx) {
HemfDrawProperties props = ctx.getProperties();
ctx.draw(props.getPath());
Path2D path = props.getPath();
path.setWindingRule(ctx.getProperties().getWindingRule());
ctx.draw(path);
}

@Override
public Rectangle2D getRecordBounds() {
return bounds;
public String toString() {
return boundsToString(bounds);
}
}


/**
* The EMR_FILLPATH record closes any open figures in the current path and fills the path's interior by
* using the current brush and polygon-filling mode.
*/
public static class EmfFillPath extends EmfStrokePath {
@Override
public Rectangle2D getShapeBounds(HemfGraphics ctx) {
HemfDrawProperties props = ctx.getProperties();
return props.getPath().getBounds2D();
public HemfRecordType getEmfRecordType() {
return HemfRecordType.fillPath;
}

@Override
public String toString() {
return "{ bounds: { x: "+bounds.getX()+", y: "+bounds.getY()+", w: "+bounds.getWidth()+", h: "+bounds.getHeight()+" }";
public void draw(HemfGraphics ctx) {
final HemfDrawProperties prop = ctx.getProperties();
final Path2D path = (Path2D)prop.getPath().clone();
path.closePath();
path.setWindingRule(ctx.getProperties().getWindingRule());
ctx.fill(path);
}
}

/**
* The EMR_STROKEANDFILLPATH record closes any open figures in a path, strokes the outline of the
* path by using the current pen, and fills its interior by using the current brush.
*/
public static class EmfStrokeAndFillPath extends EmfStrokePath {
protected final Rectangle2D bounds = new Rectangle2D.Double();

@Override
public HemfRecordType getEmfRecordType() {
return HemfRecordType.strokeAndFillPath;
}

@Override
public void draw(HemfGraphics ctx) {
HemfDrawProperties props = ctx.getProperties();
Path2D path = props.getPath();
path.closePath();
path.setWindingRule(ctx.getProperties().getWindingRule());
ctx.fill(path);
ctx.draw(path);
}
}

@@ -1080,7 +1129,7 @@ public class HemfDraw {
return 2*LittleEndianConsts.INT_SIZE;
}

private static void polyTo(final HemfGraphics ctx, final Path2D poly) {
private static void polyTo(final HemfGraphics ctx, final Path2D poly, FillDrawStyle fillDrawStyle) {
if (poly.getCurrentPoint() == null) {
return;
}
@@ -1092,6 +1141,6 @@ public class HemfDraw {
return;
}

ctx.draw((path) -> path.append(pi, true));
ctx.draw((path) -> path.append(pi, true), fillDrawStyle);
}
}

+ 26
- 124
src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfFill.java View File

@@ -51,10 +51,29 @@ public class HemfFill {
private static final int MAX_RECORD_LENGTH = 10_000_000;

public enum HemfRegionMode {
/**
* The new clipping region includes the intersection (overlapping areas)
* of the current clipping region and the current path (or new region).
*/
RGN_AND(0x01),
/**
* The new clipping region includes the union (combined areas)
* of the current clipping region and the current path (or new region).
*/
RGN_OR(0x02),
/**
* The new clipping region includes the union of the current clipping region
* and the current path (or new region) but without the overlapping areas
*/
RGN_XOR(0x03),
/**
* The new clipping region includes the areas of the current clipping region
* with those of the current path (or new region) excluded.
*/
RGN_DIFF(0x04),
/**
* The new clipping region is the current path (or the new region).
*/
RGN_COPY(0x05);

int flag;
@@ -114,7 +133,7 @@ public class HemfFill {
* optionally in combination with a brush pattern, according to a specified raster operation, stretching or
* compressing the output to fit the dimensions of the destination, if necessary.
*/
public static class EmfStretchBlt extends HwmfFill.WmfBitBlt implements HemfRecord, HemfBounded {
public static class EmfStretchBlt extends HwmfFill.WmfBitBlt implements HemfRecord {
protected final Rectangle2D bounds = new Rectangle2D.Double();

/** An XForm object that specifies a world-space to page-space transform to apply to the source bitmap. */
@@ -196,16 +215,6 @@ public class HemfFill {
return size;
}

@Override
public Rectangle2D getRecordBounds() {
return bounds;
}

@Override
public Rectangle2D getShapeBounds(HemfGraphics ctx) {
return dstBounds;
}

protected boolean srcEqualsDstDimension() {
return false;
}
@@ -226,7 +235,7 @@ public class HemfFill {
* destination rectangle, optionally in combination with a brush pattern, according to a specified raster
* operation, stretching or compressing the output to fit the dimensions of the destination, if necessary.
*/
public static class EmfStretchDiBits extends HwmfFill.WmfStretchDib implements HemfRecord, HemfBounded {
public static class EmfStretchDiBits extends HwmfFill.WmfStretchDib implements HemfRecord {
protected final Rectangle2D bounds = new Rectangle2D.Double();

@Override
@@ -286,16 +295,6 @@ public class HemfFill {

return size;
}

@Override
public Rectangle2D getRecordBounds() {
return bounds;
}

@Override
public Rectangle2D getShapeBounds(HemfGraphics ctx) {
return dstBounds;
}
}

/**
@@ -316,7 +315,7 @@ public class HemfFill {


/** The EMR_FRAMERGN record draws a border around the specified region using the specified brush. */
public static class EmfFrameRgn extends HwmfDraw.WmfFrameRegion implements HemfRecord, HemfBounded {
public static class EmfFrameRgn extends HwmfDraw.WmfFrameRegion implements HemfRecord {
private final Rectangle2D bounds = new Rectangle2D.Double();
private final List<Rectangle2D> rgnRects = new ArrayList<>();

@@ -347,23 +346,13 @@ public class HemfFill {
ctx.fill(getShape());
}

@Override
public Rectangle2D getRecordBounds() {
return bounds;
}

@Override
public Rectangle2D getShapeBounds(HemfGraphics ctx) {
return getShape().getBounds2D();
}

protected Area getShape() {
return getRgnShape(rgnRects);
}
}

/** The EMR_INVERTRGN record inverts the colors in the specified region. */
public static class EmfInvertRgn implements HemfRecord, HemfBounded {
public static class EmfInvertRgn implements HemfRecord {
protected final Rectangle2D bounds = new Rectangle2D.Double();
protected final List<Rectangle2D> rgnRects = new ArrayList<>();

@@ -382,16 +371,6 @@ public class HemfFill {
return size;
}

@Override
public Rectangle2D getRecordBounds() {
return bounds;
}

@Override
public Rectangle2D getShapeBounds(HemfGraphics ctx) {
return getShape().getBounds2D();
}

protected Area getShape() {
return getRgnShape(rgnRects);
}
@@ -409,7 +388,7 @@ public class HemfFill {
}

/** The EMR_FILLRGN record fills the specified region by using the specified brush. */
public static class EmfFillRgn extends HwmfFill.WmfFillRegion implements HemfRecord, HemfBounded {
public static class EmfFillRgn extends HwmfFill.WmfFillRegion implements HemfRecord {
protected final Rectangle2D bounds = new Rectangle2D.Double();
protected final List<Rectangle2D> rgnRects = new ArrayList<>();

@@ -429,16 +408,6 @@ public class HemfFill {
return size;
}

@Override
public Rectangle2D getRecordBounds() {
return bounds;
}

@Override
public Rectangle2D getShapeBounds(HemfGraphics ctx) {
return getShape().getBounds2D();
}

protected Area getShape() {
return getRgnShape(rgnRects);
}
@@ -474,7 +443,7 @@ public class HemfFill {
}
}

public static class EmfAlphaBlend implements HemfRecord, HemfBounded {
public static class EmfAlphaBlend implements HemfRecord {
/** the destination bounding rectangle in device units */
protected final Rectangle2D bounds = new Rectangle2D.Double();
/** the destination rectangle */
@@ -583,24 +552,13 @@ public class HemfFill {

return size;
}


@Override
public Rectangle2D getRecordBounds() {
return bounds;
}

@Override
public Rectangle2D getShapeBounds(HemfGraphics ctx) {
return destRect;
}
}

/**
* The EMR_SETDIBITSTODEVICE record specifies a block transfer of pixels from specified scanlines of
* a source bitmap to a destination rectangle.
*/
public static class EmfSetDiBitsToDevice implements HemfRecord, HemfBounded {
public static class EmfSetDiBitsToDevice implements HemfRecord {
protected final Rectangle2D bounds = new Rectangle2D.Double();
protected final Point2D dest = new Point2D.Double();
protected final Rectangle2D src = new Rectangle2D.Double();
@@ -645,16 +603,6 @@ public class HemfFill {

return size;
}

@Override
public Rectangle2D getRecordBounds() {
return bounds;
}

@Override
public Rectangle2D getShapeBounds(HemfGraphics ctx) {
return new Rectangle2D.Double(dest.getX(), dest.getY(), src.getWidth(), src.getHeight());
}
}

static long readBitmap(final LittleEndianInputStream leis, final HwmfBitmapDib bitmap,
@@ -769,52 +717,6 @@ public class HemfFill {
return 6 * LittleEndian.INT_SIZE;
}

/**
* The EMR_FILLPATH record closes any open figures in the current path and fills the path's interior by
* using the current brush and polygon-filling mode.
*/
public static class EmfFillPath implements HemfRecord, HemfBounded {
protected final Rectangle2D bounds = new Rectangle2D.Double();

@Override
public HemfRecordType getEmfRecordType() {
return HemfRecordType.fillPath;
}

@Override
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
// A 128-bit WMF RectL object, which specifies bounding rectangle, in device units
return (recordSize == 0) ? 0 : readRectL(leis, bounds);
}

@Override
public Rectangle2D getRecordBounds() {
return bounds;
}

@Override
public Rectangle2D getShapeBounds(HemfGraphics ctx) {
final HemfDrawProperties prop = ctx.getProperties();
final Path2D path = prop.getPath();
return path.getBounds2D();
}

@Override
public void draw(HemfGraphics ctx) {
final HemfDrawProperties prop = ctx.getProperties();
if (!prop.usePathBracket()) {
return;
}
final Path2D path = (Path2D)prop.getPath().clone();
path.setWindingRule(ctx.getProperties().getWindingRule());
if (prop.getBrushStyle() == HwmfBrushStyle.BS_NULL) {
ctx.draw(path);
} else {
ctx.fill(path);
}
}
}

protected static Area getRgnShape(List<Rectangle2D> rgnRects) {
final Area frame = new Area();
rgnRects.forEach((rct) -> frame.add(new Area(rct)));

+ 2
- 2
src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfRecordType.java View File

@@ -85,8 +85,8 @@ public enum HemfRecordType {
beginPath(0x0000003B, HemfDraw.EmfBeginPath::new),
endPath(0x0000003C, HemfDraw.EmfEndPath::new),
closeFigure(0x0000003D, HemfDraw.EmfCloseFigure::new),
fillPath(0x0000003E, HemfFill.EmfFillPath::new),
strokeandfillpath(0x0000003F, UnimplementedHemfRecord::new),
fillPath(0x0000003E, HemfDraw.EmfFillPath::new),
strokeAndFillPath(0x0000003F, HemfDraw.EmfStrokeAndFillPath::new),
strokePath(0x00000040, HemfDraw.EmfStrokePath::new),
flattenPath(0x00000041, HemfDraw.EmfFlattenPath::new),
widenPath(0x00000042, HemfDraw.EmfWidenPath::new),

+ 33
- 39
src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfWindowing.java View File

@@ -17,8 +17,13 @@

package org.apache.poi.hemf.record.emf;

import static org.apache.poi.hemf.record.emf.HemfDraw.readDimensionInt;
import static org.apache.poi.hemf.record.emf.HemfDraw.readPointL;

import java.io.IOException;

import org.apache.poi.hemf.draw.HemfDrawProperties;
import org.apache.poi.hemf.draw.HemfGraphics;
import org.apache.poi.hemf.record.emf.HemfFill.HemfRegionMode;
import org.apache.poi.hwmf.record.HwmfWindowing;
import org.apache.poi.util.LittleEndianConsts;
@@ -37,13 +42,7 @@ public class HemfWindowing {

@Override
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
// cx (4 bytes): A 32-bit unsigned integer that defines the x-coordinate of the point.
int width = (int)leis.readUInt();
// cy (4 bytes): A 32-bit unsigned integer that defines the y-coordinate of the point.
int height = (int)leis.readUInt();
size.setSize(width, height);

return 2*LittleEndianConsts.INT_SIZE;
return readDimensionInt(leis, size);
}
}

@@ -58,12 +57,7 @@ public class HemfWindowing {

@Override
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
// x (4 bytes): A 32-bit signed integer that defines the horizontal (x) coordinate of the point.
x = leis.readInt();
// y (4 bytes): A 32-bit signed integer that defines the vertical (y) coordinate of the point.
y = leis.readInt();

return 2*LittleEndianConsts.INT_SIZE;
return readPointL(leis, origin);
}
}

@@ -78,12 +72,7 @@ public class HemfWindowing {

@Override
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
// cx (4 bytes): A 32-bit unsigned integer that defines the x-coordinate of the point.
width = (int)leis.readUInt();
// cy (4 bytes): A 32-bit unsigned integer that defines the y-coordinate of the point.
height = (int)leis.readUInt();

return 2*LittleEndianConsts.INT_SIZE;
return readDimensionInt(leis, extents);
}
}

@@ -98,12 +87,7 @@ public class HemfWindowing {

@Override
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
// x (4 bytes): A 32-bit signed integer that defines the horizontal (x) coordinate of the point.
x = leis.readInt();
// y (4 bytes): A 32-bit signed integer that defines the vertical (y) coordinate of the point.
y = leis.readInt();

return 2*LittleEndianConsts.INT_SIZE;
return readPointL(leis, origin);
}
}

@@ -119,12 +103,7 @@ public class HemfWindowing {

@Override
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
// x (4 bytes): A 32-bit signed integer that defines the horizontal (x) coordinate of the point.
xOffset = leis.readInt();
// y (4 bytes): A 32-bit signed integer that defines the vertical (y) coordinate of the point.
yOffset = leis.readInt();

return 2*LittleEndianConsts.INT_SIZE;
return readPointL(leis, offset);
}
}

@@ -172,10 +151,11 @@ public class HemfWindowing {

@Override
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
xNum = leis.readInt();
xDenom = leis.readInt();
yNum = leis.readInt();
yDenom = leis.readInt();
double xNum = leis.readInt();
double xDenom = leis.readInt();
double yNum = leis.readInt();
double yDenom = leis.readInt();
scale.setSize(xNum / xDenom, yNum / yDenom);
return 4*LittleEndianConsts.INT_SIZE;
}
}
@@ -192,10 +172,13 @@ public class HemfWindowing {

@Override
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
xNum = leis.readInt();
xDenom = leis.readInt();
yNum = leis.readInt();
yDenom = leis.readInt();
double xNum = leis.readInt();
double xDenom = leis.readInt();
double yNum = leis.readInt();
double yDenom = leis.readInt();

scale.setSize(xNum / xDenom, yNum / yDenom);

return 4*LittleEndianConsts.INT_SIZE;
}
}
@@ -220,6 +203,17 @@ public class HemfWindowing {

return LittleEndianConsts.INT_SIZE;
}

@Override
public void draw(HemfGraphics ctx) {
HemfDrawProperties props = ctx.getProperties();
ctx.setClip(props.getPath(), regionMode);
}

@Override
public String toString() {
return "{ regionMode: '"+regionMode+"' }";
}
}

}

+ 9
- 2
src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfGraphics.java View File

@@ -57,6 +57,10 @@ import org.apache.poi.util.LocaleUtil;

public class HwmfGraphics {

public enum FillDrawStyle {
NONE, FILL, DRAW, FILL_DRAW
}

protected final List<HwmfDrawProperties> propStack = new LinkedList<>();
protected HwmfDrawProperties prop;
protected final Graphics2D graphicsCtx;
@@ -297,6 +301,7 @@ public class HwmfGraphics {
*/
public void updateWindowMapMode() {
Rectangle2D win = getProperties().getWindow();
Rectangle2D view = getProperties().getViewport();
HwmfMapMode mapMode = getProperties().getMapMode();
graphicsCtx.setTransform(initialAT);

@@ -304,8 +309,10 @@ public class HwmfGraphics {
default:
case MM_ANISOTROPIC:
// scale window bounds to output bounds
graphicsCtx.scale(bbox.getWidth()/win.getWidth(), bbox.getHeight()/win.getHeight());
graphicsCtx.translate(-win.getX(), -win.getY());
if (view != null) {
graphicsCtx.translate(view.getX() - win.getX(), view.getY() - win.getY());
graphicsCtx.scale(view.getWidth() / win.getWidth(), view.getHeight() / win.getHeight());
}
break;
case MM_ISOTROPIC:
// TODO: to be validated ...

+ 20
- 7
src/scratchpad/src/org/apache/poi/hwmf/record/HwmfBitmapDib.java View File

@@ -234,20 +234,33 @@ public class HwmfBitmapDib {

leis.reset();

assert( headerSize != 0x0C || ((((headerWidth * headerPlanes * headerBitCount.flag + 31) & ~31) / 8) * Math.abs(headerHeight)) == headerImageSize);
// The size and format of this data is determined by information in the DIBHeaderInfo field. If
// it is a BitmapCoreHeader, the size in bytes MUST be calculated as follows:

if (headerImageSize < headerSize) {
imageData = IOUtils.safelyAllocate(recordSize, MAX_RECORD_LENGTH);
leis.readFully(imageData);
return recordSize;
} else {
int fileSize = (int)Math.min(introSize+headerImageSize,recordSize);
int bodySize = ((((headerWidth * headerPlanes * headerBitCount.flag + 31) & ~31) / 8) * Math.abs(headerHeight));

// This formula SHOULD also be used to calculate the size of aData when DIBHeaderInfo is a
// BitmapInfoHeader Object, using values from that object, but only if its Compression value is
// BI_RGB, BI_BITFIELDS, or BI_CMYK.
// Otherwise, the size of aData MUST be the BitmapInfoHeader Object value ImageSize.

assert( headerSize != 0x0C || bodySize == headerImageSize);

if (headerSize == 0x0C ||
headerCompression == Compression.BI_RGB ||
headerCompression == Compression.BI_BITFIELDS ||
headerCompression == Compression.BI_CMYK) {
int fileSize = (int)Math.min(introSize+bodySize,recordSize);
imageData = IOUtils.safelyAllocate(fileSize, MAX_RECORD_LENGTH);
leis.readFully(imageData, 0, introSize);
leis.skipFully(recordSize-fileSize);
// emfs are sometimes truncated, read as much as possible
int readBytes = leis.read(imageData, introSize, fileSize-introSize);
return introSize+(recordSize-fileSize)+readBytes;
} else {
imageData = IOUtils.safelyAllocate(recordSize, MAX_RECORD_LENGTH);
leis.readFully(imageData);
return recordSize;
}
}


+ 128
- 49
src/scratchpad/src/org/apache/poi/hwmf/record/HwmfDraw.java View File

@@ -32,6 +32,8 @@ import java.util.ArrayList;
import java.util.List;

import org.apache.poi.hwmf.draw.HwmfGraphics;
import org.apache.poi.hwmf.draw.HwmfGraphics.FillDrawStyle;
import org.apache.poi.util.Internal;
import org.apache.poi.util.LittleEndianConsts;
import org.apache.poi.util.LittleEndianInputStream;

@@ -61,7 +63,7 @@ public class HwmfDraw {

@Override
public String toString() {
return "{ x: "+point.getX()+", y: "+point.getY()+" }";
return pointToString(point);
}
}

@@ -93,7 +95,7 @@ public class HwmfDraw {

@Override
public String toString() {
return "{ x: "+point.getX()+", y: "+point.getY()+" }";
return pointToString(point);
}
}

@@ -139,10 +141,17 @@ public class HwmfDraw {
Path2D p = (Path2D)poly.clone();
// don't close the path
p.setWindingRule(ctx.getProperties().getWindingRule());
if (isFill()) {
ctx.fill(p);
} else {
ctx.draw(p);
switch (getFillDrawStyle()) {
case FILL:
ctx.fill(p);
break;
case DRAW:
ctx.draw(p);
break;
case FILL_DRAW:
ctx.fill(p);
ctx.draw(p);
break;
}
}

@@ -154,8 +163,8 @@ public class HwmfDraw {
/**
* @return true, if the shape should be filled
*/
protected boolean isFill() {
return true;
protected FillDrawStyle getFillDrawStyle() {
return FillDrawStyle.FILL;
}
}

@@ -171,8 +180,8 @@ public class HwmfDraw {
}

@Override
protected boolean isFill() {
return false;
protected FillDrawStyle getFillDrawStyle() {
return FillDrawStyle.DRAW;
}
}

@@ -196,8 +205,16 @@ public class HwmfDraw {

@Override
public void draw(HwmfGraphics ctx) {
Shape s = new Ellipse2D.Double(bounds.getX(), bounds.getY(), bounds.getWidth(), bounds.getHeight());
ctx.fill(s);
ctx.fill(getShape());
}

protected Ellipse2D getShape() {
return new Ellipse2D.Double(bounds.getX(), bounds.getY(), bounds.getWidth(), bounds.getHeight());
}

@Override
public String toString() {
return boundsToString(bounds);
}
}

@@ -264,7 +281,7 @@ public class HwmfDraw {
*/
public static class WmfPolyPolygon implements HwmfRecord {

protected List<Path2D> polyList = new ArrayList<>();
protected final List<Path2D> polyList = new ArrayList<>();
@Override
public HwmfRecordType getWmfRecordType() {
@@ -316,41 +333,82 @@ public class HwmfDraw {

@Override
public void draw(HwmfGraphics ctx) {
Area area = getShape(ctx);
if (area == null) {
Shape shape = getShape(ctx);
if (shape == null) {
return;
}
if (isFill()) {
ctx.fill(area);
} else {
ctx.draw(area);
}
}

protected Area getShape(HwmfGraphics ctx) {
int windingRule = ctx.getProperties().getWindingRule();
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);
}
switch (getFillDrawStyle()) {
case DRAW:
ctx.draw(shape);
break;
case FILL:
ctx.fill(shape);
break;
case FILL_DRAW:
ctx.fill(shape);
ctx.draw(shape);
break;
}
}

return area;
protected FillDrawStyle getFillDrawStyle() {
// Each polygon SHOULD be outlined using the current pen, and filled using the current brush and
// polygon fill mode that are defined in the playback device context. The polygons defined by this
// record can overlap.
return FillDrawStyle.FILL_DRAW;
}

/**
* @return true, if the shape should be filled
* @return true, if a polyline should be closed, i.e. is a polygon
*/
protected boolean isFill() {
protected boolean isClosed() {
return true;
}

protected Shape getShape(HwmfGraphics ctx) {
int windingRule = ctx.getProperties().getWindingRule();

if (isClosed()) {
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);
}
}
return area;
} else {
Path2D path = new Path2D.Double();
path.setWindingRule(windingRule);
for (Path2D poly : polyList) {
path.append(poly, false);
}
return path;
}
}

@Override
public String toString() {
final StringBuilder sb = new StringBuilder();
sb.append("{ polyList: [");
boolean isFirst = true;
for (Path2D p : polyList) {
if (!isFirst) {
sb.append(",");
}
isFirst = false;
sb.append("{ points: ");
sb.append(polyToString(p));
sb.append(" }");
}
sb.append(" }");
return sb.toString();
}
}

/**
@@ -377,12 +435,7 @@ public class HwmfDraw {

@Override
public String toString() {
return
"{ bounds: " +
"{ x: "+bounds.getX()+
", y: "+bounds.getY()+
", w: "+bounds.getWidth()+
", h: "+bounds.getHeight()+" } }";
return boundsToString(bounds);
}
}

@@ -450,8 +503,11 @@ public class HwmfDraw {

@Override
public void draw(HwmfGraphics ctx) {
Shape s = new RoundRectangle2D.Double(bounds.getX(), bounds.getY(), bounds.getWidth(), bounds.getHeight(), width, height);
ctx.fill(s);
ctx.fill(getShape());
}

protected RoundRectangle2D getShape() {
return new RoundRectangle2D.Double(bounds.getX(), bounds.getY(), bounds.getWidth(), bounds.getHeight(), width, height);
}
}

@@ -487,15 +543,28 @@ public class HwmfDraw {
@Override
public void draw(HwmfGraphics ctx) {
Shape s = getShape();
switch (getFillDrawStyle()) {
case FILL:
ctx.fill(s);
break;
case DRAW:
ctx.draw(s);
break;
case FILL_DRAW:
ctx.fill(s);
ctx.draw(s);
break;
}
}

protected FillDrawStyle getFillDrawStyle() {
switch (getWmfRecordType()) {
default:
case arc:
ctx.draw(s);
break;
return FillDrawStyle.DRAW;
case chord:
case pie:
ctx.fill(s);
break;
return FillDrawStyle.FILL_DRAW;
}
}

@@ -665,4 +734,14 @@ public class HwmfDraw {
sb.append("]");
return sb.toString();
}

@Internal
public static String pointToString(Point2D point) {
return "{ x: "+point.getX()+", y: "+point.getY()+" }";
}

@Internal
public static String boundsToString(Rectangle2D bounds) {
return "{ x: "+bounds.getX()+", y: "+bounds.getY()+", w: "+bounds.getWidth()+", h: "+bounds.getHeight()+" }";
}
}

+ 3
- 2
src/scratchpad/src/org/apache/poi/hwmf/record/HwmfFill.java View File

@@ -17,6 +17,7 @@

package org.apache.poi.hwmf.record;

import static org.apache.poi.hwmf.record.HwmfDraw.boundsToString;
import static org.apache.poi.hwmf.record.HwmfDraw.readPointS;

import java.awt.Shape;
@@ -420,8 +421,8 @@ public class HwmfFill {
public String toString() {
return
"{ rasterOperation: '"+rasterOperation+"'"+
", srcBounds: { x: "+srcBounds.getX()+", y: "+srcBounds.getY()+", w: "+srcBounds.getWidth()+", h: "+srcBounds.getHeight()+" }"+
", dstBounds: { x: "+dstBounds.getX()+", y: "+dstBounds.getY()+", w: "+dstBounds.getWidth()+", h: "+dstBounds.getHeight()+" }"+
", srcBounds: "+boundsToString(srcBounds)+
", dstBounds: "+boundsToString(dstBounds)+
"}";
}
}

+ 15
- 0
src/scratchpad/src/org/apache/poi/hwmf/record/HwmfMisc.java View File

@@ -244,6 +244,11 @@ public class HwmfMisc {
ctx.getProperties().setMapMode(mapMode);
ctx.updateWindowMapMode();
}

@Override
public String toString() {
return "{ mapMode: '"+mapMode+"' }";
}
}

/**
@@ -275,6 +280,11 @@ public class HwmfMisc {
public void draw(HwmfGraphics ctx) {

}

@Override
public String toString() {
return "{ mapperValues: "+mapperValues+" }";
}
}

/**
@@ -379,6 +389,11 @@ public class HwmfMisc {
public void draw(HwmfGraphics ctx) {

}

@Override
public String toString() {
return "{ stretchBltMode: '"+stretchBltMode+"' }";
}
}

/**

+ 4
- 2
src/scratchpad/src/org/apache/poi/hwmf/record/HwmfText.java View File

@@ -17,6 +17,8 @@

package org.apache.poi.hwmf.record;

import static org.apache.poi.hwmf.record.HwmfDraw.boundsToString;
import static org.apache.poi.hwmf.record.HwmfDraw.pointToString;
import static org.apache.poi.hwmf.record.HwmfDraw.readPointS;
import static org.apache.poi.hwmf.record.HwmfDraw.readRectS;

@@ -422,8 +424,8 @@ public class HwmfText {
}

return
"{ reference: { x: "+reference.getX()+", y: "+reference.getY()+" }"+
", bounds: { x: "+bounds.getX()+", y: "+bounds.getY()+", w: "+bounds.getWidth()+", h: "+bounds.getHeight()+"}"+
"{ reference: " + pointToString(reference) +
", bounds: " + boundsToString(bounds) +
", text: '"+text.replaceAll("\\p{Cntrl}",".")+"'"+
"}";
}

+ 133
- 173
src/scratchpad/src/org/apache/poi/hwmf/record/HwmfWindowing.java View File

@@ -17,11 +17,15 @@

package org.apache.poi.hwmf.record;

import static org.apache.poi.hwmf.record.HwmfDraw.boundsToString;
import static org.apache.poi.hwmf.record.HwmfDraw.pointToString;
import static org.apache.poi.hwmf.record.HwmfDraw.readBounds;
import static org.apache.poi.hwmf.record.HwmfDraw.readPointS;

import java.awt.Shape;
import java.awt.geom.Area;
import java.awt.geom.Dimension2D;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.io.IOException;

@@ -37,11 +41,7 @@ public class HwmfWindowing {
*/
public static class WmfSetViewportOrg implements HwmfRecord {

/** A signed integer that defines the vertical offset, in device units. */
protected int y;

/** A signed integer that defines the horizontal offset, in device units. */
protected int x;
protected final Point2D origin = new Point2D.Double();

@Override
public HwmfRecordType getWmfRecordType() {
@@ -50,14 +50,18 @@ public class HwmfWindowing {

@Override
public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException {
y = leis.readShort();
x = leis.readShort();
return 2*LittleEndianConsts.SHORT_SIZE;
return readPointS(leis, origin);
}

@Override
public void draw(HwmfGraphics ctx) {
ctx.getProperties().setViewportOrg(x, y);
ctx.getProperties().setViewportOrg(origin.getX(), origin.getY());
ctx.updateWindowMapMode();
}

@Override
public String toString() {
return pointToString(origin);
}
}

@@ -67,11 +71,7 @@ public class HwmfWindowing {
*/
public static class WmfSetViewportExt implements HwmfRecord {

/** A signed integer that defines the vertical extent of the viewport in device units. */
protected int height;

/** A signed integer that defines the horizontal extent of the viewport in device units. */
protected int width;
protected final Dimension2D extents = new Dimension2DDouble();

@Override
public HwmfRecordType getWmfRecordType() {
@@ -80,14 +80,23 @@ public class HwmfWindowing {

@Override
public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException {
height = leis.readShort();
width = leis.readShort();
// A signed integer that defines the vertical extent of the viewport in device units.
int height = leis.readShort();
// A signed integer that defines the horizontal extent of the viewport in device units.
int width = leis.readShort();
extents.setSize(width, height);
return 2*LittleEndianConsts.SHORT_SIZE;
}

@Override
public void draw(HwmfGraphics ctx) {
ctx.getProperties().setViewportExt(width, height);
ctx.getProperties().setViewportExt(extents.getWidth(), extents.getHeight());
ctx.updateWindowMapMode();
}

@Override
public String toString() {
return "{ width: "+extents.getWidth()+", height: "+extents.getHeight()+" }";
}
}

@@ -97,15 +106,7 @@ public class HwmfWindowing {
*/
public static class WmfOffsetViewportOrg implements HwmfRecord {

/**
* A 16-bit signed integer that defines the vertical offset, in device units.
*/
private int yOffset;

/**
* A 16-bit signed integer that defines the horizontal offset, in device units.
*/
private int xOffset;
protected final Point2D offset = new Point2D.Double();

@Override
public HwmfRecordType getWmfRecordType() {
@@ -114,9 +115,7 @@ public class HwmfWindowing {

@Override
public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException {
yOffset = leis.readShort();
xOffset = leis.readShort();
return 2*LittleEndianConsts.SHORT_SIZE;
return readPointS(leis, offset);
}

@Override
@@ -124,7 +123,12 @@ public class HwmfWindowing {
Rectangle2D viewport = ctx.getProperties().getViewport();
double x = (viewport == null) ? 0 : viewport.getX();
double y = (viewport == null) ? 0 : viewport.getY();
ctx.getProperties().setViewportOrg(x+xOffset, y+yOffset);
ctx.getProperties().setViewportOrg(x+offset.getX(), y+offset.getY());
}

@Override
public String toString() {
return pointToString(offset);
}
}

@@ -133,11 +137,7 @@ public class HwmfWindowing {
*/
public static class WmfSetWindowOrg implements HwmfRecord {

/** A signed integer that defines the y-coordinate, in logical units. */
protected int y;

/** A signed integer that defines the x-coordinate, in logical units. */
protected int x;
protected final Point2D origin = new Point2D.Double();

@Override
public HwmfRecordType getWmfRecordType() {
@@ -146,23 +146,26 @@ public class HwmfWindowing {

@Override
public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException {
y = leis.readShort();
x = leis.readShort();
return 2*LittleEndianConsts.SHORT_SIZE;
return readPointS(leis, origin);
}

@Override
public void draw(HwmfGraphics ctx) {
ctx.getProperties().setWindowOrg(x, y);
ctx.getProperties().setWindowOrg(getX(), getY());
ctx.updateWindowMapMode();
}

public int getY() {
return y;
public double getY() {
return origin.getY();
}

public int getX() {
return x;
public double getX() {
return origin.getX();
}

@Override
public String toString() {
return pointToString(origin);
}
}

@@ -198,6 +201,11 @@ public class HwmfWindowing {
public Dimension2D getSize() {
return size;
}

@Override
public String toString() {
return "{ width: "+size.getWidth()+", height: "+size.getHeight()+" }";
}
}

/**
@@ -206,15 +214,7 @@ public class HwmfWindowing {
*/
public static class WmfOffsetWindowOrg implements HwmfRecord {

/**
* A 16-bit signed integer that defines the vertical offset, in device units.
*/
private int yOffset;

/**
* A 16-bit signed integer that defines the horizontal offset, in device units.
*/
private int xOffset;
protected final Point2D offset = new Point2D.Double();

@Override
public HwmfRecordType getWmfRecordType() {
@@ -223,17 +223,20 @@ public class HwmfWindowing {

@Override
public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException {
yOffset = leis.readShort();
xOffset = leis.readShort();
return 2*LittleEndianConsts.SHORT_SIZE;
return readPointS(leis, offset);
}

@Override
public void draw(HwmfGraphics ctx) {
Rectangle2D window = ctx.getProperties().getWindow();
ctx.getProperties().setWindowOrg(window.getX()+xOffset, window.getY()+yOffset);
ctx.getProperties().setWindowOrg(window.getX()+offset.getX(), window.getY()+offset.getY());
ctx.updateWindowMapMode();
}

@Override
public String toString() {
return pointToString(offset);
}
}

/**
@@ -242,29 +245,7 @@ public class HwmfWindowing {
*/
public static class WmfScaleWindowExt implements HwmfRecord {

/**
* A signed integer that defines the amount by which to divide the
* result of multiplying the current y-extent by the value of the yNum member.
*/
protected int yDenom;

/**
* A signed integer that defines the amount by which to multiply the
* current y-extent.
*/
protected int yNum;

/**
* A signed integer that defines the amount by which to divide the
* result of multiplying the current x-extent by the value of the xNum member.
*/
protected int xDenom;

/**
* A signed integer that defines the amount by which to multiply the
* current x-extent.
*/
protected int xNum;
protected final Dimension2D scale = new Dimension2DDouble();

@Override
public HwmfRecordType getWmfRecordType() {
@@ -273,21 +254,37 @@ public class HwmfWindowing {

@Override
public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException {
yDenom = leis.readShort();
yNum = leis.readShort();
xDenom = leis.readShort();
xNum = leis.readShort();
// A signed integer that defines the amount by which to divide the
// result of multiplying the current y-extent by the value of the yNum member.
double yDenom = leis.readShort();
// A signed integer that defines the amount by which to multiply the
// current y-extent.
double yNum = leis.readShort();
// A signed integer that defines the amount by which to divide the
// result of multiplying the current x-extent by the value of the xNum member.
double xDenom = leis.readShort();
// A signed integer that defines the amount by which to multiply the
// current x-extent.
double xNum = leis.readShort();

scale.setSize(xNum / xDenom, yNum / yDenom);

return 4*LittleEndianConsts.SHORT_SIZE;
}

@Override
public void draw(HwmfGraphics ctx) {
Rectangle2D window = ctx.getProperties().getWindow();
double width = window.getWidth() * xNum / xDenom;
double height = window.getHeight() * yNum / yDenom;
double width = window.getWidth() * scale.getWidth();
double height = window.getHeight() * scale.getHeight();
ctx.getProperties().setWindowExt(width, height);
ctx.updateWindowMapMode();
}

@Override
public String toString() {
return "{ scaleX: "+scale.getWidth()+", scaleY: "+scale.getHeight()+" }";
}
}


@@ -298,29 +295,7 @@ public class HwmfWindowing {
*/
public static class WmfScaleViewportExt implements HwmfRecord {

/**
* A signed integer that defines the amount by which to divide the
* result of multiplying the current y-extent by the value of the yNum member.
*/
protected int yDenom;

/**
* A signed integer that defines the amount by which to multiply the
* current y-extent.
*/
protected int yNum;

/**
* A signed integer that defines the amount by which to divide the
* result of multiplying the current x-extent by the value of the xNum member.
*/
protected int xDenom;

/**
* A signed integer that defines the amount by which to multiply the
* current x-extent.
*/
protected int xNum;
protected final Dimension2D scale = new Dimension2DDouble();

@Override
public HwmfRecordType getWmfRecordType() {
@@ -329,10 +304,21 @@ public class HwmfWindowing {

@Override
public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException {
yDenom = leis.readShort();
yNum = leis.readShort();
xDenom = leis.readShort();
xNum = leis.readShort();
// A signed integer that defines the amount by which to divide the
// result of multiplying the current y-extent by the value of the yNum member.
double yDenom = leis.readShort();
// A signed integer that defines the amount by which to multiply the
// current y-extent.
double yNum = leis.readShort();
// A signed integer that defines the amount by which to divide the
// result of multiplying the current x-extent by the value of the xNum member.
double xDenom = leis.readShort();
// A signed integer that defines the amount by which to multiply the
// current x-extent.
double xNum = leis.readShort();

scale.setSize(xNum / xDenom, yNum / yDenom);

return 4*LittleEndianConsts.SHORT_SIZE;
}

@@ -342,10 +328,15 @@ public class HwmfWindowing {
if (viewport == null) {
viewport = ctx.getProperties().getWindow();
}
double width = viewport.getWidth() * xNum / xDenom;
double height = viewport.getHeight() * yNum / yDenom;
double width = viewport.getWidth() * scale.getWidth();
double height = viewport.getHeight() * scale.getHeight();
ctx.getProperties().setViewportExt(width, height);
}

@Override
public String toString() {
return "{ scaleX: "+scale.getWidth()+", scaleY: "+scale.getHeight()+" }";
}
}

/**
@@ -354,15 +345,7 @@ public class HwmfWindowing {
*/
public static class WmfOffsetClipRgn implements HwmfRecord, HwmfObjectTableEntry {

/**
* A signed integer that defines the number of logical units to move up or down.
*/
protected int yOffset;

/**
* A signed integer that defines the number of logical units to move left or right.
*/
protected int xOffset;
protected final Point2D offset = new Point2D.Double();

@Override
public HwmfRecordType getWmfRecordType() {
@@ -371,9 +354,7 @@ public class HwmfWindowing {

@Override
public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException {
yOffset = leis.readShort();
xOffset = leis.readShort();
return 2*LittleEndianConsts.SHORT_SIZE;
return readPointS(leis, offset);
}

@Override
@@ -384,6 +365,11 @@ public class HwmfWindowing {
@Override
public void applyObject(HwmfGraphics ctx) {
}

@Override
public String toString() {
return pointToString(offset);
}
}

/**
@@ -402,20 +388,7 @@ public class HwmfWindowing {

@Override
public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException {
// A 16-bit signed integer that defines the y-coordinate, in logical units, of the
// lower-right corner of the rectangle.
final int bottom = leis.readShort();
// A 16-bit signed integer that defines the x-coordinate, in logical units, of the
// lower-right corner of the rectangle.
final int right = leis.readShort();
// A 16-bit signed integer that defines the y-coordinate, in logical units, of the
// upper-left corner of the rectangle.
final int top = leis.readShort();
// A 16-bit signed integer that defines the x-coordinate, in logical units, of the
// upper-left corner of the rectangle.
final int left = leis.readShort();
bounds.setRect(left, top, right-left, bottom-top);
return 4*LittleEndianConsts.SHORT_SIZE;
return readBounds(leis, bounds);
}

@Override
@@ -426,6 +399,11 @@ public class HwmfWindowing {
@Override
public void applyObject(HwmfGraphics ctx) {
}

@Override
public String toString() {
return boundsToString(bounds);
}
}


@@ -459,12 +437,7 @@ public class HwmfWindowing {

@Override
public String toString() {
return
"{ x: "+bounds.getX()+
", y: "+bounds.getY()+
", w: "+bounds.getWidth()+
", h: "+bounds.getHeight()+
"}";
return boundsToString(bounds);
}
}

@@ -572,29 +545,7 @@ public class HwmfWindowing {
*/
private int maxScan;

/**
* A 16-bit signed integer that defines the y-coordinate, in logical units, of the
* lower-right corner of the rectangle.
*/
private int bottom;

/**
* A 16-bit signed integer that defines the x-coordinate, in logical units, of the
* lower-right corner of the rectangle.
*/
private int right;

/**
* A 16-bit signed integer that defines the y-coordinate, in logical units, of the
* upper-left corner of the rectangle.
*/
private int top;

/**
* A 16-bit signed integer that defines the x-coordinate, in logical units, of the
* upper-left corner of the rectangle.
*/
private int left;
private Rectangle2D bounds = new Rectangle2D.Double();

/**
* An array of Scan objects that define the scanlines in the region.
@@ -614,10 +565,19 @@ public class HwmfWindowing {
regionSize = leis.readShort();
scanCount = leis.readShort();
maxScan = leis.readShort();
left = leis.readShort();
top = leis.readShort();
right = leis.readShort();
bottom = leis.readShort();
// A 16-bit signed integer that defines the x-coordinate, in logical units, of the
// upper-left corner of the rectangle.
double left = leis.readShort();
// A 16-bit signed integer that defines the y-coordinate, in logical units, of the
// upper-left corner of the rectangle.
double top = leis.readShort();
// A 16-bit signed integer that defines the x-coordinate, in logical units, of the
// lower-right corner of the rectangle.
double right = leis.readShort();
// A 16-bit signed integer that defines the y-coordinate, in logical units, of the
// lower-right corner of the rectangle.
double bottom = leis.readShort();
bounds.setRect(left, top, right-left, bottom-top);
int size = 9*LittleEndianConsts.SHORT_SIZE+LittleEndianConsts.INT_SIZE;


+ 9
- 3
src/scratchpad/testcases/org/apache/poi/hemf/usermodel/HemfPictureTest.java View File

@@ -63,7 +63,13 @@ public class HemfPictureTest {
public void paint() throws IOException {
byte buf[] = new byte[50_000_000];

final boolean writeLog = false;
// good test samples to validate rendering:
// emfs/commoncrawl2/NB/NBWN2YH5VFCLZRFDQU7PB7IDD4UKY7DN_2.emf
// emfs/govdocs1/777/777525.ppt_0.emf
// emfs/govdocs1/844/844795.ppt_2.emf
// emfs/commoncrawl2/TO/TOYZSTNUSW5OFCFUQ6T5FBLIDLCRF3NH_0.emf

final boolean writeLog = true;
final boolean dumpRecords = false;
final boolean savePng = true;

@@ -257,7 +263,7 @@ public class HemfPictureTest {
long fudgeFactorX = 1000;
StringBuilder sb = new StringBuilder();
for (HemfRecord record : pic) {
if (record.getEmfRecordType().equals(HemfRecordType.exttextoutw)) {
if (record.getEmfRecordType().equals(HemfRecordType.extTextOutW)) {
HemfText.EmfExtTextOutW extTextOutW = (HemfText.EmfExtTextOutW) record;
Point2D reference = extTextOutW.getReference();
if (lastY > -1 && lastY != reference.getY()) {
@@ -291,7 +297,7 @@ public class HemfPictureTest {
expectedParts.add("testPDF.pdf");
int foundExpected = 0;
for (HemfRecord record : pic) {
if (record.getEmfRecordType().equals(HemfRecordType.exttextoutw)) {
if (record.getEmfRecordType().equals(HemfRecordType.extTextOutW)) {
HemfText.EmfExtTextOutW extTextOutW = (HemfText.EmfExtTextOutW) record;
Point2D reference = extTextOutW.getReference();
if (lastY > -1 && lastY != reference.getY()) {

Loading…
Cancel
Save