]> source.dussan.org Git - poi.git/commitdiff
initial support for rendering powerpoint slides into images
authorYegor Kozlov <yegor@apache.org>
Thu, 17 Apr 2008 15:08:03 +0000 (15:08 +0000)
committerYegor Kozlov <yegor@apache.org>
Thu, 17 Apr 2008 15:08:03 +0000 (15:08 +0000)
git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@649143 13f79535-47bb-0310-9956-ffa450edef68

28 files changed:
src/documentation/content/xdocs/hslf/how-to-shapes.xml
src/scratchpad/examples/src/org/apache/poi/hslf/examples/PPT2PNG.java [new file with mode: 0755]
src/scratchpad/src/org/apache/poi/hslf/model/AutoShape.java
src/scratchpad/src/org/apache/poi/hslf/model/AutoShapes.java [new file with mode: 0755]
src/scratchpad/src/org/apache/poi/hslf/model/Fill.java
src/scratchpad/src/org/apache/poi/hslf/model/Freeform.java
src/scratchpad/src/org/apache/poi/hslf/model/Line.java
src/scratchpad/src/org/apache/poi/hslf/model/MasterSheet.java
src/scratchpad/src/org/apache/poi/hslf/model/OLEShape.java
src/scratchpad/src/org/apache/poi/hslf/model/PPGraphics2D.java
src/scratchpad/src/org/apache/poi/hslf/model/Picture.java
src/scratchpad/src/org/apache/poi/hslf/model/Shape.java
src/scratchpad/src/org/apache/poi/hslf/model/ShapeGroup.java
src/scratchpad/src/org/apache/poi/hslf/model/ShapeOutline.java [new file with mode: 0755]
src/scratchpad/src/org/apache/poi/hslf/model/ShapePainter.java [new file with mode: 0755]
src/scratchpad/src/org/apache/poi/hslf/model/Sheet.java
src/scratchpad/src/org/apache/poi/hslf/model/SimpleShape.java
src/scratchpad/src/org/apache/poi/hslf/model/Slide.java
src/scratchpad/src/org/apache/poi/hslf/model/SlideMaster.java
src/scratchpad/src/org/apache/poi/hslf/model/Table.java
src/scratchpad/src/org/apache/poi/hslf/model/TableCell.java
src/scratchpad/src/org/apache/poi/hslf/model/TextBox.java
src/scratchpad/src/org/apache/poi/hslf/model/TextPainter.java [new file with mode: 0755]
src/scratchpad/src/org/apache/poi/hslf/model/TextRun.java
src/scratchpad/src/org/apache/poi/hslf/model/TextShape.java
src/scratchpad/src/org/apache/poi/hslf/record/StyleTextPropAtom.java
src/scratchpad/src/org/apache/poi/hslf/usermodel/RichTextRun.java
src/scratchpad/testcases/org/apache/poi/hslf/usermodel/TestSlideOrdering.java

index eec5ffcafabbb102ed5f6a6c5082ac8db435eb8c..6460f2b1db4a8221b5c68f2ca9c975e01fff6ca9 100644 (file)
@@ -44,6 +44,7 @@
                     <li><link href="#OLE">How to retrieve embedded OLE objects</link></li>
                     <li><link href="#Freeform">How to create shapes of arbitrary geometry</link></li>
                     <li><link href="#Graphics2D">Shapes and Graphics2D</link></li>
+                    <li><link href="#Render">How to convert slides into images</link></li>
                 </ul>
             </section>
             <section><title>Features</title>
 
                    </source>
                   </section>
-    
+
+                <anchor id="Render"/>
+                <section><title>Export PowerPoint slides into java.awt.Graphics2D</title>
+                  <p>
+                    HSLF provides a way to export slides into images. You can capture slides into java.awt.Graphics2D object (or any other) 
+                    and serialize it into a PNG or JPEG format. Please note, although HSLF attempts to render slides as close to PowerPoint as possible, 
+                    the output might look differently from PowerPoint due to the following reasons: 
+                  </p>
+            <ul>
+              <li>Java2D renders fonts differently vs PowerPoint. There are always some differences in the way the font glyphs are painted</li>   
+              <li>HSLF uses java.awt.font.LineBreakMeasurer to break text into lines. PowerPoint may do it in a different way.</li>
+              <li>If a font from the presentation is not avaiable, then the JDK default font will be used.</li>
+            </ul>
+            <p>
+            Current Limitations:
+            </p>
+            <ul>
+              <li>Some types of shapes are not yet supported (WordArt, complex auto-shapes)</li>
+              <li>Only Bitmap images (PNG, JPEG, DIB) can be rendered in Java</li>  
+            </ul>
+                  <source>
+        FileInputStream is = new FileInputStream("slideshow.ppt");
+        SlideShow ppt = new SlideShow(is);
+        is.close();
+        
+        Dimension pgsize = ppt.getPageSize();
+
+        Slide[] slide = ppt.getSlides();
+        for (int i = 0; i &lt; slide.length; i++) {
+
+            BufferedImage img = new BufferedImage(pgsize.width, pgsize.height, BufferedImage.TYPE_INT_RGB);
+            Graphics2D graphics = img.createGraphics();
+            //clear the drawing area
+            graphics.setPaint(Color.white);
+            graphics.fill(new Rectangle2D.Float(0, 0, pgsize.width, pgsize.height));
+
+            //render
+            slide[i].draw(graphics);
+
+            //save the output
+            FileOutputStream out = new FileOutputStream("slide-"  + (i+1) + ".png");
+            javax.imageio.ImageIO.write(img, "png", out);
+            out.close();
+        }
+
+                  </source>
+                  </section>
+                  
                 </section>
         </section>
     </body>
diff --git a/src/scratchpad/examples/src/org/apache/poi/hslf/examples/PPT2PNG.java b/src/scratchpad/examples/src/org/apache/poi/hslf/examples/PPT2PNG.java
new file mode 100755 (executable)
index 0000000..aa81450
--- /dev/null
@@ -0,0 +1,103 @@
+\r
+/* ====================================================================\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.hslf.examples;\r
+\r
+import org.apache.poi.hslf.usermodel.*;\r
+import org.apache.poi.hslf.model.*;\r
+import org.apache.poi.hslf.record.TextHeaderAtom;\r
+\r
+import javax.imageio.ImageIO;\r
+import java.io.IOException;\r
+import java.io.FileOutputStream;\r
+import java.io.File;\r
+import java.io.FileInputStream;\r
+import java.awt.*;\r
+import java.awt.image.BufferedImage;\r
+import java.awt.geom.AffineTransform;\r
+import java.awt.geom.Rectangle2D;\r
+\r
+/**\r
+ * Demonstrates how you can use HSLF to convert each slide into a PNG image\r
+ *\r
+ * @author Yegor Kozlov\r
+ */\r
+public class PPT2PNG {\r
+\r
+    public static void main(String args[]) throws Exception {\r
+\r
+        if (args.length == 0) {\r
+            usage();\r
+            return;\r
+        }\r
+\r
+        int slidenum = -1;\r
+        float scale = 1;\r
+        String file = null;\r
+\r
+        for (int i = 0; i < args.length; i++) {\r
+            if (args[i].startsWith("-")) {\r
+                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
+                }\r
+            } else {\r
+                file = args[i];\r
+            }\r
+        }\r
+        if(file == null){\r
+            usage();\r
+            return;\r
+        }\r
+\r
+        FileInputStream is = new FileInputStream(file);\r
+        SlideShow ppt = new SlideShow(is);\r
+        is.close();\r
+\r
+        Dimension pgsize = ppt.getPageSize();\r
+        int width = (int)(pgsize.width*scale);\r
+        int height = (int)(pgsize.height*scale);\r
+\r
+        Slide[] slide = ppt.getSlides();\r
+        for (int i = 0; i < slide.length; i++) {\r
+            if (slidenum != -1 && slidenum != (i+1)) continue;\r
+\r
+            String title = slide[i].getTitle();\r
+            System.out.println("Rendering slide "+slide[i].getSlideNumber() + (title == null ? "" : ": " + title));\r
+\r
+            BufferedImage img = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);\r
+            Graphics2D graphics = img.createGraphics();\r
+            graphics.setPaint(Color.white);\r
+            graphics.fill(new Rectangle2D.Float(0, 0, width, height));\r
+\r
+            graphics.scale((double)width/pgsize.width, (double)height/pgsize.height);\r
+\r
+            slide[i].draw(graphics);\r
+\r
+            String fname = file.replaceAll("\\.ppt", "-" + (i+1) + ".png");\r
+            FileOutputStream out = new FileOutputStream(fname);\r
+            ImageIO.write(img, "png", out);\r
+            out.close();\r
+        }\r
+    }\r
+\r
+    private static void usage(){\r
+        System.out.println("Usage: PPT2PNG [-scale <scale> -slide <num>] ppt");\r
+    }\r
+}\r
index cf65a9b13cb2cf3007cab12b461a81f71925f08f..dc5f798cd86440a90152c7acc93bc63f338b2708 100644 (file)
@@ -18,6 +18,9 @@
 package org.apache.poi.hslf.model;
 
 import org.apache.poi.ddf.*;
+import org.apache.poi.util.POILogger;
+
+import java.awt.geom.Rectangle2D;
 
 /**
  * Represents an AutoShape.
@@ -102,4 +105,17 @@ public class AutoShape extends TextShape {
 
         setEscherProperty((short)(EscherProperties.GEOMETRY__ADJUSTVALUE + idx), val);
     }
+
+    public java.awt.Shape getOutline(){
+        ShapeOutline outline = AutoShapes.getShapeOutline(getShapeType());
+        Rectangle2D anchor = getAnchor2D();
+        if(outline == null){
+            logger.log(POILogger.WARN, "getOutline() is not implemented for " + ShapeTypes.typeName(getShapeType()));
+            return anchor;
+        } else {
+            java.awt.Shape shape = outline.getOutline(this);
+            return AutoShapes.transform(shape, anchor);
+        }
+    }
+
 }
diff --git a/src/scratchpad/src/org/apache/poi/hslf/model/AutoShapes.java b/src/scratchpad/src/org/apache/poi/hslf/model/AutoShapes.java
new file mode 100755 (executable)
index 0000000..9d9f5e2
--- /dev/null
@@ -0,0 +1,293 @@
+/* ====================================================================\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
+package org.apache.poi.hslf.model;\r
+\r
+import org.apache.poi.ddf.EscherProperties;\r
+\r
+import java.awt.geom.*;\r
+\r
+/**\r
+ * Stores definition of auto-shapes.\r
+ * See the Office Drawing 97-2007 Binary Format Specification for details.\r
+ *\r
+ * TODO: follow the spec and define all the auto-shapes\r
+ *\r
+ * @author Yegor Kozlov\r
+ */\r
+public class AutoShapes {\r
+    protected static ShapeOutline[] shapes;\r
+\r
+\r
+    /**\r
+     * Return shape outline by shape type\r
+     * @param type shape type see {@link ShapeTypes}\r
+     *\r
+     * @return the shape outline\r
+     */\r
+    public static ShapeOutline getShapeOutline(int type){\r
+        ShapeOutline outline = shapes[type];\r
+        return outline;\r
+    }\r
+\r
+    /**\r
+     * Auto-shapes are defined in the [0,21600] coordinate system.\r
+     * We need to transform it into normal slide coordinates\r
+     *\r
+    */\r
+    public static java.awt.Shape transform(java.awt.Shape outline, Rectangle2D anchor){\r
+        AffineTransform at = new AffineTransform();\r
+        at.translate(anchor.getX(), anchor.getY());\r
+        at.scale(\r
+                1.0f/21600*anchor.getWidth(),\r
+                1.0f/21600*anchor.getHeight()\r
+        );\r
+        return at.createTransformedShape(outline);\r
+    }\r
+\r
+    static {\r
+        shapes = new ShapeOutline[255];\r
+\r
+        shapes[ShapeTypes.Rectangle] = new ShapeOutline(){\r
+            public java.awt.Shape getOutline(Shape shape){\r
+                Rectangle2D path = new Rectangle2D.Float(0, 0, 21600, 21600);\r
+                return path;\r
+            }\r
+        };\r
+\r
+        shapes[ShapeTypes.RoundRectangle] = new ShapeOutline(){\r
+            public java.awt.Shape getOutline(Shape shape){\r
+                int adjval = shape.getEscherProperty(EscherProperties.GEOMETRY__ADJUSTVALUE, 5400);\r
+                RoundRectangle2D path = new RoundRectangle2D.Float(0, 0, 21600, 21600, adjval, adjval);\r
+                return path;\r
+            }\r
+        };\r
+\r
+        shapes[ShapeTypes.Ellipse] = new ShapeOutline(){\r
+            public java.awt.Shape getOutline(Shape shape){\r
+                Ellipse2D path = new Ellipse2D.Float(0, 0, 21600, 21600);\r
+                return path;\r
+            }\r
+        };\r
+\r
+        shapes[ShapeTypes.Diamond] = new ShapeOutline(){\r
+            public java.awt.Shape getOutline(Shape shape){\r
+                GeneralPath path = new GeneralPath();\r
+                path.moveTo(10800, 0);\r
+                path.lineTo(21600, 10800);\r
+                path.lineTo(10800, 21600);\r
+                path.lineTo(0, 10800);\r
+                path.closePath();\r
+                return path;\r
+           }\r
+        };\r
+\r
+        //m@0,l,21600r21600\r
+        shapes[ShapeTypes.IsocelesTriangle] = new ShapeOutline(){\r
+            public java.awt.Shape getOutline(Shape shape){\r
+                int adjval = shape.getEscherProperty(EscherProperties.GEOMETRY__ADJUSTVALUE, 10800);\r
+                GeneralPath path = new GeneralPath();\r
+                path.moveTo(adjval, 0);\r
+                path.lineTo(0, 21600);\r
+                path.lineTo(21600, 21600);\r
+                path.closePath();\r
+                return path;\r
+           }\r
+        };\r
+\r
+        shapes[ShapeTypes.RightTriangle] = new ShapeOutline(){\r
+            public java.awt.Shape getOutline(Shape shape){\r
+                GeneralPath path = new GeneralPath();\r
+                path.moveTo(0, 0);\r
+                path.lineTo(21600, 21600);\r
+                path.lineTo(0, 21600);\r
+                path.closePath();\r
+                return path;\r
+           }\r
+        };\r
+\r
+        shapes[ShapeTypes.Parallelogram] = new ShapeOutline(){\r
+            public java.awt.Shape getOutline(Shape shape){\r
+                int adjval = shape.getEscherProperty(EscherProperties.GEOMETRY__ADJUSTVALUE, 5400);\r
+\r
+                GeneralPath path = new GeneralPath();\r
+                path.moveTo(adjval, 0);\r
+                path.lineTo(21600, 0);\r
+                path.lineTo(21600 - adjval, 21600);\r
+                path.lineTo(0, 21600);\r
+                path.closePath();\r
+                return path;\r
+            }\r
+        };\r
+\r
+        shapes[ShapeTypes.Trapezoid] = new ShapeOutline(){\r
+            public java.awt.Shape getOutline(Shape shape){\r
+                int adjval = shape.getEscherProperty(EscherProperties.GEOMETRY__ADJUSTVALUE, 5400);\r
+\r
+                GeneralPath path = new GeneralPath();\r
+                path.moveTo(0, 0);\r
+                path.lineTo(adjval, 21600);\r
+                path.lineTo(21600 - adjval, 21600);\r
+                path.lineTo(21600, 0);\r
+                path.closePath();\r
+                return path;\r
+            }\r
+        };\r
+\r
+        shapes[ShapeTypes.Hexagon] = new ShapeOutline(){\r
+            public java.awt.Shape getOutline(Shape shape){\r
+                int adjval = shape.getEscherProperty(EscherProperties.GEOMETRY__ADJUSTVALUE, 5400);\r
+\r
+                GeneralPath path = new GeneralPath();\r
+                path.moveTo(adjval, 0);\r
+                path.lineTo(21600 - adjval, 0);\r
+                path.lineTo(21600, 10800);\r
+                path.lineTo(21600 - adjval, 21600);\r
+                path.lineTo(adjval, 21600);\r
+                path.lineTo(0, 10800);\r
+                path.closePath();\r
+                return path;\r
+            }\r
+        };\r
+\r
+        shapes[ShapeTypes.Octagon] = new ShapeOutline(){\r
+            public java.awt.Shape getOutline(Shape shape){\r
+                int adjval = shape.getEscherProperty(EscherProperties.GEOMETRY__ADJUSTVALUE, 6326);\r
+\r
+                GeneralPath path = new GeneralPath();\r
+                path.moveTo(adjval, 0);\r
+                path.lineTo(21600 - adjval, 0);\r
+                path.lineTo(21600, adjval);\r
+                path.lineTo(21600, 21600-adjval);\r
+                path.lineTo(21600-adjval, 21600);\r
+                path.lineTo(adjval, 21600);\r
+                path.lineTo(0, 21600-adjval);\r
+                path.lineTo(0, adjval);\r
+                path.closePath();\r
+                return path;\r
+            }\r
+        };\r
+\r
+        shapes[ShapeTypes.Plus] = new ShapeOutline(){\r
+            public java.awt.Shape getOutline(Shape shape){\r
+                int adjval = shape.getEscherProperty(EscherProperties.GEOMETRY__ADJUSTVALUE, 5400);\r
+\r
+                GeneralPath path = new GeneralPath();\r
+                path.moveTo(adjval, 0);\r
+                path.lineTo(21600 - adjval, 0);\r
+                path.lineTo(21600 - adjval, adjval);\r
+                path.lineTo(21600, adjval);\r
+                path.lineTo(21600, 21600-adjval);\r
+                path.lineTo(21600-adjval, 21600-adjval);\r
+                path.lineTo(21600-adjval, 21600);\r
+                path.lineTo(adjval, 21600);\r
+                path.lineTo(adjval, 21600-adjval);\r
+                path.lineTo(0, 21600-adjval);\r
+                path.lineTo(0, adjval);\r
+                path.lineTo(adjval, adjval);\r
+                path.closePath();\r
+                return path;\r
+            }\r
+        };\r
+\r
+        shapes[ShapeTypes.Pentagon] = new ShapeOutline(){\r
+            public java.awt.Shape getOutline(Shape shape){\r
+\r
+                GeneralPath path = new GeneralPath();\r
+                path.moveTo(10800, 0);\r
+                path.lineTo(21600, 8259);\r
+                path.lineTo(21600 - 4200, 21600);\r
+                path.lineTo(4200, 21600);\r
+                path.lineTo(0, 8259);\r
+                path.closePath();\r
+                return path;\r
+            }\r
+        };\r
+\r
+        shapes[ShapeTypes.DownArrow] = new ShapeOutline(){\r
+            public java.awt.Shape getOutline(Shape shape){\r
+                //m0@0 l@1@0 @1,0 @2,0 @2@0,21600@0,10800,21600xe\r
+                int adjval = shape.getEscherProperty(EscherProperties.GEOMETRY__ADJUSTVALUE, 16200);\r
+                int adjval2 = shape.getEscherProperty(EscherProperties.GEOMETRY__ADJUST2VALUE, 5400);\r
+                GeneralPath path = new GeneralPath();\r
+                path.moveTo(0, adjval);\r
+                path.lineTo(adjval2, adjval);\r
+                path.lineTo(adjval2, 0);\r
+                path.lineTo(21600-adjval2, 0);\r
+                path.lineTo(21600-adjval2, adjval);\r
+                path.lineTo(21600, adjval);\r
+                path.lineTo(10800, 21600);\r
+                path.closePath();\r
+                return path;\r
+            }\r
+        };\r
+\r
+        shapes[ShapeTypes.UpArrow] = new ShapeOutline(){\r
+            public java.awt.Shape getOutline(Shape shape){\r
+                //m0@0 l@1@0 @1,21600@2,21600@2@0,21600@0,10800,xe\r
+                int adjval = shape.getEscherProperty(EscherProperties.GEOMETRY__ADJUSTVALUE, 5400);\r
+                int adjval2 = shape.getEscherProperty(EscherProperties.GEOMETRY__ADJUST2VALUE, 5400);\r
+                GeneralPath path = new GeneralPath();\r
+                path.moveTo(0, adjval);\r
+                path.lineTo(adjval2, adjval);\r
+                path.lineTo(adjval2, 21600);\r
+                path.lineTo(21600-adjval2, 21600);\r
+                path.lineTo(21600-adjval2, adjval);\r
+                path.lineTo(21600, adjval);\r
+                path.lineTo(10800, 0);\r
+                path.closePath();\r
+                return path;\r
+            }\r
+        };\r
+\r
+        shapes[ShapeTypes.Arrow] = new ShapeOutline(){\r
+            public java.awt.Shape getOutline(Shape shape){\r
+                //m@0, l@0@1 ,0@1,0@2@0@2@0,21600,21600,10800xe\r
+                int adjval = shape.getEscherProperty(EscherProperties.GEOMETRY__ADJUSTVALUE, 16200);\r
+                int adjval2 = shape.getEscherProperty(EscherProperties.GEOMETRY__ADJUST2VALUE, 5400);\r
+                GeneralPath path = new GeneralPath();\r
+                path.moveTo(adjval, 0);\r
+                path.lineTo(adjval, adjval2);\r
+                path.lineTo(0, adjval2);\r
+                path.lineTo(0, 21600-adjval2);\r
+                path.lineTo(adjval, 21600-adjval2);\r
+                path.lineTo(adjval, 21600);\r
+                path.lineTo(21600, 10800);\r
+                path.closePath();\r
+                return path;\r
+            }\r
+        };\r
+\r
+        shapes[ShapeTypes.LeftArrow] = new ShapeOutline(){\r
+            public java.awt.Shape getOutline(Shape shape){\r
+                //m@0, l@0@1,21600@1,21600@2@0@2@0,21600,,10800xe\r
+                int adjval = shape.getEscherProperty(EscherProperties.GEOMETRY__ADJUSTVALUE, 5400);\r
+                int adjval2 = shape.getEscherProperty(EscherProperties.GEOMETRY__ADJUST2VALUE, 5400);\r
+                GeneralPath path = new GeneralPath();\r
+                path.moveTo(adjval, 0);\r
+                path.lineTo(adjval, adjval2);\r
+                path.lineTo(21600, adjval2);\r
+                path.lineTo(21600, 21600-adjval2);\r
+                path.lineTo(adjval, 21600-adjval2);\r
+                path.lineTo(adjval, 21600);\r
+                path.lineTo(0, 10800);\r
+                path.closePath();\r
+                return path;\r
+            }\r
+        };\r
+    }\r
+\r
+}\r
index c4df7b6779bd4ebc23806d3ae2989190a8f974f8..49393250d1484232d5385247a67a17d475582c72 100644 (file)
@@ -22,12 +22,10 @@ import org.apache.poi.ddf.*;
 import org.apache.poi.hslf.record.*;
 import org.apache.poi.hslf.usermodel.PictureData;
 import org.apache.poi.hslf.usermodel.SlideShow;
-import org.apache.poi.hslf.exceptions.HSLFException;
 import org.apache.poi.util.POILogger;
 import org.apache.poi.util.POILogFactory;
 
 import java.awt.*;
-import java.util.*;
 
 /**
  * Represents functionality provided by the 'Fill Effects' dialog in PowerPoint.
@@ -137,13 +135,15 @@ public class Fill {
         EscherOptRecord opt = (EscherOptRecord)Shape.getEscherChild(shape.getSpContainer(), EscherOptRecord.RECORD_ID);
         EscherSimpleProperty p1 = (EscherSimpleProperty)Shape.getEscherProperty(opt, EscherProperties.FILL__FILLCOLOR);
         EscherSimpleProperty p2 = (EscherSimpleProperty)Shape.getEscherProperty(opt, EscherProperties.FILL__NOFILLHITTEST);
+        EscherSimpleProperty p3 = (EscherSimpleProperty)Shape.getEscherProperty(opt, EscherProperties.FILL__FILLOPACITY);
 
         int p2val = p2 == null ? 0 : p2.getPropertyValue();
+        int alpha =  p3 == null ? 255 : ((p3.getPropertyValue() >> 8) & 0xFF);
 
         Color clr = null;
         if (p1 != null && (p2val  & 0x10) != 0){
             int rgb = p1.getPropertyValue();
-            clr = shape.getColor(rgb);
+            clr = shape.getColor(rgb, alpha);
         }
         return clr;
     }
@@ -176,7 +176,7 @@ public class Fill {
         Color clr = null;
         if (p1 != null && (p2val  & 0x10) != 0){
             int rgb = p1.getPropertyValue();
-            clr = shape.getColor(rgb);
+            clr = shape.getColor(rgb, 255);
         }
         return clr;
     }
index 28e41f8b0991cfbbd2a0ae1db643f99081820a6c..d31237f8db1378ce96a436ae8d25ba7d730b1c77 100755 (executable)
@@ -19,7 +19,6 @@ package org.apache.poi.hslf.model;
 import org.apache.poi.ddf.*;\r
 import org.apache.poi.util.LittleEndian;\r
 import org.apache.poi.util.POILogger;\r
-import org.apache.poi.util.HexDump;\r
 \r
 import java.awt.geom.*;\r
 import java.util.ArrayList;\r
@@ -237,4 +236,8 @@ public class Freeform extends AutoShape {
 \r
         return path;\r
     }\r
+\r
+    public java.awt.Shape getOutline(){\r
+        return getPath();\r
+    }\r
 }\r
index 9237183689aef981e9985767d16b3a0a08949d49..073efa39519e26e0ac2b87eaab9632ae1539b715 100644 (file)
@@ -19,6 +19,9 @@ package org.apache.poi.hslf.model;
 
 import org.apache.poi.ddf.*;
 
+import java.awt.geom.Rectangle2D;
+import java.awt.geom.Line2D;
+
 /**
  * Represents a line in a PowerPoint drawing
  *
@@ -126,4 +129,8 @@ public class Line extends SimpleShape {
         return _escherContainer;
     }
 
+    public java.awt.Shape getOutline(){
+        Rectangle2D anchor = getAnchor2D();
+        return new Line2D.Double(anchor.getX(), anchor.getY(), anchor.getX() + anchor.getWidth(), anchor.getY() + anchor.getHeight());
+    }
 }
index 4371e2a371ffdf4481c5509efdec0f726efb7c4e..5b1b1016e4f3487875d03a2a6ac8665003cd0db2 100644 (file)
@@ -17,6 +17,8 @@
 package org.apache.poi.hslf.model;
 
 import org.apache.poi.hslf.record.SheetContainer;
+import org.apache.poi.hslf.record.Record;
+import org.apache.poi.hslf.record.RecordTypes;
 import org.apache.poi.hslf.model.textproperties.TextProp;
 
 /**
@@ -37,4 +39,32 @@ public abstract class MasterSheet extends Sheet {
      */
     public abstract TextProp getStyleAttribute(int txtype, int level, String name, boolean isCharacter) ;
 
+
+    /**
+     * Checks if the shape is a placeholder.
+     * (placeholders aren't normal shapes, they are visible only in the Edit Master mode)
+     *
+     *
+     * @return true if the shape is a placeholder
+     */
+    public static boolean isPlaceholder(Shape shape){
+        if(!(shape instanceof TextShape)) return false;
+
+        TextShape tx = (TextShape)shape;
+        TextRun run = tx.getTextRun();
+        if(run == null) return false;
+
+        Record[] records = run._records;
+        for (int i = 0; i < records.length; i++) {
+            int type = (int)records[i].getRecordType();
+            if (type == RecordTypes.BaseTextPropAtom.typeID ||
+                type == RecordTypes.DateTimeMCAtom.typeID ||
+                type == RecordTypes.GenericDateMCAtom.typeID ||
+                type == RecordTypes.FooterMCAtom.typeID ||
+                type == RecordTypes.SlideNumberMCAtom.typeID
+                    ) return true;
+
+        }
+        return false;
+    }
 }
index 908dc4d9be86ed3399bceb0cb21128904c0bb488..3f574c36fae5a57b1fe2248eef868aaa2fa56c49 100755 (executable)
 package org.apache.poi.hslf.model;\r
 \r
 import org.apache.poi.ddf.*;\r
-import org.apache.poi.hslf.usermodel.PictureData;\r
 import org.apache.poi.hslf.usermodel.SlideShow;\r
 import org.apache.poi.hslf.usermodel.ObjectData;\r
-import org.apache.poi.hslf.record.Document;\r
 import org.apache.poi.hslf.record.ExObjList;\r
 import org.apache.poi.hslf.record.Record;\r
 import org.apache.poi.hslf.record.ExEmbed;\r
-import org.apache.poi.hslf.blip.Bitmap;\r
 import org.apache.poi.util.POILogger;\r
 \r
-import javax.imageio.ImageIO;\r
-import java.awt.image.BufferedImage;\r
-import java.awt.*;\r
-import java.io.ByteArrayInputStream;\r
-import java.io.IOException;\r
-import java.util.List;\r
-import java.util.Arrays;\r
-\r
 \r
 /**\r
  * A shape representing embedded OLE obejct.\r
index 23088f056b5ff34dc1403e4e87134f4e7f392bb1..37cedce0fabcb36dfebeb3a8e73cfd859dbbd5a1 100644 (file)
@@ -27,7 +27,6 @@ import java.awt.image.renderable.RenderableImage;
 import java.awt.geom.*;
 import java.text.AttributedCharacterIterator;
 import java.util.Map;
-import java.util.ArrayList;
 import org.apache.poi.hslf.usermodel.RichTextRun;
 import org.apache.poi.hslf.exceptions.HSLFException;
 import org.apache.poi.util.POILogger;
index 910d5c850344e79d12dc4aa68cd65e5bcaeb68b7..1e8dbcc253603ae8676552e757c9dc6c6178538d 100644 (file)
@@ -26,6 +26,7 @@ import org.apache.poi.util.POILogger;
 import javax.imageio.ImageIO;
 import java.awt.image.BufferedImage;
 import java.awt.*;
+import java.awt.geom.Rectangle2D;
 import java.io.ByteArrayInputStream;
 import java.io.IOException;
 import java.util.List;
@@ -202,4 +203,22 @@ public class Picture extends SimpleShape {
         }
     }
 
+    public void draw(Graphics2D graphics){
+        PictureData data = getPictureData();
+        if (data  instanceof Bitmap){
+            BufferedImage img = null;
+            try {
+                       img = ImageIO.read(new ByteArrayInputStream(data.getData()));
+            }
+            catch (Exception e){
+                logger.log(POILogger.WARN, "ImageIO failed to create image. image.type: " + data.getType());
+                return;
+            }
+            Rectangle anchor = getAnchor();
+            Image scaledImg = img.getScaledInstance(anchor.width, anchor.height, Image.SCALE_SMOOTH);
+            graphics.drawImage(scaledImg, anchor.x, anchor.y, null);
+        } else {
+            logger.log(POILogger.WARN, "Rendering of metafiles is not yet supported. image.type: " + data.getType());
+        }
+    }
 }
index 7b67c35fb27e0efe9de99f3767352497808ee023..9ac61dbff17a54b6bb5c85203eaabc828230da85 100644 (file)
@@ -17,7 +17,6 @@
 package org.apache.poi.hslf.model;
 
 import org.apache.poi.ddf.*;
-import org.apache.poi.hslf.model.ShapeTypes;
 import org.apache.poi.hslf.record.ColorSchemeAtom;
 import org.apache.poi.util.POILogger;
 import org.apache.poi.util.POILogFactory;
@@ -297,6 +296,17 @@ public abstract class Shape {
         return prop == null ? 0 : prop.getPropertyValue();
     }
 
+    /**
+     * Get the value of a simple escher property for this shape.
+     *
+     * @param propId    The id of the property. One of the constants defined in EscherOptRecord.
+     */
+   public int getEscherProperty(short propId, int defaultValue){
+        EscherOptRecord opt = (EscherOptRecord)getEscherChild(_escherContainer, EscherOptRecord.RECORD_ID);
+        EscherSimpleProperty prop = (EscherSimpleProperty)getEscherProperty(opt, propId);
+        return prop == null ? defaultValue : prop.getPropertyValue();
+    }
+
     /**
      * @return  The shape container and it's children that can represent this
      *          shape.
@@ -333,14 +343,14 @@ public abstract class Shape {
         _sheet = sheet;
     }
 
-    protected Color getColor(int rgb){
+    protected Color getColor(int rgb, int alpha){
         if (rgb >= 0x8000000) {
             int idx = rgb - 0x8000000;
             ColorSchemeAtom ca = getSheet().getColorScheme();
             if(idx >= 0 && idx <= 7) rgb = ca.getColor(idx);
         }
         Color tmp = new Color(rgb, true);
-        return new Color(tmp.getBlue(), tmp.getGreen(), tmp.getRed());
+        return new Color(tmp.getBlue(), tmp.getGreen(), tmp.getRed(), alpha);
     }
 
     /**
@@ -364,4 +374,16 @@ public abstract class Shape {
         return Hyperlink.find(this);
     }
 
+    public void draw(Graphics2D graphics){
+        logger.log(POILogger.INFO, "Rendering " + getShapeName());
+    }
+
+    /**
+     * Return shape outline as a java.awt.Shape object
+     *
+     * @return the shape outline
+     */
+    public java.awt.Shape getOutline(){
+        return getAnchor2D();
+    }
 }
index b22ed8d70b1222f0041b2e6a257cb6439383780a..837d76b61ce8af02ee955a60ca9b91858cac0ce7 100644 (file)
@@ -24,6 +24,7 @@ import org.apache.poi.hslf.record.EscherTextboxWrapper;
 import java.util.ArrayList;
 import java.util.List;
 import java.awt.geom.Rectangle2D;
+import java.awt.*;
 
 /**
  *  Represents a group of shapes.
@@ -222,5 +223,11 @@ public class ShapeGroup extends Shape{
      public Hyperlink getHyperlink(){
         return null;
     }
-    
+
+    public void draw(Graphics2D graphics){
+        Shape[] sh = getShapes();
+        for (int i = 0; i < sh.length; i++) {
+            sh[i].draw(graphics);
+        }
+    }
 }
diff --git a/src/scratchpad/src/org/apache/poi/hslf/model/ShapeOutline.java b/src/scratchpad/src/org/apache/poi/hslf/model/ShapeOutline.java
new file mode 100755 (executable)
index 0000000..069fdbc
--- /dev/null
@@ -0,0 +1,27 @@
+/* ====================================================================\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
+package org.apache.poi.hslf.model;\r
+\r
+/**\r
+ * Date: Apr 17, 2008\r
+ * \r
+ * @author Yegor Kozlov\r
+ */\r
+public interface ShapeOutline {\r
+    java.awt.Shape getOutline(Shape shape);\r
+\r
+}\r
diff --git a/src/scratchpad/src/org/apache/poi/hslf/model/ShapePainter.java b/src/scratchpad/src/org/apache/poi/hslf/model/ShapePainter.java
new file mode 100755 (executable)
index 0000000..4e30545
--- /dev/null
@@ -0,0 +1,85 @@
+/* ====================================================================\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
+package org.apache.poi.hslf.model;\r
+\r
+\r
+import java.awt.*;\r
+import java.awt.geom.Rectangle2D;\r
+\r
+/**\r
+ * Paint a shape into java.awt.Graphics2D\r
+ * \r
+ * @author Yegor Kozlov\r
+ */\r
+public class ShapePainter {\r
+\r
+    public static void paint(SimpleShape shape, Graphics2D graphics){\r
+        Rectangle2D anchor = shape.getAnchor2D();\r
+        java.awt.Shape outline = shape.getOutline();\r
+\r
+        //flip vertical\r
+        if(shape.getFlipVertical()){\r
+            graphics.translate(anchor.getX(), anchor.getY() + anchor.getHeight());\r
+            graphics.scale(1, -1);\r
+            graphics.translate(-anchor.getX(), -anchor.getY());\r
+        }\r
+        //flip horizontal\r
+        if(shape.getFlipHorizontal()){\r
+            graphics.translate(anchor.getX() + anchor.getWidth(), anchor.getY());\r
+            graphics.scale(-1, 1);\r
+            graphics.translate(-anchor.getX() , -anchor.getY());\r
+        }\r
+\r
+        //rotate transform\r
+        double angle = shape.getRotation();\r
+\r
+        if(angle != 0){\r
+            double centerX = anchor.getX() + anchor.getWidth()/2;\r
+            double centerY = anchor.getY() + anchor.getHeight()/2;\r
+\r
+            graphics.translate(centerX, centerY);\r
+            graphics.rotate(Math.toRadians(angle));\r
+            graphics.translate(-centerX, -centerY);\r
+        }\r
+\r
+        //fill\r
+        Color fillColor = shape.getFill().getForegroundColor();\r
+        if (fillColor != null) {\r
+            graphics.setPaint(fillColor);\r
+            graphics.fill(outline);\r
+        }\r
+\r
+        //border\r
+        Color lineColor = shape.getLineColor();\r
+        if (lineColor != null){\r
+            graphics.setPaint(lineColor);\r
+            float width = (float)shape.getLineWidth();\r
+            int dashing = shape.getLineDashing();\r
+            //TODO: implement more dashing styles\r
+            float[] dashptrn = null;\r
+            switch(dashing){\r
+                case Line.PEN_PS_DASH:\r
+                    dashptrn = new float[]{2, 2};\r
+                    break;\r
+            }\r
+\r
+            Stroke stroke = new BasicStroke(width, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 10.0f, dashptrn, 0.0f);\r
+            graphics.setStroke(stroke);\r
+            graphics.draw(outline);\r
+        }\r
+    }\r
+}\r
index abf2fec5890050779205c21f3edfc7627a46b51f..2a1785bd12e3ac9c2f713049bfba089f8ed2d599 100644 (file)
@@ -29,6 +29,7 @@ import java.util.ArrayList;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Vector;
+import java.awt.*;
 
 /**
  * This class defines the common format of "Sheets" in a powerpoint
@@ -329,4 +330,7 @@ public abstract class Sheet {
         return _background;
     }
 
+    public void draw(Graphics2D graphics){
+
+    }
 }
index 0831d453b5925b35de5f838cc6b606203051bb4f..0155ca3f5cdcdb9938548824f658e64b94f3bd85 100644 (file)
@@ -22,6 +22,7 @@ import org.apache.poi.util.LittleEndian;
 import org.apache.poi.hslf.record.ColorSchemeAtom;
 
 import java.awt.*;
+import java.awt.geom.AffineTransform;
 
 /**
  *  An abstract simple (non-group) shape.
@@ -199,4 +200,41 @@ public class SimpleShape extends Shape {
         getFill().setForegroundColor(color);
     }
 
+    /**
+     * Whether the shape is horizontally flipped
+     *
+     * @return whether the shape is horizontally flipped
+     */
+     public boolean getFlipHorizontal(){
+        EscherSpRecord spRecord = _escherContainer.getChildById(EscherSpRecord.RECORD_ID);
+        return (spRecord.getFlags()& EscherSpRecord.FLAG_FLIPHORIZ) != 0;
+    }
+
+    /**
+     * Whether the shape is vertically flipped
+     *
+     * @return whether the shape is vertically flipped
+     */
+    public boolean getFlipVertical(){
+        EscherSpRecord spRecord = _escherContainer.getChildById(EscherSpRecord.RECORD_ID);
+        return (spRecord.getFlags()& EscherSpRecord.FLAG_FLIPVERT) != 0;
+    }
+
+    /**
+     * Rotation angle in degrees
+     *
+     * @return rotation angle in degrees
+     */
+    public int getRotation(){
+        int rot = getEscherProperty(EscherProperties.TRANSFORM__ROTATION);
+        int angle = (rot >> 16) % 360;
+
+        return angle;
+    }
+
+    public void draw(Graphics2D graphics){
+        AffineTransform at = graphics.getTransform();
+        ShapePainter.paint(this, graphics);
+        graphics.setTransform(at);
+    }
 }
index ea7201751d79310cb73d43925f549a8779c06e02..e618ae23033372b2869a996a0a9a84906a984396 100644 (file)
 package org.apache.poi.hslf.model;
 
 import java.util.Vector;
-import java.util.List;
-import java.util.Iterator;
-import java.util.ArrayList;
+import java.awt.*;
 
-import org.apache.poi.hslf.record.PPDrawing;
 import org.apache.poi.hslf.record.SlideAtom;
 import org.apache.poi.hslf.record.TextHeaderAtom;
 import org.apache.poi.hslf.record.ColorSchemeAtom;
 import org.apache.poi.hslf.record.SlideListWithText.SlideAtomsSet;
-import org.apache.poi.ddf.EscherContainerRecord;
-import org.apache.poi.ddf.EscherRecord;
 
 /**
  * This class represents a slide in a PowerPoint Document. It allows 
@@ -263,10 +258,85 @@ public class Slide extends Sheet
         return sa.getFollowMasterBackground();
     }
 
-    public Background getBackground() {
+    /**
+     * Sets whether this slide draws master sheet objects
+     *
+     * @param flag  <code>true</code> if the slide draws master sheet objects,
+     * <code>false</code> otherwise
+     */
+    public void setFollowMasterObjects(boolean flag){
+        SlideAtom sa = getSlideRecord().getSlideAtom();
+        sa.setFollowMasterObjects(flag);
+    }
+
+    /**
+     * Whether this slide follows master color scheme
+     *
+     * @return <code>true</code> if the slide follows master color scheme,
+     * <code>false</code> otherwise
+     */
+    public boolean getFollowMasterScheme(){
+        SlideAtom sa = getSlideRecord().getSlideAtom();
+        return sa.getFollowMasterScheme();
+    }
+
+    /**
+     * Sets whether this slide draws master color scheme
+     *
+     * @param flag  <code>true</code> if the slide draws master color scheme,
+     * <code>false</code> otherwise
+     */
+    public void setFollowMasterScheme(boolean flag){
+        SlideAtom sa = getSlideRecord().getSlideAtom();
+        sa.setFollowMasterScheme(flag);
+    }
+
+    /**
+     * Whether this slide draws master sheet objects
+     *
+     * @return <code>true</code> if the slide draws master sheet objects,
+     * <code>false</code> otherwise
+     */
+    public boolean getFollowMasterObjects(){
+        SlideAtom sa = getSlideRecord().getSlideAtom();
+        return sa.getFollowMasterObjects();
+    }
+
+    /**
+     * Background for this slide.
+     */
+     public Background getBackground() {
         if(getFollowMasterBackground())
             return getMasterSheet().getBackground();
         else
             return super.getBackground();
     }
+
+    /**
+     * Color scheme for this slide.
+     */
+    public ColorSchemeAtom getColorScheme() {
+        if(getFollowMasterScheme()){
+            return getMasterSheet().getColorScheme();
+        }
+        return super.getColorScheme();
+    }
+
+    public void draw(Graphics2D graphics){
+        MasterSheet master = getMasterSheet();
+        if(getFollowMasterBackground()) master.getBackground().draw(graphics);
+        if(getFollowMasterObjects()){
+            Shape[] sh = master.getShapes();
+            for (int i = 0; i < sh.length; i++) {
+                if(MasterSheet.isPlaceholder(sh[i])) continue;
+
+                sh[i].draw(graphics);
+            }
+        }
+        Shape[] sh = getShapes();
+        for (int i = 0; i < sh.length; i++) {
+            sh[i].draw(graphics);
+        }
+    }
+
 }
index e442ae304760c7b70ec7b44e9c150b2b02f6d572..2a8467437b4c6cb6c4a90c0bbea5d24e0da44bce 100644 (file)
@@ -21,12 +21,6 @@ import org.apache.poi.hslf.model.textproperties.TextProp;
 import org.apache.poi.hslf.model.textproperties.TextPropCollection;
 import org.apache.poi.hslf.record.*;
 import org.apache.poi.hslf.usermodel.SlideShow;
-import org.apache.poi.hslf.record.StyleTextPropAtom.*;
-import org.apache.poi.ddf.EscherContainerRecord;
-import org.apache.poi.ddf.EscherRecord;
-
-import java.util.List;
-import java.util.Iterator;
 
 /**
  * SlideMaster determines the graphics, layout, and formatting for all the slides in a given presentation.
@@ -82,17 +76,33 @@ public class SlideMaster extends MasterSheet {
             if (prop != null) break;
         }
         if (prop == null) {
-            switch (txtype) {
-                case TextHeaderAtom.CENTRE_BODY_TYPE:
-                case TextHeaderAtom.HALF_BODY_TYPE:
-                case TextHeaderAtom.QUARTER_BODY_TYPE:
-                    txtype = TextHeaderAtom.BODY_TYPE;
-                    break;
-                case TextHeaderAtom.CENTER_TITLE_TYPE:
-                    txtype = TextHeaderAtom.TITLE_TYPE;
-                    break;
-                default:
-                    return null;
+            if(isCharacter) {
+                switch (txtype) {
+                    case TextHeaderAtom.CENTRE_BODY_TYPE:
+                    case TextHeaderAtom.HALF_BODY_TYPE:
+                    case TextHeaderAtom.QUARTER_BODY_TYPE:
+                        txtype = TextHeaderAtom.BODY_TYPE;
+                        break;
+                    case TextHeaderAtom.CENTER_TITLE_TYPE:
+                        txtype = TextHeaderAtom.TITLE_TYPE;
+                        break;
+                    default:
+                        return null;
+                }
+            } else {
+                switch (txtype) {
+                    case TextHeaderAtom.CENTRE_BODY_TYPE:
+                    case TextHeaderAtom.QUARTER_BODY_TYPE:
+                        txtype = TextHeaderAtom.BODY_TYPE;
+                        break;
+                    case TextHeaderAtom.CENTER_TITLE_TYPE:
+                        txtype = TextHeaderAtom.TITLE_TYPE;
+                        break;
+                    default:
+                        return null;
+                }
+                return null;
+
             }
             prop = getStyleAttribute(txtype, level, name, isCharacter);
         }
@@ -119,4 +129,34 @@ public class SlideMaster extends MasterSheet {
             }
         }
     }
+
+    /**
+     * Checks if the shape is a placeholder.
+     * (placeholders aren't normal shapes, they are visible only in the Edit Master mode)
+     *
+     *
+     * @return true if the shape is a placeholder
+     */
+    public static boolean isPlaceholder(Shape shape){
+        if(!(shape instanceof TextShape)) return false;
+
+        TextShape tx = (TextShape)shape;
+        TextRun run = tx.getTextRun();
+        if(run == null) return false;
+
+        Record[] records = run._records;
+        for (int i = 0; i < records.length; i++) {
+            int type = (int)records[i].getRecordType();
+            if (type == RecordTypes.OEPlaceholderAtom.typeID ||
+                    type == RecordTypes.SlideNumberMCAtom.typeID ||
+                    type == RecordTypes.DateTimeMCAtom.typeID ||
+                    type == RecordTypes.GenericDateMCAtom.typeID ||
+                    type == RecordTypes.FooterMCAtom.typeID ){
+                return true;
+
+            }
+
+        }
+        return false;
+    }
 }
index 947c41af63e06e61aae0f7b4dc4623088a2eb5e4..dd4a6a33f047c091961765424e52411ffaa0666f 100755 (executable)
@@ -23,7 +23,6 @@ import org.apache.poi.util.LittleEndian;
 import java.util.*;\r
 import java.util.List;\r
 import java.awt.*;\r
-import java.awt.geom.Rectangle2D;\r
 \r
 /**\r
  * Represents a table in a PowerPoint presentation\r
index 36789ad91a2f0462c52b0e95311c9c84e1d400dc..7fa69b1d0c2a31c8fdc92e19dc944405b1de8925 100755 (executable)
@@ -18,9 +18,7 @@
 package org.apache.poi.hslf.model;\r
 \r
 import org.apache.poi.ddf.*;\r
-import org.apache.poi.hslf.record.EscherTextboxWrapper;\r
 import org.apache.poi.hslf.record.TextHeaderAtom;\r
-import org.apache.poi.hslf.usermodel.RichTextRun;\r
 \r
 import java.awt.*;\r
 \r
index a1d45ffbaed9ad44678541b769249b2cfedef6e2..dda55a2c7b197da6fd778ad125da32b836b49f01 100644 (file)
 package org.apache.poi.hslf.model;
 
 import org.apache.poi.ddf.*;
-import org.apache.poi.hslf.record.*;
-import org.apache.poi.hslf.usermodel.RichTextRun;
-import org.apache.poi.hslf.exceptions.HSLFException;
-import org.apache.poi.util.POILogger;
-
-import java.awt.*;
-import java.awt.font.FontRenderContext;
-import java.awt.font.TextLayout;
-import java.io.IOException;
 
 /**
  * Represents a TextFrame shape in PowerPoint.
diff --git a/src/scratchpad/src/org/apache/poi/hslf/model/TextPainter.java b/src/scratchpad/src/org/apache/poi/hslf/model/TextPainter.java
new file mode 100755 (executable)
index 0000000..cfd98bd
--- /dev/null
@@ -0,0 +1,238 @@
+/* ====================================================================\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
+package org.apache.poi.hslf.model;\r
+\r
+import org.apache.poi.hslf.usermodel.RichTextRun;\r
+import org.apache.poi.util.POILogger;\r
+import org.apache.poi.util.POILogFactory;\r
+\r
+import java.text.AttributedString;\r
+import java.text.AttributedCharacterIterator;\r
+import java.awt.font.TextAttribute;\r
+import java.awt.font.LineBreakMeasurer;\r
+import java.awt.font.TextLayout;\r
+import java.awt.*;\r
+import java.awt.geom.Rectangle2D;\r
+import java.awt.geom.Point2D;\r
+import java.util.ArrayList;\r
+\r
+/**\r
+ * Paint text into java.awt.Graphics2D\r
+ * \r
+ * @author Yegor Kozlov\r
+ */\r
+public class TextPainter {\r
+    protected POILogger logger = POILogFactory.getLogger(this.getClass());\r
+\r
+    protected TextShape _shape;\r
+\r
+    public TextPainter(TextShape shape){\r
+        _shape = shape;\r
+    }\r
+\r
+    public AttributedString getAttributedString(TextRun txrun){\r
+        String text = txrun.getText();\r
+        AttributedString at = new AttributedString(text);\r
+        RichTextRun[] rt = txrun.getRichTextRuns();\r
+        for (int i = 0; i < rt.length; i++) {\r
+            int start = rt[i].getStartIndex();\r
+            int end = rt[i].getEndIndex();\r
+            if(start == end) continue;\r
+\r
+            at.addAttribute(TextAttribute.FAMILY, rt[i].getFontName(), start, end);\r
+            at.addAttribute(TextAttribute.SIZE, new Float(rt[i].getFontSize()), start, end);\r
+            at.addAttribute(TextAttribute.FOREGROUND, rt[i].getFontColor(), start, end);\r
+            if(rt[i].isBold()) at.addAttribute(TextAttribute.WEIGHT, TextAttribute.WEIGHT_BOLD, start, end);\r
+            if(rt[i].isItalic()) at.addAttribute(TextAttribute.POSTURE, TextAttribute.POSTURE_OBLIQUE, start, end);\r
+            if(rt[i].isUnderlined()) {\r
+                at.addAttribute(TextAttribute.UNDERLINE, TextAttribute.UNDERLINE_ON, start, end);\r
+                at.addAttribute(TextAttribute.INPUT_METHOD_UNDERLINE, TextAttribute.UNDERLINE_LOW_TWO_PIXEL, start, end);\r
+            }\r
+            if(rt[i].isStrikethrough()) at.addAttribute(TextAttribute.STRIKETHROUGH, TextAttribute.STRIKETHROUGH_ON, start, end);\r
+            int superScript = rt[i].getSuperscript();\r
+            if(superScript != 0) at.addAttribute(TextAttribute.SUPERSCRIPT, superScript > 0 ? TextAttribute.SUPERSCRIPT_SUPER : TextAttribute.SUPERSCRIPT_SUB, start, end);\r
+\r
+        }\r
+        return at;\r
+    }\r
+\r
+    protected RichTextRun getRichTextRunAt(int pos){\r
+        RichTextRun[] rt = _shape.getTextRun().getRichTextRuns();\r
+        for (int i = 0; i < rt.length; i++) {\r
+            int start = rt[i].getStartIndex();\r
+            int end = rt[i].getEndIndex();\r
+            if(pos >= start && pos < end) return rt[i];\r
+        }\r
+        return null;\r
+    }\r
+\r
+    public void paint(Graphics2D graphics){\r
+        TextRun run = _shape.getTextRun();\r
+        if (run == null) return;\r
+\r
+        String text = run.getText();\r
+        if (text == null || text.equals("")) return;\r
+\r
+        AttributedString at = getAttributedString(run);\r
+\r
+        AttributedCharacterIterator it = at.getIterator();\r
+        int paragraphStart = it.getBeginIndex();\r
+        int paragraphEnd = it.getEndIndex();\r
+\r
+        Rectangle2D anchor = _shape.getAnchor2D();\r
+\r
+        float textHeight = 0;\r
+        ArrayList lines = new ArrayList();\r
+        LineBreakMeasurer measurer = new LineBreakMeasurer(it, graphics.getFontRenderContext());\r
+        measurer.setPosition(paragraphStart);\r
+        while (measurer.getPosition() < paragraphEnd) {\r
+            int startIndex = measurer.getPosition();\r
+            int nextBreak = text.indexOf('\n', measurer.getPosition() + 1);\r
+            RichTextRun rt = getRichTextRunAt(startIndex + 1);\r
+            if(rt == null) {\r
+                logger.log(POILogger.WARN,  "RichTextRun not found at pos" + (startIndex + 1) + "; text.length: " + text.length());\r
+                break;\r
+            }\r
+\r
+            float wrappingWidth = (float)anchor.getWidth() - _shape.getMarginLeft() - _shape.getMarginRight();\r
+            wrappingWidth -= rt.getTextOffset();\r
+\r
+            if (_shape.getWordWrap() == TextShape.WrapNone) {\r
+                wrappingWidth = _shape.getSheet().getSlideShow().getPageSize().width;\r
+            }\r
+\r
+            TextLayout textLayout = measurer.nextLayout(wrappingWidth + 1,\r
+                    nextBreak == -1 ? paragraphEnd : nextBreak, true);\r
+            if (textLayout == null) {\r
+                textLayout = measurer.nextLayout(wrappingWidth,\r
+                    nextBreak == -1 ? paragraphEnd : nextBreak, false);\r
+            }\r
+            if(textLayout == null){\r
+                logger.log(POILogger.WARN, "Failed to break text into lines: wrappingWidth: "+wrappingWidth+\r
+                        "; text: " + rt.getText());\r
+                measurer.setPosition(rt.getEndIndex());\r
+                continue;\r
+            }\r
+            int endIndex = measurer.getPosition();\r
+\r
+            TextElement el = new TextElement();\r
+            el._startIndex = startIndex;\r
+            el._endIndex = endIndex;\r
+            el._align = rt.getAlignment();\r
+            el._text = textLayout;\r
+            el._textOffset = rt.getTextOffset();\r
+\r
+            boolean prStart = text.charAt(startIndex) == '\n' || startIndex == 0;\r
+            if (text.charAt(startIndex) == '\n'){\r
+                int spaceBefore = rt.getSpaceBefore();\r
+                if (spaceBefore != 0) {\r
+                    float val = (textLayout.getAscent() + textLayout.getDescent()) * spaceBefore/100;\r
+                    textHeight += val;\r
+                }\r
+            }\r
+            if(rt.isBullet() && prStart){\r
+                it.setIndex(startIndex);\r
+\r
+                AttributedString bat = new AttributedString(Character.toString(rt.getBulletChar()), it.getAttributes());\r
+                int bulletSize = rt.getBulletSize();\r
+                if (bulletSize != -1){\r
+                    Float sz =  (Float)bat.getIterator().getAttribute(TextAttribute.SIZE);\r
+                    if(sz != null) bat.addAttribute(TextAttribute.SIZE, new Float(sz.floatValue()*bulletSize/100));\r
+                }\r
+\r
+                TextLayout bulletLayout = new TextLayout(bat.getIterator(), graphics.getFontRenderContext());\r
+                if(text.substring(startIndex, endIndex).length() > 1){\r
+                    el._bullet = bulletLayout;\r
+                    el._bulletOffset = rt.getBulletOffset();\r
+                }\r
+            }\r
+\r
+            textHeight += textLayout.getAscent() + textLayout.getLeading();\r
+\r
+            int lineSpacing = rt.getLineSpacing();\r
+            if(lineSpacing != 0) el._spacing = textLayout.getDescent()*lineSpacing/100;\r
+            else el._spacing = textLayout.getDescent();\r
+\r
+            textHeight += el._spacing;\r
+\r
+            lines.add(el);\r
+        }\r
+\r
+        int valign = _shape.getVerticalAlignment();\r
+        double y0 = anchor.getY();\r
+        switch (valign){\r
+            case TextBox.AnchorTopBaseline:\r
+            case TextBox.AnchorTop:\r
+                y0 += _shape.getMarginTop();\r
+                break;\r
+            case TextBox.AnchorBottom:\r
+                y0 += anchor.getHeight() - textHeight - _shape.getMarginBottom();\r
+                break;\r
+            default:\r
+            case TextBox.AnchorMiddle:\r
+                float delta =  (float)anchor.getHeight() - textHeight - _shape.getMarginTop() - _shape.getMarginBottom();\r
+                y0 += _shape.getMarginTop()  + delta/2;\r
+                break;\r
+        }\r
+\r
+        //finally draw the text fragments\r
+        for (int i = 0; i < lines.size(); i++) {\r
+            TextElement elem = (TextElement)lines.get(i);\r
+            y0 += elem._text.getAscent();\r
+\r
+            Point2D.Double pen = new Point2D.Double();\r
+            pen.y = y0;\r
+            switch (elem._align) {\r
+                case TextShape.AlignLeft:\r
+                    pen.x = anchor.getX() + _shape.getMarginLeft();\r
+                    break;\r
+                case TextShape.AlignCenter:\r
+                    pen.x = anchor.getX() + _shape.getMarginLeft() +\r
+                            (anchor.getWidth() - elem._text.getAdvance() - _shape.getMarginLeft() - _shape.getMarginRight()) / 2;\r
+                    break;\r
+                case TextShape.AlignRight:\r
+                    pen.x = anchor.getX() + _shape.getMarginLeft() +\r
+                            (anchor.getWidth() - elem._text.getAdvance() - _shape.getMarginLeft() - _shape.getMarginRight());\r
+                    break;\r
+                default:\r
+                    pen.x = anchor.getX() + _shape.getMarginLeft();\r
+                    break;\r
+            }\r
+            if(elem._bullet != null){\r
+                elem._bullet.draw(graphics, (float)(pen.x + elem._bulletOffset), (float)pen.y);\r
+            }\r
+            elem._text.draw(graphics, (float)(pen.x + elem._textOffset), (float)pen.y);\r
+\r
+            y0 += elem._text.getDescent();\r
+            y0 += elem._text.getLeading();\r
+\r
+            y0 += elem._spacing;\r
+        }\r
+    }\r
+\r
+\r
+    public static class TextElement {\r
+        public TextLayout _text;\r
+        public int _textOffset;\r
+        public TextLayout _bullet;\r
+        public int _bulletOffset;\r
+        public int _align;\r
+        public int _startIndex;\r
+        public int _endIndex;\r
+        public float _spacing;\r
+    }\r
+}\r
index 5834e2eaecbf0e9da4960bfc833ca98daea36970..e425b83f18328640d649dd3c38cd8eb7cf77dec5 100644 (file)
@@ -534,6 +534,10 @@ public class TextRun
                // The messes things up on everything but a Mac, so translate
                //  them to \n
                String text = rawText.replace('\r','\n');
+
+        //0xB acts like cariage return in page titles
+        text = text.replace((char) 0x0B, '\n');
+
                return text;
        }
 
index 3c45b34b0ba9937ab25adf01a376387b90d1b2d4..24c3126376b7a70d1878f98bb18700cb49998f0c 100755 (executable)
@@ -513,4 +513,12 @@ public abstract class TextShape extends SimpleShape {
             }\r
         }\r
     }\r
+\r
+    public void draw(Graphics2D graphics){\r
+        AffineTransform at = graphics.getTransform();\r
+        ShapePainter.paint(this, graphics);\r
+        new TextPainter(this).paint(graphics);\r
+        graphics.setTransform(at);\r
+    }\r
+\r
 }\r
index fdaa9eec2c41f3a3134466bf430638ced4d42fb6..187dec12a12f8b578d7e4fb27240cd67f37e8cfe 100644 (file)
@@ -150,7 +150,7 @@ public class StyleTextPropAtom extends RecordAtom
                 new TextProp(2, 0x800000, "symbol"),
                                new TextProp(2, 0x20000, "font.size"),
                                new TextProp(4, 0x40000, "font.color"),
-                               new TextProp(2, 0x80000, "offset"),
+                               new TextProp(2, 0x80000, "superscript"),
                                new TextProp(2, 0x100000, "char_unknown_1"),
                                new TextProp(2, 0x1000000, "char_unknown_3"),
                                new TextProp(2, 0x2000000, "char_unknown_4"),
index b250285ac91a3b4e2ad55ca70a2af78d07e13b93..54cf60c48f7788aa179a548a6e73abbf16fd7068 100644 (file)
@@ -37,9 +37,6 @@ import org.apache.poi.hslf.record.ColorSchemeAtom;
 /**
  * Represents a run of text, all with the same style
  * 
- * TODO: finish all the getters and setters to the
- *  font/character/paragraph properties (currently only
- *  has some of them) 
  */
 public class RichTextRun {
        /** The TextRun we belong to */
@@ -125,7 +122,25 @@ public class RichTextRun {
        public int getLength() {
                return length;
        }
-       
+
+    /**
+     * The beginning index, inclusive.
+     *
+     * @return the beginning index, inclusive.
+     */
+    public int getStartIndex(){
+        return startPos;
+    }
+
+    /**
+     *  The ending index, exclusive.
+     *
+     * @return the ending index, exclusive.
+     */
+    public int getEndIndex(){
+        return startPos + length;
+    }
+
        /**
         * Fetch the text, in output suitable form
         */
@@ -313,35 +328,143 @@ public class RichTextRun {
        
        
        // --------------- Friendly getters / setters on rich text properties -------
-       
+
+    /**
+     * Is the text bold?
+     */
        public boolean isBold() {
                return isCharFlagsTextPropVal(CharFlagsTextProp.BOLD_IDX);
        }
+
+    /**
+     * Is the text bold?
+     */
        public void setBold(boolean bold) {
                setCharFlagsTextPropVal(CharFlagsTextProp.BOLD_IDX, bold);
        }
        
+    /**
+     * Is the text italic?
+     */
        public boolean isItalic() {
                return isCharFlagsTextPropVal(CharFlagsTextProp.ITALIC_IDX);
        }
+
+    /**
+     * Is the text italic?
+     */
        public void setItalic(boolean italic) {
                setCharFlagsTextPropVal(CharFlagsTextProp.ITALIC_IDX, italic);
        }
        
+    /**
+     * Is the text underlined?
+     */
        public boolean isUnderlined() {
                return isCharFlagsTextPropVal(CharFlagsTextProp.UNDERLINE_IDX);
        }
+
+    /**
+     * Is the text underlined?
+     */
        public void setUnderlined(boolean underlined) {
                setCharFlagsTextPropVal(CharFlagsTextProp.UNDERLINE_IDX, underlined);
        }
-       
+
+    /**
+     * Does the text have a shadow?
+     */
+    public boolean isShadowed() {
+        return isCharFlagsTextPropVal(CharFlagsTextProp.SHADOW_IDX);
+    }
+
+    /**
+     * Does the text have a shadow?
+     */
+    public void setShadowed(boolean flag) {
+        setCharFlagsTextPropVal(CharFlagsTextProp.SHADOW_IDX, flag);
+    }
+
+    /**
+     * Is this text embossed?
+     */
+     public boolean isEmbossed() {
+        return isCharFlagsTextPropVal(CharFlagsTextProp.RELIEF_IDX);
+    }
+
+    /**
+     * Is this text embossed?
+     */
+     public void setEmbossed(boolean flag) {
+        setCharFlagsTextPropVal(CharFlagsTextProp.RELIEF_IDX, flag);
+    }
+
+    /**
+     * Gets the strikethrough flag
+     */
+    public boolean isStrikethrough() {
+        return isCharFlagsTextPropVal(CharFlagsTextProp.STRIKETHROUGH_IDX);
+    }
+
+    /**
+     * Sets the strikethrough flag
+     */
+    public void setStrikethrough(boolean flag) {
+        setCharFlagsTextPropVal(CharFlagsTextProp.STRIKETHROUGH_IDX, flag);
+    }
+
+    /**
+     * Gets the subscript/superscript option
+     *
+     * @return the percentage of the font size. If the value is positive, it is superscript, otherwise it is subscript
+     */
+       public int getSuperscript() {
+        int val = getCharTextPropVal("superscript");
+               return val == -1 ? 0 : val;
+       }
+
+    /**
+     * Sets the subscript/superscript option
+     *
+     * @param val the percentage of the font size. If the value is positive, it is superscript, otherwise it is subscript
+     */
+       public void setSuperscript(int val) {
+               setCharTextPropVal("superscript", val);
+       }
+
+    /**
+     * Gets the font size
+     */
        public int getFontSize() {
                return getCharTextPropVal("font.size");
        }
+
+
+    /**
+     * Sets the font size
+     */
        public void setFontSize(int fontSize) {
                setCharTextPropVal("font.size", fontSize);
        }
-       
+
+    /**
+     * Gets the font index
+     */
+       public int getFontIndex() {
+               return getCharTextPropVal("font.index");
+       }
+
+    /**
+     * Sets the font index
+     */
+       public void setFontIndex(int idx) {
+               setCharTextPropVal("font.index", idx);
+       }
+
+
+    /**
+     * Sets the font name to use
+     */
        public void setFontName(String fontName) {
         if (slideShow == null) {
             //we can't set font since slideshow is not assigned yet
@@ -352,6 +475,10 @@ public class RichTextRun {
                    setCharTextPropVal("font.index", fontIdx);
         }
        }
+
+    /**
+     * Gets the font name
+     */
        public String getFontName() {
         if (slideShow == null) {
             return _fontname;
@@ -427,7 +554,7 @@ public class RichTextRun {
     /**
      * Sets indentation level
      *
-     * @param level indentation level. Must be in the range [0, 5]
+     * @param level indentation level. Must be in the range [0, 4]
      */
     public void setIndentLevel(int level) {
         if(paragraphStyle != null ) paragraphStyle.setReservedField((short)level);
@@ -488,6 +615,107 @@ public class RichTextRun {
     public int getTextOffset() {
         return getParaTextPropVal("text.offset")*Shape.POINT_DPI/Shape.MASTER_DPI;
     }
+
+    /**
+     * Sets the bullet size
+     */
+    public void setBulletSize(int size) {
+        setParaTextPropVal("bullet.size", size);
+    }
+
+    /**
+     * Returns the bullet size
+     */
+    public int getBulletSize() {
+        return getParaTextPropVal("bullet.size");
+    }
+
+    /**
+     * Sets the bullet color
+     */
+    public void setBulletColor(Color color) {
+        int rgb = new Color(color.getBlue(), color.getGreen(), color.getRed(), 254).getRGB();
+        setParaTextPropVal("bullet.color", rgb);
+    }
+
+    /**
+     * Returns the bullet color
+     */
+    public Color getBulletColor() {
+        int rgb = getCharTextPropVal("bullet.color");
+        if (rgb >= 0x8000000) {
+            int idx = rgb % 0x8000000;
+            ColorSchemeAtom ca = parentRun.getSheet().getColorScheme();
+            if(idx >= 0 && idx <= 7) rgb = ca.getColor(idx);
+        }
+
+        Color tmp = new Color(rgb, true);
+        return new Color(tmp.getBlue(), tmp.getGreen(), tmp.getRed());
+    }
+
+    /**
+     * Sets the bullet font
+     */
+    public void setBulletFont(int idx) {
+        setParaTextPropVal("bullet.font", idx);
+    }
+
+    /**
+     * Returns the bullet font
+     */
+    public int getBulletFont() {
+        return getParaTextPropVal("bullet.font");
+    }
+
+    /**
+     * Sets the line spacing.
+     * <p>
+     * If linespacing >= 0, then linespacing is a percentage of normal line height.
+     * If linespacing < 0, the absolute value of linespacing is the spacing in master coordinates.
+     * </p>
+     */
+    public void setLineSpacing(int val) {
+        setParaTextPropVal("linespacing", val);
+    }
+
+    /**
+     * Returns the line spacing
+     * <p>
+     * If linespacing >= 0, then linespacing is a percentage of normal line height.
+     * If linespacing < 0, the absolute value of linespacing is the spacing in master coordinates.
+     * </p>
+     *
+     * @return the spacing between lines
+     */
+    public int getLineSpacing() {
+        int val = getParaTextPropVal("linespacing");
+        return val == -1 ? 0 : val;
+    }
+
+    /**
+     * Sets spacing before a paragraph.
+     * <p>
+     * If spacebefore >= 0, then spacebefore is a percentage of normal line height.
+     * If spacebefore < 0, the absolute value of spacebefore is the spacing in master coordinates.
+     * </p>
+     */
+    public void setSpaceBefore(int val) {
+        setParaTextPropVal("spacebefore", val);
+    }
+
+    /**
+     * Returns spacing before a paragraph
+     * <p>
+     * If spacebefore >= 0, then spacebefore is a percentage of normal line height.
+     * If spacebefore < 0, the absolute value of spacebefore is the spacing in master coordinates.
+     * </p>
+     *
+     * @return the spacing before a paragraph
+     */
+    public int getSpaceBefore() {
+        int val = getParaTextPropVal("spacebefore");
+        return val == -1 ? 0 : val;
+    }
        // --------------- Internal HSLF methods, not intended for end-user use! -------
        
        /**
index 3fda0fda17f006d8490ff7b71a742acb85a4cb41..3d89d0272f4f7b15c53b56e9a8fd297aa8591760 100644 (file)
@@ -141,7 +141,7 @@ public class TestSlideOrdering extends TestCase {
                     "ROMANCE: AN ANALYSIS",
                     "AGENDA",
                     "You are an important supplier of various items that I need",
-                    (char)0x0B + "Although The Psycho set back my relationship process, recovery is luckily enough under way",
+                    '\n' + "Although The Psycho set back my relationship process, recovery is luckily enough under way",
                     "Since the time that we seriously go out together, you rank highly among existing relationships",
                     "Although our personal interests are mostly compatible, the greatest gap exists in Sex and Shopping",
                     "Your physical characteristics are strong when compared with your competition",