--- /dev/null
+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;
+ }
+
+}
--- /dev/null
+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;
+ }
+
+}
--- /dev/null
+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 <code>EscherBlipRecord</code> 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 <code>data</code>.
+ * @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 <code>data</code> 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 <code>NullEscherSerailizationListener</code> 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";
+ }
+ }
+
+
+}
--- /dev/null
+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 <code>data</code>.
+ * @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 <code>data</code> 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 <code>NullEscherSerailizationListener</code> 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();
+ }
+
+}
--- /dev/null
+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);
+// }
+
+}
--- /dev/null
+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 <code>data</code>.
+ * @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 <code>data</code> 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 <code>NullEscherSerailizationListener</code> 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;
+ }
+
+}
--- /dev/null
+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 <code>data</code>.
+ * @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 <code>data</code> 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 <code>NullEscherSerailizationListener</code> 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;
+ }
+}
--- /dev/null
+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 <code>data</code>.
+ * @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 <code>data</code> 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 <code>NullEscherSerailizationListener</code> 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;
+ }
+}
--- /dev/null
+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;
+ }
+
+}
--- /dev/null
+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;
+ }
+
+}
--- /dev/null
+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 <code>data</code>.
+ * @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 <code>data</code> 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 <code>NullEscherSerailizationListener</code> 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++;
+ }
+}
--- /dev/null
+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 <code>data</code>.
+ * @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 <code>data</code> 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 <code>NullEscherSerailizationListener</code> 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()] );
+ }
+}
--- /dev/null
+/* ====================================================================
+ * 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
+ * <http://www.apache.org/>.
+ */
+
+package org.apache.poi.ddf;
+
+import org.apache.poi.util.HexDump;
+import org.apache.poi.util.HexRead;
+import org.apache.poi.util.LittleEndian;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.PrintStream;
+import java.util.zip.InflaterInputStream;
+
+/**
+ * Used to dump the contents of escher records to a PrintStream.
+ *
+ * @author Glen Stampoultzis (glens at apache.org)
+ */
+public class EscherDump
+{
+
+ public EscherDump()
+ {
+ }
+
+ /**
+ * Decodes the escher stream from a byte array and dumps the results to
+ * a print stream.
+ *
+ * @param data The data array containing the escher records.
+ * @param offset The starting offset within the data array.
+ * @param size The number of bytes to read.
+ * @param out The output stream to write the results to.
+ *
+ */
+ public void dump( byte[] data, int offset, int size, PrintStream out ) throws IOException, LittleEndian.BufferUnderrunException
+ {
+ EscherRecordFactory recordFactory = new DefaultEscherRecordFactory();
+ int pos = offset;
+ while ( pos < offset + size )
+ {
+ EscherRecord r = recordFactory.createRecord(data, pos);
+ int bytesRead = r.fillFields(data, pos, recordFactory );
+ System.out.println( r.toString() );
+ pos += bytesRead;
+ }
+ }
+
+ /**
+ * This version of dump is a translation from the open office escher dump routine.
+ *
+ * @param maxLength The number of bytes to read
+ * @param in An input stream to read from.
+ * @param out An output stream to write to.
+ */
+ public void dumpOld( long maxLength, InputStream in, PrintStream out ) throws IOException, LittleEndian.BufferUnderrunException
+ {
+ long remainingBytes = maxLength;
+ short options; // 4 bits for the version and 12 bits for the instance
+ short recordId;
+ int recordBytesRemaining; // including enclosing records
+ StringBuffer stringBuf = new StringBuffer();
+ short nDumpSize;
+ String recordName;
+
+ boolean atEOF = false;
+
+ while ( !atEOF && ( remainingBytes > 0 ) )
+ {
+ stringBuf = new StringBuffer();
+ options = LittleEndian.readShort( in );
+ recordId = LittleEndian.readShort( in );
+ recordBytesRemaining = LittleEndian.readInt( in );
+
+ remainingBytes -= 2 + 2 + 4;
+
+ switch ( recordId )
+ {
+ case (short) 0xF000:
+ recordName = "MsofbtDggContainer";
+ break;
+ case (short) 0xF006:
+ recordName = "MsofbtDgg";
+ break;
+ case (short) 0xF016:
+ recordName = "MsofbtCLSID";
+ break;
+ case (short) 0xF00B:
+ recordName = "MsofbtOPT";
+ break;
+ case (short) 0xF11A:
+ recordName = "MsofbtColorMRU";
+ break;
+ case (short) 0xF11E:
+ recordName = "MsofbtSplitMenuColors";
+ break;
+ case (short) 0xF001:
+ recordName = "MsofbtBstoreContainer";
+ break;
+ case (short) 0xF007:
+ recordName = "MsofbtBSE";
+ break;
+ case (short) 0xF002:
+ recordName = "MsofbtDgContainer";
+ break;
+ case (short) 0xF008:
+ recordName = "MsofbtDg";
+ break;
+ case (short) 0xF118:
+ recordName = "MsofbtRegroupItem";
+ break;
+ case (short) 0xF120:
+ recordName = "MsofbtColorScheme";
+ break;
+ case (short) 0xF003:
+ recordName = "MsofbtSpgrContainer";
+ break;
+ case (short) 0xF004:
+ recordName = "MsofbtSpContainer";
+ break;
+ case (short) 0xF009:
+ recordName = "MsofbtSpgr";
+ break;
+ case (short) 0xF00A:
+ recordName = "MsofbtSp";
+ break;
+ case (short) 0xF00C:
+ recordName = "MsofbtTextbox";
+ break;
+ case (short) 0xF00D:
+ recordName = "MsofbtClientTextbox";
+ break;
+ case (short) 0xF00E:
+ recordName = "MsofbtAnchor";
+ break;
+ case (short) 0xF00F:
+ recordName = "MsofbtChildAnchor";
+ break;
+ case (short) 0xF010:
+ recordName = "MsofbtClientAnchor";
+ break;
+ case (short) 0xF011:
+ recordName = "MsofbtClientData";
+ break;
+ case (short) 0xF11F:
+ recordName = "MsofbtOleObject";
+ break;
+ case (short) 0xF11D:
+ recordName = "MsofbtDeletedPspl";
+ break;
+ case (short) 0xF005:
+ recordName = "MsofbtSolverContainer";
+ break;
+ case (short) 0xF012:
+ recordName = "MsofbtConnectorRule";
+ break;
+ case (short) 0xF013:
+ recordName = "MsofbtAlignRule";
+ break;
+ case (short) 0xF014:
+ recordName = "MsofbtArcRule";
+ break;
+ case (short) 0xF015:
+ recordName = "MsofbtClientRule";
+ break;
+ case (short) 0xF017:
+ recordName = "MsofbtCalloutRule";
+ break;
+ case (short) 0xF119:
+ recordName = "MsofbtSelection";
+ break;
+ case (short) 0xF122:
+ recordName = "MsofbtUDefProp";
+ break;
+ default:
+ if ( recordId >= (short) 0xF018 && recordId <= (short) 0xF117 )
+ recordName = "MsofbtBLIP";
+ else if ( ( options & (short) 0x000F ) == (short) 0x000F )
+ recordName = "UNKNOWN container";
+ else
+ recordName = "UNKNOWN ID";
+ }
+
+ stringBuf.append( " " );
+ stringBuf.append( HexDump.toHex( recordId ) );
+ stringBuf.append( " " ).append( recordName ).append( " [" );
+ stringBuf.append( HexDump.toHex( options ) );
+ stringBuf.append( ',' );
+ stringBuf.append( HexDump.toHex( recordBytesRemaining ) );
+ stringBuf.append( "] instance: " );
+ stringBuf.append( HexDump.toHex( ( (short) ( options >> 4 ) ) ) );
+ out.println( stringBuf.toString() );
+
+
+ if ( recordId == (short) 0xF007 && 36 <= remainingBytes && 36 <= recordBytesRemaining )
+ { // BSE, FBSE
+ // ULONG nP = pIn->GetRecPos();
+
+ byte n8;
+ // short n16;
+ // int n32;
+
+ stringBuf = new StringBuffer( " btWin32: " );
+ n8 = (byte) in.read();
+ stringBuf.append( HexDump.toHex( n8 ) );
+ stringBuf.append( getBlipType( n8 ) );
+ stringBuf.append( " btMacOS: " );
+ n8 = (byte) in.read();
+ stringBuf.append( HexDump.toHex( n8 ) );
+ stringBuf.append( getBlipType( n8 ) );
+ out.println( stringBuf.toString() );
+
+ out.println( " rgbUid:" );
+ HexDump.dump( in, out, 0, 16 );
+
+ out.print( " tag: " );
+ outHex( 2, in, out );
+ out.println();
+ out.print( " size: " );
+ outHex( 4, in, out );
+ out.println();
+ out.print( " cRef: " );
+ outHex( 4, in, out );
+ out.println();
+ out.print( " offs: " );
+ outHex( 4, in, out );
+ out.println();
+ out.print( " usage: " );
+ outHex( 1, in, out );
+ out.println();
+ out.print( " cbName: " );
+ outHex( 1, in, out );
+ out.println();
+ out.print( " unused2: " );
+ outHex( 1, in, out );
+ out.println();
+ out.print( " unused3: " );
+ outHex( 1, in, out );
+ out.println();
+
+ // subtract the number of bytes we've read
+ remainingBytes -= 36;
+ //n -= pIn->GetRecPos() - nP;
+ recordBytesRemaining = 0; // loop to MsofbtBLIP
+ }
+ else if ( recordId == (short) 0xF010 && 0x12 <= remainingBytes && 0x12 <= recordBytesRemaining )
+ { // ClientAnchor
+ //ULONG nP = pIn->GetRecPos();
+ // short n16;
+
+ out.print( " Flag: " );
+ outHex( 2, in, out );
+ out.println();
+ out.print( " Col1: " );
+ outHex( 2, in, out );
+ out.print( " dX1: " );
+ outHex( 2, in, out );
+ out.print( " Row1: " );
+ outHex( 2, in, out );
+ out.print( " dY1: " );
+ outHex( 2, in, out );
+ out.println();
+ out.print( " Col2: " );
+ outHex( 2, in, out );
+ out.print( " dX2: " );
+ outHex( 2, in, out );
+ out.print( " Row2: " );
+ outHex( 2, in, out );
+ out.print( " dY2: " );
+ outHex( 2, in, out );
+ out.println();
+
+ remainingBytes -= 18;
+ recordBytesRemaining -= 18;
+
+ }
+ else if ( recordId == (short) 0xF00B || recordId == (short) 0xF122 )
+ { // OPT
+ int nComplex = 0;
+ out.println( " PROPID VALUE" );
+ while ( recordBytesRemaining >= 6 + nComplex && remainingBytes >= 6 + nComplex )
+ {
+ short n16;
+ int n32;
+ n16 = LittleEndian.readShort( in );
+ n32 = LittleEndian.readInt( in );
+
+ recordBytesRemaining -= 6;
+ remainingBytes -= 6;
+ out.print( " " );
+ out.print( HexDump.toHex( n16 ) );
+ out.print( " (" );
+ int propertyId = n16 & (short) 0x3FFF;
+ out.print( " " + propertyId );
+ if ( ( n16 & (short) 0x8000 ) == 0 )
+ {
+ if ( ( n16 & (short) 0x4000 ) != 0 )
+ out.print( ", fBlipID" );
+ out.print( ") " );
+
+ out.print( HexDump.toHex( n32 ) );
+
+ if ( ( n16 & (short) 0x4000 ) == 0 )
+ {
+ out.print( " (" );
+ out.print( dec1616( n32 ) );
+ out.print( ')' );
+ out.print( " {" + propName( (short)propertyId ) + "}" );
+ }
+ out.println();
+ }
+ else
+ {
+ out.print( ", fComplex) " );
+ out.print( HexDump.toHex( n32 ) );
+ out.print( " - Complex prop len" );
+ out.println( " {" + propName( (short)propertyId ) + "}" );
+
+ nComplex += n32;
+ }
+
+ }
+ // complex property data
+ while ( ( nComplex & remainingBytes ) > 0 )
+ {
+ nDumpSize = ( nComplex > (int) remainingBytes ) ? (short) remainingBytes : (short) nComplex;
+ HexDump.dump( in, out, 0, nDumpSize );
+ nComplex -= nDumpSize;
+ recordBytesRemaining -= nDumpSize;
+ remainingBytes -= nDumpSize;
+ }
+ }
+ else if ( recordId == (short) 0xF012 )
+ {
+ out.print( " Connector rule: " );
+ out.print( LittleEndian.readInt( in ) );
+ out.print( " ShapeID A: " );
+ out.print( LittleEndian.readInt( in ) );
+ out.print( " ShapeID B: " );
+ out.print( LittleEndian.readInt( in ) );
+ out.print( " ShapeID connector: " );
+ out.print( LittleEndian.readInt( in ) );
+ out.print( " Connect pt A: " );
+ out.print( LittleEndian.readInt( in ) );
+ out.print( " Connect pt B: " );
+ out.println( LittleEndian.readInt( in ) );
+
+ recordBytesRemaining -= 24;
+ remainingBytes -= 24;
+ }
+ else if ( recordId >= (short) 0xF018 && recordId < (short) 0xF117 )
+ {
+ out.println( " Secondary UID: " );
+ HexDump.dump( in, out, 0, 16 );
+ out.println( " Cache of size: " + HexDump.toHex( LittleEndian.readInt( in ) ) );
+ out.println( " Boundary top: " + HexDump.toHex( LittleEndian.readInt( in ) ) );
+ out.println( " Boundary left: " + HexDump.toHex( LittleEndian.readInt( in ) ) );
+ out.println( " Boundary width: " + HexDump.toHex( LittleEndian.readInt( in ) ) );
+ out.println( " Boundary height: " + HexDump.toHex( LittleEndian.readInt( in ) ) );
+ out.println( " X: " + HexDump.toHex( LittleEndian.readInt( in ) ) );
+ out.println( " Y: " + HexDump.toHex( LittleEndian.readInt( in ) ) );
+ out.println( " Cache of saved size: " + HexDump.toHex( LittleEndian.readInt( in ) ) );
+ out.println( " Compression Flag: " + HexDump.toHex( (byte) in.read() ) );
+ out.println( " Filter: " + HexDump.toHex( (byte) in.read() ) );
+ out.println( " Data (after decompression): " );
+
+ recordBytesRemaining -= 34 + 16;
+ remainingBytes -= 34 + 16;
+
+ nDumpSize = ( recordBytesRemaining > (int) remainingBytes ) ? (short) remainingBytes : (short) recordBytesRemaining;
+
+
+ byte[] buf = new byte[nDumpSize];
+ int read = in.read( buf );
+ while ( read != -1 && read < nDumpSize )
+ read += in.read( buf, read, buf.length );
+ ByteArrayInputStream bin = new ByteArrayInputStream( buf );
+
+ InputStream in1 = new InflaterInputStream( bin );
+ int bytesToDump = -1;
+ HexDump.dump( in1, out, 0, bytesToDump );
+
+ recordBytesRemaining -= nDumpSize;
+ remainingBytes -= nDumpSize;
+
+ }
+
+ boolean isContainer = ( options & (short) 0x000F ) == (short) 0x000F;
+ if ( isContainer && remainingBytes >= 0 )
+ { // Container
+ if ( recordBytesRemaining <= (int) remainingBytes )
+ out.println( " completed within" );
+ else
+ out.println( " continued elsewhere" );
+ }
+ else if ( remainingBytes >= 0 )
+ // -> 0x0000 ... 0x0FFF
+ {
+ nDumpSize = ( recordBytesRemaining > (int) remainingBytes ) ? (short) remainingBytes : (short) recordBytesRemaining;
+
+ if ( nDumpSize != 0 )
+ {
+ HexDump.dump( in, out, 0, nDumpSize );
+ remainingBytes -= nDumpSize;
+ }
+ }
+ else
+ out.println( " >> OVERRUN <<" );
+ }
+
+ }
+
+ /**
+ * Returns a property name given a property id. This is used only by the
+ * old escher dump routine.
+ *
+ * @param propertyId The property number for the name
+ * @return A descriptive name.
+ */
+ private String propName( short propertyId )
+ {
+ class PropName {
+ public PropName( int id, String name )
+ {
+ this.id = id;
+ this.name = name;
+ }
+
+ int id;
+ String name;
+ }
+
+ final PropName[] props = new PropName[] {
+ new PropName(4, "transform.rotation"),
+ new PropName(119, "protection.lockrotation"),
+ new PropName(120, "protection.lockaspectratio"),
+ new PropName(121, "protection.lockposition"),
+ new PropName(122, "protection.lockagainstselect"),
+ new PropName(123, "protection.lockcropping"),
+ new PropName(124, "protection.lockvertices"),
+ new PropName(125, "protection.locktext"),
+ new PropName(126, "protection.lockadjusthandles"),
+ new PropName(127, "protection.lockagainstgrouping"),
+ new PropName(128, "text.textid"),
+ new PropName(129, "text.textleft"),
+ new PropName(130, "text.texttop"),
+ new PropName(131, "text.textright"),
+ new PropName(132, "text.textbottom"),
+ new PropName(133, "text.wraptext"),
+ new PropName(134, "text.scaletext"),
+ new PropName(135, "text.anchortext"),
+ new PropName(136, "text.textflow"),
+ new PropName(137, "text.fontrotation"),
+ new PropName(138, "text.idofnextshape"),
+ new PropName(139, "text.bidir"),
+ new PropName(187, "text.singleclickselects"),
+ new PropName(188, "text.usehostmargins"),
+ new PropName(189, "text.rotatetextwithshape"),
+ new PropName(190, "text.sizeshapetofittext"),
+ new PropName(191, "text.sizetexttofitshape"),
+ new PropName(192, "geotext.unicode"),
+ new PropName(193, "geotext.rtftext"),
+ new PropName(194, "geotext.alignmentoncurve"),
+ new PropName(195, "geotext.defaultpointsize"),
+ new PropName(196, "geotext.textspacing"),
+ new PropName(197, "geotext.fontfamilyname"),
+ new PropName(240, "geotext.reverseroworder"),
+ new PropName(241, "geotext.hastexteffect"),
+ new PropName(242, "geotext.rotatecharacters"),
+ new PropName(243, "geotext.kerncharacters"),
+ new PropName(244, "geotext.tightortrack"),
+ new PropName(245, "geotext.stretchtofitshape"),
+ new PropName(246, "geotext.charboundingbox"),
+ new PropName(247, "geotext.scaletextonpath"),
+ new PropName(248, "geotext.stretchcharheight"),
+ new PropName(249, "geotext.nomeasurealongpath"),
+ new PropName(250, "geotext.boldfont"),
+ new PropName(251, "geotext.italicfont"),
+ new PropName(252, "geotext.underlinefont"),
+ new PropName(253, "geotext.shadowfont"),
+ new PropName(254, "geotext.smallcapsfont"),
+ new PropName(255, "geotext.strikethroughfont"),
+ new PropName(256, "blip.cropfromtop"),
+ new PropName(257, "blip.cropfrombottom"),
+ new PropName(258, "blip.cropfromleft"),
+ new PropName(259, "blip.cropfromright"),
+ new PropName(260, "blip.bliptodisplay"),
+ new PropName(261, "blip.blipfilename"),
+ new PropName(262, "blip.blipflags"),
+ new PropName(263, "blip.transparentcolor"),
+ new PropName(264, "blip.contrastsetting"),
+ new PropName(265, "blip.brightnesssetting"),
+ new PropName(266, "blip.gamma"),
+ new PropName(267, "blip.pictureid"),
+ new PropName(268, "blip.doublemod"),
+ new PropName(269, "blip.picturefillmod"),
+ new PropName(270, "blip.pictureline"),
+ new PropName(271, "blip.printblip"),
+ new PropName(272, "blip.printblipfilename"),
+ new PropName(273, "blip.printflags"),
+ new PropName(316, "blip.nohittestpicture"),
+ new PropName(317, "blip.picturegray"),
+ new PropName(318, "blip.picturebilevel"),
+ new PropName(319, "blip.pictureactive"),
+ new PropName(320, "geometry.left"),
+ new PropName(321, "geometry.top"),
+ new PropName(322, "geometry.right"),
+ new PropName(323, "geometry.bottom"),
+ new PropName(324, "geometry.shapepath"),
+ new PropName(325, "geometry.vertices"),
+ new PropName(326, "geometry.segmentinfo"),
+ new PropName(327, "geometry.adjustvalue"),
+ new PropName(328, "geometry.adjust2value"),
+ new PropName(329, "geometry.adjust3value"),
+ new PropName(330, "geometry.adjust4value"),
+ new PropName(331, "geometry.adjust5value"),
+ new PropName(332, "geometry.adjust6value"),
+ new PropName(333, "geometry.adjust7value"),
+ new PropName(334, "geometry.adjust8value"),
+ new PropName(335, "geometry.adjust9value"),
+ new PropName(336, "geometry.adjust10value"),
+ new PropName(378, "geometry.shadowOK"),
+ new PropName(379, "geometry.3dok"),
+ new PropName(380, "geometry.lineok"),
+ new PropName(381, "geometry.geotextok"),
+ new PropName(382, "geometry.fillshadeshapeok"),
+ new PropName(383, "geometry.fillok"),
+ new PropName(384, "fill.filltype"),
+ new PropName(385, "fill.fillcolor"),
+ new PropName(386, "fill.fillopacity"),
+ new PropName(387, "fill.fillbackcolor"),
+ new PropName(388, "fill.backopacity"),
+ new PropName(389, "fill.crmod"),
+ new PropName(390, "fill.patterntexture"),
+ new PropName(391, "fill.blipfilename"),
+ new PropName(392, "fill.blipflags"),
+ new PropName(393, "fill.width"),
+ new PropName(394, "fill.height"),
+ new PropName(395, "fill.angle"),
+ new PropName(396, "fill.focus"),
+ new PropName(397, "fill.toleft"),
+ new PropName(398, "fill.totop"),
+ new PropName(399, "fill.toright"),
+ new PropName(400, "fill.tobottom"),
+ new PropName(401, "fill.rectleft"),
+ new PropName(402, "fill.recttop"),
+ new PropName(403, "fill.rectright"),
+ new PropName(404, "fill.rectbottom"),
+ new PropName(405, "fill.dztype"),
+ new PropName(406, "fill.shadepreset"),
+ new PropName(407, "fill.shadecolors"),
+ new PropName(408, "fill.originx"),
+ new PropName(409, "fill.originy"),
+ new PropName(410, "fill.shapeoriginx"),
+ new PropName(411, "fill.shapeoriginy"),
+ new PropName(412, "fill.shadetype"),
+ new PropName(443, "fill.filled"),
+ new PropName(444, "fill.hittestfill"),
+ new PropName(445, "fill.shape"),
+ new PropName(446, "fill.userect"),
+ new PropName(447, "fill.nofillhittest"),
+ new PropName(448, "linestyle.color"),
+ new PropName(449, "linestyle.opacity"),
+ new PropName(450, "linestyle.backcolor"),
+ new PropName(451, "linestyle.crmod"),
+ new PropName(452, "linestyle.linetype"),
+ new PropName(453, "linestyle.fillblip"),
+ new PropName(454, "linestyle.fillblipname"),
+ new PropName(455, "linestyle.fillblipflags"),
+ new PropName(456, "linestyle.fillwidth"),
+ new PropName(457, "linestyle.fillheight"),
+ new PropName(458, "linestyle.filldztype"),
+ new PropName(459, "linestyle.linewidth"),
+ new PropName(460, "linestyle.linemiterlimit"),
+ new PropName(461, "linestyle.linestyle"),
+ new PropName(462, "linestyle.linedashing"),
+ new PropName(463, "linestyle.linedashstyle"),
+ new PropName(464, "linestyle.linestartarrowhead"),
+ new PropName(465, "linestyle.lineendarrowhead"),
+ new PropName(466, "linestyle.linestartarrowwidth"),
+ new PropName(467, "linestyle.lineestartarrowlength"),
+ new PropName(468, "linestyle.lineendarrowwidth"),
+ new PropName(469, "linestyle.lineendarrowlength"),
+ new PropName(470, "linestyle.linejoinstyle"),
+ new PropName(471, "linestyle.lineendcapstyle"),
+ new PropName(507, "linestyle.arrowheadsok"),
+ new PropName(508, "linestyle.anyline"),
+ new PropName(509, "linestyle.hitlinetest"),
+ new PropName(510, "linestyle.linefillshape"),
+ new PropName(511, "linestyle.nolinedrawdash"),
+ new PropName(512, "shadowstyle.type"),
+ new PropName(513, "shadowstyle.color"),
+ new PropName(514, "shadowstyle.highlight"),
+ new PropName(515, "shadowstyle.crmod"),
+ new PropName(516, "shadowstyle.opacity"),
+ new PropName(517, "shadowstyle.offsetx"),
+ new PropName(518, "shadowstyle.offsety"),
+ new PropName(519, "shadowstyle.secondoffsetx"),
+ new PropName(520, "shadowstyle.secondoffsety"),
+ new PropName(521, "shadowstyle.scalextox"),
+ new PropName(522, "shadowstyle.scaleytox"),
+ new PropName(523, "shadowstyle.scalextoy"),
+ new PropName(524, "shadowstyle.scaleytoy"),
+ new PropName(525, "shadowstyle.perspectivex"),
+ new PropName(526, "shadowstyle.perspectivey"),
+ new PropName(527, "shadowstyle.weight"),
+ new PropName(528, "shadowstyle.originx"),
+ new PropName(529, "shadowstyle.originy"),
+ new PropName(574, "shadowstyle.shadow"),
+ new PropName(575, "shadowstyle.shadowobsured"),
+ new PropName(576, "perspective.type"),
+ new PropName(577, "perspective.offsetx"),
+ new PropName(578, "perspective.offsety"),
+ new PropName(579, "perspective.scalextox"),
+ new PropName(580, "perspective.scaleytox"),
+ new PropName(581, "perspective.scalextoy"),
+ new PropName(582, "perspective.scaleytox"),
+ new PropName(583, "perspective.perspectivex"),
+ new PropName(584, "perspective.perspectivey"),
+ new PropName(585, "perspective.weight"),
+ new PropName(586, "perspective.originx"),
+ new PropName(587, "perspective.originy"),
+ new PropName(639, "perspective.perspectiveon"),
+ new PropName(640, "3d.specularamount"),
+ new PropName(661, "3d.diffuseamount"),
+ new PropName(662, "3d.shininess"),
+ new PropName(663, "3d.edgethickness"),
+ new PropName(664, "3d.extrudeforward"),
+ new PropName(665, "3d.extrudebackward"),
+ new PropName(666, "3d.extrudeplane"),
+ new PropName(667, "3d.extrusioncolor"),
+ new PropName(648, "3d.crmod"),
+ new PropName(700, "3d.3deffect"),
+ new PropName(701, "3d.metallic"),
+ new PropName(702, "3d.useextrusioncolor"),
+ new PropName(703, "3d.lightface"),
+ new PropName(704, "3dstyle.yrotationangle"),
+ new PropName(705, "3dstyle.xrotationangle"),
+ new PropName(706, "3dstyle.rotationaxisx"),
+ new PropName(707, "3dstyle.rotationaxisy"),
+ new PropName(708, "3dstyle.rotationaxisz"),
+ new PropName(709, "3dstyle.rotationangle"),
+ new PropName(710, "3dstyle.rotationcenterx"),
+ new PropName(711, "3dstyle.rotationcentery"),
+ new PropName(712, "3dstyle.rotationcenterz"),
+ new PropName(713, "3dstyle.rendermode"),
+ new PropName(714, "3dstyle.tolerance"),
+ new PropName(715, "3dstyle.xviewpoint"),
+ new PropName(716, "3dstyle.yviewpoint"),
+ new PropName(717, "3dstyle.zviewpoint"),
+ new PropName(718, "3dstyle.originx"),
+ new PropName(719, "3dstyle.originy"),
+ new PropName(720, "3dstyle.skewangle"),
+ new PropName(721, "3dstyle.skewamount"),
+ new PropName(722, "3dstyle.ambientintensity"),
+ new PropName(723, "3dstyle.keyx"),
+ new PropName(724, "3dstyle.keyy"),
+ new PropName(725, "3dstyle.keyz"),
+ new PropName(726, "3dstyle.keyintensity"),
+ new PropName(727, "3dstyle.fillx"),
+ new PropName(728, "3dstyle.filly"),
+ new PropName(729, "3dstyle.fillz"),
+ new PropName(730, "3dstyle.fillintensity"),
+ new PropName(763, "3dstyle.constrainrotation"),
+ new PropName(764, "3dstyle.rotationcenterauto"),
+ new PropName(765, "3dstyle.parallel"),
+ new PropName(766, "3dstyle.keyharsh"),
+ new PropName(767, "3dstyle.fillharsh"),
+ new PropName(769, "shape.master"),
+ new PropName(771, "shape.connectorstyle"),
+ new PropName(772, "shape.blackandwhitesettings"),
+ new PropName(773, "shape.wmodepurebw"),
+ new PropName(774, "shape.wmodebw"),
+ new PropName(826, "shape.oleicon"),
+ new PropName(827, "shape.preferrelativeresize"),
+ new PropName(828, "shape.lockshapetype"),
+ new PropName(830, "shape.deleteattachedobject"),
+ new PropName(831, "shape.backgroundshape"),
+ new PropName(832, "callout.callouttype"),
+ new PropName(833, "callout.xycalloutgap"),
+ new PropName(834, "callout.calloutangle"),
+ new PropName(835, "callout.calloutdroptype"),
+ new PropName(836, "callout.calloutdropspecified"),
+ new PropName(837, "callout.calloutlengthspecified"),
+ new PropName(889, "callout.iscallout"),
+ new PropName(890, "callout.calloutaccentbar"),
+ new PropName(891, "callout.callouttextborder"),
+ new PropName(892, "callout.calloutminusx"),
+ new PropName(893, "callout.calloutminusy"),
+ new PropName(894, "callout.dropauto"),
+ new PropName(895, "callout.lengthspecified"),
+ new PropName(896, "groupshape.shapename"),
+ new PropName(897, "groupshape.description"),
+ new PropName(898, "groupshape.hyperlink"),
+ new PropName(899, "groupshape.wrappolygonvertices"),
+ new PropName(900, "groupshape.wrapdistleft"),
+ new PropName(901, "groupshape.wrapdisttop"),
+ new PropName(902, "groupshape.wrapdistright"),
+ new PropName(903, "groupshape.wrapdistbottom"),
+ new PropName(904, "groupshape.regroupid"),
+ new PropName(953, "groupshape.editedwrap"),
+ new PropName(954, "groupshape.behinddocument"),
+ new PropName(955, "groupshape.ondblclicknotify"),
+ new PropName(956, "groupshape.isbutton"),
+ new PropName(957, "groupshape.1dadjustment"),
+ new PropName(958, "groupshape.hidden"),
+ new PropName(959, "groupshape.print"),
+ };
+
+ for ( int i = 0; i < props.length; i++ )
+ {
+ if (props[i].id == propertyId)
+ {
+ return props[i].name;
+ }
+ }
+
+ return "unknown property";
+ }
+
+ /**
+ * Returns the blip description given a blip id.
+ *
+ * @param b blip id
+ * @return A description.
+ */
+ private String getBlipType( byte b )
+ {
+ switch ( b )
+ {
+ case 0:
+ return " ERROR";
+ case 1:
+ return " UNKNOWN";
+ case 2:
+ return " EMF";
+ case 3:
+ return " WMF";
+ case 4:
+ return " PICT";
+ case 5:
+ return " JPEG";
+ case 6:
+ return " PNG";
+ case 7:
+ return " DIB";
+ default:
+ if ( b < 32 )
+ return " NotKnown";
+ else
+ return " Client";
+ }
+ }
+
+ /**
+ * Straight conversion from OO. Converts a type of float.
+ */
+ private String dec1616( int n32 )
+ {
+ String result = "";
+ result += (short) ( n32 >> 16 );
+ result += '.';
+ result += (short) ( n32 & (short) 0xFFFF );
+ return result;
+ }
+
+ /**
+ * Dumps out a hex value by reading from a input stream.
+ *
+ * @param bytes How many bytes this hex value consists of.
+ * @param in The stream to read the hex value from.
+ * @param out The stream to write the nicely formatted hex value to.
+ */
+ private void outHex( int bytes, InputStream in, PrintStream out ) throws IOException, LittleEndian.BufferUnderrunException
+ {
+ switch ( bytes )
+ {
+ case 1:
+ out.print( HexDump.toHex( (byte) in.read() ) );
+ break;
+ case 2:
+ out.print( HexDump.toHex( LittleEndian.readShort( in ) ) );
+ break;
+ case 4:
+ out.print( HexDump.toHex( LittleEndian.readInt( in ) ) );
+ break;
+ default:
+ throw new IOException( "Unable to output variable of that width" );
+ }
+ }
+
+ /**
+ * A simple test stub.
+ */
+ public static void main( String[] args ) throws IOException
+ {
+ String dump =
+ "0F 00 00 F0 89 07 00 00 00 00 06 F0 18 00 00 00 " +
+ "05 04 00 00 02 00 00 00 05 00 00 00 01 00 00 00 " +
+ "01 00 00 00 05 00 00 00 4F 00 01 F0 2F 07 00 00 " +
+ "42 00 07 F0 B7 01 00 00 03 04 3F 14 AE 6B 0F 65 " +
+ "B0 48 BF 5E 94 63 80 E8 91 73 FF 00 93 01 00 00 " +
+ "01 00 00 00 00 00 00 00 00 00 FF FF 20 54 1C F0 " +
+ "8B 01 00 00 3F 14 AE 6B 0F 65 B0 48 BF 5E 94 63 " +
+ "80 E8 91 73 92 0E 00 00 00 00 00 00 00 00 00 00 " +
+ "D1 07 00 00 DD 05 00 00 4A AD 6F 00 8A C5 53 00 " +
+ "59 01 00 00 00 FE 78 9C E3 9B C4 00 04 AC 77 D9 " +
+ "2F 32 08 32 FD E7 61 F8 FF 0F C8 FD 05 C5 30 19 " +
+ "10 90 63 90 FA 0F 06 0C 8C 0C 5C 70 19 43 30 EB " +
+ "0E FB 05 86 85 0C DB 18 58 80 72 8C 70 16 0B 83 " +
+ "05 56 51 29 88 C9 60 D9 69 0C 6C 20 26 23 03 C8 " +
+ "74 B0 A8 0E 03 07 FB 45 56 C7 A2 CC C4 1C 06 66 " +
+ "A0 0D 2C 40 39 5E 86 4C 06 3D A0 4E 10 D0 60 D9 " +
+ "C8 58 CC E8 CF B0 80 61 3A 8A 7E 0D C6 23 AC 4F " +
+ "E0 E2 98 B6 12 2B 06 73 9D 12 E3 52 56 59 F6 08 " +
+ "8A CC 52 66 A3 50 FF 96 2B 94 E9 DF 4C A1 FE 2D " +
+ "3A 03 AB 9F 81 C2 F0 A3 54 BF 0F 85 EE A7 54 FF " +
+ "40 FB 7F A0 E3 9F D2 F4 4F 71 FE 19 58 FF 2B 31 " +
+ "7F 67 36 3B 25 4F 99 1B 4E 53 A6 5F 89 25 95 E9 " +
+ "C4 00 C7 83 12 F3 1F 26 35 4A D3 D2 47 0E 0A C3 " +
+ "41 8E C9 8A 52 37 DC 15 A1 D0 0D BC 4C 06 0C 2B " +
+ "28 2C 13 28 D4 EF 43 61 5A A0 58 3F 85 71 E0 4B " +
+ "69 9E 64 65 FE 39 C0 E5 22 30 1D 30 27 0E 74 3A " +
+ "18 60 FD 4A CC B1 2C 13 7D 07 36 2D 2A 31 85 B2 " +
+ "6A 0D 74 1D 1D 22 4D 99 FE 60 0A F5 9B EC 1C 58 " +
+ "FD 67 06 56 3F 38 0D 84 3C A5 30 0E 28 D3 AF C4 " +
+ "A4 CA FA 44 7A 0D 65 6E 60 7F 4D A1 1B 24 58 F7 " +
+ "49 AF A5 CC 0D CC DF 19 FE 03 00 F0 B1 25 4D 42 " +
+ "00 07 F0 E1 01 00 00 03 04 39 50 BE 98 B0 6F 57 " +
+ "24 31 70 5D 23 2F 9F 10 66 FF 00 BD 01 00 00 01 " +
+ "00 00 00 00 00 00 00 00 00 FF FF 20 54 1C F0 B5 " +
+ "01 00 00 39 50 BE 98 B0 6F 57 24 31 70 5D 23 2F " +
+ "9F 10 66 DA 03 00 00 00 00 00 00 00 00 00 00 D1 " +
+ "07 00 00 DD 05 00 00 4A AD 6F 00 8A C5 53 00 83 " +
+ "01 00 00 00 FE 78 9C A5 52 BF 4B 42 51 14 3E F7 " +
+ "DC 77 7A 16 45 48 8B 3C 48 A8 16 15 0D 6C 88 D0 " +
+ "04 C3 40 A3 32 1C 84 96 08 21 04 A1 C5 5C A2 35 " +
+ "82 C0 35 6A AB 1C 6A 6B A8 24 5A 83 68 08 84 84 " +
+ "96 A2 86 A0 7F C2 86 5E E7 5E F5 41 E4 10 BC 03 " +
+ "1F E7 FB F1 CE B9 F7 F1 9E 7C 05 2E 7A 37 9B E0 " +
+ "45 7B 10 EC 6F 96 5F 1D 74 13 55 7E B0 6C 5D 20 " +
+ "60 C0 49 A2 9A BD 99 4F 50 83 1B 30 38 13 0E 33 " +
+ "60 A6 A7 6B B5 37 EB F4 10 FA 14 15 A0 B6 6B 37 " +
+ "0C 1E B3 49 73 5B A5 C2 26 48 3E C1 E0 6C 08 4A " +
+ "30 C9 93 AA 02 B8 20 13 62 05 4E E1 E8 D7 7C C0 " +
+ "B8 14 95 5E BE B8 A7 CF 1E BE 55 2C 56 B9 78 DF " +
+ "08 7E 88 4C 27 FF 7B DB FF 7A DD B7 1A 17 67 34 " +
+ "6A AE BA DA 35 D1 E7 72 BE FE EC 6E FE DA E5 7C " +
+ "3D EC 7A DE 03 FD 50 06 0B 23 F2 0E F3 B2 A5 11 " +
+ "91 0D 4C B5 B5 F3 BF 94 C1 8F 24 F7 D9 6F 60 94 " +
+ "3B C9 9A F3 1C 6B E7 BB F0 2E 49 B2 25 2B C6 B1 " +
+ "EE 69 EE 15 63 4F 71 7D CE 85 CC C8 35 B9 C3 28 " +
+ "28 CE D0 5C 67 79 F2 4A A2 14 23 A4 38 43 73 9D " +
+ "2D 69 2F C1 08 31 9F C5 5C 9B EB 7B C5 69 19 B3 " +
+ "B4 81 F3 DC E3 B4 8E 8B CC B3 94 53 5A E7 41 2A " +
+ "63 9A AA 38 C5 3D 48 BB EC 57 59 6F 2B AD 73 1F " +
+ "1D 60 92 AE 70 8C BB 8F CE 31 C1 3C 49 27 4A EB " +
+ "DC A4 5B 8C D1 0B 0E 73 37 E9 11 A7 99 C7 E8 41 " +
+ "69 B0 7F 00 96 F2 A7 E8 42 00 07 F0 B4 01 00 00 " +
+ "03 04 1A BA F9 D6 A9 B9 3A 03 08 61 E9 90 FF 7B " +
+ "9E E6 FF 00 90 01 00 00 01 00 00 00 00 00 00 00 " +
+ "00 00 FF FF 20 54 1C F0 88 01 00 00 1A BA F9 D6 " +
+ "A9 B9 3A 03 08 61 E9 90 FF 7B 9E E6 12 0E 00 00 " +
+ "00 00 00 00 00 00 00 00 D1 07 00 00 DD 05 00 00 " +
+ "4A AD 6F 00 8A C5 53 00 56 01 00 00 00 FE 78 9C " +
+ "E3 13 62 00 02 D6 BB EC 17 19 04 99 FE F3 30 FC " +
+ "FF 07 E4 FE 82 62 98 0C 08 C8 31 48 FD 07 03 06 " +
+ "46 06 2E B8 8C 21 98 75 87 FD 02 C3 42 86 6D 0C " +
+ "2C 40 39 46 38 8B 85 C1 02 AB A8 14 C4 64 B0 EC " +
+ "34 06 36 10 93 91 01 64 3A 58 54 87 81 83 FD 22 " +
+ "AB 63 51 66 62 0E 03 33 D0 06 16 A0 1C 2F 43 26 " +
+ "83 1E 50 27 08 68 B0 6C 64 2C 66 F4 67 58 C0 30 " +
+ "1D 45 BF 06 E3 11 D6 27 70 71 4C 5B 89 15 83 B9 " +
+ "4E 89 71 29 AB 2C 7B 04 45 66 29 B3 51 A8 7F CB " +
+ "15 CA F4 6F A6 50 FF 16 9D 81 D5 CF 40 61 F8 51 " +
+ "AA DF 87 42 F7 53 AA 7F A0 FD 3F D0 F1 4F 69 FA " +
+ "A7 38 FF 0C AC FF 95 98 BF 33 9B 9D 92 A7 CC 0D " +
+ "A7 29 D3 AF C4 92 CA 74 62 80 E3 41 89 F9 0F 93 " +
+ "1A A5 69 E9 23 07 85 E1 20 C7 64 45 A9 1B EE 8A " +
+ "50 E8 06 5E 26 03 86 15 14 96 09 14 EA F7 A1 30 " +
+ "2D 50 AC 9F C2 38 F0 A5 34 4F B2 32 FF 1C E0 72 " +
+ "11 98 0E 98 13 07 38 1D 28 31 C7 B2 4C F4 1D D8 " +
+ "B4 A0 C4 14 CA AA 35 D0 75 64 88 34 65 FA 83 29 " +
+ "D4 6F B2 73 60 F5 9F A1 54 FF 0E CA D3 40 C8 53 " +
+ "0A E3 E0 09 85 6E 50 65 7D 22 BD 86 32 37 B0 BF " +
+ "A6 D0 0D 12 AC FB A4 D7 52 E6 06 E6 EF 0C FF 01 " +
+ "97 1D 12 C7 42 00 07 F0 C3 01 00 00 03 04 BA 4C " +
+ "B6 23 BA 8B 27 BE C8 55 59 86 24 9F 89 D4 FF 00 " +
+ "9F 01 00 00 01 00 00 00 00 00 00 00 00 00 FF FF " +
+ "20 54 1C F0 97 01 00 00 BA 4C B6 23 BA 8B 27 BE " +
+ "C8 55 59 86 24 9F 89 D4 AE 0E 00 00 00 00 00 00 " +
+ "00 00 00 00 D1 07 00 00 DD 05 00 00 4A AD 6F 00 " +
+ "8A C5 53 00 65 01 00 00 00 FE 78 9C E3 5B C7 00 " +
+ "04 AC 77 D9 2F 32 08 32 FD E7 61 F8 FF 0F C8 FD " +
+ "05 C5 30 19 10 90 63 90 FA 0F 06 0C 8C 0C 5C 70 " +
+ "19 43 30 EB 0E FB 05 86 85 0C DB 18 58 80 72 8C " +
+ "70 16 0B 83 05 56 51 29 88 C9 60 D9 69 0C 6C 20 " +
+ "26 23 03 C8 74 B0 A8 0E 03 07 FB 45 56 C7 A2 CC " +
+ "C4 1C 06 66 A0 0D 2C 40 39 5E 86 4C 06 3D A0 4E " +
+ "10 D0 60 99 C6 B8 98 D1 9F 61 01 C3 74 14 FD 1A " +
+ "8C 2B D8 84 B1 88 4B A5 A5 75 03 01 50 DF 59 46 " +
+ "77 46 0F A8 3C A6 AB 88 15 83 B9 5E 89 B1 8B D5 " +
+ "97 2D 82 22 B3 94 29 D5 BF E5 CA C0 EA DF AC 43 " +
+ "A1 FD 14 EA 67 A0 30 FC 28 D5 EF 43 A1 FB 7D 87 " +
+ "B8 FF 07 3A FE 07 3A FD 53 EA 7E 0A C3 4F 89 F9 " +
+ "0E 73 EA 69 79 CA DC 70 8A 32 FD 4A 2C 5E 4C DF " +
+ "87 7A 3C BC E0 A5 30 1E 3E 31 C5 33 AC A0 30 2F " +
+ "52 A8 DF 87 C2 30 A4 54 3F A5 65 19 85 65 A9 12 " +
+ "D3 2B 16 0D 8A CB 13 4A F3 E3 27 E6 09 03 9D 0E " +
+ "06 58 BF 12 B3 13 CB C1 01 4E 8B 4A 4C 56 AC 91 " +
+ "03 5D 37 86 48 53 A6 3F 98 42 FD 26 3B 07 56 FF " +
+ "99 1D 14 EA A7 CC 7E 70 1A 08 79 42 61 1C 3C A5 " +
+ "D0 0D 9C 6C C2 32 6B 29 73 03 DB 6B CA DC C0 F8 " +
+ "97 F5 AD CC 1A CA DC C0 F4 83 32 37 B0 A4 30 CE " +
+ "FC C7 48 99 1B FE 33 32 FC 07 00 6C CC 2E 23 33 " +
+ "00 0B F0 12 00 00 00 BF 00 08 00 08 00 81 01 09 " +
+ "00 00 08 C0 01 40 00 00 08 40 00 1E F1 10 00 00 " +
+ "00 0D 00 00 08 0C 00 00 08 17 00 00 08 F7 00 00 " +
+ "10 ";
+
+ // Decode the stream to bytes
+ byte[] bytes = HexRead.readData( new ByteArrayInputStream( dump.getBytes() ), -1 );
+ // Create a new instance of the escher dumper
+ EscherDump dumper = new EscherDump();
+ // Dump the contents of scher to screen.
+// dumper.dumpOld( bytes.length, new ByteArrayInputStream( bytes ), System.out );
+ dumper.dump(bytes, 0, bytes.length, System.out);
+
+ }
+
+ public void dump( int recordSize, byte[] data, PrintStream out ) throws IOException, LittleEndian.BufferUnderrunException
+ {
+// ByteArrayInputStream is = new ByteArrayInputStream( data );
+// dump( recordSize, is, out );
+ dump( data, 0, recordSize, System.out );
+ }
+}
--- /dev/null
+package org.apache.poi.ddf;
+
+import org.apache.poi.util.LittleEndian;
+import org.apache.poi.util.HexDump;
+
+import java.util.*;
+import java.io.IOException;
+
+/**
+ * The opt record is used to store property values for a shape. It is the key to determining
+ * the attributes of a shape. Properties can be of two types: simple or complex. Simple types
+ * are fixed length. Complex properties are variable length.
+ *
+ * @author Glen Stampoultzis
+ */
+public class EscherOptRecord
+ extends EscherRecord
+{
+ public static final short RECORD_ID = (short) 0xF00B;
+ public static final String RECORD_DESCRIPTION = "msofbtOPT";
+
+ private List properties = new ArrayList();
+
+ /**
+ * 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 <code>data</code>.
+ * @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 <code>data</code> 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 <code>NullEscherSerailizationListener</code> 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() ) );
+ }
+ } );
+ }
+
+
+}
--- /dev/null
+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();
+ }
+}
+
+
+
--- /dev/null
+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 );
+}
--- /dev/null
+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;
+ }
+
+
+}
--- /dev/null
+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;
+ }
+
+}
--- /dev/null
+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 );
+ }
+
+}
--- /dev/null
+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 <code>options</code>
+ * and <code>recordId</code> 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 +
+ "}";
+ }
+
+
+ }
+
+}
--- /dev/null
+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 );
+}
--- /dev/null
+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);
+}
--- /dev/null
+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 );
+ }
+
+
+
+}
--- /dev/null
+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) + ")";
+ }
+
+}
--- /dev/null
+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 <code>data</code>.
+ * @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 <code>data</code> 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 <code>NullEscherSerailizationListener</code> 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;
+ }
+}
--- /dev/null
+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 <code>data</code>.
+ * @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 <code>data</code> 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 <code>NullEscherSerailizationListener</code> 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;
+ }
+}
--- /dev/null
+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 <code>data</code>.
+ * @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 <code>data</code> 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 <code>NullEscherSerailizationListener</code> 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;
+ }
+
+}
--- /dev/null
+/* ====================================================================
+ * 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
+ * <http://www.apache.org/>.
+ */
+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.util.Iterator;
+import java.util.List;
+import java.util.ArrayList;
+
+/**
+ * Supports text boxes
+ *
+ * @author Glen Stampoultzis (glens at apache.org)
+ */
+public class EscherTextboxRecord extends EscherRecord
+{
+ public static final short RECORD_ID = (short)0xF00D;
+ public static final String RECORD_DESCRIPTION = "msofbtClientTextbox";
+
+ private static final byte[] NO_BYTES = new byte[0];
+
+ /** The data for this record not including the the 8 byte header */
+ private byte[] thedata = NO_BYTES;
+
+ public EscherTextboxRecord()
+ {
+ }
+
+ /**
+ * 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 <code>data</code>.
+ * @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;
+ }
+
+}
+
+
+
--- /dev/null
+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
+ }
+
+}
--- /dev/null
+/* ====================================================================
+ * 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
+ * <http://www.apache.org/>.
+ */
+package org.apache.poi.ddf;
+
+import org.apache.poi.util.HexDump;
+import org.apache.poi.util.LittleEndian;
+
+import java.util.Iterator;
+import java.util.List;
+import java.util.ArrayList;
+
+/**
+ * This record is used whenever a escher record is encountered that
+ * we do not explicitly support.
+ *
+ * @author Glen Stampoultzis (glens at apache.org)
+ */
+public class UnknownEscherRecord extends EscherRecord
+{
+ private static final byte[] NO_BYTES = new byte[0];
+
+ /** The data for this record not including the the 8 byte header */
+ private byte[] thedata = NO_BYTES;
+ private List childRecords = new ArrayList();
+
+ public UnknownEscherRecord()
+ {
+ }
+
+ /**
+ * 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 <code>data</code>.
+ * @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 );
+ }
+
+}
+
+
+
--- /dev/null
+<html>
+
+<body>
+
+ <p>This package contains classes for decoding the Microsoft Office
+ Drawing format otherwise known as escher henceforth known in POI
+ as the Dreadful Drawing Format.
+ </p>
+
+</body>
+</html>
\ No newline at end of file