]> source.dussan.org Git - poi.git/commitdiff
#60656 - Support export file that contains emf and render it correctly
authorAndreas Beeker <kiwiwings@apache.org>
Sun, 23 Sep 2018 01:58:40 +0000 (01:58 +0000)
committerAndreas Beeker <kiwiwings@apache.org>
Sun, 23 Sep 2018 01:58:40 +0000 (01:58 +0000)
git-svn-id: https://svn.apache.org/repos/asf/poi/branches/hemf@1841712 13f79535-47bb-0310-9956-ffa450edef68

31 files changed:
src/integrationtest/org/apache/poi/stress/SlideShowHandler.java
src/java/org/apache/poi/sl/draw/BitmapImageRenderer.java
src/java/org/apache/poi/sl/draw/DrawFactory.java
src/java/org/apache/poi/sl/draw/DrawPictureShape.java
src/java/org/apache/poi/sl/draw/DrawTextParagraph.java
src/java/org/apache/poi/sl/draw/DrawTextShape.java
src/java/org/apache/poi/sl/draw/ImageRenderer.java
src/ooxml/java/org/apache/poi/xslf/util/PPTX2PNG.java
src/ooxml/testcases/org/apache/poi/sl/TestFonts.java
src/ooxml/testcases/org/apache/poi/xslf/usermodel/TestXSLFSimpleShape.java
src/resources/main/META-INF/services/org.apache.poi.sl.draw.ImageRenderer [new file with mode: 0644]
src/resources/scratchpad/META-INF/services/org.apache.poi.sl.draw.ImageRenderer [new file with mode: 0644]
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/draw/HemfImageRenderer.java [new file with mode: 0644]
src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfBounded.java [new file with mode: 0644]
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/HemfHeader.java
src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfMisc.java
src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfRecord.java
src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfRecordType.java
src/scratchpad/src/org/apache/poi/hemf/usermodel/HemfPicture.java
src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfDrawProperties.java
src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfGraphics.java
src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfImageRenderer.java [new file with mode: 0644]
src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfSLImageRenderer.java [deleted file]
src/scratchpad/src/org/apache/poi/hwmf/record/HwmfDraw.java
src/scratchpad/src/org/apache/poi/hwmf/record/HwmfHatchStyle.java
src/scratchpad/testcases/org/apache/poi/hemf/usermodel/HemfPictureTest.java
src/scratchpad/testcases/org/apache/poi/hslf/usermodel/TestPicture.java

index 63ffdbd58c2211573cf3b54efd22c5cdcbb2d6d7..20460cc2db7ae3a77cb450a1eead1312b63a78ae 100644 (file)
@@ -105,7 +105,6 @@ public abstract class SlideShowHandler extends POIFSFileHandler {
         for (Slide<?,?> s : ss.getSlides()) {
             BufferedImage img = new BufferedImage(pgsize.width, pgsize.height, BufferedImage.TYPE_INT_ARGB);
             Graphics2D graphics = img.createGraphics();
-            DrawFactory.getInstance(graphics).fixFonts(graphics);
 
             // default rendering options
             graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
index a92c8dc5900ed512637d6dad37e0c41b86984d79..c37ce006a0f17cd9c90b6992da6f0afef73ec33b 100644 (file)
@@ -40,6 +40,7 @@ import javax.imageio.ImageTypeSpecifier;
 import javax.imageio.stream.ImageInputStream;
 import javax.imageio.stream.MemoryCacheImageInputStream;
 
+import org.apache.poi.sl.usermodel.PictureData.PictureType;
 import org.apache.poi.util.IOUtils;
 import org.apache.poi.util.POILogFactory;
 import org.apache.poi.util.POILogger;
@@ -48,10 +49,23 @@ import org.apache.poi.util.POILogger;
  * For now this class renders only images supported by the javax.imageio.ImageIO framework.
  **/
 public class BitmapImageRenderer implements ImageRenderer {
-    private final static POILogger LOG = POILogFactory.getLogger(ImageRenderer.class);
+    private final static POILogger LOG = POILogFactory.getLogger(BitmapImageRenderer.class);
 
     protected BufferedImage img;
 
+    @Override
+    public boolean canRender(String contentType) {
+        PictureType[] pts = {
+            PictureType.JPEG, PictureType.PNG, PictureType.BMP, PictureType.GIF
+        };
+        for (PictureType pt : pts) {
+            if (pt.contentType.equalsIgnoreCase(contentType)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     @Override
     public void loadImage(InputStream data, String contentType) throws IOException {
         img = readImage(data, contentType);
index 98c41ed993c697800e04c1dd67b001926d5a12b0..99c9942b67c1dcd5aa0acbd031e1771e81ad2c39 100644 (file)
@@ -22,8 +22,6 @@ import java.awt.font.TextLayout;
 import java.awt.geom.AffineTransform;
 import java.awt.geom.Rectangle2D;
 import java.text.AttributedString;
-import java.util.HashMap;
-import java.util.Map;
 
 import org.apache.poi.sl.usermodel.Background;
 import org.apache.poi.sl.usermodel.ConnectorShape;
@@ -40,18 +38,18 @@ import org.apache.poi.sl.usermodel.TableShape;
 import org.apache.poi.sl.usermodel.TextBox;
 import org.apache.poi.sl.usermodel.TextParagraph;
 import org.apache.poi.sl.usermodel.TextShape;
-import org.apache.poi.util.JvmBugs;
 
 public class DrawFactory {
-    protected static final ThreadLocal<DrawFactory> defaultFactory = new ThreadLocal<>();
+    private static final ThreadLocal<DrawFactory> defaultFactory = new ThreadLocal<>();
 
     /**
      * Set a custom draw factory for the current thread.
      * This is a fallback, for operations where usercode can't set a graphics context.
      * Preferably use the rendering hint {@link Drawable#DRAW_FACTORY} to set the factory.
      *
-     * @param factory
+     * @param factory the custom factory
      */
+    @SuppressWarnings("unused")
     public static void setDefaultFactory(DrawFactory factory) {
         defaultFactory.set(factory);
     }
@@ -170,6 +168,7 @@ public class DrawFactory {
         return new DrawBackground(shape);
     }
     
+    @SuppressWarnings("WeakerAccess")
     public DrawTextFragment getTextFragment(TextLayout layout, AttributedString str) {
         return new DrawTextFragment(layout, str);
     }
@@ -213,35 +212,6 @@ public class DrawFactory {
     }
     
     
-    /**
-     * Replace font families for Windows JVM 6, which contains a font rendering error.
-     * This is likely to be removed, when POI upgrades to JDK 7
-     *
-     * @param graphics the graphics context which will contain the font mapping
-     */
-    public void fixFonts(Graphics2D graphics) {
-        if (!JvmBugs.hasLineBreakMeasurerBug()) return;
-        @SuppressWarnings("unchecked")
-        Map<String,String> fontMap = (Map<String,String>)graphics.getRenderingHint(Drawable.FONT_MAP);
-        if (fontMap == null) {
-            fontMap = new HashMap<>();
-            graphics.setRenderingHint(Drawable.FONT_MAP, fontMap);
-        }
-        
-        String fonts[][] = {
-            { "Calibri", "Lucida Sans" },
-            { "Cambria", "Lucida Bright" },
-            { "Times New Roman", "Lucida Bright" },
-            { "serif", "Lucida Bright" }
-        };
-
-        for (String f[] : fonts) {
-            if (!fontMap.containsKey(f[0])) {
-                fontMap.put(f[0], f[1]);
-            }
-        }
-    }
-    
     /**
      * Return a FontManager, either registered beforehand or a default implementation
      *
index fcca6d07a7ffaa13cb308aafd451afd868a84d7f..5c42d6fd1322e00975f3bcf915826447526feda9 100644 (file)
@@ -22,19 +22,19 @@ import java.awt.Graphics2D;
 import java.awt.Insets;
 import java.awt.geom.Rectangle2D;
 import java.io.IOException;
+import java.util.ServiceLoader;
 
 import org.apache.poi.sl.usermodel.PictureData;
-import org.apache.poi.sl.usermodel.PictureData.PictureType;
-import org.apache.poi.util.POILogFactory;
-import org.apache.poi.util.POILogger;
 import org.apache.poi.sl.usermodel.PictureShape;
 import org.apache.poi.sl.usermodel.RectAlign;
+import org.apache.poi.util.POILogFactory;
+import org.apache.poi.util.POILogger;
 
 
 public class DrawPictureShape extends DrawSimpleShape {
     private static final POILogger LOG = POILogFactory.getLogger(DrawPictureShape.class);
-    private static final String WMF_IMAGE_RENDERER = "org.apache.poi.hwmf.draw.HwmfSLImageRenderer";
-    
+    private static final ServiceLoader<ImageRenderer> rendererLoader = ServiceLoader.load(ImageRenderer.class);
+
     public DrawPictureShape(PictureShape<?,?> shape) {
         super(shape);
     }
@@ -59,28 +59,26 @@ public class DrawPictureShape extends DrawSimpleShape {
     /**
      * Returns an ImageRenderer for the PictureData
      *
-     * @param graphics
+     * @param graphics the graphics context
      * @return the image renderer
      */
+    @SuppressWarnings("WeakerAccess")
     public static ImageRenderer getImageRenderer(Graphics2D graphics, String contentType) {
         ImageRenderer renderer = (ImageRenderer)graphics.getRenderingHint(Drawable.IMAGE_RENDERER);
         if (renderer != null) {
             return renderer;
         }
-        
-        if (PictureType.WMF.contentType.equals(contentType)) {
-            try {
-                @SuppressWarnings("unchecked")
-                Class<? extends ImageRenderer> irc = (Class<? extends ImageRenderer>)
-                        DrawPictureShape.class.getClassLoader().loadClass(WMF_IMAGE_RENDERER);
-                return irc.newInstance();
-            } catch (Exception e) {
-                // WMF image renderer is not on the classpath, continuing with BitmapRenderer
-                // although this doesn't make much sense ...
-                LOG.log(POILogger.ERROR, "WMF image renderer is not on the classpath - include poi-scratchpad jar!", e);
+
+        for (ImageRenderer ir : rendererLoader) {
+            if (ir.canRender(contentType)) {
+                return ir;
             }
         }
-        
+
+        LOG.log(POILogger.ERROR, "No suiteable image renderer found for content-type '"+
+                contentType+"' - include poi-scratchpad jar!");
+
+        // falling back to BitmapImageRenderer although this doesn't make much sense ...
         return new BitmapImageRenderer();
     }
     
index 938d46230a4a7a4c1804078c5d6df4539a447daf..1ab4af3a1e97d4f9b473882612aa46a528c022eb 100644 (file)
@@ -254,7 +254,6 @@ public class DrawTextParagraph implements Drawable {
         lines.clear();
 
         DrawFactory fact = DrawFactory.getInstance(graphics);
-        fact.fixFonts(graphics);
         StringBuilder text = new StringBuilder();
         AttributedString at = getAttributedString(graphics, text);
         boolean emptyParagraph = text.toString().trim().isEmpty();
@@ -635,13 +634,6 @@ public class DrawTextParagraph implements Drawable {
      * <li>determine the font group - a text run can have different font groups. Depending on the chars,
      * the correct font group needs to be used
      *
-     * @param graphics
-     * @param dfm
-     * @param attList
-     * @param beginIndex
-     * @param run
-     * @param runText
-     *
      * @see <a href="https://blogs.msdn.microsoft.com/officeinteroperability/2013/04/22/office-open-xml-themes-schemes-and-fonts/">Office Open XML Themes, Schemes, and Fonts</a>
      */
     private void processGlyphs(Graphics2D graphics, DrawFontManager dfm, List<AttributedStringData> attList, final int beginIndex, TextRun run, String runText) {
index 413ab218c9461d7bd3e7c3e942e5a74c97e8a934..c4dd65bb7528ce1f46eecb9b50dacf59d8c9b524 100644 (file)
@@ -40,8 +40,6 @@ public class DrawTextShape extends DrawSimpleShape {
 
     @Override
     public void drawContent(Graphics2D graphics) {
-        DrawFactory.getInstance(graphics).fixFonts(graphics);
-        
         TextShape<?,?> s = getShape();
         
         Rectangle2D anchor = DrawShape.getAnchor(graphics, s);
@@ -219,10 +217,9 @@ public class DrawTextShape extends DrawSimpleShape {
             graphics.addRenderingHints(oldGraphics.getRenderingHints());
             graphics.setTransform(oldGraphics.getTransform());
         }
-        DrawFactory.getInstance(graphics).fixFonts(graphics);
         return drawParagraphs(graphics, 0, 0);
     }
-    
+
     @Override
     protected TextShape<?,? extends TextParagraph<?,?,? extends TextRun>> getShape() {
         return (TextShape<?,? extends TextParagraph<?,?,? extends TextRun>>)shape;
index 7ecc96a9671843fbf6cdd6a6ee1a9d737510e71b..b2355b30128ee347405cd287c4375e99978064b0 100644 (file)
@@ -75,6 +75,13 @@ import java.io.InputStream;
  * </pre>
  */
 public interface ImageRenderer {
+    /**
+     * Determines if this image renderer implementation supports the given contentType
+     * @param contentType the image content type
+     * @return if the content type is supported
+     */
+    boolean canRender(String contentType);
+
     /**
      * Load and buffer the image
      *
index 79327bce4fe3d03775e570df59055f9e7f0470c5..c4fbb4ca3b01d752b561b14540c85511102e5833 100644 (file)
@@ -139,7 +139,6 @@ public class PPTX2PNG {
 
                 BufferedImage img = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
                 Graphics2D graphics = img.createGraphics();
-                DrawFactory.getInstance(graphics).fixFonts(graphics);
 
                 // default rendering options
                 graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
index b7a6d16a548fcbb63a09d0412a46e5dd8e4d9e1d..846335ce90aaab8eebcf7d881df409833e08c6d1 100644 (file)
@@ -131,8 +131,6 @@ public class TestFonts {
         graphics.setRenderingHint(Drawable.FONT_FALLBACK, fallbackMap);
         graphics.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
 
-        DrawFactory.getInstance(graphics).fixFonts(graphics);
-
         tb.resizeToFitText(graphics);
         graphics.dispose();
 
index e35ed35a29db7c8d871de9160f6e7aecbf1449be..ddbfe26652aee8166d9e70f14365d5e08a798dff 100644 (file)
@@ -361,7 +361,6 @@ public class TestXSLFSimpleShape {
 
             BufferedImage img = new BufferedImage(pgsize.width, pgsize.height, BufferedImage.TYPE_INT_ARGB);
             Graphics2D graphics = img.createGraphics();
-            DrawFactory.getInstance(graphics).fixFonts(graphics);
 
             // default rendering options
             graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
diff --git a/src/resources/main/META-INF/services/org.apache.poi.sl.draw.ImageRenderer b/src/resources/main/META-INF/services/org.apache.poi.sl.draw.ImageRenderer
new file mode 100644 (file)
index 0000000..1e7699f
--- /dev/null
@@ -0,0 +1 @@
+org.apache.poi.sl.draw.BitmapImageRenderer
\ No newline at end of file
diff --git a/src/resources/scratchpad/META-INF/services/org.apache.poi.sl.draw.ImageRenderer b/src/resources/scratchpad/META-INF/services/org.apache.poi.sl.draw.ImageRenderer
new file mode 100644 (file)
index 0000000..b23f654
--- /dev/null
@@ -0,0 +1,2 @@
+org.apache.poi.hwmf.draw.HwmfImageRenderer
+org.apache.poi.hemf.draw.HemfImageRenderer
\ No newline at end of file
index f2e1957a28063387959b64f1e7176019de748ba2..8bb8af33bfeaa09042fd4e1af032ac56fcc7b67a 100644 (file)
 
 package org.apache.poi.hemf.draw;
 
+import java.awt.geom.Path2D;
+
 import org.apache.poi.hwmf.draw.HwmfDrawProperties;
 
 public class HemfDrawProperties extends HwmfDrawProperties {
 
+    /** Path for path bracket operations */
+    protected final Path2D path;
+
+
     public HemfDrawProperties() {
+        path = new Path2D.Double();
     }
 
     public HemfDrawProperties(HemfDrawProperties other) {
         super(other);
+        path = (Path2D)other.path.clone();
+    }
+
+    /**
+     * @return the current path used for bracket operations
+     */
+    public Path2D getPath() {
+        return path;
     }
 }
index ff190652e12a00f38ab1881dc0d51199686bdd51..05701ef823030c24fa906db699bb275ba1289582 100644 (file)
 package org.apache.poi.hemf.draw;
 
 import java.awt.Graphics2D;
+import java.awt.geom.AffineTransform;
 import java.awt.geom.Rectangle2D;
+import java.util.ArrayDeque;
+import java.util.Deque;
 
-import org.apache.poi.hwmf.draw.HwmfDrawProperties;
+import org.apache.poi.hemf.record.emf.HemfBounded;
+import org.apache.poi.hemf.record.emf.HemfRecord;
 import org.apache.poi.hwmf.draw.HwmfGraphics;
 
 public class HemfGraphics extends HwmfGraphics {
+
+    private final Deque<AffineTransform> transforms = new ArrayDeque<>();
+
     public HemfGraphics(Graphics2D graphicsCtx, Rectangle2D bbox) {
         super(graphicsCtx,bbox);
+        // add dummy entry for object index 0, as emf is 1-based
+        addObjectTableEntry((ctx)->{});
     }
 
     @Override
@@ -42,4 +51,43 @@ public class HemfGraphics extends HwmfGraphics {
         propStack.add(prop);
         prop = new HemfDrawProperties((HemfDrawProperties)prop);
     }
+
+    @Override
+    public void updateWindowMapMode() {
+        // ignore window settings
+    }
+
+    public void draw(HemfRecord r) {
+        if (r instanceof HemfBounded) {
+            saveTransform();
+            final HemfBounded bounded = (HemfBounded)r;
+            final Rectangle2D tgt = bounded.getRecordBounds();
+            if (tgt != null && !tgt.isEmpty()) {
+                final Rectangle2D src = bounded.getShapeBounds(this);
+                if (src != null && !src.isEmpty()) {
+                    graphicsCtx.translate(tgt.getCenterX() - src.getCenterX(), tgt.getCenterY() - src.getCenterY());
+                    graphicsCtx.translate(src.getCenterX(), src.getCenterY());
+                    graphicsCtx.scale(tgt.getWidth() / src.getWidth(), tgt.getHeight() / src.getHeight());
+                    graphicsCtx.translate(-src.getCenterX(), -src.getCenterY());
+                }
+            }
+        }
+
+        r.draw(this);
+
+        if (r instanceof HemfBounded) {
+            restoreTransform();
+        }
+    }
+
+
+    /** saves the current affine transform on the stack */
+    private void saveTransform() {
+        transforms.push(graphicsCtx.getTransform());
+    }
+
+    /** restore the last saved affine transform */
+    private void restoreTransform() {
+        graphicsCtx.setTransform(transforms.pop());
+    }
 }
diff --git a/src/scratchpad/src/org/apache/poi/hemf/draw/HemfImageRenderer.java b/src/scratchpad/src/org/apache/poi/hemf/draw/HemfImageRenderer.java
new file mode 100644 (file)
index 0000000..712c2e6
--- /dev/null
@@ -0,0 +1,126 @@
+/* ====================================================================
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+==================================================================== */
+
+package org.apache.poi.hemf.draw;
+
+import java.awt.Dimension;
+import java.awt.Graphics2D;
+import java.awt.Insets;
+import java.awt.RenderingHints;
+import java.awt.geom.Dimension2D;
+import java.awt.geom.Rectangle2D;
+import java.awt.image.BufferedImage;
+import java.awt.image.RescaleOp;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.apache.poi.hemf.usermodel.HemfPicture;
+import org.apache.poi.sl.draw.ImageRenderer;
+import org.apache.poi.sl.usermodel.PictureData;
+import org.apache.poi.util.Units;
+
+public class HemfImageRenderer implements ImageRenderer {
+    HemfPicture image;
+    double alpha;
+
+    @Override
+    public boolean canRender(String contentType) {
+        return PictureData.PictureType.EMF.contentType.equalsIgnoreCase(contentType);
+    }
+
+    @Override
+    public void loadImage(InputStream data, String contentType) throws IOException {
+        if (!PictureData.PictureType.EMF.contentType.equals(contentType)) {
+            throw new IOException("Invalid picture type");
+        }
+        image = new HemfPicture(data);
+    }
+
+    @Override
+    public void loadImage(byte[] data, String contentType) throws IOException {
+        if (!PictureData.PictureType.EMF.contentType.equals(contentType)) {
+            throw new IOException("Invalid picture type");
+        }
+        image = new HemfPicture(new ByteArrayInputStream(data));
+    }
+
+    @Override
+    public Dimension getDimension() {
+        int width = 0, height = 0;
+        if (image != null) {
+            Dimension2D dim = image.getSize();
+            width = Units.pointsToPixel(dim.getWidth());
+            // keep aspect ratio for height
+            height = Units.pointsToPixel(dim.getHeight());
+        }
+        return new Dimension(width, height);
+    }
+
+    @Override
+    public void setAlpha(double alpha) {
+        this.alpha = alpha;
+    }
+
+    @Override
+    public BufferedImage getImage() {
+        return getImage(getDimension());
+    }
+
+    @Override
+    public BufferedImage getImage(Dimension dim) {
+        if (image == null) {
+            return new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB);
+        }
+
+        BufferedImage bufImg = new BufferedImage((int)dim.getWidth(), (int)dim.getHeight(), 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);
+        image.draw(g, new Rectangle2D.Double(0,0,dim.getWidth(),dim.getHeight()));
+        g.dispose();
+
+        if (alpha != 0) {
+            BufferedImage newImg = new BufferedImage((int)dim.getWidth(), (int)dim.getHeight(), BufferedImage.TYPE_INT_ARGB);
+            g = newImg.createGraphics();
+            RescaleOp op = new RescaleOp(new float[]{1.0f, 1.0f, 1.0f, (float)alpha}, new float[]{0,0,0,0}, null);
+            g.drawImage(bufImg, op, 0, 0);
+            g.dispose();
+            bufImg = newImg;
+        }
+
+        return bufImg;
+    }
+
+    @Override
+    public boolean drawImage(Graphics2D graphics, Rectangle2D anchor) {
+        return drawImage(graphics, anchor, null);
+    }
+
+    @Override
+    public boolean drawImage(Graphics2D graphics, Rectangle2D anchor, Insets clip) {
+        if (image == null) {
+            return false;
+        } else {
+            image.draw(graphics, anchor);
+            return true;
+        }
+    }
+
+}
diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfBounded.java b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfBounded.java
new file mode 100644 (file)
index 0000000..dee8f94
--- /dev/null
@@ -0,0 +1,45 @@
+/* ====================================================================
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+==================================================================== */
+
+package org.apache.poi.hemf.record.emf;
+
+import java.awt.geom.Rectangle2D;
+
+import org.apache.poi.hemf.draw.HemfGraphics;
+
+/**
+ * In EMF, shape records bring their own bounding.
+ * The record bounding is in the same space as the global drawing context,
+ * but the specified shape points can have a different space and therefore
+ * need to be translated/normalized
+ */
+public interface HemfBounded {
+    /**
+     * Getter for the outer bounds which are given in the record
+     *
+     * @return the bounds specified in the record
+     */
+    Rectangle2D getRecordBounds();
+
+    /**
+     * Getter for the inner bounds which are calculated by the shape points
+     *
+     * @param ctx the graphics context
+     * @return the bounds of the shape points
+     */
+    Rectangle2D getShapeBounds(HemfGraphics ctx);
+}
index 77ddf7e39e7bf39a6604832fe1b066eadbd8d5b5..c2cc9c3de2748d8c559f6d57c3314e2d56cd6047 100644 (file)
@@ -21,6 +21,8 @@ import static org.apache.poi.hwmf.record.HwmfBrushStyle.BS_NULL;
 import static org.apache.poi.hwmf.record.HwmfBrushStyle.BS_SOLID;
 
 import java.awt.Color;
+import java.awt.geom.Arc2D;
+import java.awt.geom.Area;
 import java.awt.geom.Dimension2D;
 import java.awt.geom.Path2D;
 import java.awt.geom.PathIterator;
@@ -30,7 +32,6 @@ import java.io.IOException;
 
 import org.apache.poi.hemf.draw.HemfDrawProperties;
 import org.apache.poi.hemf.draw.HemfGraphics;
-import org.apache.poi.hwmf.draw.HwmfGraphics;
 import org.apache.poi.hwmf.record.HwmfColorRef;
 import org.apache.poi.hwmf.record.HwmfDraw;
 import org.apache.poi.hwmf.record.HwmfDraw.WmfSelectObject;
@@ -187,7 +188,7 @@ public class HemfDraw {
 
 
     /** The EMR_POLYBEZIER record specifies one or more Bezier curves. */
-    public static class EmfPolyBezier extends HwmfDraw.WmfPolygon implements HemfRecord {
+    public static class EmfPolyBezier extends HwmfDraw.WmfPolygon implements HemfRecord, HemfBounded {
         private final Rectangle2D bounds = new Rectangle2D.Double();
 
         @Override
@@ -238,9 +239,13 @@ public class HemfDraw {
             for (int i=0; i+3<pointCnt; i+=3) {
                 // x (4 bytes): A 32-bit signed integer that defines the horizontal (x) coordinate of the point.
                 // y (4 bytes): A 32-bit signed integer that defines the vertical (y) coordinate of the point.
-                if (i==0 && hasStartPoint()) {
-                    size += readPoint(leis, pnt[0]);
-                    poly.moveTo(pnt[0].getX(),pnt[0].getY());
+                if (i==0) {
+                    if (hasStartPoint()) {
+                        size += readPoint(leis, pnt[0]);
+                        poly.moveTo(pnt[0].getX(), pnt[0].getY());
+                    } else {
+                        poly.moveTo(0, 0);
+                    }
                 }
 
                 size += readPoint(leis, pnt[0]);
@@ -257,6 +262,19 @@ public class HemfDraw {
             return size;
         }
 
+        @Override
+        public Rectangle2D getRecordBounds() {
+            return bounds;
+        }
+
+        @Override
+        public Rectangle2D getShapeBounds(HemfGraphics ctx) {
+            if (!hasStartPoint()) {
+                throw new IllegalStateException("shape bounds not valid for path bracket based record: "+getClass().getName());
+            }
+            return poly.getBounds2D();
+        }
+
         /**
          * @return true, if start point is in the list of points. false, if start point is taken from the context
          */
@@ -285,7 +303,7 @@ public class HemfDraw {
      * The EMR_POLYGON record specifies a polygon consisting of two or more vertexes connected by
      * straight lines.
      */
-    public static class EmfPolygon extends HwmfDraw.WmfPolygon implements HemfRecord {
+    public static class EmfPolygon extends HwmfDraw.WmfPolygon implements HemfRecord, HemfBounded {
         private final Rectangle2D bounds = new Rectangle2D.Double();
 
         @Override
@@ -310,21 +328,34 @@ public class HemfDraw {
             for (int i=0; i<points; i++) {
                 size += readPoint(leis, pnt);
                 if (i==0) {
-                    poly.moveTo(pnt.getX(), pnt.getY());
-
                     if (hasStartPoint()) {
-                        continue;
+                        poly.moveTo(pnt.getX(), pnt.getY());
+                    } else {
+                        // if this path is connected to the current position (= has no start point)
+                        // the first entry is a dummy entry and will be skipped later
+                        poly.moveTo(0,0);
                     }
-
-                    // if this path is connected to the current position (= has no start point)
-                    // the first entry is a dummy entry and will be skipped later
+                } else {
+                    poly.lineTo(pnt.getX(), pnt.getY());
                 }
-                poly.lineTo(pnt.getX(), pnt.getY());
             }
 
             return size;
         }
 
+        @Override
+        public Rectangle2D getRecordBounds() {
+            return bounds;
+        }
+
+        @Override
+        public Rectangle2D getShapeBounds(HemfGraphics ctx) {
+            if (!hasStartPoint()) {
+                throw new IllegalStateException("shape bounds not valid for path bracket based record: "+getClass().getName());
+            }
+            return poly.getBounds2D();
+        }
+
         /**
          * @return true, if start point is in the list of points. false, if start point is taken from the context
          */
@@ -398,8 +429,15 @@ public class HemfDraw {
         }
 
         @Override
-        protected Path2D getShape(HwmfGraphics ctx) {
-            return polyTo(ctx, poly);
+        public void draw(HemfGraphics ctx) {
+            polyTo(ctx, poly);
+        }
+
+        @Override
+        public Rectangle2D getShapeBounds(HemfGraphics ctx) {
+            // should be called in a beginPath/endPath bracket, so the shape bounds
+            // of this path segment are irrelevant
+            return null;
         }
     }
 
@@ -432,8 +470,15 @@ public class HemfDraw {
         }
 
         @Override
-        protected Path2D getShape(HwmfGraphics ctx) {
-            return polyTo(ctx, poly);
+        public void draw(HemfGraphics ctx) {
+            polyTo(ctx, poly);
+        }
+
+        @Override
+        public Rectangle2D getShapeBounds(HemfGraphics ctx) {
+            // should be called in a beginPath/endPath bracket, so the shape bounds
+            // of this path segment are irrelevant
+            return null;
         }
     }
 
@@ -458,7 +503,7 @@ public class HemfDraw {
     /**
      * The EMR_POLYPOLYGON record specifies a series of closed polygons.
      */
-    public static class EmfPolyPolygon extends HwmfDraw.WmfPolyPolygon implements HemfRecord {
+    public static class EmfPolyPolygon extends HwmfDraw.WmfPolyPolygon implements HemfRecord, HemfBounded {
         private final Rectangle2D bounds = new Rectangle2D.Double();
 
         @Override
@@ -519,6 +564,17 @@ public class HemfDraw {
         protected boolean isClosed() {
             return true;
         }
+
+        @Override
+        public Rectangle2D getRecordBounds() {
+            return bounds;
+        }
+
+        @Override
+        public Rectangle2D getShapeBounds(HemfGraphics ctx) {
+            Area area = getShape(ctx);
+            return area == null ? bounds : area.getBounds2D();
+        }
     }
 
     /**
@@ -601,6 +657,14 @@ public class HemfDraw {
         public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
             return readPointL(leis, point);
         }
+
+        @Override
+        public void draw(HemfGraphics ctx) {
+            HemfDrawProperties prop = ctx.getProperties();
+            final Path2D path = prop.getPath();
+            path.moveTo(point.getX(), point.getY());
+            prop.setLocation(point);
+        }
     }
 
     /**
@@ -736,6 +800,14 @@ public class HemfDraw {
         public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
             return readPointL(leis, point);
         }
+
+        @Override
+        public void draw(HemfGraphics ctx) {
+            final HemfDrawProperties prop = ctx.getProperties();
+            final Path2D path = prop.getPath();
+            path.lineTo(point.getX(), point.getY());
+            prop.setLocation(point);
+        }
     }
 
     /**
@@ -758,13 +830,15 @@ public class HemfDraw {
 
         @Override
         public void draw(HemfGraphics ctx) {
-            super.draw(ctx);
+            final Path2D path = ctx.getProperties().getPath();
+            Arc2D arc = getShape();
+            path.append(arc, true);
             ctx.getProperties().setLocation(endPoint);
         }
     }
 
     /** The EMR_POLYDRAW record specifies a set of line segments and Bezier curves. */
-    public static class EmfPolyDraw extends HwmfDraw.WmfPolygon implements HemfRecord {
+    public static class EmfPolyDraw extends HwmfDraw.WmfPolygon implements HemfRecord, HemfBounded {
         private final Rectangle2D bounds = new Rectangle2D.Double();
 
         @Override
@@ -837,6 +911,16 @@ public class HemfDraw {
             size += count;
             return size;
         }
+
+        @Override
+        public Rectangle2D getRecordBounds() {
+            return bounds;
+        }
+
+        @Override
+        public Rectangle2D getShapeBounds(HemfGraphics ctx) {
+            return poly.getBounds2D();
+        }
     }
 
     public static class EmfPolyDraw16 extends EmfPolyDraw {
@@ -850,6 +934,16 @@ public class HemfDraw {
         }
     }
 
+    /**
+     * This record opens a path bracket in the current playback device context.
+     *
+     * After a path bracket is open, an application can begin processing records to define
+     * the points that lie in the path. An application MUST close an open path bracket by
+     * processing the EMR_ENDPATH record.
+     *
+     * When an application processes the EMR_BEGINPATH record, all previous paths
+     * MUST be discarded from the playback device context.
+     */
     public static class EmfBeginPath implements HemfRecord {
         @Override
         public HemfRecordType getEmfRecordType() {
@@ -860,8 +954,19 @@ public class HemfDraw {
         public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
             return 0;
         }
+
+        @Override
+        public void draw(HemfGraphics ctx) {
+            final HemfDrawProperties prop = ctx.getProperties();
+            final Path2D path = prop.getPath();
+            path.reset();
+        }
     }
 
+    /**
+     * This record closes a path bracket and selects the path defined by the bracket into
+     * the playback device context.
+     */
     public static class EmfEndPath implements HemfRecord {
         @Override
         public HemfRecordType getEmfRecordType() {
@@ -874,6 +979,9 @@ public class HemfDraw {
         }
     }
 
+    /**
+     * This record aborts a path bracket or discards the path from a closed path bracket.
+     */
     public static class EmfAbortPath implements HemfRecord {
         @Override
         public HemfRecordType getEmfRecordType() {
@@ -884,8 +992,34 @@ public class HemfDraw {
         public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
             return 0;
         }
+
+        @Override
+        public void draw(HemfGraphics ctx) {
+            final HemfDrawProperties prop = ctx.getProperties();
+            final Path2D path = prop.getPath();
+            path.reset();
+        }
     }
 
+    /**
+     * This record closes an open figure in a path.
+     *
+     * Processing the EMR_CLOSEFIGURE record MUST close the figure by drawing a line
+     * from the current position to the first point of the figure, and then it MUST connect
+     * the lines by using the line join style. If a figure is closed by processing the
+     * EMR_LINETO record instead of the EMR_CLOSEFIGURE record, end caps are
+     * used to create the corner instead of a join.
+     *
+     * The EMR_CLOSEFIGURE record SHOULD only be used if there is an open path
+     * bracket in the playback device context.
+     *
+     * A figure in a path is open unless it is explicitly closed by processing this record.
+     * Note: A figure can be open even if the current point and the starting point of the
+     * figure are the same.
+     *
+     * After processing the EMR_CLOSEFIGURE record, adding a line or curve to the path
+     * MUST start a new figure.
+     */
     public static class EmfCloseFigure implements HemfRecord {
         @Override
         public HemfRecordType getEmfRecordType() {
@@ -896,8 +1030,22 @@ public class HemfDraw {
         public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
             return 0;
         }
+
+
+        @Override
+        public void draw(HemfGraphics ctx) {
+            final HemfDrawProperties prop = ctx.getProperties();
+            final Path2D path = prop.getPath();
+            path.closePath();
+            prop.setLocation(path.getCurrentPoint());
+
+        }
     }
 
+    /**
+     * This record transforms any curves in the selected path into the playback device
+     * context; each curve MUST be turned into a sequence of lines.
+     */
     public static class EmfFlattenPath implements HemfRecord {
         @Override
         public HemfRecordType getEmfRecordType() {
@@ -910,6 +1058,10 @@ public class HemfDraw {
         }
     }
 
+    /**
+     * This record redefines the current path as the area that would be painted if the path
+     * were drawn using the pen currently selected into the playback device context.
+     */
     public static class EmfWidenPath implements HemfRecord {
         @Override
         public HemfRecordType getEmfRecordType() {
@@ -988,16 +1140,15 @@ public class HemfDraw {
         return 2*LittleEndianConsts.INT_SIZE;
     }
 
-    private static Path2D polyTo(HwmfGraphics ctx, Path2D poly) {
-        Path2D polyCopy = new Path2D.Double();
-        Point2D start = ctx.getProperties().getLocation();
-        polyCopy.moveTo(start.getX(), start.getY());
-
-        PathIterator iter = poly.getPathIterator(null);
-        iter.next();
-
-        polyCopy.append(iter, true);
-        return polyCopy;
+    private static void polyTo(HemfGraphics ctx, Path2D poly) {
+        final HemfDrawProperties prop = ctx.getProperties();
+        final Path2D path = prop.getPath();
+        PathIterator pi = poly.getPathIterator(null);
+        // ignore dummy start point (moveTo)
+        pi.next();
+        assert(!pi.isDone());
+        path.append(pi, true);
+        prop.setLocation(path.getCurrentPoint());
     }
 
 
index f832a7abeb14be18c760e7aecea998bedfd0c530..e28a355f0c4ce2bfd5b00f48a3542e169fac51ab 100644 (file)
 
 package org.apache.poi.hemf.record.emf;
 
-import static org.apache.poi.hemf.record.emf.HemfDraw.readRectL;
 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.HemfRecordIterator.HEADER_SIZE;
 
 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.io.ByteArrayInputStream;
@@ -31,8 +32,11 @@ import java.io.IOException;
 import java.util.ArrayList;
 import java.util.List;
 
+import org.apache.poi.hemf.draw.HemfDrawProperties;
+import org.apache.poi.hemf.draw.HemfGraphics;
 import org.apache.poi.hwmf.draw.HwmfGraphics;
 import org.apache.poi.hwmf.record.HwmfBitmapDib;
+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.HwmfFill;
@@ -515,7 +519,7 @@ public class HemfFill {
     static long readBitmap(final LittleEndianInputStream leis, final HwmfBitmapDib bitmap,
             final int startIdx, final int offBmiSrc, final int cbBmiSrc, final int offBitsSrc, int cbBitsSrc)
     throws IOException {
-        final int offCurr = leis.getReadIndex()-(startIdx-HEADER_SIZE);
+        final int offCurr = leis.getReadIndex()-startIdx;
         final int undefinedSpace1 = offBmiSrc-offCurr;
         assert(undefinedSpace1 >= 0);
 
@@ -622,7 +626,7 @@ public class HemfFill {
      * The EMR_FILLPATH record closes any open figures in the current path and fills the path's interior by
      * using the current brush and polygon-filling mode.
      */
-    public static class EmfFillPath implements HemfRecord {
+    public static class EmfFillPath implements HemfRecord, HemfBounded {
         protected final Rectangle2D bounds = new Rectangle2D.Double();
 
         @Override
@@ -635,5 +639,29 @@ public class HemfFill {
             // A 128-bit WMF RectL object, which specifies bounding rectangle, in device units
             return readRectL(leis, bounds);
         }
+
+        @Override
+        public Rectangle2D getRecordBounds() {
+            return bounds;
+        }
+
+        @Override
+        public Rectangle2D getShapeBounds(HemfGraphics ctx) {
+            final HemfDrawProperties prop = ctx.getProperties();
+            final Path2D path = prop.getPath();
+            return path.getBounds2D();
+        }
+
+        @Override
+        public void draw(HemfGraphics ctx) {
+            final HemfDrawProperties prop = ctx.getProperties();
+            final Path2D path = (Path2D)prop.getPath().clone();
+            path.setWindingRule(ctx.getProperties().getWindingRule());
+            if (prop.getBrushStyle() == HwmfBrushStyle.BS_NULL) {
+                ctx.draw(path);
+            } else {
+                ctx.fill(path);
+            }
+        }
     }
 }
index 0821d363ad3c43c63e7f0b1d684eeac984d92837..c1c0712c4807024982d76ab54afe5f4b3a8fe975 100644 (file)
@@ -111,12 +111,16 @@ public class HemfHeader implements HemfRecord {
         return hasExtension2;
     }
 
-    public long getMicrometersX() {
-        return (long)microDimension.getWidth();
+    public Dimension2D getDeviceDimension() {
+        return deviceDimension;
     }
 
-    public long getMicrometersY() {
-        return (long)microDimension.getHeight();
+    public Dimension2D getMilliDimension() {
+        return milliDimension;
+    }
+
+    public Dimension2D getMicroDimension() {
+        return microDimension;
     }
 
     @Override
@@ -135,8 +139,9 @@ public class HemfHeader implements HemfRecord {
                 ", offPixelFormat=" + offPixelFormat +
                 ", bOpenGL=" + bOpenGL +
                 ", hasExtension2=" + hasExtension2 +
-                ", micrometersX=" + getMicrometersX() +
-                ", micrometersY=" + getMicrometersY() +
+                ", deviceDimension=" + deviceDimension +
+                ", microDimension=" + microDimension +
+                ", milliDimension=" + milliDimension +
                 '}';
     }
 
index e843dc4f11a9a4c88136dd8a53c44e0b737a4106..f3424d08d068157ac65d1e923aec52fd9abb8257 100644 (file)
@@ -19,8 +19,10 @@ 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.HemfFill.readBitmap;
+import static org.apache.poi.hemf.record.emf.HemfFill.readXForm;
 import static org.apache.poi.hemf.record.emf.HemfRecordIterator.HEADER_SIZE;
 
+import java.awt.geom.AffineTransform;
 import java.awt.geom.Point2D;
 import java.io.IOException;
 import java.util.ArrayList;
@@ -54,34 +56,40 @@ public class HemfMisc {
 
         @Override
         public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
+            final int startIdx = leis.getReadIndex();
 
             // A 32-bit unsigned integer that specifies the number of palette entries.
-            int nPalEntries = (int) leis.readUInt();
+            final int nPalEntries = (int) leis.readUInt();
             // A 32-bit unsigned integer that specifies the offset to the palette entries from the start of this record.
-            int offPalEntries = (int) leis.readUInt();
+            final int offPalEntries = (int) leis.readUInt();
 
             int size = 2 * LittleEndianConsts.INT_SIZE;
-            int undefinedSpace1 = (int) (offPalEntries - size - HEADER_SIZE);
-            assert (undefinedSpace1 >= 0);
-            leis.skipFully(undefinedSpace1);
-            size += undefinedSpace1;
-
-            for (int i = 0; i < nPalEntries; i++) {
-                PaletteEntry pe = new PaletteEntry();
-                size += pe.init(leis);
-            }
 
-            int undefinedSpace2 = (int) (recordSize - size - LittleEndianConsts.INT_SIZE);
-            assert (undefinedSpace2 >= 0);
-            leis.skipFully(undefinedSpace2);
-            size += undefinedSpace2;
+            if (offPalEntries > 0) {
+                int undefinedSpace1 = (int) (offPalEntries - size - HEADER_SIZE);
+                assert (undefinedSpace1 >= 0);
+                leis.skipFully(undefinedSpace1);
+                size += undefinedSpace1;
+
+                for (int i = 0; i < nPalEntries; i++) {
+                    PaletteEntry pe = new PaletteEntry();
+                    size += pe.init(leis);
+                }
+
+                int undefinedSpace2 = (int) (recordSize - size - LittleEndianConsts.INT_SIZE);
+                assert (undefinedSpace2 >= 0);
+                leis.skipFully(undefinedSpace2);
+                size += undefinedSpace2;
+            }
 
             // A 32-bit unsigned integer that MUST be the same as Size and MUST be the
             // last field of the record and hence the metafile.
             // LogPaletteEntry objects, if they exist, MUST precede this field.
             long sizeLast = leis.readUInt();
             size += LittleEndianConsts.INT_SIZE;
-            assert ((sizeLast - HEADER_SIZE) == recordSize && recordSize == size);
+            // some files store the whole file size in sizeLast, other just the last record size
+            // assert (sizeLast == size+HEADER_SIZE);
+            assert (recordSize == size);
 
             return size;
         }
@@ -381,6 +389,7 @@ public class HemfMisc {
             // PS_COSMETIC, this value MUST be 0x00000001.
             long width = leis.readUInt();
             dimension.setSize(width, 0);
+            int size = 7 * LittleEndianConsts.INT_SIZE;
 
             // A 32-bit unsigned integer that specifies a brush style for the pen from the WMF BrushStyle enumeration
             //
@@ -389,16 +398,17 @@ public class HemfMisc {
             // The BS_NULL style SHOULD be used to specify a brush that has no effect
             brushStyle = HwmfBrushStyle.valueOf((int) leis.readUInt());
 
-            int size = 8 * LittleEndianConsts.INT_SIZE;
+            size += LittleEndianConsts.INT_SIZE;
 
             size += colorRef.init(leis);
 
             hatchStyle = HwmfHatchStyle.valueOf(leis.readInt());
+            size += LittleEndianConsts.INT_SIZE;
 
             // The number of elements in the array specified in the StyleEntry
             // field. This value SHOULD be zero if PenStyle does not specify PS_USERSTYLE.
             final int numStyleEntries = (int) leis.readUInt();
-            size += 2 * LittleEndianConsts.INT_SIZE;
+            size += LittleEndianConsts.INT_SIZE;
 
             assert (numStyleEntries == 0 || penStyle.getLineDash() == HwmfLineDash.USERSTYLE);
 
@@ -463,4 +473,42 @@ public class HemfMisc {
             return readPointL(leis, origin);
         }
     }
+
+    public static class EmfSetWorldTransform implements HemfRecord {
+        protected final AffineTransform xForm = new AffineTransform();
+
+        @Override
+        public HemfRecordType getEmfRecordType() {
+            return HemfRecordType.setWorldTransform;
+        }
+
+        @Override
+        public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
+            return readXForm(leis, xForm);
+        }
+    }
+
+    public static class EmfModifyWorldTransform implements HemfRecord {
+        protected final AffineTransform xForm = new AffineTransform();
+        protected int modifyWorldTransformMode;
+
+        @Override
+        public HemfRecordType getEmfRecordType() {
+            return HemfRecordType.modifyWorldTransform;
+        }
+
+        @Override
+        public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
+            // An XForm object that defines a two-dimensional linear transform in logical units.
+            // This transform is used according to the ModifyWorldTransformMode to define a new value for
+            // the world-space to page-space transform in the playback device context.
+            int size = readXForm(leis, xForm);
+
+            // A 32-bit unsigned integer that specifies how the transform specified in Xform is used.
+            // This value MUST be in the ModifyWorldTransformMode enumeration
+            modifyWorldTransformMode = (int)leis.readUInt();
+
+            return size + LittleEndianConsts.INT_SIZE;
+        }
+    }
 }
index 627bd7315530ea5996d22a2a0e8fe987df7fb8db..4f11906bbdb20a675724eae1e868a36f389625f7 100644 (file)
@@ -21,6 +21,7 @@ package org.apache.poi.hemf.record.emf;
 import java.io.IOException;
 
 import org.apache.poi.hemf.draw.HemfGraphics;
+import org.apache.poi.hwmf.record.HwmfRecord;
 import org.apache.poi.util.Internal;
 import org.apache.poi.util.LittleEndianInputStream;
 
@@ -42,5 +43,9 @@ public interface HemfRecord {
      */
     long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException;
 
-    default void draw(HemfGraphics ctx) {}
+    default void draw(HemfGraphics ctx) {
+        if (this instanceof HwmfRecord) {
+            ((HwmfRecord) this).draw(ctx);
+        }
+    }
 }
index c3e97b8e751fc8d686d23a98b93d2a1f04f38615..6c858ccc53550514805b0bb08cdf664ce925d864 100644 (file)
@@ -58,8 +58,8 @@ public enum HemfRecordType {
     scaleWindowExtEx(0x00000020, HemfWindowing.EmfScaleWindowExtEx::new),
     saveDc(0x00000021, HemfMisc.EmfSaveDc::new),
     restoreDc(0x00000022, HemfMisc.EmfRestoreDc::new),
-    setworldtransform(0x00000023, UnimplementedHemfRecord::new),
-    modifyworldtransform(0x00000024, UnimplementedHemfRecord::new),
+    setWorldTransform(0x00000023, HemfMisc.EmfSetWorldTransform::new),
+    modifyWorldTransform(0x00000024, HemfMisc.EmfModifyWorldTransform::new),
     selectObject(0x00000025, HemfDraw.EmfSelectObject::new),
     createPen(0x00000026, HemfMisc.EmfCreatePen::new),
     createBrushIndirect(0x00000027, HemfMisc.EmfCreateBrushIndirect::new),
index 40b04d716aa75daa68d170641e326d3a30409a73..3a685478237036ac3d0a4f3ef808869f08b185e2 100644 (file)
 package org.apache.poi.hemf.usermodel;
 
 
+import java.awt.Graphics2D;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Dimension2D;
+import java.awt.geom.Rectangle2D;
 import java.io.IOException;
 import java.io.InputStream;
 import java.util.ArrayList;
@@ -26,11 +30,14 @@ import java.util.List;
 import java.util.Spliterator;
 import java.util.function.Consumer;
 
+import org.apache.poi.hemf.draw.HemfGraphics;
 import org.apache.poi.hemf.record.emf.HemfHeader;
 import org.apache.poi.hemf.record.emf.HemfRecord;
 import org.apache.poi.hemf.record.emf.HemfRecordIterator;
+import org.apache.poi.util.Dimension2DDouble;
 import org.apache.poi.util.Internal;
 import org.apache.poi.util.LittleEndianInputStream;
+import org.apache.poi.util.Units;
 
 /**
  * Read-only EMF extractor.  Lots remain
@@ -74,4 +81,42 @@ public class HemfPicture implements Iterable<HemfRecord> {
     public void forEach(Consumer<? super HemfRecord> action) {
         getRecords().forEach(action);
     }
+
+    /**
+     * Return the image size in points
+     *
+     * @return the image size in points
+     */
+    public Dimension2D getSize() {
+        HemfHeader header = (HemfHeader)getRecords().get(0);
+        Rectangle2D dim = header.getFrameRectangle();
+
+        double coeff = (double)Units.EMU_PER_CENTIMETER/Units.EMU_PER_POINT/10.;
+        return new Dimension2DDouble(dim.getWidth()*coeff, dim.getHeight()*coeff);
+    }
+
+    public void draw(Graphics2D ctx, Rectangle2D graphicsBounds) {
+        HemfHeader header = (HemfHeader)getRecords().get(0);
+
+        AffineTransform at = ctx.getTransform();
+        try {
+            Rectangle2D emfBounds = header.getBoundsRectangle();
+            ctx.translate(graphicsBounds.getCenterX()-emfBounds.getCenterX(), graphicsBounds.getCenterY()-emfBounds.getCenterY());
+
+            // scale output bounds to image bounds
+            ctx.translate(emfBounds.getCenterX(), emfBounds.getCenterY());
+            ctx.scale(graphicsBounds.getWidth()/emfBounds.getWidth(), graphicsBounds.getHeight()/emfBounds.getHeight());
+            ctx.translate(-emfBounds.getCenterX(), -emfBounds.getCenterY());
+
+
+
+            HemfGraphics g = new HemfGraphics(ctx, emfBounds);
+            for (HemfRecord r : getRecords()) {
+                g.draw(r);
+            }
+        } finally {
+            ctx.setTransform(at);
+        }
+    }
+
 }
index cdbfefe71e47ab7c06d34e193a16b51ddc069635..705f02705dbfa2ed11391dd1e9d5810f6d427562 100644 (file)
@@ -20,6 +20,7 @@ package org.apache.poi.hwmf.draw;
 import java.awt.Color;
 import java.awt.Shape;
 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;
@@ -88,7 +89,7 @@ public class HwmfDrawProperties {
     }
     
     public HwmfDrawProperties(HwmfDrawProperties other) {
-        this.window = (Rectangle2D)other.window.clone();
+        this.window = (other.window == null) ? null : (Rectangle2D)other.window.clone();
         this.viewport = (other.viewport == null) ? null : (Rectangle2D)other.viewport.clone();
         this.location = (Point2D)other.location.clone();
         this.mapMode = other.mapMode;
@@ -367,4 +368,11 @@ public class HwmfDrawProperties {
     public void setTextVAlignAsian(HwmfTextVerticalAlignment textVAlignAsian) {
         this.textVAlignAsian = textVAlignAsian;
     }
+
+    /**
+     * @return the current active winding rule ({@link Path2D#WIND_EVEN_ODD} or {@link Path2D#WIND_NON_ZERO})
+     */
+    public int getWindingRule() {
+        return getPolyfillMode().awtFlag;
+    }
 }
index 1789337d2aeef0eb865beac5b66d57007e11cbf2..de348480ad3a9b5b6997c8c7c9f17432b52e6077 100644 (file)
@@ -54,9 +54,9 @@ public class HwmfGraphics {
 
     protected final List<HwmfDrawProperties> propStack = new LinkedList<>();
     protected HwmfDrawProperties prop;
+    protected final Graphics2D graphicsCtx;
 
     private static final Charset DEFAULT_CHARSET = LocaleUtil.CHARSET_1252;
-    private final Graphics2D graphicsCtx;
     private final List<HwmfObjectTableEntry> objectTable = new ArrayList<>();
     /** Bounding box from the placeable header */ 
     private final Rectangle2D bbox;
@@ -72,7 +72,6 @@ public class HwmfGraphics {
         this.graphicsCtx = graphicsCtx;
         this.bbox = (Rectangle2D)bbox.clone();
         this.initialAT = graphicsCtx.getTransform();
-        DrawFactory.getInstance(graphicsCtx).fixFonts(graphicsCtx);
     }
 
     public HwmfDrawProperties getProperties() {
@@ -178,6 +177,7 @@ public class HwmfGraphics {
         if (h == HwmfHatchStyle.HS_BDIAGONAL || h == HwmfHatchStyle.HS_DIAGCROSS) {
             g.drawLine(0, dim, dim, 0);
         }
+        // TODO: handle new HS_* enumeration values
         g.dispose();
         return new TexturePaint(bi, new Rectangle(0,0,dim,dim));
     }
@@ -248,9 +248,10 @@ public class HwmfGraphics {
      * Saves the current properties to the stack
      */
     public void saveProperties() {
-        assert(prop != null);
-        propStack.add(prop);
-        prop = new HwmfDrawProperties(prop);
+        final HwmfDrawProperties p = getProperties();
+        assert(p != null);
+        propStack.add(p);
+        prop = new HwmfDrawProperties(p);
     }
     
     /**
diff --git a/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfImageRenderer.java b/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfImageRenderer.java
new file mode 100644 (file)
index 0000000..a87db04
--- /dev/null
@@ -0,0 +1,129 @@
+/* ====================================================================
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+==================================================================== */
+
+package org.apache.poi.hwmf.draw;
+
+import java.awt.Dimension;
+import java.awt.Graphics2D;
+import java.awt.Insets;
+import java.awt.RenderingHints;
+import java.awt.geom.Rectangle2D;
+import java.awt.image.BufferedImage;
+import java.awt.image.RescaleOp;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.apache.poi.hwmf.usermodel.HwmfPicture;
+import org.apache.poi.sl.draw.DrawPictureShape;
+import org.apache.poi.sl.draw.ImageRenderer;
+import org.apache.poi.sl.usermodel.PictureData.PictureType;
+import org.apache.poi.util.Units;
+
+/**
+ * Helper class which is instantiated by {@link DrawPictureShape}
+ * via reflection
+ */
+public class HwmfImageRenderer implements ImageRenderer {
+    HwmfPicture image;
+    double alpha;
+
+    @Override
+    public boolean canRender(String contentType) {
+        return PictureType.WMF.contentType.equalsIgnoreCase(contentType);
+    }
+
+    @Override
+    public void loadImage(InputStream data, String contentType) throws IOException {
+        if (!PictureType.WMF.contentType.equals(contentType)) {
+            throw new IOException("Invalid picture type");
+        }
+        image = new HwmfPicture(data);
+    }
+
+    @Override
+    public void loadImage(byte[] data, String contentType) throws IOException {
+        if (!PictureType.WMF.contentType.equals(contentType)) {
+            throw new IOException("Invalid picture type");
+        }
+        image = new HwmfPicture(new ByteArrayInputStream(data));
+    }
+
+    @Override
+    public Dimension getDimension() {
+        int width = 0, height = 0;
+        if (image != null) {
+            Dimension dim = image.getSize();
+            width = Units.pointsToPixel(dim.getWidth());
+            // keep aspect ratio for height
+            height = Units.pointsToPixel(dim.getHeight());
+        }
+        return new Dimension(width, height);
+    }
+
+    @Override
+    public void setAlpha(double alpha) {
+        this.alpha = alpha;
+    }
+
+    @Override
+    public BufferedImage getImage() {
+        return getImage(getDimension());
+    }
+
+    @Override
+    public BufferedImage getImage(Dimension dim) {
+        if (image == null) {
+            return new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB); 
+        }
+        
+        BufferedImage bufImg = new BufferedImage((int)dim.getWidth(), (int)dim.getHeight(), 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);
+        image.draw(g, new Rectangle2D.Double(0,0,dim.getWidth(),dim.getHeight()));
+        g.dispose();
+        
+        if (alpha != 0) {
+            BufferedImage newImg = new BufferedImage((int)dim.getWidth(), (int)dim.getHeight(), BufferedImage.TYPE_INT_ARGB);
+            g = newImg.createGraphics();
+            RescaleOp op = new RescaleOp(new float[]{1.0f, 1.0f, 1.0f, (float)alpha}, new float[]{0,0,0,0}, null);
+            g.drawImage(bufImg, op, 0, 0);
+            g.dispose();
+            bufImg = newImg;
+        }
+        
+        return bufImg;
+    }
+    
+    @Override
+    public boolean drawImage(Graphics2D graphics, Rectangle2D anchor) {
+        return drawImage(graphics, anchor, null);
+    }
+
+    @Override
+    public boolean drawImage(Graphics2D graphics, Rectangle2D anchor, Insets clip) {
+        if (image == null) {
+            return false;
+        } else {
+            image.draw(graphics, anchor);
+            return true;
+        }
+    }
+}
diff --git a/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfSLImageRenderer.java b/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfSLImageRenderer.java
deleted file mode 100644 (file)
index e2601bc..0000000
+++ /dev/null
@@ -1,124 +0,0 @@
-/* ====================================================================
-   Licensed to the Apache Software Foundation (ASF) under one or more
-   contributor license agreements.  See the NOTICE file distributed with
-   this work for additional information regarding copyright ownership.
-   The ASF licenses this file to You under the Apache License, Version 2.0
-   (the "License"); you may not use this file except in compliance with
-   the License.  You may obtain a copy of the License at
-
-       http://www.apache.org/licenses/LICENSE-2.0
-
-   Unless required by applicable law or agreed to in writing, software
-   distributed under the License is distributed on an "AS IS" BASIS,
-   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-   See the License for the specific language governing permissions and
-   limitations under the License.
-==================================================================== */
-
-package org.apache.poi.hwmf.draw;
-
-import java.awt.Dimension;
-import java.awt.Graphics2D;
-import java.awt.Insets;
-import java.awt.RenderingHints;
-import java.awt.geom.Rectangle2D;
-import java.awt.image.BufferedImage;
-import java.awt.image.RescaleOp;
-import java.io.ByteArrayInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-
-import org.apache.poi.hwmf.usermodel.HwmfPicture;
-import org.apache.poi.sl.draw.DrawPictureShape;
-import org.apache.poi.sl.draw.ImageRenderer;
-import org.apache.poi.sl.usermodel.PictureData;
-import org.apache.poi.util.Units;
-
-/**
- * Helper class which is instantiated by {@link DrawPictureShape}
- * via reflection
- */
-public class HwmfSLImageRenderer implements ImageRenderer {
-    HwmfPicture image;
-    double alpha;
-    
-    @Override
-    public void loadImage(InputStream data, String contentType) throws IOException {
-        if (!PictureData.PictureType.WMF.contentType.equals(contentType)) {
-            throw new IOException("Invalid picture type");
-        }
-        image = new HwmfPicture(data);
-    }
-
-    @Override
-    public void loadImage(byte[] data, String contentType) throws IOException {
-        if (!PictureData.PictureType.WMF.contentType.equals(contentType)) {
-            throw new IOException("Invalid picture type");
-        }
-        image = new HwmfPicture(new ByteArrayInputStream(data));
-    }
-
-    @Override
-    public Dimension getDimension() {
-        int width = 0, height = 0;
-        if (image != null) {
-            Dimension dim = image.getSize();
-            width = Units.pointsToPixel(dim.getWidth());
-            // keep aspect ratio for height
-            height = Units.pointsToPixel(dim.getHeight());
-        }
-        return new Dimension(width, height);
-    }
-
-    @Override
-    public void setAlpha(double alpha) {
-        this.alpha = alpha;
-    }
-
-    @Override
-    public BufferedImage getImage() {
-        return getImage(getDimension());
-    }
-
-    @Override
-    public BufferedImage getImage(Dimension dim) {
-        if (image == null) {
-            return new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB); 
-        }
-        
-        BufferedImage bufImg = new BufferedImage((int)dim.getWidth(), (int)dim.getHeight(), 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);
-        image.draw(g, new Rectangle2D.Double(0,0,dim.getWidth(),dim.getHeight()));
-        g.dispose();
-        
-        if (alpha != 0) {
-            BufferedImage newImg = new BufferedImage((int)dim.getWidth(), (int)dim.getHeight(), BufferedImage.TYPE_INT_ARGB);
-            g = newImg.createGraphics();
-            RescaleOp op = new RescaleOp(new float[]{1.0f, 1.0f, 1.0f, (float)alpha}, new float[]{0,0,0,0}, null);
-            g.drawImage(bufImg, op, 0, 0);
-            g.dispose();
-            bufImg = newImg;
-        }
-        
-        return bufImg;
-    }
-    
-    @Override
-    public boolean drawImage(Graphics2D graphics, Rectangle2D anchor) {
-        return drawImage(graphics, anchor, null);
-    }
-
-    @Override
-    public boolean drawImage(Graphics2D graphics, Rectangle2D anchor, Insets clip) {
-        if (image == null) {
-            return false;
-        } else {
-            image.draw(graphics, anchor);
-            return true;
-        }
-    }
-}
index fb78158a9d53fae7889ea074709e6aeddeb524f8..2f052075b516506b9b88f461d1e14d75622ad083 100644 (file)
@@ -124,9 +124,9 @@ public class HwmfDraw {
 
         @Override
         public void draw(HwmfGraphics ctx) {
-            Path2D p = getShape(ctx);
+            Path2D p = (Path2D)poly.clone();
             // don't close the path
-            p.setWindingRule(getWindingRule(ctx));
+            p.setWindingRule(ctx.getProperties().getWindingRule());
             if (isFill()) {
                 ctx.fill(p);
             } else {
@@ -134,10 +134,6 @@ public class HwmfDraw {
             }
         }
 
-        protected Path2D getShape(HwmfGraphics ctx) {
-            return (Path2D)poly.clone();
-        }
-
         /**
          * @return true, if the shape should be filled
          */
@@ -303,11 +299,20 @@ public class HwmfDraw {
 
         @Override
         public void draw(HwmfGraphics ctx) {
-            if (polyList.isEmpty()) {
+            Area area = getShape(ctx);
+            if (area == null) {
                 return;
             }
             
-            int windingRule = getWindingRule(ctx);
+            if (isFill()) {
+                ctx.fill(area);
+            } else {
+                ctx.draw(area);
+            }
+        }
+
+        protected Area getShape(HwmfGraphics ctx) {
+            int windingRule = ctx.getProperties().getWindingRule();
             Area area = null;
             for (Path2D poly : polyList) {
                 Path2D p = (Path2D)poly.clone();
@@ -320,14 +325,9 @@ public class HwmfDraw {
                 }
             }
 
-            if (isFill()) {
-                ctx.fill(area);
-            } else {
-                ctx.draw(area);
-            }
+            return area;
         }
 
-
         /**
          * @return true, if the shape should be filled
          */
@@ -459,6 +459,20 @@ public class HwmfDraw {
 
         @Override
         public void draw(HwmfGraphics ctx) {
+            Shape s = getShape();
+            switch (getWmfRecordType()) {
+                default:
+                case arc:
+                    ctx.draw(s);
+                    break;
+                case chord:
+                case pie:
+                    ctx.fill(s);
+                    break;
+            }
+        }
+
+        protected Arc2D getShape() {
             double startAngle = Math.toDegrees(Math.atan2(-(startPoint.getY() - bounds.getCenterY()), startPoint.getX() - bounds.getCenterX()));
             double endAngle =   Math.toDegrees(Math.atan2(-(endPoint.getY() - bounds.getCenterY()), endPoint.getX() - bounds.getCenterX()));
             double arcAngle = (endAngle - startAngle) + (endAngle - startAngle > 0 ? 0 : 360);
@@ -472,24 +486,16 @@ public class HwmfDraw {
                 default:
                 case arc:
                     arcClosure = Arc2D.OPEN;
-                    fillShape = false;
                     break;
                 case chord:
                     arcClosure = Arc2D.CHORD;
-                    fillShape = true;
                     break;
                 case pie:
                     arcClosure = Arc2D.PIE;
-                    fillShape = true;
                     break;
             }
-            
-            Shape s = new Arc2D.Double(bounds.getX(), bounds.getY(), bounds.getWidth(), bounds.getHeight(), startAngle, arcAngle, arcClosure);
-            if (fillShape) {
-                ctx.fill(s);
-            } else {
-                ctx.draw(s);
-            }
+
+            return new Arc2D.Double(bounds.getX(), bounds.getY(), bounds.getWidth(), bounds.getHeight(), startAngle, arcAngle, arcClosure);
         }
     }
 
@@ -552,10 +558,6 @@ public class HwmfDraw {
         }
     }
     
-    private static int getWindingRule(HwmfGraphics ctx) {
-        return ctx.getProperties().getPolyfillMode().awtFlag;
-    }
-
     static int readBounds(LittleEndianInputStream leis, Rectangle2D bounds) {
         /**
          * The 16-bit signed integers that defines the corners of the bounding rectangle.
index e364d83b3b729d8f5fcea004da010af6d59aac3d..6574deaed3cc95927e9c1c99f54ff9092cec34a3 100644 (file)
@@ -32,7 +32,20 @@ public enum HwmfHatchStyle {
     /** +++++ - A horizontal and vertical cross-hatch. */
     HS_CROSS(0x0004),
     /** xxxxx - A 45-degree crosshatch. */
-    HS_DIAGCROSS(0x0005);
+    HS_DIAGCROSS(0x0005),
+    /** The hatch is not a pattern, but is a solid color. */
+    HS_SOLIDCLR(0x0006),
+    /** The hatch is not a pattern, but is a dithered color. */
+    HS_DITHEREDCLR(0x0007),
+    /** The hatch is not a pattern, but is a solid color, defined by the current text (foreground) color. */
+    HS_SOLIDTEXTCLR(0x0008),
+    /** The hatch is not a pattern, but is a dithered color, defined by the current text (foreground) color. */
+    HS_DITHEREDTEXTCLR(0x0009),
+    /** The hatch is not a pattern, but is a solid color, defined by the current background color. */
+    HS_SOLIDBKCLR(0x000A),
+    /** The hatch is not a pattern, but is a dithered color, defined by the current background color. */
+    HS_DITHEREDBKCLR(0x000B)
+    ;
 
     int flag;
     HwmfHatchStyle(int flag) {
index f0e259df4d5fe20aab3fb511560831721d4670ae..8d360d9aefae26a3d54684bd3e88cff7e8390819 100644 (file)
@@ -22,14 +22,24 @@ import static org.apache.poi.POITestCase.assertContains;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 
+import java.awt.Graphics2D;
+import java.awt.RenderingHints;
+import java.awt.geom.Dimension2D;
 import java.awt.geom.Point2D;
+import java.awt.geom.Rectangle2D;
+import java.awt.image.BufferedImage;
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
 import java.io.InputStream;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
 
+import javax.imageio.ImageIO;
+
 import org.apache.poi.POIDataSamples;
 import org.apache.poi.hemf.record.emf.HemfComment;
 import org.apache.poi.hemf.record.emf.HemfComment.EmfComment;
@@ -41,22 +51,60 @@ import org.apache.poi.hemf.record.emf.HemfRecordType;
 import org.apache.poi.hemf.record.emf.HemfText;
 import org.apache.poi.util.IOUtils;
 import org.apache.poi.util.RecordFormatException;
+import org.apache.poi.util.Units;
+import org.junit.Ignore;
 import org.junit.Test;
 
 public class HemfPictureTest {
 
-    private POIDataSamples samples = POIDataSamples.getSpreadSheetInstance();
+    private static final POIDataSamples ss_samples = POIDataSamples.getSpreadSheetInstance();
+    private static final POIDataSamples sl_samples = POIDataSamples.getSlideShowInstance();
+
+    @Test
+    @Ignore("Only for manual tests")
+    public void paint() throws IOException {
+        File f = sl_samples.getFile("wrench.emf");
+        try (FileInputStream fis = new FileInputStream(f)) {
+            HemfPicture emf = new HemfPicture(fis);
+
+            Dimension2D dim = emf.getSize();
+            int width = Units.pointsToPixel(dim.getWidth());
+            // keep aspect ratio for height
+            int height = Units.pointsToPixel(dim.getHeight());
+            double max = Math.max(width, height);
+            if (max > 1500) {
+                width *= 1500 / max;
+                height *= 1500 / max;
+            }
+
+            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);
+
+            emf.draw(g, new Rectangle2D.Double(0,0,width,height));
+
+            g.dispose();
+
+            ImageIO.write(bufImg, "PNG", new File("bla.png"));
+        }
+    }
+
+
+
 
     @Test
     public void testBasicWindows() throws Exception {
-        try (InputStream is = samples.openResourceAsStream("SimpleEMF_windows.emf")) {
+        try (InputStream is = ss_samples.openResourceAsStream("SimpleEMF_windows.emf")) {
             HemfPicture pic = new HemfPicture(is);
             HemfHeader header = pic.getHeader();
             assertEquals(27864, header.getBytes());
             assertEquals(31, header.getRecords());
             assertEquals(3, header.getHandles());
-            assertEquals(346000, header.getMicrometersX());
-            assertEquals(194000, header.getMicrometersY());
+            assertEquals(346000, header.getMicroDimension().getWidth());
+            assertEquals(194000, header.getMicroDimension().getHeight());
 
             List<HemfRecord> records = pic.getRecords();
 
@@ -66,7 +114,7 @@ public class HemfPictureTest {
 
     @Test
     public void testBasicMac() throws Exception {
-        try (InputStream is = samples.openResourceAsStream("SimpleEMF_mac.emf")) {
+        try (InputStream is = ss_samples.openResourceAsStream("SimpleEMF_mac.emf")) {
             HemfPicture pic = new HemfPicture(is);
             HemfHeader header = pic.getHeader();
 
@@ -102,7 +150,7 @@ public class HemfPictureTest {
 
     @Test
     public void testMacText() throws Exception {
-        try (InputStream is = samples.openResourceAsStream("SimpleEMF_mac.emf")) {
+        try (InputStream is = ss_samples.openResourceAsStream("SimpleEMF_mac.emf")) {
             HemfPicture pic = new HemfPicture(is);
 
             double lastY = -1;
@@ -134,7 +182,7 @@ public class HemfPictureTest {
 
     @Test
     public void testWindowsText() throws Exception {
-        try (InputStream is = samples.openResourceAsStream("SimpleEMF_windows.emf")) {
+        try (InputStream is = ss_samples.openResourceAsStream("SimpleEMF_windows.emf")) {
             HemfPicture pic = new HemfPicture(is);
             double lastY = -1;
             double lastX = -1;
@@ -173,7 +221,7 @@ public class HemfPictureTest {
 
     @Test(expected = RecordFormatException.class)
     public void testInfiniteLoopOnFile() throws Exception {
-        try (InputStream is = samples.openResourceAsStream("61294.emf")) {
+        try (InputStream is = ss_samples.openResourceAsStream("61294.emf")) {
             HemfPicture pic = new HemfPicture(is);
             for (HemfRecord record : pic) {
 
@@ -183,7 +231,7 @@ public class HemfPictureTest {
 
     @Test(expected = RecordFormatException.class)
     public void testInfiniteLoopOnByteArray() throws Exception {
-        try (InputStream is = samples.openResourceAsStream("61294.emf")) {
+        try (InputStream is = ss_samples.openResourceAsStream("61294.emf")) {
             ByteArrayOutputStream bos = new ByteArrayOutputStream();
             IOUtils.copy(is, bos);
             is.close();
index 72eee287c2fc505490db3ce7cdc40ff810e7e094..d071f2dbdc949756fab75a5666b8603f0bbfb0c0 100644 (file)
@@ -210,7 +210,6 @@ public final class TestPicture {
                 } else {
                     BufferedImage img = new BufferedImage(pg.width, pg.height, BufferedImage.TYPE_INT_ARGB);
                     Graphics2D graphics = img.createGraphics();
-                    DrawFactory.getInstance(graphics).fixFonts(graphics);
                     slide.draw(graphics);
                     graphics.setColor(Color.BLACK);
                     graphics.setStroke(new BasicStroke(1));