]> source.dussan.org Git - poi.git/commitdiff
Fixed TextShape.resizeToFitText() to properly resize TextShape. See patch#45140
authorYegor Kozlov <yegor@apache.org>
Sun, 8 Jun 2008 12:32:10 +0000 (12:32 +0000)
committerYegor Kozlov <yegor@apache.org>
Sun, 8 Jun 2008 12:32:10 +0000 (12:32 +0000)
git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@664492 13f79535-47bb-0310-9956-ffa450edef68

src/scratchpad/src/org/apache/poi/hslf/model/TextShape.java

index 53f8ef97d57cf91c6f359946d6ea1f6ca3c76b7e..d1a84a5e44416f130975697e268d71ddc5c2d8bd 100755 (executable)
-\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.model;\r
-\r
-import org.apache.poi.ddf.*;\r
-import org.apache.poi.hslf.record.*;\r
-import org.apache.poi.hslf.usermodel.RichTextRun;\r
-import org.apache.poi.hslf.exceptions.HSLFException;\r
-import org.apache.poi.util.POILogger;\r
-\r
-import java.awt.*;\r
-import java.awt.geom.Rectangle2D;\r
-import java.awt.geom.AffineTransform;\r
-import java.awt.font.FontRenderContext;\r
-import java.awt.font.TextLayout;\r
-import java.io.IOException;\r
-\r
-/**\r
- * A common superclass of all shapes that can hold text.\r
- *\r
- * @author Yegor Kozlov\r
- */\r
-public abstract class TextShape extends SimpleShape {\r
-\r
-    /**\r
-     * How to anchor the text\r
-     */\r
-    public static final int AnchorTop = 0;\r
-    public static final int AnchorMiddle = 1;\r
-    public static final int AnchorBottom = 2;\r
-    public static final int AnchorTopCentered = 3;\r
-    public static final int AnchorMiddleCentered = 4;\r
-    public static final int AnchorBottomCentered = 5;\r
-    public static final int AnchorTopBaseline = 6;\r
-    public static final int AnchorBottomBaseline = 7;\r
-    public static final int AnchorTopCenteredBaseline = 8;\r
-    public static final int AnchorBottomCenteredBaseline = 9;\r
-\r
-    /**\r
-     * How to wrap the text\r
-     */\r
-    public static final int WrapSquare = 0;\r
-    public static final int WrapByPoints = 1;\r
-    public static final int WrapNone = 2;\r
-    public static final int WrapTopBottom = 3;\r
-    public static final int WrapThrough = 4;\r
-\r
-    /**\r
-     * How to align the text\r
-     */\r
-    public static final int AlignLeft = 0;\r
-    public static final int AlignCenter = 1;\r
-    public static final int AlignRight = 2;\r
-    public static final int AlignJustify = 3;\r
-\r
-    /**\r
-     * TextRun object which holds actual text and format data\r
-     */\r
-    protected TextRun _txtrun;\r
-\r
-    /**\r
-     * Escher container which holds text attributes such as\r
-     * TextHeaderAtom, TextBytesAtom ot TextCharsAtom, StyleTextPropAtom etc.\r
-     */\r
-    protected EscherTextboxWrapper _txtbox;\r
-\r
-    /**\r
-     * Used to calculate text bounds\r
-     */\r
-    protected static final FontRenderContext _frc = new FontRenderContext(null, true, true);\r
-\r
-    /**\r
-     * Create a TextBox object and initialize it from the supplied Record container.\r
-     * \r
-     * @param escherRecord       <code>EscherSpContainer</code> container which holds information about this shape\r
-     * @param parent    the parent of the shape\r
-     */\r
-   protected TextShape(EscherContainerRecord escherRecord, Shape parent){\r
-        super(escherRecord, parent);\r
-\r
-    }\r
-\r
-    /**\r
-     * Create a new TextBox. This constructor is used when a new shape is created.\r
-     *\r
-     * @param parent    the parent of this Shape. For example, if this text box is a cell\r
-     * in a table then the parent is Table.\r
-     */\r
-    public TextShape(Shape parent){\r
-        super(null, parent);\r
-        _escherContainer = createSpContainer(parent instanceof ShapeGroup);\r
-    }\r
-\r
-    /**\r
-     * Create a new TextBox. This constructor is used when a new shape is created.\r
-     *\r
-     */\r
-    public TextShape(){\r
-        this(null);\r
-    }\r
-\r
-    public TextRun createTextRun(){\r
-        _txtbox = getEscherTextboxWrapper();\r
-        if(_txtbox == null) _txtbox = new EscherTextboxWrapper();\r
-\r
-        _txtrun = getTextRun();\r
-        if(_txtrun == null){\r
-            TextHeaderAtom tha = new TextHeaderAtom();\r
-            tha.setParentRecord(_txtbox);\r
-            _txtbox.appendChildRecord(tha);\r
-\r
-            TextCharsAtom tca = new TextCharsAtom();\r
-            _txtbox.appendChildRecord(tca);\r
-\r
-            StyleTextPropAtom sta = new StyleTextPropAtom(0);\r
-            _txtbox.appendChildRecord(sta);\r
-\r
-            _txtrun = new TextRun(tha,tca,sta);\r
-            _txtrun.setText("");\r
-\r
-            _escherContainer.addChildRecord(_txtbox.getEscherRecord());\r
-\r
-            setDefaultTextProperties(_txtrun);\r
-        }\r
-\r
-        return _txtrun;\r
-    }\r
-\r
-    /**\r
-     * Set default properties for the  TextRun.\r
-     * Depending on the text and shape type the defaults are different:\r
-     *   TextBox: align=left, valign=top\r
-     *   AutoShape: align=center, valign=middle\r
-     *\r
-     */\r
-    protected void setDefaultTextProperties(TextRun _txtrun){\r
-\r
-    }\r
-\r
-    /**\r
-     * Returns the text contained in this text frame.\r
-     *\r
-     * @return the text string for this textbox.\r
-     */\r
-     public String getText(){\r
-        TextRun tx = getTextRun();\r
-        return tx == null ? null : tx.getText();\r
-    }\r
-\r
-    /**\r
-     * Sets the text contained in this text frame.\r
-     *\r
-     * @param text the text string used by this object.\r
-     */\r
-    public void setText(String text){\r
-        TextRun tx = getTextRun();\r
-        if(tx == null){\r
-            tx = createTextRun();\r
-        }\r
-        tx.setText(text);\r
-        setTextId(text.hashCode());\r
-    }\r
-\r
-    /**\r
-     * When a textbox is added to  a sheet we need to tell upper-level\r
-     * <code>PPDrawing</code> about it.\r
-     *\r
-     * @param sh the sheet we are adding to\r
-     */\r
-    protected void afterInsert(Sheet sh){\r
-        super.afterInsert(sh);\r
-\r
-        EscherTextboxWrapper _txtbox = getEscherTextboxWrapper();\r
-        if(_txtbox != null){\r
-            PPDrawing ppdrawing = sh.getPPDrawing();\r
-            ppdrawing.addTextboxWrapper(_txtbox);\r
-            // Ensure the escher layer knows about the added records\r
-            try {\r
-                _txtbox.writeOut(null);\r
-            } catch (IOException e){\r
-                throw new HSLFException(e);\r
-            }\r
-            if(getAnchor().equals(new Rectangle()) && !"".equals(getText())) resizeToFitText();\r
-        }\r
-    }\r
-\r
-    protected EscherTextboxWrapper getEscherTextboxWrapper(){\r
-        if(_txtbox == null){\r
-            EscherTextboxRecord textRecord = (EscherTextboxRecord)Shape.getEscherChild(_escherContainer, EscherTextboxRecord.RECORD_ID);\r
-            if(textRecord != null) _txtbox = new EscherTextboxWrapper(textRecord);\r
-        }\r
-        return _txtbox;\r
-    }\r
-    /**\r
-     * Adjust the size of the TextShape so it encompasses the text inside it.\r
-     *\r
-     * @return a <code>Rectangle2D</code> that is the bounds of this <code>TextShape</code>.\r
-     */\r
-    public Rectangle2D resizeToFitText(){\r
-        String txt = getText();\r
-        if(txt == null || txt.length() == 0) return new Rectangle2D.Float();\r
-\r
-        RichTextRun rt = getTextRun().getRichTextRuns()[0];\r
-        int size = rt.getFontSize();\r
-        int style = 0;\r
-        if (rt.isBold()) style |= Font.BOLD;\r
-        if (rt.isItalic()) style |= Font.ITALIC;\r
-        String fntname = rt.getFontName();\r
-        Font font = new Font(fntname, style, size);\r
-\r
-        float width = 0, height = 0;\r
-        String[] lines = txt.split("\r");\r
-        for (int i = 0; i < lines.length; i++) {\r
-            if(lines[i].length() == 0) continue;\r
-\r
-            TextLayout layout = new TextLayout(lines[i], font, _frc);\r
-\r
-            width = Math.max(width, layout.getAdvance());\r
-\r
-            /**\r
-             * Even if top and bottom margins are set to 0 PowerPoint\r
-             * always sets extra space between the text and its bounding box.\r
-             *\r
-             * The approximation height = ascent*2 works good enough in most cases\r
-             */\r
-            height = Math.max(height, 2*layout.getAscent());\r
-        }\r
-\r
-        width += getMarginLeft() + getMarginRight();\r
-        height += getMarginTop() + getMarginBottom();\r
-\r
-        Rectangle2D anchor = getAnchor2D();\r
-        anchor.setRect(anchor.getX(), anchor.getY(), width, height);\r
-        setAnchor(anchor);\r
-\r
-        return anchor;\r
-    }\r
-\r
-    /**\r
-     * Returns the type of vertical alignment for the text.\r
-     * One of the <code>Anchor*</code> constants defined in this class.\r
-     *\r
-     * @return the type of alignment\r
-     */\r
-    public int getVerticalAlignment(){\r
-        EscherOptRecord opt = (EscherOptRecord)getEscherChild(_escherContainer, EscherOptRecord.RECORD_ID);\r
-        EscherSimpleProperty prop = (EscherSimpleProperty)getEscherProperty(opt, EscherProperties.TEXT__ANCHORTEXT);\r
-        int valign = TextShape.AnchorTop;\r
-        if (prop == null){\r
-            /**\r
-             * If vertical alignment was not found in the shape properties then try to\r
-             * fetch the master shape and search for the align property there.\r
-             */\r
-            int type = getTextRun().getRunType();\r
-            MasterSheet master = getSheet().getMasterSheet();\r
-            if(master != null){\r
-                TextShape masterShape = master.getPlaceholder(type);\r
-                if(masterShape != null) valign = masterShape.getVerticalAlignment();\r
-            } else {\r
-                //not found in the master sheet. Use the hardcoded defaults.\r
-                switch (type){\r
-                     case TextHeaderAtom.TITLE_TYPE:\r
-                     case TextHeaderAtom.CENTER_TITLE_TYPE:\r
-                         valign = TextShape.AnchorMiddle;\r
-                         break;\r
-                     default:\r
-                         valign = TextShape.AnchorTop;\r
-                         break;\r
-                 }\r
-            }\r
-        } else {\r
-            valign = prop.getPropertyValue();\r
-        }\r
-        return valign;\r
-    }\r
-\r
-    /**\r
-     * Sets the type of vertical alignment for the text.\r
-     * One of the <code>Anchor*</code> constants defined in this class.\r
-     *\r
-     * @param align - the type of alignment\r
-     */\r
-    public void setVerticalAlignment(int align){\r
-        setEscherProperty(EscherProperties.TEXT__ANCHORTEXT, align);\r
-    }\r
-\r
-    /**\r
-     * Sets the type of horizontal alignment for the text.\r
-     * One of the <code>Align*</code> constants defined in this class.\r
-     *\r
-     * @param align - the type of horizontal alignment\r
-     */\r
-    public void setHorizontalAlignment(int align){\r
-        TextRun tx = getTextRun();\r
-        if(tx != null) tx.getRichTextRuns()[0].setAlignment(align);\r
-    }\r
-\r
-    /**\r
-     * Gets the type of horizontal alignment for the text.\r
-     * One of the <code>Align*</code> constants defined in this class.\r
-     *\r
-     * @return align - the type of horizontal alignment\r
-     */\r
-    public int getHorizontalAlignment(){\r
-        TextRun tx = getTextRun();\r
-        return tx == null ? -1 : tx.getRichTextRuns()[0].getAlignment();\r
-    }\r
-\r
-    /**\r
-     * Returns the distance (in points) between the bottom of the text frame\r
-     * and the bottom of the inscribed rectangle of the shape that contains the text.\r
-     * Default value is 1/20 inch.\r
-     *\r
-     * @return the botom margin\r
-     */\r
-    public float getMarginBottom(){\r
-        EscherOptRecord opt = (EscherOptRecord)getEscherChild(_escherContainer, EscherOptRecord.RECORD_ID);\r
-        EscherSimpleProperty prop = (EscherSimpleProperty)getEscherProperty(opt, EscherProperties.TEXT__TEXTBOTTOM);\r
-        int val = prop == null ? EMU_PER_INCH/20 : prop.getPropertyValue();\r
-        return (float)val/EMU_PER_POINT;\r
-    }\r
-\r
-    /**\r
-     * Sets the botom margin.\r
-     * @see #getMarginBottom()\r
-     *\r
-     * @param margin    the bottom margin\r
-     */\r
-    public void setMarginBottom(float margin){\r
-        setEscherProperty(EscherProperties.TEXT__TEXTBOTTOM, (int)(margin*EMU_PER_POINT));\r
-    }\r
-\r
-    /**\r
-     *  Returns the distance (in points) between the left edge of the text frame\r
-     *  and the left edge of the inscribed rectangle of the shape that contains\r
-     *  the text.\r
-     *  Default value is 1/10 inch.\r
-     *\r
-     * @return the left margin\r
-     */\r
-    public float getMarginLeft(){\r
-        EscherOptRecord opt = (EscherOptRecord)getEscherChild(_escherContainer, EscherOptRecord.RECORD_ID);\r
-        EscherSimpleProperty prop = (EscherSimpleProperty)getEscherProperty(opt, EscherProperties.TEXT__TEXTBOTTOM);\r
-        int val = prop == null ? EMU_PER_INCH/10 : prop.getPropertyValue();\r
-        return (float)val/EMU_PER_POINT;\r
-    }\r
-\r
-    /**\r
-     * Sets the left margin.\r
-     * @see #getMarginLeft()\r
-     *\r
-     * @param margin    the left margin\r
-     */\r
-    public void setMarginLeft(float margin){\r
-        setEscherProperty(EscherProperties.TEXT__TEXTLEFT, (int)(margin*EMU_PER_POINT));\r
-    }\r
-\r
-    /**\r
-     *  Returns the distance (in points) between the right edge of the\r
-     *  text frame and the right edge of the inscribed rectangle of the shape\r
-     *  that contains the text.\r
-     *  Default value is 1/10 inch.\r
-     *\r
-     * @return the right margin\r
-     */\r
-    public float getMarginRight(){\r
-        EscherOptRecord opt = (EscherOptRecord)getEscherChild(_escherContainer, EscherOptRecord.RECORD_ID);\r
-        EscherSimpleProperty prop = (EscherSimpleProperty)getEscherProperty(opt, EscherProperties.TEXT__TEXTRIGHT);\r
-        int val = prop == null ? EMU_PER_INCH/10 : prop.getPropertyValue();\r
-        return (float)val/EMU_PER_POINT;\r
-    }\r
-\r
-    /**\r
-     * Sets the right margin.\r
-     * @see #getMarginRight()\r
-     *\r
-     * @param margin    the right margin\r
-     */\r
-    public void setMarginRight(float margin){\r
-        setEscherProperty(EscherProperties.TEXT__TEXTRIGHT, (int)(margin*EMU_PER_POINT));\r
-    }\r
-\r
-     /**\r
-     *  Returns the distance (in points) between the top of the text frame\r
-     *  and the top of the inscribed rectangle of the shape that contains the text.\r
-     *  Default value is 1/20 inch.\r
-     *\r
-     * @return the top margin\r
-     */\r
-    public float getMarginTop(){\r
-        EscherOptRecord opt = (EscherOptRecord)getEscherChild(_escherContainer, EscherOptRecord.RECORD_ID);\r
-        EscherSimpleProperty prop = (EscherSimpleProperty)getEscherProperty(opt, EscherProperties.TEXT__TEXTTOP);\r
-        int val = prop == null ? EMU_PER_INCH/20 : prop.getPropertyValue();\r
-        return (float)val/EMU_PER_POINT;\r
-    }\r
-\r
-   /**\r
-     * Sets the top margin.\r
-     * @see #getMarginTop()\r
-     *\r
-     * @param margin    the top margin\r
-     */\r
-    public void setMarginTop(float margin){\r
-        setEscherProperty(EscherProperties.TEXT__TEXTTOP, (int)(margin*EMU_PER_POINT));\r
-    }\r
-\r
-\r
-    /**\r
-     * Returns the value indicating word wrap.\r
-     *\r
-     * @return the value indicating word wrap.\r
-     *  Must be one of the <code>Wrap*</code> constants defined in this class.\r
-     */\r
-    public int getWordWrap(){\r
-        EscherOptRecord opt = (EscherOptRecord)getEscherChild(_escherContainer, EscherOptRecord.RECORD_ID);\r
-        EscherSimpleProperty prop = (EscherSimpleProperty)getEscherProperty(opt, EscherProperties.TEXT__WRAPTEXT);\r
-        return prop == null ? WrapSquare : prop.getPropertyValue();\r
-    }\r
-\r
-    /**\r
-     *  Specifies how the text should be wrapped\r
-     *\r
-     * @param wrap  the value indicating how the text should be wrapped.\r
-     *  Must be one of the <code>Wrap*</code> constants defined in this class.\r
-     */\r
-    public void setWordWrap(int wrap){\r
-        setEscherProperty(EscherProperties.TEXT__WRAPTEXT, wrap);\r
-    }\r
-\r
-    /**\r
-     * @return id for the text.\r
-     */\r
-    public int getTextId(){\r
-        EscherOptRecord opt = (EscherOptRecord)getEscherChild(_escherContainer, EscherOptRecord.RECORD_ID);\r
-        EscherSimpleProperty prop = (EscherSimpleProperty)getEscherProperty(opt, EscherProperties.TEXT__TEXTID);\r
-        return prop == null ? 0 : prop.getPropertyValue();\r
-    }\r
-\r
-    /**\r
-     * Sets text ID\r
-     *\r
-     * @param id of the text\r
-     */\r
-    public void setTextId(int id){\r
-        setEscherProperty(EscherProperties.TEXT__TEXTID, id);\r
-    }\r
-\r
-    /**\r
-      * @return the TextRun object for this text box\r
-      */\r
-     public TextRun getTextRun(){\r
-         if(_txtrun == null) initTextRun();\r
-         return _txtrun;\r
-     }\r
-\r
-    public void setSheet(Sheet sheet) {\r
-        _sheet = sheet;\r
-\r
-        // Initialize _txtrun object.\r
-        // (We can't do it in the constructor because the sheet\r
-        //  is not assigned then, it's only built once we have\r
-        //  all the records)\r
-        TextRun tx = getTextRun();\r
-        if (tx != null) {\r
-            // Supply the sheet to our child RichTextRuns\r
-            tx.setSheet(_sheet);\r
-            RichTextRun[] rt = tx.getRichTextRuns();\r
-            for (int i = 0; i < rt.length; i++) {\r
-                rt[i].supplySlideShow(_sheet.getSlideShow());\r
-            }\r
-        }\r
-\r
-    }\r
-\r
-    protected void initTextRun(){\r
-        EscherTextboxWrapper txtbox = getEscherTextboxWrapper();\r
-        Sheet sheet = getSheet();\r
-\r
-        if(sheet == null || txtbox == null) return;\r
-\r
-        OutlineTextRefAtom ota = null;\r
-\r
-        Record[] child = txtbox.getChildRecords();\r
-        for (int i = 0; i < child.length; i++) {\r
-            if (child[i] instanceof OutlineTextRefAtom) {\r
-                ota = (OutlineTextRefAtom)child[i];\r
-                break;\r
-            }\r
-        }\r
-\r
-        TextRun[] runs = _sheet.getTextRuns();\r
-        if (ota != null) {\r
-            int idx = ota.getTextIndex();\r
-            for (int i = 0; i < runs.length; i++) {\r
-                if(runs[i].getIndex() == idx){\r
-                    _txtrun = runs[i];\r
-                    break;\r
-                }\r
-            }\r
-            if(_txtrun == null) {\r
-                logger.log(POILogger.WARN, "text run not found for OutlineTextRefAtom.TextIndex=" + idx);\r
-            }\r
-        } else {\r
-            int shapeId = _escherContainer.getChildById(EscherSpRecord.RECORD_ID).getShapeId();\r
-            if(runs != null) for (int i = 0; i < runs.length; i++) {\r
-                if(runs[i].getShapeId() == shapeId){\r
-                    _txtrun = runs[i];\r
-                    break;\r
-                }\r
-            }\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
+
+/* ====================================================================
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+==================================================================== */
+
+package org.apache.poi.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.geom.Rectangle2D;
+import java.awt.geom.AffineTransform;
+import java.awt.font.FontRenderContext;
+import java.awt.font.TextLayout;
+import java.io.IOException;
+import java.util.Iterator;
+
+/**
+ * A common superclass of all shapes that can hold text.
+ *
+ * @author Yegor Kozlov
+ */
+public abstract class TextShape extends SimpleShape {
+
+    /**
+     * How to anchor the text
+     */
+    public static final int AnchorTop = 0;
+    public static final int AnchorMiddle = 1;
+    public static final int AnchorBottom = 2;
+    public static final int AnchorTopCentered = 3;
+    public static final int AnchorMiddleCentered = 4;
+    public static final int AnchorBottomCentered = 5;
+    public static final int AnchorTopBaseline = 6;
+    public static final int AnchorBottomBaseline = 7;
+    public static final int AnchorTopCenteredBaseline = 8;
+    public static final int AnchorBottomCenteredBaseline = 9;
+
+    /**
+     * How to wrap the text
+     */
+    public static final int WrapSquare = 0;
+    public static final int WrapByPoints = 1;
+    public static final int WrapNone = 2;
+    public static final int WrapTopBottom = 3;
+    public static final int WrapThrough = 4;
+
+    /**
+     * How to align the text
+     */
+    public static final int AlignLeft = 0;
+    public static final int AlignCenter = 1;
+    public static final int AlignRight = 2;
+    public static final int AlignJustify = 3;
+
+    /**
+     * TextRun object which holds actual text and format data
+     */
+    protected TextRun _txtrun;
+
+    /**
+     * Escher container which holds text attributes such as
+     * TextHeaderAtom, TextBytesAtom ot TextCharsAtom, StyleTextPropAtom etc.
+     */
+    protected EscherTextboxWrapper _txtbox;
+
+    /**
+     * Used to calculate text bounds
+     */
+    protected static final FontRenderContext _frc = new FontRenderContext(null, true, true);
+
+    /**
+     * Create a TextBox object and initialize it from the supplied Record container.
+     * 
+     * @param escherRecord       <code>EscherSpContainer</code> container which holds information about this shape
+     * @param parent    the parent of the shape
+     */
+   protected TextShape(EscherContainerRecord escherRecord, Shape parent){
+        super(escherRecord, parent);
+
+    }
+
+    /**
+     * Create a new TextBox. This constructor is used when a new shape is created.
+     *
+     * @param parent    the parent of this Shape. For example, if this text box is a cell
+     * in a table then the parent is Table.
+     */
+    public TextShape(Shape parent){
+        super(null, parent);
+        _escherContainer = createSpContainer(parent instanceof ShapeGroup);
+    }
+
+    /**
+     * Create a new TextBox. This constructor is used when a new shape is created.
+     *
+     */
+    public TextShape(){
+        this(null);
+    }
+
+    public TextRun createTextRun(){
+        _txtbox = getEscherTextboxWrapper();
+        if(_txtbox == null) _txtbox = new EscherTextboxWrapper();
+
+        _txtrun = getTextRun();
+        if(_txtrun == null){
+            TextHeaderAtom tha = new TextHeaderAtom();
+            tha.setParentRecord(_txtbox);
+            _txtbox.appendChildRecord(tha);
+
+            TextCharsAtom tca = new TextCharsAtom();
+            _txtbox.appendChildRecord(tca);
+
+            StyleTextPropAtom sta = new StyleTextPropAtom(0);
+            _txtbox.appendChildRecord(sta);
+
+            _txtrun = new TextRun(tha,tca,sta);
+            _txtrun.setText("");
+
+            _escherContainer.addChildRecord(_txtbox.getEscherRecord());
+
+            setDefaultTextProperties(_txtrun);
+        }
+
+        return _txtrun;
+    }
+
+    /**
+     * Set default properties for the  TextRun.
+     * Depending on the text and shape type the defaults are different:
+     *   TextBox: align=left, valign=top
+     *   AutoShape: align=center, valign=middle
+     *
+     */
+    protected void setDefaultTextProperties(TextRun _txtrun){
+
+    }
+
+    /**
+     * Returns the text contained in this text frame.
+     *
+     * @return the text string for this textbox.
+     */
+     public String getText(){
+        TextRun tx = getTextRun();
+        return tx == null ? null : tx.getText();
+    }
+
+    /**
+     * Sets the text contained in this text frame.
+     *
+     * @param text the text string used by this object.
+     */
+    public void setText(String text){
+        TextRun tx = getTextRun();
+        if(tx == null){
+            tx = createTextRun();
+        }
+        tx.setText(text);
+        setTextId(text.hashCode());
+    }
+
+    /**
+     * When a textbox is added to  a sheet we need to tell upper-level
+     * <code>PPDrawing</code> about it.
+     *
+     * @param sh the sheet we are adding to
+     */
+    protected void afterInsert(Sheet sh){
+        super.afterInsert(sh);
+
+        EscherTextboxWrapper _txtbox = getEscherTextboxWrapper();
+        if(_txtbox != null){
+            PPDrawing ppdrawing = sh.getPPDrawing();
+            ppdrawing.addTextboxWrapper(_txtbox);
+            // Ensure the escher layer knows about the added records
+            try {
+                _txtbox.writeOut(null);
+            } catch (IOException e){
+                throw new HSLFException(e);
+            }
+            if(getAnchor().equals(new Rectangle()) && !"".equals(getText())) resizeToFitText();
+        }
+    }
+
+    protected EscherTextboxWrapper getEscherTextboxWrapper(){
+        if(_txtbox == null){
+            EscherTextboxRecord textRecord = (EscherTextboxRecord)Shape.getEscherChild(_escherContainer, EscherTextboxRecord.RECORD_ID);
+            if(textRecord != null) _txtbox = new EscherTextboxWrapper(textRecord);
+        }
+        return _txtbox;
+    }
+    /**
+     * Adjust the size of the TextShape so it encompasses the text inside it.
+     *
+     * @return a <code>Rectangle2D</code> that is the bounds of this <code>TextShape</code>.
+     */
+    public Rectangle2D resizeToFitText(){
+        String txt = getText();
+        if(txt == null || txt.length() == 0) return new Rectangle2D.Float();
+
+        RichTextRun rt = getTextRun().getRichTextRuns()[0];
+        int size = rt.getFontSize();
+        int style = 0;
+        if (rt.isBold()) style |= Font.BOLD;
+        if (rt.isItalic()) style |= Font.ITALIC;
+        String fntname = rt.getFontName();
+        Font font = new Font(fntname, style, size);
+
+        float width = 0, height = 0, leading = 0;        
+        String[] lines = txt.split("\n");        
+        for (int i = 0; i < lines.length; i++) {
+            if(lines[i].length() == 0) continue;
+            
+            TextLayout layout = new TextLayout(lines[i], font, _frc);
+            
+            leading = Math.max(leading, layout.getLeading());           
+            width = Math.max(width, layout.getAdvance());
+            height = Math.max(height, (height + (layout.getDescent() + layout.getAscent())));
+        }      
+       
+        // add one character to width
+        Rectangle2D charBounds = font.getMaxCharBounds(_frc);                
+        width += getMarginLeft() + getMarginRight() + charBounds.getWidth();
+        
+        // add leading to height        
+        height += getMarginTop() + getMarginBottom() + leading;
+
+        Rectangle2D anchor = getAnchor2D();
+        anchor.setRect(anchor.getX(), anchor.getY(), width, height);
+        setAnchor(anchor);
+
+        return anchor;
+    }
+
+    /**
+     * Returns the type of vertical alignment for the text.
+     * One of the <code>Anchor*</code> constants defined in this class.
+     *
+     * @return the type of alignment
+     */
+    public int getVerticalAlignment(){
+        EscherOptRecord opt = (EscherOptRecord)getEscherChild(_escherContainer, EscherOptRecord.RECORD_ID);
+        EscherSimpleProperty prop = (EscherSimpleProperty)getEscherProperty(opt, EscherProperties.TEXT__ANCHORTEXT);
+        int valign = TextShape.AnchorTop;
+        if (prop == null){
+            /**
+             * If vertical alignment was not found in the shape properties then try to
+             * fetch the master shape and search for the align property there.
+             */
+            int type = getTextRun().getRunType();
+            MasterSheet master = getSheet().getMasterSheet();
+            if(master != null){
+                TextShape masterShape = master.getPlaceholder(type);
+                if(masterShape != null) valign = masterShape.getVerticalAlignment();
+            } else {
+                //not found in the master sheet. Use the hardcoded defaults.
+                switch (type){
+                     case TextHeaderAtom.TITLE_TYPE:
+                     case TextHeaderAtom.CENTER_TITLE_TYPE:
+                         valign = TextShape.AnchorMiddle;
+                         break;
+                     default:
+                         valign = TextShape.AnchorTop;
+                         break;
+                 }
+            }
+        } else {
+            valign = prop.getPropertyValue();
+        }
+        return valign;
+    }
+
+    /**
+     * Sets the type of vertical alignment for the text.
+     * One of the <code>Anchor*</code> constants defined in this class.
+     *
+     * @param align - the type of alignment
+     */
+    public void setVerticalAlignment(int align){
+        setEscherProperty(EscherProperties.TEXT__ANCHORTEXT, align);
+    }
+
+    /**
+     * Sets the type of horizontal alignment for the text.
+     * One of the <code>Align*</code> constants defined in this class.
+     *
+     * @param align - the type of horizontal alignment
+     */
+    public void setHorizontalAlignment(int align){
+        TextRun tx = getTextRun();
+        if(tx != null) tx.getRichTextRuns()[0].setAlignment(align);
+    }
+
+    /**
+     * Gets the type of horizontal alignment for the text.
+     * One of the <code>Align*</code> constants defined in this class.
+     *
+     * @return align - the type of horizontal alignment
+     */
+    public int getHorizontalAlignment(){
+        TextRun tx = getTextRun();
+        return tx == null ? -1 : tx.getRichTextRuns()[0].getAlignment();
+    }
+
+    /**
+     * Returns the distance (in points) between the bottom of the text frame
+     * and the bottom of the inscribed rectangle of the shape that contains the text.
+     * Default value is 1/20 inch.
+     *
+     * @return the botom margin
+     */
+    public float getMarginBottom(){
+        EscherOptRecord opt = (EscherOptRecord)getEscherChild(_escherContainer, EscherOptRecord.RECORD_ID);
+        EscherSimpleProperty prop = (EscherSimpleProperty)getEscherProperty(opt, EscherProperties.TEXT__TEXTBOTTOM);
+        int val = prop == null ? EMU_PER_INCH/20 : prop.getPropertyValue();
+        return (float)val/EMU_PER_POINT;
+    }
+
+    /**
+     * Sets the botom margin.
+     * @see #getMarginBottom()
+     *
+     * @param margin    the bottom margin
+     */
+    public void setMarginBottom(float margin){
+        setEscherProperty(EscherProperties.TEXT__TEXTBOTTOM, (int)(margin*EMU_PER_POINT));
+    }
+
+    /**
+     *  Returns the distance (in points) between the left edge of the text frame
+     *  and the left edge of the inscribed rectangle of the shape that contains
+     *  the text.
+     *  Default value is 1/10 inch.
+     *
+     * @return the left margin
+     */
+    public float getMarginLeft(){
+        EscherOptRecord opt = (EscherOptRecord)getEscherChild(_escherContainer, EscherOptRecord.RECORD_ID);
+        EscherSimpleProperty prop = (EscherSimpleProperty)getEscherProperty(opt, EscherProperties.TEXT__TEXTBOTTOM);
+        int val = prop == null ? EMU_PER_INCH/10 : prop.getPropertyValue();
+        return (float)val/EMU_PER_POINT;
+    }
+
+    /**
+     * Sets the left margin.
+     * @see #getMarginLeft()
+     *
+     * @param margin    the left margin
+     */
+    public void setMarginLeft(float margin){
+        setEscherProperty(EscherProperties.TEXT__TEXTLEFT, (int)(margin*EMU_PER_POINT));
+    }
+
+    /**
+     *  Returns the distance (in points) between the right edge of the
+     *  text frame and the right edge of the inscribed rectangle of the shape
+     *  that contains the text.
+     *  Default value is 1/10 inch.
+     *
+     * @return the right margin
+     */
+    public float getMarginRight(){
+        EscherOptRecord opt = (EscherOptRecord)getEscherChild(_escherContainer, EscherOptRecord.RECORD_ID);
+        EscherSimpleProperty prop = (EscherSimpleProperty)getEscherProperty(opt, EscherProperties.TEXT__TEXTRIGHT);
+        int val = prop == null ? EMU_PER_INCH/10 : prop.getPropertyValue();
+        return (float)val/EMU_PER_POINT;
+    }
+
+    /**
+     * Sets the right margin.
+     * @see #getMarginRight()
+     *
+     * @param margin    the right margin
+     */
+    public void setMarginRight(float margin){
+        setEscherProperty(EscherProperties.TEXT__TEXTRIGHT, (int)(margin*EMU_PER_POINT));
+    }
+
+     /**
+     *  Returns the distance (in points) between the top of the text frame
+     *  and the top of the inscribed rectangle of the shape that contains the text.
+     *  Default value is 1/20 inch.
+     *
+     * @return the top margin
+     */
+    public float getMarginTop(){
+        EscherOptRecord opt = (EscherOptRecord)getEscherChild(_escherContainer, EscherOptRecord.RECORD_ID);
+        EscherSimpleProperty prop = (EscherSimpleProperty)getEscherProperty(opt, EscherProperties.TEXT__TEXTTOP);
+        int val = prop == null ? EMU_PER_INCH/20 : prop.getPropertyValue();
+        return (float)val/EMU_PER_POINT;
+    }
+
+   /**
+     * Sets the top margin.
+     * @see #getMarginTop()
+     *
+     * @param margin    the top margin
+     */
+    public void setMarginTop(float margin){
+        setEscherProperty(EscherProperties.TEXT__TEXTTOP, (int)(margin*EMU_PER_POINT));
+    }
+
+
+    /**
+     * Returns the value indicating word wrap.
+     *
+     * @return the value indicating word wrap.
+     *  Must be one of the <code>Wrap*</code> constants defined in this class.
+     */
+    public int getWordWrap(){
+        EscherOptRecord opt = (EscherOptRecord)getEscherChild(_escherContainer, EscherOptRecord.RECORD_ID);
+        EscherSimpleProperty prop = (EscherSimpleProperty)getEscherProperty(opt, EscherProperties.TEXT__WRAPTEXT);
+        return prop == null ? WrapSquare : prop.getPropertyValue();
+    }
+
+    /**
+     *  Specifies how the text should be wrapped
+     *
+     * @param wrap  the value indicating how the text should be wrapped.
+     *  Must be one of the <code>Wrap*</code> constants defined in this class.
+     */
+    public void setWordWrap(int wrap){
+        setEscherProperty(EscherProperties.TEXT__WRAPTEXT, wrap);
+    }
+
+    /**
+     * @return id for the text.
+     */
+    public int getTextId(){
+        EscherOptRecord opt = (EscherOptRecord)getEscherChild(_escherContainer, EscherOptRecord.RECORD_ID);
+        EscherSimpleProperty prop = (EscherSimpleProperty)getEscherProperty(opt, EscherProperties.TEXT__TEXTID);
+        return prop == null ? 0 : prop.getPropertyValue();
+    }
+
+    /**
+     * Sets text ID
+     *
+     * @param id of the text
+     */
+    public void setTextId(int id){
+        setEscherProperty(EscherProperties.TEXT__TEXTID, id);
+    }
+
+    /**
+      * @return the TextRun object for this text box
+      */
+     public TextRun getTextRun(){
+         if(_txtrun == null) initTextRun();
+         return _txtrun;
+     }
+
+    public void setSheet(Sheet sheet) {
+        _sheet = sheet;
+
+        // Initialize _txtrun object.
+        // (We can't do it in the constructor because the sheet
+        //  is not assigned then, it's only built once we have
+        //  all the records)
+        TextRun tx = getTextRun();
+        if (tx != null) {
+            // Supply the sheet to our child RichTextRuns
+            tx.setSheet(_sheet);
+            RichTextRun[] rt = tx.getRichTextRuns();
+            for (int i = 0; i < rt.length; i++) {
+                rt[i].supplySlideShow(_sheet.getSlideShow());
+            }
+        }
+
+    }
+
+    protected void initTextRun(){
+        EscherTextboxWrapper txtbox = getEscherTextboxWrapper();
+        Sheet sheet = getSheet();
+
+        if(sheet == null || txtbox == null) return;
+
+        OutlineTextRefAtom ota = null;
+
+        Record[] child = txtbox.getChildRecords();
+        for (int i = 0; i < child.length; i++) {
+            if (child[i] instanceof OutlineTextRefAtom) {
+                ota = (OutlineTextRefAtom)child[i];
+                break;
+            }
+        }
+
+        TextRun[] runs = _sheet.getTextRuns();
+        if (ota != null) {
+            int idx = ota.getTextIndex();
+            for (int i = 0; i < runs.length; i++) {
+                if(runs[i].getIndex() == idx){
+                    _txtrun = runs[i];
+                    break;
+                }
+            }
+            if(_txtrun == null) {
+                logger.log(POILogger.WARN, "text run not found for OutlineTextRefAtom.TextIndex=" + idx);
+            }
+        } else {
+            int shapeId = _escherContainer.getChildById(EscherSpRecord.RECORD_ID).getShapeId();
+            if(runs != null) for (int i = 0; i < runs.length; i++) {
+                if(runs[i].getShapeId() == shapeId){
+                    _txtrun = runs[i];
+                    break;
+                }
+            }
+        }
+    }
+
+    public void draw(Graphics2D graphics){
+        AffineTransform at = graphics.getTransform();
+        ShapePainter.paint(this, graphics);
+        new TextPainter(this).paint(graphics);
+        graphics.setTransform(at);
+    }
+
+    /**
+     * Return <code>OEPlaceholderAtom</code>, the atom that describes a placeholder.
+     *
+     * @return <code>OEPlaceholderAtom</code> or <code>null</code> if not found
+     */
+    public OEPlaceholderAtom getPlaceholderAtom(){
+        return (OEPlaceholderAtom)getClientDataRecord(RecordTypes.OEPlaceholderAtom.typeID);
+    }
+
+}