diff --git a/src/java/org/apache/poi/ddf/DefaultEscherRecordFactory.java b/src/java/org/apache/poi/ddf/DefaultEscherRecordFactory.java
new file mode 100644
index 000000000..09d8c5f81
--- /dev/null
+++ b/src/java/org/apache/poi/ddf/DefaultEscherRecordFactory.java
@@ -0,0 +1,114 @@
+package org.apache.poi.ddf;
+
+import org.apache.poi.hssf.record.RecordFormatException;
+
+import java.lang.reflect.Constructor;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Generates escher records when provided the byte array containing those records.
+ *
+ * @author Glen Stampoultzis
+ * @see EscherRecordFactory
+ */
+public class DefaultEscherRecordFactory
+ implements EscherRecordFactory
+{
+ private static Class[] escherRecordClasses = {
+ EscherBSERecord.class, EscherOptRecord.class, EscherClientAnchorRecord.class, EscherDgRecord.class,
+ EscherSpgrRecord.class, EscherSpRecord.class, EscherClientDataRecord.class, EscherDggRecord.class,
+ EscherSplitMenuColorsRecord.class, EscherChildAnchorRecord.class, EscherTextboxRecord.class
+ };
+ private static Map recordsMap = recordsToMap( escherRecordClasses );
+
+ /**
+ * Creates an instance of the escher record factory
+ */
+ public DefaultEscherRecordFactory()
+ {
+ }
+
+ /**
+ * Generates an escher record including the any children contained under that record.
+ * An exception is thrown if the record could not be generated.
+ *
+ * @param data The byte array containing the records
+ * @param offset The starting offset into the byte array
+ * @return The generated escher record
+ */
+ public EscherRecord createRecord( byte[] data, int offset )
+ {
+ EscherRecord.EscherRecordHeader header = EscherRecord.EscherRecordHeader.readHeader( data, offset );
+ if ( ( header.getOptions() & (short) 0x000F ) == (short) 0x000F )
+ {
+ EscherContainerRecord r = new EscherContainerRecord();
+ r.setRecordId( header.getRecordId() );
+ r.setOptions( header.getOptions() );
+ return r;
+ }
+ else if ( header.getRecordId() >= EscherBlipRecord.RECORD_ID_START && header.getRecordId() <= EscherBlipRecord.RECORD_ID_END )
+ {
+ EscherBlipRecord r = new EscherBlipRecord();
+ r.setRecordId( header.getRecordId() );
+ r.setOptions( header.getOptions() );
+ return r;
+ }
+ else
+ {
+ Constructor recordConstructor = (Constructor) recordsMap.get( new Short( header.getRecordId() ) );
+ EscherRecord escherRecord = null;
+ if ( recordConstructor != null )
+ {
+ try
+ {
+ escherRecord = (EscherRecord) recordConstructor.newInstance( new Object[]{} );
+ escherRecord.setRecordId( header.getRecordId() );
+ escherRecord.setOptions( header.getOptions() );
+ }
+ catch ( Exception e )
+ {
+ escherRecord = null;
+ }
+ }
+ return escherRecord == null ? new UnknownEscherRecord() : escherRecord;
+ }
+ }
+
+ /**
+ * Converts from a list of classes into a map that contains the record id as the key and
+ * the Constructor in the value part of the map. It does this by using reflection to look up
+ * the RECORD_ID field then using reflection again to find a reference to the constructor.
+ *
+ * @param records The records to convert
+ * @return The map containing the id/constructor pairs.
+ */
+ private static Map recordsToMap( Class[] records )
+ {
+ Map result = new HashMap();
+ Constructor constructor;
+
+ for ( int i = 0; i < records.length; i++ )
+ {
+ Class record = null;
+ short sid = 0;
+
+ record = records[i];
+ try
+ {
+ sid = record.getField( "RECORD_ID" ).getShort( null );
+ constructor = record.getConstructor( new Class[]
+ {
+ } );
+ }
+ catch ( Exception illegalArgumentException )
+ {
+ throw new RecordFormatException(
+ "Unable to determine record types" );
+ }
+ result.put( new Short( sid ), constructor );
+ }
+ return result;
+ }
+
+}
diff --git a/src/java/org/apache/poi/ddf/EscherArrayProperty.java b/src/java/org/apache/poi/ddf/EscherArrayProperty.java
new file mode 100644
index 000000000..8979b30a2
--- /dev/null
+++ b/src/java/org/apache/poi/ddf/EscherArrayProperty.java
@@ -0,0 +1,162 @@
+package org.apache.poi.ddf;
+
+import org.apache.poi.util.LittleEndian;
+import org.apache.poi.util.HexDump;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+/**
+ * Escher array properties are the most wierd construction ever invented
+ * with all sorts of special cases. I'm hopeful I've got them all.
+ *
+ * @author Glen Stampoultzis (glens at superlinksoftware.com)
+ */
+public class EscherArrayProperty
+ extends EscherComplexProperty
+{
+ private static final int FIXED_SIZE = 3 * 2;
+
+ public EscherArrayProperty( short id, byte[] complexData )
+ {
+ super( id, checkComplexData(complexData) );
+ }
+
+ public EscherArrayProperty( short propertyNumber, boolean isBlipId, byte[] complexData )
+ {
+ super( propertyNumber, isBlipId, checkComplexData(complexData) );
+ }
+
+ private static byte[] checkComplexData( byte[] complexData )
+ {
+ if (complexData == null || complexData.length == 0)
+ complexData = new byte[6];
+
+ return complexData;
+ }
+
+ public int getNumberOfElementsInArray()
+ {
+ return LittleEndian.getUShort( complexData, 0 );
+ }
+
+ public void setNumberOfElementsInArray( int numberOfElements )
+ {
+ int expectedArraySize = numberOfElements * getActualSizeOfElements(getSizeOfElements()) + FIXED_SIZE;
+ if ( expectedArraySize != complexData.length )
+ {
+ byte[] newArray = new byte[expectedArraySize];
+ System.arraycopy( complexData, 0, newArray, 0, complexData.length );
+ complexData = newArray;
+ }
+ LittleEndian.putShort( complexData, 0, (short) numberOfElements );
+ }
+
+ public int getNumberOfElementsInMemory()
+ {
+ return LittleEndian.getUShort( complexData, 2 );
+ }
+
+ public void setNumberOfElementsInMemory( int numberOfElements )
+ {
+ int expectedArraySize = numberOfElements * getActualSizeOfElements(getSizeOfElements()) + FIXED_SIZE;
+ if ( expectedArraySize != complexData.length )
+ {
+ byte[] newArray = new byte[expectedArraySize];
+ System.arraycopy( complexData, 0, newArray, 0, expectedArraySize );
+ complexData = newArray;
+ }
+ LittleEndian.putShort( complexData, 2, (short) numberOfElements );
+ }
+
+ public short getSizeOfElements()
+ {
+ return LittleEndian.getShort( complexData, 4 );
+ }
+
+ public void setSizeOfElements( int sizeOfElements )
+ {
+ LittleEndian.putShort( complexData, 4, (short) sizeOfElements );
+
+ int expectedArraySize = getNumberOfElementsInArray() * getActualSizeOfElements(getSizeOfElements()) + FIXED_SIZE;
+ if ( expectedArraySize != complexData.length )
+ {
+ // Keep just the first 6 bytes. The rest is no good to us anyway.
+ byte[] newArray = new byte[expectedArraySize];
+ System.arraycopy( complexData, 0, newArray, 0, 6 );
+ complexData = newArray;
+ }
+ }
+
+ public byte[] getElement( int index )
+ {
+ int actualSize = getActualSizeOfElements(getSizeOfElements());
+ byte[] result = new byte[actualSize];
+ System.arraycopy(complexData, FIXED_SIZE + index * actualSize, result, 0, result.length );
+ return result;
+ }
+
+ public void setElement( int index, byte[] element )
+ {
+ int actualSize = getActualSizeOfElements(getSizeOfElements());
+ System.arraycopy( element, 0, complexData, FIXED_SIZE + index * actualSize, actualSize);
+ }
+
+ public String toString()
+ {
+ String nl = System.getProperty("line.separator");
+
+ StringBuffer results = new StringBuffer();
+ results.append(" {EscherArrayProperty:" + nl);
+ results.append(" Num Elements: " + getNumberOfElementsInArray() + nl);
+ results.append(" Num Elements In Memory: " + getNumberOfElementsInMemory() + nl);
+ results.append(" Size of elements: " + getSizeOfElements() + nl);
+ for (int i = 0; i < getNumberOfElementsInArray(); i++)
+ {
+ results.append(" Element " + i + ": " + HexDump.toHex(getElement(i)) + nl);
+ }
+ results.append("}" + nl);
+
+ return "propNum: " + getPropertyNumber()
+ + ", propName: " + EscherProperties.getPropertyName( getPropertyNumber() )
+ + ", complex: " + isComplex()
+ + ", blipId: " + isBlipId()
+ + ", data: " + nl + results.toString();
+ }
+
+ /**
+ * We have this method because the way in which arrays in escher works
+ * is screwed for seemly arbitary reasons. While most properties are
+ * fairly consistent and have a predictable array size, escher arrays
+ * have special cases.
+ *
+ * @param data The data array containing the escher array information
+ * @param offset The offset into the array to start reading from.
+ * @return the number of bytes used by this complex property.
+ */
+ public int setArrayData( byte[] data, int offset )
+ {
+ short numElements = LittleEndian.getShort(data, offset);
+ short numReserved = LittleEndian.getShort(data, offset + 2);
+ short sizeOfElements = LittleEndian.getShort(data, offset + 4);
+
+ int arraySize = getActualSizeOfElements(sizeOfElements) * numElements;
+ if (arraySize == complexData.length)
+ complexData = new byte[arraySize + 6]; // Calculation missing the header for some reason
+ System.arraycopy(data, offset, complexData, 0, complexData.length );
+ return complexData.length;
+ }
+
+ /**
+ * Sometimes the element size is stored as a negative number. We
+ * negate it and shift it to get the real value.
+ */
+ public static int getActualSizeOfElements(short sizeOfElements)
+ {
+ if (sizeOfElements < 0)
+ return (short) ( ( -sizeOfElements ) >> 2 );
+ else
+ return sizeOfElements;
+ }
+
+}
diff --git a/src/java/org/apache/poi/ddf/EscherBSERecord.java b/src/java/org/apache/poi/ddf/EscherBSERecord.java
new file mode 100644
index 000000000..2d170ab07
--- /dev/null
+++ b/src/java/org/apache/poi/ddf/EscherBSERecord.java
@@ -0,0 +1,383 @@
+package org.apache.poi.ddf;
+
+import org.apache.poi.util.LittleEndian;
+import org.apache.poi.util.HexDump;
+
+import java.io.ByteArrayOutputStream;
+import java.io.PrintWriter;
+
+/**
+ * The BSE record is related closely to the EscherBlipRecord
and stores
+ * extra information about the blip.
+ *
+ * @author Glen Stampoultzis
+ * @see EscherBlipRecord
+ */
+public class EscherBSERecord
+ extends EscherRecord
+{
+ public static final short RECORD_ID = (short) 0xF007;
+ public static final String RECORD_DESCRIPTION = "MsofbtBSE";
+
+ public static final byte BT_ERROR = 0;
+ public static final byte BT_UNKNOWN = 1;
+ public static final byte BT_EMF = 2;
+ public static final byte BT_WMF = 3;
+ public static final byte BT_PICT = 4;
+ public static final byte BT_JPEG = 5;
+ public static final byte BT_PNG = 6;
+ public static final byte BT_DIB = 7;
+
+ private byte field_1_blipTypeWin32;
+ private byte field_2_blipTypeMacOS;
+ private byte[] field_3_uid; // 16 bytes
+ private short field_4_tag;
+ private int field_5_size;
+ private int field_6_ref;
+ private int field_7_offset;
+ private byte field_8_usage;
+ private byte field_9_name;
+ private byte field_10_unused2;
+ private byte field_11_unused3;
+
+ private byte[] remainingData;
+
+ /**
+ * This method deserializes the record from a byte array.
+ *
+ * @param data The byte array containing the escher record information
+ * @param offset The starting offset into data
.
+ * @param recordFactory May be null since this is not a container record.
+ * @return The number of bytes read from the byte array.
+ */
+ public int fillFields( byte[] data, int offset,
+ EscherRecordFactory recordFactory
+ )
+ {
+ int bytesRemaining = readHeader( data, offset );
+ int pos = offset + 8;
+ field_1_blipTypeWin32 = data[pos];
+ field_2_blipTypeMacOS = data[pos + 1];
+ System.arraycopy( data, pos + 2, field_3_uid = new byte[16], 0, 16 );
+ field_4_tag = LittleEndian.getShort( data, pos + 18 );
+ field_5_size = LittleEndian.getInt( data, pos + 20 );
+ field_6_ref = LittleEndian.getInt( data, pos + 24 );
+ field_7_offset = LittleEndian.getInt( data, pos + 28 );
+ field_8_usage = data[pos + 32];
+ field_9_name = data[pos + 33];
+ field_10_unused2 = data[pos + 34];
+ field_11_unused3 = data[pos + 35];
+ bytesRemaining -= 36;
+ remainingData = new byte[bytesRemaining];
+ System.arraycopy( data, pos + 36, remainingData, 0, bytesRemaining );
+ return bytesRemaining + 8 + 36;
+
+ }
+
+ /**
+ * This method serializes this escher record into a byte array.
+ *
+ * @param offset The offset into data
to start writing the record data to.
+ * @param data The byte array to serialize to.
+ * @param listener A listener to retrieve start and end callbacks. Use a NullEscherSerailizationListener
to ignore these events.
+ * @return The number of bytes written.
+ * @see NullEscherSerializationListener
+ */
+ public int serialize( int offset, byte[] data, EscherSerializationListener listener )
+ {
+ listener.beforeRecordSerialize( offset, getRecordId(), this );
+
+ LittleEndian.putShort( data, offset, getOptions() );
+ LittleEndian.putShort( data, offset + 2, getRecordId() );
+ int remainingBytes = remainingData.length + 36;
+ LittleEndian.putInt( data, offset + 4, remainingBytes );
+
+ data[offset + 8] = field_1_blipTypeWin32;
+ data[offset + 9] = field_2_blipTypeMacOS;
+ for ( int i = 0; i < 16; i++ )
+ data[offset + 10 + i] = field_3_uid[i];
+ LittleEndian.putShort( data, offset + 26, field_4_tag );
+ LittleEndian.putInt( data, offset + 28, field_5_size );
+ LittleEndian.putInt( data, offset + 32, field_6_ref );
+ LittleEndian.putInt( data, offset + 36, field_7_offset );
+ data[offset + 40] = field_8_usage;
+ data[offset + 41] = field_9_name;
+ data[offset + 42] = field_10_unused2;
+ data[offset + 43] = field_11_unused3;
+ System.arraycopy( remainingData, 0, data, offset + 44, remainingData.length );
+ int pos = offset + 8 + 36 + remainingData.length;
+
+ listener.afterRecordSerialize(pos, getRecordId(), pos - offset, this);
+ return pos - offset;
+ }
+
+ /**
+ * Returns the number of bytes that are required to serialize this record.
+ *
+ * @return Number of bytes
+ */
+ public int getRecordSize()
+ {
+ return 8 + 1 + 1 + 16 + 2 + 4 + 4 + 4 + 1 + 1 + 1 + 1 + remainingData.length;
+ }
+
+ /**
+ * The short name for this record
+ */
+ public String getRecordName()
+ {
+ return "BSE";
+ }
+
+ /**
+ * The expected blip type under windows (failure to match this blip type will result in
+ * Excel converting to this format).
+ */
+ public byte getBlipTypeWin32()
+ {
+ return field_1_blipTypeWin32;
+ }
+
+ /**
+ * Set the expected win32 blip type
+ */
+ public void setBlipTypeWin32( byte blipTypeWin32 )
+ {
+ this.field_1_blipTypeWin32 = blipTypeWin32;
+ }
+
+ /**
+ * The expected blip type under MacOS (failure to match this blip type will result in
+ * Excel converting to this format).
+ */
+ public byte getBlipTypeMacOS()
+ {
+ return field_2_blipTypeMacOS;
+ }
+
+ /**
+ * Set the expected MacOS blip type
+ */
+ public void setBlipTypeMacOS( byte blipTypeMacOS )
+ {
+ this.field_2_blipTypeMacOS = blipTypeMacOS;
+ }
+
+ /**
+ * 16 byte MD4 checksum.
+ */
+ public byte[] getUid()
+ {
+ return field_3_uid;
+ }
+
+ /**
+ * 16 byte MD4 checksum.
+ */
+ public void setUid( byte[] uid )
+ {
+ this.field_3_uid = uid;
+ }
+
+ /**
+ * unused
+ */
+ public short getTag()
+ {
+ return field_4_tag;
+ }
+
+ /**
+ * unused
+ */
+ public void setTag( short tag )
+ {
+ this.field_4_tag = tag;
+ }
+
+ /**
+ * Blip size in stream.
+ */
+ public int getSize()
+ {
+ return field_5_size;
+ }
+
+ /**
+ * Blip size in stream.
+ */
+ public void setSize( int size )
+ {
+ this.field_5_size = size;
+ }
+
+ /**
+ * The reference count of this blip.
+ */
+ public int getRef()
+ {
+ return field_6_ref;
+ }
+
+ /**
+ * The reference count of this blip.
+ */
+ public void setRef( int ref )
+ {
+ this.field_6_ref = ref;
+ }
+
+ /**
+ * File offset in the delay stream.
+ */
+ public int getOffset()
+ {
+ return field_7_offset;
+ }
+
+ /**
+ * File offset in the delay stream.
+ */
+ public void setOffset( int offset )
+ {
+ this.field_7_offset = offset;
+ }
+
+ /**
+ * Defines the way this blip is used.
+ */
+ public byte getUsage()
+ {
+ return field_8_usage;
+ }
+
+ /**
+ * Defines the way this blip is used.
+ */
+ public void setUsage( byte usage )
+ {
+ this.field_8_usage = usage;
+ }
+
+ /**
+ * The length in characters of the blip name.
+ */
+ public byte getName()
+ {
+ return field_9_name;
+ }
+
+ /**
+ * The length in characters of the blip name.
+ */
+ public void setName( byte name )
+ {
+ this.field_9_name = name;
+ }
+
+ public byte getUnused2()
+ {
+ return field_10_unused2;
+ }
+
+ public void setUnused2( byte unused2 )
+ {
+ this.field_10_unused2 = unused2;
+ }
+
+ public byte getUnused3()
+ {
+ return field_11_unused3;
+ }
+
+ public void setUnused3( byte unused3 )
+ {
+ this.field_11_unused3 = unused3;
+ }
+
+ /**
+ * Any remaining data in this record.
+ */
+ public byte[] getRemainingData()
+ {
+ return remainingData;
+ }
+
+ /**
+ * Any remaining data in this record.
+ */
+ public void setRemainingData( byte[] remainingData )
+ {
+ this.remainingData = remainingData;
+ }
+
+ /**
+ * Calculate the string representation of this object
+ */
+ public String toString()
+ {
+ String nl = System.getProperty( "line.separator" );
+
+ String extraData;
+ ByteArrayOutputStream b = new ByteArrayOutputStream();
+ try
+ {
+ HexDump.dump( this.remainingData, 0, b, 0 );
+ extraData = b.toString();
+ }
+ catch ( Exception e )
+ {
+ extraData = e.toString();
+ }
+ return getClass().getName() + ":" + nl +
+ " RecordId: 0x" + HexDump.toHex( RECORD_ID ) + nl +
+ " Options: 0x" + HexDump.toHex( getOptions() ) + nl +
+ " BlipTypeWin32: " + field_1_blipTypeWin32 + nl +
+ " BlipTypeMacOS: " + field_2_blipTypeMacOS + nl +
+ " SUID: " + HexDump.toHex(field_3_uid) + nl +
+ " Tag: " + field_4_tag + nl +
+ " Size: " + field_5_size + nl +
+ " Ref: " + field_6_ref + nl +
+ " Offset: " + field_7_offset + nl +
+ " Usage: " + field_8_usage + nl +
+ " Name: " + field_9_name + nl +
+ " Unused2: " + field_10_unused2 + nl +
+ " Unused3: " + field_11_unused3 + nl +
+ " Extra Data:" + nl + extraData;
+
+
+ }
+
+ /**
+ * Retrieve the string representation given a blip id.
+ */
+ public String getBlipType( byte b )
+ {
+ switch ( b )
+ {
+ case BT_ERROR:
+ return " ERROR";
+ case BT_UNKNOWN:
+ return " UNKNOWN";
+ case BT_EMF:
+ return " EMF";
+ case BT_WMF:
+ return " WMF";
+ case BT_PICT:
+ return " PICT";
+ case BT_JPEG:
+ return " JPEG";
+ case BT_PNG:
+ return " PNG";
+ case BT_DIB:
+ return " DIB";
+ default:
+ if ( b < 32 )
+ return " NotKnown";
+ else
+ return " Client";
+ }
+ }
+
+
+}
diff --git a/src/java/org/apache/poi/ddf/EscherBlipRecord.java b/src/java/org/apache/poi/ddf/EscherBlipRecord.java
new file mode 100644
index 000000000..ca8be718b
--- /dev/null
+++ b/src/java/org/apache/poi/ddf/EscherBlipRecord.java
@@ -0,0 +1,417 @@
+package org.apache.poi.ddf;
+
+import org.apache.poi.hssf.record.RecordFormatException;
+import org.apache.poi.util.HexDump;
+import org.apache.poi.util.LittleEndian;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+import java.io.IOException;
+import java.util.zip.InflaterInputStream;
+import java.util.zip.DeflaterOutputStream;
+
+/**
+ * The blip record is used to hold details about large binary objects that occur in escher such
+ * as JPEG, GIF, PICT and WMF files. The contents of the stream is usually compressed. Inflate
+ * can be used to decompress the data.
+ *
+ * @author Glen Stampoultzis
+ * @see java.util.zip.Inflater
+ */
+public class EscherBlipRecord
+ extends EscherRecord
+{
+ public static final short RECORD_ID_START = (short) 0xF018;
+ public static final short RECORD_ID_END = (short) 0xF117;
+ public static final String RECORD_DESCRIPTION = "msofbtBlip";
+ private static final int HEADER_SIZE = 8;
+
+ private byte[] field_1_secondaryUID;
+ private int field_2_cacheOfSize;
+ private int field_3_boundaryTop;
+ private int field_4_boundaryLeft;
+ private int field_5_boundaryWidth;
+ private int field_6_boundaryHeight;
+ private int field_7_width;
+ private int field_8_height;
+ private int field_9_cacheOfSavedSize;
+ private byte field_10_compressionFlag;
+ private byte field_11_filter;
+ private byte[] field_12_data;
+
+
+ /**
+ * This method deserializes the record from a byte array.
+ *
+ * @param data The byte array containing the escher record information
+ * @param offset The starting offset into data
.
+ * @param recordFactory May be null since this is not a container record.
+ * @return The number of bytes read from the byte array.
+ */
+ public int fillFields( byte[] data, int offset,
+ EscherRecordFactory recordFactory
+ )
+ {
+ int bytesAfterHeader = readHeader( data, offset );
+ int pos = offset + HEADER_SIZE;
+
+ int size = 0;
+ field_1_secondaryUID = new byte[16];
+ System.arraycopy( data, pos + size, field_1_secondaryUID, 0, 16 ); size += 16;
+ field_2_cacheOfSize = LittleEndian.getInt( data, pos + size );size+=4;
+ field_3_boundaryTop = LittleEndian.getInt( data, pos + size );size+=4;
+ field_4_boundaryLeft = LittleEndian.getInt( data, pos + size );size+=4;
+ field_5_boundaryWidth = LittleEndian.getInt( data, pos + size );size+=4;
+ field_6_boundaryHeight = LittleEndian.getInt( data, pos + size );size+=4;
+ field_7_width = LittleEndian.getInt( data, pos + size );size+=4;
+ field_8_height = LittleEndian.getInt( data, pos + size );size+=4;
+ field_9_cacheOfSavedSize = LittleEndian.getInt( data, pos + size );size+=4;
+ field_10_compressionFlag = data[pos + size]; size++;
+ field_11_filter = data[pos + size]; size++;
+
+ int bytesRemaining = bytesAfterHeader - size;
+ field_12_data = new byte[bytesRemaining];
+ System.arraycopy(data, pos + size, field_12_data, 0, bytesRemaining);
+
+ return bytesRemaining + HEADER_SIZE + bytesAfterHeader;
+ }
+
+
+ /**
+ * This method serializes this escher record into a byte array.
+ *
+ * @param offset The offset into data
to start writing the record data to.
+ * @param data The byte array to serialize to.
+ * @param listener A listener to retrieve start and end callbacks. Use a NullEscherSerailizationListener
to ignore these events.
+ * @return The number of bytes written.
+ *
+ * @see NullEscherSerializationListener
+ */
+ public int serialize( int offset, byte[] data, EscherSerializationListener listener )
+ {
+ listener.beforeRecordSerialize(offset, getRecordId(), this);
+
+ LittleEndian.putShort( data, offset, getOptions() );
+ LittleEndian.putShort( data, offset + 2, getRecordId() );
+ int remainingBytes = field_12_data.length + 36;
+ LittleEndian.putInt( data, offset + 4, remainingBytes );
+
+ int pos = offset + HEADER_SIZE;
+ System.arraycopy(field_1_secondaryUID, 0, data, pos, 16 ); pos += 16;
+ LittleEndian.putInt( data, pos, field_2_cacheOfSize); pos += 4;
+ LittleEndian.putInt( data, pos, field_3_boundaryTop); pos += 4;
+ LittleEndian.putInt( data, pos, field_4_boundaryLeft); pos += 4;
+ LittleEndian.putInt( data, pos, field_5_boundaryWidth); pos += 4;
+ LittleEndian.putInt( data, pos, field_6_boundaryHeight); pos += 4;
+ LittleEndian.putInt( data, pos, field_7_width); pos += 4;
+ LittleEndian.putInt( data, pos, field_8_height); pos += 4;
+ LittleEndian.putInt( data, pos, field_9_cacheOfSavedSize); pos += 4;
+ data[pos++] = field_10_compressionFlag;
+ data[pos++] = field_11_filter;
+ System.arraycopy(field_12_data, 0, data, pos, field_12_data.length); pos += field_12_data.length;
+
+ listener.afterRecordSerialize(pos, getRecordId(), pos - offset, this);
+ return pos - offset;
+ }
+
+ /**
+ * Returns the number of bytes that are required to serialize this record.
+ *
+ * @return Number of bytes
+ */
+ public int getRecordSize()
+ {
+ return 58 + field_12_data.length;
+ }
+
+ /**
+ * The short name for this record
+ */
+ public String getRecordName()
+ {
+ return "Blip";
+ }
+
+ /**
+ * Retrieve the secondary UID
+ */
+ public byte[] getSecondaryUID()
+ {
+ return field_1_secondaryUID;
+ }
+
+ /**
+ * Set the secondary UID
+ */
+ public void setSecondaryUID( byte[] field_1_secondaryUID )
+ {
+ this.field_1_secondaryUID = field_1_secondaryUID;
+ }
+
+ /**
+ * Retrieve the cache of the metafile size
+ */
+ public int getCacheOfSize()
+ {
+ return field_2_cacheOfSize;
+ }
+
+ /**
+ * Set the cache of the metafile size
+ */
+ public void setCacheOfSize( int field_2_cacheOfSize )
+ {
+ this.field_2_cacheOfSize = field_2_cacheOfSize;
+ }
+
+ /**
+ * Retrieve the top boundary of the metafile drawing commands
+ */
+ public int getBoundaryTop()
+ {
+ return field_3_boundaryTop;
+ }
+
+ /**
+ * Set the top boundary of the metafile drawing commands
+ */
+ public void setBoundaryTop( int field_3_boundaryTop )
+ {
+ this.field_3_boundaryTop = field_3_boundaryTop;
+ }
+
+ /**
+ * Retrieve the left boundary of the metafile drawing commands
+ */
+ public int getBoundaryLeft()
+ {
+ return field_4_boundaryLeft;
+ }
+
+ /**
+ * Set the left boundary of the metafile drawing commands
+ */
+ public void setBoundaryLeft( int field_4_boundaryLeft )
+ {
+ this.field_4_boundaryLeft = field_4_boundaryLeft;
+ }
+
+ /**
+ * Retrieve the boundary width of the metafile drawing commands
+ */
+ public int getBoundaryWidth()
+ {
+ return field_5_boundaryWidth;
+ }
+
+ /**
+ * Set the boundary width of the metafile drawing commands
+ */
+ public void setBoundaryWidth( int field_5_boundaryWidth )
+ {
+ this.field_5_boundaryWidth = field_5_boundaryWidth;
+ }
+
+ /**
+ * Retrieve the boundary height of the metafile drawing commands
+ */
+ public int getBoundaryHeight()
+ {
+ return field_6_boundaryHeight;
+ }
+
+ /**
+ * Set the boundary height of the metafile drawing commands
+ */
+ public void setBoundaryHeight( int field_6_boundaryHeight )
+ {
+ this.field_6_boundaryHeight = field_6_boundaryHeight;
+ }
+
+ /**
+ * Retrieve the width of the metafile in EMU's (English Metric Units).
+ */
+ public int getWidth()
+ {
+ return field_7_width;
+ }
+
+ /**
+ * Set the width of the metafile in EMU's (English Metric Units).
+ */
+ public void setWidth( int width )
+ {
+ this.field_7_width = width;
+ }
+
+ /**
+ * Retrieve the height of the metafile in EMU's (English Metric Units).
+ */
+ public int getHeight()
+ {
+ return field_8_height;
+ }
+
+ /**
+ * Set the height of the metafile in EMU's (English Metric Units).
+ */
+ public void setHeight( int height )
+ {
+ this.field_8_height = height;
+ }
+
+ /**
+ * Retrieve the cache of the saved size
+ */
+ public int getCacheOfSavedSize()
+ {
+ return field_9_cacheOfSavedSize;
+ }
+
+ /**
+ * Set the cache of the saved size
+ */
+ public void setCacheOfSavedSize( int field_9_cacheOfSavedSize )
+ {
+ this.field_9_cacheOfSavedSize = field_9_cacheOfSavedSize;
+ }
+
+ /**
+ * Is the contents of the blip compressed?
+ */
+ public byte getCompressionFlag()
+ {
+ return field_10_compressionFlag;
+ }
+
+ /**
+ * Set whether the contents of the blip is compressed
+ */
+ public void setCompressionFlag( byte field_10_compressionFlag )
+ {
+ this.field_10_compressionFlag = field_10_compressionFlag;
+ }
+
+ /**
+ * Filter should always be 0
+ */
+ public byte getFilter()
+ {
+ return field_11_filter;
+ }
+
+ /**
+ * Filter should always be 0
+ */
+ public void setFilter( byte field_11_filter )
+ {
+ this.field_11_filter = field_11_filter;
+ }
+
+ /**
+ * The BLIP data
+ */
+ public byte[] getData()
+ {
+ return field_12_data;
+ }
+
+ /**
+ * The BLIP data
+ */
+ public void setData( byte[] field_12_data )
+ {
+ this.field_12_data = field_12_data;
+ }
+
+ /**
+ * The string representation of this record.
+ *
+ * @return A string
+ */
+ public String toString()
+ {
+ String nl = System.getProperty( "line.separator" );
+
+ String extraData;
+ ByteArrayOutputStream b = new ByteArrayOutputStream();
+ try
+ {
+ HexDump.dump( this.field_12_data, 0, b, 0 );
+ extraData = b.toString();
+ }
+ catch ( Exception e )
+ {
+ extraData = e.toString();
+ }
+ return getClass().getName() + ":" + nl +
+ " RecordId: 0x" + HexDump.toHex( getRecordId() ) + nl +
+ " Options: 0x" + HexDump.toHex( getOptions() ) + nl +
+ " Secondary UID: " + HexDump.toHex( field_1_secondaryUID ) + nl +
+ " CacheOfSize: " + field_2_cacheOfSize + nl +
+ " BoundaryTop: " + field_3_boundaryTop + nl +
+ " BoundaryLeft: " + field_4_boundaryLeft + nl +
+ " BoundaryWidth: " + field_5_boundaryWidth + nl +
+ " BoundaryHeight: " + field_6_boundaryHeight + nl +
+ " X: " + field_7_width + nl +
+ " Y: " + field_8_height + nl +
+ " CacheOfSavedSize: " + field_9_cacheOfSavedSize + nl +
+ " CompressionFlag: " + field_10_compressionFlag + nl +
+ " Filter: " + field_11_filter + nl +
+ " Data:" + nl + extraData;
+ }
+
+ /**
+ * Compress the contents of the provided array
+ *
+ * @param data An uncompressed byte array
+ * @see DeflaterOutputStream#write(int b)
+ */
+ public static byte[] compress( byte[] data )
+ {
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ DeflaterOutputStream deflaterOutputStream = new DeflaterOutputStream( out );
+ try
+ {
+ for ( int i = 0; i < data.length; i++ )
+ deflaterOutputStream.write( data[i] );
+ }
+ catch ( IOException e )
+ {
+ throw new RecordFormatException( e.toString() );
+ }
+
+ return out.toByteArray();
+ }
+
+ /**
+ * Decompresses a byte array.
+ *
+ * @param data The compressed byte array
+ * @param pos The starting position into the byte array
+ * @param length The number of compressed bytes to decompress
+ * @return An uncompressed byte array
+ * @see InflaterInputStream#read
+ */
+ public static byte[] decompress( byte[] data, int pos, int length )
+ {
+ byte[] compressedData = new byte[length];
+ System.arraycopy( data, pos + 50, compressedData, 0, length );
+ InputStream compressedInputStream = new ByteArrayInputStream( compressedData );
+ InflaterInputStream inflaterInputStream = new InflaterInputStream( compressedInputStream );
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ int c;
+ try
+ {
+ while ( ( c = inflaterInputStream.read() ) != -1 )
+ out.write( c );
+ }
+ catch ( IOException e )
+ {
+ throw new RecordFormatException( e.toString() );
+ }
+ return out.toByteArray();
+ }
+
+}
diff --git a/src/java/org/apache/poi/ddf/EscherBoolProperty.java b/src/java/org/apache/poi/ddf/EscherBoolProperty.java
new file mode 100644
index 000000000..86a7a0a5c
--- /dev/null
+++ b/src/java/org/apache/poi/ddf/EscherBoolProperty.java
@@ -0,0 +1,50 @@
+package org.apache.poi.ddf;
+
+/**
+ * Represents a boolean property. The actual utility of this property is in doubt because many
+ * of the properties marked as boolean seem to actually contain special values. In other words
+ * they're not true booleans.
+ *
+ * @author Glen Stampoultzis
+ * @see EscherSimpleProperty
+ * @see EscherProperty
+ */
+public class EscherBoolProperty
+ extends EscherSimpleProperty
+{
+ /**
+ * Create an instance of an escher boolean property.
+ *
+ * @param propertyNumber The property number
+ * @param value The 32 bit value of this bool property
+ */
+ public EscherBoolProperty( short propertyNumber, int value )
+ {
+ super( propertyNumber, false, false, value );
+ }
+
+ /**
+ * Whether this boolean property is true
+ */
+ public boolean isTrue()
+ {
+ return propertyValue != 0;
+ }
+
+ /**
+ * Whether this boolean property is false
+ */
+ public boolean isFalse()
+ {
+ return propertyValue == 0;
+ }
+
+// public String toString()
+// {
+// return "propNum: " + getPropertyNumber()
+// + ", complex: " + isComplex()
+// + ", blipId: " + isBlipId()
+// + ", value: " + (getValue() != 0);
+// }
+
+}
diff --git a/src/java/org/apache/poi/ddf/EscherChildAnchorRecord.java b/src/java/org/apache/poi/ddf/EscherChildAnchorRecord.java
new file mode 100644
index 000000000..f53ba7d72
--- /dev/null
+++ b/src/java/org/apache/poi/ddf/EscherChildAnchorRecord.java
@@ -0,0 +1,176 @@
+package org.apache.poi.ddf;
+
+import org.apache.poi.util.HexDump;
+import org.apache.poi.util.LittleEndian;
+
+/**
+ * The escher child achor record is used to specify the position of a shape under an
+ * existing group. The first level of shape records use a EscherClientAnchor record instead.
+ *
+ * @author Glen Stampoultzis
+ * @see EscherChildAnchorRecord
+ */
+public class EscherChildAnchorRecord
+ extends EscherRecord
+{
+ public static final short RECORD_ID = (short) 0xF00F;
+ public static final String RECORD_DESCRIPTION = "MsofbtChildAnchor";
+
+ private int field_1_dx1;
+ private int field_2_dy1;
+ private int field_3_dx2;
+ private int field_4_dy2;
+
+ /**
+ * This method deserializes the record from a byte array.
+ *
+ * @param data The byte array containing the escher record information
+ * @param offset The starting offset into data
.
+ * @param recordFactory May be null since this is not a container record.
+ * @return The number of bytes read from the byte array.
+ */
+ public int fillFields( byte[] data, int offset, EscherRecordFactory recordFactory )
+ {
+ int bytesRemaining = readHeader( data, offset );
+ int pos = offset + 8;
+ int size = 0;
+ field_1_dx1 = LittleEndian.getInt( data, pos + size );size+=4;
+ field_2_dy1 = LittleEndian.getInt( data, pos + size );size+=4;
+ field_3_dx2 = LittleEndian.getInt( data, pos + size );size+=4;
+ field_4_dy2 = LittleEndian.getInt( data, pos + size );size+=4;
+ return 8 + size;
+ }
+
+ /**
+ * This method serializes this escher record into a byte array.
+ *
+ * @param offset The offset into data
to start writing the record data to.
+ * @param data The byte array to serialize to.
+ * @param listener A listener to retrieve start and end callbacks. Use a NullEscherSerailizationListener
to ignore these events.
+ * @return The number of bytes written.
+ * @see NullEscherSerializationListener
+ */
+ public int serialize( int offset, byte[] data, EscherSerializationListener listener )
+ {
+ listener.beforeRecordSerialize( offset, getRecordId(), this );
+ int pos = offset;
+ LittleEndian.putShort( data, pos, getOptions() ); pos += 2;
+ LittleEndian.putShort( data, pos, getRecordId() ); pos += 2;
+ LittleEndian.putInt( data, pos, getRecordSize()-8 ); pos += 4;
+ LittleEndian.putInt( data, pos, field_1_dx1 ); pos += 4;
+ LittleEndian.putInt( data, pos, field_2_dy1 ); pos += 4;
+ LittleEndian.putInt( data, pos, field_3_dx2 ); pos += 4;
+ LittleEndian.putInt( data, pos, field_4_dy2 ); pos += 4;
+
+ listener.afterRecordSerialize( pos, getRecordId(), pos - offset, this );
+ return pos - offset;
+ }
+
+ /**
+ * Returns the number of bytes that are required to serialize this record.
+ *
+ * @return Number of bytes
+ */
+ public int getRecordSize()
+ {
+ return 8 + 4 * 4;
+ }
+
+ /**
+ * The record id for the EscherChildAnchorRecord.
+ */
+ public short getRecordId()
+ {
+ return RECORD_ID;
+ }
+
+ /**
+ * The short name for this record
+ */
+ public String getRecordName()
+ {
+ return "ChildAnchor";
+ }
+
+ /**
+ * The string representation of this record
+ */
+ public String toString()
+ {
+ String nl = System.getProperty("line.separator");
+
+ return getClass().getName() + ":" + nl +
+ " RecordId: 0x" + HexDump.toHex(RECORD_ID) + nl +
+ " Options: 0x" + HexDump.toHex(getOptions()) + nl +
+ " X1: " + field_1_dx1 + nl +
+ " Y1: " + field_2_dy1 + nl +
+ " X2: " + field_3_dx2 + nl +
+ " Y2: " + field_4_dy2 + nl ;
+
+ }
+
+ /**
+ * Retrieves offset within the parent coordinate space for the top left point.
+ */
+ public int getDx1()
+ {
+ return field_1_dx1;
+ }
+
+ /**
+ * Sets offset within the parent coordinate space for the top left point.
+ */
+ public void setDx1( int field_1_dx1 )
+ {
+ this.field_1_dx1 = field_1_dx1;
+ }
+
+ /**
+ * Gets offset within the parent coordinate space for the top left point.
+ */
+ public int getDy1()
+ {
+ return field_2_dy1;
+ }
+
+ /**
+ * Sets offset within the parent coordinate space for the top left point.
+ */
+ public void setDy1( int field_2_dy1 )
+ {
+ this.field_2_dy1 = field_2_dy1;
+ }
+
+ /**
+ * Retrieves offset within the parent coordinate space for the bottom right point.
+ */
+ public int getDx2()
+ {
+ return field_3_dx2;
+ }
+
+ /**
+ * Sets offset within the parent coordinate space for the bottom right point.
+ */
+ public void setDx2( int field_3_dx2 )
+ {
+ this.field_3_dx2 = field_3_dx2;
+ }
+
+ /**
+ * Gets the offset within the parent coordinate space for the bottom right point.
+ */
+ public int getDy2()
+ {
+ return field_4_dy2;
+ }
+
+ /**
+ * Sets the offset within the parent coordinate space for the bottom right point.
+ */
+ public void setDy2( int field_4_dy2 )
+ {
+ this.field_4_dy2 = field_4_dy2;
+ }
+
+}
diff --git a/src/java/org/apache/poi/ddf/EscherClientAnchorRecord.java b/src/java/org/apache/poi/ddf/EscherClientAnchorRecord.java
new file mode 100644
index 000000000..d61d77b7a
--- /dev/null
+++ b/src/java/org/apache/poi/ddf/EscherClientAnchorRecord.java
@@ -0,0 +1,317 @@
+package org.apache.poi.ddf;
+
+import org.apache.poi.util.HexDump;
+import org.apache.poi.util.LittleEndian;
+
+import java.io.ByteArrayOutputStream;
+
+/**
+ * The escher client anchor specifies which rows and cells the shape is bound to as well as
+ * the offsets within those cells. Each cell is 1024 units wide by 256 units long regardless
+ * of the actual size of the cell. The EscherClientAnchorRecord only applies to the top-most
+ * shapes. Shapes contained in groups are bound using the EscherChildAnchorRecords.
+ *
+ * @author Glen Stampoultzis
+ * @see EscherChildAnchorRecord
+ */
+public class EscherClientAnchorRecord
+ extends EscherRecord
+{
+ public static final short RECORD_ID = (short) 0xF010;
+ public static final String RECORD_DESCRIPTION = "MsofbtClientAnchor";
+
+ private short field_1_flag;
+ private short field_2_col1;
+ private short field_3_dx1;
+ private short field_4_row1;
+ private short field_5_dy1;
+ private short field_6_col2;
+ private short field_7_dx2;
+ private short field_8_row2;
+ private short field_9_dy2;
+ private byte[] remainingData;
+
+ /**
+ * This method deserializes the record from a byte array.
+ *
+ * @param data The byte array containing the escher record information
+ * @param offset The starting offset into data
.
+ * @param recordFactory May be null since this is not a container record.
+ * @return The number of bytes read from the byte array.
+ */
+ public int fillFields( byte[] data, int offset, EscherRecordFactory recordFactory )
+ {
+ int bytesRemaining = readHeader( data, offset );
+ int pos = offset + 8;
+ int size = 0;
+ field_1_flag = LittleEndian.getShort( data, pos + size ); size += 2;
+ field_2_col1 = LittleEndian.getShort( data, pos + size ); size += 2;
+ field_3_dx1 = LittleEndian.getShort( data, pos + size ); size += 2;
+ field_4_row1 = LittleEndian.getShort( data, pos + size ); size += 2;
+ field_5_dy1 = LittleEndian.getShort( data, pos + size ); size += 2;
+ field_6_col2 = LittleEndian.getShort( data, pos + size ); size += 2;
+ field_7_dx2 = LittleEndian.getShort( data, pos + size ); size += 2;
+ field_8_row2 = LittleEndian.getShort( data, pos + size ); size += 2;
+ field_9_dy2 = LittleEndian.getShort( data, pos + size ); size += 2;
+ bytesRemaining -= size;
+ remainingData = new byte[bytesRemaining];
+ System.arraycopy( data, pos + size, remainingData, 0, bytesRemaining );
+ return 8 + size + bytesRemaining;
+ }
+
+ /**
+ * This method serializes this escher record into a byte array.
+ *
+ * @param offset The offset into data
to start writing the record data to.
+ * @param data The byte array to serialize to.
+ * @param listener A listener to retrieve start and end callbacks. Use a NullEscherSerailizationListener
to ignore these events.
+ * @return The number of bytes written.
+ * @see NullEscherSerializationListener
+ */
+ public int serialize( int offset, byte[] data, EscherSerializationListener listener )
+ {
+ listener.beforeRecordSerialize( offset, getRecordId(), this );
+
+ if (remainingData == null) remainingData = new byte[0];
+ LittleEndian.putShort( data, offset, getOptions() );
+ LittleEndian.putShort( data, offset + 2, getRecordId() );
+ int remainingBytes = remainingData.length + 18;
+ LittleEndian.putInt( data, offset + 4, remainingBytes );
+ LittleEndian.putShort( data, offset + 8, field_1_flag );
+ LittleEndian.putShort( data, offset + 10, field_2_col1 );
+ LittleEndian.putShort( data, offset + 12, field_3_dx1 );
+ LittleEndian.putShort( data, offset + 14, field_4_row1 );
+ LittleEndian.putShort( data, offset + 16, field_5_dy1 );
+ LittleEndian.putShort( data, offset + 18, field_6_col2 );
+ LittleEndian.putShort( data, offset + 20, field_7_dx2 );
+ LittleEndian.putShort( data, offset + 22, field_8_row2 );
+ LittleEndian.putShort( data, offset + 24, field_9_dy2 );
+ System.arraycopy( remainingData, 0, data, offset + 26, remainingData.length );
+ int pos = offset + 8 + 18 + remainingData.length;
+
+ listener.afterRecordSerialize( pos, getRecordId(), pos - offset, this );
+ return pos - offset;
+ }
+
+ /**
+ * Returns the number of bytes that are required to serialize this record.
+ *
+ * @return Number of bytes
+ */
+ public int getRecordSize()
+ {
+ return 8 + 18 + (remainingData == null ? 0 : remainingData.length);
+ }
+
+ /**
+ * The record id for this record.
+ */
+ public short getRecordId()
+ {
+ return RECORD_ID;
+ }
+
+ /**
+ * The short name for this record
+ */
+ public String getRecordName()
+ {
+ return "ClientAnchor";
+ }
+
+ /**
+ * Returns the string representation for this record.
+ *
+ * @return A string
+ */
+ public String toString()
+ {
+ String nl = System.getProperty("line.separator");
+
+ String extraData;
+ ByteArrayOutputStream b = new ByteArrayOutputStream();
+ try
+ {
+ HexDump.dump(this.remainingData, 0, b, 0);
+ extraData = b.toString();
+ }
+ catch ( Exception e )
+ {
+ extraData = "error";
+ }
+ return getClass().getName() + ":" + nl +
+ " RecordId: 0x" + HexDump.toHex(RECORD_ID) + nl +
+ " Options: 0x" + HexDump.toHex(getOptions()) + nl +
+ " Flag: " + field_1_flag + nl +
+ " Col1: " + field_2_col1 + nl +
+ " DX1: " + field_3_dx1 + nl +
+ " Row1: " + field_4_row1 + nl +
+ " DY1: " + field_5_dy1 + nl +
+ " Col2: " + field_6_col2 + nl +
+ " DX2: " + field_7_dx2 + nl +
+ " Row2: " + field_8_row2 + nl +
+ " DY2: " + field_9_dy2 + nl +
+ " Extra Data:" + nl + extraData;
+
+ }
+
+ /**
+ * 0 = Move and size with Cells, 2 = Move but don't size with cells, 3 = Don't move or size with cells.
+ */
+ public short getFlag()
+ {
+ return field_1_flag;
+ }
+
+ /**
+ * 0 = Move and size with Cells, 2 = Move but don't size with cells, 3 = Don't move or size with cells.
+ */
+ public void setFlag( short field_1_flag )
+ {
+ this.field_1_flag = field_1_flag;
+ }
+
+ /**
+ * The column number for the top-left position. 0 based.
+ */
+ public short getCol1()
+ {
+ return field_2_col1;
+ }
+
+ /**
+ * The column number for the top-left position. 0 based.
+ */
+ public void setCol1( short field_2_col1 )
+ {
+ this.field_2_col1 = field_2_col1;
+ }
+
+ /**
+ * The x offset within the top-left cell. Range is from 0 to 1023.
+ */
+ public short getDx1()
+ {
+ return field_3_dx1;
+ }
+
+ /**
+ * The x offset within the top-left cell. Range is from 0 to 1023.
+ */
+ public void setDx1( short field_3_dx1 )
+ {
+ this.field_3_dx1 = field_3_dx1;
+ }
+
+ /**
+ * The row number for the top-left corner of the shape.
+ */
+ public short getRow1()
+ {
+ return field_4_row1;
+ }
+
+ /**
+ * The row number for the top-left corner of the shape.
+ */
+ public void setRow1( short field_4_row1 )
+ {
+ this.field_4_row1 = field_4_row1;
+ }
+
+ /**
+ * The y offset within the top-left corner of the current shape.
+ */
+ public short getDy1()
+ {
+ return field_5_dy1;
+ }
+
+ /**
+ * The y offset within the top-left corner of the current shape.
+ */
+ public void setDy1( short field_5_dy1 )
+ {
+ this.field_5_dy1 = field_5_dy1;
+ }
+
+ /**
+ * The column of the bottom right corner of this shape.
+ */
+ public short getCol2()
+ {
+ return field_6_col2;
+ }
+
+ /**
+ * The column of the bottom right corner of this shape.
+ */
+ public void setCol2( short field_6_col2 )
+ {
+ this.field_6_col2 = field_6_col2;
+ }
+
+ /**
+ * The x offset withing the cell for the bottom-right corner of this shape.
+ */
+ public short getDx2()
+ {
+ return field_7_dx2;
+ }
+
+ /**
+ * The x offset withing the cell for the bottom-right corner of this shape.
+ */
+ public void setDx2( short field_7_dx2 )
+ {
+ this.field_7_dx2 = field_7_dx2;
+ }
+
+ /**
+ * The row number for the bottom-right corner of the current shape.
+ */
+ public short getRow2()
+ {
+ return field_8_row2;
+ }
+
+ /**
+ * The row number for the bottom-right corner of the current shape.
+ */
+ public void setRow2( short field_8_row2 )
+ {
+ this.field_8_row2 = field_8_row2;
+ }
+
+ /**
+ * The y offset withing the cell for the bottom-right corner of this shape.
+ */
+ public short getDy2()
+ {
+ return field_9_dy2;
+ }
+
+ /**
+ * The y offset withing the cell for the bottom-right corner of this shape.
+ */
+ public void setDy2( short field_9_dy2 )
+ {
+ this.field_9_dy2 = field_9_dy2;
+ }
+
+ /**
+ * Any remaining data in the record
+ */
+ public byte[] getRemainingData()
+ {
+ return remainingData;
+ }
+
+ /**
+ * Any remaining data in the record
+ */
+ public void setRemainingData( byte[] remainingData )
+ {
+ this.remainingData = remainingData;
+ }
+}
diff --git a/src/java/org/apache/poi/ddf/EscherClientDataRecord.java b/src/java/org/apache/poi/ddf/EscherClientDataRecord.java
new file mode 100644
index 000000000..d568996f4
--- /dev/null
+++ b/src/java/org/apache/poi/ddf/EscherClientDataRecord.java
@@ -0,0 +1,130 @@
+package org.apache.poi.ddf;
+
+import org.apache.poi.util.HexDump;
+import org.apache.poi.util.LittleEndian;
+
+import java.io.ByteArrayOutputStream;
+
+/**
+ * The EscherClientDataRecord is used to store client specific data about the position of a
+ * shape within a container.
+ *
+ * @author Glen Stampoultzis
+ */
+public class EscherClientDataRecord
+ extends EscherRecord
+{
+ public static final short RECORD_ID = (short) 0xF011;
+ public static final String RECORD_DESCRIPTION = "MsofbtClientData";
+
+ private byte[] remainingData;
+
+ /**
+ * This method deserializes the record from a byte array.
+ *
+ * @param data The byte array containing the escher record information
+ * @param offset The starting offset into data
.
+ * @param recordFactory May be null since this is not a container record.
+ * @return The number of bytes read from the byte array.
+ */
+ public int fillFields( byte[] data, int offset, EscherRecordFactory recordFactory )
+ {
+ int bytesRemaining = readHeader( data, offset );
+ int pos = offset + 8;
+ remainingData = new byte[bytesRemaining];
+ System.arraycopy( data, pos, remainingData, 0, bytesRemaining );
+ return 8 + bytesRemaining;
+ }
+
+ /**
+ * This method serializes this escher record into a byte array.
+ *
+ * @param offset The offset into data
to start writing the record data to.
+ * @param data The byte array to serialize to.
+ * @param listener A listener to retrieve start and end callbacks. Use a NullEscherSerailizationListener
to ignore these events.
+ * @return The number of bytes written.
+ * @see NullEscherSerializationListener
+ */
+ public int serialize( int offset, byte[] data, EscherSerializationListener listener )
+ {
+ listener.beforeRecordSerialize( offset, getRecordId(), this );
+
+ if (remainingData == null) remainingData = new byte[0];
+ LittleEndian.putShort( data, offset, getOptions() );
+ LittleEndian.putShort( data, offset + 2, getRecordId() );
+ LittleEndian.putInt( data, offset + 4, remainingData.length );
+ System.arraycopy( remainingData, 0, data, offset + 8, remainingData.length );
+ int pos = offset + 8 + remainingData.length;
+
+ listener.afterRecordSerialize( pos, getRecordId(), pos - offset, this );
+ return pos - offset;
+ }
+
+ /**
+ * Returns the number of bytes that are required to serialize this record.
+ *
+ * @return Number of bytes
+ */
+ public int getRecordSize()
+ {
+ return 8 + (remainingData == null ? 0 : remainingData.length);
+ }
+
+ /**
+ * Returns the identifier of this record.
+ */
+ public short getRecordId()
+ {
+ return RECORD_ID;
+ }
+
+ /**
+ * The short name for this record
+ */
+ public String getRecordName()
+ {
+ return "ClientData";
+ }
+
+ /**
+ * Returns the string representation of this record.
+ */
+ public String toString()
+ {
+ String nl = System.getProperty("line.separator");
+
+ String extraData;
+ ByteArrayOutputStream b = new ByteArrayOutputStream();
+ try
+ {
+ HexDump.dump(this.remainingData, 0, b, 0);
+ extraData = b.toString();
+ }
+ catch ( Exception e )
+ {
+ extraData = "error";
+ }
+ return getClass().getName() + ":" + nl +
+ " RecordId: 0x" + HexDump.toHex(RECORD_ID) + nl +
+ " Options: 0x" + HexDump.toHex(getOptions()) + nl +
+ " Extra Data:" + nl +
+ extraData;
+
+ }
+
+ /**
+ * Any data recording this record.
+ */
+ public byte[] getRemainingData()
+ {
+ return remainingData;
+ }
+
+ /**
+ * Any data recording this record.
+ */
+ public void setRemainingData( byte[] remainingData )
+ {
+ this.remainingData = remainingData;
+ }
+}
diff --git a/src/java/org/apache/poi/ddf/EscherComplexProperty.java b/src/java/org/apache/poi/ddf/EscherComplexProperty.java
new file mode 100644
index 000000000..4788a66c7
--- /dev/null
+++ b/src/java/org/apache/poi/ddf/EscherComplexProperty.java
@@ -0,0 +1,152 @@
+package org.apache.poi.ddf;
+
+import org.apache.poi.util.LittleEndian;
+import org.apache.poi.util.HexDump;
+
+import java.util.Arrays;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+/**
+ * A complex property differs from a simple property in that the data can not fit inside a 32 bit
+ * integer. See the specification for more detailed information regarding exactly what is
+ * stored here.
+ *
+ * @author Glen Stampoultzis
+ */
+public class EscherComplexProperty
+ extends EscherProperty
+{
+ byte[] complexData = new byte[0];
+
+ /**
+ * Create a complex property using the property id and a byte array containing the complex
+ * data value.
+ *
+ * @param id The id consists of the property number, a flag indicating whether this is a blip id and a flag
+ * indicating that this is a complex property.
+ * @param complexData The value of this property.
+ */
+ public EscherComplexProperty( short id, byte[] complexData )
+ {
+ super( id );
+ this.complexData = complexData;
+ }
+
+ /**
+ * Create a complex property using the property number, a flag to indicate whether this is a
+ * blip reference and the complex property data.
+ *
+ * @param propertyNumber The property number
+ * @param isBlipId Whether this is a blip id. Should be false.
+ * @param complexData The value of this complex property.
+ */
+ public EscherComplexProperty( short propertyNumber, boolean isBlipId, byte[] complexData )
+ {
+ super( propertyNumber, true, isBlipId );
+ this.complexData = complexData;
+ }
+
+ /**
+ * Serializes the simple part of this property. ie the first 6 bytes.
+ */
+ public int serializeSimplePart( byte[] data, int pos )
+ {
+ LittleEndian.putShort(data, pos, getId());
+ LittleEndian.putInt(data, pos + 2, complexData.length);
+ return 6;
+ }
+
+ /**
+ * Serializes the complex part of this property
+ *
+ * @param data The data array to serialize to
+ * @param pos The offset within data to start serializing to.
+ * @return The number of bytes serialized.
+ */
+ public int serializeComplexPart( byte[] data, int pos )
+ {
+ System.arraycopy(complexData, 0, data, pos, complexData.length);
+ return complexData.length;
+ }
+
+ /**
+ * Get the complex data value.
+ */
+ public byte[] getComplexData()
+ {
+ return complexData;
+ }
+
+ /**
+ * Determine whether this property is equal to another property.
+ *
+ * @param o The object to compare to.
+ * @return True if the objects are equal.
+ */
+ public boolean equals( Object o )
+ {
+ if ( this == o ) return true;
+ if ( !( o instanceof EscherComplexProperty ) ) return false;
+
+ final EscherComplexProperty escherComplexProperty = (EscherComplexProperty) o;
+
+ if ( !Arrays.equals( complexData, escherComplexProperty.complexData ) ) return false;
+
+ return true;
+ }
+
+ /**
+ * Caclulates the number of bytes required to serialize this property.
+ *
+ * @return Number of bytes
+ */
+ public int getPropertySize()
+ {
+ return 6 + complexData.length;
+ }
+
+ /**
+ * Calculates a hashcode for this property.
+ */
+ public int hashCode()
+ {
+ return getId() * 11;
+ }
+
+ /**
+ * Retrieves the string representation for this property.
+ */
+ public String toString()
+ {
+ String dataStr;
+ ByteArrayOutputStream b = new ByteArrayOutputStream();
+ try
+ {
+ HexDump.dump( this.complexData, 0, b, 0 );
+ dataStr = b.toString();
+ }
+ catch ( Exception e )
+ {
+ dataStr = e.toString();
+ }
+ finally
+ {
+ try
+ {
+ b.close();
+ }
+ catch ( IOException e )
+ {
+ e.printStackTrace();
+ }
+ }
+
+ return "propNum: " + getPropertyNumber()
+ + ", propName: " + EscherProperties.getPropertyName( getPropertyNumber() )
+ + ", complex: " + isComplex()
+ + ", blipId: " + isBlipId()
+ + ", data: " + System.getProperty("line.separator") + dataStr;
+ }
+
+}
diff --git a/src/java/org/apache/poi/ddf/EscherContainerRecord.java b/src/java/org/apache/poi/ddf/EscherContainerRecord.java
new file mode 100644
index 000000000..41c7a7ec9
--- /dev/null
+++ b/src/java/org/apache/poi/ddf/EscherContainerRecord.java
@@ -0,0 +1,168 @@
+package org.apache.poi.ddf;
+
+import org.apache.poi.util.HexDump;
+import org.apache.poi.util.LittleEndian;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.io.PrintWriter;
+
+/**
+ * Escher container records store other escher records as children.
+ * The container records themselves never store any information beyond
+ * the standard header used by all escher records. This one record is
+ * used to represent many different types of records.
+ *
+ * @author Glen Stampoultzis
+ */
+public class EscherContainerRecord extends EscherRecord
+{
+ public static final short DGG_CONTAINER = (short)0xF000;
+ public static final short BSTORE_CONTAINER = (short)0xF001;
+ public static final short DG_CONTAINER = (short)0xF002;
+ public static final short SPGR_CONTAINER = (short)0xF003;
+ public static final short SP_CONTAINER = (short)0xF004;
+ public static final short SOLVER_CONTAINER = (short)0xF005;
+
+ private List childRecords = new ArrayList();
+
+ public int fillFields( byte[] data, int offset, EscherRecordFactory recordFactory )
+ {
+ int bytesRemaining = readHeader( data, offset );
+ int bytesWritten = 8;
+ offset += 8;
+ while ( bytesRemaining > 0 && offset < data.length )
+ {
+ EscherRecord child = recordFactory.createRecord(data, offset);
+ int childBytesWritten = child.fillFields( data, offset, recordFactory );
+ bytesWritten += childBytesWritten;
+ offset += childBytesWritten;
+ bytesRemaining -= childBytesWritten;
+ getChildRecords().add( child );
+ if (offset >= data.length && bytesRemaining > 0)
+ {
+ System.out.println("WARNING: " + bytesRemaining + " bytes remaining but no space left");
+ }
+ }
+ return bytesWritten;
+ }
+
+ public int serialize( int offset, byte[] data, EscherSerializationListener listener )
+ {
+ listener.beforeRecordSerialize( offset, getRecordId(), this );
+
+ LittleEndian.putShort(data, offset, getOptions());
+ LittleEndian.putShort(data, offset+2, getRecordId());
+ int remainingBytes = 0;
+ for ( Iterator iterator = getChildRecords().iterator(); iterator.hasNext(); )
+ {
+ EscherRecord r = (EscherRecord) iterator.next();
+ remainingBytes += r.getRecordSize();
+ }
+ LittleEndian.putInt(data, offset+4, remainingBytes);
+ int pos = offset+8;
+ for ( Iterator iterator = getChildRecords().iterator(); iterator.hasNext(); )
+ {
+ EscherRecord r = (EscherRecord) iterator.next();
+ pos += r.serialize(pos, data, listener );
+ }
+
+ listener.afterRecordSerialize( pos, getRecordId(), pos - offset, this );
+ return pos - offset;
+ }
+
+ public int getRecordSize()
+ {
+ int childRecordsSize = 0;
+ for ( Iterator iterator = getChildRecords().iterator(); iterator.hasNext(); )
+ {
+ EscherRecord r = (EscherRecord) iterator.next();
+ childRecordsSize += r.getRecordSize();
+ }
+ return 8 + childRecordsSize;
+ }
+
+ public List getChildRecords()
+ {
+ return childRecords;
+ }
+
+ public void setChildRecords( List childRecords )
+ {
+ this.childRecords = childRecords;
+ }
+
+ public String getRecordName()
+ {
+ switch ((short)getRecordId())
+ {
+ case DGG_CONTAINER:
+ return "DggContainer";
+ case BSTORE_CONTAINER:
+ return "BStoreContainer";
+ case DG_CONTAINER:
+ return "DgContainer";
+ case SPGR_CONTAINER:
+ return "SpgrContainer";
+ case SP_CONTAINER:
+ return "SpContainer";
+ case SOLVER_CONTAINER:
+ return "SolverContainer";
+ default:
+ return "Container 0x" + HexDump.toHex(getRecordId());
+ }
+ }
+
+ public void display( PrintWriter w, int indent )
+ {
+ super.display( w, indent );
+ for ( Iterator iterator = childRecords.iterator(); iterator.hasNext(); )
+ {
+ EscherRecord escherRecord = (EscherRecord) iterator.next();
+ escherRecord.display( w, indent + 1 );
+ }
+ }
+
+ public void addChildRecord( EscherRecord record )
+ {
+ this.childRecords.add( record );
+ }
+
+ public String toString()
+ {
+ String nl = System.getProperty( "line.separator" );
+
+ StringBuffer children = new StringBuffer();
+ if ( getChildRecords().size() > 0 )
+ {
+ children.append( " children: " + nl );
+ for ( Iterator iterator = getChildRecords().iterator(); iterator.hasNext(); )
+ {
+ EscherRecord record = (EscherRecord) iterator.next();
+ children.append( record.toString() );
+// children.append( nl );
+ }
+ }
+
+ return getClass().getName() + " (" + getRecordName() + "):" + nl +
+ " isContainer: " + isContainerRecord() + nl +
+ " options: 0x" + HexDump.toHex( getOptions() ) + nl +
+ " recordId: 0x" + HexDump.toHex( getRecordId() ) + nl +
+ " numchildren: " + getChildRecords().size() + nl +
+ children.toString();
+
+ }
+
+ public EscherSpRecord getChildById( short recordId )
+ {
+ for ( Iterator iterator = childRecords.iterator(); iterator.hasNext(); )
+ {
+ EscherRecord escherRecord = (EscherRecord) iterator.next();
+ if (escherRecord.getRecordId() == recordId)
+ return (EscherSpRecord) escherRecord;
+ }
+ return null;
+ }
+
+}
diff --git a/src/java/org/apache/poi/ddf/EscherDgRecord.java b/src/java/org/apache/poi/ddf/EscherDgRecord.java
new file mode 100644
index 000000000..049bf383a
--- /dev/null
+++ b/src/java/org/apache/poi/ddf/EscherDgRecord.java
@@ -0,0 +1,163 @@
+package org.apache.poi.ddf;
+
+import org.apache.poi.util.HexDump;
+import org.apache.poi.util.LittleEndian;
+
+/**
+ * This record simply holds the number of shapes in the drawing group and the
+ * last shape id used for this drawing group.
+ *
+ * @author Glen Stampoultzis
+ */
+public class EscherDgRecord
+ extends EscherRecord
+{
+ public static final short RECORD_ID = (short) 0xF008;
+ public static final String RECORD_DESCRIPTION = "MsofbtDg";
+
+ private int field_1_numShapes;
+ private int field_2_lastMSOSPID;
+
+ /**
+ * This method deserializes the record from a byte array.
+ *
+ * @param data The byte array containing the escher record information
+ * @param offset The starting offset into data
.
+ * @param recordFactory May be null since this is not a container record.
+ * @return The number of bytes read from the byte array.
+ */
+ public int fillFields( byte[] data, int offset, EscherRecordFactory recordFactory )
+ {
+ int bytesRemaining = readHeader( data, offset );
+ int pos = offset + 8;
+ int size = 0;
+ field_1_numShapes = LittleEndian.getInt( data, pos + size ); size += 4;
+ field_2_lastMSOSPID = LittleEndian.getInt( data, pos + size ); size += 4;
+// bytesRemaining -= size;
+// remainingData = new byte[bytesRemaining];
+// System.arraycopy( data, pos + size, remainingData, 0, bytesRemaining );
+ return getRecordSize();
+ }
+
+ /**
+ * This method serializes this escher record into a byte array.
+ *
+ * @param offset The offset into data
to start writing the record data to.
+ * @param data The byte array to serialize to.
+ * @param listener A listener to retrieve start and end callbacks. Use a NullEscherSerailizationListener
to ignore these events.
+ * @return The number of bytes written.
+ * @see NullEscherSerializationListener
+ */
+ public int serialize( int offset, byte[] data, EscherSerializationListener listener )
+ {
+ listener.beforeRecordSerialize( offset, getRecordId(), this );
+
+ LittleEndian.putShort( data, offset, getOptions() );
+ LittleEndian.putShort( data, offset + 2, getRecordId() );
+ LittleEndian.putInt( data, offset + 4, 8 );
+ LittleEndian.putInt( data, offset + 8, field_1_numShapes );
+ LittleEndian.putInt( data, offset + 12, field_2_lastMSOSPID );
+// System.arraycopy( remainingData, 0, data, offset + 26, remainingData.length );
+// int pos = offset + 8 + 18 + remainingData.length;
+
+ listener.afterRecordSerialize( offset + 16, getRecordId(), getRecordSize(), this );
+ return getRecordSize();
+ }
+
+ /**
+ * Returns the number of bytes that are required to serialize this record.
+ *
+ * @return Number of bytes
+ */
+ public int getRecordSize()
+ {
+ return 8 + 8;
+ }
+
+ public short getRecordId()
+ {
+ return RECORD_ID;
+ }
+
+ /**
+ * The short name for this record
+ */
+ public String getRecordName()
+ {
+ return "Dg";
+ }
+
+ /**
+ * Returns the string representation of this record.
+ */
+ public String toString()
+ {
+ String nl = System.getProperty("line.separator");
+
+// String extraData;
+// ByteArrayOutputStream b = new ByteArrayOutputStream();
+// try
+// {
+// HexDump.dump(this.remainingData, 0, b, 0);
+// extraData = b.toString();
+// }
+// catch ( Exception e )
+// {
+// extraData = "error";
+// }
+ return getClass().getName() + ":" + nl +
+ " RecordId: 0x" + HexDump.toHex(RECORD_ID) + nl +
+ " Options: 0x" + HexDump.toHex(getOptions()) + nl +
+ " NumShapes: " + field_1_numShapes + nl +
+ " LastMSOSPID: " + field_2_lastMSOSPID + nl;
+
+ }
+
+ /**
+ * The number of shapes in this drawing group.
+ */
+ public int getNumShapes()
+ {
+ return field_1_numShapes;
+ }
+
+ /**
+ * The number of shapes in this drawing group.
+ */
+ public void setNumShapes( int field_1_numShapes )
+ {
+ this.field_1_numShapes = field_1_numShapes;
+ }
+
+ /**
+ * The last shape id used in this drawing group.
+ */
+ public int getLastMSOSPID()
+ {
+ return field_2_lastMSOSPID;
+ }
+
+ /**
+ * The last shape id used in this drawing group.
+ */
+ public void setLastMSOSPID( int field_2_lastMSOSPID )
+ {
+ this.field_2_lastMSOSPID = field_2_lastMSOSPID;
+ }
+
+ /**
+ * Gets the drawing group id for this record. This is encoded in the
+ * instance part of the option record.
+ *
+ * @return a drawing group id.
+ */
+ public short getDrawingGroupId()
+ {
+ return (short) ( getOptions() >> 4 );
+ }
+
+ public void incrementShapeCount()
+ {
+ this.field_1_numShapes++;
+ }
+}
diff --git a/src/java/org/apache/poi/ddf/EscherDggRecord.java b/src/java/org/apache/poi/ddf/EscherDggRecord.java
new file mode 100644
index 000000000..1adcf82fc
--- /dev/null
+++ b/src/java/org/apache/poi/ddf/EscherDggRecord.java
@@ -0,0 +1,241 @@
+package org.apache.poi.ddf;
+
+import org.apache.poi.util.HexDump;
+import org.apache.poi.util.LittleEndian;
+import org.apache.poi.hssf.record.RecordFormatException;
+
+import java.lang.reflect.Array;
+import java.util.*;
+
+/**
+ * This record defines the drawing groups used for a particular sheet.
+ */
+public class EscherDggRecord
+ extends EscherRecord
+{
+ public static final short RECORD_ID = (short) 0xF006;
+ public static final String RECORD_DESCRIPTION = "MsofbtDgg";
+
+ private int field_1_shapeIdMax;
+// private int field_2_numIdClusters; // for some reason the number of clusters is actually the real number + 1
+ private int field_3_numShapesSaved;
+ private int field_4_drawingsSaved;
+ private FileIdCluster[] field_5_fileIdClusters;
+
+ public static class FileIdCluster
+ {
+ public FileIdCluster( int drawingGroupId, int numShapeIdsUsed )
+ {
+ this.field_1_drawingGroupId = drawingGroupId;
+ this.field_2_numShapeIdsUsed = numShapeIdsUsed;
+ }
+
+ private int field_1_drawingGroupId;
+ private int field_2_numShapeIdsUsed;
+
+ public int getDrawingGroupId()
+ {
+ return field_1_drawingGroupId;
+ }
+
+ public int getNumShapeIdsUsed()
+ {
+ return field_2_numShapeIdsUsed;
+ }
+
+ public void incrementShapeId( )
+ {
+ this.field_2_numShapeIdsUsed++;
+ }
+ }
+
+ /**
+ * This method deserializes the record from a byte array.
+ *
+ * @param data The byte array containing the escher record information
+ * @param offset The starting offset into data
.
+ * @param recordFactory May be null since this is not a container record.
+ * @return The number of bytes read from the byte array.
+ */
+ public int fillFields( byte[] data, int offset, EscherRecordFactory recordFactory )
+ {
+ int bytesRemaining = readHeader( data, offset );
+ int pos = offset + 8;
+ int size = 0;
+ field_1_shapeIdMax = LittleEndian.getInt( data, pos + size );size+=4;
+ int field_2_numIdClusters = LittleEndian.getInt( data, pos + size );size+=4;
+ field_3_numShapesSaved = LittleEndian.getInt( data, pos + size );size+=4;
+ field_4_drawingsSaved = LittleEndian.getInt( data, pos + size );size+=4;
+ field_5_fileIdClusters = new FileIdCluster[field_2_numIdClusters-1];
+ for (int i = 0; i < field_2_numIdClusters-1; i++)
+ {
+ field_5_fileIdClusters[i] = new FileIdCluster(LittleEndian.getInt( data, pos + size ), LittleEndian.getInt( data, pos + size + 4 ));
+ size += 8;
+ }
+ bytesRemaining -= size;
+ if (bytesRemaining != 0)
+ throw new RecordFormatException("Expecting no remaining data but got " + bytesRemaining + " byte(s).");
+ return 8 + size + bytesRemaining;
+ }
+
+ /**
+ * This method serializes this escher record into a byte array.
+ *
+ * @param offset The offset into data
to start writing the record data to.
+ * @param data The byte array to serialize to.
+ * @param listener A listener to retrieve start and end callbacks. Use a NullEscherSerailizationListener
to ignore these events.
+ * @return The number of bytes written.
+ *
+ * @see NullEscherSerializationListener
+ */
+ public int serialize( int offset, byte[] data, EscherSerializationListener listener )
+ {
+ listener.beforeRecordSerialize( offset, getRecordId(), this );
+
+ int pos = offset;
+ LittleEndian.putShort( data, pos, getOptions() ); pos += 2;
+ LittleEndian.putShort( data, pos, getRecordId() ); pos += 2;
+ int remainingBytes = getRecordSize() - 8;
+ LittleEndian.putInt( data, pos, remainingBytes ); pos += 4;
+ LittleEndian.putInt( data, pos, field_1_shapeIdMax ); pos += 4;
+ LittleEndian.putInt( data, pos, getNumIdClusters() ); pos += 4;
+ LittleEndian.putInt( data, pos, field_3_numShapesSaved ); pos += 4;
+ LittleEndian.putInt( data, pos, field_4_drawingsSaved ); pos += 4;
+ for ( int i = 0; i < field_5_fileIdClusters.length; i++ )
+ {
+ LittleEndian.putInt( data, pos, field_5_fileIdClusters[i].field_1_drawingGroupId ); pos += 4;
+ LittleEndian.putInt( data, pos, field_5_fileIdClusters[i].field_2_numShapeIdsUsed ); pos += 4;
+ }
+
+ listener.afterRecordSerialize( pos, getRecordId(), getRecordSize(), this );
+ return getRecordSize();
+ }
+
+ /**
+ * Returns the number of bytes that are required to serialize this record.
+ *
+ * @return Number of bytes
+ */
+ public int getRecordSize()
+ {
+ return 8 + 16 + (8 * field_5_fileIdClusters.length);
+ }
+
+ public short getRecordId()
+ {
+ return RECORD_ID;
+ }
+
+ /**
+ * The short name for this record
+ */
+ public String getRecordName()
+ {
+ return "Dgg";
+ }
+
+ public String toString()
+ {
+ String nl = System.getProperty("line.separator");
+
+// String extraData;
+// ByteArrayOutputStream b = new ByteArrayOutputStream();
+// try
+// {
+// HexDump.dump(this.remainingData, 0, b, 0);
+// extraData = b.toString();
+// }
+// catch ( Exception e )
+// {
+// extraData = "error";
+// }
+ StringBuffer field_5_string = new StringBuffer();
+ for ( int i = 0; i < field_5_fileIdClusters.length; i++ )
+ {
+ field_5_string.append(" DrawingGroupId").append(i+1).append(": ");
+ field_5_string.append(field_5_fileIdClusters[i].field_1_drawingGroupId);
+ field_5_string.append(nl);
+ field_5_string.append(" NumShapeIdsUsed").append(i+1).append(": ");
+ field_5_string.append(field_5_fileIdClusters[i].field_2_numShapeIdsUsed);
+ field_5_string.append(nl);
+ }
+ return getClass().getName() + ":" + nl +
+ " RecordId: 0x" + HexDump.toHex(RECORD_ID) + nl +
+ " Options: 0x" + HexDump.toHex(getOptions()) + nl +
+ " ShapeIdMax: " + field_1_shapeIdMax + nl +
+ " NumIdClusters: " + getNumIdClusters() + nl +
+ " NumShapesSaved: " + field_3_numShapesSaved + nl +
+ " DrawingsSaved: " + field_4_drawingsSaved + nl +
+ "" + field_5_string.toString();
+
+ }
+
+ public int getShapeIdMax()
+ {
+ return field_1_shapeIdMax;
+ }
+
+ /**
+ * The maximum is actually the next available. shape id.
+ */
+ public void setShapeIdMax( int field_1_shapeIdMax )
+ {
+ this.field_1_shapeIdMax = field_1_shapeIdMax;
+ }
+
+ public int getNumIdClusters()
+ {
+ return field_5_fileIdClusters.length + 1;
+ }
+
+ public int getNumShapesSaved()
+ {
+ return field_3_numShapesSaved;
+ }
+
+ public void setNumShapesSaved( int field_3_numShapesSaved )
+ {
+ this.field_3_numShapesSaved = field_3_numShapesSaved;
+ }
+
+ public int getDrawingsSaved()
+ {
+ return field_4_drawingsSaved;
+ }
+
+ public void setDrawingsSaved( int field_4_drawingsSaved )
+ {
+ this.field_4_drawingsSaved = field_4_drawingsSaved;
+ }
+
+ public FileIdCluster[] getFileIdClusters()
+ {
+ return field_5_fileIdClusters;
+ }
+
+ public void setFileIdClusters( FileIdCluster[] field_5_fileIdClusters )
+ {
+ this.field_5_fileIdClusters = field_5_fileIdClusters;
+ }
+
+ public void addCluster( int dgId, int numShapedUsed )
+ {
+ List clusters = new ArrayList(Arrays.asList(field_5_fileIdClusters));
+ clusters.add(new FileIdCluster(dgId, numShapedUsed));
+ Collections.sort(clusters, new Comparator()
+ {
+ public int compare( Object o1, Object o2 )
+ {
+ FileIdCluster f1 = (FileIdCluster) o1;
+ FileIdCluster f2 = (FileIdCluster) o2;
+ if (f1.getDrawingGroupId() == f2.getDrawingGroupId())
+ return 0;
+ if (f1.getDrawingGroupId() < f2.getDrawingGroupId())
+ return -1;
+ else
+ return +1;
+ }
+ } );
+ field_5_fileIdClusters = (FileIdCluster[]) clusters.toArray( new FileIdCluster[clusters.size()] );
+ }
+}
diff --git a/src/java/org/apache/poi/ddf/EscherDump.java b/src/java/org/apache/poi/ddf/EscherDump.java
new file mode 100644
index 000000000..793048696
--- /dev/null
+++ b/src/java/org/apache/poi/ddf/EscherDump.java
@@ -0,0 +1,990 @@
+/* ====================================================================
+ * The Apache Software License, Version 1.1
+ *
+ * Copyright (c) 2003 The Apache Software Foundation. All rights
+ * reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ *
+ * 3. The end-user documentation included with the redistribution,
+ * if any, must include the following acknowledgment:
+ * "This product includes software developed by the
+ * Apache Software Foundation (http://www.apache.org/)."
+ * Alternately, this acknowledgment may appear in the software itself,
+ * if and wherever such third-party acknowledgments normally appear.
+ *
+ * 4. The names "Apache" and "Apache Software Foundation" and
+ * "Apache POI" must not be used to endorse or promote products
+ * derived from this software without prior written permission. For
+ * written permission, please contact apache@apache.org.
+ *
+ * 5. Products derived from this software may not be called "Apache",
+ * "Apache POI", nor may "Apache" appear in their name, without
+ * prior written permission of the Apache Software Foundation.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
+ * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation. For more
+ * information on the Apache Software Foundation, please see
+ * data
.
+ * @param recordFactory May be null since this is not a container record.
+ * @return The number of bytes read from the byte array.
+ */
+ public int fillFields( byte[] data, int offset, EscherRecordFactory recordFactory )
+ {
+ int bytesRemaining = readHeader( data, offset );
+ int pos = offset + 8;
+
+ EscherPropertyFactory f = new EscherPropertyFactory();
+ properties = f.createProperties( data, pos, getInstance() );
+ return bytesRemaining + 8;
+ }
+
+ /**
+ * This method serializes this escher record into a byte array.
+ *
+ * @param offset The offset into data
to start writing the record data to.
+ * @param data The byte array to serialize to.
+ * @param listener A listener to retrieve start and end callbacks. Use a NullEscherSerailizationListener
to ignore these events.
+ *
+ * @return The number of bytes written.
+ * @see NullEscherSerializationListener
+ */
+ public int serialize( int offset, byte[] data, EscherSerializationListener listener )
+ {
+ listener.beforeRecordSerialize( offset, getRecordId(), this );
+
+ LittleEndian.putShort( data, offset, getOptions() );
+ LittleEndian.putShort( data, offset + 2, getRecordId() );
+ LittleEndian.putInt( data, offset + 4, getPropertiesSize() );
+ int pos = offset + 8;
+ for ( Iterator iterator = properties.iterator(); iterator.hasNext(); )
+ {
+ EscherProperty escherProperty = (EscherProperty) iterator.next();
+ pos += escherProperty.serializeSimplePart( data, pos );
+ }
+ for ( Iterator iterator = properties.iterator(); iterator.hasNext(); )
+ {
+ EscherProperty escherProperty = (EscherProperty) iterator.next();
+ pos += escherProperty.serializeComplexPart( data, pos );
+ }
+ listener.afterRecordSerialize( pos, getRecordId(), pos - offset, this );
+ return pos - offset;
+ }
+
+ /**
+ * Returns the number of bytes that are required to serialize this record.
+ *
+ * @return Number of bytes
+ */
+ public int getRecordSize()
+ {
+ return 8 + getPropertiesSize();
+ }
+
+ /**
+ * Automatically recalculate the correct option
+ */
+ public short getOptions()
+ {
+ setOptions( (short) ( ( properties.size() << 4 ) | 0x3 ) );
+ return super.getOptions();
+ }
+
+ /**
+ * The short name for this record
+ */
+ public String getRecordName()
+ {
+ return "Opt";
+ }
+
+ private int getPropertiesSize()
+ {
+ int totalSize = 0;
+ for ( Iterator iterator = properties.iterator(); iterator.hasNext(); )
+ {
+ EscherProperty escherProperty = (EscherProperty) iterator.next();
+ totalSize += escherProperty.getPropertySize();
+ }
+ return totalSize;
+ }
+
+ /**
+ * Retrieve the string representation of this record.
+ */
+ public String toString()
+ {
+ String nl = System.getProperty( "line.separator" );
+ StringBuffer propertiesBuf = new StringBuffer();
+ for ( Iterator iterator = properties.iterator(); iterator.hasNext(); )
+ propertiesBuf.append( " "
+ + iterator.next().toString()
+ + nl );
+
+ return "org.apache.poi.ddf.EscherOptRecord:" + nl +
+ " isContainer: " + isContainerRecord() + nl +
+ " options: 0x" + HexDump.toHex( getOptions() ) + nl +
+ " recordId: 0x" + HexDump.toHex( getRecordId() ) + nl +
+ " numchildren: " + getChildRecords().size() + nl +
+ " properties:" + nl +
+ propertiesBuf.toString();
+ }
+
+ /**
+ * The list of properties stored by this record.
+ */
+ public List getEscherProperties()
+ {
+ return properties;
+ }
+
+ /**
+ * The list of properties stored by this record.
+ */
+ public EscherProperty getEscherProperty( int index )
+ {
+ return (EscherProperty) properties.get( index );
+ }
+
+ /**
+ * Add a property to this record.
+ */
+ public void addEscherProperty( EscherProperty prop )
+ {
+ properties.add( prop );
+ }
+
+ /**
+ * Records should be sorted by property number before being stored.
+ */
+ public void sortProperties()
+ {
+ Collections.sort( properties, new Comparator()
+ {
+ public int compare( Object o1, Object o2 )
+ {
+ EscherProperty p1 = (EscherProperty) o1;
+ EscherProperty p2 = (EscherProperty) o2;
+ return new Short( p1.getPropertyNumber() ).compareTo( new Short( p2.getPropertyNumber() ) );
+ }
+ } );
+ }
+
+
+}
diff --git a/src/java/org/apache/poi/ddf/EscherProperties.java b/src/java/org/apache/poi/ddf/EscherProperties.java
new file mode 100644
index 000000000..ea1c8745c
--- /dev/null
+++ b/src/java/org/apache/poi/ddf/EscherProperties.java
@@ -0,0 +1,607 @@
+package org.apache.poi.ddf;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Provides a list of all known escher properties including the description and
+ * type.
+ *
+ * @author Glen Stampoultzis (glens at apache.org)
+ */
+public class EscherProperties
+{
+
+ // Property constants
+ public static final short TRANSFORM__ROTATION = 4;
+ public static final short PROTECTION__LOCKROTATION = 119;
+ public static final short PROTECTION__LOCKASPECTRATIO = 120;
+ public static final short PROTECTION__LOCKPOSITION = 121;
+ public static final short PROTECTION__LOCKAGAINSTSELECT = 122;
+ public static final short PROTECTION__LOCKCROPPING = 123;
+ public static final short PROTECTION__LOCKVERTICES = 124;
+ public static final short PROTECTION__LOCKTEXT = 125;
+ public static final short PROTECTION__LOCKADJUSTHANDLES = 126;
+ public static final short PROTECTION__LOCKAGAINSTGROUPING = 127;
+ public static final short TEXT__TEXTID = 128;
+ public static final short TEXT__TEXTLEFT = 129;
+ public static final short TEXT__TEXTTOP = 130;
+ public static final short TEXT__TEXTRIGHT = 131;
+ public static final short TEXT__TEXTBOTTOM = 132;
+ public static final short TEXT__WRAPTEXT = 133;
+ public static final short TEXT__SCALETEXT = 134;
+ public static final short TEXT__ANCHORTEXT = 135;
+ public static final short TEXT__TEXTFLOW = 136;
+ public static final short TEXT__FONTROTATION = 137;
+ public static final short TEXT__IDOFNEXTSHAPE = 138;
+ public static final short TEXT__BIDIR = 139;
+ public static final short TEXT__SINGLECLICKSELECTS = 187;
+ public static final short TEXT__USEHOSTMARGINS = 188;
+ public static final short TEXT__ROTATETEXTWITHSHAPE = 189;
+ public static final short TEXT__SIZESHAPETOFITTEXT = 190;
+ public static final short TEXT__SIZE_TEXT_TO_FIT_SHAPE = 191 ;
+ public static final short GEOTEXT__UNICODE = 192;
+ public static final short GEOTEXT__RTFTEXT = 193;
+ public static final short GEOTEXT__ALIGNMENTONCURVE = 194;
+ public static final short GEOTEXT__DEFAULTPOINTSIZE = 195;
+ public static final short GEOTEXT__TEXTSPACING = 196;
+ public static final short GEOTEXT__FONTFAMILYNAME = 197;
+ public static final short GEOTEXT__REVERSEROWORDER = 240;
+ public static final short GEOTEXT__HASTEXTEFFECT = 241;
+ public static final short GEOTEXT__ROTATECHARACTERS = 242;
+ public static final short GEOTEXT__KERNCHARACTERS = 243;
+ public static final short GEOTEXT__TIGHTORTRACK = 244;
+ public static final short GEOTEXT__STRETCHTOFITSHAPE = 245;
+ public static final short GEOTEXT__CHARBOUNDINGBOX = 246;
+ public static final short GEOTEXT__SCALETEXTONPATH = 247;
+ public static final short GEOTEXT__STRETCHCHARHEIGHT = 248;
+ public static final short GEOTEXT__NOMEASUREALONGPATH = 249;
+ public static final short GEOTEXT__BOLDFONT = 250;
+ public static final short GEOTEXT__ITALICFONT = 251;
+ public static final short GEOTEXT__UNDERLINEFONT = 252;
+ public static final short GEOTEXT__SHADOWFONT = 253;
+ public static final short GEOTEXT__SMALLCAPSFONT = 254;
+ public static final short GEOTEXT__STRIKETHROUGHFONT = 255;
+ public static final short BLIP__CROPFROMTOP = 256;
+ public static final short BLIP__CROPFROMBOTTOM = 257;
+ public static final short BLIP__CROPFROMLEFT = 258;
+ public static final short BLIP__CROPFROMRIGHT = 259;
+ public static final short BLIP__BLIPTODISPLAY = 260;
+ public static final short BLIP__BLIPFILENAME = 261;
+ public static final short BLIP__BLIPFLAGS = 262;
+ public static final short BLIP__TRANSPARENTCOLOR = 263;
+ public static final short BLIP__CONTRASTSETTING = 264;
+ public static final short BLIP__BRIGHTNESSSETTING = 265;
+ public static final short BLIP__GAMMA = 266;
+ public static final short BLIP__PICTUREID = 267;
+ public static final short BLIP__DOUBLEMOD = 268;
+ public static final short BLIP__PICTUREFILLMOD = 269;
+ public static final short BLIP__PICTURELINE = 270;
+ public static final short BLIP__PRINTBLIP = 271;
+ public static final short BLIP__PRINTBLIPFILENAME = 272;
+ public static final short BLIP__PRINTFLAGS = 273;
+ public static final short BLIP__NOHITTESTPICTURE = 316;
+ public static final short BLIP__PICTUREGRAY = 317;
+ public static final short BLIP__PICTUREBILEVEL = 318;
+ public static final short BLIP__PICTUREACTIVE = 319;
+ public static final short GEOMETRY__LEFT = 320;
+ public static final short GEOMETRY__TOP = 321;
+ public static final short GEOMETRY__RIGHT = 322;
+ public static final short GEOMETRY__BOTTOM = 323;
+ public static final short GEOMETRY__SHAPEPATH = 324;
+ public static final short GEOMETRY__VERTICES = 325;
+ public static final short GEOMETRY__SEGMENTINFO = 326;
+ public static final short GEOMETRY__ADJUSTVALUE = 327;
+ public static final short GEOMETRY__ADJUST2VALUE = 328;
+ public static final short GEOMETRY__ADJUST3VALUE = 329;
+ public static final short GEOMETRY__ADJUST4VALUE = 330;
+ public static final short GEOMETRY__ADJUST5VALUE = 331;
+ public static final short GEOMETRY__ADJUST6VALUE = 332;
+ public static final short GEOMETRY__ADJUST7VALUE = 333;
+ public static final short GEOMETRY__ADJUST8VALUE = 334;
+ public static final short GEOMETRY__ADJUST9VALUE = 335;
+ public static final short GEOMETRY__ADJUST10VALUE = 336;
+ public static final short GEOMETRY__SHADOWok = 378;
+ public static final short GEOMETRY__3DOK = 379;
+ public static final short GEOMETRY__LINEOK = 380;
+ public static final short GEOMETRY__GEOTEXTOK = 381;
+ public static final short GEOMETRY__FILLSHADESHAPEOK = 382;
+ public static final short GEOMETRY__FILLOK = 383;
+ public static final short FILL__FILLTYPE = 384;
+ public static final short FILL__FILLCOLOR = 385 ;
+ public static final short FILL__FILLOPACITY = 386;
+ public static final short FILL__FILLBACKCOLOR = 387;
+ public static final short FILL__BACKOPACITY = 388;
+ public static final short FILL__CRMOD = 389;
+ public static final short FILL__PATTERNTEXTURE = 390;
+ public static final short FILL__BLIPFILENAME = 391;
+ public static final short FILL__BLIPFLAGS = 392;
+ public static final short FILL__WIDTH = 393;
+ public static final short FILL__HEIGHT = 394;
+ public static final short FILL__ANGLE = 395;
+ public static final short FILL__FOCUS = 396;
+ public static final short FILL__TOLEFT = 397;
+ public static final short FILL__TOTOP = 398;
+ public static final short FILL__TORIGHT = 399;
+ public static final short FILL__TOBOTTOM = 400;
+ public static final short FILL__RECTLEFT = 401;
+ public static final short FILL__RECTTOP = 402;
+ public static final short FILL__RECTRIGHT = 403;
+ public static final short FILL__RECTBOTTOM = 404;
+ public static final short FILL__DZTYPE = 405;
+ public static final short FILL__SHADEPRESET = 406;
+ public static final short FILL__SHADECOLORS = 407;
+ public static final short FILL__ORIGINX = 408;
+ public static final short FILL__ORIGINY = 409;
+ public static final short FILL__SHAPEORIGINX = 410;
+ public static final short FILL__SHAPEORIGINY = 411;
+ public static final short FILL__SHADETYPE = 412;
+ public static final short FILL__FILLED = 443;
+ public static final short FILL__HITTESTFILL = 444;
+ public static final short FILL__SHAPE = 445;
+ public static final short FILL__USERECT = 446;
+ public static final short FILL__NOFILLHITTEST = 447;
+ public static final short LINESTYLE__COLOR = 448 ;
+ public static final short LINESTYLE__OPACITY = 449;
+ public static final short LINESTYLE__BACKCOLOR = 450;
+ public static final short LINESTYLE__CRMOD = 451;
+ public static final short LINESTYLE__LINETYPE = 452;
+ public static final short LINESTYLE__FILLBLIP = 453;
+ public static final short LINESTYLE__FILLBLIPNAME = 454;
+ public static final short LINESTYLE__FILLBLIPFLAGS = 455;
+ public static final short LINESTYLE__FILLWIDTH = 456;
+ public static final short LINESTYLE__FILLHEIGHT = 457;
+ public static final short LINESTYLE__FILLDZTYPE = 458;
+ public static final short LINESTYLE__LINEWIDTH = 459;
+ public static final short LINESTYLE__LINEMITERLIMIT = 460;
+ public static final short LINESTYLE__LINESTYLE = 461;
+ public static final short LINESTYLE__LINEDASHING = 462;
+ public static final short LINESTYLE__LINEDASHSTYLE = 463;
+ public static final short LINESTYLE__LINESTARTARROWHEAD = 464;
+ public static final short LINESTYLE__LINEENDARROWHEAD = 465;
+ public static final short LINESTYLE__LINESTARTARROWWIDTH = 466;
+ public static final short LINESTYLE__LINEESTARTARROWLENGTH = 467;
+ public static final short LINESTYLE__LINEENDARROWWIDTH = 468;
+ public static final short LINESTYLE__LINEENDARROWLENGTH = 469;
+ public static final short LINESTYLE__LINEJOINSTYLE = 470;
+ public static final short LINESTYLE__LINEENDCAPSTYLE = 471;
+ public static final short LINESTYLE__ARROWHEADSOK = 507;
+ public static final short LINESTYLE__ANYLINE = 508;
+ public static final short LINESTYLE__HITLINETEST = 509;
+ public static final short LINESTYLE__LINEFILLSHAPE = 510;
+ public static final short LINESTYLE__NOLINEDRAWDASH = 511;
+ public static final short SHADOWSTYLE__TYPE = 512;
+ public static final short SHADOWSTYLE__COLOR = 513;
+ public static final short SHADOWSTYLE__HIGHLIGHT = 514;
+ public static final short SHADOWSTYLE__CRMOD = 515;
+ public static final short SHADOWSTYLE__OPACITY = 516;
+ public static final short SHADOWSTYLE__OFFSETX = 517;
+ public static final short SHADOWSTYLE__OFFSETY = 518;
+ public static final short SHADOWSTYLE__SECONDOFFSETX = 519;
+ public static final short SHADOWSTYLE__SECONDOFFSETY = 520;
+ public static final short SHADOWSTYLE__SCALEXTOX = 521;
+ public static final short SHADOWSTYLE__SCALEYTOX = 522;
+ public static final short SHADOWSTYLE__SCALEXTOY = 523;
+ public static final short SHADOWSTYLE__SCALEYTOY = 524;
+ public static final short SHADOWSTYLE__PERSPECTIVEX = 525;
+ public static final short SHADOWSTYLE__PERSPECTIVEY = 526;
+ public static final short SHADOWSTYLE__WEIGHT = 527;
+ public static final short SHADOWSTYLE__ORIGINX = 528;
+ public static final short SHADOWSTYLE__ORIGINY = 529;
+ public static final short SHADOWSTYLE__SHADOW = 574;
+ public static final short SHADOWSTYLE__SHADOWOBSURED = 575;
+ public static final short PERSPECTIVE__TYPE = 576;
+ public static final short PERSPECTIVE__OFFSETX = 577;
+ public static final short PERSPECTIVE__OFFSETY = 578;
+ public static final short PERSPECTIVE__SCALEXTOX = 579;
+ public static final short PERSPECTIVE__SCALEYTOX = 580;
+ public static final short PERSPECTIVE__SCALEXTOY = 581;
+ public static final short PERSPECTIVE__SCALEYTOY = 582;
+ public static final short PERSPECTIVE__PERSPECTIVEX = 583;
+ public static final short PERSPECTIVE__PERSPECTIVEY = 584;
+ public static final short PERSPECTIVE__WEIGHT = 585;
+ public static final short PERSPECTIVE__ORIGINX = 586;
+ public static final short PERSPECTIVE__ORIGINY = 587;
+ public static final short PERSPECTIVE__PERSPECTIVEON = 639;
+ public static final short THREED__SPECULARAMOUNT = 640;
+ public static final short THREED__DIFFUSEAMOUNT = 661;
+ public static final short THREED__SHININESS = 662;
+ public static final short THREED__EDGETHICKNESS = 663;
+ public static final short THREED__EXTRUDEFORWARD = 664;
+ public static final short THREED__EXTRUDEBACKWARD = 665;
+ public static final short THREED__EXTRUDEPLANE = 666;
+ public static final short THREED__EXTRUSIONCOLOR = 667;
+ public static final short THREED__CRMOD = 648;
+ public static final short THREED__3DEFFECT = 700;
+ public static final short THREED__METALLIC = 701;
+ public static final short THREED__USEEXTRUSIONCOLOR = 702;
+ public static final short THREED__LIGHTFACE = 703;
+ public static final short THREEDSTYLE__YROTATIONANGLE = 704;
+ public static final short THREEDSTYLE__XROTATIONANGLE = 705;
+ public static final short THREEDSTYLE__ROTATIONAXISX = 706;
+ public static final short THREEDSTYLE__ROTATIONAXISY = 707;
+ public static final short THREEDSTYLE__ROTATIONAXISZ = 708;
+ public static final short THREEDSTYLE__ROTATIONANGLE = 709;
+ public static final short THREEDSTYLE__ROTATIONCENTERX = 710;
+ public static final short THREEDSTYLE__ROTATIONCENTERY = 711;
+ public static final short THREEDSTYLE__ROTATIONCENTERZ = 712;
+ public static final short THREEDSTYLE__RENDERMODE = 713;
+ public static final short THREEDSTYLE__TOLERANCE = 714;
+ public static final short THREEDSTYLE__XVIEWPOINT = 715;
+ public static final short THREEDSTYLE__YVIEWPOINT = 716;
+ public static final short THREEDSTYLE__ZVIEWPOINT = 717;
+ public static final short THREEDSTYLE__ORIGINX = 718;
+ public static final short THREEDSTYLE__ORIGINY = 719;
+ public static final short THREEDSTYLE__SKEWANGLE = 720;
+ public static final short THREEDSTYLE__SKEWAMOUNT = 721;
+ public static final short THREEDSTYLE__AMBIENTINTENSITY = 722;
+ public static final short THREEDSTYLE__KEYX = 723;
+ public static final short THREEDSTYLE__KEYY = 724;
+ public static final short THREEDSTYLE__KEYZ = 725;
+ public static final short THREEDSTYLE__KEYINTENSITY = 726;
+ public static final short THREEDSTYLE__FILLX = 727;
+ public static final short THREEDSTYLE__FILLY = 728;
+ public static final short THREEDSTYLE__FILLZ = 729;
+ public static final short THREEDSTYLE__FILLINTENSITY = 730;
+ public static final short THREEDSTYLE__CONSTRAINROTATION = 763;
+ public static final short THREEDSTYLE__ROTATIONCENTERAUTO = 764;
+ public static final short THREEDSTYLE__PARALLEL = 765;
+ public static final short THREEDSTYLE__KEYHARSH = 766;
+ public static final short THREEDSTYLE__FILLHARSH = 767;
+ public static final short SHAPE__MASTER = 769;
+ public static final short SHAPE__CONNECTORSTYLE = 771;
+ public static final short SHAPE__BLACKANDWHITESETTINGS = 772;
+ public static final short SHAPE__WMODEPUREBW = 773;
+ public static final short SHAPE__WMODEBW = 774;
+ public static final short SHAPE__OLEICON = 826;
+ public static final short SHAPE__PREFERRELATIVERESIZE = 827;
+ public static final short SHAPE__LOCKSHAPETYPE = 828;
+ public static final short SHAPE__DELETEATTACHEDOBJECT = 830;
+ public static final short SHAPE__BACKGROUNDSHAPE = 831;
+ public static final short CALLOUT__CALLOUTTYPE = 832;
+ public static final short CALLOUT__XYCALLOUTGAP = 833;
+ public static final short CALLOUT__CALLOUTANGLE = 834;
+ public static final short CALLOUT__CALLOUTDROPTYPE = 835;
+ public static final short CALLOUT__CALLOUTDROPSPECIFIED = 836;
+ public static final short CALLOUT__CALLOUTLENGTHSPECIFIED = 837;
+ public static final short CALLOUT__ISCALLOUT = 889;
+ public static final short CALLOUT__CALLOUTACCENTBAR = 890;
+ public static final short CALLOUT__CALLOUTTEXTBORDER = 891;
+ public static final short CALLOUT__CALLOUTMINUSX = 892;
+ public static final short CALLOUT__CALLOUTMINUSY = 893;
+ public static final short CALLOUT__DROPAUTO = 894;
+ public static final short CALLOUT__LENGTHSPECIFIED = 895;
+ public static final short GROUPSHAPE__SHAPENAME = 896;
+ public static final short GROUPSHAPE__DESCRIPTION = 897;
+ public static final short GROUPSHAPE__HYPERLINK = 898;
+ public static final short GROUPSHAPE__WRAPPOLYGONVERTICES = 899;
+ public static final short GROUPSHAPE__WRAPDISTLEFT = 900;
+ public static final short GROUPSHAPE__WRAPDISTTOP = 901;
+ public static final short GROUPSHAPE__WRAPDISTRIGHT = 902;
+ public static final short GROUPSHAPE__WRAPDISTBOTTOM = 903;
+ public static final short GROUPSHAPE__REGROUPID = 904;
+ public static final short GROUPSHAPE__EDITEDWRAP = 953;
+ public static final short GROUPSHAPE__BEHINDDOCUMENT = 954;
+ public static final short GROUPSHAPE__ONDBLCLICKNOTIFY = 955;
+ public static final short GROUPSHAPE__ISBUTTON = 956;
+ public static final short GROUPSHAPE__1DADJUSTMENT = 957;
+ public static final short GROUPSHAPE__HIDDEN = 958;
+ public static final short GROUPSHAPE__PRINT = 959;
+
+
+ private static Map properties;
+
+ private static void initProps()
+ {
+ if ( properties == null )
+ {
+ properties = new HashMap();
+ addProp( TRANSFORM__ROTATION, data( "transform.rotation" ) );
+ addProp( PROTECTION__LOCKROTATION , data( "protection.lockrotation" ) );
+ addProp( PROTECTION__LOCKASPECTRATIO , data( "protection.lockaspectratio" ) );
+ addProp( PROTECTION__LOCKPOSITION , data( "protection.lockposition" ) );
+ addProp( PROTECTION__LOCKAGAINSTSELECT , data( "protection.lockagainstselect" ) );
+ addProp( PROTECTION__LOCKCROPPING , data( "protection.lockcropping" ) );
+ addProp( PROTECTION__LOCKVERTICES , data( "protection.lockvertices" ) );
+ addProp( PROTECTION__LOCKTEXT , data( "protection.locktext" ) );
+ addProp( PROTECTION__LOCKADJUSTHANDLES , data( "protection.lockadjusthandles" ) );
+ addProp( PROTECTION__LOCKAGAINSTGROUPING , data( "protection.lockagainstgrouping", EscherPropertyMetaData.TYPE_BOOLEAN ) );
+ addProp( TEXT__TEXTID , data( "text.textid" ) );
+ addProp( TEXT__TEXTLEFT , data( "text.textleft" ) );
+ addProp( TEXT__TEXTTOP , data( "text.texttop" ) );
+ addProp( TEXT__TEXTRIGHT , data( "text.textright" ) );
+ addProp( TEXT__TEXTBOTTOM , data( "text.textbottom" ) );
+ addProp( TEXT__WRAPTEXT , data( "text.wraptext" ) );
+ addProp( TEXT__SCALETEXT , data( "text.scaletext" ) );
+ addProp( TEXT__ANCHORTEXT , data( "text.anchortext" ) );
+ addProp( TEXT__TEXTFLOW , data( "text.textflow" ) );
+ addProp( TEXT__FONTROTATION , data( "text.fontrotation" ) );
+ addProp( TEXT__IDOFNEXTSHAPE , data( "text.idofnextshape" ) );
+ addProp( TEXT__BIDIR , data( "text.bidir" ) );
+ addProp( TEXT__SINGLECLICKSELECTS , data( "text.singleclickselects" ) );
+ addProp( TEXT__USEHOSTMARGINS , data( "text.usehostmargins" ) );
+ addProp( TEXT__ROTATETEXTWITHSHAPE , data( "text.rotatetextwithshape" ) );
+ addProp( TEXT__SIZESHAPETOFITTEXT , data( "text.sizeshapetofittext" ) );
+ addProp( TEXT__SIZE_TEXT_TO_FIT_SHAPE, data( "text.sizetexttofitshape", EscherPropertyMetaData.TYPE_BOOLEAN ) );
+ addProp( GEOTEXT__UNICODE , data( "geotext.unicode" ) );
+ addProp( GEOTEXT__RTFTEXT , data( "geotext.rtftext" ) );
+ addProp( GEOTEXT__ALIGNMENTONCURVE , data( "geotext.alignmentoncurve" ) );
+ addProp( GEOTEXT__DEFAULTPOINTSIZE , data( "geotext.defaultpointsize" ) );
+ addProp( GEOTEXT__TEXTSPACING , data( "geotext.textspacing" ) );
+ addProp( GEOTEXT__FONTFAMILYNAME , data( "geotext.fontfamilyname" ) );
+ addProp( GEOTEXT__REVERSEROWORDER , data( "geotext.reverseroworder" ) );
+ addProp( GEOTEXT__HASTEXTEFFECT , data( "geotext.hastexteffect" ) );
+ addProp( GEOTEXT__ROTATECHARACTERS , data( "geotext.rotatecharacters" ) );
+ addProp( GEOTEXT__KERNCHARACTERS , data( "geotext.kerncharacters" ) );
+ addProp( GEOTEXT__TIGHTORTRACK , data( "geotext.tightortrack" ) );
+ addProp( GEOTEXT__STRETCHTOFITSHAPE , data( "geotext.stretchtofitshape" ) );
+ addProp( GEOTEXT__CHARBOUNDINGBOX , data( "geotext.charboundingbox" ) );
+ addProp( GEOTEXT__SCALETEXTONPATH , data( "geotext.scaletextonpath" ) );
+ addProp( GEOTEXT__STRETCHCHARHEIGHT , data( "geotext.stretchcharheight" ) );
+ addProp( GEOTEXT__NOMEASUREALONGPATH , data( "geotext.nomeasurealongpath" ) );
+ addProp( GEOTEXT__BOLDFONT , data( "geotext.boldfont" ) );
+ addProp( GEOTEXT__ITALICFONT , data( "geotext.italicfont" ) );
+ addProp( GEOTEXT__UNDERLINEFONT , data( "geotext.underlinefont" ) );
+ addProp( GEOTEXT__SHADOWFONT , data( "geotext.shadowfont" ) );
+ addProp( GEOTEXT__SMALLCAPSFONT , data( "geotext.smallcapsfont" ) );
+ addProp( GEOTEXT__STRIKETHROUGHFONT , data( "geotext.strikethroughfont" ) );
+ addProp( BLIP__CROPFROMTOP , data( "blip.cropfromtop" ) );
+ addProp( BLIP__CROPFROMBOTTOM , data( "blip.cropfrombottom" ) );
+ addProp( BLIP__CROPFROMLEFT , data( "blip.cropfromleft" ) );
+ addProp( BLIP__CROPFROMRIGHT , data( "blip.cropfromright" ) );
+ addProp( BLIP__BLIPTODISPLAY , data( "blip.bliptodisplay" ) );
+ addProp( BLIP__BLIPFILENAME , data( "blip.blipfilename" ) );
+ addProp( BLIP__BLIPFLAGS , data( "blip.blipflags" ) );
+ addProp( BLIP__TRANSPARENTCOLOR , data( "blip.transparentcolor" ) );
+ addProp( BLIP__CONTRASTSETTING , data( "blip.contrastsetting" ) );
+ addProp( BLIP__BRIGHTNESSSETTING , data( "blip.brightnesssetting" ) );
+ addProp( BLIP__GAMMA , data( "blip.gamma" ) );
+ addProp( BLIP__PICTUREID , data( "blip.pictureid" ) );
+ addProp( BLIP__DOUBLEMOD , data( "blip.doublemod" ) );
+ addProp( BLIP__PICTUREFILLMOD , data( "blip.picturefillmod" ) );
+ addProp( BLIP__PICTURELINE , data( "blip.pictureline" ) );
+ addProp( BLIP__PRINTBLIP , data( "blip.printblip" ) );
+ addProp( BLIP__PRINTBLIPFILENAME , data( "blip.printblipfilename" ) );
+ addProp( BLIP__PRINTFLAGS , data( "blip.printflags" ) );
+ addProp( BLIP__NOHITTESTPICTURE , data( "blip.nohittestpicture" ) );
+ addProp( BLIP__PICTUREGRAY , data( "blip.picturegray" ) );
+ addProp( BLIP__PICTUREBILEVEL , data( "blip.picturebilevel" ) );
+ addProp( BLIP__PICTUREACTIVE , data( "blip.pictureactive" ) );
+ addProp( GEOMETRY__LEFT , data( "geometry.left" ) );
+ addProp( GEOMETRY__TOP , data( "geometry.top" ) );
+ addProp( GEOMETRY__RIGHT , data( "geometry.right" ) );
+ addProp( GEOMETRY__BOTTOM , data( "geometry.bottom" ) );
+ addProp( GEOMETRY__SHAPEPATH , data( "geometry.shapepath", EscherPropertyMetaData.TYPE_SHAPEPATH ) );
+ addProp( GEOMETRY__VERTICES , data( "geometry.vertices" , EscherPropertyMetaData.TYPE_ARRAY ) );
+ addProp( GEOMETRY__SEGMENTINFO , data( "geometry.segmentinfo", EscherPropertyMetaData.TYPE_ARRAY ) );
+ addProp( GEOMETRY__ADJUSTVALUE , data( "geometry.adjustvalue" ) );
+ addProp( GEOMETRY__ADJUST2VALUE , data( "geometry.adjust2value" ) );
+ addProp( GEOMETRY__ADJUST3VALUE , data( "geometry.adjust3value" ) );
+ addProp( GEOMETRY__ADJUST4VALUE , data( "geometry.adjust4value" ) );
+ addProp( GEOMETRY__ADJUST5VALUE , data( "geometry.adjust5value" ) );
+ addProp( GEOMETRY__ADJUST6VALUE , data( "geometry.adjust6value" ) );
+ addProp( GEOMETRY__ADJUST7VALUE , data( "geometry.adjust7value" ) );
+ addProp( GEOMETRY__ADJUST8VALUE , data( "geometry.adjust8value" ) );
+ addProp( GEOMETRY__ADJUST9VALUE , data( "geometry.adjust9value" ) );
+ addProp( GEOMETRY__ADJUST10VALUE , data( "geometry.adjust10value" ) );
+ addProp( GEOMETRY__SHADOWok , data( "geometry.shadowOK" ) );
+ addProp( GEOMETRY__3DOK , data( "geometry.3dok" ) );
+ addProp( GEOMETRY__LINEOK , data( "geometry.lineok" ) );
+ addProp( GEOMETRY__GEOTEXTOK , data( "geometry.geotextok" ) );
+ addProp( GEOMETRY__FILLSHADESHAPEOK , data( "geometry.fillshadeshapeok" ) );
+ addProp( GEOMETRY__FILLOK , data( "geometry.fillok", EscherPropertyMetaData.TYPE_BOOLEAN ) );
+ addProp( FILL__FILLTYPE , data( "fill.filltype" ) );
+ addProp( FILL__FILLCOLOR, data( "fill.fillcolor", EscherPropertyMetaData.TYPE_RGB ) );
+ addProp( FILL__FILLOPACITY , data( "fill.fillopacity" ) );
+ addProp( FILL__FILLBACKCOLOR , data( "fill.fillbackcolor", EscherPropertyMetaData.TYPE_RGB ) );
+ addProp( FILL__BACKOPACITY , data( "fill.backopacity" ) );
+ addProp( FILL__CRMOD , data( "fill.crmod" ) );
+ addProp( FILL__PATTERNTEXTURE , data( "fill.patterntexture" ) );
+ addProp( FILL__BLIPFILENAME , data( "fill.blipfilename" ) );
+ addProp( FILL__BLIPFLAGS, data( "fill.blipflags" ) );
+ addProp( FILL__WIDTH , data( "fill.width" ) );
+ addProp( FILL__HEIGHT , data( "fill.height" ) );
+ addProp( FILL__ANGLE , data( "fill.angle" ) );
+ addProp( FILL__FOCUS , data( "fill.focus" ) );
+ addProp( FILL__TOLEFT , data( "fill.toleft" ) );
+ addProp( FILL__TOTOP , data( "fill.totop" ) );
+ addProp( FILL__TORIGHT , data( "fill.toright" ) );
+ addProp( FILL__TOBOTTOM , data( "fill.tobottom" ) );
+ addProp( FILL__RECTLEFT , data( "fill.rectleft" ) );
+ addProp( FILL__RECTTOP , data( "fill.recttop" ) );
+ addProp( FILL__RECTRIGHT , data( "fill.rectright" ) );
+ addProp( FILL__RECTBOTTOM , data( "fill.rectbottom" ) );
+ addProp( FILL__DZTYPE , data( "fill.dztype" ) );
+ addProp( FILL__SHADEPRESET , data( "fill.shadepreset" ) );
+ addProp( FILL__SHADECOLORS , data( "fill.shadecolors", EscherPropertyMetaData.TYPE_ARRAY ) );
+ addProp( FILL__ORIGINX , data( "fill.originx" ) );
+ addProp( FILL__ORIGINY , data( "fill.originy" ) );
+ addProp( FILL__SHAPEORIGINX , data( "fill.shapeoriginx" ) );
+ addProp( FILL__SHAPEORIGINY , data( "fill.shapeoriginy" ) );
+ addProp( FILL__SHADETYPE , data( "fill.shadetype" ) );
+ addProp( FILL__FILLED , data( "fill.filled" ) );
+ addProp( FILL__HITTESTFILL , data( "fill.hittestfill" ) );
+ addProp( FILL__SHAPE , data( "fill.shape" ) );
+ addProp( FILL__USERECT , data( "fill.userect" ) );
+ addProp( FILL__NOFILLHITTEST , data( "fill.nofillhittest", EscherPropertyMetaData.TYPE_BOOLEAN ) );
+ addProp( LINESTYLE__COLOR, data( "linestyle.color", EscherPropertyMetaData.TYPE_RGB ) );
+ addProp( LINESTYLE__OPACITY , data( "linestyle.opacity" ) );
+ addProp( LINESTYLE__BACKCOLOR , data( "linestyle.backcolor", EscherPropertyMetaData.TYPE_RGB ) );
+ addProp( LINESTYLE__CRMOD , data( "linestyle.crmod" ) );
+ addProp( LINESTYLE__LINETYPE , data( "linestyle.linetype" ) );
+ addProp( LINESTYLE__FILLBLIP , data( "linestyle.fillblip" ) );
+ addProp( LINESTYLE__FILLBLIPNAME , data( "linestyle.fillblipname" ) );
+ addProp( LINESTYLE__FILLBLIPFLAGS , data( "linestyle.fillblipflags" ) );
+ addProp( LINESTYLE__FILLWIDTH , data( "linestyle.fillwidth" ) );
+ addProp( LINESTYLE__FILLHEIGHT , data( "linestyle.fillheight" ) );
+ addProp( LINESTYLE__FILLDZTYPE , data( "linestyle.filldztype" ) );
+ addProp( LINESTYLE__LINEWIDTH , data( "linestyle.linewidth" ) );
+ addProp( LINESTYLE__LINEMITERLIMIT , data( "linestyle.linemiterlimit" ) );
+ addProp( LINESTYLE__LINESTYLE , data( "linestyle.linestyle" ) );
+ addProp( LINESTYLE__LINEDASHING , data( "linestyle.linedashing" ) );
+ addProp( LINESTYLE__LINEDASHSTYLE , data( "linestyle.linedashstyle", EscherPropertyMetaData.TYPE_ARRAY ) );
+ addProp( LINESTYLE__LINESTARTARROWHEAD , data( "linestyle.linestartarrowhead" ) );
+ addProp( LINESTYLE__LINEENDARROWHEAD , data( "linestyle.lineendarrowhead" ) );
+ addProp( LINESTYLE__LINESTARTARROWWIDTH , data( "linestyle.linestartarrowwidth" ) );
+ addProp( LINESTYLE__LINEESTARTARROWLENGTH , data( "linestyle.lineestartarrowlength" ) );
+ addProp( LINESTYLE__LINEENDARROWWIDTH , data( "linestyle.lineendarrowwidth" ) );
+ addProp( LINESTYLE__LINEENDARROWLENGTH , data( "linestyle.lineendarrowlength" ) );
+ addProp( LINESTYLE__LINEJOINSTYLE , data( "linestyle.linejoinstyle" ) );
+ addProp( LINESTYLE__LINEENDCAPSTYLE , data( "linestyle.lineendcapstyle" ) );
+ addProp( LINESTYLE__ARROWHEADSOK , data( "linestyle.arrowheadsok" ) );
+ addProp( LINESTYLE__ANYLINE , data( "linestyle.anyline" ) );
+ addProp( LINESTYLE__HITLINETEST , data( "linestyle.hitlinetest" ) );
+ addProp( LINESTYLE__LINEFILLSHAPE , data( "linestyle.linefillshape" ) );
+ addProp( LINESTYLE__NOLINEDRAWDASH , data( "linestyle.nolinedrawdash", EscherPropertyMetaData.TYPE_BOOLEAN ) );
+ addProp( SHADOWSTYLE__TYPE , data( "shadowstyle.type" ) );
+ addProp( SHADOWSTYLE__COLOR , data( "shadowstyle.color", EscherPropertyMetaData.TYPE_RGB ) );
+ addProp( SHADOWSTYLE__HIGHLIGHT , data( "shadowstyle.highlight" ) );
+ addProp( SHADOWSTYLE__CRMOD , data( "shadowstyle.crmod" ) );
+ addProp( SHADOWSTYLE__OPACITY , data( "shadowstyle.opacity" ) );
+ addProp( SHADOWSTYLE__OFFSETX , data( "shadowstyle.offsetx" ) );
+ addProp( SHADOWSTYLE__OFFSETY , data( "shadowstyle.offsety" ) );
+ addProp( SHADOWSTYLE__SECONDOFFSETX , data( "shadowstyle.secondoffsetx" ) );
+ addProp( SHADOWSTYLE__SECONDOFFSETY , data( "shadowstyle.secondoffsety" ) );
+ addProp( SHADOWSTYLE__SCALEXTOX , data( "shadowstyle.scalextox" ) );
+ addProp( SHADOWSTYLE__SCALEYTOX , data( "shadowstyle.scaleytox" ) );
+ addProp( SHADOWSTYLE__SCALEXTOY , data( "shadowstyle.scalextoy" ) );
+ addProp( SHADOWSTYLE__SCALEYTOY , data( "shadowstyle.scaleytoy" ) );
+ addProp( SHADOWSTYLE__PERSPECTIVEX , data( "shadowstyle.perspectivex" ) );
+ addProp( SHADOWSTYLE__PERSPECTIVEY , data( "shadowstyle.perspectivey" ) );
+ addProp( SHADOWSTYLE__WEIGHT , data( "shadowstyle.weight" ) );
+ addProp( SHADOWSTYLE__ORIGINX , data( "shadowstyle.originx" ) );
+ addProp( SHADOWSTYLE__ORIGINY , data( "shadowstyle.originy" ) );
+ addProp( SHADOWSTYLE__SHADOW , data( "shadowstyle.shadow" ) );
+ addProp( SHADOWSTYLE__SHADOWOBSURED , data( "shadowstyle.shadowobsured" ) );
+ addProp( PERSPECTIVE__TYPE , data( "perspective.type" ) );
+ addProp( PERSPECTIVE__OFFSETX , data( "perspective.offsetx" ) );
+ addProp( PERSPECTIVE__OFFSETY , data( "perspective.offsety" ) );
+ addProp( PERSPECTIVE__SCALEXTOX , data( "perspective.scalextox" ) );
+ addProp( PERSPECTIVE__SCALEYTOX , data( "perspective.scaleytox" ) );
+ addProp( PERSPECTIVE__SCALEXTOY , data( "perspective.scalextoy" ) );
+ addProp( PERSPECTIVE__SCALEYTOY , data( "perspective.scaleytoy" ) );
+ addProp( PERSPECTIVE__PERSPECTIVEX , data( "perspective.perspectivex" ) );
+ addProp( PERSPECTIVE__PERSPECTIVEY , data( "perspective.perspectivey" ) );
+ addProp( PERSPECTIVE__WEIGHT , data( "perspective.weight" ) );
+ addProp( PERSPECTIVE__ORIGINX , data( "perspective.originx" ) );
+ addProp( PERSPECTIVE__ORIGINY , data( "perspective.originy" ) );
+ addProp( PERSPECTIVE__PERSPECTIVEON , data( "perspective.perspectiveon" ) );
+ addProp( THREED__SPECULARAMOUNT , data( "3d.specularamount" ) );
+ addProp( THREED__DIFFUSEAMOUNT , data( "3d.diffuseamount" ) );
+ addProp( THREED__SHININESS , data( "3d.shininess" ) );
+ addProp( THREED__EDGETHICKNESS , data( "3d.edgethickness" ) );
+ addProp( THREED__EXTRUDEFORWARD , data( "3d.extrudeforward" ) );
+ addProp( THREED__EXTRUDEBACKWARD , data( "3d.extrudebackward" ) );
+ addProp( THREED__EXTRUDEPLANE , data( "3d.extrudeplane" ) );
+ addProp( THREED__EXTRUSIONCOLOR , data( "3d.extrusioncolor", EscherPropertyMetaData.TYPE_RGB ) );
+ addProp( THREED__CRMOD , data( "3d.crmod" ) );
+ addProp( THREED__3DEFFECT , data( "3d.3deffect" ) );
+ addProp( THREED__METALLIC , data( "3d.metallic" ) );
+ addProp( THREED__USEEXTRUSIONCOLOR , data( "3d.useextrusioncolor", EscherPropertyMetaData.TYPE_RGB ) );
+ addProp( THREED__LIGHTFACE , data( "3d.lightface" ) );
+ addProp( THREEDSTYLE__YROTATIONANGLE , data( "3dstyle.yrotationangle" ) );
+ addProp( THREEDSTYLE__XROTATIONANGLE , data( "3dstyle.xrotationangle" ) );
+ addProp( THREEDSTYLE__ROTATIONAXISX , data( "3dstyle.rotationaxisx" ) );
+ addProp( THREEDSTYLE__ROTATIONAXISY , data( "3dstyle.rotationaxisy" ) );
+ addProp( THREEDSTYLE__ROTATIONAXISZ , data( "3dstyle.rotationaxisz" ) );
+ addProp( THREEDSTYLE__ROTATIONANGLE , data( "3dstyle.rotationangle" ) );
+ addProp( THREEDSTYLE__ROTATIONCENTERX , data( "3dstyle.rotationcenterx" ) );
+ addProp( THREEDSTYLE__ROTATIONCENTERY , data( "3dstyle.rotationcentery" ) );
+ addProp( THREEDSTYLE__ROTATIONCENTERZ , data( "3dstyle.rotationcenterz" ) );
+ addProp( THREEDSTYLE__RENDERMODE , data( "3dstyle.rendermode" ) );
+ addProp( THREEDSTYLE__TOLERANCE , data( "3dstyle.tolerance" ) );
+ addProp( THREEDSTYLE__XVIEWPOINT , data( "3dstyle.xviewpoint" ) );
+ addProp( THREEDSTYLE__YVIEWPOINT , data( "3dstyle.yviewpoint" ) );
+ addProp( THREEDSTYLE__ZVIEWPOINT , data( "3dstyle.zviewpoint" ) );
+ addProp( THREEDSTYLE__ORIGINX , data( "3dstyle.originx" ) );
+ addProp( THREEDSTYLE__ORIGINY , data( "3dstyle.originy" ) );
+ addProp( THREEDSTYLE__SKEWANGLE , data( "3dstyle.skewangle" ) );
+ addProp( THREEDSTYLE__SKEWAMOUNT , data( "3dstyle.skewamount" ) );
+ addProp( THREEDSTYLE__AMBIENTINTENSITY , data( "3dstyle.ambientintensity" ) );
+ addProp( THREEDSTYLE__KEYX , data( "3dstyle.keyx" ) );
+ addProp( THREEDSTYLE__KEYY , data( "3dstyle.keyy" ) );
+ addProp( THREEDSTYLE__KEYZ , data( "3dstyle.keyz" ) );
+ addProp( THREEDSTYLE__KEYINTENSITY , data( "3dstyle.keyintensity" ) );
+ addProp( THREEDSTYLE__FILLX , data( "3dstyle.fillx" ) );
+ addProp( THREEDSTYLE__FILLY , data( "3dstyle.filly" ) );
+ addProp( THREEDSTYLE__FILLZ , data( "3dstyle.fillz" ) );
+ addProp( THREEDSTYLE__FILLINTENSITY , data( "3dstyle.fillintensity" ) );
+ addProp( THREEDSTYLE__CONSTRAINROTATION , data( "3dstyle.constrainrotation" ) );
+ addProp( THREEDSTYLE__ROTATIONCENTERAUTO , data( "3dstyle.rotationcenterauto" ) );
+ addProp( THREEDSTYLE__PARALLEL , data( "3dstyle.parallel" ) );
+ addProp( THREEDSTYLE__KEYHARSH , data( "3dstyle.keyharsh" ) );
+ addProp( THREEDSTYLE__FILLHARSH , data( "3dstyle.fillharsh" ) );
+ addProp( SHAPE__MASTER , data( "shape.master" ) );
+ addProp( SHAPE__CONNECTORSTYLE , data( "shape.connectorstyle" ) );
+ addProp( SHAPE__BLACKANDWHITESETTINGS , data( "shape.blackandwhitesettings" ) );
+ addProp( SHAPE__WMODEPUREBW , data( "shape.wmodepurebw" ) );
+ addProp( SHAPE__WMODEBW , data( "shape.wmodebw" ) );
+ addProp( SHAPE__OLEICON , data( "shape.oleicon" ) );
+ addProp( SHAPE__PREFERRELATIVERESIZE , data( "shape.preferrelativeresize" ) );
+ addProp( SHAPE__LOCKSHAPETYPE , data( "shape.lockshapetype" ) );
+ addProp( SHAPE__DELETEATTACHEDOBJECT , data( "shape.deleteattachedobject" ) );
+ addProp( SHAPE__BACKGROUNDSHAPE , data( "shape.backgroundshape" ) );
+ addProp( CALLOUT__CALLOUTTYPE , data( "callout.callouttype" ) );
+ addProp( CALLOUT__XYCALLOUTGAP , data( "callout.xycalloutgap" ) );
+ addProp( CALLOUT__CALLOUTANGLE , data( "callout.calloutangle" ) );
+ addProp( CALLOUT__CALLOUTDROPTYPE , data( "callout.calloutdroptype" ) );
+ addProp( CALLOUT__CALLOUTDROPSPECIFIED , data( "callout.calloutdropspecified" ) );
+ addProp( CALLOUT__CALLOUTLENGTHSPECIFIED , data( "callout.calloutlengthspecified" ) );
+ addProp( CALLOUT__ISCALLOUT , data( "callout.iscallout" ) );
+ addProp( CALLOUT__CALLOUTACCENTBAR , data( "callout.calloutaccentbar" ) );
+ addProp( CALLOUT__CALLOUTTEXTBORDER , data( "callout.callouttextborder" ) );
+ addProp( CALLOUT__CALLOUTMINUSX , data( "callout.calloutminusx" ) );
+ addProp( CALLOUT__CALLOUTMINUSY , data( "callout.calloutminusy" ) );
+ addProp( CALLOUT__DROPAUTO , data( "callout.dropauto" ) );
+ addProp( CALLOUT__LENGTHSPECIFIED , data( "callout.lengthspecified" ) );
+ addProp( GROUPSHAPE__SHAPENAME , data( "groupshape.shapename" ) );
+ addProp( GROUPSHAPE__DESCRIPTION , data( "groupshape.description" ) );
+ addProp( GROUPSHAPE__HYPERLINK , data( "groupshape.hyperlink" ) );
+ addProp( GROUPSHAPE__WRAPPOLYGONVERTICES , data( "groupshape.wrappolygonvertices", EscherPropertyMetaData.TYPE_ARRAY ) );
+ addProp( GROUPSHAPE__WRAPDISTLEFT , data( "groupshape.wrapdistleft" ) );
+ addProp( GROUPSHAPE__WRAPDISTTOP , data( "groupshape.wrapdisttop" ) );
+ addProp( GROUPSHAPE__WRAPDISTRIGHT , data( "groupshape.wrapdistright" ) );
+ addProp( GROUPSHAPE__WRAPDISTBOTTOM , data( "groupshape.wrapdistbottom" ) );
+ addProp( GROUPSHAPE__REGROUPID , data( "groupshape.regroupid" ) );
+ addProp( GROUPSHAPE__EDITEDWRAP , data( "groupshape.editedwrap" ) );
+ addProp( GROUPSHAPE__BEHINDDOCUMENT , data( "groupshape.behinddocument" ) );
+ addProp( GROUPSHAPE__ONDBLCLICKNOTIFY , data( "groupshape.ondblclicknotify" ) );
+ addProp( GROUPSHAPE__ISBUTTON , data( "groupshape.isbutton" ) );
+ addProp( GROUPSHAPE__1DADJUSTMENT , data( "groupshape.1dadjustment" ) );
+ addProp( GROUPSHAPE__HIDDEN , data( "groupshape.hidden" ) );
+ addProp( GROUPSHAPE__PRINT , data( "groupshape.print", EscherPropertyMetaData.TYPE_BOOLEAN ) );
+ }
+ }
+
+ private static void addProp( int s, EscherPropertyMetaData data )
+ {
+ properties.put( new Short( (short) s ), data );
+ }
+
+ private static EscherPropertyMetaData data( String propName, byte type )
+ {
+ return new EscherPropertyMetaData( propName, type );
+ }
+
+ private static EscherPropertyMetaData data( String propName )
+ {
+ return new EscherPropertyMetaData( propName );
+ }
+
+ public static String getPropertyName( short propertyId )
+ {
+ initProps();
+ EscherPropertyMetaData o = (EscherPropertyMetaData) properties.get( new Short( propertyId ) );
+ return o == null ? "unknown" : o.getDescription();
+ }
+
+ public static byte getPropertyType( short propertyId )
+ {
+ initProps();
+ EscherPropertyMetaData escherPropertyMetaData = (EscherPropertyMetaData) properties.get( new Short( propertyId ) );
+ return escherPropertyMetaData == null ? 0 : escherPropertyMetaData.getType();
+ }
+}
+
+
+
diff --git a/src/java/org/apache/poi/ddf/EscherProperty.java b/src/java/org/apache/poi/ddf/EscherProperty.java
new file mode 100644
index 000000000..787ccf2f8
--- /dev/null
+++ b/src/java/org/apache/poi/ddf/EscherProperty.java
@@ -0,0 +1,78 @@
+package org.apache.poi.ddf;
+
+/**
+ * This is the abstract base class for all escher properties.
+ *
+ * @see EscherOptRecord
+ *
+ * @author Glen Stampoultzis (glens at apache.org)
+ */
+abstract public class EscherProperty
+{
+ private short id;
+
+ /**
+ * The id is distinct from the actual property number. The id includes the property number the blip id
+ * flag and an indicator whether the property is complex or not.
+ */
+ public EscherProperty( short id )
+ {
+ this.id = id;
+ }
+
+ /**
+ * Constructs a new escher property. The three parameters are combined to form a property
+ * id.
+ */
+ public EscherProperty( short propertyNumber, boolean isComplex, boolean isBlipId )
+ {
+ this.id = (short)(propertyNumber +
+ (isComplex ? 0x8000 : 0x0) +
+ (isBlipId ? 0x4000 : 0x0));
+ }
+
+ public short getId()
+ {
+ return id;
+ }
+
+ public short getPropertyNumber()
+ {
+ return (short) ( id & (short) 0x3FFF );
+ }
+
+ public boolean isComplex()
+ {
+ return ( id & (short) 0x8000 ) != 0;
+ }
+
+ public boolean isBlipId()
+ {
+ return ( id & (short) 0x4000 ) != 0;
+ }
+
+ public String getName()
+ {
+ return EscherProperties.getPropertyName(id);
+ }
+
+ /**
+ * Most properties are just 6 bytes in length. Override this if we're
+ * dealing with complex properties.
+ */
+ public int getPropertySize()
+ {
+ return 6;
+ }
+
+ /**
+ * Escher properties consist of a simple fixed length part and a complex variable length part.
+ * The fixed length part is serialized first.
+ */
+ abstract public int serializeSimplePart( byte[] data, int pos );
+ /**
+ * Escher properties consist of a simple fixed length part and a complex variable length part.
+ * The fixed length part is serialized first.
+ */
+ abstract public int serializeComplexPart( byte[] data, int pos );
+}
diff --git a/src/java/org/apache/poi/ddf/EscherPropertyFactory.java b/src/java/org/apache/poi/ddf/EscherPropertyFactory.java
new file mode 100644
index 000000000..cc6b7ba7a
--- /dev/null
+++ b/src/java/org/apache/poi/ddf/EscherPropertyFactory.java
@@ -0,0 +1,91 @@
+package org.apache.poi.ddf;
+
+import org.apache.poi.util.LittleEndian;
+import org.apache.poi.hssf.record.RecordFormatException;
+
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Iterator;
+
+/**
+ * Generates a property given a reference into the byte array storing that property.
+ *
+ * @author Glen Stampoultzis
+ */
+public class EscherPropertyFactory
+{
+ /**
+ * Create new properties from a byte array.
+ *
+ * @param data The byte array containing the property
+ * @param offset The starting offset into the byte array
+ * @return The new properties
+ */
+ public List createProperties( byte[] data, int offset, short numProperties )
+ {
+ List results = new ArrayList();
+
+ int pos = offset;
+ int complexBytes = 0;
+// while ( bytesRemaining >= 6 )
+ for (int i = 0; i < numProperties; i++)
+ {
+ short propId;
+ int propData;
+ propId = LittleEndian.getShort( data, pos );
+ propData = LittleEndian.getInt( data, pos + 2 );
+ short propNumber = (short) ( propId & (short) 0x3FFF );
+ boolean isComplex = ( propId & (short) 0x8000 ) != 0;
+ boolean isBlipId = ( propId & (short) 0x4000 ) != 0;
+ if ( isComplex )
+ complexBytes = propData;
+ else
+ complexBytes = 0;
+ byte propertyType = EscherProperties.getPropertyType( (short) propNumber );
+ if ( propertyType == EscherPropertyMetaData.TYPE_BOOLEAN )
+ results.add( new EscherBoolProperty( propNumber, propData ) );
+ else if ( propertyType == EscherPropertyMetaData.TYPE_RGB )
+ results.add( new EscherRGBProperty( propNumber, propData ) );
+ else if ( propertyType == EscherPropertyMetaData.TYPE_SHAPEPATH )
+ results.add( new EscherShapePathProperty( propNumber, propData ) );
+ else
+ {
+ if ( !isComplex )
+ results.add( new EscherSimpleProperty( propNumber, propData ) );
+ else
+ {
+ if ( propertyType == EscherPropertyMetaData.TYPE_ARRAY)
+ results.add( new EscherArrayProperty( propId, new byte[propData]) );
+ else
+ results.add( new EscherComplexProperty( propId, new byte[propData]) );
+
+ }
+ }
+ pos += 6;
+// bytesRemaining -= 6 + complexBytes;
+ }
+
+ // Get complex data
+ for ( Iterator iterator = results.iterator(); iterator.hasNext(); )
+ {
+ EscherProperty p = (EscherProperty) iterator.next();
+ if (p instanceof EscherComplexProperty)
+ {
+ if (p instanceof EscherArrayProperty)
+ {
+ pos += ((EscherArrayProperty)p).setArrayData(data, pos);
+ }
+ else
+ {
+ byte[] complexData = ((EscherComplexProperty)p).getComplexData();
+ System.arraycopy(data, pos, complexData, 0, complexData.length);
+ pos += complexData.length;
+ }
+ }
+ }
+
+ return results;
+ }
+
+
+}
diff --git a/src/java/org/apache/poi/ddf/EscherPropertyMetaData.java b/src/java/org/apache/poi/ddf/EscherPropertyMetaData.java
new file mode 100644
index 000000000..97576064e
--- /dev/null
+++ b/src/java/org/apache/poi/ddf/EscherPropertyMetaData.java
@@ -0,0 +1,51 @@
+package org.apache.poi.ddf;
+
+/**
+ * This class stores the type and description of an escher property.
+ *
+ * @author Glen Stampoultzis (glens at apache.org)
+ */
+public class EscherPropertyMetaData
+{
+ // Escher property types.
+ public final static byte TYPE_UNKNOWN = (byte) 0;
+ public final static byte TYPE_BOOLEAN = (byte) 1;
+ public final static byte TYPE_RGB = (byte) 2;
+ public final static byte TYPE_SHAPEPATH = (byte) 3;
+ public final static byte TYPE_SIMPLE = (byte)4;
+ public final static byte TYPE_ARRAY = (byte)5;;
+
+ private String description;
+ private byte type;
+
+
+ /**
+ * @param description The description of the escher property.
+ */
+ public EscherPropertyMetaData( String description )
+ {
+ this.description = description;
+ }
+
+ /**
+ *
+ * @param description The description of the escher property.
+ * @param type The type of the property.
+ */
+ public EscherPropertyMetaData( String description, byte type )
+ {
+ this.description = description;
+ this.type = type;
+ }
+
+ public String getDescription()
+ {
+ return description;
+ }
+
+ public byte getType()
+ {
+ return type;
+ }
+
+}
diff --git a/src/java/org/apache/poi/ddf/EscherRGBProperty.java b/src/java/org/apache/poi/ddf/EscherRGBProperty.java
new file mode 100644
index 000000000..1b8c9d295
--- /dev/null
+++ b/src/java/org/apache/poi/ddf/EscherRGBProperty.java
@@ -0,0 +1,37 @@
+package org.apache.poi.ddf;
+
+/**
+ * A color property.
+ *
+ * @author Glen Stampoultzis (glens at apache.org)
+ */
+public class EscherRGBProperty
+ extends EscherSimpleProperty
+{
+
+ public EscherRGBProperty( short propertyNumber, int rgbColor )
+ {
+ super( propertyNumber, false, false, rgbColor );
+ }
+
+ public int getRgbColor()
+ {
+ return propertyValue;
+ }
+
+ public byte getRed()
+ {
+ return (byte) ( propertyValue & 0xFF );
+ }
+
+ public byte getGreen()
+ {
+ return (byte) ( (propertyValue >> 8) & 0xFF );
+ }
+
+ public byte getBlue()
+ {
+ return (byte) ( (propertyValue >> 16) & 0xFF );
+ }
+
+}
diff --git a/src/java/org/apache/poi/ddf/EscherRecord.java b/src/java/org/apache/poi/ddf/EscherRecord.java
new file mode 100644
index 000000000..0001bbfec
--- /dev/null
+++ b/src/java/org/apache/poi/ddf/EscherRecord.java
@@ -0,0 +1,273 @@
+package org.apache.poi.ddf;
+
+import org.apache.poi.util.LittleEndian;
+
+import java.io.PrintWriter;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * The base abstract record from which all escher records are defined. Subclasses will need
+ * to define methods for serialization/deserialization and for determining the record size.
+ *
+ * @author Glen Stampoultzis
+ */
+abstract public class EscherRecord
+{
+ private short options;
+ private short recordId;
+
+ /**
+ * Create a new instance
+ */
+ public EscherRecord()
+ {
+ }
+
+ /**
+ * Delegates to fillFields(byte[], int, EscherRecordFactory)
+ *
+ * @see #fillFields(byte[], int, org.apache.poi.ddf.EscherRecordFactory)
+ */
+ protected int fillFields( byte[] data, EscherRecordFactory f )
+ {
+ return fillFields( data, 0, f );
+ }
+
+ /**
+ * The contract of this method is to deserialize an escher record including
+ * it's children.
+ *
+ * @param data The byte array containing the serialized escher
+ * records.
+ * @param offset The offset into the byte array.
+ * @param recordFactory A factory for creating new escher records.
+ * @return The number of bytes written.
+ */
+ public abstract int fillFields( byte[] data, int offset, EscherRecordFactory recordFactory );
+
+ /**
+ * Reads the 8 byte header information and populates the options
+ * and recordId
records.
+ *
+ * @param data the byte array to read from
+ * @param offset the offset to start reading from
+ * @return the number of bytes remaining in this record. This
+ * may include the children if this is a container.
+ */
+ protected int readHeader( byte[] data, int offset )
+ {
+ EscherRecordHeader header = EscherRecordHeader.readHeader(data, offset);
+ options = header.getOptions();
+ recordId = header.getRecordId();
+ return header.getRemainingBytes();
+ }
+
+ /**
+ * Determine whether this is a container record by inspecting the option
+ * field.
+ * @return true is this is a container field.
+ */
+ public boolean isContainerRecord()
+ {
+ return (options & (short)0x000f) == (short)0x000f;
+ }
+
+ /**
+ * @return The options field for this record. All records have one.
+ */
+ public short getOptions()
+ {
+ return options;
+ }
+
+ /**
+ * Set the options this this record. Container records should have the
+ * last nibble set to 0xF.
+ */
+ public void setOptions( short options )
+ {
+ this.options = options;
+ }
+
+ /**
+ * Serializes to a new byte array. This is done by delegating to
+ * serialize(int, byte[]);
+ *
+ * @return the serialized record.
+ * @see #serialize(int, byte[])
+ */
+ public byte[] serialize()
+ {
+ byte[] retval = new byte[getRecordSize()];
+
+ serialize( 0, retval );
+ return retval;
+ }
+
+ /**
+ * Serializes to an existing byte array without serialization listener.
+ * This is done by delegating to serialize(int, byte[], EscherSerializationListener).
+ *
+ * @param offset the offset within the data byte array.
+ * @param data the data array to serialize to.
+ * @return The number of bytes written.
+ *
+ * @see #serialize(int, byte[], org.apache.poi.ddf.EscherSerializationListener)
+ */
+ public int serialize( int offset, byte[] data)
+ {
+ return serialize( offset, data, new NullEscherSerializationListener() );
+ }
+
+ /**
+ * Serializes the record to an existing byte array.
+ *
+ * @param offset the offset within the byte array
+ * @param data the data array to serialize to
+ * @param listener a listener for begin and end serialization events. This
+ * is useful because the serialization is
+ * hierarchical/recursive and sometimes you need to be able
+ * break into that.
+ * @return the number of bytes written.
+ */
+ public abstract int serialize( int offset, byte[] data, EscherSerializationListener listener );
+
+ /**
+ * Subclasses should effeciently return the number of bytes required to
+ * serialize the record.
+ *
+ * @return number of bytes
+ */
+ abstract public int getRecordSize();
+
+ /**
+ * Return the current record id.
+ *
+ * @return The 16 bit record id.
+ */
+ public short getRecordId()
+ {
+ return recordId;
+ }
+
+ /**
+ * Sets the record id for this record.
+ */
+ public void setRecordId( short recordId )
+ {
+ this.recordId = recordId;
+ }
+
+ /**
+ * @return Returns the children of this record. By default this will
+ * be an empty list. EscherCotainerRecord is the only record
+ * that may contain children.
+ *
+ * @see EscherContainerRecord
+ */
+ public List getChildRecords() { return Collections.EMPTY_LIST; }
+
+ /**
+ * Sets the child records for this record. By default this will throw
+ * an exception as only EscherContainerRecords may have children.
+ *
+ * @param childRecords Not used in base implementation.
+ */
+ public void setChildRecords( List childRecords ) { throw new IllegalArgumentException("This record does not support child records."); }
+
+ /**
+ * Escher records may need to be clonable in the future.
+ */
+ public Object clone()
+ {
+ throw new RuntimeException( "The class " + getClass().getName() + " needs to define a clone method" );
+ }
+
+ /**
+ * Returns the indexed child record.
+ */
+ public EscherRecord getChild( int index )
+ {
+ return (EscherRecord) getChildRecords().get(index);
+ }
+
+ /**
+ * The display methods allows escher variables to print the record names
+ * according to their hierarchy.
+ *
+ * @param w The print writer to output to.
+ * @param indent The current indent level.
+ */
+ public void display(PrintWriter w, int indent)
+ {
+ for (int i = 0; i < indent * 4; i++) w.print(' ');
+ w.println(getRecordName());
+ }
+
+ /**
+ * Subclasses should return the short name for this escher record.
+ */
+ public abstract String getRecordName();
+
+ /**
+ * Returns the instance part of the option record.
+ *
+ * @return The instance part of the record
+ */
+ public short getInstance()
+ {
+ return (short) ( options >> 4 );
+ }
+
+ /**
+ * This class reads the standard escher header.
+ */
+ static class EscherRecordHeader
+ {
+ private short options;
+ private short recordId;
+ private int remainingBytes;
+
+ private EscherRecordHeader()
+ {
+ }
+
+ public static EscherRecordHeader readHeader( byte[] data, int offset )
+ {
+ EscherRecordHeader header = new EscherRecordHeader();
+ header.options = LittleEndian.getShort(data, offset);
+ header.recordId = LittleEndian.getShort(data, offset + 2);
+ header.remainingBytes = LittleEndian.getInt( data, offset + 4 );
+ return header;
+ }
+
+
+ public short getOptions()
+ {
+ return options;
+ }
+
+ public short getRecordId()
+ {
+ return recordId;
+ }
+
+ public int getRemainingBytes()
+ {
+ return remainingBytes;
+ }
+
+ public String toString()
+ {
+ return "EscherRecordHeader{" +
+ "options=" + options +
+ ", recordId=" + recordId +
+ ", remainingBytes=" + remainingBytes +
+ "}";
+ }
+
+
+ }
+
+}
diff --git a/src/java/org/apache/poi/ddf/EscherRecordFactory.java b/src/java/org/apache/poi/ddf/EscherRecordFactory.java
new file mode 100644
index 000000000..4c6e22c8a
--- /dev/null
+++ b/src/java/org/apache/poi/ddf/EscherRecordFactory.java
@@ -0,0 +1,16 @@
+package org.apache.poi.ddf;
+
+/**
+ * The escher record factory interface allows for the creation of escher
+ * records from a pointer into a data array.
+ *
+ * @author Glen Stampoultzis (glens at apache.org)
+ */
+public interface EscherRecordFactory
+{
+ /**
+ * Create a new escher record from the data provided. Does not attempt
+ * to fill the contents of the record however.
+ */
+ EscherRecord createRecord( byte[] data, int offset );
+}
diff --git a/src/java/org/apache/poi/ddf/EscherSerializationListener.java b/src/java/org/apache/poi/ddf/EscherSerializationListener.java
new file mode 100644
index 000000000..ddea267c7
--- /dev/null
+++ b/src/java/org/apache/poi/ddf/EscherSerializationListener.java
@@ -0,0 +1,27 @@
+package org.apache.poi.ddf;
+
+/**
+ * Interface for listening to escher serialization events.
+ *
+ * @author Glen Stampoultzis (glens at apache.org)
+ */
+public interface EscherSerializationListener
+{
+ /**
+ * Fired before a given escher record is serialized.
+ *
+ * @param offset The position in the data array at which the record will be serialized.
+ * @param recordId The id of the record about to be serialized.
+ */
+ void beforeRecordSerialize(int offset, short recordId, EscherRecord record);
+
+ /**
+ * Fired after a record has been serialized.
+ *
+ * @param offset The position of the end of the serialized record + 1
+ * @param recordId The id of the record about to be serialized
+ * @param size The number of bytes written for this record. If it is a container
+ * record then this will include the size of any included records.
+ */
+ void afterRecordSerialize(int offset, short recordId, int size, EscherRecord record);
+}
diff --git a/src/java/org/apache/poi/ddf/EscherShapePathProperty.java b/src/java/org/apache/poi/ddf/EscherShapePathProperty.java
new file mode 100644
index 000000000..df70e9c83
--- /dev/null
+++ b/src/java/org/apache/poi/ddf/EscherShapePathProperty.java
@@ -0,0 +1,25 @@
+package org.apache.poi.ddf;
+
+/**
+ * Defines the constants for the various possible shape paths.
+ *
+ * @author Glen Stampoultzis (glens at apache.org)
+ */
+public class EscherShapePathProperty
+ extends EscherSimpleProperty
+{
+
+ public static final int LINE_OF_STRAIGHT_SEGMENTS = 0;
+ public static final int CLOSED_POLYGON = 1;
+ public static final int CURVES = 2;
+ public static final int CLOSED_CURVES = 3;
+ public static final int COMPLEX = 4;
+
+ public EscherShapePathProperty( short propertyNumber, int shapePath )
+ {
+ super( propertyNumber, false, false, shapePath );
+ }
+
+
+
+}
diff --git a/src/java/org/apache/poi/ddf/EscherSimpleProperty.java b/src/java/org/apache/poi/ddf/EscherSimpleProperty.java
new file mode 100644
index 000000000..b61474efd
--- /dev/null
+++ b/src/java/org/apache/poi/ddf/EscherSimpleProperty.java
@@ -0,0 +1,103 @@
+package org.apache.poi.ddf;
+
+import org.apache.poi.util.LittleEndian;
+import org.apache.poi.util.HexDump;
+
+/**
+ * A simple property is of fixed length and as a property number in addition
+ * to a 32-bit value. Properties that can't be stored in only 32-bits are
+ * stored as EscherComplexProperty objects.
+ *
+ * @author Glen Stampoultzis (glens at apache.org)
+ */
+public class EscherSimpleProperty extends EscherProperty
+{
+ protected int propertyValue;
+
+ /**
+ * The id is distinct from the actual property number. The id includes the property number the blip id
+ * flag and an indicator whether the property is complex or not.
+ */
+ public EscherSimpleProperty( short id, int propertyValue )
+ {
+ super( id );
+ this.propertyValue = propertyValue;
+ }
+
+ /**
+ * Constructs a new escher property. The three parameters are combined to form a property
+ * id.
+ */
+ public EscherSimpleProperty( short propertyNumber, boolean isComplex, boolean isBlipId, int propertyValue )
+ {
+ super( propertyNumber, isComplex, isBlipId );
+ this.propertyValue = propertyValue;
+ }
+
+ /**
+ * Serialize the simple part of the escher record.
+ *
+ * @return the number of bytes serialized.
+ */
+ public int serializeSimplePart( byte[] data, int offset )
+ {
+ LittleEndian.putShort(data, offset, getId());
+ LittleEndian.putInt(data, offset + 2, propertyValue);
+ return 6;
+ }
+
+ /**
+ * Escher properties consist of a simple fixed length part and a complex variable length part.
+ * The fixed length part is serialized first.
+ */
+ public int serializeComplexPart( byte[] data, int pos )
+ {
+ return 0;
+ }
+
+ /**
+ * @return Return the 32 bit value of this property.
+ */
+ public int getPropertyValue()
+ {
+ return propertyValue;
+ }
+
+ /**
+ * Returns true if one escher property is equal to another.
+ */
+ public boolean equals( Object o )
+ {
+ if ( this == o ) return true;
+ if ( !( o instanceof EscherSimpleProperty ) ) return false;
+
+ final EscherSimpleProperty escherSimpleProperty = (EscherSimpleProperty) o;
+
+ if ( propertyValue != escherSimpleProperty.propertyValue ) return false;
+ if ( getId() != escherSimpleProperty.getId() ) return false;
+
+ return true;
+ }
+
+ /**
+ * Returns a hashcode so that this object can be stored in collections that
+ * require the use of such things.
+ */
+ public int hashCode()
+ {
+ return propertyValue;
+ }
+
+ /**
+ * @return the string representation of this property.
+ */
+ public String toString()
+ {
+ return "propNum: " + getPropertyNumber()
+ + ", propName: " + EscherProperties.getPropertyName( getPropertyNumber() )
+ + ", complex: " + isComplex()
+ + ", blipId: " + isBlipId()
+ + ", value: " + propertyValue + " (0x" + HexDump.toHex(propertyValue) + ")";
+ }
+
+}
diff --git a/src/java/org/apache/poi/ddf/EscherSpRecord.java b/src/java/org/apache/poi/ddf/EscherSpRecord.java
new file mode 100644
index 000000000..a5e2be9ff
--- /dev/null
+++ b/src/java/org/apache/poi/ddf/EscherSpRecord.java
@@ -0,0 +1,201 @@
+package org.apache.poi.ddf;
+
+import org.apache.poi.util.HexDump;
+import org.apache.poi.util.LittleEndian;
+
+/**
+ * Together the the EscherOptRecord this record defines some of the basic
+ * properties of a shape.
+ *
+ * @author Glen Stampoultzis (glens at apache.org)
+ */
+public class EscherSpRecord
+ extends EscherRecord
+{
+ public static final short RECORD_ID = (short) 0xF00A;
+ public static final String RECORD_DESCRIPTION = "MsofbtSp";
+
+ public static final int FLAG_GROUP = 0x0001;
+ public static final int FLAG_CHILD = 0x0002;
+ public static final int FLAG_PATRIARCH = 0x0004;
+ public static final int FLAG_DELETED = 0x0008;
+ public static final int FLAG_OLESHAPE = 0x0010;
+ public static final int FLAG_HAVEMASTER = 0x0020;
+ public static final int FLAG_FLIPHORIZ = 0x0040;
+ public static final int FLAG_FLIPVERT = 0x0080;
+ public static final int FLAG_CONNECTOR = 0x0100;
+ public static final int FLAG_HAVEANCHOR = 0x0200;
+ public static final int FLAG_BACKGROUND = 0x0400;
+ public static final int FLAG_HASSHAPETYPE = 0x0800;
+
+ private int field_1_shapeId;
+ private int field_2_flags;
+
+ /**
+ * This method deserializes the record from a byte array.
+ *
+ * @param data The byte array containing the escher record information
+ * @param offset The starting offset into data
.
+ * @param recordFactory May be null since this is not a container record.
+ * @return The number of bytes read from the byte array.
+ */
+ public int fillFields( byte[] data, int offset, EscherRecordFactory recordFactory )
+ {
+ int bytesRemaining = readHeader( data, offset );
+ int pos = offset + 8;
+ int size = 0;
+ field_1_shapeId = LittleEndian.getInt( data, pos + size ); size += 4;
+ field_2_flags = LittleEndian.getInt( data, pos + size ); size += 4;
+// bytesRemaining -= size;
+// remainingData = new byte[bytesRemaining];
+// System.arraycopy( data, pos + size, remainingData, 0, bytesRemaining );
+ return getRecordSize();
+ }
+
+ /**
+ * This method serializes this escher record into a byte array.
+ *
+ * @param offset The offset into data
to start writing the record data to.
+ * @param data The byte array to serialize to.
+ * @param listener A listener to retrieve start and end callbacks. Use a NullEscherSerailizationListener
to ignore these events.
+ * @return The number of bytes written.
+ *
+ * @see NullEscherSerializationListener
+ */
+ public int serialize( int offset, byte[] data, EscherSerializationListener listener )
+ {
+ listener.beforeRecordSerialize( offset, getRecordId(), this );
+ LittleEndian.putShort( data, offset, getOptions() );
+ LittleEndian.putShort( data, offset + 2, getRecordId() );
+ int remainingBytes = 8;
+ LittleEndian.putInt( data, offset + 4, remainingBytes );
+ LittleEndian.putInt( data, offset + 8, field_1_shapeId );
+ LittleEndian.putInt( data, offset + 12, field_2_flags );
+// System.arraycopy( remainingData, 0, data, offset + 26, remainingData.length );
+// int pos = offset + 8 + 18 + remainingData.length;
+ listener.afterRecordSerialize( offset + getRecordSize(), getRecordId(), getRecordSize(), this );
+ return 8 + 8;
+ }
+
+ /**
+ * Returns the number of bytes that are required to serialize this record.
+ *
+ * @return Number of bytes
+ */
+ public int getRecordSize()
+ {
+ return 8 + 8;
+ }
+
+ /**
+ * @return the 16 bit identifier for this record.
+ */
+ public short getRecordId()
+ {
+ return RECORD_ID;
+ }
+
+ /**
+ * The short name for this record
+ */
+ public String getRecordName()
+ {
+ return "Sp";
+ }
+
+ /**
+ * @return the string representing this shape.
+ */
+ public String toString()
+ {
+ String nl = System.getProperty("line.separator");
+
+ return getClass().getName() + ":" + nl +
+ " RecordId: 0x" + HexDump.toHex(RECORD_ID) + nl +
+ " Options: 0x" + HexDump.toHex(getOptions()) + nl +
+ " ShapeId: " + field_1_shapeId + nl +
+ " Flags: " + decodeFlags(field_2_flags) + " (0x" + HexDump.toHex(field_2_flags) + ")" + nl;
+
+ }
+
+ /**
+ * Converts the shape flags into a more descriptive name.
+ */
+ private String decodeFlags( int flags )
+ {
+ StringBuffer result = new StringBuffer();
+ result.append( ( flags & FLAG_GROUP ) != 0 ? "|GROUP" : "" );
+ result.append( ( flags & FLAG_CHILD ) != 0 ? "|CHILD" : "" );
+ result.append( ( flags & FLAG_PATRIARCH ) != 0 ? "|PATRIARCH" : "" );
+ result.append( ( flags & FLAG_DELETED ) != 0 ? "|DELETED" : "" );
+ result.append( ( flags & FLAG_OLESHAPE ) != 0 ? "|OLESHAPE" : "" );
+ result.append( ( flags & FLAG_HAVEMASTER ) != 0 ? "|HAVEMASTER" : "" );
+ result.append( ( flags & FLAG_FLIPHORIZ ) != 0 ? "|FLIPHORIZ" : "" );
+ result.append( ( flags & FLAG_FLIPVERT ) != 0 ? "|FLIPVERT" : "" );
+ result.append( ( flags & FLAG_CONNECTOR ) != 0 ? "|CONNECTOR" : "" );
+ result.append( ( flags & FLAG_HAVEANCHOR ) != 0 ? "|HAVEANCHOR" : "" );
+ result.append( ( flags & FLAG_BACKGROUND ) != 0 ? "|BACKGROUND" : "" );
+ result.append( ( flags & FLAG_HASSHAPETYPE ) != 0 ? "|HASSHAPETYPE" : "" );
+
+ result.deleteCharAt(0);
+ return result.toString();
+ }
+
+ /**
+ * @return A number that identifies this shape
+ */
+ public int getShapeId()
+ {
+ return field_1_shapeId;
+ }
+
+ /**
+ * Sets a number that identifies this shape.
+ */
+ public void setShapeId( int field_1_shapeId )
+ {
+ this.field_1_shapeId = field_1_shapeId;
+ }
+
+ /**
+ * The flags that apply to this shape.
+ *
+ * @see #FLAG_GROUP
+ * @see #FLAG_CHILD
+ * @see #FLAG_PATRIARCH
+ * @see #FLAG_DELETED
+ * @see #FLAG_OLESHAPE
+ * @see #FLAG_HAVEMASTER
+ * @see #FLAG_FLIPHORIZ
+ * @see #FLAG_FLIPVERT
+ * @see #FLAG_CONNECTOR
+ * @see #FLAG_HAVEANCHOR
+ * @see #FLAG_BACKGROUND
+ * @see #FLAG_HASSHAPETYPE
+ */
+ public int getFlags()
+ {
+ return field_2_flags;
+ }
+
+ /**
+ * The flags that apply to this shape.
+ *
+ * @see #FLAG_GROUP
+ * @see #FLAG_CHILD
+ * @see #FLAG_PATRIARCH
+ * @see #FLAG_DELETED
+ * @see #FLAG_OLESHAPE
+ * @see #FLAG_HAVEMASTER
+ * @see #FLAG_FLIPHORIZ
+ * @see #FLAG_FLIPVERT
+ * @see #FLAG_CONNECTOR
+ * @see #FLAG_HAVEANCHOR
+ * @see #FLAG_BACKGROUND
+ * @see #FLAG_HASSHAPETYPE
+ */
+ public void setFlags( int field_2_flags )
+ {
+ this.field_2_flags = field_2_flags;
+ }
+}
diff --git a/src/java/org/apache/poi/ddf/EscherSpgrRecord.java b/src/java/org/apache/poi/ddf/EscherSpgrRecord.java
new file mode 100644
index 000000000..7e86108e0
--- /dev/null
+++ b/src/java/org/apache/poi/ddf/EscherSpgrRecord.java
@@ -0,0 +1,192 @@
+package org.apache.poi.ddf;
+
+import org.apache.poi.util.HexDump;
+import org.apache.poi.util.LittleEndian;
+import org.apache.poi.hssf.record.RecordFormatException;
+
+/**
+ * The spgr record defines information about a shape group. Groups in escher
+ * are simply another form of shape that you can't physically see.
+ *
+ * @author Glen Stampoultzis (glens at apache.org)
+ */
+public class EscherSpgrRecord
+ extends EscherRecord
+{
+ public static final short RECORD_ID = (short) 0xF009;
+ public static final String RECORD_DESCRIPTION = "MsofbtSpgr";
+
+ private int field_1_rectX1;
+ private int field_2_rectY1;
+ private int field_3_rectX2;
+ private int field_4_rectY2;
+
+ /**
+ * This method deserializes the record from a byte array.
+ *
+ * @param data The byte array containing the escher record information
+ * @param offset The starting offset into data
.
+ * @param recordFactory May be null since this is not a container record.
+ * @return The number of bytes read from the byte array.
+ */
+ public int fillFields( byte[] data, int offset, EscherRecordFactory recordFactory )
+ {
+ int bytesRemaining = readHeader( data, offset );
+ int pos = offset + 8;
+ int size = 0;
+ field_1_rectX1 = LittleEndian.getInt( data, pos + size );size+=4;
+ field_2_rectY1 = LittleEndian.getInt( data, pos + size );size+=4;
+ field_3_rectX2 = LittleEndian.getInt( data, pos + size );size+=4;
+ field_4_rectY2 = LittleEndian.getInt( data, pos + size );size+=4;
+ bytesRemaining -= size;
+ if (bytesRemaining != 0) throw new RecordFormatException("Expected no remaining bytes but got " + bytesRemaining);
+// remainingData = new byte[bytesRemaining];
+// System.arraycopy( data, pos + size, remainingData, 0, bytesRemaining );
+ return 8 + size + bytesRemaining;
+ }
+
+ /**
+ * This method serializes this escher record into a byte array.
+ *
+ * @param offset The offset into data
to start writing the record data to.
+ * @param data The byte array to serialize to.
+ * @param listener A listener to retrieve start and end callbacks. Use a NullEscherSerailizationListener
to ignore these events.
+ * @return The number of bytes written.
+ *
+ * @see NullEscherSerializationListener
+ */
+ public int serialize( int offset, byte[] data, EscherSerializationListener listener )
+ {
+ listener.beforeRecordSerialize( offset, getRecordId(), this );
+ LittleEndian.putShort( data, offset, getOptions() );
+ LittleEndian.putShort( data, offset + 2, getRecordId() );
+ int remainingBytes = 16;
+ LittleEndian.putInt( data, offset + 4, remainingBytes );
+ LittleEndian.putInt( data, offset + 8, field_1_rectX1 );
+ LittleEndian.putInt( data, offset + 12, field_2_rectY1 );
+ LittleEndian.putInt( data, offset + 16, field_3_rectX2 );
+ LittleEndian.putInt( data, offset + 20, field_4_rectY2 );
+// System.arraycopy( remainingData, 0, data, offset + 26, remainingData.length );
+// int pos = offset + 8 + 18 + remainingData.length;
+ listener.afterRecordSerialize( offset + getRecordSize(), getRecordId(), offset + getRecordSize(), this );
+ return 8 + 16;
+ }
+
+ /**
+ * Returns the number of bytes that are required to serialize this record.
+ *
+ * @return Number of bytes
+ */
+ public int getRecordSize()
+ {
+ return 8 + 16;
+ }
+
+ /**
+ * The 16 bit identifier of this shape group record.
+ */
+ public short getRecordId()
+ {
+ return RECORD_ID;
+ }
+
+ /**
+ * The short name for this record
+ */
+ public String getRecordName()
+ {
+ return "Spgr";
+ }
+
+ /**
+ * @return the string representation of this record.
+ */
+ public String toString()
+ {
+ String nl = System.getProperty("line.separator");
+
+// String extraData;
+// ByteArrayOutputStream b = new ByteArrayOutputStream();
+// try
+// {
+// HexDump.dump(this.remainingData, 0, b, 0);
+// extraData = b.toString();
+// }
+// catch ( Exception e )
+// {
+// extraData = "error";
+// }
+ return getClass().getName() + ":" + nl +
+ " RecordId: 0x" + HexDump.toHex(RECORD_ID) + nl +
+ " Options: 0x" + HexDump.toHex(getOptions()) + nl +
+ " RectX: " + field_1_rectX1 + nl +
+ " RectY: " + field_2_rectY1 + nl +
+ " RectWidth: " + field_3_rectX2 + nl +
+ " RectHeight: " + field_4_rectY2 + nl;
+
+ }
+
+ /**
+ * The starting top-left coordinate of child records.
+ */
+ public int getRectX1()
+ {
+ return field_1_rectX1;
+ }
+
+ /**
+ * The starting top-left coordinate of child records.
+ */
+ public void setRectX1( int x1 )
+ {
+ this.field_1_rectX1 = x1;
+ }
+
+ /**
+ * The starting top-left coordinate of child records.
+ */
+ public int getRectY1()
+ {
+ return field_2_rectY1;
+ }
+
+ /**
+ * The starting top-left coordinate of child records.
+ */
+ public void setRectY1( int y1 )
+ {
+ this.field_2_rectY1 = y1;
+ }
+
+ /**
+ * The starting bottom-right coordinate of child records.
+ */
+ public int getRectX2()
+ {
+ return field_3_rectX2;
+ }
+
+ /**
+ * The starting bottom-right coordinate of child records.
+ */
+ public void setRectX2( int x2 )
+ {
+ this.field_3_rectX2 = x2;
+ }
+
+ /**
+ * The starting bottom-right coordinate of child records.
+ */
+ public int getRectY2()
+ {
+ return field_4_rectY2;
+ }
+
+ /**
+ * The starting bottom-right coordinate of child records.
+ */
+ public void setRectY2( int field_4_rectY2 )
+ {
+ this.field_4_rectY2 = field_4_rectY2;
+ }
+}
diff --git a/src/java/org/apache/poi/ddf/EscherSplitMenuColorsRecord.java b/src/java/org/apache/poi/ddf/EscherSplitMenuColorsRecord.java
new file mode 100644
index 000000000..2790d4eac
--- /dev/null
+++ b/src/java/org/apache/poi/ddf/EscherSplitMenuColorsRecord.java
@@ -0,0 +1,171 @@
+package org.apache.poi.ddf;
+
+import org.apache.poi.util.HexDump;
+import org.apache.poi.util.LittleEndian;
+import org.apache.poi.hssf.record.RecordFormatException;
+
+/**
+ * A list of the most recently used colours for the drawings contained in
+ * this document.
+ *
+ * @author Glen Stampoultzis (glens at apache.org)
+ */
+public class EscherSplitMenuColorsRecord
+ extends EscherRecord
+{
+ public static final short RECORD_ID = (short) 0xF11E;
+ public static final String RECORD_DESCRIPTION = "MsofbtSplitMenuColors";
+
+ private int field_1_color1;
+ private int field_2_color2;
+ private int field_3_color3;
+ private int field_4_color4;
+
+ /**
+ * This method deserializes the record from a byte array.
+ *
+ * @param data The byte array containing the escher record information
+ * @param offset The starting offset into data
.
+ * @param recordFactory May be null since this is not a container record.
+ * @return The number of bytes read from the byte array.
+ */
+ public int fillFields( byte[] data, int offset, EscherRecordFactory recordFactory )
+ {
+ int bytesRemaining = readHeader( data, offset );
+ int pos = offset + 8;
+ int size = 0;
+ field_1_color1 = LittleEndian.getInt( data, pos + size );size+=4;
+ field_2_color2 = LittleEndian.getInt( data, pos + size );size+=4;
+ field_3_color3 = LittleEndian.getInt( data, pos + size );size+=4;
+ field_4_color4 = LittleEndian.getInt( data, pos + size );size+=4;
+ bytesRemaining -= size;
+ if (bytesRemaining != 0)
+ throw new RecordFormatException("Expecting no remaining data but got " + bytesRemaining + " byte(s).");
+ return 8 + size + bytesRemaining;
+ }
+
+ /**
+ * This method serializes this escher record into a byte array.
+ *
+ * @param offset The offset into data
to start writing the record data to.
+ * @param data The byte array to serialize to.
+ * @param listener A listener to retrieve start and end callbacks. Use a NullEscherSerailizationListener
to ignore these events.
+ * @return The number of bytes written.
+ *
+ * @see NullEscherSerializationListener
+ */
+ public int serialize( int offset, byte[] data, EscherSerializationListener listener )
+ {
+// int field_2_numIdClusters = field_5_fileIdClusters.length + 1;
+ listener.beforeRecordSerialize( offset, getRecordId(), this );
+
+ int pos = offset;
+ LittleEndian.putShort( data, pos, getOptions() ); pos += 2;
+ LittleEndian.putShort( data, pos, getRecordId() ); pos += 2;
+ int remainingBytes = getRecordSize() - 8;
+
+ LittleEndian.putInt( data, pos, remainingBytes ); pos += 4;
+ LittleEndian.putInt( data, pos, field_1_color1 ); pos += 4;
+ LittleEndian.putInt( data, pos, field_2_color2 ); pos += 4;
+ LittleEndian.putInt( data, pos, field_3_color3 ); pos += 4;
+ LittleEndian.putInt( data, pos, field_4_color4 ); pos += 4;
+ listener.afterRecordSerialize( pos, getRecordId(), pos - offset, this );
+ return getRecordSize();
+ }
+
+ /**
+ * Returns the number of bytes that are required to serialize this record.
+ *
+ * @return Number of bytes
+ */
+ public int getRecordSize()
+ {
+ return 8 + 4 * 4;
+ }
+
+ /**
+ * @return the 16 bit identifer for this record.
+ */
+ public short getRecordId()
+ {
+ return RECORD_ID;
+ }
+
+ /**
+ * The short name for this record
+ */
+ public String getRecordName()
+ {
+ return "SplitMenuColors";
+ }
+
+ /**
+ * @return a string representation of this record.
+ */
+ public String toString()
+ {
+ String nl = System.getProperty("line.separator");
+
+// String extraData;
+// ByteArrayOutputStream b = new ByteArrayOutputStream();
+// try
+// {
+// HexDump.dump(this.remainingData, 0, b, 0);
+// extraData = b.toString();
+// }
+// catch ( Exception e )
+// {
+// extraData = "error";
+// }
+ return getClass().getName() + ":" + nl +
+ " RecordId: 0x" + HexDump.toHex(RECORD_ID) + nl +
+ " Options: 0x" + HexDump.toHex(getOptions()) + nl +
+ " Color1: 0x" + HexDump.toHex(field_1_color1) + nl +
+ " Color2: 0x" + HexDump.toHex(field_2_color2) + nl +
+ " Color3: 0x" + HexDump.toHex(field_3_color3) + nl +
+ " Color4: 0x" + HexDump.toHex(field_4_color4) + nl +
+ "";
+
+ }
+
+ public int getColor1()
+ {
+ return field_1_color1;
+ }
+
+ public void setColor1( int field_1_color1 )
+ {
+ this.field_1_color1 = field_1_color1;
+ }
+
+ public int getColor2()
+ {
+ return field_2_color2;
+ }
+
+ public void setColor2( int field_2_color2 )
+ {
+ this.field_2_color2 = field_2_color2;
+ }
+
+ public int getColor3()
+ {
+ return field_3_color3;
+ }
+
+ public void setColor3( int field_3_color3 )
+ {
+ this.field_3_color3 = field_3_color3;
+ }
+
+ public int getColor4()
+ {
+ return field_4_color4;
+ }
+
+ public void setColor4( int field_4_color4 )
+ {
+ this.field_4_color4 = field_4_color4;
+ }
+
+}
diff --git a/src/java/org/apache/poi/ddf/EscherTextboxRecord.java b/src/java/org/apache/poi/ddf/EscherTextboxRecord.java
new file mode 100644
index 000000000..34dc4fca2
--- /dev/null
+++ b/src/java/org/apache/poi/ddf/EscherTextboxRecord.java
@@ -0,0 +1,215 @@
+/* ====================================================================
+ * The Apache Software License, Version 1.1
+ *
+ * Copyright (c) 2002 The Apache Software Foundation. All rights
+ * reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ *
+ * 3. The end-user documentation included with the redistribution,
+ * if any, must include the following acknowledgment:
+ * "This product includes software developed by the
+ * Apache Software Foundation (http://www.apache.org/)."
+ * Alternately, this acknowledgment may appear in the software itself,
+ * if and wherever such third-party acknowledgments normally appear.
+ *
+ * 4. The names "Apache" and "Apache Software Foundation" and
+ * "Apache POI" must not be used to endorse or promote products
+ * derived from this software without prior written permission. For
+ * written permission, please contact apache@apache.org.
+ *
+ * 5. Products derived from this software may not be called "Apache",
+ * "Apache POI", nor may "Apache" appear in their name, without
+ * prior written permission of the Apache Software Foundation.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
+ * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation. For more
+ * information on the Apache Software Foundation, please see
+ * data
.
+ * @param recordFactory May be null since this is not a container record.
+ * @return The number of bytes read from the byte array.
+ */
+ public int fillFields( byte[] data, int offset, EscherRecordFactory recordFactory )
+ {
+ int bytesRemaining = readHeader( data, offset );
+ if ( isContainerRecord() )
+ {
+ int bytesWritten = 0;
+ thedata = new byte[0];
+ offset += 8;
+ bytesWritten += 8;
+ while ( bytesRemaining > 0 )
+ {
+ EscherRecord child = recordFactory.createRecord( data, offset );
+ int childBytesWritten = child.fillFields( data, offset, recordFactory );
+ bytesWritten += childBytesWritten;
+ offset += childBytesWritten;
+ bytesRemaining -= childBytesWritten;
+ getChildRecords().add( child );
+ }
+ return bytesWritten;
+ }
+ else
+ {
+ thedata = new byte[bytesRemaining];
+ System.arraycopy( data, offset + 8, thedata, 0, bytesRemaining );
+ return bytesRemaining + 8;
+ }
+ }
+
+ /**
+ * Writes this record and any contained records to the supplied byte
+ * array.
+ *
+ * @return the number of bytes written.
+ */
+ public int serialize( int offset, byte[] data, EscherSerializationListener listener )
+ {
+ listener.beforeRecordSerialize( offset, getRecordId(), this );
+
+ LittleEndian.putShort(data, offset, getOptions());
+ LittleEndian.putShort(data, offset+2, getRecordId());
+ int remainingBytes = thedata.length;
+ for ( Iterator iterator = getChildRecords().iterator(); iterator.hasNext(); )
+ {
+ EscherRecord r = (EscherRecord) iterator.next();
+ remainingBytes += r.getRecordSize();
+ }
+ LittleEndian.putInt(data, offset+4, remainingBytes);
+ System.arraycopy(thedata, 0, data, offset+8, thedata.length);
+ int pos = offset+8+thedata.length;
+ for ( Iterator iterator = getChildRecords().iterator(); iterator.hasNext(); )
+ {
+ EscherRecord r = (EscherRecord) iterator.next();
+ pos += r.serialize(pos, data, listener );
+ }
+
+ listener.afterRecordSerialize( pos, getRecordId(), pos - offset, this );
+ int size = pos - offset;
+ if (size != getRecordSize())
+ throw new RecordFormatException(size + " bytes written but getRecordSize() reports " + getRecordSize());
+ return size;
+ }
+
+ /**
+ * Returns any extra data associated with this record. In practice excel
+ * does not seem to put anything here.
+ */
+ public byte[] getData()
+ {
+ return thedata;
+ }
+
+ /**
+ * Returns the number of bytes that are required to serialize this record.
+ *
+ * @return Number of bytes
+ */
+ public int getRecordSize()
+ {
+ return 8 + thedata.length;
+ }
+
+ public Object clone()
+ {
+ // shallow clone
+ return super.clone();
+ }
+
+ /**
+ * The short name for this record
+ */
+ public String getRecordName()
+ {
+ return "ClientTextbox";
+ }
+
+ public String toString()
+ {
+ String nl = System.getProperty( "line.separator" );
+
+ String theDumpHex = "";
+ try
+ {
+ if (thedata.length != 0)
+ {
+ theDumpHex = " Extra Data:" + nl;
+ theDumpHex += HexDump.dump(thedata, 0, 0);
+ }
+ }
+ catch ( Exception e )
+ {
+ theDumpHex = "Error!!";
+ }
+
+ return getClass().getName() + ":" + nl +
+ " isContainer: " + isContainerRecord() + nl +
+ " options: 0x" + HexDump.toHex( getOptions() ) + nl +
+ " recordId: 0x" + HexDump.toHex( getRecordId() ) + nl +
+ " numchildren: " + getChildRecords().size() + nl +
+ theDumpHex;
+ }
+
+}
+
+
+
diff --git a/src/java/org/apache/poi/ddf/NullEscherSerializationListener.java b/src/java/org/apache/poi/ddf/NullEscherSerializationListener.java
new file mode 100644
index 000000000..10a1e798c
--- /dev/null
+++ b/src/java/org/apache/poi/ddf/NullEscherSerializationListener.java
@@ -0,0 +1,20 @@
+package org.apache.poi.ddf;
+
+/**
+ * Ignores all serialization events.
+ *
+ * @author Glen Stampoultzis (glens at apache.org)
+ */
+public class NullEscherSerializationListener implements EscherSerializationListener
+{
+ public void beforeRecordSerialize( int offset, short recordId, EscherRecord record )
+ {
+ // do nothing
+ }
+
+ public void afterRecordSerialize( int offset, short recordId, int size, EscherRecord record )
+ {
+ // do nothing
+ }
+
+}
diff --git a/src/java/org/apache/poi/ddf/UnknownEscherRecord.java b/src/java/org/apache/poi/ddf/UnknownEscherRecord.java
new file mode 100644
index 000000000..e2d21fbab
--- /dev/null
+++ b/src/java/org/apache/poi/ddf/UnknownEscherRecord.java
@@ -0,0 +1,234 @@
+/* ====================================================================
+ * The Apache Software License, Version 1.1
+ *
+ * Copyright (c) 2002 The Apache Software Foundation. All rights
+ * reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ *
+ * 3. The end-user documentation included with the redistribution,
+ * if any, must include the following acknowledgment:
+ * "This product includes software developed by the
+ * Apache Software Foundation (http://www.apache.org/)."
+ * Alternately, this acknowledgment may appear in the software itself,
+ * if and wherever such third-party acknowledgments normally appear.
+ *
+ * 4. The names "Apache" and "Apache Software Foundation" and
+ * "Apache POI" must not be used to endorse or promote products
+ * derived from this software without prior written permission. For
+ * written permission, please contact apache@apache.org.
+ *
+ * 5. Products derived from this software may not be called "Apache",
+ * "Apache POI", nor may "Apache" appear in their name, without
+ * prior written permission of the Apache Software Foundation.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
+ * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation. For more
+ * information on the Apache Software Foundation, please see
+ * data
.
+ * @param recordFactory May be null since this is not a container record.
+ * @return The number of bytes read from the byte array.
+ */
+ public int fillFields( byte[] data, int offset, EscherRecordFactory recordFactory )
+ {
+ int bytesRemaining = readHeader( data, offset );
+ if ( isContainerRecord() )
+ {
+ int bytesWritten = 0;
+ thedata = new byte[0];
+ offset += 8;
+ bytesWritten += 8;
+ while ( bytesRemaining > 0 )
+ {
+ EscherRecord child = recordFactory.createRecord( data, offset );
+ int childBytesWritten = child.fillFields( data, offset, recordFactory );
+ bytesWritten += childBytesWritten;
+ offset += childBytesWritten;
+ bytesRemaining -= childBytesWritten;
+ getChildRecords().add( child );
+ }
+ return bytesWritten;
+ }
+ else
+ {
+ thedata = new byte[bytesRemaining];
+ System.arraycopy( data, offset + 8, thedata, 0, bytesRemaining );
+ return bytesRemaining + 8;
+ }
+ }
+
+ /**
+ * Writes this record and any contained records to the supplied byte
+ * array.
+ *
+ * @return the number of bytes written.
+ */
+ public int serialize( int offset, byte[] data, EscherSerializationListener listener )
+ {
+ listener.beforeRecordSerialize( offset, getRecordId(), this );
+
+ LittleEndian.putShort(data, offset, getOptions());
+ LittleEndian.putShort(data, offset+2, getRecordId());
+ int remainingBytes = thedata.length;
+ for ( Iterator iterator = getChildRecords().iterator(); iterator.hasNext(); )
+ {
+ EscherRecord r = (EscherRecord) iterator.next();
+ remainingBytes += r.getRecordSize();
+ }
+ LittleEndian.putInt(data, offset+4, remainingBytes);
+ System.arraycopy(thedata, 0, data, offset+8, thedata.length);
+ int pos = offset+8+thedata.length;
+ for ( Iterator iterator = getChildRecords().iterator(); iterator.hasNext(); )
+ {
+ EscherRecord r = (EscherRecord) iterator.next();
+ pos += r.serialize(pos, data, listener );
+ }
+
+ listener.afterRecordSerialize( pos, getRecordId(), pos - offset, this );
+ return pos - offset;
+ }
+
+ public byte[] getData()
+ {
+ return thedata;
+ }
+
+ /**
+ * Returns the number of bytes that are required to serialize this record.
+ *
+ * @return Number of bytes
+ */
+ public int getRecordSize()
+ {
+ return 8 + thedata.length;
+ }
+
+ public List getChildRecords()
+ {
+ return childRecords;
+ }
+
+ public void setChildRecords( List childRecords )
+ {
+ this.childRecords = childRecords;
+ }
+
+ public Object clone()
+ {
+ // shallow clone
+ return super.clone();
+ }
+
+ /**
+ * The short name for this record
+ */
+ public String getRecordName()
+ {
+ return "Unknown 0x" + HexDump.toHex(getRecordId());
+ }
+
+ public String toString()
+ {
+ String nl = System.getProperty( "line.separator" );
+
+ StringBuffer children = new StringBuffer();
+ if ( getChildRecords().size() > 0 )
+ {
+ children.append( " children: " + nl );
+ for ( Iterator iterator = getChildRecords().iterator(); iterator.hasNext(); )
+ {
+ EscherRecord record = (EscherRecord) iterator.next();
+ children.append( record.toString() );
+ children.append( nl );
+ }
+ }
+
+ String theDumpHex = "";
+ try
+ {
+ if (thedata.length != 0)
+ {
+ theDumpHex = " Extra Data:" + nl;
+ theDumpHex += HexDump.dump(thedata, 0, 0);
+ }
+ }
+ catch ( Exception e )
+ {
+ theDumpHex = "Error!!";
+ }
+
+ return getClass().getName() + ":" + nl +
+ " isContainer: " + isContainerRecord() + nl +
+ " options: 0x" + HexDump.toHex( getOptions() ) + nl +
+ " recordId: 0x" + HexDump.toHex( getRecordId() ) + nl +
+ " numchildren: " + getChildRecords().size() + nl +
+ theDumpHex +
+ children.toString();
+ }
+
+ public void addChildRecord( EscherRecord childRecord )
+ {
+ getChildRecords().add( childRecord );
+ }
+
+}
+
+
+
diff --git a/src/java/org/apache/poi/ddf/package.html b/src/java/org/apache/poi/ddf/package.html
new file mode 100644
index 000000000..3205d487d
--- /dev/null
+++ b/src/java/org/apache/poi/ddf/package.html
@@ -0,0 +1,11 @@
+
+
+
This package contains classes for decoding the Microsoft Office + Drawing format otherwise known as escher henceforth known in POI + as the Dreadful Drawing Format. +
+ + + \ No newline at end of file