From: Andreas Beeker Date: Sat, 27 Oct 2018 00:51:19 +0000 (+0000) Subject: #60656 - Support export file that contains emf and render it correctly X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=bc02eddd81f71ccdeb4aaee3f9f671618a8abe91;p=poi.git #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 --- diff --git a/src/scratchpad/src/org/apache/poi/hemf/draw/HemfDrawProperties.java b/src/scratchpad/src/org/apache/poi/hemf/draw/HemfDrawProperties.java index b12a4caa6c..f22f5a460f 100644 --- a/src/scratchpad/src/org/apache/poi/hemf/draw/HemfDrawProperties.java +++ b/src/scratchpad/src/org/apache/poi/hemf/draw/HemfDrawProperties.java @@ -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; } } diff --git a/src/scratchpad/src/org/apache/poi/hemf/draw/HemfGraphics.java b/src/scratchpad/src/org/apache/poi/hemf/draw/HemfGraphics.java index 011cb56ff7..1d947f24ff 100644 --- a/src/scratchpad/src/org/apache/poi/hemf/draw/HemfGraphics.java +++ b/src/scratchpad/src/org/apache/poi/hemf/draw/HemfGraphics.java @@ -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 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 pathConsumer) { + public void draw(Consumer 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; + } } } diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfBounded.java b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfBounded.java deleted file mode 100644 index dee8f94ffa..0000000000 --- a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfBounded.java +++ /dev/null @@ -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); -} diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfDraw.java b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfDraw.java index 0eb6522e44..103f90a052 100644 --- a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfDraw.java +++ b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfDraw.java @@ -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 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); } } diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfFill.java b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfFill.java index 90a91a0bd1..7b172d5506 100644 --- a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfFill.java +++ b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfFill.java @@ -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 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 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 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 rgnRects) { final Area frame = new Area(); rgnRects.forEach((rct) -> frame.add(new Area(rct))); diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfRecordType.java b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfRecordType.java index 5b19f9d128..25e7149251 100644 --- a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfRecordType.java +++ b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfRecordType.java @@ -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), diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfWindowing.java b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfWindowing.java index 03c033b763..294bfdcb99 100644 --- a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfWindowing.java +++ b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfWindowing.java @@ -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+"' }"; + } } } \ No newline at end of file diff --git a/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfGraphics.java b/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfGraphics.java index 7a325dc0a0..0038298946 100644 --- a/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfGraphics.java +++ b/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfGraphics.java @@ -57,6 +57,10 @@ import org.apache.poi.util.LocaleUtil; public class HwmfGraphics { + public enum FillDrawStyle { + NONE, FILL, DRAW, FILL_DRAW + } + protected final List 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 ... diff --git a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfBitmapDib.java b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfBitmapDib.java index a6f378512e..e6a8e302a3 100644 --- a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfBitmapDib.java +++ b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfBitmapDib.java @@ -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; } } diff --git a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfDraw.java b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfDraw.java index b4e7ba73ed..107d440499 100644 --- a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfDraw.java +++ b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfDraw.java @@ -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 polyList = new ArrayList<>(); + protected final List 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()+" }"; + } } diff --git a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfFill.java b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfFill.java index 4f15272e73..e335db4577 100644 --- a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfFill.java +++ b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfFill.java @@ -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)+ "}"; } } diff --git a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfMisc.java b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfMisc.java index 7197674ffa..7303d2c6fe 100644 --- a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfMisc.java +++ b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfMisc.java @@ -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+"' }"; + } } /** diff --git a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfText.java b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfText.java index a0b240d048..024e1dedf2 100644 --- a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfText.java +++ b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfText.java @@ -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}",".")+"'"+ "}"; } diff --git a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfWindowing.java b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfWindowing.java index 6e17b574fd..88915f8ad2 100644 --- a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfWindowing.java +++ b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfWindowing.java @@ -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; diff --git a/src/scratchpad/testcases/org/apache/poi/hemf/usermodel/HemfPictureTest.java b/src/scratchpad/testcases/org/apache/poi/hemf/usermodel/HemfPictureTest.java index cabe627f66..8ff4742be6 100644 --- a/src/scratchpad/testcases/org/apache/poi/hemf/usermodel/HemfPictureTest.java +++ b/src/scratchpad/testcases/org/apache/poi/hemf/usermodel/HemfPictureTest.java @@ -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()) {