more improvements in slide rendering

git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@649911 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/src/scratchpad/src/org/apache/poi/hslf/model/Background.java b/src/scratchpad/src/org/apache/poi/hslf/model/Background.java
index 4906922..e2718f6 100644
--- a/src/scratchpad/src/org/apache/poi/hslf/model/Background.java
+++ b/src/scratchpad/src/org/apache/poi/hslf/model/Background.java
@@ -1,4 +1,3 @@
-
 /* ====================================================================
    Licensed to the Apache Software Foundation (ASF) under one or more
    contributor license agreements.  See the NOTICE file distributed with
@@ -19,6 +18,14 @@
 package org.apache.poi.hslf.model;
 
 import org.apache.poi.ddf.EscherContainerRecord;
+import org.apache.poi.hslf.usermodel.PictureData;
+import org.apache.poi.hslf.blip.Bitmap;
+import org.apache.poi.util.POILogger;
+
+import javax.imageio.ImageIO;
+import java.awt.*;
+import java.awt.image.BufferedImage;
+import java.io.ByteArrayInputStream;
 
 /**
  * Background shape
@@ -27,12 +34,42 @@
  */
 public class Background extends Shape {
 
-    protected Background(EscherContainerRecord escherRecord, Shape parent){
+    protected Background(EscherContainerRecord escherRecord, Shape parent) {
         super(escherRecord, parent);
     }
 
-    protected EscherContainerRecord createSpContainer(boolean isChild){
+    protected EscherContainerRecord createSpContainer(boolean isChild) {
         return null;
     }
 
+    public void draw(Graphics2D graphics) {
+        Fill f = getFill();
+        Dimension pg = getSheet().getSlideShow().getPageSize();
+        Rectangle anchor = new Rectangle(0, 0, pg.width, pg.height);
+        switch (f.getFillType()) {
+            case Fill.FILL_SOLID:
+                Color color = f.getForegroundColor();
+                graphics.setPaint(color);
+                graphics.fill(anchor);
+                break;
+            case Fill.FILL_PICTURE:
+                PictureData data = f.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;
+                    }
+                    Image scaledImg = img.getScaledInstance(anchor.width, anchor.height, Image.SCALE_SMOOTH);
+                    graphics.drawImage(scaledImg, anchor.x, anchor.y, null);
+
+                }
+                break;
+            default:
+                logger.log(POILogger.WARN, "unsuported fill type: " + f.getFillType());
+                break;
+        }
+    }
 }
diff --git a/src/scratchpad/src/org/apache/poi/hslf/model/Picture.java b/src/scratchpad/src/org/apache/poi/hslf/model/Picture.java
index ed64279..e109869 100644
--- a/src/scratchpad/src/org/apache/poi/hslf/model/Picture.java
+++ b/src/scratchpad/src/org/apache/poi/hslf/model/Picture.java
@@ -21,6 +21,7 @@
 import org.apache.poi.hslf.usermodel.SlideShow;
 import org.apache.poi.hslf.record.Document;
 import org.apache.poi.hslf.blip.Bitmap;
+import org.apache.poi.hslf.exceptions.HSLFException;
 import org.apache.poi.util.POILogger;
 
 import javax.imageio.ImageIO;
@@ -29,6 +30,7 @@
 import java.awt.geom.Rectangle2D;
 import java.io.ByteArrayInputStream;
 import java.io.IOException;
+import java.io.UnsupportedEncodingException;
 import java.util.List;
 import java.util.Arrays;
 
@@ -129,7 +131,7 @@
 
         //set default properties for a picture
         EscherOptRecord opt = (EscherOptRecord)getEscherChild(_escherContainer, EscherOptRecord.RECORD_ID);
-        setEscherProperty(opt, EscherProperties.PROTECTION__LOCKAGAINSTGROUPING, 8388736);
+        setEscherProperty(opt, EscherProperties.PROTECTION__LOCKAGAINSTGROUPING, 0x800080);
 
         //another weird feature of powerpoint: for picture id we must add 0x4000.
         setEscherProperty(opt, (short)(EscherProperties.BLIP__BLIPTODISPLAY + 0x4000), idx);
@@ -194,6 +196,43 @@
     }
 
     /**
+     * Name of this picture.
+     *
+     * @return name of this picture
+     */
+    public String getPictureName(){
+        EscherOptRecord opt = (EscherOptRecord)getEscherChild(_escherContainer, EscherOptRecord.RECORD_ID);
+        EscherComplexProperty prop = (EscherComplexProperty)getEscherProperty(opt, EscherProperties.BLIP__BLIPFILENAME);
+        String name = null;
+        if(prop != null){
+            try {
+                name = new String(prop.getComplexData(), "UTF-16LE");
+                int idx = name.indexOf('\u0000');
+                return idx == -1 ? name : name.substring(0, idx);
+            } catch (UnsupportedEncodingException e){
+                throw new HSLFException(e);
+            }
+        }
+        return name;
+    }
+
+    /**
+     * Name of this picture.
+     *
+     * @param name of this picture
+     */
+    public void setPictureName(String name){
+        EscherOptRecord opt = (EscherOptRecord)getEscherChild(_escherContainer, EscherOptRecord.RECORD_ID);
+        try {
+            byte[] data = (name + '\u0000').getBytes("UTF-16LE");
+            EscherComplexProperty prop = new EscherComplexProperty(EscherProperties.BLIP__BLIPFILENAME, false, data);
+            opt.addEscherProperty(prop);
+        } catch (UnsupportedEncodingException e){
+            throw new HSLFException(e);
+        }
+    }
+
+    /**
      * By default set the orininal image size
      */
     protected void afterInsert(Sheet sh){
@@ -219,7 +258,7 @@
             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());
+            logger.log(POILogger.WARN, "Rendering of metafiles is not yet supported. image.type: " + (data == null ? "NA" : data.getClass().getName()));
         }
     }
 }
diff --git a/src/scratchpad/src/org/apache/poi/hslf/model/Shape.java b/src/scratchpad/src/org/apache/poi/hslf/model/Shape.java
index 2aed43f..d4975df 100644
--- a/src/scratchpad/src/org/apache/poi/hslf/model/Shape.java
+++ b/src/scratchpad/src/org/apache/poi/hslf/model/Shape.java
@@ -166,12 +166,24 @@
         if ((flags & EscherSpRecord.FLAG_CHILD) != 0){
             EscherChildAnchorRecord rec = (EscherChildAnchorRecord)getEscherChild(_escherContainer, EscherChildAnchorRecord.RECORD_ID);
             anchor = new java.awt.Rectangle();
-            anchor = new Rectangle2D.Float(
-                (float)rec.getDx1()*POINT_DPI/MASTER_DPI,
-                (float)rec.getDy1()*POINT_DPI/MASTER_DPI,
-                (float)(rec.getDx2()-rec.getDx1())*POINT_DPI/MASTER_DPI,
-                (float)(rec.getDy2()-rec.getDy1())*POINT_DPI/MASTER_DPI
-            );
+            if(rec == null){
+                logger.log(POILogger.WARN, "EscherSpRecord.FLAG_CHILD is set but EscherChildAnchorRecord was not found");
+                EscherClientAnchorRecord clrec = (EscherClientAnchorRecord)getEscherChild(_escherContainer, EscherClientAnchorRecord.RECORD_ID);
+                anchor = new java.awt.Rectangle();
+                anchor = new Rectangle2D.Float(
+                    (float)clrec.getCol1()*POINT_DPI/MASTER_DPI,
+                    (float)clrec.getFlag()*POINT_DPI/MASTER_DPI,
+                    (float)(clrec.getDx1()-clrec.getCol1())*POINT_DPI/MASTER_DPI,
+                    (float)(clrec.getRow1()-clrec.getFlag())*POINT_DPI/MASTER_DPI
+                );
+            } else {
+                anchor = new Rectangle2D.Float(
+                    (float)rec.getDx1()*POINT_DPI/MASTER_DPI,
+                    (float)rec.getDy1()*POINT_DPI/MASTER_DPI,
+                    (float)(rec.getDx2()-rec.getDx1())*POINT_DPI/MASTER_DPI,
+                    (float)(rec.getDy2()-rec.getDy1())*POINT_DPI/MASTER_DPI
+                );
+            }
         }
         else {
             EscherClientAnchorRecord rec = (EscherClientAnchorRecord)getEscherChild(_escherContainer, EscherClientAnchorRecord.RECORD_ID);
@@ -245,7 +257,7 @@
      * @return escher property or <code>null</code> if not found.
      */
      public static EscherProperty getEscherProperty(EscherOptRecord opt, int propId){
-        for ( Iterator iterator = opt.getEscherProperties().iterator(); iterator.hasNext(); )
+        if(opt != null) for ( Iterator iterator = opt.getEscherProperties().iterator(); iterator.hasNext(); )
         {
             EscherProperty prop = (EscherProperty) iterator.next();
             if (prop.getPropertyNumber() == propId)
diff --git a/src/scratchpad/src/org/apache/poi/hslf/model/ShapeGroup.java b/src/scratchpad/src/org/apache/poi/hslf/model/ShapeGroup.java
index 2c14681..a0fbb11 100644
--- a/src/scratchpad/src/org/apache/poi/hslf/model/ShapeGroup.java
+++ b/src/scratchpad/src/org/apache/poi/hslf/model/ShapeGroup.java
@@ -236,10 +236,21 @@
         EscherContainerRecord spContainer = (EscherContainerRecord)_escherContainer.getChildRecords().get(0);
         EscherClientAnchorRecord clientAnchor = (EscherClientAnchorRecord)getEscherChild(spContainer, EscherClientAnchorRecord.RECORD_ID);
         Rectangle2D.Float anchor = new Rectangle2D.Float();
-        anchor.x = (float)clientAnchor.getCol1()*POINT_DPI/MASTER_DPI;
-        anchor.y = (float)clientAnchor.getFlag()*POINT_DPI/MASTER_DPI;
-        anchor.width = (float)(clientAnchor.getDx1() - clientAnchor.getCol1())*POINT_DPI/MASTER_DPI ;
-        anchor.height = (float)(clientAnchor.getRow1() - clientAnchor.getFlag())*POINT_DPI/MASTER_DPI;
+        if(clientAnchor == null){
+            logger.log(POILogger.WARN, "EscherClientAnchorRecord was not found for the shape group");
+            EscherChildAnchorRecord rec = (EscherChildAnchorRecord)getEscherChild(spContainer, EscherChildAnchorRecord.RECORD_ID);
+            anchor = new Rectangle2D.Float(
+                (float)rec.getDx1()*POINT_DPI/MASTER_DPI,
+                (float)rec.getDy1()*POINT_DPI/MASTER_DPI,
+                (float)(rec.getDx2()-rec.getDx1())*POINT_DPI/MASTER_DPI,
+                (float)(rec.getDy2()-rec.getDy1())*POINT_DPI/MASTER_DPI
+            );
+        } else {
+            anchor.x = (float)clientAnchor.getCol1()*POINT_DPI/MASTER_DPI;
+            anchor.y = (float)clientAnchor.getFlag()*POINT_DPI/MASTER_DPI;
+            anchor.width = (float)(clientAnchor.getDx1() - clientAnchor.getCol1())*POINT_DPI/MASTER_DPI ;
+            anchor.height = (float)(clientAnchor.getRow1() - clientAnchor.getFlag())*POINT_DPI/MASTER_DPI;
+        }
 
         return anchor;
     }
diff --git a/src/scratchpad/src/org/apache/poi/hslf/model/ShapePainter.java b/src/scratchpad/src/org/apache/poi/hslf/model/ShapePainter.java
index 4e30545..b50b4f7 100755
--- a/src/scratchpad/src/org/apache/poi/hslf/model/ShapePainter.java
+++ b/src/scratchpad/src/org/apache/poi/hslf/model/ShapePainter.java
@@ -17,6 +17,9 @@
 package org.apache.poi.hslf.model;

 

 

+import org.apache.poi.util.POILogger;

+import org.apache.poi.util.POILogFactory;

+

 import java.awt.*;

 import java.awt.geom.Rectangle2D;

 

@@ -26,6 +29,7 @@
  * @author Yegor Kozlov

  */

 public class ShapePainter {

+    protected static POILogger logger = POILogFactory.getLogger(ShapePainter.class);

 

     public static void paint(SimpleShape shape, Graphics2D graphics){

         Rectangle2D anchor = shape.getAnchor2D();

@@ -59,6 +63,7 @@
         //fill

         Color fillColor = shape.getFill().getForegroundColor();

         if (fillColor != null) {

+            //TODO: implement gradient and texture fill patterns

             graphics.setPaint(fillColor);

             graphics.fill(outline);

         }

@@ -68,12 +73,24 @@
         if (lineColor != null){

             graphics.setPaint(lineColor);

             float width = (float)shape.getLineWidth();

+            if(width == 0) width = 0.75f;

+

             int dashing = shape.getLineDashing();

             //TODO: implement more dashing styles

             float[] dashptrn = null;

             switch(dashing){

+                case Line.PEN_SOLID:

+                    dashptrn = null;

+                    break;

                 case Line.PEN_PS_DASH:

-                    dashptrn = new float[]{2, 2};

+                    dashptrn = new float[]{width, width};

+                    break;

+                case Line.PEN_DOTGEL:

+                    dashptrn = new float[]{width*4, width*3};

+                    break;

+               default:

+                    logger.log(POILogger.WARN, "unsupported dashing: " + dashing);

+                    dashptrn = new float[]{width, width};

                     break;

             }

 

diff --git a/src/scratchpad/src/org/apache/poi/hslf/model/SlideMaster.java b/src/scratchpad/src/org/apache/poi/hslf/model/SlideMaster.java
index 2a84674..26870db 100644
--- a/src/scratchpad/src/org/apache/poi/hslf/model/SlideMaster.java
+++ b/src/scratchpad/src/org/apache/poi/hslf/model/SlideMaster.java
@@ -101,8 +101,6 @@
                     default:
                         return null;
                 }
-                return null;
-
             }
             prop = getStyleAttribute(txtype, level, name, isCharacter);
         }
diff --git a/src/scratchpad/src/org/apache/poi/hslf/model/TextPainter.java b/src/scratchpad/src/org/apache/poi/hslf/model/TextPainter.java
index c4eac3d..7d23c7c 100755
--- a/src/scratchpad/src/org/apache/poi/hslf/model/TextPainter.java
+++ b/src/scratchpad/src/org/apache/poi/hslf/model/TextPainter.java
@@ -44,6 +44,9 @@
         _shape = shape;

     }

 

+    /**

+     * Convert the underlying set of rich text runs into java.text.AttributedString

+     */

     public AttributedString getAttributedString(TextRun txrun){

         String text = txrun.getText();

         AttributedString at = new AttributedString(text);

@@ -70,16 +73,6 @@
         return at;

     }

 

-    protected RichTextRun getRichTextRunAt(int pos){

-        RichTextRun[] rt = _shape.getTextRun().getRichTextRuns();

-        for (int i = 0; i < rt.length; i++) {

-            int start = rt[i].getStartIndex();

-            int end = rt[i].getEndIndex();

-            if(pos >= start && pos < end) return rt[i];

-        }

-        return null;

-    }

-

     public void paint(Graphics2D graphics){

         TextRun run = _shape.getTextRun();

         if (run == null) return;

@@ -106,7 +99,7 @@
             boolean prStart = text.charAt(startIndex) == '\n';

             if(prStart) measurer.setPosition(startIndex++);

 

-            RichTextRun rt = getRichTextRunAt(startIndex);

+            RichTextRun rt = run.getRichTextRunAt(startIndex == text.length() ? (startIndex-1) : startIndex);

             if(rt == null) {

                 logger.log(POILogger.WARN,  "RichTextRun not found at pos" + startIndex + "; text.length: " + text.length());

                 break;

@@ -133,33 +126,58 @@
             }

             int endIndex = measurer.getPosition();

 

+            float lineHeight = (float)textLayout.getBounds().getHeight();

+            int linespacing = rt.getLineSpacing();

+            if(linespacing == 0) linespacing = 100;

+

             TextElement el = new TextElement();

-            el.ascent = textLayout.getAscent();

-            el._startIndex = startIndex;

-            el._endIndex = endIndex;

+            if(linespacing >= 0){

+                el.ascent = textLayout.getAscent()*linespacing/100;

+            } else {

+                el.ascent = -linespacing*Shape.POINT_DPI/Shape.MASTER_DPI;

+            }

+

             el._align = rt.getAlignment();

             el._text = textLayout;

             el._textOffset = rt.getTextOffset();

 

-            textHeight += textLayout.getAscent();

-            if (prStart || startIndex == 0){

-                int spaceBefore = rt.getSpaceBefore();

-                if (spaceBefore != 0) {

-                    float val = (float)(textLayout.getAscent() + textLayout.getDescent())* spaceBefore/100;

-                    textHeight += val;

-                    el.ascent += val;

+            if (prStart){

+                int sp = rt.getSpaceBefore();

+                float spaceBefore;

+                if(sp >= 0){

+                    spaceBefore = lineHeight * sp/100;

+                } else {

+                    spaceBefore = -sp*Shape.POINT_DPI/Shape.MASTER_DPI;

                 }

+                el.ascent += spaceBefore;

             }

 

+            float descent;

+            if(linespacing >= 0){

+                descent = (textLayout.getDescent() + textLayout.getLeading())*linespacing/100;

+            } else {

+                descent = -linespacing*Shape.POINT_DPI/Shape.MASTER_DPI;

+            }

+            if (prStart){

+                int sp = rt.getSpaceAfter();

+                float spaceAfter;

+                if(sp >= 0){

+                    spaceAfter = lineHeight * sp/100;

+                } else {

+                    spaceAfter = -sp*Shape.POINT_DPI/Shape.MASTER_DPI;

+                }

+                el.ascent += spaceAfter;

+            }

+            el.descent = descent;

+

+            textHeight += el.ascent + el.descent;

+

             if(rt.isBullet() && (prStart || startIndex == 0)){

                 it.setIndex(startIndex);

 

                 AttributedString bat = new AttributedString(Character.toString(rt.getBulletChar()), it.getAttributes());

-                int bulletSize = rt.getBulletSize();

-                if (bulletSize != -1){

-                    Float sz =  (Float)bat.getIterator().getAttribute(TextAttribute.SIZE);

-                    if(sz != null) bat.addAttribute(TextAttribute.SIZE, new Float(sz.floatValue()*bulletSize/100));

-                }

+                Color clr = rt.getBulletColor();

+                if (clr != null) bat.addAttribute(TextAttribute.FOREGROUND, clr);

 

                 TextLayout bulletLayout = new TextLayout(bat.getIterator(), graphics.getFontRenderContext());

                 if(text.substring(startIndex, endIndex).length() > 1){

@@ -167,24 +185,6 @@
                     el._bulletOffset = rt.getBulletOffset();

                 }

             }

-

-

-            float descent = textLayout.getDescent();

-            int lineSpacing = rt.getLineSpacing();

-            if(lineSpacing != 0) descent += textLayout.getLeading()*lineSpacing/100;

-            else descent = textLayout.getLeading();

-            textHeight += descent;

-

-            el.descent = descent;

-            if (prStart){

-                int spaceAfter = rt.getSpaceAfter();

-                if (spaceAfter != 0) {

-                    float val = (float)(textLayout.getAscent() + textLayout.getDescent())* spaceAfter/100;

-                    textHeight += val;

-                    el.descent += val;

-                }

-            }

-

             lines.add(el);

         }

 

@@ -242,9 +242,6 @@
         public TextLayout _bullet;

         public int _bulletOffset;

         public int _align;

-        public int _startIndex;

-        public int _endIndex;

-        public float _spacing;

         public float ascent, descent;

     }

 }

diff --git a/src/scratchpad/src/org/apache/poi/hslf/model/TextRun.java b/src/scratchpad/src/org/apache/poi/hslf/model/TextRun.java
index e425b83..2f77ac5 100644
--- a/src/scratchpad/src/org/apache/poi/hslf/model/TextRun.java
+++ b/src/scratchpad/src/org/apache/poi/hslf/model/TextRun.java
@@ -639,4 +639,20 @@
     public Hyperlink[] getHyperlinks(){
         return Hyperlink.find(this);
     }
+
+    /**
+     * Fetch RichTextRun at a given position
+     *
+     * @param pos 0-based index in the text
+     * @return RichTextRun or null if not found
+     */
+    public RichTextRun getRichTextRunAt(int pos){
+        for (int i = 0; i < _rtRuns.length; i++) {
+            int start = _rtRuns[i].getStartIndex();
+            int end = _rtRuns[i].getEndIndex();
+            if(pos >= start && pos < end) return _rtRuns[i];
+        }
+        return null;
+    }
+
 }
diff --git a/src/scratchpad/src/org/apache/poi/hslf/usermodel/RichTextRun.java b/src/scratchpad/src/org/apache/poi/hslf/usermodel/RichTextRun.java
index c589797..2a09f22 100644
--- a/src/scratchpad/src/org/apache/poi/hslf/usermodel/RichTextRun.java
+++ b/src/scratchpad/src/org/apache/poi/hslf/usermodel/RichTextRun.java
@@ -642,13 +642,14 @@
      * 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);
-        }
+        int rgb = getParaTextPropVal("bullet.color");
+        if(rgb == -1) return getFontColor();
 
+        int cidx = rgb >> 24;
+        if (rgb % 0x1000000 == 0){
+            ColorSchemeAtom ca = parentRun.getSheet().getColorScheme();
+            if(cidx >= 0 && cidx <= 7) rgb = ca.getColor(cidx);
+        }
         Color tmp = new Color(rgb, true);
         return new Color(tmp.getBlue(), tmp.getGreen(), tmp.getRed());
     }
diff --git a/src/scratchpad/testcases/org/apache/poi/hslf/usermodel/TestPictures.java b/src/scratchpad/testcases/org/apache/poi/hslf/usermodel/TestPictures.java
index 797f97c..78458f0 100644
--- a/src/scratchpad/testcases/org/apache/poi/hslf/usermodel/TestPictures.java
+++ b/src/scratchpad/testcases/org/apache/poi/hslf/usermodel/TestPictures.java
@@ -437,4 +437,33 @@
         assertTrue(pdata instanceof WMF);
         assertEquals(Picture.WMF, pdata.getType());
 	}
+
+    public void testGetPictureName() throws Exception {
+        SlideShow ppt = new SlideShow(new HSLFSlideShow(new File(cwd, "ppt_with_png.ppt").getPath()));
+        Slide slide = ppt.getSlides()[0];
+
+        Picture p = (Picture)slide.getShapes()[0]; //the first slide contains JPEG
+        assertEquals("test", p.getPictureName());
+    }
+
+    public void testSetPictureName() throws Exception {
+        SlideShow ppt = new SlideShow();
+
+        Slide slide = ppt.createSlide();
+        File img = new File(cwd, "tomcat.png");
+        int idx = ppt.addPicture(img, Picture.PNG);
+        Picture pict = new Picture(idx);
+        pict.setPictureName("tomcat.png");
+        slide.addShape(pict);
+
+        //serialize and read again
+        ByteArrayOutputStream out = new ByteArrayOutputStream();
+        ppt.write(out);
+        out.close();
+
+        ppt = new SlideShow(new ByteArrayInputStream(out.toByteArray()));
+
+        Picture p = (Picture)ppt.getSlides()[0].getShapes()[0];
+        assertEquals("tomcat.png", p.getPictureName());
+    }
 }