]> source.dussan.org Git - poi.git/commitdiff
Bug 60656 - EMF image support in slideshows
authorAndreas Beeker <kiwiwings@apache.org>
Thu, 6 Jun 2019 22:32:41 +0000 (22:32 +0000)
committerAndreas Beeker <kiwiwings@apache.org>
Thu, 6 Jun 2019 22:32:41 +0000 (22:32 +0000)
git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1860732 13f79535-47bb-0310-9956-ffa450edef68

22 files changed:
src/scratchpad/src/org/apache/poi/hemf/draw/HemfDrawProperties.java
src/scratchpad/src/org/apache/poi/hemf/draw/HemfGraphics.java
src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfComment.java
src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfDraw.java
src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfFill.java
src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfMisc.java
src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusBrush.java
src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusDraw.java
src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusFont.java
src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusHeader.java
src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusImage.java
src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusMisc.java
src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusObject.java
src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusPath.java
src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusPen.java
src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusRecord.java
src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusRegion.java
src/scratchpad/src/org/apache/poi/hemf/usermodel/HemfEmbeddedIterator.java
src/scratchpad/src/org/apache/poi/hemf/usermodel/HemfPicture.java
src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfGraphics.java
src/scratchpad/src/org/apache/poi/hwmf/record/HwmfDraw.java
src/scratchpad/testcases/org/apache/poi/hemf/usermodel/HemfPictureTest.java

index 98b07d4c6bc7ecfa0189d2291aaef18e7b74eb53..2a05b16080caaf1b51bdd800293bed31b48f1afc 100644 (file)
 
 package org.apache.poi.hemf.draw;
 
-import java.awt.Shape;
 import java.awt.geom.Path2D;
+import java.awt.image.BufferedImage;
 
+import org.apache.poi.hemf.record.emfplus.HemfPlusBrush.EmfPlusHatchStyle;
 import org.apache.poi.hwmf.draw.HwmfDrawProperties;
 
 public class HemfDrawProperties extends HwmfDrawProperties {
@@ -27,6 +28,8 @@ public class HemfDrawProperties extends HwmfDrawProperties {
     /** Path for path bracket operations */
     protected Path2D path = null;
     protected boolean usePathBracket = false;
+    private EmfPlusHatchStyle emfPlusBrushHatch;
+    private BufferedImage emfPlusImage;
 
 
     public HemfDrawProperties() {
@@ -35,8 +38,11 @@ public class HemfDrawProperties extends HwmfDrawProperties {
     public HemfDrawProperties(HemfDrawProperties other) {
         super(other);
         path = (other.path != null) ? (Path2D)other.path.clone() : null;
+        usePathBracket = other.usePathBracket;
+        emfPlusBrushHatch = other.emfPlusBrushHatch;
         // TODO: check how to clone
         clip = other.clip;
+        emfPlusImage = other.emfPlusImage;
     }
 
     /**
@@ -66,4 +72,20 @@ public class HemfDrawProperties extends HwmfDrawProperties {
     public void setUsePathBracket(boolean usePathBracket) {
         this.usePathBracket = usePathBracket;
     }
+
+    public EmfPlusHatchStyle getEmfPlusBrushHatch() {
+        return emfPlusBrushHatch;
+    }
+
+    public void setEmfPlusBrushHatch(EmfPlusHatchStyle emfPlusBrushHatch) {
+        this.emfPlusBrushHatch = emfPlusBrushHatch;
+    }
+
+    public BufferedImage getEmfPlusImage() {
+        return emfPlusImage;
+    }
+
+    public void setEmfPlusImage(BufferedImage emfPlusImage) {
+        this.emfPlusImage = emfPlusImage;
+    }
 }
index c7e99965f5fff2e8bb552cddc741c56cdd65bf91..10e42765335b79a12a559be7f66db43a7e595e49 100644 (file)
@@ -22,12 +22,15 @@ import static org.apache.poi.hwmf.record.HwmfBrushStyle.BS_SOLID;
 
 import java.awt.Color;
 import java.awt.Graphics2D;
+import java.awt.Paint;
 import java.awt.geom.Path2D;
 import java.awt.geom.Point2D;
 import java.awt.geom.Rectangle2D;
 import java.util.function.Consumer;
 
+import org.apache.poi.hemf.record.emf.HemfComment.EmfComment;
 import org.apache.poi.hemf.record.emf.HemfRecord;
+import org.apache.poi.hemf.record.emfplus.HemfPlusRecord;
 import org.apache.poi.hwmf.draw.HwmfDrawProperties;
 import org.apache.poi.hwmf.draw.HwmfGraphics;
 import org.apache.poi.hwmf.record.HwmfColorRef;
@@ -37,12 +40,20 @@ import org.apache.poi.util.Internal;
 
 public class HemfGraphics extends HwmfGraphics {
 
+    public enum EmfRenderState {
+        INITIAL,
+        EMF_ONLY,
+        EMFPLUS_ONLY,
+        EMF_DCONTEXT
+    }
+
     private static final HwmfColorRef WHITE = new HwmfColorRef(Color.WHITE);
     private static final HwmfColorRef LTGRAY = new HwmfColorRef(new Color(0x00C0C0C0));
     private static final HwmfColorRef GRAY = new HwmfColorRef(new Color(0x00808080));
     private static final HwmfColorRef DKGRAY = new HwmfColorRef(new Color(0x00404040));
     private static final HwmfColorRef BLACK = new HwmfColorRef(Color.BLACK);
 
+    private EmfRenderState renderState = EmfRenderState.INITIAL;
 
     public HemfGraphics(Graphics2D graphicsCtx, Rectangle2D bbox) {
         super(graphicsCtx,bbox);
@@ -62,8 +73,49 @@ public class HemfGraphics extends HwmfGraphics {
             : new HemfDrawProperties((HemfDrawProperties)oldProps);
     }
 
+    public EmfRenderState getRenderState() {
+        return renderState;
+    }
+
+    public void setRenderState(EmfRenderState renderState) {
+        this.renderState = renderState;
+    }
+
     public void draw(HemfRecord r) {
-        r.draw(this);
+        switch (renderState) {
+            case EMF_DCONTEXT:
+                // keep the dcontext state, if the next record is an EMF+ record
+                // only reset it, when we are processing EMF records again
+                if (!(r instanceof EmfComment)) {
+                    renderState = EmfRenderState.INITIAL;
+                }
+                r.draw(this);
+                break;
+            case INITIAL:
+                r.draw(this);
+                break;
+            case EMF_ONLY:
+            case EMFPLUS_ONLY:
+                if ((r instanceof EmfComment) == (renderState == EmfRenderState.EMFPLUS_ONLY)) {
+                    r.draw(this);
+                }
+                break;
+            default:
+                break;
+        }
+    }
+
+    public void draw(HemfPlusRecord r) {
+        switch (renderState) {
+            case EMFPLUS_ONLY:
+            case EMF_DCONTEXT:
+            case INITIAL:
+                r.draw(this);
+                break;
+            case EMF_ONLY:
+            default:
+                break;
+        }
     }
 
     @Internal
@@ -131,14 +183,36 @@ public class HemfGraphics extends HwmfGraphics {
      * @see HwmfGraphics#addObjectTableEntry(HwmfObjectTableEntry)
      */
     public void addObjectTableEntry(HwmfObjectTableEntry entry, int index) {
-        if (index < 1) {
-            throw new IndexOutOfBoundsException("Object table entry index in EMF must be > 0 - invalid index: "+index);
-        }
-
+        checkTableEntryIndex(index);
         objectIndexes.set(index);
         objectTable.put(index, entry);
     }
 
+    /**
+     * Gets a record which was registered earliser
+     * @param index the record index
+     * @return the record or {@code null} if it doesn't exist
+     */
+    public HwmfObjectTableEntry getObjectTableEntry(int index) {
+        checkTableEntryIndex(index);
+        return objectTable.get(index);
+    }
+
+    private void checkTableEntryIndex(int index) {
+        if (renderState != EmfRenderState.EMFPLUS_ONLY) {
+            // in EMF the index must > 0
+            if (index < 1) {
+                throw new IndexOutOfBoundsException("Object table entry index in EMF must be > 0 - invalid index: "+index);
+            }
+        } else {
+            // in EMF+ the index must be between 0 and 63
+            if (index < 0 || index > 63) {
+                throw new IndexOutOfBoundsException("Object table entry index in EMF+ must be [0..63] - invalid index: "+index);
+            }
+        }
+    }
+
+
     @Override
     public void applyObjectTableEntry(int index) {
         if ((index & 0x80000000) != 0) {
@@ -256,4 +330,10 @@ public class HemfGraphics extends HwmfGraphics {
                 break;
         }
     }
+
+    @Override
+    protected Paint getHatchedFill() {
+        // TODO: use EmfPlusHatchBrushData
+        return super.getHatchedFill();
+    }
 }
index a2cc75886c4918687c10a6f3605b78cb56cb5e83..1bd5bd6c25d214d5ff1e1e1984bb5f7629e4a3a9 100644 (file)
@@ -26,6 +26,7 @@ import java.util.Iterator;
 import java.util.List;
 import java.util.function.Supplier;
 
+import org.apache.poi.hemf.draw.HemfGraphics;
 import org.apache.poi.hemf.record.emfplus.HemfPlusRecord;
 import org.apache.poi.hemf.record.emfplus.HemfPlusRecordIterator;
 import org.apache.poi.hwmf.usermodel.HwmfPicture;
@@ -102,6 +103,17 @@ public class HemfComment {
             return data;
         }
 
+        @Override
+        public void draw(HemfGraphics ctx) {
+            if (data instanceof EmfCommentDataPlus) {
+                if (ctx.getRenderState() == HemfGraphics.EmfRenderState.INITIAL) {
+                    ctx.setRenderState(HemfGraphics.EmfRenderState.EMFPLUS_ONLY);
+                }
+
+                ((EmfCommentDataPlus)data).draw(ctx);
+            }
+        }
+
         @Override
         public String toString() {
             return "{ data: "+data+" }";
@@ -255,6 +267,10 @@ public class HemfComment {
         public List<HemfPlusRecord> getRecords() {
             return Collections.unmodifiableList(records);
         }
+
+        public void draw(HemfGraphics ctx) {
+            records.forEach(ctx::draw);
+        }
     }
 
     public static class EmfCommentDataBeginGroup implements EmfCommentData {
index 111f9e5e308d66397d27a415f3531e8a9f192d0c..ae0fbd2d07eee7a77e7f6f20fb57221a9590b9c5 100644 (file)
@@ -21,6 +21,7 @@ import static org.apache.poi.hwmf.record.HwmfDraw.boundsToString;
 import static org.apache.poi.hwmf.record.HwmfDraw.normalizeBounds;
 
 import java.awt.Shape;
+import java.awt.geom.AffineTransform;
 import java.awt.geom.Arc2D;
 import java.awt.geom.Dimension2D;
 import java.awt.geom.Path2D;
@@ -34,6 +35,7 @@ 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.Internal;
 import org.apache.poi.util.LittleEndianConsts;
 import org.apache.poi.util.LittleEndianInputStream;
 
@@ -1153,4 +1155,16 @@ public class HemfDraw {
 
         ctx.draw((path) -> path.append(pi, true), fillDrawStyle);
     }
+
+
+    @Internal
+    public static String xformToString(AffineTransform xForm) {
+        return (xForm == null) ? "null" :
+            "{ scaleX: "+xForm.getScaleX()+
+            ", shearX: "+xForm.getShearX()+
+            ", transX: "+xForm.getTranslateX()+
+            ", scaleY: "+xForm.getScaleY()+
+            ", shearY: "+xForm.getShearY()+
+            ", transY: "+xForm.getTranslateY()+" }";
+    }
 }
index a380d6c3fa94066299d506f29a0c008ca691795e..6979a37b7161a6650d34b62877a5bc1156a072cf 100644 (file)
@@ -19,6 +19,7 @@ package org.apache.poi.hemf.record.emf;
 
 import static org.apache.poi.hemf.record.emf.HemfDraw.readPointL;
 import static org.apache.poi.hemf.record.emf.HemfDraw.readRectL;
+import static org.apache.poi.hemf.record.emf.HemfDraw.xformToString;
 import static org.apache.poi.hemf.record.emf.HemfRecordIterator.HEADER_SIZE;
 import static org.apache.poi.hwmf.record.HwmfDraw.boundsToString;
 import static org.apache.poi.hwmf.record.HwmfDraw.pointToString;
@@ -186,7 +187,7 @@ public class HemfFill {
         public String toString() {
             return
                 "{ bounds: "+boundsToString(bounds)+
-                ", xFormSrc: { scaleX: "+xFormSrc.getScaleX()+", shearX: "+xFormSrc.getShearX()+", transX: "+xFormSrc.getTranslateX()+", scaleY: "+xFormSrc.getScaleY()+", shearY: "+xFormSrc.getShearY()+", transY: "+xFormSrc.getTranslateY()+" }"+
+                ", xFormSrc: " + xformToString(xFormSrc) +
                 ", bkColorSrc: "+bkColorSrc+
                 ","+super.toString().substring(1);
         }
@@ -705,6 +706,10 @@ public class HemfFill {
 
         xform.setTransform(m00, m10, m01, m11, m02, m12);
 
+        if (xform.isIdentity()) {
+            xform.setToIdentity();
+        }
+
         return 6 * LittleEndian.INT_SIZE;
     }
 
index 5e04e932d67d320504627e6d9623bf282b686ba5..4ab50e4a00796561ad4c768c3ae136b93e1a40f0 100644 (file)
@@ -18,6 +18,7 @@
 package org.apache.poi.hemf.record.emf;
 
 import static org.apache.poi.hemf.record.emf.HemfDraw.readPointL;
+import static org.apache.poi.hemf.record.emf.HemfDraw.xformToString;
 import static org.apache.poi.hemf.record.emf.HemfFill.readBitmap;
 import static org.apache.poi.hemf.record.emf.HemfFill.readXForm;
 import static org.apache.poi.hemf.record.emf.HemfRecordIterator.HEADER_SIZE;
@@ -621,14 +622,7 @@ public class HemfMisc {
 
         @Override
         public String toString() {
-            return
-                "{ xForm: " +
-                "{ scaleX: "+xForm.getScaleX()+
-                ", shearX: "+xForm.getShearX()+
-                ", transX: "+xForm.getTranslateX()+
-                ", scaleY: "+xForm.getScaleY()+
-                ", shearY: "+xForm.getShearY()+
-                ", transY: "+xForm.getTranslateY()+" } }";
+            return "{ xForm: " + xformToString(xForm)+" }";
         }
     }
 
@@ -695,7 +689,7 @@ public class HemfMisc {
                             wsTrans.translate(-emfBounds.getHeight(), emfBounds.getHeight());
                         }
                     } else {
-                        wsTrans = adaptXForm(ctx.getTransform());
+                        wsTrans = adaptXForm(xForm, ctx.getTransform());
                     }
 
                     tx = ctx.getTransform();
@@ -703,7 +697,7 @@ public class HemfMisc {
                     break;
                 case MWT_RIGHTMULTIPLY:
                     tx = ctx.getTransform();
-                    tx.preConcatenate(adaptXForm(tx));
+                    tx.preConcatenate(adaptXForm(xForm, tx));
                     break;
                 case MWT_IDENTITY:
                     ctx.updateWindowMapMode();
@@ -713,40 +707,16 @@ public class HemfMisc {
                 case MWT_SET:
                     ctx.updateWindowMapMode();
                     tx = ctx.getTransform();
-                    tx.concatenate(adaptXForm(tx));
+                    tx.concatenate(adaptXForm(xForm, tx));
                     break;
             }
             ctx.setTransform(tx);
         }
 
-        /**
-         * adapt xform depending on the base transformation (... experimental ...)
-         */
-        private AffineTransform adaptXForm(AffineTransform other) {
-            // normalize signed zero
-            Function<Double,Double> nn = (d) -> (d == 0. ? 0. : d);
-            double yDiff = Math.signum(nn.apply(xForm.getTranslateY())) == Math.signum(nn.apply(other.getTranslateY())) ? 1. : -1.;
-            double xDiff = Math.signum(nn.apply(xForm.getTranslateX())) == Math.signum(nn.apply(other.getTranslateX())) ? 1. : -1.;
-                return new AffineTransform(
-                    xForm.getScaleX() == 0 ? 1. : xForm.getScaleX(),
-                    yDiff * xForm.getShearY(),
-                    xDiff * xForm.getShearX(),
-                    xForm.getScaleY() == 0. ? 1. : xForm.getScaleY(),
-                    xForm.getTranslateX(),
-                    xForm.getTranslateY()
-            );
-        }
-
         @Override
         public String toString() {
             return
-                "{ xForm: " +
-                "{ scaleX: "+xForm.getScaleX()+
-                ", shearX: "+xForm.getShearX()+
-                ", transX: "+xForm.getTranslateX()+
-                ", scaleY: "+xForm.getScaleY()+
-                ", shearY: "+xForm.getShearY()+
-                ", transY: "+xForm.getTranslateY()+" }"+
+                "{ xForm: " + xformToString(xForm) +
                 ", modifyWorldTransformMode: '"+modifyWorldTransformMode+"' }";
         }
     }
@@ -825,4 +795,23 @@ public class HemfMisc {
                 "}";
         }
     }
+
+
+    /**
+     * adapt xform depending on the base transformation (... experimental ...)
+     */
+    public static AffineTransform adaptXForm(AffineTransform xForm, AffineTransform other) {
+        // normalize signed zero
+        Function<Double,Double> nn = (d) -> (d == 0. ? 0. : d);
+        double yDiff = Math.signum(nn.apply(xForm.getTranslateY())) == Math.signum(nn.apply(other.getTranslateY())) ? 1. : -1.;
+        double xDiff = Math.signum(nn.apply(xForm.getTranslateX())) == Math.signum(nn.apply(other.getTranslateX())) ? 1. : -1.;
+        return new AffineTransform(
+                xForm.getScaleX() == 0 ? 1. : xForm.getScaleX(),
+                yDiff * xForm.getShearY(),
+                xDiff * xForm.getShearX(),
+                xForm.getScaleY() == 0. ? 1. : xForm.getScaleY(),
+                xForm.getTranslateX(),
+                xForm.getTranslateY()
+        );
+    }
 }
index 65c4a3d4d5fbe9c0ccc37e2ca3806a868afd861d..36d7b90ec70423c3040e06b20eac6a728bebff6b 100644 (file)
 
 package org.apache.poi.hemf.record.emfplus;
 
+import static java.util.stream.Collectors.joining;
+import static org.apache.poi.hemf.record.emf.HemfDraw.xformToString;
 import static org.apache.poi.hemf.record.emf.HemfFill.readXForm;
 import static org.apache.poi.hemf.record.emfplus.HemfPlusDraw.readARGB;
 import static org.apache.poi.hemf.record.emfplus.HemfPlusDraw.readPointF;
 import static org.apache.poi.hemf.record.emfplus.HemfPlusDraw.readRectF;
+import static org.apache.poi.hwmf.record.HwmfDraw.boundsToString;
+import static org.apache.poi.hwmf.record.HwmfDraw.pointToString;
 
 import java.awt.Color;
 import java.awt.geom.AffineTransform;
 import java.awt.geom.Point2D;
 import java.awt.geom.Rectangle2D;
 import java.io.IOException;
+import java.util.Arrays;
+import java.util.List;
 import java.util.function.Consumer;
 import java.util.function.Supplier;
+import java.util.stream.Stream;
 
+import org.apache.poi.hemf.draw.HemfDrawProperties;
+import org.apache.poi.hemf.draw.HemfGraphics;
 import org.apache.poi.hemf.record.emfplus.HemfPlusHeader.EmfPlusGraphicsVersion;
 import org.apache.poi.hemf.record.emfplus.HemfPlusImage.EmfPlusImage;
 import org.apache.poi.hemf.record.emfplus.HemfPlusImage.EmfPlusWrapMode;
 import org.apache.poi.hemf.record.emfplus.HemfPlusObject.EmfPlusObjectData;
 import org.apache.poi.hemf.record.emfplus.HemfPlusObject.EmfPlusObjectType;
 import org.apache.poi.hemf.record.emfplus.HemfPlusPath.EmfPlusPath;
+import org.apache.poi.hwmf.record.HwmfBrushStyle;
+import org.apache.poi.hwmf.record.HwmfColorRef;
+import org.apache.poi.hwmf.record.HwmfDraw;
 import org.apache.poi.util.BitField;
 import org.apache.poi.util.BitFieldFactory;
+import org.apache.poi.util.Internal;
 import org.apache.poi.util.LittleEndianConsts;
 import org.apache.poi.util.LittleEndianInputStream;
 
@@ -68,58 +81,149 @@ public class HemfPlusBrush {
     }
 
     public enum EmfPlusHatchStyle {
+        /** Specifies equally spaced horizontal lines. */
         HORIZONTAL(0X00000000),
+        /** Specifies equally spaced vertical lines. */
         VERTICAL(0X00000001),
+        /** Specifies lines on a diagonal from upper left to lower right. */
         FORWARD_DIAGONAL(0X00000002),
+        /** Specifies lines on a diagonal from upper right to lower left. */
         BACKWARD_DIAGONAL(0X00000003),
+        /** Specifies crossing horizontal and vertical lines. */
         LARGE_GRID(0X00000004),
+        /** Specifies crossing forward diagonal and backward diagonal lines with anti-aliasing. */
         DIAGONAL_CROSS(0X00000005),
+        /** Specifies a 5-percent hatch, which is the ratio of foreground color to background color equal to 5:100. */
         PERCENT_05(0X00000006),
+        /** Specifies a 10-percent hatch, which is the ratio of foreground color to background color equal to 10:100. */
         PERCENT_10(0X00000007),
+        /** Specifies a 20-percent hatch, which is the ratio of foreground color to background color equal to 20:100. */
         PERCENT_20(0X00000008),
+        /** Specifies a 25-percent hatch, which is the ratio of foreground color to background color equal to 25:100. */
         PERCENT_25(0X00000009),
+        /** Specifies a 30-percent hatch, which is the ratio of foreground color to background color equal to 30:100. */
         PERCENT_30(0X0000000A),
+        /** Specifies a 40-percent hatch, which is the ratio of foreground color to background color equal to 40:100. */
         PERCENT_40(0X0000000B),
+        /** Specifies a 50-percent hatch, which is the ratio of foreground color to background color equal to 50:100. */
         PERCENT_50(0X0000000C),
+        /** Specifies a 60-percent hatch, which is the ratio of foreground color to background color equal to 60:100. */
         PERCENT_60(0X0000000D),
+        /** Specifies a 70-percent hatch, which is the ratio of foreground color to background color equal to 70:100. */
         PERCENT_70(0X0000000E),
+        /** Specifies a 75-percent hatch, which is the ratio of foreground color to background color equal to 75:100. */
         PERCENT_75(0X0000000F),
+        /** Specifies an 80-percent hatch, which is the ratio of foreground color to background color equal to 80:100. */
         PERCENT_80(0X00000010),
+        /** Specifies a 90-percent hatch, which is the ratio of foreground color to background color equal to 90:100. */
         PERCENT_90(0X00000011),
+        /**
+         * Specifies diagonal lines that slant to the right from top to bottom points with no anti-aliasing.
+         * They are spaced 50 percent further apart than lines in the FORWARD_DIAGONAL pattern
+         */
         LIGHT_DOWNWARD_DIAGONAL(0X00000012),
+        /**
+         * Specifies diagonal lines that slant to the left from top to bottom points with no anti-aliasing.
+         * They are spaced 50 percent further apart than lines in the BACKWARD_DIAGONAL pattern.
+         */
         LIGHT_UPWARD_DIAGONAL(0X00000013),
+        /**
+         * Specifies diagonal lines that slant to the right from top to bottom points with no anti-aliasing.
+         * They are spaced 50 percent closer and are twice the width of lines in the FORWARD_DIAGONAL pattern.
+         */
         DARK_DOWNWARD_DIAGONAL(0X00000014),
+        /**
+         * Specifies diagonal lines that slant to the left from top to bottom points with no anti-aliasing.
+         * They are spaced 50 percent closer and are twice the width of lines in the BACKWARD_DIAGONAL pattern.
+         */
         DARK_UPWARD_DIAGONAL(0X00000015),
+        /**
+         * Specifies diagonal lines that slant to the right from top to bottom points with no anti-aliasing.
+         * They have the same spacing between lines in WIDE_DOWNWARD_DIAGONAL pattern and FORWARD_DIAGONAL pattern,
+         * but WIDE_DOWNWARD_DIAGONAL has the triple line width of FORWARD_DIAGONAL.
+         */
         WIDE_DOWNWARD_DIAGONAL(0X00000016),
+        /**
+         * Specifies diagonal lines that slant to the left from top to bottom points with no anti-aliasing.
+         * They have the same spacing between lines in WIDE_UPWARD_DIAGONAL pattern and BACKWARD_DIAGONAL pattern,
+         * but WIDE_UPWARD_DIAGONAL has the triple line width of WIDE_UPWARD_DIAGONAL.
+         */
         WIDE_UPWARD_DIAGONAL(0X00000017),
+        /** Specifies vertical lines that are spaced 50 percent closer together than lines in the VERTICAL pattern. */
         LIGHT_VERTICAL(0X00000018),
+        /** Specifies horizontal lines that are spaced 50 percent closer than lines in the HORIZONTAL pattern. */
         LIGHT_HORIZONTAL(0X00000019),
+        /**
+         * Specifies vertical lines that are spaced 75 percent closer than lines in the VERTICAL pattern;
+         * or 25 percent closer than lines in the LIGHT_VERTICAL pattern.
+         */
         NARROW_VERTICAL(0X0000001A),
+        /**
+         * Specifies horizontal lines that are spaced 75 percent closer than lines in the HORIZONTAL pattern;
+         * or 25 percent closer than lines in the LIGHT_HORIZONTAL pattern.
+         */
         NARROW_HORIZONTAL(0X0000001B),
+        /** Specifies lines that are spaced 50 percent closer than lines in the VERTICAL pattern. */
         DARK_VERTICAL(0X0000001C),
+        /** Specifies lines that are spaced 50 percent closer than lines in the HORIZONTAL pattern. */
         DARK_HORIZONTAL(0X0000001D),
+        /** Specifies dashed diagonal lines that slant to the right from top to bottom points. */
         DASHED_DOWNWARD_DIAGONAL(0X0000001E),
+        /** Specifies dashed diagonal lines that slant to the left from top to bottom points. */
         DASHED_UPWARD_DIAGONAL(0X0000001F),
+        /** Specifies dashed horizontal lines. */
         DASHED_HORIZONTAL(0X00000020),
+        /** Specifies dashed vertical lines. */
         DASHED_VERTICAL(0X00000021),
+        /** Specifies a pattern of lines that has the appearance of confetti. */
         SMALL_CONFETTI(0X00000022),
+        /**
+         * Specifies a pattern of lines that has the appearance of confetti, and is composed of larger pieces
+         * than the SMALL_CONFETTI pattern.
+         */
         LARGE_CONFETTI(0X00000023),
+        /** Specifies horizontal lines that are composed of zigzags. */
         ZIGZAG(0X00000024),
+        /** Specifies horizontal lines that are composed of tildes. */
         WAVE(0X00000025),
+        /**
+         * Specifies a pattern of lines that has the appearance of layered bricks that slant to the left from
+         * top to bottom points.
+         */
         DIAGONAL_BRICK(0X00000026),
+        /** Specifies a pattern of lines that has the appearance of horizontally layered bricks. */
         HORIZONTAL_BRICK(0X00000027),
+        /** Specifies a pattern of lines that has the appearance of a woven material. */
         WEAVE(0X00000028),
+        /** Specifies a pattern of lines that has the appearance of a plaid material. */
         PLAID(0X00000029),
+        /** Specifies a pattern of lines that has the appearance of divots. */
         DIVOT(0X0000002A),
+        /** Specifies crossing horizontal and vertical lines, each of which is composed of dots. */
         DOTTED_GRID(0X0000002B),
+        /** Specifies crossing forward and backward diagonal lines, each of which is composed of dots. */
         DOTTED_DIAMOND(0X0000002C),
+        /**
+         * Specifies a pattern of lines that has the appearance of diagonally layered
+         * shingles that slant to the right from top to bottom points.
+         */
         SHINGLE(0X0000002D),
+        /** Specifies a pattern of lines that has the appearance of a trellis. */
         TRELLIS(0X0000002E),
+        /** Specifies a pattern of lines that has the appearance of spheres laid adjacent to each other. */
         SPHERE(0X0000002F),
+        /** Specifies crossing horizontal and vertical lines that are spaced 50 percent closer together than LARGE_GRID. */
         SMALL_GRID(0X00000030),
+        /** Specifies a pattern of lines that has the appearance of a checkerboard. */
         SMALL_CHECKER_BOARD(0X00000031),
+        /**
+         * Specifies a pattern of lines that has the appearance of a checkerboard, with squares that are twice the
+         * size of the squares in the SMALL_CHECKER_BOARD pattern.
+         */
         LARGE_CHECKER_BOARD(0X00000032),
+        /** Specifies crossing forward and backward diagonal lines; the lines are not anti-aliased. */
         OUTLINED_DIAMOND(0X00000033),
+        /** Specifies a pattern of lines that has the appearance of a checkerboard placed diagonally. */
         SOLID_DIAMOND(0X00000034)
         ;
 
@@ -204,17 +308,19 @@ public class HemfPlusBrush {
         BitField DO_NOT_TRANSFORM = BitFieldFactory.getInstance(0x00000100);
 
         long init(LittleEndianInputStream leis, long dataSize) throws IOException;
+
+        void applyObject(HemfGraphics ctx, List<? extends EmfPlusObjectData> continuedObjectData);
     }
 
     /** The EmfPlusBrush object specifies a graphics brush for filling regions. */
     public static class EmfPlusBrush implements EmfPlusObjectData {
-        private final EmfPlusGraphicsVersion version = new EmfPlusGraphicsVersion();
+        private final EmfPlusGraphicsVersion graphicsVersion = new EmfPlusGraphicsVersion();
         private EmfPlusBrushType brushType;
         private EmfPlusBrushData brushData;
 
         @Override
         public long init(LittleEndianInputStream leis, long dataSize, EmfPlusObjectType objectType, int flags) throws IOException {
-            long size = version.init(leis);
+            long size = graphicsVersion.init(leis);
 
             brushType = EmfPlusBrushType.valueOf(leis.readInt());
             size += LittleEndianConsts.INT_SIZE;
@@ -224,6 +330,23 @@ public class HemfPlusBrush {
 
             return size;
         }
+
+        @Override
+        public void applyObject(HemfGraphics ctx, List<? extends EmfPlusObjectData> continuedObjectData) {
+            brushData.applyObject(ctx, continuedObjectData);
+        }
+
+        @Override
+        public EmfPlusGraphicsVersion getGraphicsVersion() {
+            return graphicsVersion;
+        }
+
+        @Override
+        public String toString() {
+            return
+                "{ brushType: '"+brushType+"'" +
+                ", brushData: "+brushData+" }";
+        }
     }
 
     /** The EmfPlusSolidBrushData object specifies a solid color for a graphics brush. */
@@ -234,6 +357,17 @@ public class HemfPlusBrush {
             solidColor = readARGB(leis.readInt());
             return LittleEndianConsts.INT_SIZE;
         }
+
+        @Override
+        public void applyObject(HemfGraphics ctx, List<? extends EmfPlusObjectData> continuedObjectData) {
+            HemfDrawProperties prop = ctx.getProperties();
+            prop.setBackgroundColor(new HwmfColorRef(solidColor));
+        }
+
+        @Override
+        public String toString() {
+            return "{ solidColor: "+new HwmfColorRef(solidColor)+" }";
+        }
     }
 
 
@@ -247,6 +381,22 @@ public class HemfPlusBrush {
             backColor = readARGB(leis.readInt());
             return 3*LittleEndianConsts.INT_SIZE;
         }
+
+        @Override
+        public void applyObject(HemfGraphics ctx, List<? extends EmfPlusObjectData> continuedObjectData) {
+            HemfDrawProperties prop = ctx.getProperties();
+            prop.setBrushColor(new HwmfColorRef(foreColor));
+            prop.setBackgroundColor(new HwmfColorRef(backColor));
+            prop.setEmfPlusBrushHatch(style);
+        }
+
+        @Override
+        public String toString() {
+            return
+                "{ style: '"+style+"'" +
+                ", foreColor: "+new HwmfColorRef(foreColor) +
+                ", backColor: "+new HwmfColorRef(backColor) + " }";
+        }
     }
 
     /** The EmfPlusLinearGradientBrushData object specifies a linear gradient for a graphics brush. */
@@ -303,6 +453,32 @@ public class HemfPlusBrush {
 
             return size;
         }
+
+        @Override
+        public void applyObject(HemfGraphics ctx, List<? extends EmfPlusObjectData> continuedObjectData) {
+            HemfDrawProperties prop = ctx.getProperties();
+            // TODO: implement
+        }
+
+        @Override
+        public String toString() {
+            return
+                "{ flags: "+dataFlags+
+                ", wrapMode: '"+wrapMode+"'"+
+                ", rect: "+boundsToString(rect)+
+                ", startColor: "+new HwmfColorRef(startColor)+
+                ", endColor: "+new HwmfColorRef(endColor)+
+                ", transform: "+xformToString(transform)+
+                ", positions: "+ Arrays.toString(positions)+
+                ", blendColors: "+ colorsToString(blendColors)+
+                ", positionsV: "+ Arrays.toString(positionsV)+
+                ", blendFactorsV: "+ Arrays.toString(blendFactorsV)+
+                ", positionsH: "+ Arrays.toString(positionsH)+
+                ", blendFactorsH: "+ Arrays.toString(blendFactorsH)+
+                "}";
+        }
+
+
     }
 
     /** The EmfPlusPathGradientBrushData object specifies a path gradient for a graphics brush. */
@@ -409,6 +585,31 @@ public class HemfPlusBrush {
 
             return size;
         }
+
+        @Override
+        public void applyObject(HemfGraphics ctx, List<? extends EmfPlusObjectData> continuedObjectData) {
+
+        }
+
+        @Override
+        public String toString() {
+            return
+                "{ flags: "+dataFlags+
+                ", wrapMode: '"+wrapMode+"'"+
+                ", centerColor: "+new HwmfColorRef(centerColor)+
+                ", centerPoint: "+pointToString(centerPoint)+
+                ", surroundingColor: "+colorsToString(surroundingColor)+
+                ", boundaryPath: "+(boundaryPath == null ? "null" : boundaryPath)+
+                ", boundaryPoints: "+pointsToString(boundaryPoints)+
+                ", transform: "+xformToString(transform)+
+                ", positions: "+Arrays.toString(positions)+
+                ", blendColors: "+colorsToString(blendColors)+
+                ", blendFactorsH: "+Arrays.toString(blendFactorsH)+
+                ", focusScaleX: "+focusScaleX+
+                ", focusScaleY: "+focusScaleY+
+                "}"
+                ;
+        }
     }
 
     /** The EmfPlusTextureBrushData object specifies a texture image for a graphics brush. */
@@ -440,6 +641,24 @@ public class HemfPlusBrush {
 
             return size;
         }
+
+        @Override
+        public void applyObject(HemfGraphics ctx, List<? extends EmfPlusObjectData> continuedObjectData) {
+            image.applyObject(ctx, continuedObjectData);
+            HemfDrawProperties prop = ctx.getProperties();
+            prop.setBrushBitmap(prop.getEmfPlusImage());
+            prop.setBrushStyle(HwmfBrushStyle.BS_PATTERN);
+        }
+
+        @Override
+        public String toString() {
+            return
+                "{ flags: "+dataFlags+
+                ", wrapMode: '"+wrapMode+"'"+
+                ", transform: "+xformToString(transform)+
+                ", image: "+image+
+                "]";
+        }
     }
 
     private static int readPositions(LittleEndianInputStream leis, Consumer<double[]> pos) {
@@ -477,4 +696,18 @@ public class HemfPlusBrush {
         facs.accept(factors);
         return size + factors.length * LittleEndianConsts.INT_SIZE;
     }
+
+    @Internal
+    public static String colorsToString(Color[] colors) {
+        return (colors == null ? "null" :
+            Stream.of(colors).map(HwmfColorRef::new).map(Object::toString).
+            collect(joining(",", "{", "}")));
+    }
+
+    @Internal
+    public static String pointsToString(Point2D[] points) {
+        return (points == null ? "null" :
+            Stream.of(points).map(HwmfDraw::pointToString).
+            collect(joining(",", "{", "}")));
+    }
 }
index f35a1ea3ab5649860ea21085467246cfb9aa08d9..036b544fdc3fc23d14ad31275e1e7506911cf76a 100644 (file)
 
 package org.apache.poi.hemf.record.emfplus;
 
+import static java.util.stream.Collectors.joining;
+import static org.apache.poi.hemf.record.emf.HemfDraw.xformToString;
+import static org.apache.poi.hwmf.record.HwmfDraw.boundsToString;
+import static org.apache.poi.hwmf.record.HwmfDraw.pointToString;
+
 import java.awt.Color;
 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.awt.image.BufferedImage;
 import java.io.IOException;
 import java.math.BigDecimal;
 import java.util.ArrayList;
@@ -30,8 +38,17 @@ import java.util.function.BiFunction;
 import org.apache.commons.math3.linear.LUDecomposition;
 import org.apache.commons.math3.linear.MatrixUtils;
 import org.apache.commons.math3.linear.RealMatrix;
+import org.apache.poi.hemf.draw.HemfDrawProperties;
+import org.apache.poi.hemf.draw.HemfGraphics;
 import org.apache.poi.hemf.record.emf.HemfFill;
+import org.apache.poi.hemf.record.emfplus.HemfPlusImage.EmfPlusImage;
 import org.apache.poi.hemf.record.emfplus.HemfPlusMisc.EmfPlusObjectId;
+import org.apache.poi.hemf.record.emfplus.HemfPlusObject.EmfPlusObject;
+import org.apache.poi.hwmf.record.HwmfBrushStyle;
+import org.apache.poi.hwmf.record.HwmfColorRef;
+import org.apache.poi.hwmf.record.HwmfDraw;
+import org.apache.poi.hwmf.record.HwmfMisc.WmfSetBkMode.HwmfBkMode;
+import org.apache.poi.hwmf.record.HwmfTernaryRasterOp;
 import org.apache.poi.util.BitField;
 import org.apache.poi.util.BitFieldFactory;
 import org.apache.poi.util.IOUtils;
@@ -116,12 +133,45 @@ public class HemfPlusDraw {
         }
     }
 
+    public interface EmfPlusSolidColor {
+        /**
+         * If set, brushId specifies a color as an EmfPlusARGB object.
+         * If clear, brushId contains the index of an EmfPlusBrush object in the EMF+ Object Table.
+         */
+        BitField SOLID_COLOR = BitFieldFactory.getInstance(0x8000);
+
+        int getFlags();
+
+        int getBrushIdValue();
+
+        default boolean isSolidColor() {
+            return SOLID_COLOR.isSet(getFlags());
+        }
+
+        default int getBrushId() {
+            return (isSolidColor()) ? -1 : getBrushIdValue();
+        }
+
+        default Color getSolidColor() {
+            return (isSolidColor()) ? readARGB(getBrushIdValue()) : null;
+        }
+
+        default void applyColor(HemfGraphics ctx) {
+            HemfDrawProperties prop = ctx.getProperties();
+            if (isSolidColor()) {
+                prop.setBrushStyle(HwmfBrushStyle.BS_SOLID);
+                prop.setBrushColor(new HwmfColorRef(getSolidColor()));
+            } else {
+                ctx.applyObjectTableEntry(getBrushId());
+            }
+        }
+    }
 
 
     /**
      * The EmfPlusDrawPath record specifies drawing a graphics path
      */
-    public static class EmfPlusDrawPath implements HemfPlusRecord {
+    public static class EmfPlusDrawPath implements HemfPlusRecord, EmfPlusObjectId {
         private int flags;
         private int penId;
 
@@ -148,18 +198,31 @@ public class HemfPlusDraw {
 
             return LittleEndianConsts.INT_SIZE;
         }
+
+        @Override
+        public void draw(HemfGraphics ctx) {
+            ctx.applyObjectTableEntry(penId);
+            ctx.applyObjectTableEntry(getObjectId());
+
+            HemfDrawProperties prop = ctx.getProperties();
+            final Path2D path = prop.getPath();
+            if (path != null) {
+                ctx.draw(path);
+            }
+        }
+
+        @Override
+        public String toString() {
+            return
+                "{ flags: "+flags+
+                ", penId: "+penId+" }";
+        }
     }
 
     /**
      * The EmfPlusFillRects record specifies filling the interiors of a series of rectangles.
      */
-    public static class EmfPlusFillRects implements HemfPlusRecord, EmfPlusCompressed {
-        /**
-         * If set, brushId specifies a color as an EmfPlusARGB object.
-         * If clear, brushId contains the index of an EmfPlusBrush object in the EMF+ Object Table.
-         */
-        private static final BitField SOLID_COLOR = BitFieldFactory.getInstance(0x8000);
-
+    public static class EmfPlusFillRects implements HemfPlusRecord, EmfPlusCompressed, EmfPlusSolidColor {
         private int flags;
         private int brushId;
         private final ArrayList<Rectangle2D> rectData = new ArrayList<>();
@@ -198,6 +261,29 @@ public class HemfPlusDraw {
 
             return size;
         }
+
+        @Override
+        public void draw(HemfGraphics ctx) {
+            applyColor(ctx);
+
+            Area area = new Area();
+            rectData.stream().map(Area::new).forEach(area::add);
+            ctx.fill(area);
+        }
+
+        @Override
+        public int getBrushIdValue() {
+            return brushId;
+        }
+
+        @Override
+        public String toString() {
+            return
+                "{ flags: "+flags+
+                ", brushId: "+brushId+
+                ", rectData: "+rectData.stream().map(HwmfDraw::boundsToString).collect(joining(",", "{", "}"))+
+                "}";
+        }
     }
 
     public static class EmfPlusDrawImagePoints implements HemfPlusRecord, EmfPlusObjectId, EmfPlusCompressed, EmfPlusRelativePosition {
@@ -302,6 +388,58 @@ public class HemfPlusDraw {
 
             return size;
         }
+
+        @Override
+        public void draw(HemfGraphics ctx) {
+            HemfDrawProperties prop = ctx.getProperties();
+
+            ctx.applyObjectTableEntry(imageAttributesID);
+            ctx.applyObjectTableEntry(getObjectId());
+
+            AffineTransform txSaved = ctx.getTransform(), tx = new AffineTransform(txSaved);
+            try {
+                tx.concatenate(trans);
+                ctx.setTransform(tx);
+
+                EmfPlusObject imgObj = (EmfPlusObject)ctx.getObjectTableEntry(getObjectId());
+                EmfPlusImage img = imgObj.getObjectData();
+                Rectangle2D srcBounds = img.getBounds(imgObj.getContinuedObject());
+                BufferedImage bi = prop.getEmfPlusImage();
+
+                prop.setRasterOp(HwmfTernaryRasterOp.SRCCOPY);
+                prop.setBkMode(HwmfBkMode.TRANSPARENT);
+
+                // the buffered image might be rescaled, so we need to calculate a new src rect to take
+                // the image data from
+                AffineTransform srcTx = new AffineTransform();
+                srcTx.translate(-srcBounds.getX(), srcBounds.getY());
+                srcTx.scale(bi.getWidth()/srcBounds.getWidth(), bi.getHeight()/srcBounds.getHeight());
+                srcTx.translate(bi.getMinX(), bi.getMinY());
+
+                Rectangle2D biRect = srcTx.createTransformedShape(srcRect).getBounds2D();
+
+                // TODO: handle srcUnit
+                Rectangle2D destRect = new Rectangle2D.Double(0, 0, biRect.getWidth(), biRect.getHeight());
+                ctx.drawImage(bi, srcRect, destRect);
+            } finally {
+                ctx.setTransform(txSaved);
+            }
+        }
+
+        @Override
+        public String toString() {
+            return
+                "{ flags: "+flags+
+                ", imageAttributesID: "+imageAttributesID+
+                ", srcUnit: '"+srcUnit+"'"+
+                ", srcRect: "+boundsToString(srcRect)+
+                ", upperLeft: "+pointToString(upperLeft)+
+                ", lowerLeft: "+pointToString(lowerLeft)+
+                ", lowerRight: "+pointToString(lowerRight)+
+                ", transform: "+xformToString(trans)+
+                "}"
+                ;
+        }
     }
 
     /** The EmfPlusDrawImage record specifies drawing a scaled image. */
@@ -347,12 +485,34 @@ public class HemfPlusDraw {
 
             return size;
         }
+
+        @Override
+        public void draw(HemfGraphics ctx) {
+            ctx.applyObjectTableEntry(imageAttributesID);
+            ctx.applyObjectTableEntry(getObjectId());
+
+            HemfDrawProperties prop = ctx.getProperties();
+            prop.setRasterOp(HwmfTernaryRasterOp.SRCCOPY);
+            prop.setBkMode(HwmfBkMode.TRANSPARENT);
+
+            ctx.drawImage(prop.getEmfPlusImage(), srcRect, rectData);
+        }
+
+        @Override
+        public String toString() {
+            return
+                "{ flags: "+flags+
+                ", imageAttributesID: "+imageAttributesID+
+                ", srcUnit: '"+srcUnit+"'"+
+                ", srcRect: "+boundsToString(srcRect)+
+                ", rectData: "+boundsToString(rectData)+
+                "}"
+                ;
+        }
     }
 
     /** The EmfPlusFillRegion record specifies filling the interior of a graphics region. */
-    public static class EmfPlusFillRegion implements HemfPlusRecord {
-        private static final BitField SOLID_COLOR = BitFieldFactory.getInstance(0x8000);
-
+    public static class EmfPlusFillRegion implements HemfPlusRecord, EmfPlusSolidColor, EmfPlusObjectId {
         private int flags;
         private int brushId;
 
@@ -366,16 +526,9 @@ public class HemfPlusDraw {
             return flags;
         }
 
-        public boolean isSolidColor() {
-            return SOLID_COLOR.isSet(getFlags());
-        }
-
-        public int getBrushId() {
-            return (isSolidColor()) ? -1 : brushId;
-        }
-
-        public Color getSolidColor() {
-            return (isSolidColor()) ? readARGB(brushId) : null;
+        @Override
+        public int getBrushIdValue() {
+            return brushId;
         }
 
         @Override
@@ -390,6 +543,21 @@ public class HemfPlusDraw {
 
             return LittleEndianConsts.INT_SIZE;
         }
+
+        @Override
+        public void draw(HemfGraphics ctx) {
+            applyColor(ctx);
+            ctx.applyObjectTableEntry(getObjectId());
+            HemfDrawProperties prop = ctx.getProperties();
+            ctx.fill(prop.getPath());
+        }
+
+        @Override
+        public String toString() {
+            return
+                "{ flags: "+flags+
+                ", brushId: "+brushId+" }";
+        }
     }
 
     /** The EmfPlusFillPath record specifies filling the interior of a graphics path. */
@@ -403,13 +571,7 @@ public class HemfPlusDraw {
     }
 
     /** The EmfPlusDrawDriverString record specifies text output with character positions. */
-    public static class EmfPlusDrawDriverString implements HemfPlusRecord, EmfPlusObjectId {
-        /**
-         * If set, brushId specifies a color as an EmfPlusARGB object.
-         * If clear, brushId contains the index of an EmfPlusBrush object in the EMF+ Object Table.
-         */
-        private static final BitField SOLID_COLOR = BitFieldFactory.getInstance(0x8000);
-
+    public static class EmfPlusDrawDriverString implements HemfPlusRecord, EmfPlusObjectId, EmfPlusSolidColor {
         /**
          * If set, the positions of character glyphs SHOULD be specified in a character map lookup table.
          * If clear, the glyph positions SHOULD be obtained from an array of coordinates.
@@ -453,6 +615,11 @@ public class HemfPlusDraw {
             return flags;
         }
 
+        @Override
+        public int getBrushIdValue() {
+            return brushId;
+        }
+
         @Override
         public long init(LittleEndianInputStream leis, long dataSize, long recordId, int flags) throws IOException {
             this.flags = flags;
@@ -615,7 +782,7 @@ public class HemfPlusDraw {
     }
 
     static Color readARGB(int argb) {
-        return new Color(  (argb >>> 8) & 0xFF, (argb >>> 16) & 0xFF, (argb >>> 24) & 0xFF, argb & 0xFF);
+        return new Color((argb >>> 16) & 0xFF, (argb >>> 8) & 0xFF, argb & 0xFF, (argb >>> 24) & 0xFF);
     }
 
 }
index fc3550c43371abf80efe5bba388cb6eb641406bf..a2201320b42086557938ed815aeae121b1a91e42 100644 (file)
@@ -18,7 +18,9 @@
 package org.apache.poi.hemf.record.emfplus;
 
 import java.io.IOException;
+import java.util.List;
 
+import org.apache.poi.hemf.draw.HemfGraphics;
 import org.apache.poi.hemf.record.emfplus.HemfPlusDraw.EmfPlusUnitType;
 import org.apache.poi.hemf.record.emfplus.HemfPlusHeader.EmfPlusGraphicsVersion;
 import org.apache.poi.hemf.record.emfplus.HemfPlusObject.EmfPlusObjectData;
@@ -60,7 +62,7 @@ public class HemfPlusFont {
         private static final BitField STRIKEOUT = BitFieldFactory.getInstance(0x00000008);
 
 
-        private final EmfPlusGraphicsVersion version = new EmfPlusGraphicsVersion();
+        private final EmfPlusGraphicsVersion graphicsVersion = new EmfPlusGraphicsVersion();
         private double emSize;
         private EmfPlusUnitType sizeUnit;
         private int styleFlags;
@@ -70,7 +72,7 @@ public class HemfPlusFont {
         public long init(LittleEndianInputStream leis, long dataSize, HemfPlusObject.EmfPlusObjectType objectType, int flags) throws IOException {
             // An EmfPlusGraphicsVersion object that specifies the version of operating system graphics that was used
             // to create this object.
-            long size = version.init(leis);
+            long size = graphicsVersion.init(leis);
 
             // A 32-bit floating-point value that specifies the em size of the font in units specified by the SizeUnit field.
             emSize = leis.readFloat();
@@ -96,5 +98,15 @@ public class HemfPlusFont {
 
             return size;
         }
+
+        @Override
+        public void applyObject(HemfGraphics ctx, List<? extends EmfPlusObjectData> continuedObjectData) {
+
+        }
+
+        @Override
+        public EmfPlusGraphicsVersion getGraphicsVersion() {
+            return graphicsVersion;
+        }
     }
 }
index ee196a78d90749b5ad5c395a14975f1e9d828bec..aa93853173862c914289ea4afadbe477605a2fc9 100644 (file)
@@ -20,6 +20,8 @@ package org.apache.poi.hemf.record.emfplus;
 
 import java.io.IOException;
 
+import org.apache.poi.hemf.draw.HemfGraphics;
+import org.apache.poi.hemf.draw.HemfGraphics.EmfRenderState;
 import org.apache.poi.util.BitField;
 import org.apache.poi.util.BitFieldFactory;
 import org.apache.poi.util.Internal;
@@ -85,6 +87,19 @@ public class HemfPlusHeader implements HemfPlusRecord {
         return version;
     }
 
+    /**
+     * If set, this flag indicates that this metafile is "dual-mode", which means that it contains two sets of records,
+     * each of which completely specifies the graphics content. If clear, the graphics content is specified by EMF+
+     * records, and possibly EMF records that are preceded by an EmfPlusGetDC record. If this flag is set, EMF records
+     * alone SHOULD suffice to define the graphics content. Note that whether the "dual-mode" flag is set or not, some
+     * EMF records are always present, namely EMF control records and the EMF records that contain EMF+ records.
+     *
+     * @return {@code true} if dual-mode is enabled
+     */
+    public boolean isEmfPlusDualMode() {
+        return (emfPlusFlags & 1) == 1;
+    }
+
     public long getEmfPlusFlags() {
         return emfPlusFlags;
     }
@@ -97,6 +112,13 @@ public class HemfPlusHeader implements HemfPlusRecord {
         return logicalDpiY;
     }
 
+    @Override
+    public void draw(HemfGraphics ctx) {
+        // currently EMF is better supported than EMF+ ... so if there's a complete set of EMF records available,
+        // disable EMF+ rendering for now
+        ctx.setRenderState(isEmfPlusDualMode() ? EmfRenderState.EMF_ONLY : EmfRenderState.EMFPLUS_ONLY);
+    }
+
     @Override
     public String toString() {
         return "HemfPlusHeader{" +
index 4374b1781f3a0bfcd2d0afcc012dd636fad000c2..ffa9d9c8e9d60e90006ee4889017c3ffca7ff6ca 100644 (file)
@@ -20,18 +20,45 @@ package org.apache.poi.hemf.record.emfplus;
 import static org.apache.poi.hemf.record.emfplus.HemfPlusDraw.readARGB;
 
 import java.awt.Color;
+import java.awt.Graphics2D;
+import java.awt.RenderingHints;
+import java.awt.Transparency;
+import java.awt.color.ColorSpace;
+import java.awt.geom.Dimension2D;
+import java.awt.geom.Rectangle2D;
+import java.awt.image.BufferedImage;
+import java.awt.image.ComponentColorModel;
+import java.awt.image.DataBuffer;
+import java.awt.image.DataBufferByte;
+import java.awt.image.PixelInterleavedSampleModel;
+import java.awt.image.Raster;
+import java.awt.image.WritableRaster;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
 import java.io.IOException;
+import java.util.List;
+import java.util.function.BiConsumer;
 
+import javax.imageio.ImageIO;
+
+import org.apache.poi.hemf.draw.HemfDrawProperties;
+import org.apache.poi.hemf.draw.HemfGraphics;
 import org.apache.poi.hemf.record.emfplus.HemfPlusHeader.EmfPlusGraphicsVersion;
 import org.apache.poi.hemf.record.emfplus.HemfPlusObject.EmfPlusObjectData;
 import org.apache.poi.hemf.record.emfplus.HemfPlusObject.EmfPlusObjectType;
+import org.apache.poi.hemf.usermodel.HemfPicture;
+import org.apache.poi.hwmf.usermodel.HwmfPicture;
 import org.apache.poi.util.BitField;
 import org.apache.poi.util.BitFieldFactory;
 import org.apache.poi.util.IOUtils;
 import org.apache.poi.util.LittleEndianConsts;
 import org.apache.poi.util.LittleEndianInputStream;
+import org.apache.poi.util.Units;
 
 public class HemfPlusImage {
+    /** Maximum image dimension for converting embedded metafiles */
+    private static final int MAX_IMAGE_SIZE = 1500;
+
     /** The ImageDataType enumeration defines types of image data formats. */
     public enum EmfPlusImageDataType {
         /** The type of image is not known. */
@@ -302,7 +329,7 @@ public class HemfPlusImage {
             leis.mark(LittleEndianConsts.INT_SIZE);
             long size = graphicsVersion.init(leis);
 
-            if (graphicsVersion.getGraphicsVersion() == null || graphicsVersion.getMetafileSignature() != 0xDBC01) {
+            if (isContinuedRecord()) {
                 // CONTINUABLE is not always correctly set, so we check the version field if this record is continued
                 imageDataType = EmfPlusImageDataType.CONTINUED;
                 leis.reset();
@@ -385,8 +412,193 @@ public class HemfPlusImage {
 
             return size + fileSize;
         }
+
+        @Override
+        public EmfPlusGraphicsVersion getGraphicsVersion() {
+            return graphicsVersion;
+        }
+
+        public Rectangle2D getBounds(List<? extends EmfPlusObjectData> continuedObjectData) {
+            try {
+                switch (getImageDataType()) {
+                    case BITMAP:
+                        if (getBitmapType() == EmfPlusBitmapDataType.PIXEL) {
+                            return new Rectangle2D.Double(0, 0, bitmapWidth, bitmapHeight);
+                        } else {
+                            BufferedImage bi = ImageIO.read(new ByteArrayInputStream(getRawData(continuedObjectData)));
+                            return new Rectangle2D.Double(bi.getMinX(), bi.getMinY(), bi.getWidth(), bi.getHeight());
+                        }
+                    case METAFILE:
+                        ByteArrayInputStream bis = new ByteArrayInputStream(getRawData(continuedObjectData));
+                        switch (getMetafileType()) {
+                            case Wmf:
+                            case WmfPlaceable:
+                                HwmfPicture wmf = new HwmfPicture(bis);
+                                return wmf.getBounds();
+                            case Emf:
+                            case EmfPlusDual:
+                            case EmfPlusOnly:
+                                HemfPicture emf = new HemfPicture(bis);
+                                return emf.getBounds();
+                        }
+                        break;
+                    default:
+                        break;
+                }
+            } catch (Exception ignored) {
+            }
+            return new Rectangle2D.Double(1,1,1,1);
+        }
+
+        public byte[] getRawData(List<? extends EmfPlusObjectData> continuedObjectData) {
+            ByteArrayOutputStream bos = new ByteArrayOutputStream();
+            try {
+                bos.write(getImageData());
+                if (continuedObjectData != null) {
+                    for (EmfPlusObjectData od : continuedObjectData) {
+                        bos.write(((EmfPlusImage)od).getImageData());
+                    }
+                }
+            } catch (IOException e) {
+                throw new RuntimeException(e);
+            }
+            return bos.toByteArray();
+        }
+
+        @Override
+        public void applyObject(HemfGraphics ctx, List<? extends EmfPlusObjectData> continuedObjectData) {
+            HemfDrawProperties prop = ctx.getProperties();
+            BufferedImage bi = readImage(getRawData(continuedObjectData));
+            prop.setEmfPlusImage(bi);
+        }
+
+        /**
+         * Converts the gdi pixel data to a buffered image
+         * @param data the image data of all EmfPlusImage parts
+         * @return the BufferedImage
+         */
+        public BufferedImage readGDIImage(final byte[] data) {
+            if (getImageDataType() != EmfPlusImageDataType.BITMAP || getBitmapType() != EmfPlusBitmapDataType.PIXEL) {
+                throw new RuntimeException("image data is not a GDI image");
+            }
+
+            final int width = getBitmapWidth();
+            final int height = getBitmapHeight();
+            final int stride = getBitmapStride();
+            final EmfPlusPixelFormat pf = getPixelFormat();
+
+            int[] nBits, bOffs;
+            switch (pf) {
+                case ARGB_32BPP:
+                    nBits = new int[]{8, 8, 8, 8};
+                    bOffs = new int[]{2, 1, 0, 3};
+                    break;
+                case RGB_24BPP:
+                    nBits = new int[]{8, 8, 8};
+                    bOffs = new int[]{2, 1, 0};
+                    break;
+                default:
+                    throw new RuntimeException("not yet implemented");
+            }
+
+            ColorSpace cs = ColorSpace.getInstance(ColorSpace.CS_sRGB);
+            ComponentColorModel cm = new ComponentColorModel
+                    (cs, nBits, pf.isAlpha(), pf.isPreMultiplied(), Transparency.TRANSLUCENT, DataBuffer.TYPE_BYTE);
+            PixelInterleavedSampleModel csm =
+                    new PixelInterleavedSampleModel(cm.getTransferType(), width, height, cm.getNumComponents(), stride, bOffs);
+
+            DataBufferByte dbb = new DataBufferByte(data, data.length);
+            WritableRaster raster = (WritableRaster) Raster.createRaster(csm, dbb, null);
+
+            return new BufferedImage(cm, raster, cm.isAlphaPremultiplied(), null);
+        }
+
+        private BufferedImage readImage(final byte[] data) {
+            // TODO: instead of returning a BufferedImage, we might return a pair of raw data + image renderer
+            // instead, so metafiles aren't pixelated, but directly written to the output graphics context
+            try {
+                switch (getImageDataType()) {
+                    case BITMAP: {
+                        BufferedImage bi = (getBitmapType() == EmfPlusBitmapDataType.PIXEL)
+                                ? readGDIImage(data)
+                                : ImageIO.read(new ByteArrayInputStream(data));
+
+//                        final int w = bi.getWidth();
+//                        final int h = bi.getHeight();
+//
+//                        int[] line = new int[w];
+//
+//                        WritableRaster wr = bi.getRaster();
+//                        for (int row=0; row<h; row++) {
+//                            wr.get
+//                            for (int x=0; x<w; x++) {
+//                                // TODO: use clamp color here
+//                                if ((line[x] & 0xFFFFFF) == 0) {
+//                                    // make it transparent
+//                                    line[x] &= 0xFFFFFF;
+//                                }
+//                            }
+//                            wr.setPixels(0, row, w, 1, line);
+//                        }
+
+
+                        return bi;
+                    }
+                    case METAFILE:
+                        assert (getMetafileType() != null);
+                        switch (getMetafileType()) {
+                            case Wmf:
+                            case WmfPlaceable:
+                                HwmfPicture wmf = new HwmfPicture(new ByteArrayInputStream(data));
+                                return readImage(wmf.getSize(), wmf::draw);
+
+                            case Emf:
+                            case EmfPlusDual:
+                            case EmfPlusOnly:
+                                HemfPicture emf = new HemfPicture(new ByteArrayInputStream(data));
+                                return readImage(emf.getSize(), emf::draw);
+
+                            default:
+                                break;
+                        }
+                    default:
+                        break;
+                }
+            } catch (IOException ignored) {
+            }
+
+            // fallback to empty image
+            return new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB);
+        }
+
+        private BufferedImage readImage(final Dimension2D dim, final BiConsumer<Graphics2D,Rectangle2D> draw) {
+            int width = Units.pointsToPixel(dim.getWidth());
+            // keep aspect ratio for height
+            int height = Units.pointsToPixel(dim.getHeight());
+            double longSide = Math.max(width,height);
+            if (longSide > MAX_IMAGE_SIZE) {
+                double scale = MAX_IMAGE_SIZE / longSide;
+                width *= scale;
+                height *= scale;
+            }
+
+            BufferedImage bufImg = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
+            Graphics2D g = bufImg.createGraphics();
+            g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
+            g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
+            g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
+            g.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
+
+            draw.accept(g, new Rectangle2D.Double(0, 0, width, height));
+
+            g.dispose();
+
+            return bufImg;
+        }
     }
 
+
+
     public static class EmfPlusImageAttributes implements EmfPlusObjectData {
         private final EmfPlusGraphicsVersion graphicsVersion = new EmfPlusGraphicsVersion();
         private EmfPlusWrapMode wrapMode;
@@ -434,6 +646,10 @@ public class HemfPlusImage {
         public EmfPlusObjectClamp getObjectClamp() {
             return objectClamp;
         }
+
+        @Override
+        public void applyObject(HemfGraphics ctx, List<? extends EmfPlusObjectData> continuedObjectData) {
+        }
     }
 
 }
index 771758c81c021ff026584af23f037d1cafaa6bb2..2d006563abf79ea8a1c498bfb65b2eb882aed9b2 100644 (file)
@@ -17,6 +17,7 @@
 
 package org.apache.poi.hemf.record.emfplus;
 
+import static org.apache.poi.hemf.record.emf.HemfMisc.adaptXForm;
 import static org.apache.poi.hemf.record.emfplus.HemfPlusDraw.readRectF;
 
 import java.awt.geom.AffineTransform;
@@ -24,10 +25,10 @@ import java.awt.geom.Point2D;
 import java.awt.geom.Rectangle2D;
 import java.io.IOException;
 
+import org.apache.poi.hemf.draw.HemfGraphics;
 import org.apache.poi.hemf.record.emf.HemfFill;
 import org.apache.poi.util.BitField;
 import org.apache.poi.util.BitFieldFactory;
-import org.apache.poi.util.LittleEndian;
 import org.apache.poi.util.LittleEndianConsts;
 import org.apache.poi.util.LittleEndianInputStream;
 
@@ -134,6 +135,12 @@ public class HemfPlusMisc {
      * SHOULD be processed.
      */
     public static class EmfPlusGetDC extends EmfPlusFlagOnly {
+        @Override
+        public void draw(HemfGraphics ctx) {
+            if (ctx.getRenderState() == HemfGraphics.EmfRenderState.EMFPLUS_ONLY) {
+                ctx.setRenderState(HemfGraphics.EmfRenderState.EMF_DCONTEXT);
+            }
+        }
     }
 
     /**
@@ -174,6 +181,18 @@ public class HemfPlusMisc {
 
             return HemfFill.readXForm(leis, matrixData);
         }
+
+        public AffineTransform getMatrixData() {
+            return matrixData;
+        }
+
+        @Override
+        public void draw(HemfGraphics ctx) {
+            ctx.updateWindowMapMode();
+            AffineTransform tx = ctx.getTransform();
+            tx.concatenate(getMatrixData());
+            ctx.setTransform(tx);
+        }
     }
 
     /**
@@ -185,6 +204,15 @@ public class HemfPlusMisc {
         public HemfPlusRecordType getEmfPlusRecordType() {
             return HemfPlusRecordType.multiplyWorldTransform;
         }
+
+        @Override
+        public void draw(HemfGraphics ctx) {
+            ctx.updateWindowMapMode();
+            AffineTransform tx = ctx.getTransform();
+            tx.preConcatenate(adaptXForm(getMatrixData(), tx));
+            tx.concatenate(getMatrixData());
+            ctx.setTransform(tx);
+        }
     }
 
     /**
index 35845044da5b9c1d48d855ea88dd700bc9d420dd..4d23bab6e068074cf94167f293028b344518dabe 100644 (file)
 package org.apache.poi.hemf.record.emfplus;
 
 import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
 import java.util.function.Supplier;
 
+import org.apache.poi.hemf.draw.HemfGraphics;
 import org.apache.poi.hemf.record.emfplus.HemfPlusBrush.EmfPlusBrush;
 import org.apache.poi.hemf.record.emfplus.HemfPlusFont.EmfPlusFont;
 import org.apache.poi.hemf.record.emfplus.HemfPlusHeader.EmfPlusGraphicsVersion;
@@ -29,6 +32,8 @@ import org.apache.poi.hemf.record.emfplus.HemfPlusMisc.EmfPlusObjectId;
 import org.apache.poi.hemf.record.emfplus.HemfPlusPath.EmfPlusPath;
 import org.apache.poi.hemf.record.emfplus.HemfPlusPen.EmfPlusPen;
 import org.apache.poi.hemf.record.emfplus.HemfPlusRegion.EmfPlusRegion;
+import org.apache.poi.hwmf.draw.HwmfGraphics;
+import org.apache.poi.hwmf.record.HwmfObjectTableEntry;
 import org.apache.poi.util.BitField;
 import org.apache.poi.util.BitFieldFactory;
 import org.apache.poi.util.IOUtils;
@@ -107,7 +112,7 @@ public class HemfPlusObject {
      * The EmfPlusObject record specifies an object for use in graphics operations. The object definition
      * can span multiple records), which is indicated by the value of the Flags field.
      */
-    public static class EmfPlusObject implements HemfPlusRecord, EmfPlusObjectId {
+    public static class EmfPlusObject implements HemfPlusRecord, EmfPlusObjectId, HwmfObjectTableEntry {
 
 
         /**
@@ -126,6 +131,7 @@ public class HemfPlusObject {
         // for debugging
         private int objectId;
         private EmfPlusObjectData objectData;
+        private List<EmfPlusObjectData> continuedObjectData;
         private int totalObjectSize;
 
         @Override
@@ -174,10 +180,50 @@ public class HemfPlusObject {
 
             return size;
         }
+
+        @Override
+        public void draw(HemfGraphics ctx) {
+            HwmfObjectTableEntry entry = ctx.getObjectTableEntry(getObjectId());
+            if (objectData.isContinuedRecord()) {
+                EmfPlusObject other;
+                if (entry instanceof EmfPlusObject && objectData.getClass().isInstance((other = (EmfPlusObject)entry).getObjectData())) {
+                    other.linkContinuedObject(objectData);
+                    return;
+                } else {
+                    throw new RuntimeException("can't find previous record for continued record");
+                }
+            }
+            ctx.addObjectTableEntry(this, getObjectId());
+        }
+
+        @Override
+        public void applyObject(HwmfGraphics ctx) {
+            objectData.applyObject((HemfGraphics)ctx, continuedObjectData);
+        }
+
+        void linkContinuedObject(EmfPlusObjectData continueObject) {
+            if (continuedObjectData == null) {
+                continuedObjectData = new ArrayList<>();
+            }
+            continuedObjectData.add(continueObject);
+        }
+
+        List<EmfPlusObjectData> getContinuedObject() {
+            return continuedObjectData;
+        }
     }
 
     public interface EmfPlusObjectData {
         long init(LittleEndianInputStream leis, long dataSize, EmfPlusObjectType objectType, int flags) throws IOException;
+
+        void applyObject(HemfGraphics ctx, List<? extends EmfPlusObjectData> continuedObjectData);
+
+        EmfPlusGraphicsVersion getGraphicsVersion();
+
+        default boolean isContinuedRecord() {
+            EmfPlusGraphicsVersion gv = getGraphicsVersion();
+            return (gv.getGraphicsVersion() == null || gv.getMetafileSignature() != 0xDBC01);
+        }
     }
 
     public static class EmfPlusUnknownData implements EmfPlusObjectData {
@@ -195,5 +241,15 @@ public class HemfPlusObject {
 
             return dataSize;
         }
+
+        @Override
+        public void applyObject(HemfGraphics ctx, List<? extends EmfPlusObjectData> continuedObjectData) {
+
+        }
+
+        @Override
+        public EmfPlusGraphicsVersion getGraphicsVersion() {
+            return graphicsVersion;
+        }
     }
 }
index 86d2e48a5953fc25a580f9ba2d046c9c65bb6508..c8405fece68c879ad0d4193a45e354042b6fdc21 100644 (file)
 
 package org.apache.poi.hemf.record.emfplus;
 
+import java.awt.geom.Path2D;
 import java.awt.geom.Point2D;
 import java.io.IOException;
 import java.util.Arrays;
+import java.util.List;
 import java.util.function.BiFunction;
 
+import org.apache.poi.hemf.draw.HemfDrawProperties;
+import org.apache.poi.hemf.draw.HemfGraphics;
 import org.apache.poi.hemf.record.emfplus.HemfPlusDraw.EmfPlusCompressed;
 import org.apache.poi.hemf.record.emfplus.HemfPlusDraw.EmfPlusRelativePosition;
 import org.apache.poi.hemf.record.emfplus.HemfPlusObject.EmfPlusObjectData;
@@ -69,14 +73,14 @@ public class HemfPlusPath {
 
         private static final BitField POINT_RLE_COUNT = BitFieldFactory.getInstance(0x3F);
 
-        private final HemfPlusHeader.EmfPlusGraphicsVersion version = new HemfPlusHeader.EmfPlusGraphicsVersion();
+        private final HemfPlusHeader.EmfPlusGraphicsVersion graphicsVersion = new HemfPlusHeader.EmfPlusGraphicsVersion();
         private int pointFlags;
         private Point2D[] pathPoints;
         private byte[] pointTypes;
 
         @Override
         public long init(LittleEndianInputStream leis, long dataSize, EmfPlusObjectType objectType, int flags) throws IOException {
-            long size = version.init(leis);
+            long size = graphicsVersion.init(leis);
 
             // A 32-bit unsigned integer that specifies the number of points and associated point types that
             // are defined by this object.
@@ -124,6 +128,11 @@ public class HemfPlusPath {
             return size;
         }
 
+        @Override
+        public HemfPlusHeader.EmfPlusGraphicsVersion getGraphicsVersion() {
+            return graphicsVersion;
+        }
+
         public boolean isPointDashed(int index) {
             return POINT_TYPE_DASHED.isSet(pointTypes[index]);
         }
@@ -144,6 +153,38 @@ public class HemfPlusPath {
         public int getFlags() {
             return pointFlags;
         }
+
+
+
+        @Override
+        public void applyObject(HemfGraphics ctx, List<? extends EmfPlusObjectData> continuedObjectData) {
+            HemfDrawProperties prop = ctx.getProperties();
+            Path2D path = new Path2D.Double(Path2D.WIND_NON_ZERO);
+            prop.setPath(path);
+
+            for (int idx=0; idx < pathPoints.length; idx++) {
+                Point2D p1 = pathPoints[idx];
+                switch (getPointType(idx)) {
+                    case START:
+                        path.moveTo(p1.getX(), p1.getY());
+                        break;
+                    case LINE:
+                        path.lineTo(p1.getX(), p1.getY());
+                        break;
+                    case BEZIER: {
+                        Point2D p2 = pathPoints[++idx];
+                        Point2D p3 = pathPoints[++idx];
+                        path.curveTo(p1.getX(), p1.getY(), p2.getX(), p2.getY(), p3.getX(), p3.getY());
+                        break;
+                    }
+                }
+                if (isPointClosed(idx)) {
+                    path.closePath();
+                }
+            }
+        }
+
+
     }
 
 
index 6dff00bdf7916e625995391f62ffeb33fc51acc3..c412b425a7a0c805037efb3abdf8f22ab86686f6 100644 (file)
@@ -23,12 +23,17 @@ import static org.apache.poi.hemf.record.emfplus.HemfPlusDraw.readPointF;
 import java.awt.geom.AffineTransform;
 import java.awt.geom.Point2D;
 import java.io.IOException;
+import java.util.List;
 import java.util.function.Consumer;
 
+import org.apache.poi.hemf.draw.HemfDrawProperties;
+import org.apache.poi.hemf.draw.HemfGraphics;
+import org.apache.poi.hemf.record.emfplus.HemfPlusDraw.EmfPlusUnitType;
 import org.apache.poi.hemf.record.emfplus.HemfPlusHeader.EmfPlusGraphicsVersion;
 import org.apache.poi.hemf.record.emfplus.HemfPlusObject.EmfPlusObjectData;
 import org.apache.poi.hemf.record.emfplus.HemfPlusObject.EmfPlusObjectType;
 import org.apache.poi.hemf.record.emfplus.HemfPlusPath.EmfPlusPath;
+import org.apache.poi.hwmf.record.HwmfPenStyle;
 import org.apache.poi.util.BitField;
 import org.apache.poi.util.BitFieldFactory;
 import org.apache.poi.util.Internal;
@@ -215,6 +220,13 @@ public class HemfPlusPen {
         }
     }
 
+
+    @Internal
+    public interface EmfPlusCustomLineCap {
+        long init(LittleEndianInputStream leis) throws IOException;
+    }
+
+
     public static class EmfPlusPen implements EmfPlusObjectData {
 
 
@@ -283,16 +295,17 @@ public class HemfPlusPen {
 
         private int type;
         private int penDataFlags;
-        private HemfPlusDraw.EmfPlusUnitType unitType;
+        private EmfPlusUnitType unitType;
         private double penWidth;
-        private AffineTransform trans;
-        private EmfPlusLineCapType startCap, endCap;
-        private EmfPlusLineJoin join;
-        private Double mitterLimit;
-        private EmfPlusLineStyle style;
-        EmfPlusDashedLineCapType dashedLineCapType;
+        private final AffineTransform trans = new AffineTransform();
+        private EmfPlusLineCapType startCap = EmfPlusLineCapType.FLAT;
+        private EmfPlusLineCapType endCap = startCap;
+        private EmfPlusLineJoin join = EmfPlusLineJoin.ROUND;
+        private Double miterLimit = 1.;
+        private EmfPlusLineStyle style = EmfPlusLineStyle.SOLID;
+        private EmfPlusDashedLineCapType dashedLineCapType;
         private Double dashOffset;
-        private double[] dashedLineData;
+        private float[] dashedLineData;
         private EmfPlusPenAlignment penAlignment;
         private double[] compoundLineData;
         private EmfPlusCustomLineCap customStartCap;
@@ -310,17 +323,16 @@ public class HemfPlusPen {
             penDataFlags = leis.readInt();
             // A 32-bit unsigned integer that specifies the measuring units for the pen.
             // The value MUST be from the UnitType enumeration
-            unitType = HemfPlusDraw.EmfPlusUnitType.valueOf(leis.readInt());
+            unitType = EmfPlusUnitType.valueOf(leis.readInt());
             // A 32-bit floating-point value that specifies the width of the line drawn by the pen in the units specified
             // by the PenUnit field. If a zero width is specified, a minimum value is used, which is determined by the units.
             penWidth = leis.readFloat();
-            size += 4* LittleEndianConsts.INT_SIZE;
+            size += 4*LittleEndianConsts.INT_SIZE;
 
             if (TRANSFORM.isSet(penDataFlags)) {
                 // An optional EmfPlusTransformMatrix object that specifies a world space to device space transform for
                 // the pen. This field MUST be present if the PenDataTransform flag is set in the PenDataFlags field of
                 // the EmfPlusPenData object.
-                trans = new AffineTransform();
                 size += readXForm(leis, trans);
             }
 
@@ -354,7 +366,7 @@ public class HemfPlusPen {
                 // line walls on the inside the join to the intersection of the line walls outside the join. The miter
                 // length can be large when the angle between two lines is small. This field MUST be present if the
                 // PenDataMiterLimit flag is set in the PenDataFlags field of the EmfPlusPenData object.
-                mitterLimit = (double)leis.readFloat();
+                miterLimit = (double)leis.readFloat();
                 size += LittleEndianConsts.INT_SIZE;
             }
 
@@ -390,7 +402,7 @@ public class HemfPlusPen {
                 }
 
                 // An array of DashedLineDataSize floating-point values that specify the lengths of the dashes and spaces in a dashed line.
-                dashedLineData = new double[dashesSize];
+                dashedLineData = new float[dashesSize];
                 for (int i=0; i<dashesSize; i++) {
                     dashedLineData[i] = leis.readFloat();
                 }
@@ -435,6 +447,11 @@ public class HemfPlusPen {
             return size;
         }
 
+        @Override
+        public EmfPlusGraphicsVersion getGraphicsVersion() {
+            return graphicsVersion;
+        }
+
         private long initCustomCap(Consumer<EmfPlusCustomLineCap> setter, LittleEndianInputStream leis) throws IOException {
             EmfPlusGraphicsVersion version = new EmfPlusGraphicsVersion();
             long size = version.init(leis);
@@ -450,153 +467,209 @@ public class HemfPlusPen {
             return size;
         }
 
-        @Internal
-        public interface EmfPlusCustomLineCap {
-            long init(LittleEndianInputStream leis) throws IOException;
+        @Override
+        public void applyObject(HemfGraphics ctx, List<? extends EmfPlusObjectData> continuedObjectData) {
+            final HemfDrawProperties prop = ctx.getProperties();
+            // TOOD:
+            // - set width according unit type
+            // - provide logic for different start and end cap
+            // - provide standard caps like diamondd
+            // - support custom caps
+
+            // workaround for too wide pens ... just arbitrary reduce high values ...
+            prop.setPenWidth(penWidth > 20 ? 1 : penWidth);
+            prop.setPenStyle(new HwmfPenStyle(){
+                @Override
+                public HwmfLineCap getLineCap() {
+                    // ignore endCap for now
+                    switch(startCap) {
+                        default:
+                        case FLAT:
+                            return HwmfLineCap.FLAT;
+                        case ROUND:
+                            return HwmfLineCap.ROUND;
+                        case SQUARE:
+                            return HwmfLineCap.SQUARE;
+                    }
+                }
+
+                @Override
+                public HwmfLineJoin getLineJoin() {
+                    switch (join) {
+                        default:
+                        case BEVEL:
+                            return HwmfLineJoin.BEVEL;
+                        case ROUND:
+                            return HwmfLineJoin.ROUND;
+                        case MITER_CLIPPED:
+                        case MITER:
+                            return HwmfLineJoin.MITER;
+                    }
+                }
+
+                @Override
+                public HwmfLineDash getLineDash() {
+                    return dashedLineData == null ? HwmfLineDash.SOLID : HwmfLineDash.USERSTYLE;
+                }
+
+                @Override
+                public float[] getLineDashes() {
+                    return dashedLineData;
+                }
+
+                @Override
+                public boolean isAlternateDash() {
+                    return (getLineDash() != HwmfLineDash.SOLID && dashOffset != null && dashOffset == 0);
+                }
+
+                @Override
+                public boolean isGeometric() {
+                    return (unitType == EmfPlusUnitType.World || unitType == EmfPlusUnitType.Display);
+                }
+            });
         }
+    }
 
-        public static class EmfPlusPathArrowCap implements EmfPlusCustomLineCap {
-            /**
-             * If set, an EmfPlusFillPath object MUST be specified in the OptionalData field of the
-             * EmfPlusCustomLineCapData object for filling the custom line cap.
-             */
-            private static final BitField FILL_PATH = BitFieldFactory.getInstance(0x00000001);
-            /**
-             * If set, an EmfPlusLinePath object MUST be specified in the OptionalData field of the
-             * EmfPlusCustomLineCapData object for outlining the custom line cap.
-             */
-            private static final BitField LINE_PATH = BitFieldFactory.getInstance(0x00000002);
-
-
-            private int dataFlags;
-            private EmfPlusLineCapType baseCap;
-            private double baseInset;
-            private EmfPlusLineCapType startCap;
-            private EmfPlusLineCapType endCap;
-            private EmfPlusLineJoin join;
-            private double mitterLimit;
-            private double widthScale;
-            private final Point2D fillHotSpot = new Point2D.Double();
-            private final Point2D lineHotSpot = new Point2D.Double();
-            private EmfPlusPath fillPath;
-            private EmfPlusPath outlinePath;
-
-            @Override
-            public long init(LittleEndianInputStream leis) throws IOException {
-                // A 32-bit unsigned integer that specifies the data in the OptionalData field.
-                // This value MUST be composed of CustomLineCapData flags
-                dataFlags = leis.readInt();
-
-                // A 32-bit unsigned integer that specifies the value from the LineCap enumeration on which
-                // the custom line cap is based.
-                baseCap = EmfPlusLineCapType.valueOf(leis.readInt());
-
-                // A 32-bit floating-point value that specifies the distance between the
-                // beginning of the line cap and the end of the line.
-                baseInset = leis.readFloat();
-
-                // A 32-bit unsigned integer that specifies the value in the LineCap enumeration that indicates the line
-                // cap used at the start/end of the line to be drawn.
-                startCap = EmfPlusLineCapType.valueOf(leis.readInt());
-                endCap = EmfPlusLineCapType.valueOf(leis.readInt());
+    public static class EmfPlusPathArrowCap implements EmfPlusCustomLineCap {
+        /**
+         * If set, an EmfPlusFillPath object MUST be specified in the OptionalData field of the
+         * EmfPlusCustomLineCapData object for filling the custom line cap.
+         */
+        private static final BitField FILL_PATH = BitFieldFactory.getInstance(0x00000001);
+        /**
+         * If set, an EmfPlusLinePath object MUST be specified in the OptionalData field of the
+         * EmfPlusCustomLineCapData object for outlining the custom line cap.
+         */
+        private static final BitField LINE_PATH = BitFieldFactory.getInstance(0x00000002);
 
-                // A 32-bit unsigned integer that specifies the value in the LineJoin enumeration, which specifies how
-                // to join two lines that are drawn by the same pen and whose ends meet. At the intersection of the two
-                // line ends, a line join makes the connection look more continuous.
-                join = EmfPlusLineJoin.valueOf(leis.readInt());
 
-                // A 32-bit floating-point value that contains the limit of the thickness of the join on a mitered corner
-                // by setting the maximum allowed ratio of miter length to line width.
-                mitterLimit = leis.readFloat();
+        private int dataFlags;
+        private EmfPlusLineCapType baseCap;
+        private double baseInset;
+        private EmfPlusLineCapType startCap;
+        private EmfPlusLineCapType endCap;
+        private EmfPlusLineJoin join;
+        private double mitterLimit;
+        private double widthScale;
+        private final Point2D fillHotSpot = new Point2D.Double();
+        private final Point2D lineHotSpot = new Point2D.Double();
+        private EmfPlusPath fillPath;
+        private EmfPlusPath outlinePath;
 
-                // A 32-bit floating-point value that specifies the amount by which to scale the custom line cap with
-                // respect to the width of the EmfPlusPen object that is used to draw the lines.
-                widthScale = leis.readFloat();
+        @Override
+        public long init(LittleEndianInputStream leis) throws IOException {
+            // A 32-bit unsigned integer that specifies the data in the OptionalData field.
+            // This value MUST be composed of CustomLineCapData flags
+            dataFlags = leis.readInt();
 
-                int size = 8* LittleEndianConsts.INT_SIZE;
+            // A 32-bit unsigned integer that specifies the value from the LineCap enumeration on which
+            // the custom line cap is based.
+            baseCap = EmfPlusLineCapType.valueOf(leis.readInt());
 
-                // An EmfPlusPointF object that is not currently used. It MUST be set to {0.0, 0.0}.
-                size += readPointF(leis, fillHotSpot);
-                size += readPointF(leis, lineHotSpot);
+            // A 32-bit floating-point value that specifies the distance between the
+            // beginning of the line cap and the end of the line.
+            baseInset = leis.readFloat();
 
-                if (FILL_PATH.isSet(dataFlags)) {
-                    fillPath = new EmfPlusPath();
-                    size += fillPath.init(leis, -1, null, -1);
-                }
+            // A 32-bit unsigned integer that specifies the value in the LineCap enumeration that indicates the line
+            // cap used at the start/end of the line to be drawn.
+            startCap = EmfPlusLineCapType.valueOf(leis.readInt());
+            endCap = EmfPlusLineCapType.valueOf(leis.readInt());
 
-                if (LINE_PATH.isSet(dataFlags)) {
-                    outlinePath = new EmfPlusPath();
-                    size += outlinePath.init(leis, -1, null, -1);
-                }
+            // A 32-bit unsigned integer that specifies the value in the LineJoin enumeration, which specifies how
+            // to join two lines that are drawn by the same pen and whose ends meet. At the intersection of the two
+            // line ends, a line join makes the connection look more continuous.
+            join = EmfPlusLineJoin.valueOf(leis.readInt());
+
+            // A 32-bit floating-point value that contains the limit of the thickness of the join on a mitered corner
+            // by setting the maximum allowed ratio of miter length to line width.
+            mitterLimit = leis.readFloat();
+
+            // A 32-bit floating-point value that specifies the amount by which to scale the custom line cap with
+            // respect to the width of the EmfPlusPen object that is used to draw the lines.
+            widthScale = leis.readFloat();
 
-                return size;
+            int size = 8* LittleEndianConsts.INT_SIZE;
+
+            // An EmfPlusPointF object that is not currently used. It MUST be set to {0.0, 0.0}.
+            size += readPointF(leis, fillHotSpot);
+            size += readPointF(leis, lineHotSpot);
+
+            if (FILL_PATH.isSet(dataFlags)) {
+                fillPath = new EmfPlusPath();
+                size += fillPath.init(leis, -1, null, -1);
+            }
+
+            if (LINE_PATH.isSet(dataFlags)) {
+                outlinePath = new EmfPlusPath();
+                size += outlinePath.init(leis, -1, null, -1);
             }
+
+            return size;
         }
+    }
 
-        public static class EmfPlusAdjustableArrowCap implements EmfPlusCustomLineCap {
-            private double width;
-            private double height;
-            private double middleInset;
-            private boolean isFilled;
-            private EmfPlusLineCapType startCap;
-            private EmfPlusLineCapType endCap;
-            private EmfPlusLineJoin join;
-            private double mitterLimit;
-            private double widthScale;
-            private final Point2D fillHotSpot = new Point2D.Double();
-            private final Point2D lineHotSpot = new Point2D.Double();
-
-            @Override
-            public long init(LittleEndianInputStream leis) throws IOException {
-                // A 32-bit floating-point value that specifies the width of the arrow cap.
-                // The width of the arrow cap is scaled by the width of the EmfPlusPen object that is used to draw the
-                // line being capped. For example, when drawing a capped line with a pen that has a width of 5 pixels,
-                // and the adjustable arrow cap object has a width of 3, the actual arrow cap is drawn 15 pixels wide.
-                width = leis.readFloat();
-
-                // A 32-bit floating-point value that specifies the height of the arrow cap.
-                // The height of the arrow cap is scaled by the width of the EmfPlusPen object that is used to draw the
-                // line being capped. For example, when drawing a capped line with a pen that has a width of 5 pixels,
-                // and the adjustable arrow cap object has a height of 3, the actual arrow cap is drawn 15 pixels high.
-                height = leis.readFloat();
-
-                // A 32-bit floating-point value that specifies the number of pixels between the outline of the arrow
-                // cap and the fill of the arrow cap.
-                middleInset = leis.readFloat();
-
-                // A 32-bit Boolean value that specifies whether the arrow cap is filled.
-                // If the arrow cap is not filled, only the outline is drawn.
-                isFilled = (leis.readInt() != 0);
-
-                // A 32-bit unsigned integer that specifies the value in the LineCap enumeration that indicates
-                // the line cap to be used at the start/end of the line to be drawn.
-                startCap = EmfPlusLineCapType.valueOf(leis.readInt());
-                endCap = EmfPlusLineCapType.valueOf(leis.readInt());
+    public static class EmfPlusAdjustableArrowCap implements EmfPlusCustomLineCap {
+        private double width;
+        private double height;
+        private double middleInset;
+        private boolean isFilled;
+        private EmfPlusLineCapType startCap;
+        private EmfPlusLineCapType endCap;
+        private EmfPlusLineJoin join;
+        private double mitterLimit;
+        private double widthScale;
+        private final Point2D fillHotSpot = new Point2D.Double();
+        private final Point2D lineHotSpot = new Point2D.Double();
 
-                // 32-bit unsigned integer that specifies the value in the LineJoin enumeration that specifies how to
-                // join two lines that are drawn by the same pen and whose ends meet. At the intersection of the two
-                // line ends, a line join makes the connection look more continuous.
-                join = EmfPlusLineJoin.valueOf(leis.readInt());
+        @Override
+        public long init(LittleEndianInputStream leis) throws IOException {
+            // A 32-bit floating-point value that specifies the width of the arrow cap.
+            // The width of the arrow cap is scaled by the width of the EmfPlusPen object that is used to draw the
+            // line being capped. For example, when drawing a capped line with a pen that has a width of 5 pixels,
+            // and the adjustable arrow cap object has a width of 3, the actual arrow cap is drawn 15 pixels wide.
+            width = leis.readFloat();
 
-                // A 32-bit floating-point value that specifies the limit of the thickness of the join on a mitered
-                // corner by setting the maximum allowed ratio of miter length to line width.
-                mitterLimit = leis.readFloat();
+            // A 32-bit floating-point value that specifies the height of the arrow cap.
+            // The height of the arrow cap is scaled by the width of the EmfPlusPen object that is used to draw the
+            // line being capped. For example, when drawing a capped line with a pen that has a width of 5 pixels,
+            // and the adjustable arrow cap object has a height of 3, the actual arrow cap is drawn 15 pixels high.
+            height = leis.readFloat();
 
-                // A 32-bit floating-point value that specifies the amount by which to scale an EmfPlusCustomLineCap
-                // object with respect to the width of the graphics pen that is used to draw the lines.
-                widthScale = leis.readFloat();
+            // A 32-bit floating-point value that specifies the number of pixels between the outline of the arrow
+            // cap and the fill of the arrow cap.
+            middleInset = leis.readFloat();
 
-                int size = 9 * LittleEndianConsts.INT_SIZE;
+            // A 32-bit Boolean value that specifies whether the arrow cap is filled.
+            // If the arrow cap is not filled, only the outline is drawn.
+            isFilled = (leis.readInt() != 0);
 
-                // An EmfPlusPointF object that is not currently used. It MUST be set to {0.0, 0.0}.
-                size += readPointF(leis, fillHotSpot);
+            // A 32-bit unsigned integer that specifies the value in the LineCap enumeration that indicates
+            // the line cap to be used at the start/end of the line to be drawn.
+            startCap = EmfPlusLineCapType.valueOf(leis.readInt());
+            endCap = EmfPlusLineCapType.valueOf(leis.readInt());
 
-                // An EmfPlusPointF object that is not currently used. It MUST be set to {0.0, 0.0}.
-                size += readPointF(leis, lineHotSpot);
+            // 32-bit unsigned integer that specifies the value in the LineJoin enumeration that specifies how to
+            // join two lines that are drawn by the same pen and whose ends meet. At the intersection of the two
+            // line ends, a line join makes the connection look more continuous.
+            join = EmfPlusLineJoin.valueOf(leis.readInt());
 
-                return size;
-            }
-        }
+            // A 32-bit floating-point value that specifies the limit of the thickness of the join on a mitered
+            // corner by setting the maximum allowed ratio of miter length to line width.
+            mitterLimit = leis.readFloat();
 
+            // A 32-bit floating-point value that specifies the amount by which to scale an EmfPlusCustomLineCap
+            // object with respect to the width of the graphics pen that is used to draw the lines.
+            widthScale = leis.readFloat();
+
+            int size = 9 * LittleEndianConsts.INT_SIZE;
+
+            // An EmfPlusPointF object that is not currently used. It MUST be set to {0.0, 0.0}.
+            size += readPointF(leis, fillHotSpot);
+
+            // An EmfPlusPointF object that is not currently used. It MUST be set to {0.0, 0.0}.
+            size += readPointF(leis, lineHotSpot);
+
+            return size;
+        }
     }
 }
index 808c166360cf9d8cddde8886aba7e9dad9dd52cf..c074b66704358b25acc26dcf3445f07a7ce5a2ae 100644 (file)
@@ -20,7 +20,7 @@ package org.apache.poi.hemf.record.emfplus;
 
 import java.io.IOException;
 
-import org.apache.poi.hemf.record.emf.HemfRecordType;
+import org.apache.poi.hemf.draw.HemfGraphics;
 import org.apache.poi.util.Internal;
 import org.apache.poi.util.LittleEndianInputStream;
 
@@ -45,4 +45,13 @@ public interface HemfPlusRecord {
      */
     long init(LittleEndianInputStream leis, long dataSize, long recordId, int flags) throws IOException;
 
+
+
+    /**
+     * Draws the record, the default redirects to the parent WMF record drawing
+     * @param ctx the drawing context
+     */
+    default void draw(HemfGraphics ctx) {
+    }
+
 }
index d3f3002f9936ff5c46666c20bb5686de3b6945b3..7caeff1f3b3ba16567ac4b4fe2217f6efd23c207 100644 (file)
@@ -21,9 +21,11 @@ import static org.apache.poi.hemf.record.emfplus.HemfPlusDraw.readRectF;
 
 import java.awt.geom.Rectangle2D;
 import java.io.IOException;
+import java.util.List;
 import java.util.function.Consumer;
 import java.util.function.Supplier;
 
+import org.apache.poi.hemf.draw.HemfGraphics;
 import org.apache.poi.hemf.record.emfplus.HemfPlusHeader.EmfPlusGraphicsVersion;
 import org.apache.poi.hemf.record.emfplus.HemfPlusObject.EmfPlusObjectData;
 import org.apache.poi.hemf.record.emfplus.HemfPlusObject.EmfPlusObjectType;
@@ -92,12 +94,12 @@ public class HemfPlusRegion {
 
     public static class EmfPlusRegion implements EmfPlusObjectData {
 
-        private final EmfPlusGraphicsVersion version = new EmfPlusGraphicsVersion();
+        private final EmfPlusGraphicsVersion graphicsVersion = new EmfPlusGraphicsVersion();
         private EmfPlusRegionNodeData regionNode;
 
         @Override
         public long init(LittleEndianInputStream leis, long dataSize, EmfPlusObjectType objectType, int flags) throws IOException {
-            long size = version.init(leis);
+            long size = graphicsVersion.init(leis);
 
             // A 32-bit unsigned integer that specifies the number of child nodes in the RegionNode field.
             int nodeCount = leis.readInt();
@@ -111,7 +113,15 @@ public class HemfPlusRegion {
             return size;
         }
 
+        @Override
+        public void applyObject(HemfGraphics ctx, List<? extends EmfPlusObjectData> continuedObjectData) {
+
+        }
 
+        @Override
+        public EmfPlusGraphicsVersion getGraphicsVersion() {
+            return graphicsVersion;
+        }
     }
 
 
index 751f102b9e5298db6a8673dced9eb9bb659ac1fd..3b3b7fa393a928d7055570c1c0dd1f87105c5529 100644 (file)
@@ -267,36 +267,7 @@ public class HemfEmbeddedIterator implements Iterator<HwmfEmbedded> {
      * Compress GDIs internal format to something useful
      */
     private void compressGDIBitmap(EmfPlusImage img, HwmfEmbedded emb, HwmfEmbeddedType et) {
-        final int width = img.getBitmapWidth();
-        final int height = img.getBitmapHeight();
-        final int stride = img.getBitmapStride();
-        final EmfPlusPixelFormat pf = img.getPixelFormat();
-
-        int[] nBits, bOffs;
-        switch (pf) {
-            case ARGB_32BPP:
-                nBits = new int[]{8, 8, 8, 8};
-                bOffs = new int[]{2, 1, 0, 3};
-                break;
-            case RGB_24BPP:
-                nBits = new int[]{8, 8, 8};
-                bOffs = new int[]{2, 1, 0};
-                break;
-            default:
-                throw new RuntimeException("not yet implemented");
-        }
-
-        ColorSpace cs = ColorSpace.getInstance(ColorSpace.CS_sRGB);
-        ComponentColorModel cm = new ComponentColorModel
-                (cs, nBits, pf.isAlpha(), pf.isPreMultiplied(), Transparency.TRANSLUCENT, DataBuffer.TYPE_BYTE);
-        PixelInterleavedSampleModel csm =
-                new PixelInterleavedSampleModel(cm.getTransferType(), width, height, cm.getNumColorComponents(), stride, bOffs);
-
-        byte d[] = emb.getRawData();
-        WritableRaster raster = (WritableRaster) Raster.createRaster(csm, new DataBufferByte(d, d.length), null);
-
-        BufferedImage bi = new BufferedImage(cm, raster, cm.isAlphaPremultiplied(), null);
-
+        BufferedImage bi = img.readGDIImage(emb.getRawData());
         try {
             ByteArrayOutputStream bos = new ByteArrayOutputStream();
             // use HwmfEmbeddedType literal for conversion
index 0915fe5f27f49b24002009c7b99f068d7e3d68b3..6e062cdc48aec2c2954a495ae8792540069c83a9 100644 (file)
@@ -46,7 +46,6 @@ import org.apache.poi.util.Units;
  */
 @Internal
 public class HemfPicture implements Iterable<HemfRecord> {
-
     private final LittleEndianInputStream stream;
     private final List<HemfRecord> records = new ArrayList<>();
     private boolean isParsed = false;
@@ -96,32 +95,52 @@ public class HemfPicture implements Iterable<HemfRecord> {
     }
 
     /**
-     * Return the image size in points
+     * Returns the bounding box in device-independent units. Usually this is taken from the placeable header.
      *
-     * @return the image size in points
+     * @return the bounding box
      */
-    public Dimension2D getSize() {
+    public Rectangle2D getBounds() {
         HemfHeader header = (HemfHeader)getRecords().get(0);
-        final double coeff = (double) Units.EMU_PER_CENTIMETER / Units.EMU_PER_POINT / 10.;
         Rectangle2D dim = header.getFrameRectangle();
+        double x = dim.getX(), y = dim.getY();
         double width = dim.getWidth(), height = dim.getHeight();
-        if (dim.isEmpty() || Math.rint(width*coeff) == 0 || Math.rint(height*coeff) == 0) {
+        if (dim.isEmpty() || Math.rint(width) == 0 || Math.rint(height) == 0) {
             for (HemfRecord r : getRecords()) {
                 if (r instanceof HemfWindowing.EmfSetWindowExtEx) {
-                    Dimension2D d = ((HemfWindowing.EmfSetWindowExtEx)r).getSize();
+                    HemfWindowing.EmfSetWindowExtEx extEx = (HemfWindowing.EmfSetWindowExtEx)r;
+                    Dimension2D d = extEx.getSize();
                     width = d.getWidth();
                     height = d.getHeight();
                     // keep searching - sometimes there's another record
                 }
+                if (r instanceof HemfWindowing.EmfSetWindowOrgEx) {
+                    HemfWindowing.EmfSetWindowOrgEx orgEx = (HemfWindowing.EmfSetWindowOrgEx)r;
+                    x = orgEx.getX();
+                    y = orgEx.getY();
+                }
             }
         }
 
-        if (Math.rint(width*coeff) == 0 || Math.rint(height*coeff) == 0) {
-            width = 100;
-            height = 100;
+        return new Rectangle2D.Double(x, y, width, height);
+    }
+
+    /**
+     * Return the image size in points
+     *
+     * @return the image size in points
+     */
+    public Dimension2D getSize() {
+        final Rectangle2D bounds = getBounds();
+
+        if (bounds.isEmpty()) {
+            return new Dimension2DDouble(100,100);
         }
 
-        return new Dimension2DDouble(Math.abs(width*coeff), Math.abs(height*coeff));
+        final double coeff = (double) Units.EMU_PER_CENTIMETER / Units.EMU_PER_POINT / 10.;
+        double width = Math.abs(bounds.getWidth()*coeff);
+        double height = Math.abs(bounds.getHeight()*coeff);
+
+        return new Dimension2DDouble(width, height);
     }
 
     private static double minX(Rectangle2D bounds) {
index 744c995efe6c4563355a476542515eaefb1f020c..a4c27749451ea3e1ca2303f06d9dabb096904b82 100644 (file)
 
 package org.apache.poi.hwmf.draw;
 
+import java.awt.AlphaComposite;
 import java.awt.BasicStroke;
 import java.awt.Color;
+import java.awt.Composite;
 import java.awt.Graphics2D;
 import java.awt.GraphicsConfiguration;
 import java.awt.Paint;
@@ -132,6 +134,9 @@ public class HwmfGraphics {
 
     public void fill(Shape shape) {
         HwmfDrawProperties prop = getProperties();
+
+        Composite old = graphicsCtx.getComposite();
+        graphicsCtx.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER));
         if (prop.getBrushStyle() != HwmfBrushStyle.BS_NULL) {
             if (prop.getBkMode() == HwmfBkMode.OPAQUE) {
                 graphicsCtx.setPaint(prop.getBackgroundColor().getColor());
@@ -141,20 +146,22 @@ public class HwmfGraphics {
             graphicsCtx.setPaint(getFill());
             graphicsCtx.fill(shape);
         }
+        graphicsCtx.setComposite(old);
 
         draw(shape);
     }
 
     protected BasicStroke getStroke() {
+        HwmfDrawProperties prop = getProperties();
+        HwmfPenStyle ps = prop.getPenStyle();
         // TODO: fix line width calculation
-        float width = (float)getProperties().getPenWidth();
+        float width = (float)prop.getPenWidth();
         if (width == 0) {
             width = 1;
         }
-        HwmfPenStyle ps = getProperties().getPenStyle();
         int cap = ps.getLineCap().awtFlag;
         int join = ps.getLineJoin().awtFlag;
-        float miterLimit = (float)getProperties().getPenMiterLimit();
+        float miterLimit = (float)prop.getPenMiterLimit();
         float[] dashes = ps.getLineDashes();
         boolean dashAlt = ps.isAlternateDash();
         // This value is not an integer index into the dash pattern array.
@@ -602,7 +609,14 @@ public class HwmfGraphics {
                 graphicsCtx.scale(dstBounds.getWidth()/srcBounds2.getWidth(), dstBounds.getHeight()/srcBounds2.getHeight());
                 graphicsCtx.translate(-srcBounds2.getX(), -srcBounds2.getY());
 
-                graphicsCtx.drawImage(img, 0, 0, prop.getBackgroundColor().getColor(), null);
+                if (prop.getBkMode() == HwmfBkMode.OPAQUE) {
+                    graphicsCtx.drawImage(img, 0, 0, prop.getBackgroundColor().getColor(), null);
+                } else {
+                    Composite old = graphicsCtx.getComposite();
+                    graphicsCtx.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER));
+                    graphicsCtx.drawImage(img, 0, 0, null);
+                    graphicsCtx.setComposite(old);
+                }
 
                 graphicsCtx.setTransform(at);
                 graphicsCtx.setClip(clip);
index 733d0fca062c1ac1f6cd252bf2fa29ba6b2006dc..6f8b3aa045be2305a688f1bd5161e8b7d1c5b182 100644 (file)
@@ -18,6 +18,7 @@
 package org.apache.poi.hwmf.record;
 
 import java.awt.Shape;
+import java.awt.geom.AffineTransform;
 import java.awt.geom.Arc2D;
 import java.awt.geom.Area;
 import java.awt.geom.Dimension2D;
@@ -749,17 +750,17 @@ public class HwmfDraw {
 
     @Internal
     public static String pointToString(Point2D point) {
-        return "{ x: "+point.getX()+", y: "+point.getY()+" }";
+        return (point == null) ? "null" : "{ 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()+" }";
+        return (bounds == null) ? "null" : "{ x: "+bounds.getX()+", y: "+bounds.getY()+", w: "+bounds.getWidth()+", h: "+bounds.getHeight()+" }";
     }
 
     @Internal
     public static String dimToString(Dimension2D dim) {
-        return "{ w: "+dim.getWidth()+", h: "+dim.getHeight()+" }";
+        return (dim == null) ? "null" : "{ w: "+dim.getWidth()+", h: "+dim.getHeight()+" }";
     }
 
     @Internal
@@ -772,5 +773,4 @@ public class HwmfDraw {
                 Math.abs(bounds.getHeight())
         );
     }
-
 }
index 5caa4afe129d5ead1d08b582e8b680b866dddade..b3d506532595a3bfb550c4661f889ee3aa5e482f 100644 (file)
@@ -24,22 +24,14 @@ import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 
 import java.awt.geom.Point2D;
-import java.io.BufferedWriter;
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
-import java.io.FileWriter;
-import java.io.IOException;
 import java.io.InputStream;
 import java.nio.charset.StandardCharsets;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.nio.file.StandardOpenOption;
 import java.util.ArrayList;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
-import java.util.stream.Stream;
 
 import org.apache.poi.POIDataSamples;
 import org.apache.poi.hemf.record.emf.HemfComment;
@@ -64,11 +56,10 @@ public class HemfPictureTest {
     private static final POIDataSamples ss_samples = POIDataSamples.getSpreadSheetInstance();
     private static final POIDataSamples sl_samples = POIDataSamples.getSlideShowInstance();
 
-    /*
-    @Test
+/*    @Test
     @Ignore("Only for manual tests - need to add org.tukaani:xz:1.8 for this to work")
     public void paint() throws IOException {
-        byte buf[] = new byte[50_000_000];
+        final byte buf[] = new byte[50_000_000];
 
         // good test samples to validate rendering:
         // emfs/commoncrawl2/NB/NBWN2YH5VFCLZRFDQU7PB7IDD4UKY7DN_2.emf
@@ -78,15 +69,16 @@ public class HemfPictureTest {
 
         final boolean writeLog = false;
         final boolean dumpRecords = false;
-        final boolean savePng = false;
-        final boolean dumpEmbedded = true;
+        final boolean savePng = true;
+        final boolean dumpEmbedded = false;
 
         Set<String> passed = new HashSet<>();
 
-        try (BufferedWriter sucWrite = parseEmfLog(passed, "emf-success.txt");
-             BufferedWriter parseError = parseEmfLog(passed, "emf-parse.txt");
-             BufferedWriter renderError = parseEmfLog(passed, "emf-render.txt");
-             SevenZFile sevenZFile = new SevenZFile(new File("tmp/plus_emf.7z"))) {
+        try (BufferedWriter sucWrite = parseEmfLog(passed, "emf-success.txt")
+            ;BufferedWriter parseError = parseEmfLog(passed, "emf-parse.txt")
+            ;BufferedWriter renderError = parseEmfLog(passed, "emf-render.txt")
+            ;SevenZFile sevenZFile = new SevenZFile(new File("tmp/plus_emf.7z"))
+            ) {
             for (int idx=0;;idx++) {
                 SevenZArchiveEntry entry = sevenZFile.getNextEntry();
                 if (entry == null) break;
@@ -94,10 +86,14 @@ public class HemfPictureTest {
 
                 if (entry.isDirectory() || !etName.endsWith(".emf") || passed.contains(etName)) continue;
 
-                // if (!etName.equals("emfs/commoncrawl2/2S/2SYMYPLNJURGCXJKLNZCJQGIBHVMQTRS_0.emf")) continue;
 
-                // emfs/commoncrawl2/ZJ/ZJT2BZPLQR7DKSKYLYL6GRDEUM2KIO5F_4.emf
-                // emfs/govdocs1/005/005203.ppt_3.emf
+                // KEEDHN6XES4EKK52E3AJHKCARNTQF7PO_0.emf takes ages, time is spent while drawing paths
+//                if (!etName.contains("KEEDHN6XES4EKK52E3AJHKCARNTQF7PO_0.emf")) continue;
+
+                // F7GK5XOLERFURVTQALOCX3GJ6FH45LNQ strange colors
+                // ISS3ANIX2PL4PXR7SZSJSPBZI7YQQE3U stroke wrong
+//                if (!etName.contains("ISS3ANIX2PL4PXR7SZSJSPBZI7YQQE3U")) continue;
+
 
                 System.out.println(etName);
 
@@ -129,9 +125,9 @@ public class HemfPictureTest {
                     int embIdx = 0;
                     for (HwmfEmbedded emb : emf.getEmbeddings()) {
                         final File embName = new File("build/tmp", "emb_"+etName.replaceFirst(".+/", "").replace(".emf", "_"+embIdx + emb.getEmbeddedType().extension) );
-//                        try (FileOutputStream fos = new FileOutputStream(embName)) {
-//                            fos.write(emb.getRawData());
-//                        }
+                        try (FileOutputStream fos = new FileOutputStream(embName)) {
+                            fos.write(emb.getRawData());
+                        }
                         embIdx++;
                     }
                 }
@@ -154,17 +150,19 @@ public class HemfPictureTest {
                     BufferedImage bufImg = new BufferedImage((int)width, (int)height, BufferedImage.TYPE_INT_ARGB);
                     g = bufImg.createGraphics();
                     g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
-                    g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
-                    g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
+                    g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_SPEED);
+                    g.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_SPEED);
+                    g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR);
                     g.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
 
                     g.setComposite(AlphaComposite.Clear);
                     g.fillRect(0, 0, (int)width, (int)height);
                     g.setComposite(AlphaComposite.Src);
 
+                    final File pngName = new File("build/tmp", etName.replaceFirst(".+/", "").replace(".emf", ".png"));
+
                     emf.draw(g, new Rectangle2D.Double(0, 0, width, height));
 
-                    final File pngName = new File("build/tmp", etName.replaceFirst(".+/", "").replace(".emf", ".png"));
                     if (savePng) {
                         ImageIO.write(bufImg, "PNG", pngName);
                     }
@@ -186,7 +184,7 @@ public class HemfPictureTest {
                 }
             }
         }
-    } */
+    }
 
     private static int hashException(Throwable e) {
         StringBuilder sb = new StringBuilder();
@@ -222,7 +220,7 @@ public class HemfPictureTest {
         }
 
         return Files.newBufferedWriter(log, StandardCharsets.UTF_8, soo);
-    }
+    }*/
 
     @Test
     public void testBasicWindows() throws Exception {