]> source.dussan.org Git - poi.git/commitdiff
Regression fixes for H/XSLF and HWMF
authorAndreas Beeker <kiwiwings@apache.org>
Wed, 24 Feb 2016 22:43:51 +0000 (22:43 +0000)
committerAndreas Beeker <kiwiwings@apache.org>
Wed, 24 Feb 2016 22:43:51 +0000 (22:43 +0000)
see http://apache-poi.1045710.n5.nabble.com/3-14-beta-2-3-14-final-tt5721829.html

git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1732236 13f79535-47bb-0310-9956-ffa450edef68

59 files changed:
src/integrationtest/org/apache/poi/stress/SlideShowHandler.java
src/java/org/apache/poi/ddf/EscherArrayProperty.java
src/java/org/apache/poi/sl/draw/DrawBackground.java
src/java/org/apache/poi/sl/draw/DrawFactory.java
src/java/org/apache/poi/sl/draw/DrawPaint.java
src/java/org/apache/poi/sl/draw/DrawPictureShape.java
src/java/org/apache/poi/sl/draw/DrawShape.java
src/java/org/apache/poi/sl/draw/DrawSimpleShape.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/PathGradientPaint.java
src/java/org/apache/poi/sl/draw/SLGraphics.java
src/java/org/apache/poi/sl/draw/geom/ArcToCommand.java
src/java/org/apache/poi/sl/draw/geom/ClosePathCommand.java
src/java/org/apache/poi/sl/draw/geom/CurveToCommand.java
src/java/org/apache/poi/sl/draw/geom/LineToCommand.java
src/java/org/apache/poi/sl/draw/geom/MoveToCommand.java
src/java/org/apache/poi/sl/draw/geom/Path.java
src/java/org/apache/poi/sl/draw/geom/PathCommand.java
src/java/org/apache/poi/sl/draw/geom/QuadToCommand.java
src/java/org/apache/poi/sl/usermodel/FreeformShape.java
src/java/org/apache/poi/sl/usermodel/PlaceableShape.java
src/java/org/apache/poi/sl/usermodel/Shape.java
src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFFreeformShape.java
src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFHyperlink.java
src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFTextRun.java
src/ooxml/java/org/apache/poi/xslf/util/PPTX2PNG.java
src/ooxml/testcases/org/apache/poi/xslf/usermodel/TestPPTX2PNG.java
src/ooxml/testcases/org/apache/poi/xslf/usermodel/TestXSLFFreeformShape.java
src/scratchpad/src/org/apache/poi/hslf/model/PPGraphics2D.java
src/scratchpad/src/org/apache/poi/hslf/record/InteractiveInfo.java
src/scratchpad/src/org/apache/poi/hslf/record/Record.java
src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFFill.java
src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFFreeformShape.java
src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFHyperlink.java
src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFPictureShape.java
src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFShape.java
src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFShapeFactory.java
src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFSimpleShape.java
src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFTextParagraph.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/record/HwmfBinaryRasterOp.java [new file with mode: 0644]
src/scratchpad/src/org/apache/poi/hwmf/record/HwmfBitmapDib.java
src/scratchpad/src/org/apache/poi/hwmf/record/HwmfDraw.java
src/scratchpad/src/org/apache/poi/hwmf/record/HwmfEscape.java
src/scratchpad/src/org/apache/poi/hwmf/record/HwmfFill.java
src/scratchpad/src/org/apache/poi/hwmf/record/HwmfFont.java
src/scratchpad/src/org/apache/poi/hwmf/record/HwmfMapMode.java
src/scratchpad/src/org/apache/poi/hwmf/record/HwmfMisc.java
src/scratchpad/src/org/apache/poi/hwmf/record/HwmfPalette.java
src/scratchpad/src/org/apache/poi/hwmf/record/HwmfTernaryRasterOp.java
src/scratchpad/src/org/apache/poi/hwmf/record/HwmfText.java
src/scratchpad/src/org/apache/poi/hwmf/record/HwmfWindowing.java
src/scratchpad/src/org/apache/poi/hwmf/usermodel/HwmfPicture.java
src/scratchpad/testcases/org/apache/poi/hslf/model/TestFreeform.java
src/scratchpad/testcases/org/apache/poi/hslf/usermodel/TestPicture.java
src/scratchpad/testcases/org/apache/poi/hwmf/TestHwmfParsing.java
src/testcases/org/apache/poi/sl/draw/geom/TestPresetGeometries.java

index 9d6d1b3dcdaaa52e76ab62675fe967acdb0ec987..e798b353397f6986762d35197c9a7f85d2c80b1b 100644 (file)
@@ -26,10 +26,8 @@ import java.awt.image.BufferedImage;
 import java.io.ByteArrayInputStream;\r
 import java.io.ByteArrayOutputStream;\r
 import java.io.IOException;\r
-import java.util.HashMap;\r
-import java.util.Map;\r
 \r
-import org.apache.poi.sl.draw.Drawable;\r
+import org.apache.poi.sl.draw.DrawFactory;\r
 import org.apache.poi.sl.usermodel.PictureData;\r
 import org.apache.poi.sl.usermodel.Shape;\r
 import org.apache.poi.sl.usermodel.ShapeContainer;\r
@@ -39,7 +37,6 @@ import org.apache.poi.sl.usermodel.SlideShowFactory;
 import org.apache.poi.sl.usermodel.TextParagraph;\r
 import org.apache.poi.sl.usermodel.TextRun;\r
 import org.apache.poi.sl.usermodel.TextShape;\r
-import org.apache.poi.util.JvmBugs;\r
 \r
 public abstract class SlideShowHandler extends POIFSFileHandler {\r
     public void handleSlideShow(SlideShow<?,?> ss) throws IOException {\r
@@ -55,10 +52,12 @@ public abstract class SlideShowHandler extends POIFSFileHandler {
 \r
         // read in the writen file\r
         SlideShow<?,?> read = SlideShowFactory.create(new ByteArrayInputStream(out.toByteArray()));\r
-        assertNotNull(read);\r
-        \r
-        readContent(read);\r
-        \r
+        try {\r
+            assertNotNull(read);\r
+            readContent(read);\r
+        } finally {\r
+            read.close();\r
+        }\r
     }\r
 \r
     private ByteArrayOutputStream writeToArray(SlideShow<?,?> ss) throws IOException {\r
@@ -109,7 +108,7 @@ public abstract class SlideShowHandler extends POIFSFileHandler {
         for (Slide<?,?> s : ss.getSlides()) {\r
             BufferedImage img = new BufferedImage(pgsize.width, pgsize.height, BufferedImage.TYPE_INT_ARGB);\r
             Graphics2D graphics = img.createGraphics();\r
-            fixFonts(graphics);\r
+            DrawFactory.getInstance(graphics).fixFonts(graphics);\r
 \r
             // default rendering options\r
             graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);\r
@@ -124,15 +123,4 @@ public abstract class SlideShowHandler extends POIFSFileHandler {
             img.flush();\r
         }\r
     }\r
-\r
-    @SuppressWarnings("unchecked")\r
-    private static void fixFonts(Graphics2D graphics) {\r
-        if (!JvmBugs.hasLineBreakMeasurerBug()) return;\r
-        Map<String,String> fontMap = (Map<String,String>)graphics.getRenderingHint(Drawable.FONT_MAP);\r
-        if (fontMap == null) fontMap = new HashMap<String,String>();\r
-        fontMap.put("Calibri", "Lucida Sans");\r
-        fontMap.put("Cambria", "Lucida Bright");\r
-        graphics.setRenderingHint(Drawable.FONT_MAP, fontMap);        \r
-    }\r
-\r
 }
\ No newline at end of file
index 880057e0ff3382f05356c5016e3cd39cbd088d18..bec5f24d0013437a7271f61ad5b145d60d7687ea 100644 (file)
@@ -43,7 +43,7 @@ public final class EscherArrayProperty extends EscherComplexProperty implements
     private boolean sizeIncludesHeaderSize = true;
 
     /**
-     * When reading a property from data stream remeber if the complex part is empty and set this flag.
+     * When reading a property from data stream remember if the complex part is empty and set this flag.
      */
     private boolean emptyComplexPart = false;
 
@@ -65,10 +65,7 @@ public final class EscherArrayProperty extends EscherComplexProperty implements
     }
 
     public int getNumberOfElementsInArray() {
-        if (emptyComplexPart){
-            return 0;
-        }
-        return LittleEndian.getUShort(_complexData, 0);
+        return (emptyComplexPart) ? 0 : LittleEndian.getUShort(_complexData, 0);
     }
 
     public void setNumberOfElementsInArray(int numberOfElements) {
@@ -82,7 +79,7 @@ public final class EscherArrayProperty extends EscherComplexProperty implements
     }
 
     public int getNumberOfElementsInMemory() {
-        return LittleEndian.getUShort(_complexData, 2);
+        return (emptyComplexPart) ? 0 : LittleEndian.getUShort(_complexData, 2);
     }
 
     public void setNumberOfElementsInMemory(int numberOfElements) {
@@ -96,7 +93,7 @@ public final class EscherArrayProperty extends EscherComplexProperty implements
     }
 
     public short getSizeOfElements() {
-        return LittleEndian.getShort( _complexData, 4 );
+        return (emptyComplexPart) ? 0 : LittleEndian.getShort( _complexData, 4 );
     }
 
     public void setSizeOfElements(int sizeOfElements) {
index b09bc985897cc39f61af0f4a2cc5d303f1c20855..aa12b470cb3184d0bd95918428ca27613d32f248 100644 (file)
@@ -25,6 +25,7 @@ import java.awt.geom.Rectangle2D;
 import org.apache.poi.sl.usermodel.Background;\r
 import org.apache.poi.sl.usermodel.PlaceableShape;\r
 import org.apache.poi.sl.usermodel.ShapeContainer;\r
+import org.apache.poi.sl.usermodel.Sheet;\r
 \r
 \r
 public class DrawBackground extends DrawShape {\r
@@ -47,6 +48,7 @@ public class DrawBackground extends DrawShape {
             public void setFlipVertical(boolean flip) {}\r
             public boolean getFlipHorizontal() { return false; }\r
             public boolean getFlipVertical() { return false; }\r
+            public Sheet<?,?> getSheet() { return shape.getSheet(); }\r
         };\r
         \r
         DrawFactory drawFact = DrawFactory.getInstance(graphics);\r
@@ -55,6 +57,7 @@ public class DrawBackground extends DrawShape {
         Rectangle2D anchor2 = getAnchor(graphics, anchor);\r
         \r
         if(fill != null) {\r
+            graphics.setRenderingHint(Drawable.GRADIENT_SHAPE, anchor);\r
             graphics.setPaint(fill);\r
             graphics.fill(anchor2);\r
         }\r
index 971feae185cc253648d0681c3ba88afc72d61a79..a7edeafbb074742ffabc1476a7bc8773a46e7f4d 100644 (file)
 \r
 package org.apache.poi.sl.draw;\r
 \r
-import static org.apache.poi.sl.draw.Drawable.DRAW_FACTORY;\r
-\r
 import java.awt.Graphics2D;\r
 import java.awt.font.TextLayout;\r
 import java.text.AttributedString;\r
+import java.util.HashMap;\r
+import java.util.Map;\r
 \r
 import org.apache.poi.sl.usermodel.Background;\r
 import org.apache.poi.sl.usermodel.ConnectorShape;\r
@@ -38,6 +38,7 @@ import org.apache.poi.sl.usermodel.TableShape;
 import org.apache.poi.sl.usermodel.TextBox;\r
 import org.apache.poi.sl.usermodel.TextParagraph;\r
 import org.apache.poi.sl.usermodel.TextShape;\r
+import org.apache.poi.util.JvmBugs;\r
 \r
 public class DrawFactory {\r
     protected static final ThreadLocal<DrawFactory> defaultFactory = new ThreadLocal<DrawFactory>();\r
@@ -58,7 +59,7 @@ public class DrawFactory {
         DrawFactory factory = null;\r
         boolean isHint = false;\r
         if (graphics != null) {\r
-            factory = (DrawFactory)graphics.getRenderingHint(DRAW_FACTORY);\r
+            factory = (DrawFactory)graphics.getRenderingHint(Drawable.DRAW_FACTORY);\r
             isHint = (factory != null);\r
         }\r
         // secondly try the thread local default\r
@@ -70,7 +71,7 @@ public class DrawFactory {
             factory = new DrawFactory();\r
         }\r
         if (graphics != null && !isHint) {\r
-            graphics.setRenderingHint(DRAW_FACTORY, factory);\r
+            graphics.setRenderingHint(Drawable.DRAW_FACTORY, factory);\r
         }\r
         return factory;\r
     }\r
@@ -166,4 +167,29 @@ public class DrawFactory {
     public DrawPaint getPaint(PlaceableShape<?,?> shape) {\r
         return new DrawPaint(shape);\r
     }\r
-}\r
+\r
+\r
+    /**\r
+     * Replace font families for Windows JVM 6, which contains a font rendering error.\r
+     * This is likely to be removed, when POI upgrades to JDK 7\r
+     *\r
+     * @param graphics the graphics context which will contain the font mapping\r
+     */\r
+    public void fixFonts(Graphics2D graphics) {\r
+        if (!JvmBugs.hasLineBreakMeasurerBug()) return;\r
+        @SuppressWarnings("unchecked")\r
+        Map<String,String> fontMap = (Map<String,String>)graphics.getRenderingHint(Drawable.FONT_MAP);\r
+        if (fontMap == null) {\r
+            fontMap = new HashMap<String,String>();\r
+            graphics.setRenderingHint(Drawable.FONT_MAP, fontMap);\r
+        }\r
+        \r
+        String fonts[][] = { { "Calibri", "Lucida Sans" }, { "Cambria", "Lucida Bright" } };\r
+\r
+        for (String f[] : fonts) {\r
+            if (!fontMap.containsKey(f[0])) {\r
+                fontMap.put(f[0], f[1]);\r
+            }\r
+        }\r
+    }\r
+}
\ No newline at end of file
index 8d5648f8527795c91216fb3fcaa443648c90f754..d4da4313666c2cb763514877ecba9aeaaa7c092a 100644 (file)
@@ -134,8 +134,11 @@ public class DrawPaint {
         ImageRenderer renderer = DrawPictureShape.getImageRenderer(graphics, fill.getContentType());\r
 \r
         try {\r
-            renderer.loadImage(is, fill.getContentType());\r
-            is.close();\r
+            try {\r
+                renderer.loadImage(is, fill.getContentType());\r
+            } finally {\r
+                is.close();\r
+            }\r
         } catch (IOException e) {\r
             LOG.log(POILogger.ERROR, "Can't load image data - using transparent color", e);\r
             return null;\r
@@ -274,15 +277,35 @@ public class DrawPaint {
 \r
         Point2D p2 = new Point2D.Double(anchor.getX() + anchor.getWidth(), anchor.getY() + anchor.getHeight() / 2);\r
         p2 = at.transform(p2, null);\r
-\r
+        \r
         snapToAnchor(p1, anchor);\r
         snapToAnchor(p2, anchor);\r
 \r
+        if (p1.equals(p2)) {\r
+            // gradient paint on the same point throws an exception ... and doesn't make sense\r
+            return null;\r
+        }\r
+\r
         float[] fractions = fill.getGradientFractions();\r
         Color[] colors = new Color[fractions.length];\r
         \r
         int i = 0;\r
         for (ColorStyle fc : fill.getGradientColors()) {\r
+            if (fc == null) {\r
+                // get color of background\r
+                fc = new ColorStyle() {\r
+                    public int getTint() { return -1; }\r
+                    public int getShade() { return -1; }\r
+                    public int getSatOff() { return -1; }\r
+                    public int getSatMod() { return -1; }\r
+                    public int getLumOff() { return -1; }\r
+                    public int getLumMod() { return -1; }\r
+                    public int getHueOff() { return -1; }\r
+                    public int getHueMod() { return -1; }\r
+                    public Color getColor() { return Color.white; }\r
+                    public int getAlpha() { return 0; }\r
+                };\r
+            }\r
             colors[i++] = applyColorTransform(fc);\r
         }\r
 \r
index beadc1f98833968cb448ffaff5d173b52e5e538c..bdc5ab68c4f4e3b551d04570694760604e7fbda8 100644 (file)
@@ -52,8 +52,7 @@ public class DrawPictureShape extends DrawSimpleShape {
             renderer.loadImage(data.getData(), data.getContentType());\r
             renderer.drawImage(graphics, anchor, insets);\r
         } catch (IOException e) {\r
-            // TODO: draw specific runtime exception?\r
-            throw new RuntimeException(e);\r
+            LOG.log(POILogger.ERROR, "image can't be loaded/rendered.", e);\r
         }\r
     }    \r
 \r
index d036e5273397a72b12c7a54da2963410042ef358..0c465bc52a6f4d58368813e269adfb4e6fab6d65 100644 (file)
@@ -90,17 +90,23 @@ public class DrawShape implements Drawable {
 \r
                 Rectangle2D anchor2 = txs.createTransformedShape(ps.getAnchor()).getBounds2D();\r
 \r
-                scaleX = anchor.getWidth() == 0. ? 1.0 : anchor.getWidth() / anchor2.getWidth();\r
-                scaleY = anchor.getHeight() == 0. ? 1.0 : anchor.getHeight() / anchor2.getHeight();\r
+                scaleX = safeScale(anchor.getWidth(), anchor2.getWidth());\r
+                scaleY = safeScale(anchor.getHeight(), anchor2.getHeight());\r
             } else {\r
                 quadrant = 0;\r
             }\r
 \r
             // transformation is applied reversed ...\r
             graphics.translate(centerX, centerY);\r
-            graphics.rotate(Math.toRadians(rotation-quadrant*90.));\r
+            double rot = Math.toRadians(rotation-quadrant*90.);\r
+            if (rot != 0) {\r
+                graphics.rotate(rot);\r
+            }\r
             graphics.scale(scaleX, scaleY);\r
-            graphics.rotate(Math.toRadians(quadrant*90));\r
+            rot = Math.toRadians(quadrant*90);\r
+            if (rot != 0) {\r
+                graphics.rotate(rot);\r
+            }\r
             graphics.translate(-centerX, -centerY);\r
         }\r
 \r
@@ -119,6 +125,12 @@ public class DrawShape implements Drawable {
         }\r
     }\r
 \r
+    private static double safeScale(double dim1, double dim2) {\r
+        if (dim1 == 0.) {\r
+            return 1;\r
+        }\r
+        return (dim2 == 0.) ? 1 : dim1/dim2;\r
+    }\r
 \r
     public void draw(Graphics2D graphics) {\r
     }\r
index aa03f8a53bd728e657d40c1b84a996afc27d4b09..720699666033bde68ecfb72305bfb9e75aa25535 100644 (file)
@@ -23,7 +23,7 @@ import java.awt.Graphics2D;
 import java.awt.Paint;\r
 import java.awt.geom.AffineTransform;\r
 import java.awt.geom.Ellipse2D;\r
-import java.awt.geom.GeneralPath;\r
+import java.awt.geom.Path2D;\r
 import java.awt.geom.Rectangle2D;\r
 import java.io.IOException;\r
 import java.io.InputStream;\r
@@ -179,20 +179,20 @@ public class DrawSimpleShape extends DrawShape {
             case STEALTH:\r
             case ARROW:\r
                 p = new Path(false, true);\r
-                GeneralPath arrow = new GeneralPath();\r
-                arrow.moveTo((float) (-lineWidth * scaleX), (float) (-lineWidth * scaleY / 2));\r
+                Path2D.Double arrow = new Path2D.Double();\r
+                arrow.moveTo((-lineWidth * scaleX), (-lineWidth * scaleY / 2));\r
                 arrow.lineTo(0, 0);\r
-                arrow.lineTo((float) (-lineWidth * scaleX), (float) (lineWidth * scaleY / 2));\r
+                arrow.lineTo((-lineWidth * scaleX), (lineWidth * scaleY / 2));\r
                 tailShape = arrow;\r
                 at.translate(x2, y2);\r
                 at.rotate(alpha);\r
                 break;\r
             case TRIANGLE:\r
                 p = new Path();\r
-                GeneralPath triangle = new GeneralPath();\r
-                triangle.moveTo((float) (-lineWidth * scaleX), (float) (-lineWidth * scaleY / 2));\r
+                Path2D.Double triangle = new Path2D.Double();\r
+                triangle.moveTo((-lineWidth * scaleX), (-lineWidth * scaleY / 2));\r
                 triangle.lineTo(0, 0);\r
-                triangle.lineTo((float) (-lineWidth * scaleX), (float) (lineWidth * scaleY / 2));\r
+                triangle.lineTo((-lineWidth * scaleX), (lineWidth * scaleY / 2));\r
                 triangle.closePath();\r
                 tailShape = triangle;\r
                 at.translate(x2, y2);\r
@@ -252,20 +252,20 @@ public class DrawSimpleShape extends DrawShape {
             case STEALTH:\r
             case ARROW:\r
                 p = new Path(false, true);\r
-                GeneralPath arrow = new GeneralPath();\r
-                arrow.moveTo((float) (lineWidth * scaleX), (float) (-lineWidth * scaleY / 2));\r
+                Path2D.Double arrow = new Path2D.Double();\r
+                arrow.moveTo((lineWidth * scaleX), (-lineWidth * scaleY / 2));\r
                 arrow.lineTo(0, 0);\r
-                arrow.lineTo((float) (lineWidth * scaleX), (float) (lineWidth * scaleY / 2));\r
+                arrow.lineTo((lineWidth * scaleX), (lineWidth * scaleY / 2));\r
                 headShape = arrow;\r
                 at.translate(x1, y1);\r
                 at.rotate(alpha);\r
                 break;\r
             case TRIANGLE:\r
                 p = new Path();\r
-                GeneralPath triangle = new GeneralPath();\r
-                triangle.moveTo((float) (lineWidth * scaleX), (float) (-lineWidth * scaleY / 2));\r
+                Path2D.Double triangle = new Path2D.Double();\r
+                triangle.moveTo((lineWidth * scaleX), (-lineWidth * scaleY / 2));\r
                 triangle.lineTo(0, 0);\r
-                triangle.lineTo((float) (lineWidth * scaleX), (float) (lineWidth * scaleY / 2));\r
+                triangle.lineTo((lineWidth * scaleX), (lineWidth * scaleY / 2));\r
                 triangle.closePath();\r
                 headShape = triangle;\r
                 at.translate(x1, y1);\r
index e8a7b0b216dcfc76ccf843658164042561b26151..7b28bde58246bd159aa07faa8e043ee574d722ee 100644 (file)
@@ -38,6 +38,7 @@ import org.apache.poi.sl.usermodel.Insets2D;
 import org.apache.poi.sl.usermodel.PaintStyle;\r
 import org.apache.poi.sl.usermodel.PlaceableShape;\r
 import org.apache.poi.sl.usermodel.ShapeContainer;\r
+import org.apache.poi.sl.usermodel.Sheet;\r
 import org.apache.poi.sl.usermodel.TextParagraph;\r
 import org.apache.poi.sl.usermodel.TextParagraph.BulletStyle;\r
 import org.apache.poi.sl.usermodel.TextParagraph.TextAlign;\r
@@ -465,6 +466,7 @@ public class DrawTextParagraph implements Drawable {
             public void setFlipVertical(boolean flip) {}\r
             public boolean getFlipHorizontal() { return false; }\r
             public boolean getFlipVertical() { return false; }\r
+            public Sheet<?,?> getSheet() { return paragraph.getParentShape().getSheet(); }\r
         };\r
         return ps;\r
     }\r
@@ -530,7 +532,7 @@ public class DrawTextParagraph implements Drawable {
                 attList.add(new AttributedStringData(TextAttribute.SUPERSCRIPT, TextAttribute.SUPERSCRIPT_SUPER, beginIndex, endIndex));\r
             }\r
             \r
-            Hyperlink hl = run.getHyperlink();\r
+            Hyperlink<?,?> hl = run.getHyperlink();\r
             if (hl != null) {\r
                 attList.add(new AttributedStringData(HYPERLINK_HREF, hl.getAddress(), beginIndex, endIndex));\r
                 attList.add(new AttributedStringData(HYPERLINK_LABEL, hl.getLabel(), beginIndex, endIndex));\r
index 06b57dab264472a004c387da821bbdd55c10e235..648dd48cec0fc2acc36bc0fbc7160b45d06b7d11 100644 (file)
@@ -21,11 +21,15 @@ import java.awt.Graphics2D;
 import java.awt.geom.AffineTransform;\r
 import java.awt.geom.Rectangle2D;\r
 import java.awt.image.BufferedImage;\r
-import java.util.*;\r
+import java.util.Iterator;\r
 \r
-import org.apache.poi.sl.usermodel.*;\r
+import org.apache.poi.sl.usermodel.Insets2D;\r
+import org.apache.poi.sl.usermodel.PlaceableShape;\r
+import org.apache.poi.sl.usermodel.ShapeContainer;\r
+import org.apache.poi.sl.usermodel.TextParagraph;\r
 import org.apache.poi.sl.usermodel.TextParagraph.BulletStyle;\r
-import org.apache.poi.util.JvmBugs;\r
+import org.apache.poi.sl.usermodel.TextRun;\r
+import org.apache.poi.sl.usermodel.TextShape;\r
 \r
 public class DrawTextShape extends DrawSimpleShape {\r
 \r
@@ -35,7 +39,7 @@ public class DrawTextShape extends DrawSimpleShape {
 \r
     @Override\r
     public void drawContent(Graphics2D graphics) {\r
-        fixFonts(graphics);\r
+        DrawFactory.getInstance(graphics).fixFonts(graphics);\r
         \r
         TextShape<?,?> s = getShape();\r
         \r
@@ -71,7 +75,7 @@ public class DrawTextShape extends DrawSimpleShape {
         }\r
         \r
         Double textRot = s.getTextRotation();\r
-        if (textRot != null) {\r
+        if (textRot != null && textRot != 0) {\r
             graphics.translate(anchor.getCenterX(), anchor.getCenterY());\r
             graphics.rotate(Math.toRadians(textRot));\r
             graphics.translate(-anchor.getCenterX(), -anchor.getCenterY());\r
@@ -110,8 +114,9 @@ public class DrawTextShape extends DrawSimpleShape {
 \r
         double y0 = y;\r
         //noinspection RedundantCast\r
-        Iterator<? extends TextParagraph<?,?,? extends TextRun>> paragraphs = (Iterator<? extends TextParagraph<?, ?, ? extends TextRun>>) getShape().iterator();\r
-\r
+        Iterator<? extends TextParagraph<?,?,? extends TextRun>> paragraphs =\r
+            (Iterator<? extends TextParagraph<?,?,? extends TextRun>>) getShape().iterator();\r
+        \r
         boolean isFirstLine = true;\r
         for (int autoNbrIdx=0; paragraphs.hasNext(); autoNbrIdx++){\r
             TextParagraph<?,?,? extends TextRun> p = paragraphs.next();\r
@@ -170,23 +175,10 @@ public class DrawTextShape extends DrawSimpleShape {
         // dry-run in a 1x1 image and return the vertical advance\r
         BufferedImage img = new BufferedImage(1, 1, BufferedImage.TYPE_INT_RGB);\r
         Graphics2D graphics = img.createGraphics();\r
-        fixFonts(graphics);\r
+        DrawFactory.getInstance(graphics).fixFonts(graphics);\r
         return drawParagraphs(graphics, 0, 0);\r
     }\r
     \r
-    @SuppressWarnings("unchecked")\r
-    private static void fixFonts(Graphics2D graphics) {\r
-        if (!JvmBugs.hasLineBreakMeasurerBug()) return;\r
-        Map<String,String> fontMap = (Map<String,String>)graphics.getRenderingHint(Drawable.FONT_MAP);\r
-        if (fontMap == null) {\r
-            fontMap = new HashMap<String,String>();\r
-            graphics.setRenderingHint(Drawable.FONT_MAP, fontMap);\r
-        }\r
-        \r
-        if (!fontMap.containsKey("Calibri")) fontMap.put("Calibri", "Lucida Sans");\r
-        if (!fontMap.containsKey("Cambria")) fontMap.put("Cambria", "Lucida Bright");\r
-    }\r
-\r
     @Override\r
     protected TextShape<?,?> getShape() {\r
         return (TextShape<?,?>)shape;\r
index c5ad799f4c3cc44ef49be79f3eb3f42219655ce2..292ee3a7e70f4d436dd6f7c31196434da785a74f 100644 (file)
@@ -87,7 +87,7 @@ class PathGradientPaint implements Paint {
         ) {\r
             shape = (Shape)hints.get(Drawable.GRADIENT_SHAPE);\r
             if (shape == null) {\r
-                throw new IllegalPathStateException("PathGradientPaint needs a shape to be set via the rendering hint PathGradientPaint.GRADIANT_SHAPE.");\r
+                throw new IllegalPathStateException("PathGradientPaint needs a shape to be set via the rendering hint Drawable.GRADIANT_SHAPE.");\r
             }\r
 \r
             this.deviceBounds = deviceBounds;\r
@@ -137,14 +137,14 @@ class PathGradientPaint implements Paint {
             return childRaster;\r
         }\r
 \r
-        protected int getGradientSteps(Shape shape) {\r
-            Rectangle rect = shape.getBounds();\r
+        protected int getGradientSteps(Shape gradientShape) {\r
+            Rectangle rect = gradientShape.getBounds();\r
             int lower = 1;\r
             int upper = (int)(Math.max(rect.getWidth(),rect.getHeight())/2.0);\r
             while (lower < upper-1) {\r
                 int mid = lower + (upper - lower) / 2;\r
                 BasicStroke bs = new BasicStroke(mid, capStyle, joinStyle);\r
-                Area area = new Area(bs.createStrokedShape(shape));\r
+                Area area = new Area(bs.createStrokedShape(gradientShape));\r
                 if (area.isSingular()) {\r
                     upper = mid;\r
                 } else {\r
index 2a6d884ca15c880cfb4d6af234a7467ae99567d9..b4bc14ae965d6a3b2d59a05ba0912844d66a16b9 100644 (file)
@@ -42,6 +42,7 @@ import java.awt.geom.Arc2D;
 import java.awt.geom.Ellipse2D;\r
 import java.awt.geom.GeneralPath;\r
 import java.awt.geom.Line2D;\r
+import java.awt.geom.Path2D;\r
 import java.awt.geom.RoundRectangle2D;\r
 import java.awt.image.BufferedImage;\r
 import java.awt.image.BufferedImageOp;\r
@@ -243,7 +244,7 @@ public final class SLGraphics extends Graphics2D implements Cloneable {
      * @see #setComposite\r
      */\r
     public void draw(Shape shape){\r
-        GeneralPath path = new GeneralPath(_transform.createTransformedShape(shape));\r
+        Path2D.Double path = new Path2D.Double(_transform.createTransformedShape(shape));\r
         FreeformShape<?,?> p = _group.createFreeform();\r
         p.setPath(path);\r
         p.setFillColor(null);\r
@@ -339,7 +340,7 @@ public final class SLGraphics extends Graphics2D implements Cloneable {
      * @see #setClip\r
      */\r
     public void fill(Shape shape){\r
-        GeneralPath path = new GeneralPath(_transform.createTransformedShape(shape));\r
+        Path2D.Double path = new Path2D.Double(_transform.createTransformedShape(shape));\r
         FreeformShape<?,?> p = _group.createFreeform();\r
         p.setPath(path);\r
         applyPaint(p);\r
@@ -450,7 +451,7 @@ public final class SLGraphics extends Graphics2D implements Cloneable {
      */\r
     public void drawRoundRect(int x, int y, int width, int height,\r
                               int arcWidth, int arcHeight){\r
-        RoundRectangle2D rect = new RoundRectangle2D.Float(x, y, width, height, arcWidth, arcHeight);\r
+        RoundRectangle2D rect = new RoundRectangle2D.Double(x, y, width, height, arcWidth, arcHeight);\r
         draw(rect);\r
      }\r
 \r
@@ -481,7 +482,7 @@ public final class SLGraphics extends Graphics2D implements Cloneable {
      * @see         java.awt.Graphics#drawOval\r
      */\r
     public void fillOval(int x, int y, int width, int height){\r
-        Ellipse2D oval = new Ellipse2D.Float(x, y, width, height);\r
+        Ellipse2D oval = new Ellipse2D.Double(x, y, width, height);\r
         fill(oval);\r
     }\r
 \r
@@ -504,7 +505,7 @@ public final class SLGraphics extends Graphics2D implements Cloneable {
     public void fillRoundRect(int x, int y, int width, int height,\r
                               int arcWidth, int arcHeight){\r
 \r
-        RoundRectangle2D rect = new RoundRectangle2D.Float(x, y, width, height, arcWidth, arcHeight);\r
+        RoundRectangle2D rect = new RoundRectangle2D.Double(x, y, width, height, arcWidth, arcHeight);\r
         fill(rect);\r
     }\r
 \r
@@ -544,9 +545,8 @@ public final class SLGraphics extends Graphics2D implements Cloneable {
      *                    relative to the start angle.\r
      * @see         java.awt.Graphics#drawArc\r
      */\r
-    public void fillArc(int x, int y, int width, int height,\r
-                        int startAngle, int arcAngle){\r
-        Arc2D arc = new Arc2D.Float(x, y, width, height, startAngle, arcAngle, Arc2D.PIE);\r
+    public void fillArc(int x, int y, int width, int height, int startAngle, int arcAngle){\r
+        Arc2D arc = new Arc2D.Double(x, y, width, height, startAngle, arcAngle, Arc2D.PIE);\r
         fill(arc);\r
     }\r
 \r
@@ -587,9 +587,8 @@ public final class SLGraphics extends Graphics2D implements Cloneable {
      *                    relative to the start angle.\r
      * @see         java.awt.Graphics#fillArc\r
      */\r
-    public void drawArc(int x, int y, int width, int height,\r
-                        int startAngle, int arcAngle) {\r
-        Arc2D arc = new Arc2D.Float(x, y, width, height, startAngle, arcAngle, Arc2D.OPEN);\r
+    public void drawArc(int x, int y, int width, int height, int startAngle, int arcAngle) {\r
+        Arc2D arc = new Arc2D.Double(x, y, width, height, startAngle, arcAngle, Arc2D.OPEN);\r
         draw(arc);\r
     }\r
 \r
@@ -636,7 +635,7 @@ public final class SLGraphics extends Graphics2D implements Cloneable {
      * @see         java.awt.Graphics#fillOval\r
      */\r
     public void drawOval(int x, int y, int width, int height){\r
-        Ellipse2D oval = new Ellipse2D.Float(x, y, width, height);\r
+        Ellipse2D oval = new Ellipse2D.Double(x, y, width, height);\r
         draw(oval);\r
     }\r
 \r
@@ -932,7 +931,7 @@ public final class SLGraphics extends Graphics2D implements Cloneable {
      * @param   y2  the second point's <i>y</i> coordinate.\r
      */\r
     public void drawLine(int x1, int y1, int x2, int y2){\r
-        Line2D line = new Line2D.Float(x1, y1, x2, y2);\r
+        Line2D line = new Line2D.Double(x1, y1, x2, y2);\r
         draw(line);\r
     }\r
 \r
index 0382d7f9976c62076a84a1d1e86585f5f3a89fa6..18531d7ed9a7cb0e21bb4204deacf3c219acc765 100644 (file)
 
 package org.apache.poi.sl.draw.geom;
 
-import org.apache.poi.sl.draw.binding.CTPath2DArcTo;
-
 import java.awt.geom.Arc2D;
-import java.awt.geom.GeneralPath;
+import java.awt.geom.Path2D;
 import java.awt.geom.Point2D;
 
+import org.apache.poi.sl.draw.binding.CTPath2DArcTo;
+
 /**
  * ArcTo command within a shape path in DrawingML:
  *
@@ -48,7 +48,7 @@ public class ArcToCommand implements PathCommand {
         swAng = arc.getSwAng().toString();
     }
 
-    public void execute(GeneralPath path, Context ctx){
+    public void execute(Path2D.Double path, Context ctx){
         double rx = ctx.getValue(wr);
         double ry = ctx.getValue(hr);
         double start = ctx.getValue(stAng) / 60000;
index 9d29062806dcc5571dd21b3cd891f7047b7e94a5..66b35ed585921f59e9f791b8ddcb89fafe91bbd0 100644 (file)
@@ -19,7 +19,7 @@
 
 package org.apache.poi.sl.draw.geom;
 
-import java.awt.geom.GeneralPath;
+import java.awt.geom.Path2D;
 
 /**
  * Date: 10/25/11
@@ -31,7 +31,7 @@ public class ClosePathCommand implements PathCommand {
     ClosePathCommand(){
     }
 
-    public void execute(GeneralPath path, Context ctx){
+    public void execute(Path2D.Double path, Context ctx){
         path.closePath();
     }
 }
index 02eeb2953eb97dd8a4e9cb67d6cd9b1735b85892..6c6361aed98287013465c966b27eb6a588d439d6 100644 (file)
@@ -19,9 +19,9 @@
 
 package org.apache.poi.sl.draw.geom;
 
-import org.apache.poi.sl.draw.binding.CTAdjPoint2D;
+import java.awt.geom.Path2D;
 
-import java.awt.geom.GeneralPath;
+import org.apache.poi.sl.draw.binding.CTAdjPoint2D;
 
 /**
  * Date: 10/25/11
@@ -40,13 +40,13 @@ public class CurveToCommand implements PathCommand {
         arg6 = pt3.getY().toString();
     }
 
-    public void execute(GeneralPath path, Context ctx){
+    public void execute(Path2D.Double path, Context ctx){
         double x1 = ctx.getValue(arg1);
         double y1 = ctx.getValue(arg2);
         double x2 = ctx.getValue(arg3);
         double y2 = ctx.getValue(arg4);
         double x3 = ctx.getValue(arg5);
         double y3 = ctx.getValue(arg6);
-        path.curveTo((float)x1, (float)y1, (float)x2, (float)y2, (float)x3, (float)y3);
+        path.curveTo(x1, y1, x2, y2, x3, y3);
     }
 }
index 99c5a6b20c14378ce4a59f007a9b4b5730f8ce31..7f6e13c542e2834a16434f71a790346dfd4424bd 100644 (file)
@@ -19,9 +19,9 @@
 
 package org.apache.poi.sl.draw.geom;
 
-import org.apache.poi.sl.draw.binding.CTAdjPoint2D;
+import java.awt.geom.Path2D;
 
-import java.awt.geom.GeneralPath;
+import org.apache.poi.sl.draw.binding.CTAdjPoint2D;
 
 /**
  * Date: 10/25/11
@@ -41,9 +41,9 @@ public class LineToCommand implements PathCommand {
         arg2 = s2;
     }
 
-    public void execute(GeneralPath path, Context ctx){
+    public void execute(Path2D.Double path, Context ctx){
         double x = ctx.getValue(arg1);
         double y = ctx.getValue(arg2);
-        path.lineTo((float)x, (float)y);
+        path.lineTo(x, y);
     }
 }
index 22ccd540920702be5b0540fb98ee51b972470600..59c7a8adf2061da0e59ec86130f0e4fb3593e289 100644 (file)
@@ -19,9 +19,9 @@
 
 package org.apache.poi.sl.draw.geom;
 
-import org.apache.poi.sl.draw.binding.CTAdjPoint2D;
+import java.awt.geom.Path2D;
 
-import java.awt.geom.GeneralPath;
+import org.apache.poi.sl.draw.binding.CTAdjPoint2D;
 
 /**
  * Date: 10/25/11
@@ -41,9 +41,9 @@ public class MoveToCommand implements PathCommand {
         arg2 = s2;
     }
 
-    public void execute(GeneralPath path, Context ctx){
+    public void execute(Path2D.Double path, Context ctx){
         double x = ctx.getValue(arg1);
         double y = ctx.getValue(arg2);
-        path.moveTo((float)x, (float)y);
+        path.moveTo(x, y);
     }
 }
index b496e9fc96b6d4a081031d1d404cb4aa634c3d1e..78590faf062e5107c2d481ba2fcb665f231b9ab7 100644 (file)
 
 package org.apache.poi.sl.draw.geom;
 
-import java.awt.geom.GeneralPath;
+import java.awt.geom.Path2D;
 import java.util.ArrayList;
 import java.util.List;
 
-import org.apache.poi.sl.draw.binding.*;
+import org.apache.poi.sl.draw.binding.CTAdjPoint2D;
+import org.apache.poi.sl.draw.binding.CTPath2D;
+import org.apache.poi.sl.draw.binding.CTPath2DArcTo;
+import org.apache.poi.sl.draw.binding.CTPath2DClose;
+import org.apache.poi.sl.draw.binding.CTPath2DCubicBezierTo;
+import org.apache.poi.sl.draw.binding.CTPath2DLineTo;
+import org.apache.poi.sl.draw.binding.CTPath2DMoveTo;
+import org.apache.poi.sl.draw.binding.CTPath2DQuadBezierTo;
+import org.apache.poi.sl.draw.binding.STPathFillMode;
 
 /**
  * Specifies a creation path consisting of a series of moves, lines and curves
@@ -90,10 +98,10 @@ public class Path {
     }
 
     /**
-     * Convert the internal represenation to java.awt.GeneralPath
+     * Convert the internal represenation to java.awt.geom.Path2D
      */
-    public GeneralPath getPath(Context ctx) {
-        GeneralPath path = new GeneralPath();
+    public Path2D.Double getPath(Context ctx) {
+        Path2D.Double path = new Path2D.Double();
         for(PathCommand cmd : commands)
             cmd.execute(path, ctx);
         return path;
index 3063ab81bc3705b38a16ee49c98de3b3edf27feb..41fa21a54673447d08e0a7294e157778d855f545 100644 (file)
@@ -19,7 +19,7 @@
 
 package org.apache.poi.sl.draw.geom;
 
-import java.awt.geom.GeneralPath;
+import java.awt.geom.Path2D;
 
 /**
  * A path command in DrawingML. One of:
@@ -41,5 +41,5 @@ public interface PathCommand {
      * @param path  the path to append the result to
      * @param ctx   the context to lookup variables
      */
-    void execute(GeneralPath path, Context ctx);
+    void execute(Path2D.Double path, Context ctx);
 }
index e9a9364b2dbcfa357ee8ecab8cc3514674f6693a..d5e848b5a9a5c04e935fb09aef1d0478590acc94 100644 (file)
@@ -19,9 +19,9 @@
 
 package org.apache.poi.sl.draw.geom;
 
-import org.apache.poi.sl.draw.binding.CTAdjPoint2D;
+import java.awt.geom.Path2D;
 
-import java.awt.geom.GeneralPath;
+import org.apache.poi.sl.draw.binding.CTAdjPoint2D;
 
 /**
  * Date: 10/25/11
@@ -38,11 +38,11 @@ public class QuadToCommand implements PathCommand {
         arg4 = pt2.getY().toString();
     }
 
-    public void execute(GeneralPath path, Context ctx){
+    public void execute(Path2D.Double path, Context ctx){
         double x1 = ctx.getValue(arg1);
         double y1 = ctx.getValue(arg2);
         double x2 = ctx.getValue(arg3);
         double y2 = ctx.getValue(arg4);
-        path.quadTo((float)x1, (float)y1, (float)x2, (float)y2);
+        path.quadTo(x1, y1, x2, y2);
     }
 }
index ca9a8d30f1613730f49f4097c963d490104492f3..9536065ef05a372bcfe172d1a7c52defcee64a81 100644 (file)
@@ -17,7 +17,7 @@
 \r
 package org.apache.poi.sl.usermodel;\r
 \r
-import java.awt.geom.GeneralPath;\r
+import java.awt.geom.Path2D;\r
 \r
 public interface FreeformShape<\r
     S extends Shape<S,P>,\r
@@ -33,7 +33,7 @@ public interface FreeformShape<
      *\r
      * @return the path\r
      */\r
-    GeneralPath getPath();\r
+    Path2D.Double getPath();\r
 \r
     /**\r
      * Set the shape path\r
@@ -41,5 +41,5 @@ public interface FreeformShape<
      * @param path  shape outline\r
      * @return the number of points written\r
      */\r
-    int setPath(GeneralPath path);\r
+    int setPath(Path2D.Double path);\r
 }\r
index 191bad65f6058ef1fe145ec34437b64a333d1106..9fdcf91f6bf0376db2147a83482c038da857fb75 100644 (file)
@@ -25,6 +25,11 @@ public interface PlaceableShape<
 > {\r
     ShapeContainer<S,P> getParent();\r
     \r
+    /**\r
+     * @return the sheet this shape belongs to\r
+     */\r
+    Sheet<S,P> getSheet();\r
+    \r
     /**\r
      * @return the position of this shape within the drawing canvas.\r
      *         The coordinates are expressed in points\r
index 36f8379c58b0452677d006b4224265159a1222e8..4d053df6fefdc19490ab2911ec6cb3c29887f56f 100644 (file)
@@ -25,8 +25,7 @@ public interface Shape<
 > {
        ShapeContainer<S,P> getParent();
        
-    /**
-    *
+   /**
     * @return the sheet this shape belongs to
     */
    Sheet<S,P> getSheet();
index f75ffa8ed4aefacbd55d7f79d5fa588ec6200506..77e99d2d399420527c6bf81f3deeddeb86ef2f7f 100644 (file)
@@ -20,7 +20,7 @@
 package org.apache.poi.xslf.usermodel;\r
 \r
 import java.awt.geom.AffineTransform;\r
-import java.awt.geom.GeneralPath;\r
+import java.awt.geom.Path2D;\r
 import java.awt.geom.PathIterator;\r
 import java.awt.geom.Rectangle2D;\r
 \r
@@ -57,7 +57,7 @@ public class XSLFFreeformShape extends XSLFAutoShape
     }\r
 \r
     @Override\r
-    public int setPath(GeneralPath path) {\r
+    public int setPath(Path2D.Double path) {\r
         CTPath2D ctPath = CTPath2D.Factory.newInstance();\r
 \r
         Rectangle2D bounds = path.getBounds2D();\r
@@ -119,8 +119,8 @@ public class XSLFFreeformShape extends XSLFAutoShape
     }\r
 \r
     @Override\r
-    public GeneralPath getPath() {\r
-        GeneralPath path = new GeneralPath();\r
+    public Path2D.Double getPath() {\r
+        Path2D.Double path = new Path2D.Double();\r
         Rectangle2D bounds = getAnchor();\r
 \r
         CTCustomGeometry2D geom = getSpPr().getCustGeom();\r
@@ -168,7 +168,7 @@ public class XSLFFreeformShape extends XSLFAutoShape
         // The returned path should fit in the bounding rectangle\r
         AffineTransform at = new AffineTransform();\r
         at.translate(bounds.getX(), bounds.getY());\r
-        return new GeneralPath(at.createTransformedShape(path));\r
+        return new Path2D.Double(at.createTransformedShape(path));\r
     }\r
     /**\r
      * @param shapeId 1-based shapeId\r
index 6f4a5228d061d947d8db022f381436b84aecd82b..fd66d1f7e5692d54b26b461ce7c13ad5161a86d3 100644 (file)
@@ -48,11 +48,11 @@ public class XSLFHyperlink implements Hyperlink<XSLFShape,XSLFTextParagraph> {
 \r
     @Override\r
     public String getAddress() {\r
-        if (!_link.isSetId()) {\r
+        String id = _link.getId();\r
+        if (id == null || "".equals(id)) {\r
             return _link.getAction();\r
         }\r
 \r
-        String id = _link.getId();\r
         URI targetURI = _sheet.getPackagePart().getRelationship(id).getTargetURI();\r
         \r
         return targetURI.toASCIIString();\r
index 049de50261488d86e2371933bfd01ae774d85e66..08804b0a8eded2ec909ca714039605702a68882b 100644 (file)
@@ -455,8 +455,15 @@ public class XSLFTextRun implements TextRun {
 \r
     @Override\r
     public XSLFHyperlink getHyperlink(){\r
-        if(!_r.getRPr().isSetHlinkClick()) return null;\r
-        return new XSLFHyperlink(_r.getRPr().getHlinkClick(), _p.getParentShape().getSheet());\r
+        CTTextCharacterProperties rPr = _r.getRPr();\r
+        if (rPr == null) { \r
+            return null;\r
+        }\r
+        CTHyperlink hl = rPr.getHlinkClick();\r
+        if (hl == null) {\r
+            return null;\r
+        }\r
+        return new XSLFHyperlink(hl, _p.getParentShape().getSheet());\r
     }\r
 \r
     private boolean fetchCharacterProperty(CharacterPropertyFetcher<?> fetcher){\r
index 2ec85f5ac738f00bc03cc92e5a4ac9943b093423..7f2f03968866255f286a8560258f09b3aabbf4a6 100644 (file)
@@ -24,18 +24,17 @@ import java.awt.Graphics2D;
 import java.awt.RenderingHints;\r
 import java.awt.image.BufferedImage;\r
 import java.io.File;\r
-import java.util.HashMap;\r
 import java.util.List;\r
 import java.util.Locale;\r
-import java.util.Map;\r
+import java.util.Set;\r
+import java.util.TreeSet;\r
 \r
 import javax.imageio.ImageIO;\r
 \r
-import org.apache.poi.sl.draw.Drawable;\r
+import org.apache.poi.sl.draw.DrawFactory;\r
 import org.apache.poi.sl.usermodel.Slide;\r
 import org.apache.poi.sl.usermodel.SlideShow;\r
 import org.apache.poi.sl.usermodel.SlideShowFactory;\r
-import org.apache.poi.util.JvmBugs;\r
 \r
 /**\r
  * An utility to convert slides of a .pptx slide show to a PNG image\r
@@ -65,7 +64,7 @@ public class PPTX2PNG {
             return;\r
         }\r
 \r
-        int slidenum = -1;\r
+        String slidenumStr = "-1";\r
         float scale = 1;\r
         File file = null;\r
         String format = "png";\r
@@ -77,7 +76,7 @@ public class PPTX2PNG {
                 if ("-scale".equals(args[i])) {\r
                     scale = Float.parseFloat(args[++i]);\r
                 } else if ("-slide".equals(args[i])) {\r
-                    slidenum = Integer.parseInt(args[++i]);\r
+                    slidenumStr = args[++i];\r
                 } else if ("-format".equals(args[i])) {\r
                     format = args[++i];\r
                 } else if ("-outdir".equals(args[i])) {\r
@@ -120,9 +119,11 @@ public class PPTX2PNG {
         SlideShow<?,?> ss = SlideShowFactory.create(file, null, true);\r
         List<? extends Slide<?,?>> slides = ss.getSlides();\r
 \r
+        Set<Integer> slidenum = slideIndexes(slides.size(), slidenumStr);\r
         \r
-        if (slidenum < -1 || slidenum == 0 || slidenum > slides.size()) {\r
+        if (slidenum.isEmpty()) {\r
             usage("slidenum must be either -1 (for all) or within range: [1.."+slides.size()+"] for "+file);\r
+            ss.close();\r
             return;\r
         }\r
         \r
@@ -130,39 +131,36 @@ public class PPTX2PNG {
         int width = (int) (pgsize.width * scale);\r
         int height = (int) (pgsize.height * scale);\r
 \r
-        int slideNo=1;\r
-        for(Slide<?,?> slide : slides) {\r
-            if (slidenum == -1 || slideNo == slidenum) {\r
-                String title = slide.getTitle();\r
-                if (!quiet) {\r
-                    System.out.println("Rendering slide " + slideNo + (title == null ? "" : ": " + title));\r
-                }\r
+        for(Integer slideNo : slidenum) {\r
+            Slide<?,?> slide = slides.get(slideNo);\r
+            String title = slide.getTitle();\r
+            if (!quiet) {\r
+                System.out.println("Rendering slide " + slideNo + (title == null ? "" : ": " + title));\r
+            }\r
 \r
-                BufferedImage img = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);\r
-                Graphics2D graphics = img.createGraphics();\r
-                fixFonts(graphics);\r
-            \r
-                // default rendering options\r
-                graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);\r
-                graphics.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);\r
-                graphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);\r
-                graphics.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);\r
-\r
-                graphics.scale(scale, scale);\r
-\r
-                // draw stuff\r
-                slide.draw(graphics);\r
-\r
-                // save the result\r
-                if (!"null".equals(format)) {\r
-                    String outname = file.getName().replaceFirst(".pptx?", "");\r
-                    outname = String.format(Locale.ROOT, "%1$s-%2$04d.%3$s", outname, slideNo, format);\r
-                    File outfile = new File(outdir, outname);\r
-                    ImageIO.write(img, format, outfile);\r
-                }\r
-            }                \r
-            slideNo++;\r
-        }\r
+            BufferedImage img = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);\r
+            Graphics2D graphics = img.createGraphics();\r
+            DrawFactory.getInstance(graphics).fixFonts(graphics);\r
+        \r
+            // default rendering options\r
+            graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);\r
+            graphics.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);\r
+            graphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);\r
+            graphics.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);\r
+\r
+            graphics.scale(scale, scale);\r
+\r
+            // draw stuff\r
+            slide.draw(graphics);\r
+\r
+            // save the result\r
+            if (!"null".equals(format)) {\r
+                String outname = file.getName().replaceFirst(".pptx?", "");\r
+                outname = String.format(Locale.ROOT, "%1$s-%2$04d.%3$s", outname, slideNo, format);\r
+                File outfile = new File(outdir, outname);\r
+                ImageIO.write(img, format, outfile);\r
+            }\r
+        }                \r
         \r
         if (!quiet) {\r
             System.out.println("Done");\r
@@ -170,14 +168,43 @@ public class PPTX2PNG {
         \r
         ss.close();\r
     }\r
-\r
-    @SuppressWarnings("unchecked")\r
-    private static void fixFonts(Graphics2D graphics) {\r
-        if (!JvmBugs.hasLineBreakMeasurerBug()) return;\r
-        Map<String,String> fontMap = (Map<String,String>)graphics.getRenderingHint(Drawable.FONT_MAP);\r
-        if (fontMap == null) fontMap = new HashMap<String,String>();\r
-        fontMap.put("Calibri", "Lucida Sans");\r
-        fontMap.put("Cambria", "Lucida Bright");\r
-        graphics.setRenderingHint(Drawable.FONT_MAP, fontMap);        \r
+    \r
+    private static Set<Integer> slideIndexes(final int slideCount, String range) {\r
+        Set<Integer> slideIdx = new TreeSet<Integer>();\r
+        if ("-1".equals(range)) {\r
+            for (int i=0; i<slideCount; i++) {\r
+                slideIdx.add(i);\r
+            }\r
+        } else {\r
+            for (String subrange : range.split(",")) {\r
+                String idx[] = subrange.split("-");\r
+                switch (idx.length) {\r
+                default:\r
+                case 0: break;\r
+                case 1: {\r
+                    int subidx = Integer.parseInt(idx[0]);\r
+                    if (subrange.contains("-")) {\r
+                        int startIdx = subrange.startsWith("-") ? 0 : subidx;\r
+                        int endIdx = subrange.endsWith("-") ? slideCount : Math.min(subidx,slideCount);\r
+                        for (int i=Math.max(startIdx,1); i<endIdx; i++) {\r
+                            slideIdx.add(i-1);\r
+                        }\r
+                    } else {\r
+                        slideIdx.add(Math.max(subidx,1)-1);\r
+                    }\r
+                    break;\r
+                }\r
+                case 2: {\r
+                    int startIdx = Math.min(Integer.parseInt(idx[0]), slideCount);\r
+                    int endIdx = Math.min(Integer.parseInt(idx[1]), slideCount);\r
+                    for (int i=Math.max(startIdx,1); i<endIdx; i++) {\r
+                        slideIdx.add(i-1);\r
+                    }\r
+                    break;\r
+                }\r
+                }\r
+            }\r
+        }\r
+        return slideIdx;\r
     }\r
 }\r
index b80ef77fb87248d45cce67e9c9b9744d4f2dbd24..9ad888da414761cc7078c1abd812cde00664fa4c 100644 (file)
@@ -36,17 +36,28 @@ public class TestPPTX2PNG {
     public void render() throws Exception {\r
         POIDataSamples samples = POIDataSamples.getSlideShowInstance();\r
 \r
-        String[] testFiles = {"alterman_security.ppt","alterman_security.pptx","KEY02.pptx","themes.pptx","backgrounds.pptx","layouts.pptx", "sample.pptx", "shapes.pptx",};\r
-//        String[] testFiles = {"41246-2.ppt","45543.ppt","53446.ppt","ParagraphStylesShorterThanCharStyles.ppt"};\r
+//        File testFilesX[] = new File("tmp_ppt").listFiles(new FileFilter() {\r
+//            public boolean accept(File pathname) {\r
+//                return pathname.getName().toLowerCase().contains("ppt");\r
+//            }\r
+//        });\r
+//        String testFiles[] = new String[testFilesX.length];\r
+//        for (int i=0; i<testFilesX.length; i++) {\r
+//            testFiles[i] = testFilesX[i].getPath();\r
+//        }\r
+        \r
+\r
+        String[] testFiles = {"53446.ppt", "alterman_security.ppt","alterman_security.pptx","KEY02.pptx","themes.pptx","backgrounds.pptx","layouts.pptx", "sample.pptx", "shapes.pptx",};\r
         String[] args = {\r
             "-format", "null", // png,gif,jpg or null for test\r
             "-slide", "-1", // -1 for all\r
             "-outdir", new File("build/tmp/").getCanonicalPath(),\r
-            "-quite",\r
+            "-quiet",\r
             "dummyfile"\r
         };\r
         for(String sampleFile : testFiles){\r
             args[args.length-1] = samples.getFile(sampleFile).getCanonicalPath();\r
+//            args[args.length-1] = new File(sampleFile).getCanonicalPath();\r
             try {\r
                 PPTX2PNG.main(args);\r
             } catch (IllegalStateException e) {\r
index 720c5f6e00128d0a8be8e9abd370cb51fef372e0..b7b902f54255840ede0bb95516a77bf2202989f1 100644 (file)
@@ -19,8 +19,9 @@ package org.apache.poi.xslf.usermodel;
 import static org.junit.Assert.assertEquals;\r
 \r
 import java.awt.geom.Ellipse2D;\r
-import java.awt.geom.GeneralPath;\r
+import java.awt.geom.Path2D;\r
 import java.awt.geom.Rectangle2D;\r
+import java.io.IOException;\r
 \r
 import org.junit.Test;\r
 \r
@@ -30,16 +31,16 @@ import org.junit.Test;
 public class TestXSLFFreeformShape {\r
 \r
     @Test\r
-    public void testSetPath() {\r
+    public void testSetPath() throws IOException {\r
         XMLSlideShow ppt = new XMLSlideShow();\r
         XSLFSlide slide = ppt.createSlide();\r
         XSLFFreeformShape shape1 = slide.createFreeform();\r
         // comples path consisting of a rectangle and an ellipse inside it\r
-        GeneralPath path1 = new GeneralPath(new Rectangle2D.Double(150, 150, 300, 300));\r
+        Path2D.Double path1 = new Path2D.Double(new Rectangle2D.Double(150, 150, 300, 300));\r
         path1.append(new Ellipse2D.Double(200, 200, 100, 50), false);\r
         shape1.setPath(path1);\r
 \r
-        GeneralPath path2 = shape1.getPath();\r
+        Path2D.Double path2 = shape1.getPath();\r
 \r
         // YK: how to compare the original path1 and the value returned by XSLFFreeformShape.getPath() ?\r
         // one way is to create another XSLFFreeformShape from path2 and compare the resulting xml\r
@@ -49,5 +50,7 @@ public class TestXSLFFreeformShape {
         shape2.setPath(path2);\r
 \r
         assertEquals(shape1.getSpPr().getCustGeom().toString(), shape2.getSpPr().getCustGeom().toString());\r
+        \r
+        ppt.close();\r
     }\r
 }
\ No newline at end of file
index 4a3af15f8d8be154dabf59b0a284b010b9362f73..7d6b0c262619b4e95426b71fc93e2d255fcf29ff 100644 (file)
@@ -42,6 +42,7 @@ import java.awt.geom.Arc2D;
 import java.awt.geom.Ellipse2D;
 import java.awt.geom.GeneralPath;
 import java.awt.geom.Line2D;
+import java.awt.geom.Path2D;
 import java.awt.geom.RoundRectangle2D;
 import java.awt.image.BufferedImage;
 import java.awt.image.BufferedImageOp;
@@ -244,7 +245,7 @@ public final class PPGraphics2D extends Graphics2D implements Cloneable {
      * @see #setComposite
      */
     public void draw(Shape shape){
-        GeneralPath path = new GeneralPath(_transform.createTransformedShape(shape));
+        Path2D.Double path = new Path2D.Double(_transform.createTransformedShape(shape));
         HSLFFreeformShape p = new HSLFFreeformShape(_group);
         p.setPath(path);
         p.getFill().setForegroundColor(null);
@@ -346,7 +347,7 @@ public final class PPGraphics2D extends Graphics2D implements Cloneable {
      * @see #setClip
      */
     public void fill(Shape shape){
-        GeneralPath path = new GeneralPath(_transform.createTransformedShape(shape));
+        Path2D.Double path = new Path2D.Double(_transform.createTransformedShape(shape));
         HSLFFreeformShape p = new HSLFFreeformShape(_group);
         p.setPath(path);
         applyPaint(p);
index f51d20ae5d4a5107abcfe3ec336015bd8f001d78..8ca722d856c0a76e722c01eacf3d78a26857d340 100644 (file)
@@ -20,6 +20,7 @@ import java.io.IOException;
 import java.io.OutputStream;
 
 import org.apache.poi.util.LittleEndian;
+import org.apache.poi.util.POILogger;
 
 /**
  * This class represents the metadata of a link in a slide/notes/etc.
@@ -59,11 +60,12 @@ public class InteractiveInfo extends RecordContainer {
         */     
        private void findInterestingChildren() {
                // First child should be the InteractiveInfoAtom
-               if(_children[0] instanceof InteractiveInfoAtom) {
-                       infoAtom = (InteractiveInfoAtom)_children[0];
-               } else {
-                       throw new IllegalStateException("First child record wasn't a InteractiveInfoAtom, was of type " + _children[0].getRecordType());
-               }
+           if (_children == null || _children.length == 0 || !(_children[0] instanceof InteractiveInfoAtom)) {
+               logger.log(POILogger.WARN, "First child record wasn't a InteractiveInfoAtom - leaving this atom in an invalid state...");
+               return;
+           }
+
+           infoAtom = (InteractiveInfoAtom)_children[0];
        }
        
        /**
index 61034f1f949987739651c133d195d5bf1a6b66bf..e2987b3d62aab950d78460cd51d14a131941a697 100644 (file)
@@ -168,7 +168,7 @@ public abstract class Record
                try {
                        c = RecordTypes.forTypeID((short)type).handlingClass;
                        if(c == null) {
-                               // How odd. RecordTypes normally subsitutes in
+                               // How odd. RecordTypes normally substitutes in
                                //  a default handler class if it has heard of the record
                                //  type but there's no support for it. Explicitly request
                                //  that now
index 5c4f1461aff59595e69d118530c2065632aa443f..06e8d00f8ccf1c51051e25e7e7e7099a7d22cd0b 100644 (file)
@@ -23,23 +23,28 @@ import java.io.InputStream;
 import java.util.List;
 
 import org.apache.poi.ddf.AbstractEscherOptRecord;
+import org.apache.poi.ddf.EscherArrayProperty;
 import org.apache.poi.ddf.EscherBSERecord;
+import org.apache.poi.ddf.EscherColorRef;
 import org.apache.poi.ddf.EscherContainerRecord;
 import org.apache.poi.ddf.EscherProperties;
 import org.apache.poi.ddf.EscherRecord;
 import org.apache.poi.ddf.EscherSimpleProperty;
 import org.apache.poi.hslf.record.Document;
 import org.apache.poi.sl.draw.DrawPaint;
+import org.apache.poi.sl.usermodel.ColorStyle;
 import org.apache.poi.sl.usermodel.FillStyle;
 import org.apache.poi.sl.usermodel.PaintStyle;
+import org.apache.poi.sl.usermodel.PaintStyle.GradientPaint;
+import org.apache.poi.sl.usermodel.PaintStyle.GradientPaint.GradientType;
 import org.apache.poi.sl.usermodel.PaintStyle.TexturePaint;
+import org.apache.poi.util.LittleEndian;
 import org.apache.poi.util.POILogFactory;
 import org.apache.poi.util.POILogger;
+import org.apache.poi.util.Units;
 
 /**
  * Represents functionality provided by the 'Fill Effects' dialog in PowerPoint.
- *
- * @author Yegor Kozlov
  */
 public final class HSLFFill {
     // For logging
@@ -118,36 +123,110 @@ public final class HSLFFill {
     public FillStyle getFillStyle() {
         return new FillStyle() {
             public PaintStyle getPaint() {
-                switch (getFillType()) {
+                final int fillType = getFillType();
+                // TODO: fix gradient types, this mismatches with the MS-ODRAW definition ...
+                // need to handle (not only) the type (radial,rectangular,linear),
+                // the direction, e.g. top right, and bounds (e.g. for rectangular boxes)
+                switch (fillType) {
                     case FILL_SOLID:
                         return DrawPaint.createSolidPaint(getForegroundColor());
-                    case FILL_PICTURE: {
-                        final HSLFPictureData pd = getPictureData();
-                        if (pd == null) break;
-                        
-                        return new TexturePaint() {
-                            public InputStream getImageData() {
-                                return new ByteArrayInputStream(pd.getData());
-                            }
-
-                            public String getContentType() {
-                                return pd.getContentType();
-                            }
-
-                            public int getAlpha() {
-                                return (int)(shape.getAlpha(EscherProperties.FILL__FILLOPACITY)*100000.0);
-                            }
-                        };
-                    }
+                    case FILL_SHADE_SHAPE:
+                        return getGradientPaint(GradientType.shape);
+                    case FILL_SHADE_CENTER:
+                    case FILL_SHADE_TITLE:
+                        return getGradientPaint(GradientType.circular);
+                    case FILL_SHADE:
+                    case FILL_SHADE_SCALE:
+                        return getGradientPaint(GradientType.linear);
+                    case FILL_PICTURE:
+                        return getTexturePaint();
                     default:
-                        logger.log(POILogger.WARN, "unsuported fill type: " + getFillType());
-                        break;
+                        logger.log(POILogger.WARN, "unsuported fill type: " + fillType);
+                        return null;
                 }
-                return null;
             }
         };
     }
     
+    
+
+    private GradientPaint getGradientPaint(final GradientType gradientType) {
+        final AbstractEscherOptRecord opt = shape.getEscherOptRecord();
+        final EscherArrayProperty ep = HSLFShape.getEscherProperty(opt, EscherProperties.FILL__SHADECOLORS);
+        final int colorCnt = (ep == null) ? 0 : ep.getNumberOfElementsInArray();
+
+        return new GradientPaint() {
+            public double getGradientAngle() {
+                // A value of type FixedPoint, as specified in [MS-OSHARED] section 2.2.1.6,
+                // that specifies the angle of the gradient fill. Zero degrees represents a vertical vector from
+                // bottom to top. The default value for this property is 0x00000000.
+                int rot = shape.getEscherProperty(EscherProperties.FILL__ANGLE);
+                return 90-Units.fixedPointToDouble(rot);
+            }
+            public ColorStyle[] getGradientColors() {
+                ColorStyle cs[];
+                if (colorCnt == 0) {
+                    cs = new ColorStyle[2];
+                    cs[0] = wrapColor(getBackgroundColor());
+                    cs[1] = wrapColor(getForegroundColor());
+                } else {
+                    cs = new ColorStyle[colorCnt];
+                    int idx = 0;
+                    // TODO: handle palette colors and alpha(?) value 
+                    for (byte data[] : ep) {
+                        EscherColorRef ecr = new EscherColorRef(data, 0, 4);
+                        cs[idx++] = wrapColor(shape.getColor(ecr));
+                    }
+                }
+                return cs;
+            }
+            private ColorStyle wrapColor(Color col) {
+                return (col == null) ? null : DrawPaint.createSolidPaint(col).getSolidColor();
+            }
+            public float[] getGradientFractions() {
+                float frc[];
+                if (colorCnt == 0) {
+                    frc = new float[]{0, 1};
+                } else {
+                    frc = new float[colorCnt];
+                    int idx = 0;
+                    for (byte data[] : ep) {
+                        double pos = Units.fixedPointToDouble(LittleEndian.getInt(data, 4));
+                        frc[idx++] = (float)pos;
+                    }
+                }
+                return frc;
+            }
+            public boolean isRotatedWithShape() {
+                return false;
+            }
+            public GradientType getGradientType() {
+                return gradientType;
+            }
+        };
+    }
+    
+    private TexturePaint getTexturePaint() {
+        final HSLFPictureData pd = getPictureData();
+        if (pd == null) {
+            return null;
+        }
+
+        return new TexturePaint() {
+            public InputStream getImageData() {
+                return new ByteArrayInputStream(pd.getData());
+            }
+
+            public String getContentType() {
+                return pd.getContentType();
+            }
+
+            public int getAlpha() {
+                return (int)(shape.getAlpha(EscherProperties.FILL__FILLOPACITY)*100000.0);
+            }
+        };
+    }
+
     /**
      * Returns fill type.
      * Must be one of the <code>FILL_*</code> constants defined in this class.
@@ -172,6 +251,7 @@ public final class HSLFFill {
         }
     }
 
+    @SuppressWarnings("resource")
     protected EscherBSERecord getEscherBSERecord(int idx){
         HSLFSheet sheet = shape.getSheet();
         if(sheet == null) {
@@ -258,6 +338,7 @@ public final class HSLFFill {
     /**
      * <code>PictureData</code> object used in a texture, pattern of picture fill.
      */
+    @SuppressWarnings("resource")
     public HSLFPictureData getPictureData(){
         AbstractEscherOptRecord opt = shape.getEscherOptRecord();
         EscherSimpleProperty p = HSLFShape.getEscherProperty(opt, EscherProperties.FILL__PATTERNTEXTURE);
index dde778ffcc47830faf97d4de10fc3df138e6ce6d..8be86f0995ed9e0df51c3cf32b2ec9192d88818b 100644 (file)
 package org.apache.poi.hslf.usermodel;
 
 import java.awt.geom.AffineTransform;
-import java.awt.geom.GeneralPath;
+import java.awt.geom.Path2D;
 import java.awt.geom.PathIterator;
 import java.awt.geom.Point2D;
 import java.awt.geom.Rectangle2D;
 import java.util.ArrayList;
-import java.util.Arrays;
+import java.util.Iterator;
 import java.util.List;
 
 import org.apache.poi.ddf.AbstractEscherOptRecord;
 import org.apache.poi.ddf.EscherArrayProperty;
 import org.apache.poi.ddf.EscherContainerRecord;
 import org.apache.poi.ddf.EscherProperties;
+import org.apache.poi.ddf.EscherProperty;
 import org.apache.poi.ddf.EscherSimpleProperty;
 import org.apache.poi.sl.usermodel.FreeformShape;
 import org.apache.poi.sl.usermodel.ShapeContainer;
 import org.apache.poi.sl.usermodel.ShapeType;
+import org.apache.poi.util.BitField;
+import org.apache.poi.util.BitFieldFactory;
 import org.apache.poi.util.LittleEndian;
 import org.apache.poi.util.POILogger;
 import org.apache.poi.util.Units;
@@ -57,6 +60,85 @@ public final class HSLFFreeformShape extends HSLFAutoShape implements FreeformSh
     public static final byte[] SEGMENTINFO_CLOSE    = new byte[]{0x01, (byte)0x60};
     public static final byte[] SEGMENTINFO_END      = new byte[]{0x00, (byte)0x80};
 
+    private static BitField PATH_INFO = BitFieldFactory.getInstance(0xE000);
+    private static BitField ESCAPE_INFO = BitFieldFactory.getInstance(0x1F00);
+
+    enum PathInfo {
+        lineTo(0),curveTo(1),moveTo(2),close(3),end(4),escape(5),clientEscape(6);
+        int flag;
+        PathInfo(int flag) {
+            this.flag = flag;
+        }
+        static PathInfo valueOf(int flag) {
+            for (PathInfo v : values()) {
+                if (v.flag == flag) {
+                    return v;
+                }
+            }
+            return null;
+        }
+    }
+
+    enum EscapeInfo {
+        EXTENSION(0x0000),
+        ANGLE_ELLIPSE_TO(0x0001),
+        ANGLE_ELLIPSE(0x0002),
+        ARC_TO(0x0003),
+        ARC(0x0004),
+        CLOCKWISE_ARC_TO(0x0005),
+        CLOCKWISE_ARC(0x0006),
+        ELLIPTICAL_QUADRANT_X(0x0007),
+        ELLIPTICAL_QUADRANT_Y(0x0008),
+        QUADRATIC_BEZIER(0x0009),
+        NO_FILL(0X000A),
+        NO_LINE(0X000B),
+        AUTO_LINE(0X000C),
+        AUTO_CURVE(0X000D),
+        CORNER_LINE(0X000E),
+        CORNER_CURVE(0X000F),
+        SMOOTH_LINE(0X0010),
+        SMOOTH_CURVE(0X0011),
+        SYMMETRIC_LINE(0X0012),
+        SYMMETRIC_CURVE(0X0013),
+        FREEFORM(0X0014),
+        FILL_COLOR(0X0015),
+        LINE_COLOR(0X0016);
+
+        int flag;
+        EscapeInfo(int flag) {
+            this.flag = flag;
+        }
+        static EscapeInfo valueOf(int flag) {
+            for (EscapeInfo v : values()) {
+                if (v.flag == flag) {
+                    return v;
+                }
+            }
+            return null;
+        }
+    }
+
+    enum ShapePath {
+        LINES(0),
+        LINES_CLOSED(1),
+        CURVES(2),
+        CURVES_CLOSED(3),
+        COMPLEX(4);
+
+        int flag;
+        ShapePath(int flag) {
+            this.flag = flag;
+        }
+        static ShapePath valueOf(int flag) {
+            for (ShapePath v : values()) {
+                if (v.flag == flag) {
+                    return v;
+                }
+            }
+            return null;
+        }
+    }
+    
     /**
      * Create a Freeform object and initialize it from the supplied Record container.
      *
@@ -88,7 +170,7 @@ public final class HSLFFreeformShape extends HSLFAutoShape implements FreeformSh
     }
 
     @Override
-    public int setPath(GeneralPath path) {
+    public int setPath(Path2D.Double path) {
         Rectangle2D bounds = path.getBounds2D();
         PathIterator it = path.getPathIterator(new AffineTransform());
 
@@ -174,23 +256,19 @@ public final class HSLFFreeformShape extends HSLFAutoShape implements FreeformSh
         opt.sortProperties();
 
         setAnchor(bounds);
-        
+
         return numPoints;
     }
 
     @Override
-    public GeneralPath getPath(){
+    public Path2D.Double getPath(){
         AbstractEscherOptRecord opt = getEscherOptRecord();
-        opt.addEscherProperty(new EscherSimpleProperty(EscherProperties.GEOMETRY__SHAPEPATH, 0x4));
 
-        EscherArrayProperty verticesProp = getEscherProperty(opt, (short)(EscherProperties.GEOMETRY__VERTICES + 0x4000));
-        if(verticesProp == null) verticesProp = getEscherProperty(opt, EscherProperties.GEOMETRY__VERTICES);
-
-        EscherArrayProperty segmentsProp = getEscherProperty(opt, (short)(EscherProperties.GEOMETRY__SEGMENTINFO + 0x4000));
-        if(segmentsProp == null) segmentsProp = getEscherProperty(opt, EscherProperties.GEOMETRY__SEGMENTINFO);
+        EscherArrayProperty verticesProp = getShapeProp(opt, EscherProperties.GEOMETRY__VERTICES);
+        EscherArrayProperty segmentsProp = getShapeProp(opt, EscherProperties.GEOMETRY__SEGMENTINFO);
 
         // return empty path if either GEOMETRY__VERTICES or GEOMETRY__SEGMENTINFO is missing, see Bugzilla 54188
-        GeneralPath path = new GeneralPath();
+        Path2D.Double path = new Path2D.Double();
 
         //sanity check
         if(verticesProp == null) {
@@ -202,46 +280,60 @@ public final class HSLFFreeformShape extends HSLFAutoShape implements FreeformSh
             return path;
         }
 
-        int numPoints = verticesProp.getNumberOfElementsInArray();
-        int numSegments = segmentsProp.getNumberOfElementsInArray();
-        for (int i = 0, j = 0; i < numSegments && j < numPoints; i++) {
-            byte[] elem = segmentsProp.getElement(i);
-            if(Arrays.equals(elem, SEGMENTINFO_MOVETO)){
-                byte[] p = verticesProp.getElement(j++);
-                short x = LittleEndian.getShort(p, 0);
-                short y = LittleEndian.getShort(p, 2);
-                path.moveTo(Units.masterToPoints(x), Units.masterToPoints(y));
-            } else if (Arrays.equals(elem, SEGMENTINFO_CUBICTO) || Arrays.equals(elem, SEGMENTINFO_CUBICTO2)){
-                i++;
-                byte[] p1 = verticesProp.getElement(j++);
-                short x1 = LittleEndian.getShort(p1, 0);
-                short y1 = LittleEndian.getShort(p1, 2);
-                byte[] p2 = verticesProp.getElement(j++);
-                short x2 = LittleEndian.getShort(p2, 0);
-                short y2 = LittleEndian.getShort(p2, 2);
-                byte[] p3 = verticesProp.getElement(j++);
-                short x3 = LittleEndian.getShort(p3, 0);
-                short y3 = LittleEndian.getShort(p3, 2);
-                path.curveTo(
-                    Units.masterToPoints(x1), Units.masterToPoints(y1),
-                    Units.masterToPoints(x2), Units.masterToPoints(y2),
-                    Units.masterToPoints(x3), Units.masterToPoints(y3));
-
-            } else if (Arrays.equals(elem, SEGMENTINFO_LINETO)){
-                i++;
-                byte[] pnext = segmentsProp.getElement(i);
-                if(Arrays.equals(pnext, SEGMENTINFO_ESCAPE)){
-                    if(j + 1 < numPoints){
-                        byte[] p = verticesProp.getElement(j++);
-                        short x = LittleEndian.getShort(p, 0);
-                        short y = LittleEndian.getShort(p, 2);
-                        path.lineTo(Units.masterToPoints(x), Units.masterToPoints(y));
+        Iterator<byte[]> vertIter = verticesProp.iterator();
+        Iterator<byte[]> segIter = segmentsProp.iterator();
+        
+        byte segPushBack[] = null;
+        while (vertIter.hasNext() && segIter.hasNext()) {
+            byte[] segElem = (segPushBack != null) ? segPushBack : segIter.next();
+            segPushBack = null;
+            PathInfo pi = getPathInfo(segElem);
+            switch (pi) {
+                case escape: {
+                    handleEscapeInfo(path, segElem, vertIter);
+                    break;
+                }
+                case moveTo: {
+                    byte[] p = vertIter.next();
+                    double x = Units.masterToPoints(LittleEndian.getShort(p, 0));
+                    double y = Units.masterToPoints(LittleEndian.getShort(p, 2));
+                    path.moveTo(x,y);
+                    break;
+                }
+                case curveTo: {
+                    byte[] p1 = vertIter.next();
+                    double x1 = Units.masterToPoints(LittleEndian.getShort(p1, 0));
+                    double y1 = Units.masterToPoints(LittleEndian.getShort(p1, 2));
+                    byte[] p2 = vertIter.next();
+                    double x2 = Units.masterToPoints(LittleEndian.getShort(p2, 0));
+                    double y2 = Units.masterToPoints(LittleEndian.getShort(p2, 2));
+                    byte[] p3 = vertIter.next();
+                    double x3 = Units.masterToPoints(LittleEndian.getShort(p3, 0));
+                    double y3 = Units.masterToPoints(LittleEndian.getShort(p3, 2));
+                    path.curveTo(x1,y1,x2,y2,x3,y3);
+                    break;
+                }
+                case lineTo:
+                    if (vertIter.hasNext()) {
+                        byte[] p = vertIter.next();
+                        double x = Units.masterToPoints(LittleEndian.getShort(p, 0));
+                        double y = Units.masterToPoints(LittleEndian.getShort(p, 2));
+                        path.lineTo(x,y);
                     }
-                } else if (Arrays.equals(pnext, SEGMENTINFO_CLOSE)){
+                    break;
+                case close:
                     path.closePath();
-                }
+                    break;
+                default:
+                    break;
             }
         }
+
+        EscherSimpleProperty shapePath = getShapeProp(opt, EscherProperties.GEOMETRY__SHAPEPATH);
+        ShapePath sp = ShapePath.valueOf(shapePath == null ? 1 : shapePath.getPropertyValue());
+        if (sp == ShapePath.LINES_CLOSED || sp == ShapePath.CURVES_CLOSED) {
+            path.closePath();
+        }
         
         Rectangle2D anchor = getAnchor();
         Rectangle2D bounds = path.getBounds2D();
@@ -251,6 +343,81 @@ public final class HSLFFreeformShape extends HSLFAutoShape implements FreeformSh
                 anchor.getWidth()/bounds.getWidth(),
                 anchor.getHeight()/bounds.getHeight()
         );
-        return new GeneralPath(at.createTransformedShape(path));
+        return new Path2D.Double(at.createTransformedShape(path));
+    }
+    
+    private static <T extends EscherProperty> T getShapeProp(AbstractEscherOptRecord opt, int propId) {
+        T prop = getEscherProperty(opt, (short)(propId + 0x4000));
+        if (prop == null) {
+            prop = getEscherProperty(opt, propId);
+        }
+        return prop;
+    }
+    
+    private void handleEscapeInfo(Path2D path, byte segElem[], Iterator<byte[]> vertIter) {
+        EscapeInfo ei = getEscapeInfo(segElem);
+        switch (ei) {
+            case EXTENSION:
+                break;
+            case ANGLE_ELLIPSE_TO:
+                break;
+            case ANGLE_ELLIPSE:
+                break;
+            case ARC_TO:
+                break;
+            case ARC:
+                break;
+            case CLOCKWISE_ARC_TO:
+                break;
+            case CLOCKWISE_ARC:
+                break;
+            case ELLIPTICAL_QUADRANT_X:
+                break;
+            case ELLIPTICAL_QUADRANT_Y:
+                break;
+            case QUADRATIC_BEZIER:
+                break;
+            case NO_FILL:
+                break;
+            case NO_LINE:
+                break;
+            case AUTO_LINE:
+                break;
+            case AUTO_CURVE:
+                break;
+            case CORNER_LINE:
+                break;
+            case CORNER_CURVE:
+                break;
+            case SMOOTH_LINE:
+                break;
+            case SMOOTH_CURVE:
+                break;
+            case SYMMETRIC_LINE:
+                break;
+            case SYMMETRIC_CURVE:
+                break;
+            case FREEFORM:
+                break;
+            case FILL_COLOR:
+                break;
+            case LINE_COLOR:
+                break;
+            default:
+                break;
+        }
+    }
+    
+
+    private static PathInfo getPathInfo(byte elem[]) {
+        int elemUS = LittleEndian.getUShort(elem, 0);
+        int pathInfo = PATH_INFO.getValue(elemUS);
+        return PathInfo.valueOf(pathInfo);
+    }
+    
+    private static EscapeInfo getEscapeInfo(byte elem[]) {
+        int elemUS = LittleEndian.getUShort(elem, 0);
+        int escInfo = ESCAPE_INFO.getValue(elemUS);
+        return EscapeInfo.valueOf(escInfo);
     }
 }
index 74fe3cb183047050ddd11ee6a5359b523ea5bb5c..949a1230dd873a97cd8e5debf99dcc80c205f350 100644 (file)
@@ -363,6 +363,9 @@ public final class HSLFHyperlink implements Hyperlink<HSLFShape,HSLFTextParagrap
 
             InteractiveInfo hldr = (InteractiveInfo)r;
             InteractiveInfoAtom info = hldr.getInteractiveInfoAtom();
+            if (info == null) {
+                continue;
+            }
             int id = info.getHyperlinkID();
             ExHyperlink exHyper = exobj.get(id);
             if (exHyper == null) {
index 81acffa25e0d53e7a1f6a26a8f8300cbc4dd8231..6f8802d31356ed05d46d4d7eed8c55e47bd329b5 100644 (file)
@@ -209,6 +209,13 @@ public class HSLFPictureShape extends HSLFSimpleShape implements PictureShape<HS
             ? null
             : new Insets((int)(top*100000), (int)(left*100000), (int)(bottom*100000), (int)(right*100000));
     }
+
+    @Override
+    public ShapeType getShapeType() {
+        // this is kind of a hack, as picture/ole shapes can have a shape type of "frame"
+        // but rendering is handled like a rectangle
+        return ShapeType.RECT;
+    }
     
     /**
      * @return the fractional property or 0 if not defined
index 0210a1d8a60b97a354c6475be2e2745e6a7e3ca4..12f781ee431e371beffd9d9796bd00169a85a848 100644 (file)
@@ -346,7 +346,13 @@ public abstract class HSLFShape implements Shape<HSLFShape,HSLFTextParagraph> {
         int val = (p == null) ? defaultColor : p.getPropertyValue();
 
         EscherColorRef ecr = new EscherColorRef(val);
-        
+        Color col = getColor(ecr);
+
+        double alpha = getAlpha(opacityProperty);
+        return new Color(col.getRed(), col.getGreen(), col.getBlue(), (int)(alpha*255.0));
+    }
+
+    Color getColor(EscherColorRef ecr) {
         boolean fPaletteIndex = ecr.hasPaletteIndexFlag();
         boolean fPaletteRGB = ecr.hasPaletteRGBFlag();
         boolean fSystemRGB = ecr.hasSystemRGBFlag();
@@ -373,11 +379,10 @@ public abstract class HSLFShape implements Shape<HSLFShape,HSLFTextParagraph> {
         } else if (fSysIndex){
             //TODO
         }
-
-        double alpha = getAlpha(opacityProperty);
-        return new Color(rgb[0], rgb[1], rgb[2], (int)(alpha*255.0));
+        
+        return new Color(rgb[0], rgb[1], rgb[2]);
     }
-
+    
     double getAlpha(short opacityProperty) {
         AbstractEscherOptRecord opt = getEscherOptRecord();
         EscherSimpleProperty op = getEscherProperty(opt, opacityProperty);
index 19717135368b4b705de2c8775451ae9975967716..4797ff5f57c2c6678c33ad69e5c04709adfd11cd 100644 (file)
@@ -111,8 +111,15 @@ public final class HSLFShapeFactory {
                 shape = createNonPrimitive(spContainer, parent);
                 break;
             default:
-                EscherTextboxRecord etr = spContainer.getChildById(EscherTextboxRecord.RECORD_ID);
-                if (parent instanceof HSLFTable && etr != null) {
+                if (parent instanceof HSLFTable) {
+                    EscherTextboxRecord etr = spContainer.getChildById(EscherTextboxRecord.RECORD_ID);
+                    if (etr == null) {
+                        logger.log(POILogger.WARN, "invalid ppt - add EscherTextboxRecord to cell");
+                        etr = new EscherTextboxRecord();
+                        etr.setRecordId(EscherTextboxRecord.RECORD_ID);
+                        etr.setOptions((short)15);
+                        spContainer.addChildRecord(etr);
+                    }
                     shape = new HSLFTableCell(spContainer, (HSLFTable)parent);
                 } else {
                     shape = new HSLFAutoShape(spContainer, parent);
index 1326de19a0ec1e107ad0167bd20ea4f3b2d1a20b..0a13a62d57bea55f6146e63c423fc2d4dbf2aa95 100644 (file)
@@ -275,7 +275,8 @@ public abstract class HSLFSimpleShape extends HSLFShape implements SimpleShape<H
 
     public Guide getAdjustValue(String name) {
         if (name == null || !name.matches("adj([1-9]|10)?")) {
-            throw new IllegalArgumentException("Adjust value '"+name+"' not supported.");
+            logger.log(POILogger.INFO, "Adjust value '"+name+"' not supported. Using default value.");
+            return null;
         }
 
         name = name.replace("adj", "");
@@ -296,7 +297,13 @@ public abstract class HSLFSimpleShape extends HSLFShape implements SimpleShape<H
             default: throw new RuntimeException();
         }
 
+        // TODO: the adjust values need to be corrected somehow depending on the shape width/height
+        // see https://social.msdn.microsoft.com/Forums/en-US/3f69ebb3-62a0-4fdd-b367-64790dfb2491/presetshapedefinitionsxml-does-not-specify-width-and-height-form-some-autoshapes?forum=os_binaryfile
+        
+        // the adjust value can be format dependent, e.g. hexagon has different values,
+        // other shape types have the same adjust values in OOXML and native
         int adjval = getEscherProperty(escherProp, -1);
+
         return (adjval == -1) ? null : new Guide(name, "val "+adjval);
     }
 
index ca76f84ee3276a8c3428e454adc5eaf03f637599..7db12d97de987018c6f6fd08750be7c45d54fd86 100644 (file)
@@ -717,9 +717,25 @@ public final class HSLFTextParagraph implements TextParagraph<HSLFShape,HSLFText
         String propNames[] = propName.split(",");\r
         for (String pn : propNames) {\r
             TextProp prop = props.findByName(pn);\r
-            if (prop != null) return prop;\r
-        }\r
+            if (prop == null) {\r
+                continue;\r
+            }\r
 \r
+            // Font properties (maybe other too???) can have an index of -1\r
+            // so we check the master for this font index then\r
+            if (pn.contains("font") && prop.getValue() == -1) {\r
+                return getMasterPropVal(props, pn, paragraph);\r
+            }\r
+            \r
+            return prop;\r
+        }\r
+        \r
+        return getMasterPropVal(props, propName, paragraph);\r
+    }\r
+    \r
+    private static TextProp getMasterPropVal(TextPropCollection props, String propName, HSLFTextParagraph paragraph) {\r
+        String propNames[] = propName.split(",");\r
+        \r
         BitMaskTextProp maskProp = (BitMaskTextProp) props.findByName(ParagraphFlagsTextProp.NAME);\r
         boolean hardAttribute = (maskProp != null && maskProp.getValue() == 0);\r
         if (hardAttribute) return null;\r
index 8c112169f1cf44d22fde40995046a8a86d577897..88a9766e9bfe6809784e207747889e45b6732cfb 100644 (file)
@@ -115,6 +115,9 @@ public class HwmfDrawProperties {
     }\r
 \r
     public void setViewportOrg(double x, double y) {\r
+        if (viewport == null) {\r
+            viewport = (Rectangle2D)window.clone();\r
+        }\r
         double w = viewport.getWidth();\r
         double h = viewport.getHeight();\r
         viewport.setRect(x, y, w, h);\r
index 0c13d13074d0952373abc51e975f3f9df4d53853..ace62b49ba5e67131c84f7a02c66c3ea533bddc8 100644 (file)
@@ -27,7 +27,6 @@ import java.awt.Shape;
 import java.awt.TexturePaint;\r
 import java.awt.font.TextAttribute;\r
 import java.awt.geom.AffineTransform;\r
-import java.awt.geom.GeneralPath;\r
 import java.awt.geom.Rectangle2D;\r
 import java.awt.image.BufferedImage;\r
 import java.text.AttributedString;\r
@@ -35,6 +34,7 @@ import java.util.ArrayList;
 import java.util.LinkedList;\r
 import java.util.List;\r
 import java.util.ListIterator;\r
+import java.util.Map;\r
 import java.util.NoSuchElementException;\r
 \r
 import org.apache.poi.hwmf.record.HwmfBrushStyle;\r
@@ -45,6 +45,9 @@ import org.apache.poi.hwmf.record.HwmfMisc.WmfSetBkMode.HwmfBkMode;
 import org.apache.poi.hwmf.record.HwmfObjectTableEntry;\r
 import org.apache.poi.hwmf.record.HwmfPenStyle;\r
 import org.apache.poi.hwmf.record.HwmfPenStyle.HwmfLineDash;\r
+import org.apache.poi.sl.draw.DrawFactory;\r
+import org.apache.poi.sl.draw.DrawFontManager;\r
+import org.apache.poi.sl.draw.Drawable;\r
 \r
 public class HwmfGraphics {\r
     private final Graphics2D graphicsCtx;\r
@@ -65,6 +68,7 @@ public class HwmfGraphics {
         this.graphicsCtx = graphicsCtx;\r
         this.bbox = (Rectangle2D)bbox.clone();\r
         this.initialAT = graphicsCtx.getTransform();\r
+        DrawFactory.getInstance(graphicsCtx).fixFonts(graphicsCtx);\r
     }\r
 \r
     public HwmfDrawProperties getProperties() {\r
@@ -96,8 +100,8 @@ public class HwmfGraphics {
 \r
     public void fill(Shape shape) {\r
         if (prop.getBrushStyle() != HwmfBrushStyle.BS_NULL) {\r
-            GeneralPath gp = new GeneralPath(shape);\r
-            gp.setWindingRule(prop.getPolyfillMode().awtFlag);\r
+//            GeneralPath gp = new GeneralPath(shape);\r
+//            gp.setWindingRule(prop.getPolyfillMode().awtFlag);\r
             graphicsCtx.setPaint(getFill());\r
             graphicsCtx.fill(shape);\r
         }\r
@@ -111,7 +115,12 @@ public class HwmfGraphics {
         if (view == null) {\r
             view = win;\r
         }\r
-        float width = (float)(prop.getPenWidth() * view.getWidth() / win.getWidth());\r
+        \r
+        // TODO: fix line width calculation\r
+        float width = (float)prop.getPenWidth();\r
+        if (width == 0) {\r
+            width = 1;\r
+        }\r
         HwmfPenStyle ps = prop.getPenStyle();\r
         int cap = ps.getLineCap().awtFlag;\r
         int join = ps.getLineJoin().awtFlag;\r
@@ -261,6 +270,10 @@ public class HwmfGraphics {
             }\r
             stackIndex = curIdx + index;\r
         }\r
+        if (stackIndex == -1) {\r
+            // roll to last when curIdx == 0\r
+            stackIndex = propStack.size()-1;\r
+        }\r
         prop = propStack.get(stackIndex);\r
     }\r
 \r
@@ -306,34 +319,47 @@ public class HwmfGraphics {
     }\r
     \r
     public void drawString(String text, Rectangle2D bounds) {\r
+        drawString(text, bounds, null);\r
+    }\r
+    \r
+    public void drawString(String text, Rectangle2D bounds, int dx[]) {\r
         HwmfFont font = prop.getFont();\r
-        if (font == null) {\r
+        if (font == null || text == null || text.isEmpty()) {\r
             return;\r
         }\r
+        \r
+        double fontH = getFontHeight(font);\r
+        // TODO: another approx. ...\r
+        double fontW = fontH/1.8;\r
+        \r
+        int len = text.length();\r
         AttributedString as = new AttributedString(text);\r
-        as.addAttribute(TextAttribute.FAMILY, font.getFacename());\r
-        // TODO: fix font height calculation\r
-        as.addAttribute(TextAttribute.SIZE, Math.abs(font.getHeight()));\r
-        as.addAttribute(TextAttribute.STRIKETHROUGH, font.isStrikeOut());\r
-        if (font.isUnderline()) {\r
-            as.addAttribute(TextAttribute.UNDERLINE, TextAttribute.UNDERLINE_ON);\r
-        }\r
-        if (font.isItalic()) {\r
-            as.addAttribute(TextAttribute.POSTURE, TextAttribute.POSTURE_OBLIQUE);\r
+        if (dx == null || dx.length == 0) {\r
+            addAttributes(as, font, 0, len);\r
+        } else {\r
+            for (int i=0; i<len; i++) {\r
+                addAttributes(as, font, i, i+1);\r
+                // Tracking works as a prefix/advance space on characters whereas\r
+                // dx[...] is the complete width of the current char\r
+                // therefore we need to add the additional/suffix width to the next char\r
+                if (i<len-1) {\r
+                    as.addAttribute(TextAttribute.TRACKING, (dx[i]-fontW)/fontH, i+1, i+2);\r
+                }\r
+            }\r
         }\r
-        as.addAttribute(TextAttribute.WEIGHT, font.getWeight());\r
+        \r
         \r
         double angle = Math.toRadians(-font.getEscapement()/10.);\r
         \r
         \r
         final AffineTransform at = graphicsCtx.getTransform();\r
         try {\r
-            graphicsCtx.translate(bounds.getX(), bounds.getY());\r
+            graphicsCtx.translate(bounds.getX(), bounds.getY()+fontH);\r
             graphicsCtx.rotate(angle);\r
             if (prop.getBkMode() == HwmfBkMode.OPAQUE) {\r
                 // TODO: validate bounds\r
                 graphicsCtx.setBackground(prop.getBackgroundColor().getColor());\r
-                graphicsCtx.fill(bounds);\r
+                graphicsCtx.fill(new Rectangle2D.Double(0, 0, bounds.getWidth(), bounds.getHeight()));\r
             }\r
             graphicsCtx.setColor(prop.getTextColor().getColor());\r
             graphicsCtx.drawString(as.getIterator(), 0, 0); // (float)bounds.getX(), (float)bounds.getY());\r
@@ -341,4 +367,46 @@ public class HwmfGraphics {
             graphicsCtx.setTransform(at);\r
         }\r
     }\r
+    \r
+    private void addAttributes(AttributedString as, HwmfFont font, int start, int end) {\r
+        DrawFontManager fontHandler = (DrawFontManager)graphicsCtx.getRenderingHint(Drawable.FONT_HANDLER);\r
+        String fontFamily = null;\r
+        @SuppressWarnings("unchecked")\r
+        Map<String,String> fontMap = (Map<String,String>)graphicsCtx.getRenderingHint(Drawable.FONT_MAP);\r
+        if (fontMap != null && fontMap.containsKey(font.getFacename())) {\r
+            fontFamily = fontMap.get(font.getFacename());\r
+        }\r
+        if (fontHandler != null) {\r
+            fontFamily = fontHandler.getRendererableFont(font.getFacename(), font.getPitchAndFamily());\r
+        }\r
+        if (fontFamily == null) {\r
+            fontFamily = font.getFacename();\r
+        }\r
+        \r
+        as.addAttribute(TextAttribute.FAMILY, fontFamily);\r
+        as.addAttribute(TextAttribute.SIZE, getFontHeight(font));\r
+        as.addAttribute(TextAttribute.STRIKETHROUGH, font.isStrikeOut());\r
+        if (font.isUnderline()) {\r
+            as.addAttribute(TextAttribute.UNDERLINE, TextAttribute.UNDERLINE_ON);\r
+        }\r
+        if (font.isItalic()) {\r
+            as.addAttribute(TextAttribute.POSTURE, TextAttribute.POSTURE_OBLIQUE);\r
+        }\r
+        as.addAttribute(TextAttribute.WEIGHT, font.getWeight());\r
+    }\r
+    \r
+    private double getFontHeight(HwmfFont font) {\r
+        // see HwmfFont#height for details\r
+        double fontHeight = font.getHeight();\r
+        if (fontHeight == 0) {\r
+            return 12;\r
+        } else if (fontHeight < 0) {\r
+            return -fontHeight;\r
+        } else {\r
+            // TODO: fix font height calculation \r
+            // the height is given as font size + ascent + descent\r
+            // as an approximation we reduce the height by a static factor \r
+            return fontHeight*3/4;\r
+        }\r
+    }\r
 }\r
diff --git a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfBinaryRasterOp.java b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfBinaryRasterOp.java
new file mode 100644 (file)
index 0000000..31ddcd7
--- /dev/null
@@ -0,0 +1,112 @@
+/* ====================================================================\r
+   Licensed to the Apache Software Foundation (ASF) under one or more\r
+   contributor license agreements.  See the NOTICE file distributed with\r
+   this work for additional information regarding copyright ownership.\r
+   The ASF licenses this file to You under the Apache License, Version 2.0\r
+   (the "License"); you may not use this file except in compliance with\r
+   the License.  You may obtain a copy of the License at\r
+\r
+       http://www.apache.org/licenses/LICENSE-2.0\r
+\r
+   Unless required by applicable law or agreed to in writing, software\r
+   distributed under the License is distributed on an "AS IS" BASIS,\r
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+   See the License for the specific language governing permissions and\r
+   limitations under the License.\r
+==================================================================== */\r
+\r
+package org.apache.poi.hwmf.record;\r
+\r
+/**\r
+ * The BinaryRasterOperation Enumeration section lists the binary raster-operation codes.\r
+ * Rasteroperation codes define how metafile processing combines the bits from the selected\r
+ * pen with the bits in the destination bitmap.\r
+ *\r
+ * Each raster-operation code represents a Boolean operation in which the values of the pixels in the\r
+ * selected pen and the destination bitmap are combined. Following are the two operands used in these\r
+ * operations.\r
+ *\r
+ * <table>\r
+ * <tr><th>Operand</th><th>Meaning</th></tr>\r
+ * <tr><td>P</td><td>Selected pen</td></tr>\r
+ * <tr><td>D</td><td>Destination bitmap</td></tr>\r
+ * </table>\r
+ *\r
+ * Following are the Boolean operators used in these operations.\r
+ * <table>\r
+ * <tr><th>Operand</th><th>Meaning</th></tr>\r
+ * <tr><td>a</td><td>Bitwise AND</td></tr>\r
+ * <tr><td>n</td><td>Bitwise NOT (inverse)</td></tr>\r
+ * <tr><td>o</td><td>Bitwise OR</td></tr>\r
+ * <tr><td>x</td><td>Bitwise exclusive OR (XOR)</td></tr>\r
+ * </table>\r
+ *\r
+ * All Boolean operations are presented in reverse Polish notation. For example, the following\r
+ * operation replaces the values of the pixels in the destination bitmap with a combination of the pixel\r
+ * values of the pen and the selected brush: DPo.\r
+ *\r
+ * Each raster-operation code is a 32-bit integer whose high-order word is a Boolean operation index and\r
+ * whose low-order word is the operation code. The 16-bit operation index is a zero-extended, 8-bit\r
+ * value that represents all possible outcomes resulting from the Boolean operation on two parameters\r
+ * (in this case, the pen and destination values). For example, the operation indexes for the DPo and\r
+ * DPan operations are shown in the following list.\r
+ *\r
+ * <table>\r
+ * <tr><th>P</th><th>D</th><th>DPo</th><th>DPan</th></tr>\r
+ * <tr><td>0</td><td>0</td><td>0</td><td>1</td></tr>\r
+ * <tr><td>0</td><td>1</td><td>1</td><td>1</td></tr>\r
+ * <tr><td>1</td><td>0</td><td>1</td><td>1</td></tr>\r
+ * <tr><td>1</td><td>1</td><td>1</td><td>0</td></tr>\r
+ * </table>\r
+ *\r
+ */\r
+public enum HwmfBinaryRasterOp {\r
+    /** 0, Pixel is always 0 */\r
+    R2_BLACK(0x0001),\r
+    /** DPon, Pixel is the inverse of the R2_MERGEPEN color. */\r
+    R2_NOTMERGEPEN(0x0002),\r
+    /** DPna, Pixel is a combination of the screen color and the inverse of the pen color. */\r
+    R2_MASKNOTPEN(0x0003),\r
+    /** Pn, Pixel is the inverse of the pen color. */\r
+    R2_NOTCOPYPEN(0x0004),\r
+    /** PDna, Pixel is a combination of the colors common to both the pen and the inverse of the screen. */\r
+    R2_MASKPENNOT(0x0005),\r
+    /** Dn, Pixel is the inverse of the screen color. */\r
+    R2_NOT(0x0006),\r
+    /** DPx, Pixel is a combination of the colors in the pen or in the screen, but not in both. */\r
+    R2_XORPEN(0x0007),\r
+    /** DPan, Pixel is the inverse of the R2_MASKPEN color. */\r
+    R2_NOTMASKPEN(0x0008),\r
+    /** DPa, Pixel is a combination of the colors common to both the pen and the screen. */\r
+    R2_MASKPEN(0x0009),\r
+    /** DPxn, Pixel is the inverse of the R2_XORPEN color. */\r
+    R2_NOTXORPEN(0x000A),\r
+    /** D, Pixel remains unchanged. */\r
+    R2_NOP(0x000B),\r
+    /** DPno, Pixel is a combination of the colors common to both the screen and the inverse of the pen. */\r
+    R2_MERGENOTPEN(0x000C),\r
+    /** P, Pixel is the pen color. */\r
+    R2_COPYPEN(0x000D),\r
+    /** PDno, Pixel is a combination of the pen color and the inverse of the screen color.*/\r
+    R2_MERGEPENNOT(0x000E),\r
+    /** DPo, Pixel is a combination of the pen color and the screen color. */\r
+    R2_MERGEPEN(0x000F),\r
+    /** 1, Pixel is always 1 */\r
+    R2_WHITE(0x0010);\r
+\r
+    int opIndex;\r
+\r
+    HwmfBinaryRasterOp(int opIndex) {\r
+        this.opIndex=opIndex;\r
+    }\r
+\r
+    public static HwmfBinaryRasterOp valueOf(int opIndex) {\r
+        for (HwmfBinaryRasterOp bb : HwmfBinaryRasterOp.values()) {\r
+            if (bb.opIndex == opIndex) {\r
+                return bb;\r
+            }\r
+        }\r
+        return null;\r
+    }\r
+\r
+}\r
index 0f720239f3450164dc8da2baae7e9c4b43e69339..ae7f5cb30797fa703d37dd70e792a5d08b92c439 100644 (file)
 package org.apache.poi.hwmf.record;\r
 \r
 import java.awt.Color;\r
+import java.awt.Graphics2D;\r
 import java.awt.image.BufferedImage;\r
-import java.io.BufferedInputStream;\r
 import java.io.ByteArrayInputStream;\r
 import java.io.IOException;\r
 import java.io.InputStream;\r
-import java.util.ArrayList;\r
-import java.util.List;\r
 \r
 import javax.imageio.ImageIO;\r
 \r
@@ -32,6 +30,8 @@ import org.apache.poi.hssf.record.RecordFormatException;
 import org.apache.poi.util.LittleEndian;\r
 import org.apache.poi.util.LittleEndianConsts;\r
 import org.apache.poi.util.LittleEndianInputStream;\r
+import org.apache.poi.util.POILogFactory;\r
+import org.apache.poi.util.POILogger;\r
 \r
 /**\r
  * The DeviceIndependentBitmap Object defines an image in device-independent bitmap (DIB) format.\r
@@ -188,6 +188,7 @@ public class HwmfBitmapDib {
         }\r
     }\r
 \r
+    private static POILogger logger = POILogFactory.getLogger(HwmfBitmapDib.class);\r
     private static final int BMP_HEADER_SIZE = 14;\r
     \r
     private int headerSize;\r
@@ -204,8 +205,6 @@ public class HwmfBitmapDib {
     private long headerColorUsed = -1;\r
     @SuppressWarnings("unused")\r
     private long headerColorImportant = -1;\r
-\r
-    @SuppressWarnings("unused")\r
     private Color colorTable[];\r
     @SuppressWarnings("unused")\r
     private int colorMaskR=0,colorMaskG=0,colorMaskB=0;\r
@@ -360,22 +359,24 @@ public class HwmfBitmapDib {
 \r
     protected int readRGBQuad(LittleEndianInputStream leis, int count) throws IOException {\r
         int size = 0;\r
-        List<Color> colorList = new ArrayList<Color>();\r
+        colorTable = new Color[count];\r
         for (int i=0; i<count; i++) {\r
             int blue = leis.readUByte();\r
             int green = leis.readUByte();\r
             int red = leis.readUByte();\r
             @SuppressWarnings("unused")\r
             int reserved = leis.readUByte();\r
-            Color c = new Color(red, green, blue);\r
-            colorList.add(c);\r
+            colorTable[i] = new Color(red, green, blue);\r
             size += 4 * LittleEndianConsts.BYTE_SIZE;\r
         }\r
-        colorTable = colorList.toArray(new Color[colorList.size()]);\r
         return size;\r
     }\r
 \r
     public InputStream getBMPStream() {\r
+        return new ByteArrayInputStream(getBMPData());\r
+    }\r
+\r
+    private byte[] getBMPData() {\r
         if (imageData == null) {\r
             throw new RecordFormatException("bitmap not initialized ... need to call init() before");\r
         }\r
@@ -398,14 +399,20 @@ public class HwmfBitmapDib {
         // fill the "known" image data\r
         System.arraycopy(imageData, 0, buf, BMP_HEADER_SIZE, imageData.length);\r
         \r
-        return new ByteArrayInputStream(buf);\r
+        return buf;\r
     }\r
     \r
     public BufferedImage getImage() {\r
         try {\r
             return ImageIO.read(getBMPStream());\r
         } catch (IOException e) {\r
-            throw new RecordFormatException("invalid bitmap data", e);\r
+            logger.log(POILogger.ERROR, "invalid bitmap data - returning black opaque image");\r
+            BufferedImage bi = new BufferedImage(headerWidth, headerHeight, BufferedImage.TYPE_INT_ARGB);\r
+            Graphics2D g = bi.createGraphics();\r
+            g.setPaint(Color.black);\r
+            g.fillRect(0, 0, headerWidth, headerHeight);\r
+            g.dispose();\r
+            return bi;\r
         }\r
     }\r
 }\r
index b167d0236a70516069babe8efa908b1253ef763f..acfbd77b0c553c380a739b0555cbc450d88f240b 100644 (file)
@@ -17,6 +17,7 @@
 \r
 package org.apache.poi.hwmf.record;\r
 \r
+import java.awt.Color;\r
 import java.awt.Shape;\r
 import java.awt.geom.Arc2D;\r
 import java.awt.geom.Area;\r
@@ -146,9 +147,10 @@ public class HwmfDraw {
 \r
         @Override\r
         public void draw(HwmfGraphics ctx) {\r
-            Path2D p = getShape();\r
-            p.closePath();\r
-            p.setWindingRule(ctx.getProperties().getPolyfillMode().awtFlag);\r
+            Path2D shape = getShape();\r
+//            shape.closePath();\r
+            Path2D p = (Path2D)shape.clone();\r
+            p.setWindingRule(getWindingRule(ctx));\r
             ctx.fill(p);\r
         }\r
 \r
@@ -170,8 +172,9 @@ public class HwmfDraw {
 \r
         @Override\r
         public void draw(HwmfGraphics ctx) {\r
-            Path2D p = getShape();\r
-            p.setWindingRule(ctx.getProperties().getPolyfillMode().awtFlag);\r
+            Path2D shape = getShape();\r
+            Path2D p = (Path2D)shape.clone();\r
+            p.setWindingRule(getWindingRule(ctx));\r
             ctx.draw(p);\r
         }\r
     }\r
@@ -320,12 +323,13 @@ public class HwmfDraw {
 \r
             for (int nPoints : pointsPerPolygon) {\r
                 /**\r
-                 * An array of 16-bit unsigned integers that define the coordinates of the polygons.\r
+                 * An array of 16-bit signed integers that define the coordinates of the polygons.\r
+                 * (Note: MS-WMF wrongly says unsigned integers ...)\r
                  */\r
                 Path2D poly = new Path2D.Double();\r
                 for (int i=0; i<nPoints; i++) {\r
-                    int x = leis.readUShort();\r
-                    int y = leis.readUShort();\r
+                    int x = leis.readShort();\r
+                    int y = leis.readShort();\r
                     size += 2*LittleEndianConsts.SHORT_SIZE;\r
                     if (i == 0) {\r
                         poly.moveTo(x, y);\r
@@ -342,14 +346,23 @@ public class HwmfDraw {
 \r
         @Override\r
         public void draw(HwmfGraphics ctx) {\r
-            int windingRule = ctx.getProperties().getPolyfillMode().awtFlag;\r
-            Area area = new Area();\r
+            if (polyList.isEmpty()) {\r
+                return;\r
+            }\r
+            \r
+            int windingRule = getWindingRule(ctx);\r
+            Area area = null;\r
             for (Path2D poly : polyList) {\r
                 Path2D p = (Path2D)poly.clone();\r
                 p.setWindingRule(windingRule);\r
-                area.add(new Area(p));\r
+                Area newArea = new Area(p);\r
+                if (area == null) {\r
+                    area = newArea;\r
+                } else {\r
+                    area.exclusiveOr(newArea);\r
+                }\r
             }\r
-            ctx.draw(area);\r
+            ctx.fill(area);\r
         }\r
     }\r
 \r
@@ -679,4 +692,8 @@ public class HwmfDraw {
             ctx.applyObjectTableEntry(objectIndex);\r
         }\r
     }\r
+    \r
+    private static int getWindingRule(HwmfGraphics ctx) {\r
+        return ctx.getProperties().getPolyfillMode().awtFlag;\r
+    }\r
  }\r
index 7d616271dfc59ebe1c99e4d338ae1357ec0a75af..b005abc8c0baac4fe74f1e51d641652f7165890f 100644 (file)
@@ -24,13 +24,155 @@ import org.apache.poi.util.HexDump;
 import org.apache.poi.util.LittleEndianConsts;\r
 import org.apache.poi.util.LittleEndianInputStream;\r
 \r
+/**\r
+ * The MetafileEscapes specifies printer driver functionality that\r
+ * might not be directly accessible through WMF records\r
+ */\r
 public class HwmfEscape implements HwmfRecord {\r
     \r
+    public enum EscapeFunction {\r
+        /** Notifies the printer driver that the application has finished writing to a page. */\r
+        NEWFRAME(0x0001),\r
+        /** Stops processing the current document. */\r
+        ABORTDOC(0x0002),\r
+        /** Notifies the printer driver that the application has finished writing to a band. */\r
+        NEXTBAND(0x0003),\r
+        /** Sets color table values. */\r
+        SETCOLORTABLE(0x0004),\r
+        /** Gets color table values. */\r
+        GETCOLORTABLE(0x0005),\r
+        /** Causes all pending output to be flushed to the output device. */\r
+        FLUSHOUT(0x0006),\r
+        /** Indicates that the printer driver SHOULD print text only, and no graphics. */\r
+        DRAFTMODE(0x0007),\r
+        /** Queries a printer driver to determine whether a specific escape function is supported on the output device it drives. */\r
+        QUERYESCSUPPORT(0x0008),\r
+        /** Sets the application-defined function that allows a print job to be canceled during printing. */\r
+        SETABORTPROC(0x0009),\r
+        /** Notifies the printer driver that a new print job is starting. */\r
+        STARTDOC(0x000A),\r
+        /** Notifies the printer driver that the current print job is ending. */\r
+        ENDDOC(0x000B),\r
+        /** Retrieves the physical page size currently selected on an output device. */\r
+        GETPHYSPAGESIZE(0x000C),\r
+        /** Retrieves the offset from the upper-left corner of the physical page where the actual printing or drawing begins. */\r
+        GETPRINTINGOFFSET(0x000D),\r
+        /** Retrieves the scaling factors for the x-axis and the y-axis of a printer. */\r
+        GETSCALINGFACTOR(0x000E),\r
+        /** Used to embed an enhanced metafile format (EMF) metafile within a WMF metafile. */\r
+        META_ESCAPE_ENHANCED_METAFILE(0x000F),\r
+        /** Sets the width of a pen in pixels. */\r
+        SETPENWIDTH(0x0010),\r
+        /** Sets the number of copies. */\r
+        SETCOPYCOUNT(0x0011),\r
+        /** Sets the source, such as a particular paper tray or bin on a printer, for output forms. */\r
+        SETPAPERSOURCE(0x0012),\r
+        /** This record passes through arbitrary data. */\r
+        PASSTHROUGH(0x0013),\r
+        /** Gets information concerning graphics technology that is supported on a device. */\r
+        GETTECHNOLOGY(0x0014),\r
+        /** Specifies the line-drawing mode to use in output to a device. */\r
+        SETLINECAP(0x0015),\r
+        /** Specifies the line-joining mode to use in output to a device. */\r
+        SETLINEJOIN(0x0016),\r
+        /** Sets the limit for the length of miter joins to use in output to a device. */\r
+        SETMITERLIMIT(0x0017),\r
+        /** Retrieves or specifies settings concerning banding on a device, such as the number of bands. */\r
+        BANDINFO(0x0018),\r
+        /** Draws a rectangle with a defined pattern. */\r
+        DRAWPATTERNRECT(0x0019),\r
+        /** Retrieves the physical pen size currently defined on a device. */\r
+        GETVECTORPENSIZE(0x001A),\r
+        /** Retrieves the physical brush size currently defined on a device. */\r
+        GETVECTORBRUSHSIZE(0x001B),\r
+        /** Enables or disables double-sided (duplex) printing on a device. */\r
+        ENABLEDUPLEX(0x001C),\r
+        /** Retrieves or specifies the source of output forms on a device. */\r
+        GETSETPAPERBINS(0x001D),\r
+        /** Retrieves or specifies the paper orientation on a device. */\r
+        GETSETPRINTORIENT(0x001E),\r
+        /** Retrieves information concerning the sources of different forms on an output device. */\r
+        ENUMPAPERBINS(0x001F),\r
+        /** Specifies the scaling of device-independent bitmaps (DIBs). */\r
+        SETDIBSCALING(0x0020),\r
+        /** Indicates the start and end of an encapsulated PostScript (EPS) section. */\r
+        EPSPRINTING(0x0021),\r
+        /** Queries a printer driver for paper dimensions and other forms data. */\r
+        ENUMPAPERMETRICS(0x0022),\r
+        /** Retrieves or specifies paper dimensions and other forms data on an output device. */\r
+        GETSETPAPERMETRICS(0x0023),\r
+        /** Sends arbitrary PostScript data to an output device. */\r
+        POSTSCRIPT_DATA(0x0025),\r
+        /** Notifies an output device to ignore PostScript data. */\r
+        POSTSCRIPT_IGNORE(0x0026),\r
+        /** Gets the device units currently configured on an output device. */\r
+        GETDEVICEUNITS(0x002A),\r
+        /** Gets extended text metrics currently configured on an output device. */\r
+        GETEXTENDEDTEXTMETRICS(0x0100),\r
+        /** Gets the font kern table currently defined on an output device. */\r
+        GETPAIRKERNTABLE(0x0102),\r
+        /** Draws text using the currently selected font, background color, and text color. */\r
+        EXTTEXTOUT(0x0200),\r
+        /** Gets the font face name currently configured on a device. */\r
+        GETFACENAME(0x0201),\r
+        /** Sets the font face name on a device. */\r
+        DOWNLOADFACE(0x0202),\r
+        /** Queries a printer driver about the support for metafiles on an output device. */\r
+        METAFILE_DRIVER(0x0801),\r
+        /** Queries the printer driver about its support for DIBs on an output device. */\r
+        QUERYDIBSUPPORT(0x0C01),\r
+        /** Opens a path. */\r
+        BEGIN_PATH(0x1000),\r
+        /** Defines a clip region that is bounded by a path. The input MUST be a 16-bit quantity that defines the action to take. */\r
+        CLIP_TO_PATH(0x1001),\r
+        /** Ends a path. */\r
+        END_PATH(0x1002),\r
+        /** The same as STARTDOC specified with a NULL document and output filename, data in raw mode, and a type of zero. */\r
+        OPEN_CHANNEL(0x100E),\r
+        /** Instructs the printer driver to download sets of PostScript procedures. */\r
+        DOWNLOADHEADER(0x100F),\r
+        /** The same as ENDDOC. See OPEN_CHANNEL. */\r
+        CLOSE_CHANNEL(0x1010),\r
+        /** Sends arbitrary data directly to a printer driver, which is expected to process this data only when in PostScript mode. */\r
+        POSTSCRIPT_PASSTHROUGH(0x1013),\r
+        /** Sends arbitrary data directly to the printer driver. */\r
+        ENCAPSULATED_POSTSCRIPT(0x1014),\r
+        /** Sets the printer driver to either PostScript or GDI mode. */\r
+        POSTSCRIPT_IDENTIFY(0x1015),\r
+        /** Inserts a block of raw data into a PostScript stream. The input MUST be\r
+        a 32-bit quantity specifying the number of bytes to inject, a 16-bit quantity specifying the\r
+        injection point, and a 16-bit quantity specifying the page number, followed by the bytes to\r
+        inject. */\r
+        POSTSCRIPT_INJECTION(0x1016),\r
+        /** Checks whether the printer supports a JPEG image. */\r
+        CHECKJPEGFORMAT(0x1017),\r
+        /** Checks whether the printer supports a PNG image */\r
+        CHECKPNGFORMAT(0x1018),\r
+        /** Gets information on a specified feature setting for a PostScript printer driver. */\r
+        GET_PS_FEATURESETTING(0x1019),\r
+        /** Enables applications to write documents to a file or to a printer in XML Paper Specification (XPS) format. */\r
+        MXDC_ESCAPE(0x101A),\r
+        /** Enables applications to include private procedures and other arbitrary data in documents. */\r
+        SPCLPASSTHROUGH2(0x11D8);\r
+        \r
+        int flag;\r
+        EscapeFunction(int flag) {\r
+            this.flag = flag;\r
+        }\r
+\r
+        static EscapeFunction valueOf(int flag) {\r
+            for (EscapeFunction hs : values()) {\r
+                if (hs.flag == flag) return hs;\r
+            }\r
+            return null;\r
+        }\r
+    }\r
+    \r
     /**\r
      * A 16-bit unsigned integer that defines the escape function. The \r
      * value MUST be from the MetafileEscapes enumeration.\r
      */\r
-    private int escapeFunction;\r
+    private EscapeFunction escapeFunction;\r
     /**\r
      * A 16-bit unsigned integer that specifies the size, in bytes, of the \r
      * EscapeData field.\r
@@ -48,7 +190,7 @@ public class HwmfEscape implements HwmfRecord {
     \r
     @Override\r
     public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException {\r
-        escapeFunction = leis.readUShort();\r
+        escapeFunction = EscapeFunction.valueOf(leis.readUShort());\r
         byteCount = leis.readUShort();\r
         escapeData = new byte[byteCount];\r
         leis.read(escapeData);\r
index d77aa49323c23fa9a4a1c75e0a8b89f1c2ac9809..2dabb069fdf76fe8c9fa38160a4b4762599e5dbc 100644 (file)
@@ -20,13 +20,9 @@ package org.apache.poi.hwmf.record;
 import java.awt.Shape;\r
 import java.awt.geom.Path2D;\r
 import java.awt.image.BufferedImage;\r
-import java.io.File;\r
 import java.io.IOException;\r
 \r
-import javax.imageio.ImageIO;\r
-\r
 import org.apache.poi.hwmf.draw.HwmfGraphics;\r
-import org.apache.poi.hwmf.record.HwmfWindowing.WmfCreateRegion;\r
 import org.apache.poi.util.LittleEndianConsts;\r
 import org.apache.poi.util.LittleEndianInputStream;\r
 \r
@@ -240,7 +236,7 @@ public class HwmfFill {
         \r
         @Override\r
         public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException {\r
-            polyfillMode = HwmfPolyfillMode.valueOf(leis.readUShort());\r
+            polyfillMode = HwmfPolyfillMode.valueOf(leis.readUShort() & 3);\r
             return LittleEndianConsts.SHORT_SIZE;\r
         }\r
 \r
@@ -402,6 +398,15 @@ public class HwmfFill {
     }\r
 \r
     /**\r
+     * The META_STRETCHBLT record specifies the transfer of a block of pixels according to a raster\r
+     * operation, with possible expansion or contraction.\r
+     * The destination of the transfer is the current output region in the playback device context.\r
+     * There are two forms of META_STRETCHBLT, one which specifies a bitmap as the source, and the other\r
+     * which uses the playback device context as the source. Definitions follow for the fields that are the\r
+     * same in the two forms of META_STRETCHBLT are defined below. The subsections that follow specify\r
+     * the packet structures of the two forms of META_STRETCHBLT.\r
+     * The expansion or contraction is performed according to the stretching mode currently set in the\r
+     * playback device context, which MUST be a value from the StretchMode.\r
      */\r
     public static class WmfStretchBlt implements HwmfRecord {\r
         /**\r
@@ -502,12 +507,12 @@ public class HwmfFill {
 \r
     /**\r
      * The META_STRETCHDIB record specifies the transfer of color data from a\r
-     * block of pixels in deviceindependent format according to a raster operation,\r
+     * block of pixels in device independent format according to a raster operation,\r
      * with possible expansion or contraction.\r
      * The source of the color data is a DIB, and the destination of the transfer is\r
      * the current output region in the playback device context.\r
      */\r
-    public static class WmfStretchDib implements HwmfRecord, HwmfImageRecord {\r
+    public static class WmfStretchDib implements HwmfRecord, HwmfImageRecord, HwmfObjectTableEntry {\r
         /**\r
          * A 32-bit unsigned integer that defines how the source pixels, the current brush in\r
          * the playback device context, and the destination pixels are to be combined to\r
@@ -599,6 +604,11 @@ public class HwmfFill {
 \r
         @Override\r
         public void draw(HwmfGraphics ctx) {\r
+            ctx.addObjectTableEntry(this);\r
+        }\r
+        \r
+        @Override\r
+        public void applyObject(HwmfGraphics ctx) {\r
             \r
         }\r
 \r
@@ -706,7 +716,7 @@ public class HwmfFill {
      * using deviceindependent color data.\r
      * The source of the color data is a DIB\r
      */\r
-    public static class WmfSetDibToDev implements HwmfRecord, HwmfImageRecord {\r
+    public static class WmfSetDibToDev implements HwmfRecord, HwmfImageRecord, HwmfObjectTableEntry {\r
 \r
         /**\r
          * A 16-bit unsigned integer that defines whether the Colors field of the\r
@@ -783,6 +793,11 @@ public class HwmfFill {
 \r
         @Override\r
         public void draw(HwmfGraphics ctx) {\r
+            ctx.addObjectTableEntry(this);\r
+        }\r
+        \r
+        @Override\r
+        public void applyObject(HwmfGraphics ctx) {\r
             \r
         }\r
 \r
@@ -793,7 +808,7 @@ public class HwmfFill {
     }\r
 \r
 \r
-    public static class WmfDibBitBlt implements HwmfRecord, HwmfImageRecord {\r
+    public static class WmfDibBitBlt implements HwmfRecord, HwmfImageRecord, HwmfObjectTableEntry {\r
 \r
         /**\r
          * A 32-bit unsigned integer that defines how the source pixels, the current brush\r
@@ -877,6 +892,11 @@ public class HwmfFill {
 \r
         @Override\r
         public void draw(HwmfGraphics ctx) {\r
+            ctx.addObjectTableEntry(this);\r
+        }\r
+        \r
+        @Override\r
+        public void applyObject(HwmfGraphics ctx) {\r
             \r
         }\r
 \r
@@ -886,7 +906,7 @@ public class HwmfFill {
         }\r
     }\r
 \r
-    public static class WmfDibStretchBlt implements HwmfRecord, HwmfImageRecord {\r
+    public static class WmfDibStretchBlt implements HwmfRecord, HwmfImageRecord, HwmfObjectTableEntry {\r
         /**\r
          * A 32-bit unsigned integer that defines how the source pixels, the current brush\r
          * in the playback device context, and the destination pixels are to be combined to form the\r
@@ -978,6 +998,11 @@ public class HwmfFill {
 \r
         @Override\r
         public void draw(HwmfGraphics ctx) {\r
+            ctx.addObjectTableEntry(this);\r
+        }\r
+        \r
+        @Override\r
+        public void applyObject(HwmfGraphics ctx) {\r
             \r
         }\r
 \r
index d23b2381b6e8d2e227556dcbe56f54a43f0752eb..fce75a365696b84881d9d4f3ae62555788c8831c 100644 (file)
@@ -448,6 +448,13 @@ public class HwmfFont {
      */\r
     WmfFontQuality quality;\r
 \r
+    /**\r
+     * A PitchAndFamily object that defines the pitch and the family of the font.\r
+     * Font families specify the look of fonts in a general way and are intended for\r
+     * specifying fonts when the exact typeface wanted is not available.\r
+     */\r
+    int pitchAndFamily;\r
+    \r
     /**\r
      * Font families specify the look of fonts in a general way and are\r
      * intended for specifying fonts when the exact typeface wanted is not available.\r
@@ -480,9 +487,7 @@ public class HwmfFont {
         outPrecision = WmfOutPrecision.valueOf(leis.readUByte());\r
         clipPrecision = WmfClipPrecision.valueOf(leis.readUByte());\r
         quality = WmfFontQuality.valueOf(leis.readUByte());\r
-        int pitchAndFamily = leis.readUByte();\r
-        family = WmfFontFamilyClass.valueOf(pitchAndFamily & 0xF);\r
-        pitch = WmfFontPitch.valueOf((pitchAndFamily >>> 6) & 3);\r
+        pitchAndFamily = leis.readUByte();\r
         \r
         byte buf[] = new byte[32], b, readBytes = 0;\r
         do {\r
@@ -546,12 +551,16 @@ public class HwmfFont {
         return quality;\r
     }\r
 \r
+    public int getPitchAndFamily() {\r
+        return pitchAndFamily;\r
+    }\r
+\r
     public WmfFontFamilyClass getFamily() {\r
-        return family;\r
+        return WmfFontFamilyClass.valueOf(pitchAndFamily & 0xF);\r
     }\r
 \r
     public WmfFontPitch getPitch() {\r
-        return pitch;\r
+        return WmfFontPitch.valueOf((pitchAndFamily >>> 6) & 3);\r
     }\r
 \r
     public String getFacename() {\r
index 28a26a88f6f32bd30b222f741c370f6f8c565606..cae700e6dc6f4c8b3945ffe3fb1ad3c350e6a00f 100644 (file)
@@ -109,6 +109,6 @@ public enum HwmfMapMode {
         for (HwmfMapMode mm : values()) {\r
             if (mm.flag == flag) return mm;\r
         }\r
-        return null;\r
+        return MM_ISOTROPIC;\r
     }        \r
 }
\ No newline at end of file
index 2d6ab4e77bbdd38c3ed5480b975521a291b3f408..c597b1d9648407c776c5343604a32bb404580d63 100644 (file)
@@ -265,25 +265,9 @@ public class HwmfMisc {
 \r
         /**\r
          * A 16-bit unsigned integer that defines the foreground binary raster\r
-         * operation mixing mode. This MUST be one of the values:\r
-         * R2_BLACK = 0x0001,\r
-         * R2_NOTMERGEPEN = 0x0002,\r
-         * R2_MASKNOTPEN = 0x0003,\r
-         * R2_NOTCOPYPEN = 0x0004,\r
-         * R2_MASKPENNOT = 0x0005,\r
-         * R2_NOT = 0x0006,\r
-         * R2_XORPEN = 0x0007,\r
-         * R2_NOTMASKPEN = 0x0008,\r
-         * R2_MASKPEN = 0x0009,\r
-         * R2_NOTXORPEN = 0x000A,\r
-         * R2_NOP = 0x000B,\r
-         * R2_MERGENOTPEN = 0x000C,\r
-         * R2_COPYPEN = 0x000D,\r
-         * R2_MERGEPENNOT = 0x000E,\r
-         * R2_MERGEPEN = 0x000F,\r
-         * R2_WHITE = 0x0010\r
+         * operation mixing mode\r
          */\r
-        private int drawMode;\r
+        private HwmfBinaryRasterOp drawMode;\r
 \r
         @Override\r
         public HwmfRecordType getRecordType() {\r
@@ -292,7 +276,7 @@ public class HwmfMisc {
 \r
         @Override\r
         public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException {\r
-            drawMode = leis.readUShort();\r
+            drawMode = HwmfBinaryRasterOp.valueOf(leis.readUShort());\r
             return LittleEndianConsts.SHORT_SIZE;\r
         }\r
 \r
index 42e661b17eebd5c8e7a393800e6a04cd951bb2d9..eb6139c217e352e45f919860993cdef6f89e176a 100644 (file)
@@ -93,7 +93,7 @@ public class HwmfPalette {
         }\r
     }\r
 \r
-    public static abstract class WmfPaletteParent implements HwmfRecord  {\r
+    public static abstract class WmfPaletteParent implements HwmfRecord, HwmfObjectTableEntry  {\r
 \r
         /**\r
          * Start (2 bytes):  A 16-bit unsigned integer that defines the offset into the Palette Object when\r
@@ -121,6 +121,11 @@ public class HwmfPalette {
             return size;\r
         }\r
 \r
+        @Override\r
+        public final void draw(HwmfGraphics ctx) {\r
+            ctx.addObjectTableEntry(this);\r
+        }\r
+        \r
         protected List<PaletteEntry> getPaletteCopy() {\r
             List<PaletteEntry> newPalette = new ArrayList<PaletteEntry>();\r
             for (PaletteEntry et : palette) {\r
@@ -143,11 +148,6 @@ public class HwmfPalette {
             return HwmfRecordType.createPalette;\r
         }\r
 \r
-        @Override\r
-        public void draw(HwmfGraphics ctx) {\r
-            ctx.addObjectTableEntry(this);\r
-        }\r
-\r
         @Override\r
         public void applyObject(HwmfGraphics ctx) {\r
             ctx.getProperties().setPalette(getPaletteCopy());\r
@@ -165,7 +165,7 @@ public class HwmfPalette {
         }\r
 \r
         @Override\r
-        public void draw(HwmfGraphics ctx) {\r
+        public void applyObject(HwmfGraphics ctx) {\r
             HwmfDrawProperties props = ctx.getProperties();\r
             List<PaletteEntry> palette = props.getPalette();\r
             if (palette == null) {\r
@@ -192,7 +192,7 @@ public class HwmfPalette {
      * The META_RESIZEPALETTE record redefines the size of the logical palette that is defined in the\r
      * playback device context.\r
      */\r
-    public static class WmfResizePalette implements HwmfRecord {\r
+    public static class WmfResizePalette implements HwmfRecord, HwmfObjectTableEntry {\r
         /**\r
          * A 16-bit unsigned integer that defines the number of entries in\r
          * the logical palette.\r
@@ -212,6 +212,11 @@ public class HwmfPalette {
 \r
         @Override\r
         public void draw(HwmfGraphics ctx) {\r
+            ctx.addObjectTableEntry(this);\r
+        }\r
+        \r
+        @Override\r
+        public void applyObject(HwmfGraphics ctx) {\r
             HwmfDrawProperties props = ctx.getProperties();\r
             List<PaletteEntry> palette = props.getPalette();\r
             if (palette == null) {\r
@@ -292,7 +297,7 @@ public class HwmfPalette {
         }\r
 \r
         @Override\r
-        public void draw(HwmfGraphics ctx) {\r
+        public void applyObject(HwmfGraphics ctx) {\r
             HwmfDrawProperties props = ctx.getProperties();\r
             List<PaletteEntry> dest = props.getPalette();\r
             List<PaletteEntry> src = getPaletteCopy();\r
index 5ee3100fc04a7b2fdad09140735c8b008c45600f..9cd9b1e81c69d8e6437b92f419f171da0249f55d 100644 (file)
 \r
 package org.apache.poi.hwmf.record;\r
 \r
+/**\r
+ * Each ternary raster operation code represents a Boolean operation in which the values of the pixels in\r
+ * the source, the selected brush, and the destination are combined. Following are the three operands\r
+ * used in these operations.\r
+ *\r
+ * <table>\r
+ * <tr><th>Operand</th><th>Meaning</th></tr>\r
+ * <tr><td>D</td><td>Destination bitmap</td></tr>\r
+ * <tr><td>P</td><td>Selected brush (also called pattern)</td></tr>\r
+ * <tr><td>S</td><td>Source bitmap</td></tr>\r
+ * </table>\r
+ *\r
+ * Following are the Boolean operators used in these operations.\r
+ * <table>\r
+ * <tr><th>Operand</th><th>Meaning</th></tr>\r
+ * <tr><td>a</td><td>Bitwise AND</td></tr>\r
+ * <tr><td>n</td><td>Bitwise NOT (inverse)</td></tr>\r
+ * <tr><td>o</td><td>Bitwise OR</td></tr>\r
+ * <tr><td>x</td><td>Bitwise exclusive OR (XOR)</td></tr>\r
+ * </table>\r
+ *\r
+ * All Boolean operations are presented in reverse Polish notation. For example, the following operation\r
+ * replaces the values of the pixels in the destination bitmap with a combination of the pixel values of the\r
+ * source and brush: PSo.\r
+ * \r
+ * The following operation combines the values of the pixels in the source and brush with the pixel values\r
+ * of the destination bitmap: DPSoo (there are alternative spellings of some functions, so although a\r
+ * particular spelling MAY NOT be listed in the enumeration, an equivalent form SHOULD be).\r
+ * \r
+ * Each raster operation code is a 32-bit integer whose high-order word is a Boolean operation index and\r
+ * whose low-order word is the operation code. The 16-bit operation index is a zero-extended, 8-bit\r
+ * value that represents the result of the Boolean operation on predefined brush, source, and destination\r
+ * values. For example, the operation indexes for the PSo and DPSoo operations are shown in the\r
+ * following list.\r
+ * \r
+ * <table>\r
+ * <tr><th>P</th><th>S</th><th>D</th><th>DPo</th><th>DPan</th></tr>\r
+ * <tr><td>0</td><td>0</td><td>0</td><td>0</td><td>0</td></tr>\r
+ * <tr><td>0</td><td>0</td><td>1</td><td>0</td><td>1</td></tr>\r
+ * <tr><td>0</td><td>1</td><td>0</td><td>1</td><td>1</td></tr>\r
+ * <tr><td>0</td><td>1</td><td>1</td><td>1</td><td>1</td></tr>\r
+ * <tr><td>1</td><td>0</td><td>0</td><td>1</td><td>1</td></tr>\r
+ * <tr><td>1</td><td>0</td><td>1</td><td>1</td><td>1</td></tr>\r
+ * <tr><td>1</td><td>1</td><td>0</td><td>1</td><td>1</td></tr>\r
+ * <tr><td>1</td><td>1</td><td>1</td><td>1</td><td>1</td></tr>\r
+ * </table>\r
+ * \r
+ * The operation indexes are determined by reading the binary values in a column of the table from the\r
+ * bottom up. For example, in the PSo column, the binary value is 11111100, which is equivalent to 00FC\r
+ * (hexadecimal is implicit for these values), which is the operation index for PSo.\r
+ * \r
+ * Using this method, DPSoo can be seen to have the operation index 00FE. Operation indexes define the\r
+ * locations of corresponding raster operation codes in the preceding enumeration. The PSo operation is\r
+ * in line 252 (0x00FC) of the enumeration; DPSoo is in line 254 (0x00FE).\r
+ * \r
+ * The most commonly used raster operations have been given explicit enumeration names, which\r
+ * SHOULD be used; examples are PATCOPY and WHITENESS.\r
+ * \r
+ * When the source and destination bitmaps are monochrome, a bit value of 0 represents a black pixel\r
+ * and a bit value of 1 represents a white pixel. When the source and the destination bitmaps are color,\r
+ * those colors are represented with red green blue (RGB) values.\r
+ */\r
 public enum HwmfTernaryRasterOp {\r
     BLACKNESS(0x0000,0x0042,"0"),\r
     DPSOON(0x0001,0x0289,"DPSoon"),\r
@@ -274,17 +336,17 @@ public enum HwmfTernaryRasterOp {
     PSDNOO(0x00FD,0x0A0A,"PSDnoo"),\r
     DPSOO(0x00FE,0x02A9,"DPSoo"),\r
     WHITENESS(0x00FF,0x0062,"1");\r
-    \r
+\r
     int opIndex;\r
     int opCode;\r
     String opCmd;\r
-    \r
+\r
     HwmfTernaryRasterOp(int opIndex, int opCode, String opCmd) {\r
         this.opIndex=opIndex;\r
         this.opCode=opCode;\r
         this.opCmd=opCmd;\r
     }\r
-    \r
+\r
     public static HwmfTernaryRasterOp valueOf(int opIndex) {\r
         for (HwmfTernaryRasterOp bb : HwmfTernaryRasterOp.values()) {\r
             if (bb.opIndex == opIndex) {\r
@@ -293,11 +355,11 @@ public enum HwmfTernaryRasterOp {
         }\r
         return null;\r
     }\r
-    \r
+\r
     public String describeCmd() {\r
         String stack[] = new String[10];\r
         int stackPnt = 0;\r
-        \r
+\r
         for (char c : opCmd.toCharArray()) {\r
             switch (c) {\r
                 case 'S':\r
index 8c6c41549b5e262face5b1340c02d35a7085530d..e4e4ecb4d8df7f22f2a8ef05992adf4fb89216d7 100644 (file)
@@ -19,7 +19,6 @@ package org.apache.poi.hwmf.record;
 \r
 import java.awt.geom.Rectangle2D;\r
 import java.io.IOException;\r
-import java.text.AttributedString;\r
 \r
 import org.apache.poi.hwmf.draw.HwmfDrawProperties;\r
 import org.apache.poi.hwmf.draw.HwmfGraphics;\r
@@ -29,10 +28,13 @@ import org.apache.poi.util.BitFieldFactory;
 import org.apache.poi.util.LittleEndianConsts;\r
 import org.apache.poi.util.LittleEndianInputStream;\r
 import org.apache.poi.util.LocaleUtil;\r
+import org.apache.poi.util.POILogFactory;\r
+import org.apache.poi.util.POILogger;\r
 import org.apache.poi.util.RecordFormatException;\r
 \r
 public class HwmfText {\r
-\r
+    private static final POILogger logger = POILogFactory.getLogger(HwmfText.class);\r
+    \r
     /**\r
      * The META_SETTEXTCHAREXTRA record defines inter-character spacing for text justification in the \r
      * playback device context. Spacing is added to the white space between each character, including\r
@@ -185,6 +187,48 @@ public class HwmfText {
      */\r
     public static class WmfExtTextOut implements HwmfRecord {\r
 \r
+        /**\r
+         * Indicates that the background color that is defined in the playback device context \r
+         * SHOULD be used to fill the rectangle.\r
+         */ \r
+        private static final BitField ETO_OPAQUE = BitFieldFactory.getInstance(0x0002);\r
+        \r
+        /**\r
+         * Indicates that the text SHOULD be clipped to the rectangle.\r
+         */\r
+        private static final BitField ETO_CLIPPED = BitFieldFactory.getInstance(0x0004);\r
+\r
+        /**\r
+         * Indicates that the string to be output SHOULD NOT require further processing \r
+         * with respect to the placement of the characters, and an array of character \r
+         * placement values SHOULD be provided. This character placement process is \r
+         * useful for fonts in which diacritical characters affect character spacing.\r
+         */\r
+        private static final BitField ETO_GLYPH_INDEX = BitFieldFactory.getInstance(0x0010);\r
+\r
+        /**\r
+         * Indicates that the text MUST be laid out in right-to-left reading order, instead of \r
+         * the default left-to-right order. This SHOULD be applied only when the font that is \r
+         * defined in the playback device context is either Hebrew or Arabic.\r
+         */\r
+        private static final BitField ETO_RTLREADING = BitFieldFactory.getInstance(0x0080);\r
+\r
+        /**\r
+         * Indicates that to display numbers, digits appropriate to the locale SHOULD be used.\r
+         */\r
+        private static final BitField ETO_NUMERICSLOCAL = BitFieldFactory.getInstance(0x0400);\r
+\r
+        /**\r
+         * Indicates that to display numbers, European digits SHOULD be used.\r
+         */\r
+        private static final BitField ETO_NUMERICSLATIN = BitFieldFactory.getInstance(0x0800);\r
+\r
+        /**\r
+         * Indicates that both horizontal and vertical character displacement values \r
+         * SHOULD be provided.\r
+         */\r
+        private static final BitField ETO_PDY = BitFieldFactory.getInstance(0x2000);\r
+\r
         /**\r
          * A 16-bit signed integer that defines the y-coordinate, in logical units, where the \r
         text string is to be located.\r
@@ -199,40 +243,12 @@ public class HwmfText {
          * A 16-bit signed integer that defines the length of the string.\r
          */\r
         private int stringLength;\r
-        /**\r
-         * A 16-bit unsigned integer that defines the use of the application-defined \r
-         * rectangle. This member can be a combination of one or more values in the \r
-         * ExtTextOutOptions Flags:\r
-         * \r
-         * ETO_OPAQUE (0x0002):\r
-         * Indicates that the background color that is defined in the playback device context \r
-         * SHOULD be used to fill the rectangle.\r
-         * \r
-         * ETO_CLIPPED (0x0004):\r
-         * Indicates that the text SHOULD be clipped to the rectangle.\r
-         * \r
-         * ETO_GLYPH_INDEX (0x0010):\r
-         * Indicates that the string to be output SHOULD NOT require further processing \r
-         * with respect to the placement of the characters, and an array of character \r
-         * placement values SHOULD be provided. This character placement process is \r
-         * useful for fonts in which diacritical characters affect character spacing.\r
-         * \r
-         * ETO_RTLREADING (0x0080):\r
-         * Indicates that the text MUST be laid out in right-to-left reading order, instead of \r
-         * the default left-to-right order. This SHOULD be applied only when the font that is \r
-         * defined in the playback device context is either Hebrew or Arabic. <37>\r
-         * \r
-         * ETO_NUMERICSLOCAL (0x0400):\r
-         * Indicates that to display numbers, digits appropriate to the locale SHOULD be \r
-         * used.\r
-         * \r
-         * ETO_NUMERICSLATIN (0x0800):\r
-         * Indicates that to display numbers, European digits SHOULD be used. <39>\r
-         * \r
-         * ETO_PDY (0x2000):\r
-         * Indicates that both horizontal and vertical character displacement values \r
-         * SHOULD be provided.\r
-         */\r
+\r
+         /**\r
+          * A 16-bit unsigned integer that defines the use of the application-defined \r
+          * rectangle. This member can be a combination of one or more values in the \r
+          * ExtTextOutOptions Flags (ETO_*)\r
+          */\r
         private int fwOpts;\r
         /**\r
          * An optional 8-byte Rect Object (section 2.2.2.18) that defines the \r
@@ -275,7 +291,8 @@ public class HwmfText {
             \r
             int size = 4*LittleEndianConsts.SHORT_SIZE;\r
             \r
-            if (fwOpts != 0 && size+8<=remainingRecordSize) {\r
+            // Check if we have a rectangle\r
+            if ((ETO_OPAQUE.isSet(fwOpts) || ETO_CLIPPED.isSet(fwOpts)) && size+8<=remainingRecordSize) {\r
                 // the bounding rectangle is optional and only read when fwOpts are given\r
                 left = leis.readShort();\r
                 top = leis.readShort();\r
@@ -289,16 +306,20 @@ public class HwmfText {
             text = new String(buf, 0, stringLength, LocaleUtil.CHARSET_1252);\r
             size += buf.length;\r
             \r
-            if (size < remainingRecordSize) {\r
-                if (size + stringLength*LittleEndianConsts.SHORT_SIZE < remainingRecordSize) {\r
-                    throw new RecordFormatException("can't read Dx array - given recordSize doesn't contain enough values for string length "+stringLength);\r
-                }\r
-                \r
-                dx = new int[stringLength];\r
-                for (int i=0; i<dx.length; i++) {\r
-                    dx[i] = leis.readShort();\r
-                }\r
-                size += dx.length*LittleEndianConsts.SHORT_SIZE;\r
+            if (size >= remainingRecordSize) {\r
+                logger.log(POILogger.INFO, "META_EXTTEXTOUT doesn't contain character tracking info");\r
+                return size;\r
+            }\r
+            \r
+            int dxLen = Math.min(stringLength, (remainingRecordSize-size)/LittleEndianConsts.SHORT_SIZE);\r
+            if (dxLen < stringLength) {\r
+                logger.log(POILogger.WARN, "META_EXTTEXTOUT tracking info doesn't cover all characters");\r
+            }\r
+\r
+            dx = new int[stringLength]; \r
+            for (int i=0; i<dxLen; i++) {\r
+                dx[i] = leis.readShort();\r
+                size += LittleEndianConsts.SHORT_SIZE;\r
             }\r
             \r
             return size;\r
@@ -306,7 +327,8 @@ public class HwmfText {
 \r
         @Override\r
         public void draw(HwmfGraphics ctx) {\r
-\r
+            Rectangle2D bounds = new Rectangle2D.Double(x, y, 0, 0);\r
+            ctx.drawString(text, bounds, dx);\r
         }\r
     }\r
     \r
index 6dbde2bc4ff8fc7fd4642ce706c5453d7f600894..d6c4d196b547d3927306e6456a3c820025be1c66 100644 (file)
@@ -376,7 +376,7 @@ public class HwmfWindowing {
      * The META_OFFSETCLIPRGN record moves the clipping region in the playback device context by the\r
      * specified offsets.\r
      */\r
-    public static class WmfOffsetClipRgn implements HwmfRecord {\r
+    public static class WmfOffsetClipRgn implements HwmfRecord, HwmfObjectTableEntry {\r
 \r
         /**\r
          * A 16-bit signed integer that defines the number of logical units to move up or down.\r
@@ -402,7 +402,11 @@ public class HwmfWindowing {
 \r
         @Override\r
         public void draw(HwmfGraphics ctx) {\r
-\r
+            ctx.addObjectTableEntry(this);\r
+        }\r
+        \r
+        @Override\r
+        public void applyObject(HwmfGraphics ctx) {\r
         }\r
     }\r
 \r
@@ -410,7 +414,7 @@ public class HwmfWindowing {
      * The META_EXCLUDECLIPRECT record sets the clipping region in the playback device context to the\r
      * existing clipping region minus the specified rectangle.\r
      */\r
-    public static class WmfExcludeClipRect implements HwmfRecord {\r
+    public static class WmfExcludeClipRect implements HwmfRecord, HwmfObjectTableEntry {\r
 \r
         /**\r
          * A 16-bit signed integer that defines the y-coordinate, in logical units, of the\r
@@ -452,7 +456,11 @@ public class HwmfWindowing {
 \r
         @Override\r
         public void draw(HwmfGraphics ctx) {\r
-\r
+            ctx.addObjectTableEntry(this);\r
+        }\r
+        \r
+        @Override\r
+        public void applyObject(HwmfGraphics ctx) {\r
         }\r
     }\r
 \r
@@ -461,7 +469,7 @@ public class HwmfWindowing {
      * The META_INTERSECTCLIPRECT record sets the clipping region in the playback device context to the\r
      * intersection of the existing clipping region and the specified rectangle.\r
      */\r
-    public static class WmfIntersectClipRect implements HwmfRecord {\r
+    public static class WmfIntersectClipRect implements HwmfRecord, HwmfObjectTableEntry {\r
 \r
         /**\r
          * A 16-bit signed integer that defines the y-coordinate, in logical units, of the\r
@@ -503,7 +511,11 @@ public class HwmfWindowing {
 \r
         @Override\r
         public void draw(HwmfGraphics ctx) {\r
-\r
+            ctx.addObjectTableEntry(this);\r
+        }\r
+        \r
+        @Override\r
+        public void applyObject(HwmfGraphics ctx) {\r
         }\r
     }\r
 \r
index a5144d4ae3b6fb2ad6bd4e9264f61304cc53cf99..5b7601f7a32e792d20faa7ad53aa5ef665d41ec2 100644 (file)
@@ -19,10 +19,8 @@ package org.apache.poi.hwmf.usermodel;
 \r
 import java.awt.Dimension;\r
 import java.awt.Graphics2D;\r
-import java.awt.GraphicsConfiguration;\r
 import java.awt.geom.AffineTransform;\r
 import java.awt.geom.Rectangle2D;\r
-import java.awt.geom.Rectangle2D.Double;\r
 import java.io.BufferedInputStream;\r
 import java.io.IOException;\r
 import java.io.InputStream;\r
@@ -38,9 +36,13 @@ import org.apache.poi.hwmf.record.HwmfRecordType;
 import org.apache.poi.hwmf.record.HwmfWindowing.WmfSetWindowExt;\r
 import org.apache.poi.hwmf.record.HwmfWindowing.WmfSetWindowOrg;\r
 import org.apache.poi.util.LittleEndianInputStream;\r
+import org.apache.poi.util.POILogFactory;\r
+import org.apache.poi.util.POILogger;\r
 import org.apache.poi.util.Units;\r
 \r
 public class HwmfPicture {\r
+    private static final POILogger logger = POILogFactory.getLogger(HwmfPicture.class);\r
+    \r
     final List<HwmfRecord> records = new ArrayList<HwmfRecord>();\r
     final HwmfPlaceableHeader placeableHeader;\r
     final HwmfHeader header;\r
@@ -52,6 +54,10 @@ public class HwmfPicture {
         header = new HwmfHeader(leis);\r
         \r
         for (;;) {\r
+            if (leis.available() < 6) {\r
+                logger.log(POILogger.ERROR, "unexpected eof - wmf file was truncated");\r
+                break;\r
+            }\r
             // recordSize in DWORDs\r
             long recordSize = leis.readUInt()*2;\r
             int recordFunction = leis.readShort();\r
index 7b3b785444db35521687de1ced2eda1a6e97b77a..91d23664d828c80ab72c61dbdc25d4ebef48e662 100644 (file)
@@ -20,7 +20,10 @@ package org.apache.poi.hslf.model;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 
-import java.awt.geom.*;
+import java.awt.geom.Area;
+import java.awt.geom.Line2D;
+import java.awt.geom.Path2D;
+import java.awt.geom.Rectangle2D;
 
 import org.apache.poi.hslf.usermodel.HSLFFreeformShape;
 import org.junit.Test;
@@ -38,7 +41,7 @@ public final class TestFreeform {
     @Test
     public void testClosedPath() {
 
-        GeneralPath path1 = new GeneralPath();
+        Path2D.Double path1 = new Path2D.Double();
         path1.moveTo(100, 100);
         path1.lineTo(200, 100);
         path1.lineTo(200, 200);
@@ -55,7 +58,7 @@ public final class TestFreeform {
     @Test
     public void testLine() {
 
-        GeneralPath path1 = new GeneralPath(new Line2D.Double(100, 100, 200, 100));
+        Path2D.Double path1 = new Path2D.Double(new Line2D.Double(100, 100, 200, 100));
 
         HSLFFreeformShape p = new HSLFFreeformShape();
         p.setPath(path1);
@@ -67,7 +70,7 @@ public final class TestFreeform {
     @Test
     public void testRectangle() {
 
-        GeneralPath path1 = new GeneralPath(new Rectangle2D.Double(100, 100, 200, 50));
+        Path2D.Double path1 = new Path2D.Double(new Rectangle2D.Double(100, 100, 200, 50));
 
         HSLFFreeformShape p = new HSLFFreeformShape();
         p.setPath(path1);
@@ -84,8 +87,8 @@ public final class TestFreeform {
     public void test54188() {
 
         HSLFFreeformShape p = new HSLFFreeformShape();
-        GeneralPath path = p.getPath();
-        GeneralPath emptyPath = new GeneralPath();
+        Path2D.Double path = p.getPath();
+        Path2D.Double emptyPath = new Path2D.Double();
         assertEquals(emptyPath.getBounds2D(), path.getBounds2D());
     }
 }
index 7dc57f772ce6e056391d90f7a477f595c322b881..08d6c64211367d1b15b5994a9b30efc0c843e7a1 100644 (file)
@@ -30,23 +30,22 @@ import java.awt.Rectangle;
 import java.awt.image.BufferedImage;
 import java.io.ByteArrayInputStream;
 import java.io.File;
+import java.io.IOException;
 import java.io.InputStream;
 import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
 import java.util.BitSet;
-import java.util.HashMap;
 import java.util.List;
-import java.util.Map;
 
 import javax.imageio.ImageIO;
 
 import org.apache.poi.POIDataSamples;
 import org.apache.poi.ddf.EscherBSERecord;
 import org.apache.poi.hssf.usermodel.DummyGraphics2d;
-import org.apache.poi.sl.draw.Drawable;
+import org.apache.poi.sl.draw.DrawFactory;
 import org.apache.poi.sl.usermodel.PictureData.PictureType;
 import org.apache.poi.sl.usermodel.Slide;
 import org.apache.poi.sl.usermodel.SlideShow;
-import org.apache.poi.util.JvmBugs;
 import org.junit.Ignore;
 import org.junit.Test;
 
@@ -64,7 +63,7 @@ public final class TestPicture {
      *
      */
     @Test
-    public void multiplePictures() throws Exception {
+    public void multiplePictures() throws IOException {
         HSLFSlideShow ppt = new HSLFSlideShow();
 
         HSLFSlide s = ppt.createSlide();
@@ -92,6 +91,8 @@ public final class TestPicture {
         EscherBSERecord bse3 = pict.getEscherBSERecord();
         assertSame(bse2, bse3);
         assertEquals(3, bse1.getRef());
+        
+        ppt.close();
     }
 
     /**
@@ -99,7 +100,7 @@ public final class TestPicture {
      * was not found. The correct behaviour is to return null.
      */
     @Test
-    public void bug46122() {
+    public void bug46122() throws IOException {
         HSLFSlideShow ppt = new HSLFSlideShow();
         HSLFSlide slide = ppt.createSlide();
         HSLFPictureData pd = HSLFPictureData.create(PictureType.PNG);
@@ -112,10 +113,12 @@ public final class TestPicture {
         BufferedImage img = new BufferedImage(100, 100, BufferedImage.TYPE_INT_RGB);
         Graphics2D graphics = img.createGraphics();
         pict.draw(graphics);
+        
+        ppt.close();
     }
 
     @Test
-    public void macImages() throws Exception {
+    public void macImages() throws IOException {
         HSLFSlideShowImpl hss = new HSLFSlideShowImpl(_slTests.openResourceAsStream("53446.ppt"));
 
         List<HSLFPictureData> pictures = hss.getPictureData();
@@ -154,11 +157,14 @@ public final class TestPicture {
                     break;
             }
         }
+        
+        hss.close();
     }
 
     @Test
     @Ignore("Just for visual validation - antialiasing is different on various systems")
-    public void bug54541() throws Exception {
+    public void bug54541()
+    throws IOException, ClassNotFoundException, SecurityException, NoSuchMethodException, IllegalArgumentException, InstantiationException, IllegalAccessException, InvocationTargetException {
         String files[] = {
 //            "sample_pptx_grouping_issues.pptx",
 //            "54542_cropped_bitmap.pptx",
@@ -196,7 +202,7 @@ public final class TestPicture {
                 } else {
                     BufferedImage img = new BufferedImage(pg.width, pg.height, BufferedImage.TYPE_INT_ARGB);
                     Graphics2D graphics = img.createGraphics();
-                    fixFonts(graphics);
+                    DrawFactory.getInstance(graphics).fixFonts(graphics);
                     slide.draw(graphics);
                     graphics.setColor(Color.BLACK);
                     graphics.setStroke(new BasicStroke(1));
@@ -204,16 +210,8 @@ public final class TestPicture {
                     ImageIO.write(img, "PNG", new File(file.replaceFirst(".pptx?", "-")+slideNo+".png"));
                 }
             }
+            
+            ss.close();
         }
     }
-    
-    @SuppressWarnings("unchecked")
-    private void fixFonts(Graphics2D graphics) {
-        if (!JvmBugs.hasLineBreakMeasurerBug()) return;
-        Map<String,String> fontMap = (Map<String,String>)graphics.getRenderingHint(Drawable.FONT_MAP);
-        if (fontMap == null) fontMap = new HashMap<String,String>();
-        fontMap.put("Calibri", "Lucida Sans");
-        fontMap.put("Cambria", "Lucida Bright");
-        graphics.setRenderingHint(Drawable.FONT_MAP, fontMap);        
-    }
 }
index 7ddacf475a97d2c16da5bb301d42b00d323173f2..78e6534569397e327a3e870ea633028214107955 100644 (file)
@@ -22,6 +22,7 @@ import static org.junit.Assert.assertEquals;
 import java.awt.Dimension;\r
 import java.awt.Graphics2D;\r
 import java.awt.RenderingHints;\r
+import java.awt.geom.Rectangle2D;\r
 import java.awt.image.BufferedImage;\r
 import java.io.File;\r
 import java.io.FileFilter;\r
@@ -64,7 +65,7 @@ public class TestHwmfParsing {
     @Ignore("This is work-in-progress and not a real unit test ...")\r
     public void paint() throws IOException {\r
         File f = POIDataSamples.getSlideShowInstance().getFile("santa.wmf");\r
-//        File f = new File("E:\\project\\poi\\misc\\govdocs-ppt", "000133-0001.wmf");\r
+        // File f = new File("bla.wmf");\r
         FileInputStream fis = new FileInputStream(f);\r
         HwmfPicture wmf = new HwmfPicture(fis);\r
         fis.close();\r
@@ -73,6 +74,11 @@ public class TestHwmfParsing {
         int width = Units.pointsToPixel(dim.getWidth());\r
         // keep aspect ratio for height\r
         int height = Units.pointsToPixel(dim.getHeight());\r
+        double max = Math.max(width, height);\r
+        if (max > 1500) {\r
+            width *= 1500/max;\r
+            height *= 1500/max;\r
+        }\r
         \r
         BufferedImage bufImg = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);\r
         Graphics2D g = bufImg.createGraphics();\r
@@ -81,7 +87,7 @@ public class TestHwmfParsing {
         g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);\r
         g.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);\r
         \r
-        wmf.draw(g);\r
+        wmf.draw(g, new Rectangle2D.Double(0,0,width,height));\r
 \r
         g.dispose();\r
         \r
index 49338e0b6818566931d3bfae8ab8e2607130c5be..24c6f7f91b8b82838542826101acb480cb4199bb 100644 (file)
@@ -22,7 +22,7 @@ import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 
-import java.awt.geom.GeneralPath;
+import java.awt.geom.Path2D;
 import java.awt.geom.Rectangle2D;
 import java.net.URL;
 import java.util.Enumeration;
@@ -44,7 +44,7 @@ public class TestPresetGeometries {
                 }
             });
             for(Path p : geom){
-                GeneralPath path = p.getPath(ctx);
+                Path2D path = p.getPath(ctx);
                 assertNotNull(path);
             }
         }