]> source.dussan.org Git - poi.git/commitdiff
Fix for bug 45964 - support for link formulas in Text Objects
authorJosh Micich <josh@apache.org>
Fri, 10 Oct 2008 00:40:58 +0000 (00:40 +0000)
committerJosh Micich <josh@apache.org>
Fri, 10 Oct 2008 00:40:58 +0000 (00:40 +0000)
git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@703302 13f79535-47bb-0310-9956-ffa450edef68

src/documentation/content/xdocs/changes.xml
src/documentation/content/xdocs/status.xml
src/java/org/apache/poi/hssf/model/TextboxShape.java
src/java/org/apache/poi/hssf/record/ContinueRecord.java
src/java/org/apache/poi/hssf/record/RecordFactory.java
src/java/org/apache/poi/hssf/record/TextObjectBaseRecord.java [deleted file]
src/java/org/apache/poi/hssf/record/TextObjectRecord.java
src/java/org/apache/poi/hssf/usermodel/HSSFComment.java
src/java/org/apache/poi/hssf/usermodel/HSSFRichTextString.java
src/testcases/org/apache/poi/hssf/record/TestTextObjectBaseRecord.java
src/testcases/org/apache/poi/hssf/record/TestTextObjectRecord.java

index 19943dc4aff224a7e464bbf96cc8613c636dc466..2ba7f0c978bb4e23344467e1a9c41c46887e8a59 100644 (file)
@@ -37,6 +37,7 @@
 
                <!-- Don't forget to update status.xml too! -->
         <release version="3.2-alpha1" date="2008-??-??">
+           <action dev="POI-DEVELOPERS" type="fix">45964 - support for link formulas in Text Objects</action>
            <action dev="POI-DEVELOPERS" type="fix">43354 - support for evalating formulas with missing args</action>
            <action dev="POI-DEVELOPERS" type="fix">45912 - fixed ArrayIndexOutOfBoundsException in EmbeddedObjectRefSubRecord</action>
            <action dev="POI-DEVELOPERS" type="fix">45889 - fixed ArrayIndexOutOfBoundsException when constructing HSLF Table with a single row </action>
index 94d1e2caee6a21152e67ca08f03d4d13f64bf5aa..8f661cdb4ed6f023a213c5918d9dc58a0e2410ff 100644 (file)
@@ -34,6 +34,7 @@
        <!-- Don't forget to update changes.xml too! -->
     <changes>
         <release version="3.2-alpha1" date="2008-??-??">
+           <action dev="POI-DEVELOPERS" type="fix">45964 - support for link formulas in Text Objects</action>
            <action dev="POI-DEVELOPERS" type="fix">43354 - support for evalating formulas with missing args</action>
            <action dev="POI-DEVELOPERS" type="fix">45912 - fixed ArrayIndexOutOfBoundsException in EmbeddedObjectRefSubRecord</action>
            <action dev="POI-DEVELOPERS" type="fix">45889 - fixed ArrayIndexOutOfBoundsException when constructing HSLF Table with a single row </action>
index b1d3370fbf0d5e94fa9b82e4717fbd646b800ed2..e74197711b24921a21ac7fb42b0efdd25cdab789 100644 (file)
@@ -138,15 +138,11 @@ public class TextboxShape
         HSSFTextbox shape = hssfShape;
 
         TextObjectRecord obj = new TextObjectRecord();
-        obj.setHorizontalTextAlignment( hssfShape.getHorizontalAlignment() );
-        obj.setVerticalTextAlignment( hssfShape.getVerticalAlignment());
-        obj.setTextLocked( true );
-        obj.setTextOrientation( TextObjectRecord.TEXT_ORIENTATION_NONE );
-        int frLength = ( shape.getString().numFormattingRuns() + 1 ) * 8;
-        obj.setFormattingRunLength( (short) frLength );
-        obj.setTextLength( (short) shape.getString().length() );
-        obj.setStr( shape.getString() );
-        obj.setReserved7( 0 );
+        obj.setHorizontalTextAlignment(hssfShape.getHorizontalAlignment());
+        obj.setVerticalTextAlignment(hssfShape.getVerticalAlignment());
+        obj.setTextLocked(true);
+        obj.setTextOrientation(TextObjectRecord.TEXT_ORIENTATION_NONE);
+        obj.setStr(shape.getString());
 
         return obj;
     }
index 435c45471231430f2f90d71fb395a6b119472c4f..e20c4633c3b8936022e43ef81af4b27cbc0aad9b 100644 (file)
@@ -22,76 +22,44 @@ package org.apache.poi.hssf.record;
 import org.apache.poi.util.LittleEndian;
 
 /**
- * Title:        Continue Record - Helper class used primarily for SST Records <P>
+ * Title:        Continue Record(0x003C) - Helper class used primarily for SST Records <P>
  * Description:  handles overflow for prior record in the input
  *               stream; content is tailored to that prior record<P>
  * @author Marc Johnson (mjohnson at apache dot org)
  * @author Andrew C. Oliver (acoliver at apache dot org)
  * @author Csaba Nagy (ncsaba at yahoo dot com)
- * @version 2.0-pre
  */
-
-public class ContinueRecord
-    extends Record
-{
+public final class ContinueRecord extends Record {
     public final static short sid = 0x003C;
-    private byte[]            field_1_data;
-
-    /**
-     * default constructor
-     */
+    private byte[]            _data;
 
-    public ContinueRecord()
-    {
+    public ContinueRecord(byte[] data) {
+        _data = data;
     }
 
     /**
      * USE ONLY within "processContinue"
      */
-
     public byte [] serialize()
     {
-        byte[] retval = new byte[ field_1_data.length + 4 ];
+        byte[] retval = new byte[ _data.length + 4 ];
         serialize(0, retval);
         return retval;
     }
 
-    public int serialize(int offset, byte [] data)
-    {
-
-        LittleEndian.putShort(data, offset, sid);
-        LittleEndian.putShort(data, offset + 2, ( short ) field_1_data.length);
-        System.arraycopy(field_1_data, 0, data, offset + 4, field_1_data.length);
-        return field_1_data.length + 4;
-        // throw new RecordFormatException(
-        //    "You're not supposed to serialize Continue records like this directly");
-    }
-
-       /*
-     * @param data raw data
-     */
-
-    public void setData(byte [] data)
-    {
-        field_1_data = data;
+    public int serialize(int offset, byte[] data) {
+        return write(data, offset, null, _data);
     }
 
     /**
      * get the data for continuation
      * @return byte array containing all of the continued data
      */
-
     public byte [] getData()
     {
-        return field_1_data;
+        return _data;
     }
 
-    /**
-     * Debugging toString
-     *
-     * @return string representation
-     */
-
     public String toString()
     {
         StringBuffer buffer = new StringBuffer();
@@ -113,19 +81,36 @@ public class ContinueRecord
      *
      * @param in the RecordInputstream to read the record from
      */
-
     public ContinueRecord(RecordInputStream in)
     {
-      field_1_data = in.readRemainder();
+      _data = in.readRemainder();
     }
 
-    /**
-     * Clone this record.
-     */
     public Object clone() {
-      ContinueRecord clone = new ContinueRecord();
-      clone.setData(field_1_data);
-      return clone;
+      return new ContinueRecord(_data);
     }
 
+    /**
+     * Writes the full encoding of a Continue record without making an instance
+     */
+    public static int write(byte[] destBuf, int destOffset, Byte initialDataByte, byte[] srcData) {
+        return write(destBuf, destOffset, initialDataByte, srcData, 0, srcData.length);
+    }
+    /**
+     * @param initialDataByte (optional - often used for unicode flag). 
+     * If supplied, this will be written before srcData
+     * @return the total number of bytes written
+     */
+    public static int write(byte[] destBuf, int destOffset, Byte initialDataByte, byte[] srcData, int srcOffset, int len) {
+        int totalLen = len + (initialDataByte == null ? 0 : 1);
+        LittleEndian.putUShort(destBuf, destOffset, sid);
+        LittleEndian.putUShort(destBuf, destOffset + 2, totalLen);
+        int pos = destOffset + 4;
+        if (initialDataByte != null) {
+            LittleEndian.putByte(destBuf, pos, initialDataByte.byteValue());
+            pos += 1;
+        }
+        System.arraycopy(srcData, srcOffset, destBuf, pos, len);
+        return 4 + totalLen;
+    }
 }
index 864065de77cebf96d118e0fe35578f8ee78d1b58..7539e597da5a9742b27b56b960f7978f4a056829 100644 (file)
@@ -321,51 +321,54 @@ public final class RecordFactory {
                Record lastRecord = null;
                while (recStream.hasNextRecord()) {
                        recStream.nextRecord();
-                       if (recStream.getSid() != 0) {
-                               Record[] recs = createRecord(recStream);   // handle MulRK records
+                       if (recStream.getSid() == 0) {
+                               // After EOF, Excel seems to pad block with zeros
+                               continue;
+                       }
+                       Record[] recs = createRecord(recStream);   // handle MulRK records
 
-                               if (recs.length > 1) {
-                                       for (int k = 0; k < recs.length; k++) {
-                                               records.add(recs[ k ]);                    // these will be number records
-                                       }
-                               } else {
-                                       Record record = recs[ 0 ];
+                       if (recs.length > 1) {
+                               for (int k = 0; k < recs.length; k++) {
+                                       records.add(recs[ k ]);                    // these will be number records
+                               }
+                               continue;
+                       }
+                       Record record = recs[ 0 ];
 
-                                       if (record != null) {
-                                               if (record.getSid() == DrawingGroupRecord.sid
-                                                          && lastRecord instanceof DrawingGroupRecord) {
-                                                       DrawingGroupRecord lastDGRecord = (DrawingGroupRecord) lastRecord;
-                                                       lastDGRecord.join((AbstractEscherHolderRecord) record);
-                                               } else if (record.getSid() == ContinueRecord.sid &&
-                                                                ((lastRecord instanceof ObjRecord) || (lastRecord instanceof TextObjectRecord))) {
-                                                       // Drawing records have a very strange continue behaviour.
-                                                       //There can actually be OBJ records mixed between the continues.
-                                                       lastDrawingRecord.processContinueRecord( ((ContinueRecord)record).getData() );
-                                                       //we must remember the position of the continue record.
-                                                       //in the serialization procedure the original structure of records must be preserved
-                                                       records.add(record);
-                                               } else if (record.getSid() == ContinueRecord.sid &&
-                                                               (lastRecord instanceof DrawingGroupRecord)) {
-                                                       ((DrawingGroupRecord)lastRecord).processContinueRecord(((ContinueRecord)record).getData());
-                                               } else if (record.getSid() == ContinueRecord.sid &&
-                                                               (lastRecord instanceof StringRecord)) {
-                                                       ((StringRecord)lastRecord).processContinueRecord(((ContinueRecord)record).getData());
-                                               } else if (record.getSid() == ContinueRecord.sid) {
-                                                       if (lastRecord instanceof UnknownRecord) {
-                                                               //Gracefully handle records that we dont know about,
-                                                               //that happen to be continued
-                                                               records.add(record);
-                                                       } else 
-                                                               throw new RecordFormatException("Unhandled Continue Record");
-                                               } else {
-                                                       lastRecord = record;
-                                                       if (record instanceof DrawingRecord) {
-                                                               lastDrawingRecord = (DrawingRecord) record;
-                                                       }
-                                                       records.add(record);
-                                               }
-                                       }
+                       if (record == null) {
+                               continue;
+                       }
+                       if (record.getSid() == DrawingGroupRecord.sid
+                                  && lastRecord instanceof DrawingGroupRecord) {
+                               DrawingGroupRecord lastDGRecord = (DrawingGroupRecord) lastRecord;
+                               lastDGRecord.join((AbstractEscherHolderRecord) record);
+                       } else if (record.getSid() == ContinueRecord.sid) {
+                               ContinueRecord contRec = (ContinueRecord)record;
+                               
+                               if (lastRecord instanceof ObjRecord || lastRecord instanceof TextObjectRecord) {
+                                       // Drawing records have a very strange continue behaviour.
+                                       //There can actually be OBJ records mixed between the continues.
+                                       lastDrawingRecord.processContinueRecord(contRec.getData() );
+                                       //we must remember the position of the continue record.
+                                       //in the serialization procedure the original structure of records must be preserved
+                                       records.add(record);
+                               } else if (lastRecord instanceof DrawingGroupRecord) {
+                                       ((DrawingGroupRecord)lastRecord).processContinueRecord(contRec.getData());
+                               } else if (lastRecord instanceof StringRecord) {
+                                       ((StringRecord)lastRecord).processContinueRecord(contRec.getData());
+                               } else if (lastRecord instanceof UnknownRecord) {
+                                       //Gracefully handle records that we don't know about,
+                                       //that happen to be continued
+                                       records.add(record);
+                               } else {
+                                       throw new RecordFormatException("Unhandled Continue Record");
+                               }
+                       } else {
+                               lastRecord = record;
+                               if (record instanceof DrawingRecord) {
+                                       lastDrawingRecord = (DrawingRecord) record;
                                }
+                               records.add(record);
                        }
                }
                return records;
diff --git a/src/java/org/apache/poi/hssf/record/TextObjectBaseRecord.java b/src/java/org/apache/poi/hssf/record/TextObjectBaseRecord.java
deleted file mode 100644 (file)
index 59e0cd2..0000000
+++ /dev/null
@@ -1,427 +0,0 @@
-
-/* ====================================================================
-   Licensed to the Apache Software Foundation (ASF) under one or more
-   contributor license agreements.  See the NOTICE file distributed with
-   this work for additional information regarding copyright ownership.
-   The ASF licenses this file to You under the Apache License, Version 2.0
-   (the "License"); you may not use this file except in compliance with
-   the License.  You may obtain a copy of the License at
-
-       http://www.apache.org/licenses/LICENSE-2.0
-
-   Unless required by applicable law or agreed to in writing, software
-   distributed under the License is distributed on an "AS IS" BASIS,
-   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-   See the License for the specific language governing permissions and
-   limitations under the License.
-==================================================================== */
-        
-
-package org.apache.poi.hssf.record;
-
-import org.apache.poi.util.BitField;
-import org.apache.poi.util.BitFieldFactory;
-import org.apache.poi.util.HexDump;
-import org.apache.poi.util.LittleEndian;
-
-/**
- * The TXO record is used to define the properties of a text box.  It is followed
- * by two continue records unless there is no actual text.  The first continue record contains
- * the text data and the next continue record contains the formatting runs.<p/>
- * 
- * @author Glen Stampoultzis (glens at apache.org)
- */
-public class TextObjectBaseRecord extends Record {
-       // TODO - don't instantiate superclass
-    public final static short      sid                             = 0x01B6;
-    
-    private static final BitField reserved1               = BitFieldFactory.getInstance(0x0001);
-    private static final BitField HorizontalTextAlignment = BitFieldFactory.getInstance(0x000E);
-    private static final BitField VerticalTextAlignment   = BitFieldFactory.getInstance(0x0070);
-    private static final BitField reserved2               = BitFieldFactory.getInstance(0x0180);
-    private static final BitField textLocked              = BitFieldFactory.getInstance(0x0200);
-    private static final BitField reserved3               = BitFieldFactory.getInstance(0xFC00);
-    
-    private  short      field_1_options;
-    public final static short  HORIZONTAL_TEXT_ALIGNMENT_LEFT_ALIGNED = 1;
-    public final static short  HORIZONTAL_TEXT_ALIGNMENT_CENTERED = 2;
-    public final static short  HORIZONTAL_TEXT_ALIGNMENT_RIGHT_ALIGNED = 3;
-    public final static short  HORIZONTAL_TEXT_ALIGNMENT_JUSTIFIED = 4;
-    public final static short  VERTICAL_TEXT_ALIGNMENT_TOP    = 1;
-    public final static short  VERTICAL_TEXT_ALIGNMENT_CENTER = 2;
-    public final static short  VERTICAL_TEXT_ALIGNMENT_BOTTOM = 3;
-    public final static short  VERTICAL_TEXT_ALIGNMENT_JUSTIFY = 4;
-    private  short      field_2_textOrientation;
-    public final static short       TEXT_ORIENTATION_NONE          = 0;
-    public final static short       TEXT_ORIENTATION_TOP_TO_BOTTOM = 1;
-    public final static short       TEXT_ORIENTATION_ROT_RIGHT     = 2;
-    public final static short       TEXT_ORIENTATION_ROT_LEFT      = 3;
-    private  short      field_3_reserved4;
-    private  short      field_4_reserved5;
-    private  short      field_5_reserved6;
-    private  short      field_6_textLength;
-    private  short      field_7_formattingRunLength;
-    private  int        field_8_reserved7;
-
-
-    public TextObjectBaseRecord()
-    {
-
-    }
-
-    public TextObjectBaseRecord(RecordInputStream in)
-    {
-        field_1_options                = in.readShort();
-        field_2_textOrientation        = in.readShort();
-        field_3_reserved4              = in.readShort();
-        field_4_reserved5              = in.readShort();
-        field_5_reserved6              = in.readShort();
-        field_6_textLength             = in.readShort();
-        field_7_formattingRunLength    = in.readShort();
-        field_8_reserved7              = in.readInt();
-
-    }
-
-    public String toString()
-    {
-        StringBuffer buffer = new StringBuffer();
-
-        buffer.append("[TXO]\n");
-        buffer.append("    .options              = ")
-            .append("0x").append(HexDump.toHex(  getOptions ()))
-            .append(" (").append( getOptions() ).append(" )");
-        buffer.append(System.getProperty("line.separator")); 
-        buffer.append("         .reserved1                = ").append(isReserved1()).append('\n'); 
-            buffer.append("         .HorizontalTextAlignment     = ").append(getHorizontalTextAlignment()).append('\n'); 
-            buffer.append("         .VerticalTextAlignment     = ").append(getVerticalTextAlignment()).append('\n'); 
-            buffer.append("         .reserved2                = ").append(getReserved2()).append('\n'); 
-        buffer.append("         .textLocked               = ").append(isTextLocked()).append('\n'); 
-            buffer.append("         .reserved3                = ").append(getReserved3()).append('\n'); 
-        buffer.append("    .textOrientation      = ")
-            .append("0x").append(HexDump.toHex(  getTextOrientation ()))
-            .append(" (").append( getTextOrientation() ).append(" )");
-        buffer.append(System.getProperty("line.separator")); 
-        buffer.append("    .reserved4            = ")
-            .append("0x").append(HexDump.toHex(  getReserved4 ()))
-            .append(" (").append( getReserved4() ).append(" )");
-        buffer.append(System.getProperty("line.separator")); 
-        buffer.append("    .reserved5            = ")
-            .append("0x").append(HexDump.toHex(  getReserved5 ()))
-            .append(" (").append( getReserved5() ).append(" )");
-        buffer.append(System.getProperty("line.separator")); 
-        buffer.append("    .reserved6            = ")
-            .append("0x").append(HexDump.toHex(  getReserved6 ()))
-            .append(" (").append( getReserved6() ).append(" )");
-        buffer.append(System.getProperty("line.separator")); 
-        buffer.append("    .textLength           = ")
-            .append("0x").append(HexDump.toHex(  getTextLength ()))
-            .append(" (").append( getTextLength() ).append(" )");
-        buffer.append(System.getProperty("line.separator")); 
-        buffer.append("    .formattingRunLength  = ")
-            .append("0x").append(HexDump.toHex(  getFormattingRunLength ()))
-            .append(" (").append( getFormattingRunLength() ).append(" )");
-        buffer.append(System.getProperty("line.separator")); 
-        buffer.append("    .reserved7            = ")
-            .append("0x").append(HexDump.toHex(  getReserved7 ()))
-            .append(" (").append( getReserved7() ).append(" )");
-        buffer.append(System.getProperty("line.separator")); 
-
-        buffer.append("[/TXO]\n");
-        return buffer.toString();
-    }
-
-    public int serialize(int offset, byte[] data)
-    {
-        int pos = 0;
-
-        LittleEndian.putShort(data, 0 + offset, sid);
-        LittleEndian.putShort(data, 2 + offset, (short)(getRecordSize() - 4));
-
-        LittleEndian.putShort(data, 4 + offset + pos, field_1_options);
-        LittleEndian.putShort(data, 6 + offset + pos, field_2_textOrientation);
-        LittleEndian.putShort(data, 8 + offset + pos, field_3_reserved4);
-        LittleEndian.putShort(data, 10 + offset + pos, field_4_reserved5);
-        LittleEndian.putShort(data, 12 + offset + pos, field_5_reserved6);
-        LittleEndian.putShort(data, 14 + offset + pos, field_6_textLength);
-        LittleEndian.putShort(data, 16 + offset + pos, field_7_formattingRunLength);
-        LittleEndian.putInt(data, 18 + offset + pos, field_8_reserved7);
-
-        return getRecordSize();
-    }
-
-    public int getRecordSize()
-    {
-        return 4  + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 4;
-    }
-
-    public short getSid()
-    {
-        return sid;
-    }
-
-    public Object clone() {
-        TextObjectBaseRecord rec = new TextObjectBaseRecord();
-    
-        rec.field_1_options = field_1_options;
-        rec.field_2_textOrientation = field_2_textOrientation;
-        rec.field_3_reserved4 = field_3_reserved4;
-        rec.field_4_reserved5 = field_4_reserved5;
-        rec.field_5_reserved6 = field_5_reserved6;
-        rec.field_6_textLength = field_6_textLength;
-        rec.field_7_formattingRunLength = field_7_formattingRunLength;
-        rec.field_8_reserved7 = field_8_reserved7;
-        return rec;
-    }
-
-
-
-
-    /**
-     * Get the options field for the TextObjectBase record.
-     */
-    public short getOptions()
-    {
-        return field_1_options;
-    }
-
-    /**
-     * Set the options field for the TextObjectBase record.
-     */
-    public void setOptions(short field_1_options)
-    {
-        this.field_1_options = field_1_options;
-    }
-
-    /**
-     * Get the text orientation field for the TextObjectBase record.
-     *
-     * @return  One of 
-     *        TEXT_ORIENTATION_NONE
-     *        TEXT_ORIENTATION_TOP_TO_BOTTOM
-     *        TEXT_ORIENTATION_ROT_RIGHT
-     *        TEXT_ORIENTATION_ROT_LEFT
-     */
-    public short getTextOrientation()
-    {
-        return field_2_textOrientation;
-    }
-
-    /**
-     * Set the text orientation field for the TextObjectBase record.
-     *
-     * @param field_2_textOrientation
-     *        One of 
-     *        TEXT_ORIENTATION_NONE
-     *        TEXT_ORIENTATION_TOP_TO_BOTTOM
-     *        TEXT_ORIENTATION_ROT_RIGHT
-     *        TEXT_ORIENTATION_ROT_LEFT
-     */
-    public void setTextOrientation(short field_2_textOrientation)
-    {
-        this.field_2_textOrientation = field_2_textOrientation;
-    }
-
-    /**
-     * Get the reserved4 field for the TextObjectBase record.
-     */
-    public short getReserved4()
-    {
-        return field_3_reserved4;
-    }
-
-    /**
-     * Set the reserved4 field for the TextObjectBase record.
-     */
-    public void setReserved4(short field_3_reserved4)
-    {
-        this.field_3_reserved4 = field_3_reserved4;
-    }
-
-    /**
-     * Get the reserved5 field for the TextObjectBase record.
-     */
-    public short getReserved5()
-    {
-        return field_4_reserved5;
-    }
-
-    /**
-     * Set the reserved5 field for the TextObjectBase record.
-     */
-    public void setReserved5(short field_4_reserved5)
-    {
-        this.field_4_reserved5 = field_4_reserved5;
-    }
-
-    /**
-     * Get the reserved6 field for the TextObjectBase record.
-     */
-    public short getReserved6()
-    {
-        return field_5_reserved6;
-    }
-
-    /**
-     * Set the reserved6 field for the TextObjectBase record.
-     */
-    public void setReserved6(short field_5_reserved6)
-    {
-        this.field_5_reserved6 = field_5_reserved6;
-    }
-
-    /**
-     * Get the text length field for the TextObjectBase record.
-     */
-    public short getTextLength()
-    {
-        return field_6_textLength;
-    }
-
-    /**
-     * Set the text length field for the TextObjectBase record.
-     */
-    public void setTextLength(short field_6_textLength)
-    {
-        this.field_6_textLength = field_6_textLength;
-    }
-
-    /**
-     * Get the formatting run length field for the TextObjectBase record.
-     */
-    public short getFormattingRunLength()
-    {
-        return field_7_formattingRunLength;
-    }
-
-    /**
-     * Set the formatting run length field for the TextObjectBase record.
-     */
-    public void setFormattingRunLength(short field_7_formattingRunLength)
-    {
-        this.field_7_formattingRunLength = field_7_formattingRunLength;
-    }
-
-    /**
-     * Get the reserved7 field for the TextObjectBase record.
-     */
-    public int getReserved7()
-    {
-        return field_8_reserved7;
-    }
-
-    /**
-     * Set the reserved7 field for the TextObjectBase record.
-     */
-    public void setReserved7(int field_8_reserved7)
-    {
-        this.field_8_reserved7 = field_8_reserved7;
-    }
-
-    /**
-     * Sets the reserved1 field value.
-     * reserved field
-     */
-    public void setReserved1(boolean value)
-    {
-        field_1_options = reserved1.setShortBoolean(field_1_options, value);
-    }
-
-    /**
-     * reserved field
-     * @return  the reserved1 field value.
-     */
-    public boolean isReserved1()
-    {
-        return reserved1.isSet(field_1_options);
-    }
-
-    /**
-     * Sets the Horizontal text alignment field value.
-     * 
-     */
-    public void setHorizontalTextAlignment(short value)
-    {
-        field_1_options = HorizontalTextAlignment.setShortValue(field_1_options, value);
-    }
-
-    /**
-     * 
-     * @return  the Horizontal text alignment field value.
-     */
-    public short getHorizontalTextAlignment()
-    {
-        return HorizontalTextAlignment.getShortValue(field_1_options);
-    }
-
-    /**
-     * Sets the Vertical text alignment field value.
-     * 
-     */
-    public void setVerticalTextAlignment(short value)
-    {
-        field_1_options = VerticalTextAlignment.setShortValue(field_1_options, value);
-    }
-
-    /**
-     * 
-     * @return  the Vertical text alignment field value.
-     */
-    public short getVerticalTextAlignment()
-    {
-        return VerticalTextAlignment.getShortValue(field_1_options);
-    }
-
-    /**
-     * Sets the reserved2 field value.
-     * 
-     */
-    public void setReserved2(short value)
-    {
-        field_1_options = reserved2.setShortValue(field_1_options, value);
-    }
-
-    /**
-     * 
-     * @return  the reserved2 field value.
-     */
-    public short getReserved2()
-    {
-        return reserved2.getShortValue(field_1_options);
-    }
-
-    /**
-     * Sets the text locked field value.
-     * Text has been locked
-     */
-    public void setTextLocked(boolean value)
-    {
-        field_1_options = textLocked.setShortBoolean(field_1_options, value);
-    }
-
-    /**
-     * Text has been locked
-     * @return  the text locked field value.
-     */
-    public boolean isTextLocked()
-    {
-        return textLocked.isSet(field_1_options);
-    }
-
-    /**
-     * Sets the reserved3 field value.
-     * 
-     */
-    public void setReserved3(short value)
-    {
-        field_1_options = reserved3.setShortValue(field_1_options, value);
-    }
-
-    /**
-     * 
-     * @return  the reserved3 field value.
-     */
-    public short getReserved3()
-    {
-        return reserved3.getShortValue(field_1_options);
-    }
-}
index 600aaf1542c6d63e74c20aa85ea4beef28d8e12e..6529dc93f8664902f403558b6d4b0a2956f9cc99 100644 (file)
@@ -19,253 +19,427 @@ package org.apache.poi.hssf.record;
 
 import java.io.UnsupportedEncodingException;
 
+import org.apache.poi.hssf.record.formula.Ptg;
 import org.apache.poi.hssf.usermodel.HSSFRichTextString;
+import org.apache.poi.util.BitField;
+import org.apache.poi.util.BitFieldFactory;
 import org.apache.poi.util.HexDump;
 import org.apache.poi.util.LittleEndian;
 
-public class TextObjectRecord
-        extends TextObjectBaseRecord
-{
-    HSSFRichTextString str;
-
-    public TextObjectRecord()
-    {
-    }
-
-    public TextObjectRecord( RecordInputStream in )
-    {
-        super( in );
-
-        if (getTextLength() > 0) {
-        if (in.isContinueNext() && in.remaining() == 0) {
-            //1st Continue
-            in.nextRecord();
-            processRawString(in);
-        } else
-            throw new RecordFormatException("Expected Continue record to hold string data for TextObjectRecord");
-        }
-        if (getFormattingRunLength() > 0) {
-            if (in.isContinueNext() && in.remaining() == 0) {
-                in.nextRecord();
-                processFontRuns(in);
-            } else throw new RecordFormatException("Expected Continue Record to hold font runs for TextObjectRecord");
-        }
-        if (str == null)
-               str = new HSSFRichTextString("");
-    }
-
-
-    public int getRecordSize()
-    {
-        int continue1Size = 0;
-        int continue2Size = 0;
-        if (str.length() != 0)
-        {
-            int length = str.length() * 2;
-            while(length > 0){
-                int chunkSize = Math.min(RecordInputStream.MAX_RECORD_DATA_SIZE-2, length);
-                length -= chunkSize;
-
-                continue1Size += chunkSize;
-                continue1Size += 1 + 4;
-            }
-
-            continue2Size = (str.numFormattingRuns() + 1) * 8 + 4;
-        }
-        return super.getRecordSize() + continue1Size + continue2Size;
-    }
-
-
-
-    public int serialize( int offset, byte[] data )
-    {
-        // Temporarily blank out str so that record size is calculated without the continue records.
-        HSSFRichTextString temp = str;
-        str = new HSSFRichTextString("");
-        int bytesWritten1 = super.serialize( offset, data );
-        str = temp;
-
-        int pos = offset + bytesWritten1;
-        if ( str.getString().equals( "" ) == false )
-        {
-            ContinueRecord c2 = createContinue2();
-            int bytesWritten2 = 0;
-
-            try
-            {
-                byte[] c1Data = str.getString().getBytes( "UTF-16LE" );
-                int length = c1Data.length;
-
-                int charsWritten = 0;
-                int spos = pos;
-                while(length > 0){
-                    int chunkSize = Math.min(RecordInputStream.MAX_RECORD_DATA_SIZE-2 , length);
-                    length -= chunkSize;
-
-                    //continue header
-                    LittleEndian.putShort(data, spos, ContinueRecord.sid);
-                    spos += LittleEndian.SHORT_SIZE;
-                    LittleEndian.putShort(data, spos, (short)(chunkSize+1));
-                    spos += LittleEndian.SHORT_SIZE;
-
-                    //The first byte specifies if the text is compressed unicode or unicode.
-                    //(regardless what was read, we always serialize double-byte unicode characters (UTF-16LE).
-                    data[spos] = 1;
-                    spos += LittleEndian.BYTE_SIZE;
-
-                    //copy characters data
-                    System.arraycopy(c1Data, charsWritten, data, spos, chunkSize);
-                    spos += chunkSize;
-                    charsWritten += chunkSize;
-                }
-
-                bytesWritten2 = (spos-pos);
-            }
-            catch ( UnsupportedEncodingException e )
-            {
-                throw new RuntimeException( e.getMessage(), e );
-            }
-
-            pos += bytesWritten2;
-            int bytesWritten3 = c2.serialize( pos, data );
-            pos += bytesWritten3;
-
-            int size = bytesWritten1 + bytesWritten2 + bytesWritten3;
-            if ( size != getRecordSize() )
-                throw new RecordFormatException(size + " bytes written but getRecordSize() reports " + getRecordSize());
-            return size;
-        }
-        if ( bytesWritten1 != getRecordSize() )
-            throw new RecordFormatException(bytesWritten1 + " bytes written but getRecordSize() reports " + getRecordSize());
-        return bytesWritten1;
-    }
-
-    private ContinueRecord createContinue2()
-    {
-        ContinueRecord c2 = new ContinueRecord();
-        byte[] c2Data = new byte[str.numFormattingRuns() * 8 + 8];
-        int pos = 0;
-        for ( int i = 0; i < str.numFormattingRuns(); i++ )
-        {
-            LittleEndian.putShort( c2Data, pos, (short) str.getIndexOfFormattingRun( i ) );
-            pos += 2;
-            LittleEndian.putShort( c2Data, pos, str.getFontOfFormattingRun( i ) == str.NO_FONT ? 0 : str.getFontOfFormattingRun( i ) );
-            pos += 2;
-            pos += 4;  // skip reserved
-        }
-        LittleEndian.putShort( c2Data, pos, (short) str.length() );
-        pos += 2;
-        LittleEndian.putShort( c2Data, pos, (short) 0 );
-        pos += 2;
-        pos += 4;  // skip reserved
-
-        c2.setData( c2Data );
-
-        return c2;
-    }
-
-    private void processFontRuns( RecordInputStream in )
-    {
-        while (in.remaining() > 0)
-        {
-            short index = in.readShort();
-            short iFont = in.readShort();
-            in.readInt();  // skip reserved.
-
-            str.applyFont( index, str.length(), iFont );
-        }
-    }
-
-    private void processRawString( RecordInputStream in )
-    {
-        String s;
-        byte compressByte = in.readByte();
-        boolean isCompressed = compressByte == 0;
-            if ( isCompressed )
-            {
-            s = in.readCompressedUnicode(getTextLength());
-            }
-            else
-            {
-            s = in.readUnicodeLEString(getTextLength());
-        }
-        str = new HSSFRichTextString( s );
-    }
-
-    public HSSFRichTextString getStr()
-    {
-        return str;
-    }
-
-    public void setStr( HSSFRichTextString str )
-    {
-        this.str = str;
-    }
-
-    public String toString()
-    {
-        StringBuffer buffer = new StringBuffer();
-
-        buffer.append( "[TXO]\n" );
-        buffer.append( "    .options              = " )
-                .append( "0x" ).append( HexDump.toHex( getOptions() ) )
-                .append( " (" ).append( getOptions() ).append( " )" );
-        buffer.append( System.getProperty( "line.separator" ) );
-        buffer.append( "         .reserved1                = " ).append( isReserved1() ).append( '\n' );
-        buffer.append( "         .HorizontalTextAlignment     = " ).append( getHorizontalTextAlignment() ).append( '\n' );
-        buffer.append( "         .VerticalTextAlignment     = " ).append( getVerticalTextAlignment() ).append( '\n' );
-        buffer.append( "         .reserved2                = " ).append( getReserved2() ).append( '\n' );
-        buffer.append( "         .textLocked               = " ).append( isTextLocked() ).append( '\n' );
-        buffer.append( "         .reserved3                = " ).append( getReserved3() ).append( '\n' );
-        buffer.append( "    .textOrientation      = " )
-                .append( "0x" ).append( HexDump.toHex( getTextOrientation() ) )
-                .append( " (" ).append( getTextOrientation() ).append( " )" );
-        buffer.append( System.getProperty( "line.separator" ) );
-        buffer.append( "    .reserved4            = " )
-                .append( "0x" ).append( HexDump.toHex( getReserved4() ) )
-                .append( " (" ).append( getReserved4() ).append( " )" );
-        buffer.append( System.getProperty( "line.separator" ) );
-        buffer.append( "    .reserved5            = " )
-                .append( "0x" ).append( HexDump.toHex( getReserved5() ) )
-                .append( " (" ).append( getReserved5() ).append( " )" );
-        buffer.append( System.getProperty( "line.separator" ) );
-        buffer.append( "    .reserved6            = " )
-                .append( "0x" ).append( HexDump.toHex( getReserved6() ) )
-                .append( " (" ).append( getReserved6() ).append( " )" );
-        buffer.append( System.getProperty( "line.separator" ) );
-        buffer.append( "    .textLength           = " )
-                .append( "0x" ).append( HexDump.toHex( getTextLength() ) )
-                .append( " (" ).append( getTextLength() ).append( " )" );
-        buffer.append( System.getProperty( "line.separator" ) );
-        buffer.append( "    .reserved7            = " )
-                .append( "0x" ).append( HexDump.toHex( getReserved7() ) )
-                .append( " (" ).append( getReserved7() ).append( " )" );
-        buffer.append( System.getProperty( "line.separator" ) );
-
-        buffer.append( "    .string = " ).append(str).append('\n');
-
-        for (int i = 0; i < str.numFormattingRuns(); i++) {
-            buffer.append( "    .textrun = " ).append(str.getFontOfFormattingRun(i)).append('\n');
-
-        }
-        buffer.append( "[/TXO]\n" );
-        return buffer.toString();
-    }
-
-    public Object clone() {
-
-        TextObjectRecord rec = new TextObjectRecord();
-        rec.str = str;
-
-        rec.setOptions(getOptions());
-        rec.setTextOrientation(getTextOrientation());
-        rec.setReserved4(getReserved4());
-        rec.setReserved5(getReserved5());
-        rec.setReserved6(getReserved6());
-        rec.setTextLength(getTextLength());
-        rec.setFormattingRunLength(getFormattingRunLength());
-        rec.setReserved7(getReserved7());
-        return rec;
-    }
-
+/**
+ * The TXO record (0x01B6) is used to define the properties of a text box. It is
+ * followed by two or more continue records unless there is no actual text. The
+ * first continue records contain the text data and the last continue record
+ * contains the formatting runs.<p/>
+ * 
+ * @author Glen Stampoultzis (glens at apache.org)
+ */
+public final class TextObjectRecord extends Record {
+       public final static short sid = 0x01B6;
+
+       private static final int FORMAT_RUN_ENCODED_SIZE = 8; // 2 shorts and 4 bytes reserved
+
+       private static final BitField HorizontalTextAlignment = BitFieldFactory.getInstance(0x000E);
+       private static final BitField VerticalTextAlignment = BitFieldFactory.getInstance(0x0070);
+       private static final BitField textLocked = BitFieldFactory.getInstance(0x0200);
+
+       public final static short HORIZONTAL_TEXT_ALIGNMENT_LEFT_ALIGNED = 1;
+       public final static short HORIZONTAL_TEXT_ALIGNMENT_CENTERED = 2;
+       public final static short HORIZONTAL_TEXT_ALIGNMENT_RIGHT_ALIGNED = 3;
+       public final static short HORIZONTAL_TEXT_ALIGNMENT_JUSTIFIED = 4;
+       public final static short VERTICAL_TEXT_ALIGNMENT_TOP = 1;
+       public final static short VERTICAL_TEXT_ALIGNMENT_CENTER = 2;
+       public final static short VERTICAL_TEXT_ALIGNMENT_BOTTOM = 3;
+       public final static short VERTICAL_TEXT_ALIGNMENT_JUSTIFY = 4;
+
+       public final static short TEXT_ORIENTATION_NONE = 0;
+       public final static short TEXT_ORIENTATION_TOP_TO_BOTTOM = 1;
+       public final static short TEXT_ORIENTATION_ROT_RIGHT = 2;
+       public final static short TEXT_ORIENTATION_ROT_LEFT = 3;
+
+       private int field_1_options;
+       private int field_2_textOrientation;
+       private int field_3_reserved4;
+       private int field_4_reserved5;
+       private int field_5_reserved6;
+       private int field_8_reserved7;
+
+       private HSSFRichTextString _text;
+
+       /*
+        * Note - the next three fields are very similar to those on
+        * EmbededObjectRefSubRecord(ftPictFmla 0x0009)
+        * 
+        * some observed values for the 4 bytes preceding the formula: C0 5E 86 03
+        * C0 11 AC 02 80 F1 8A 03 D4 F0 8A 03
+        */
+       private int _unknownPreFormulaInt;
+       /** expect tRef, tRef3D, tArea, tArea3D or tName */
+       private Ptg _linkRefPtg;
+       /**
+        * Not clear if needed .  Excel seems to be OK if this byte is not present. 
+        * Value is often the same as the earlier firstColumn byte. */
+       private Byte _unknownPostFormulaByte;
+
+       public TextObjectRecord() {
+       }
+
+       public TextObjectRecord(RecordInputStream in) {
+               field_1_options = in.readUShort();
+               field_2_textOrientation = in.readUShort();
+               field_3_reserved4 = in.readUShort();
+               field_4_reserved5 = in.readUShort();
+               field_5_reserved6 = in.readUShort();
+               int field_6_textLength = in.readUShort();
+               int field_7_formattingDataLength = in.readUShort();
+               field_8_reserved7 = in.readInt();
+
+               if (in.remaining() > 0) {
+                       // Text Objects can have simple reference formulas
+                       // (This bit not mentioned in the MS document)
+                       if (in.remaining() < 11) {
+                               throw new RecordFormatException("Not enough remaining data for a link formula");
+                       }
+                       int formulaSize = in.readUShort();
+                       _unknownPreFormulaInt = in.readInt();
+                       Ptg[] ptgs = Ptg.readTokens(formulaSize, in);
+                       if (ptgs.length != 1) {
+                               throw new RecordFormatException("Read " + ptgs.length
+                                               + " tokens but expected exactly 1");
+                       }
+                       _linkRefPtg = ptgs[0];
+                       if (in.remaining() > 0) {
+                               _unknownPostFormulaByte = new Byte(in.readByte());
+                       } else {
+                               _unknownPostFormulaByte = null;
+                       }
+               } else {
+                       _linkRefPtg = null;
+               }
+               if (in.remaining() > 0) {
+                       throw new RecordFormatException("Unused " + in.remaining() + " bytes at end of record");
+               }
+
+               String text;
+               if (field_6_textLength > 0) {
+                       text = readRawString(in, field_6_textLength);
+               } else {
+                       text = "";
+               }
+               _text = new HSSFRichTextString(text);
+
+               if (field_7_formattingDataLength > 0) {
+                       if (in.isContinueNext() && in.remaining() == 0) {
+                               in.nextRecord();
+                               processFontRuns(in, _text, field_7_formattingDataLength);
+                       } else {
+                               throw new RecordFormatException(
+                                               "Expected Continue Record to hold font runs for TextObjectRecord");
+                       }
+               }
+       }
+
+       private static String readRawString(RecordInputStream in, int textLength) {
+               byte compressByte = in.readByte();
+               boolean isCompressed = (compressByte & 0x01) == 0;
+               if (isCompressed) {
+                       return in.readCompressedUnicode(textLength);
+               }
+               return in.readUnicodeLEString(textLength);
+       }
+
+       private static void processFontRuns(RecordInputStream in, HSSFRichTextString str,
+                       int formattingRunDataLength) {
+               if (formattingRunDataLength % FORMAT_RUN_ENCODED_SIZE != 0) {
+                       throw new RecordFormatException("Bad format run data length " + formattingRunDataLength
+                                       + ")");
+               }
+               if (in.remaining() != formattingRunDataLength) {
+                       throw new RecordFormatException("Expected " + formattingRunDataLength
+                                       + " bytes but got " + in.remaining());
+               }
+               int nRuns = formattingRunDataLength / FORMAT_RUN_ENCODED_SIZE;
+               for (int i = 0; i < nRuns; i++) {
+                       short index = in.readShort();
+                       short iFont = in.readShort();
+                       in.readInt(); // skip reserved.
+                       str.applyFont(index, str.length(), iFont);
+               }
+       }
+
+       public short getSid() {
+               return sid;
+       }
+
+       /**
+        * Only for the current record. does not include any subsequent Continue
+        * records
+        */
+       private int getDataSize() {
+               int result = 2 + 2 + 2 + 2 + 2 + 2 + 2 + 4;
+               if (_linkRefPtg != null) {
+                       result += 2 // formula size
+                               + 4  // unknownInt
+                               +_linkRefPtg.getSize();
+                       if (_unknownPostFormulaByte != null) {
+                               result += 1;
+                       }
+               }
+               return result;
+       }
+
+       private int serializeTXORecord(int offset, byte[] data) {
+               int dataSize = getDataSize();
+               
+               LittleEndian.putUShort(data, 0 + offset, TextObjectRecord.sid);
+               LittleEndian.putUShort(data, 2 + offset, dataSize);
+
+               
+               LittleEndian.putUShort(data, 4 + offset, field_1_options);
+               LittleEndian.putUShort(data, 6 + offset, field_2_textOrientation);
+               LittleEndian.putUShort(data, 8 + offset, field_3_reserved4);
+               LittleEndian.putUShort(data, 10 + offset, field_4_reserved5);
+               LittleEndian.putUShort(data, 12 + offset, field_5_reserved6);
+               LittleEndian.putUShort(data, 14 + offset, _text.length());
+               LittleEndian.putUShort(data, 16 + offset, getFormattingDataLength());
+               LittleEndian.putInt(data, 18 + offset, field_8_reserved7);
+               
+               if (_linkRefPtg != null) {
+                       int pos = offset+22;
+                       int formulaSize = _linkRefPtg.getSize();
+                       LittleEndian.putUShort(data, pos, formulaSize);
+                       pos += LittleEndian.SHORT_SIZE;
+                       LittleEndian.putInt(data, pos, _unknownPreFormulaInt);
+                       pos += LittleEndian.INT_SIZE;
+                       _linkRefPtg.writeBytes(data, pos);
+                       pos += formulaSize;
+                       if (_unknownPostFormulaByte != null) {
+                               LittleEndian.putByte(data, pos, _unknownPostFormulaByte.byteValue());
+                               pos += LittleEndian.BYTE_SIZE;
+                       }
+               }
+               
+               return 4 + dataSize;
+       }
+
+       private int serializeTrailingRecords(int offset, byte[] data) {
+               byte[] textBytes;
+               try {
+                       textBytes = _text.getString().getBytes("UTF-16LE");
+               } catch (UnsupportedEncodingException e) {
+                       throw new RuntimeException(e.getMessage(), e);
+               }
+               int remainingLength = textBytes.length;
+
+               int countTextBytesWritten = 0;
+               int pos = offset;
+               // (regardless what was read, we always serialize double-byte
+               // unicode characters (UTF-16LE).
+               Byte unicodeFlag = new Byte((byte)1);
+               while (remainingLength > 0) {
+                       int chunkSize = Math.min(RecordInputStream.MAX_RECORD_DATA_SIZE - 2, remainingLength);
+                       remainingLength -= chunkSize;
+                       pos += ContinueRecord.write(data, pos, unicodeFlag, textBytes, countTextBytesWritten, chunkSize);
+                       countTextBytesWritten += chunkSize;
+               }
+
+               byte[] formatData = createFormatData(_text);
+               pos += ContinueRecord.write(data, pos, null, formatData);
+               return pos - offset;
+       }
+
+       private int getTrailingRecordsSize() {
+               if (_text.length() < 1) {
+                       return 0;
+               }
+               int encodedTextSize = 0;
+               int textBytesLength = _text.length() * LittleEndian.SHORT_SIZE;
+               while (textBytesLength > 0) {
+                       int chunkSize = Math.min(RecordInputStream.MAX_RECORD_DATA_SIZE - 2, textBytesLength);
+                       textBytesLength -= chunkSize;
+
+                       encodedTextSize += 4;           // +4 for ContinueRecord sid+size
+                       encodedTextSize += 1+chunkSize; // +1 for compressed unicode flag, 
+               }
+
+               int encodedFormatSize = (_text.numFormattingRuns() + 1) * FORMAT_RUN_ENCODED_SIZE
+                       + 4;  // +4 for ContinueRecord sid+size
+               return encodedTextSize + encodedFormatSize;
+       }
+
+
+       public int serialize(int offset, byte[] data) {
+
+               int expectedTotalSize = getRecordSize();
+               int totalSize = serializeTXORecord(offset, data);
+               
+               if (_text.getString().length() > 0) {
+                       totalSize += serializeTrailingRecords(offset+totalSize, data);
+               } 
+               
+               if (totalSize != expectedTotalSize)
+                       throw new RecordFormatException(totalSize
+                                       + " bytes written but getRecordSize() reports " + expectedTotalSize);
+               return totalSize;
+       }
+
+       /**
+        * Note - this total size includes all potential {@link ContinueRecord}s written
+        */
+       public int getRecordSize() {
+               int baseSize = 4 + getDataSize();
+               return baseSize + getTrailingRecordsSize();
+       }
+
+       
+       private int getFormattingDataLength() {
+               if (_text.length() < 1) {
+                       // important - no formatting data if text is empty 
+                       return 0;
+               }
+               return (_text.numFormattingRuns() + 1) * FORMAT_RUN_ENCODED_SIZE;
+       }
+
+       private static byte[] createFormatData(HSSFRichTextString str) {
+               int nRuns = str.numFormattingRuns();
+               byte[] result = new byte[(nRuns + 1) * FORMAT_RUN_ENCODED_SIZE];
+               int pos = 0;
+               for (int i = 0; i < nRuns; i++) {
+                       LittleEndian.putUShort(result, pos, str.getIndexOfFormattingRun(i));
+                       pos += 2;
+                       int fontIndex = str.getFontOfFormattingRun(i);
+                       LittleEndian.putUShort(result, pos, fontIndex == str.NO_FONT ? 0 : fontIndex);
+                       pos += 2;
+                       pos += 4; // skip reserved
+               }
+               LittleEndian.putUShort(result, pos, str.length());
+               pos += 2;
+               LittleEndian.putUShort(result, pos, 0);
+               pos += 2;
+               pos += 4; // skip reserved
+
+               return result;
+       }
+
+       /**
+        * Sets the Horizontal text alignment field value.
+        */
+       public void setHorizontalTextAlignment(int value) {
+               field_1_options = HorizontalTextAlignment.setValue(field_1_options, value);
+       }
+
+       /**
+        * @return the Horizontal text alignment field value.
+        */
+       public int getHorizontalTextAlignment() {
+               return HorizontalTextAlignment.getValue(field_1_options);
+       }
+
+       /**
+        * Sets the Vertical text alignment field value.
+        */
+       public void setVerticalTextAlignment(int value) {
+               field_1_options = VerticalTextAlignment.setValue(field_1_options, value);
+       }
+
+       /**
+        * @return the Vertical text alignment field value.
+        */
+       public int getVerticalTextAlignment() {
+               return VerticalTextAlignment.getValue(field_1_options);
+       }
+
+       /**
+        * Sets the text locked field value.
+        */
+       public void setTextLocked(boolean value) {
+               field_1_options = textLocked.setBoolean(field_1_options, value);
+       }
+
+       /**
+        * @return the text locked field value.
+        */
+       public boolean isTextLocked() {
+               return textLocked.isSet(field_1_options);
+       }
+
+       /**
+        * Get the text orientation field for the TextObjectBase record.
+        * 
+        * @return One of TEXT_ORIENTATION_NONE TEXT_ORIENTATION_TOP_TO_BOTTOM
+        *         TEXT_ORIENTATION_ROT_RIGHT TEXT_ORIENTATION_ROT_LEFT
+        */
+       public int getTextOrientation() {
+               return field_2_textOrientation;
+       }
+
+       /**
+        * Set the text orientation field for the TextObjectBase record.
+        * 
+        * @param textOrientation
+        *            One of TEXT_ORIENTATION_NONE TEXT_ORIENTATION_TOP_TO_BOTTOM
+        *            TEXT_ORIENTATION_ROT_RIGHT TEXT_ORIENTATION_ROT_LEFT
+        */
+       public void setTextOrientation(int textOrientation) {
+               this.field_2_textOrientation = textOrientation;
+       }
+
+       public HSSFRichTextString getStr() {
+               return _text;
+       }
+
+       public void setStr(HSSFRichTextString str) {
+               _text = str;
+       }
+       
+       public Ptg getLinkRefPtg() {
+               return _linkRefPtg;
+       }
+
+       public String toString() {
+               StringBuffer sb = new StringBuffer();
+
+               sb.append("[TXO]\n");
+               sb.append("    .options        = ").append(HexDump.shortToHex(field_1_options)).append("\n");
+               sb.append("         .isHorizontal = ").append(getHorizontalTextAlignment()).append('\n');
+               sb.append("         .isVertical   = ").append(getVerticalTextAlignment()).append('\n');
+               sb.append("         .textLocked   = ").append(isTextLocked()).append('\n');
+               sb.append("    .textOrientation= ").append(HexDump.shortToHex(getTextOrientation())).append("\n");
+               sb.append("    .reserved4      = ").append(HexDump.shortToHex(field_3_reserved4)).append("\n");
+               sb.append("    .reserved5      = ").append(HexDump.shortToHex(field_4_reserved5)).append("\n");
+               sb.append("    .reserved6      = ").append(HexDump.shortToHex(field_5_reserved6)).append("\n");
+               sb.append("    .textLength     = ").append(HexDump.shortToHex(_text.length())).append("\n");
+               sb.append("    .reserved7      = ").append(HexDump.intToHex(field_8_reserved7)).append("\n");
+
+               sb.append("    .string = ").append(_text).append('\n');
+
+               for (int i = 0; i < _text.numFormattingRuns(); i++) {
+                       sb.append("    .textrun = ").append(_text.getFontOfFormattingRun(i)).append('\n');
+
+               }
+               sb.append("[/TXO]\n");
+               return sb.toString();
+       }
+
+       public Object clone() {
+
+               TextObjectRecord rec = new TextObjectRecord();
+               rec._text = _text;
+
+               rec.field_1_options = field_1_options;
+               rec.field_2_textOrientation = field_2_textOrientation;
+               rec.field_3_reserved4 = field_3_reserved4;
+               rec.field_4_reserved5 = field_4_reserved5;
+               rec.field_5_reserved6 = field_5_reserved6;
+               rec.field_8_reserved7 = field_8_reserved7;
+
+               rec._text = _text; // clone needed?
+
+               if (_linkRefPtg != null) {
+                       rec._unknownPreFormulaInt = _unknownPreFormulaInt;
+                       rec._linkRefPtg = _linkRefPtg.copy();
+                       rec._unknownPostFormulaByte = rec._unknownPostFormulaByte;
+               }
+               return rec;
+       }
 }
index f47286879d23b6aebcb829382902e5b1e10f41ae..7018388a45f08c5b3d0f0dd41ee32ee1c1b8a15d 100644 (file)
@@ -152,10 +152,7 @@ public class HSSFComment extends HSSFTextbox {
         if (string.numFormattingRuns() == 0) string.applyFont((short)0);
 
         if (txo != null) {
-            int frLength = ( string.numFormattingRuns() + 1 ) * 8;
-            txo.setFormattingRunLength( (short) frLength );
-            txo.setTextLength( (short) string.length() );
-            txo.setStr( string );
+            txo.setStr(string);
         }
         super.setString(string);
     }
index 1b7342be614cb2830e277654d02c3fe43d286ce7..6ddacf69763f140372795f9931d9466ed6ab6ef0 100644 (file)
@@ -194,7 +194,7 @@ public class HSSFRichTextString
     
 
     /**
-     * @return  the number of characters in the font.
+     * @return  the number of characters in the text.
      */
     public int length()
     {
index b9d80dbad50fdf10c67b7aca345e4510f87ce4f6..b8f9e86468169cc47b7ef4b53e5b2dba05dcbab5 100644 (file)
 package org.apache.poi.hssf.record;
 
 
+import java.io.ByteArrayInputStream;
+
+import org.apache.poi.hssf.usermodel.HSSFRichTextString;
+import org.apache.poi.util.HexRead;
+
 import junit.framework.TestCase;
 
 /**
@@ -25,63 +30,62 @@ import junit.framework.TestCase;
  * class works correctly.  Test data taken directly from a real
  * Excel file.
  *
-
  * @author Glen Stampoultzis (glens at apache.org)
  */
-public class TestTextObjectBaseRecord extends TestCase {
-    byte[] data = new byte[] {
-           0x44, 0x02, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00,
-           0x00, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, 0x00,
-        0x00, 0x00,
-    };
+public final class TestTextObjectBaseRecord extends TestCase {
+       /** data for one TXO rec and two continue recs */
+    private static final byte[] data = HexRead.readFromString(
+        "B6 01 " + // TextObjectRecord.sid
+        "12 00 " + // size 18
+        "44 02 02 00 00 00 00 00" +
+        "00 00 " +
+        "02 00 " + // strLen 2
+        "10 00 " + // 16 bytes for 2 format runs
+        "00 00" +
+        "00 00 " +
+        "3C 00 " + // ContinueRecord.sid
+        "05 00 " + // size 5
+        "01 " + // unicode uncompressed
+        "41 00 42 00 " + // 'AB'
+        "3C 00 " + // ContinueRecord.sid
+        "10 00 " + // size 16 
+        "00 00 18 00 00 00 00 00 " +
+        "02 00 00 00 00 00 00 00 " 
+    );
+    
 
     public void testLoad() {
-        TextObjectBaseRecord record = new TextObjectBaseRecord(new TestcaseRecordInputStream((short)0x1B6, (short)data.length, data));
-
-
-//        assertEquals( (short), record.getOptions());
-        assertEquals( false, record.isReserved1() );
-        assertEquals( TextObjectBaseRecord.HORIZONTAL_TEXT_ALIGNMENT_CENTERED, record.getHorizontalTextAlignment() );
-        assertEquals( TextObjectBaseRecord.VERTICAL_TEXT_ALIGNMENT_JUSTIFY, record.getVerticalTextAlignment() );
-        assertEquals( 0, record.getReserved2() );
-        assertEquals( true, record.isTextLocked() );
-        assertEquals( 0, record.getReserved3() );
-        assertEquals( TextObjectBaseRecord.TEXT_ORIENTATION_ROT_RIGHT, record.getTextOrientation());
-        assertEquals( 0, record.getReserved4());
-        assertEquals( 0, record.getReserved5());
-        assertEquals( 0, record.getReserved6());
-        assertEquals( 2, record.getTextLength());
-        assertEquals( 2, record.getFormattingRunLength());
-        assertEquals( 0, record.getReserved7());
-
-
-        assertEquals( 22, record.getRecordSize() );
+        RecordInputStream in = new RecordInputStream(new ByteArrayInputStream(data));
+        in.nextRecord();
+        TextObjectRecord record = new TextObjectRecord(in);
+
+
+        assertEquals(TextObjectRecord.HORIZONTAL_TEXT_ALIGNMENT_CENTERED, record.getHorizontalTextAlignment());
+        assertEquals(TextObjectRecord.VERTICAL_TEXT_ALIGNMENT_JUSTIFY, record.getVerticalTextAlignment());
+        assertEquals(true, record.isTextLocked());
+        assertEquals(TextObjectRecord.TEXT_ORIENTATION_ROT_RIGHT, record.getTextOrientation());
+
+        assertEquals(51, record.getRecordSize() );
     }
 
     public void testStore()
     {
-        TextObjectBaseRecord record = new TextObjectBaseRecord();
+        TextObjectRecord record = new TextObjectRecord();
 
 
+        HSSFRichTextString str = new HSSFRichTextString("AB");
+        str.applyFont(0, 2, (short)0x0018);
+        str.applyFont(2, 2, (short)0x0320);
 
-//        record.setOptions( (short) 0x0000);
-        record.setReserved1( false );
-        record.setHorizontalTextAlignment( TextObjectBaseRecord.HORIZONTAL_TEXT_ALIGNMENT_CENTERED );
-        record.setVerticalTextAlignment( TextObjectBaseRecord.VERTICAL_TEXT_ALIGNMENT_JUSTIFY );
-        record.setReserved2( (short)0 );
-        record.setTextLocked( true );
-        record.setReserved3( (short)0 );
-        record.setTextOrientation( TextObjectBaseRecord.TEXT_ORIENTATION_ROT_RIGHT );
-        record.setReserved4( (short)0 );
-        record.setReserved5( (short)0 );
-        record.setReserved6( (short)0 );
-        record.setTextLength( (short)2 );
-        record.setFormattingRunLength( (short)2 );
-        record.setReserved7( 0 );
+        record.setHorizontalTextAlignment(TextObjectRecord.HORIZONTAL_TEXT_ALIGNMENT_CENTERED);
+        record.setVerticalTextAlignment(TextObjectRecord.VERTICAL_TEXT_ALIGNMENT_JUSTIFY);
+        record.setTextLocked(true);
+        record.setTextOrientation(TextObjectRecord.TEXT_ORIENTATION_ROT_RIGHT);
+        record.setStr(str);
 
         byte [] recordBytes = record.serialize();
-        assertEquals(recordBytes.length - 4, data.length);
+        assertEquals(recordBytes.length, data.length);
         for (int i = 0; i < data.length; i++)
-            assertEquals("At offset " + i, data[i], recordBytes[i+4]);
+            assertEquals("At offset " + i, data[i], recordBytes[i]);
     }
 }
index 91458e9ddb720426c16c04063590d5468cd6f314..b7d45e28b0ef6df0ec798e4370495c4365ffc991 100644 (file)
@@ -22,7 +22,11 @@ import java.util.Arrays;
 \r
 import junit.framework.TestCase;\r
 \r
+import org.apache.poi.hssf.record.formula.Ptg;\r
+import org.apache.poi.hssf.record.formula.RefPtg;\r
 import org.apache.poi.hssf.usermodel.HSSFRichTextString;\r
+import org.apache.poi.util.HexRead;\r
+import org.apache.poi.util.LittleEndian;\r
 \r
 /**\r
  * Tests that serialization and deserialization of the TextObjectRecord .\r
@@ -32,17 +36,23 @@ import org.apache.poi.hssf.usermodel.HSSFRichTextString;
  */\r
 public final class TestTextObjectRecord extends TestCase {\r
 \r
-    byte[] data = {(byte)0xB6, 0x01, 0x12, 0x00, 0x12, 0x02, 0x00, 0x00, 0x00, 0x00,\r
-                   0x00, 0x00, 0x00, 0x00, 0x0D, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00,\r
-                   0x00, 0x3C, 0x00, 0x1B, 0x00, 0x01, 0x48, 0x00, 0x65, 0x00, 0x6C,\r
-                   0x00, 0x6C, 0x00, 0x6F, 0x00, 0x2C, 0x00, 0x20, 0x00, 0x57, 0x00,\r
-                   0x6F, 0x00, 0x72, 0x00, 0x6C, 0x00, 0x64, 0x00, 0x21, 0x00, 0x3C,\r
-                   0x00, 0x08, 0x00, 0x0D, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };\r
+    private static final byte[] simpleData = HexRead.readFromString(\r
+       "B6 01 12 00 " +\r
+       "12 02 00 00 00 00 00 00" +\r
+       "00 00 0D 00 08 00      00 00" +\r
+       "00 00 " +\r
+       "3C 00 1B 00 " +\r
+       "01 48 00 65 00 6C 00 6C 00 6F 00 " +\r
+       "2C 00 20 00 57 00 6F 00 72 00 6C " +\r
+       "00 64 00 21 00 " + \r
+       "3C 00 08 " +\r
+       "00 0D 00 00 00 00 00 00 00"\r
+    );\r
 \r
 \r
     public void testRead() {\r
 \r
-        RecordInputStream is = new RecordInputStream(new ByteArrayInputStream(data));\r
+        RecordInputStream is = new RecordInputStream(new ByteArrayInputStream(simpleData));\r
         is.nextRecord();\r
         TextObjectRecord record = new TextObjectRecord(is);\r
 \r
@@ -50,36 +60,51 @@ public final class TestTextObjectRecord extends TestCase {
         assertEquals(TextObjectRecord.HORIZONTAL_TEXT_ALIGNMENT_LEFT_ALIGNED, record.getHorizontalTextAlignment());\r
         assertEquals(TextObjectRecord.VERTICAL_TEXT_ALIGNMENT_TOP, record.getVerticalTextAlignment());\r
         assertEquals(TextObjectRecord.TEXT_ORIENTATION_NONE, record.getTextOrientation());\r
-        assertEquals(0, record.getReserved7());\r
         assertEquals("Hello, World!", record.getStr().getString());\r
-\r
     }\r
 \r
-    public void testWrite()\r
-    {\r
+    public void testWrite() {\r
         HSSFRichTextString str = new HSSFRichTextString("Hello, World!");\r
 \r
         TextObjectRecord record = new TextObjectRecord();\r
-        int frLength = ( str.numFormattingRuns() + 1 ) * 8;\r
-        record.setFormattingRunLength( (short) frLength );\r
-        record.setTextLength( (short) str.length() );\r
-        record.setStr( str );\r
+        record.setStr(str);\r
         record.setHorizontalTextAlignment( TextObjectRecord.HORIZONTAL_TEXT_ALIGNMENT_LEFT_ALIGNED );\r
         record.setVerticalTextAlignment( TextObjectRecord.VERTICAL_TEXT_ALIGNMENT_TOP );\r
         record.setTextLocked( true );\r
         record.setTextOrientation( TextObjectRecord.TEXT_ORIENTATION_NONE );\r
-        record.setReserved7( 0 );\r
 \r
         byte [] ser = record.serialize();\r
-        //assertEquals(ser.length , data.length);\r
+        assertEquals(ser.length , simpleData.length);\r
 \r
-        //assertTrue(Arrays.equals(data, ser));\r
+        assertTrue(Arrays.equals(simpleData, ser));\r
 \r
         //read again\r
-        RecordInputStream is = new RecordInputStream(new ByteArrayInputStream(data));\r
+        RecordInputStream is = new RecordInputStream(new ByteArrayInputStream(simpleData));\r
         is.nextRecord();\r
         record = new TextObjectRecord(is);\r
+    }\r
+\r
+    /**\r
+     * Zero {@link ContinueRecord}s follow a {@link TextObjectRecord} if the text is empty\r
+     */\r
+    public void testWriteEmpty() {\r
+        HSSFRichTextString str = new HSSFRichTextString("");\r
+\r
+        TextObjectRecord record = new TextObjectRecord();\r
+        record.setStr(str);\r
 \r
+        byte [] ser = record.serialize();\r
+        \r
+        int formatDataLen = LittleEndian.getUShort(ser, 16);\r
+        assertEquals("formatDataLength", 0, formatDataLen);\r
+\r
+        assertEquals(22, ser.length); // just the TXO record\r
+        \r
+        //read again\r
+        RecordInputStream is = new RecordInputStream(new ByteArrayInputStream(ser));\r
+        is.nextRecord();\r
+        record = new TextObjectRecord(is);\r
+        assertEquals(0, record.getStr().length());\r
     }\r
 \r
     /**\r
@@ -95,10 +120,7 @@ public final class TestTextObjectRecord extends TestCase {
             HSSFRichTextString str = new HSSFRichTextString(buff.toString());\r
 \r
             TextObjectRecord obj = new TextObjectRecord();\r
-            int frLength = ( str.numFormattingRuns() + 1 ) * 8;\r
-            obj.setFormattingRunLength( (short) frLength );\r
-            obj.setTextLength( (short) str.length() );\r
-            obj.setStr( str );\r
+            obj.setStr(str);\r
 \r
             byte [] data = obj.serialize();\r
             RecordInputStream is = new RecordInputStream(new ByteArrayInputStream(data));\r
@@ -120,30 +142,12 @@ public final class TestTextObjectRecord extends TestCase {
         HSSFRichTextString str = new HSSFRichTextString(text);\r
 \r
         TextObjectRecord obj = new TextObjectRecord();\r
-        int frLength = ( str.numFormattingRuns() + 1 ) * 8;\r
-        obj.setFormattingRunLength( (short) frLength );\r
-        obj.setTextLength( (short) str.length() );\r
-        obj.setReserved1(true);\r
-        obj.setReserved2((short)2);\r
-        obj.setReserved3((short)3);\r
-        obj.setReserved4((short)4);\r
-        obj.setReserved5((short)5);\r
-        obj.setReserved6((short)6);\r
-        obj.setReserved7((short)7);\r
         obj.setStr( str );\r
 \r
 \r
         TextObjectRecord cloned = (TextObjectRecord)obj.clone();\r
-        assertEquals(obj.getReserved2(), cloned.getReserved2());\r
-        assertEquals(obj.getReserved3(), cloned.getReserved3());\r
-        assertEquals(obj.getReserved4(), cloned.getReserved4());\r
-        assertEquals(obj.getReserved5(), cloned.getReserved5());\r
-        assertEquals(obj.getReserved6(), cloned.getReserved6());\r
-        assertEquals(obj.getReserved7(), cloned.getReserved7());\r
         assertEquals(obj.getRecordSize(), cloned.getRecordSize());\r
-        assertEquals(obj.getOptions(), cloned.getOptions());\r
         assertEquals(obj.getHorizontalTextAlignment(), cloned.getHorizontalTextAlignment());\r
-        assertEquals(obj.getFormattingRunLength(), cloned.getFormattingRunLength());\r
         assertEquals(obj.getStr().getString(), cloned.getStr().getString());\r
 \r
         //finally check that the serialized data is the same\r
@@ -151,4 +155,47 @@ public final class TestTextObjectRecord extends TestCase {
         byte[] cln = cloned.serialize();\r
         assertTrue(Arrays.equals(src, cln));\r
     }\r
+    \r
+    /** similar to {@link #simpleData} but with link formula at end of TXO rec*/ \r
+    private static final byte[] linkData = HexRead.readFromString(\r
+               "B6 01 " + // TextObjectRecord.sid\r
+               "1E 00 " + // size 18\r
+           "44 02 02 00 00 00 00 00" +\r
+           "00 00 " +\r
+           "02 00 " + // strLen 2\r
+           "10 00 " + // 16 bytes for 2 format runs\r
+           "00 00 00 00 " +\r
+\r
+            "05 00 " +          // formula size\r
+            "D4 F0 8A 03 " +    // unknownInt\r
+            "24 01 00 13 C0 " + //tRef(T2)\r
+            "13 " +             // ??\r
+\r
+               "3C 00 " + // ContinueRecord.sid\r
+           "05 00 " + // size 5\r
+           "01 " + // unicode uncompressed\r
+           "41 00 42 00 " + // 'AB'\r
+               "3C 00 " + // ContinueRecord.sid\r
+           "10 00 " + // size 16 \r
+           "00 00 18 00 00 00 00 00 " +\r
+           "02 00 00 00 00 00 00 00 " \r
+        );\r
+    \r
+    \r
+    public void testLinkFormula() {\r
+        RecordInputStream is = new RecordInputStream(new ByteArrayInputStream(linkData));\r
+        is.nextRecord();\r
+        TextObjectRecord rec = new TextObjectRecord(is);\r
+               \r
+        Ptg ptg = rec.getLinkRefPtg();\r
+        assertNotNull(ptg);\r
+        assertEquals(RefPtg.class, ptg.getClass());\r
+        RefPtg rptg = (RefPtg) ptg;\r
+        assertEquals("T2", rptg.toFormulaString());\r
+\r
+        byte [] data2 = rec.serialize();\r
+        assertEquals(linkData.length, data2.length);\r
+        assertTrue(Arrays.equals(linkData, data2));\r
+       }\r
+    \r
 }\r