From 585f86a986ea2d6f23a61ce6c552fc0f6194798f Mon Sep 17 00:00:00 2001 From: Glen Stampoultzis Date: Sun, 1 May 2005 11:26:18 +0000 Subject: [PATCH] Added image support for POI. See the quick guide for details. Sponsored through superlink software. git-svn-id: https://svn.apache.org/repos/asf/jakarta/poi/trunk@353660 13f79535-47bb-0310-9956-ffa450edef68 --- build.xml | 12 +- .../content/xdocs/hssf/quick-guide.xml | 56 ++- src/documentation/content/xdocs/status.xml | 1 + .../usermodel/examples/OfficeDrawing.java | 53 ++- .../poi/ddf/DefaultEscherRecordFactory.java | 12 +- .../org/apache/poi/ddf/EscherBSERecord.java | 63 ++- .../org/apache/poi/ddf/EscherBlipRecord.java | 358 +------------- .../apache/poi/ddf/EscherBlipWMFRecord.java | 435 ++++++++++++++++++ .../apache/poi/ddf/EscherBoolProperty.java | 4 +- .../poi/ddf/EscherClientAnchorRecord.java | 2 +- .../poi/ddf/EscherClientDataRecord.java | 2 +- .../org/apache/poi/ddf/EscherDggRecord.java | 11 +- .../org/apache/poi/ddf/EscherProperty.java | 2 +- .../apache/poi/ddf/EscherPropertyFactory.java | 18 +- .../org/apache/poi/ddf/EscherRGBProperty.java | 2 +- .../apache/poi/ddf/EscherSimpleProperty.java | 1 + .../org/apache/poi/hssf/dev/BiffViewer.java | 59 ++- .../apache/poi/hssf/model/AbstractShape.java | 5 +- .../apache/poi/hssf/model/ConvertAnchor.java | 2 +- src/java/org/apache/poi/hssf/model/Sheet.java | 30 +- .../org/apache/poi/hssf/model/Workbook.java | 88 +++- .../record/AbstractEscherHolderRecord.java | 74 ++- .../poi/hssf/record/DrawingGroupRecord.java | 100 ++++ .../apache/poi/hssf/record/DrawingRecord.java | 8 + .../poi/hssf/record/EscherAggregate.java | 13 +- .../org/apache/poi/hssf/record/ObjRecord.java | 8 + .../apache/poi/hssf/record/RecordFactory.java | 18 +- .../ColumnInfoRecordsAggregate.java | 7 +- .../aggregates/RowRecordsAggregate.java | 2 +- .../poi/hssf/usermodel/HSSFClientAnchor.java | 24 +- .../poi/hssf/usermodel/HSSFPatriarch.java | 17 + .../apache/poi/hssf/usermodel/HSSFSheet.java | 11 +- .../poi/hssf/usermodel/HSSFSimpleShape.java | 4 +- .../poi/hssf/usermodel/HSSFWorkbook.java | 94 +++- src/java/org/apache/poi/util/DrawingDump.java | 15 +- src/java/org/apache/poi/util/HexDump.java | 2 +- .../apache/poi/ddf/TestEscherBSERecord.java | 3 +- ...cord.java => TestEscherBlipWMFRecord.java} | 14 +- .../poi/ddf/TestEscherBoolProperty.java | 2 +- .../poi/ddf/TestEscherClientDataRecord.java | 2 +- .../apache/poi/ddf/TestEscherOptRecord.java | 2 +- .../org/apache/poi/hssf/model/TestSheet.java | 238 +++++++++- .../hssf/record/TestDrawingGroupRecord.java | 91 ++++ .../poi/hssf/record/TestEscherAggregate.java | 13 +- .../org/apache/poi/util/TestHexDump.java | 6 +- .../org/apache/poi/util/TestLittleEndian.java | 8 +- 46 files changed, 1497 insertions(+), 495 deletions(-) create mode 100644 src/java/org/apache/poi/ddf/EscherBlipWMFRecord.java rename src/testcases/org/apache/poi/ddf/{TestEscherBlipRecord.java => TestEscherBlipWMFRecord.java} (91%) diff --git a/build.xml b/build.xml index 319f5f6451..41469093b3 100644 --- a/build.xml +++ b/build.xml @@ -256,10 +256,10 @@ - + - + @@ -269,10 +269,10 @@ - + - + @@ -282,10 +282,10 @@ - + - + diff --git a/src/documentation/content/xdocs/hssf/quick-guide.xml b/src/documentation/content/xdocs/hssf/quick-guide.xml index 3ea0ad8afd..d4ecc43116 100644 --- a/src/documentation/content/xdocs/hssf/quick-guide.xml +++ b/src/documentation/content/xdocs/hssf/quick-guide.xml @@ -31,20 +31,21 @@
  • Custom colors
  • Reading and writing
  • Use newlines in cells.
  • -
  • Create user defined data formats.
  • +
  • Create user defined data formats
  • Fit Sheet to One Page
  • -
  • Set print area for a sheet.
  • -
  • Set page numbers on the footer of a sheet.
  • -
  • Shift rows.
  • -
  • Set a sheet as selected.
  • -
  • Set the zoom magnification for a sheet.
  • -
  • Create split and freeze panes.
  • -
  • Repeating rows and columns.
  • -
  • Headers and Footers.
  • -
  • Drawing Shapes.
  • -
  • Styling Shapes.
  • -
  • Shapes and Graphics2d.
  • -
  • Outlining.
  • +
  • Set print area for a sheet
  • +
  • Set page numbers on the footer of a sheet
  • +
  • Shift rows
  • +
  • Set a sheet as selected
  • +
  • Set the zoom magnification for a sheet
  • +
  • Create split and freeze panes
  • +
  • Repeating rows and columns
  • +
  • Headers and Footers
  • +
  • Drawing Shapes
  • +
  • Styling Shapes
  • +
  • Shapes and Graphics2d
  • +
  • Outlining
  • +
  • Images
  • Features @@ -940,5 +941,34 @@
    + +
    + Images +

    + Images are part of the drawing support. To add an image just + call createPicture() on the drawing patriarch. + At the time of writing the following types are supported: +

    +
      +
    • PNG
    • +
    • JPG
    • +
    • DIB
    • +
    +

    + It is not currently possible to read existing images and it + should be noted that any existing drawings may be erased + once you add a image to a sheet. +

    + + // Create the drawing patriarch. This is the top level container for + // all shapes. This will clear out any existing shapes for that sheet. + HSSFPatriarch patriarch = sheet5.createDrawingPatriarch(); + + HSSFClientAnchor anchor; + anchor = new HSSFClientAnchor(0,0,0,255,(short)2,2,(short)4,7); + anchor.setAnchorType( 2 ); + patriarch.createPicture(anchor, loadPicture( "src/resources/logos/logoKarmokar4.png", wb )); + +
    diff --git a/src/documentation/content/xdocs/status.xml b/src/documentation/content/xdocs/status.xml index 8f4ffbf828..4a87113c90 100644 --- a/src/documentation/content/xdocs/status.xml +++ b/src/documentation/content/xdocs/status.xml @@ -14,6 +14,7 @@ Bugzilla Bug 29976 [PATCH] HSSF hyperlink formula size problem + Image writing support diff --git a/src/examples/src/org/apache/poi/hssf/usermodel/examples/OfficeDrawing.java b/src/examples/src/org/apache/poi/hssf/usermodel/examples/OfficeDrawing.java index e6520a0a15..693b78235d 100644 --- a/src/examples/src/org/apache/poi/hssf/usermodel/examples/OfficeDrawing.java +++ b/src/examples/src/org/apache/poi/hssf/usermodel/examples/OfficeDrawing.java @@ -19,9 +19,7 @@ package org.apache.poi.hssf.usermodel.examples; import org.apache.poi.hssf.usermodel.*; -import java.io.IOException; -import java.io.FileOutputStream; -import java.io.FileNotFoundException; +import java.io.*; /** * Demonstrates how to use the office drawing capabilities of POI. @@ -39,12 +37,14 @@ public class OfficeDrawing HSSFSheet sheet2 = wb.createSheet("second sheet"); HSSFSheet sheet3 = wb.createSheet("third sheet"); HSSFSheet sheet4 = wb.createSheet("fourth sheet"); + HSSFSheet sheet5 = wb.createSheet("fifth sheet"); // Draw stuff in them drawSheet1( sheet1 ); drawSheet2( sheet2 ); drawSheet3( sheet3 ); drawSheet4( sheet4, wb ); + drawSheet5( sheet5, wb ); // Write the file out. FileOutputStream fileOut = new FileOutputStream("workbook.xls"); @@ -143,6 +143,53 @@ public class OfficeDrawing textbox3.setNoFill(true); // make it transparent } + private static void drawSheet5( HSSFSheet sheet5, HSSFWorkbook wb ) throws IOException + { + + // Create the drawing patriarch. This is the top level container for + // all shapes. This will clear out any existing shapes for that sheet. + HSSFPatriarch patriarch = sheet5.createDrawingPatriarch(); + + HSSFClientAnchor anchor; + anchor = new HSSFClientAnchor(0,0,0,255,(short)2,2,(short)4,7); + anchor.setAnchorType( 2 ); + patriarch.createPicture(anchor, loadPicture( "src/resources/logos/logoKarmokar4.png", wb )); + + anchor = new HSSFClientAnchor(0,0,0,255,(short)4,2,(short)5,7); + anchor.setAnchorType( 2 ); + patriarch.createPicture(anchor, loadPicture( "src/resources/logos/logoKarmokar4edited.png", wb )); + + anchor = new HSSFClientAnchor(0,0,1023,255,(short)6,2,(short)8,7); + anchor.setAnchorType( 2 ); + HSSFPicture picture = patriarch.createPicture(anchor, loadPicture( "src/resources/logos/logoKarmokar4s.png", wb )); + picture.setLineStyle( picture.LINESTYLE_DASHDOTGEL ); + + } + + private static int loadPicture( String path, HSSFWorkbook wb ) throws IOException + { + int pictureIndex; + FileInputStream fis = null; + ByteArrayOutputStream bos = null; + try + { + fis = new FileInputStream( path); + bos = new ByteArrayOutputStream( ); + int c; + while ( (c = fis.read()) != -1) + bos.write( c ); + pictureIndex = wb.addPicture( bos.toByteArray(), HSSFWorkbook.PICTURE_TYPE_PNG ); + } + finally + { + if (fis != null) + fis.close(); + if (bos != null) + bos.close(); + } + return pictureIndex; + } + private static void drawOval( HSSFPatriarch patriarch ) { // Create an oval and style to taste. diff --git a/src/java/org/apache/poi/ddf/DefaultEscherRecordFactory.java b/src/java/org/apache/poi/ddf/DefaultEscherRecordFactory.java index 69076710a1..1aa9c6ee7f 100644 --- a/src/java/org/apache/poi/ddf/DefaultEscherRecordFactory.java +++ b/src/java/org/apache/poi/ddf/DefaultEscherRecordFactory.java @@ -66,7 +66,17 @@ public class DefaultEscherRecordFactory } else if ( header.getRecordId() >= EscherBlipRecord.RECORD_ID_START && header.getRecordId() <= EscherBlipRecord.RECORD_ID_END ) { - EscherBlipRecord r = new EscherBlipRecord(); + EscherBlipRecord r; + if (header.getRecordId() == EscherBitmapBlip.RECORD_ID_DIB || + header.getRecordId() == EscherBitmapBlip.RECORD_ID_JPEG || + header.getRecordId() == EscherBitmapBlip.RECORD_ID_PNG) + { + r = new EscherBitmapBlip(); + } + else + { + r = new EscherBlipRecord(); + } r.setRecordId( header.getRecordId() ); r.setOptions( header.getOptions() ); return r; diff --git a/src/java/org/apache/poi/ddf/EscherBSERecord.java b/src/java/org/apache/poi/ddf/EscherBSERecord.java index 42504954ed..b8165ba0be 100644 --- a/src/java/org/apache/poi/ddf/EscherBSERecord.java +++ b/src/java/org/apache/poi/ddf/EscherBSERecord.java @@ -17,15 +17,15 @@ package org.apache.poi.ddf; -import org.apache.poi.util.LittleEndian; import org.apache.poi.util.HexDump; +import org.apache.poi.util.LittleEndian; import java.io.ByteArrayOutputStream; -import java.io.PrintWriter; /** * The BSE record is related closely to the EscherBlipRecord and stores - * extra information about the blip. + * extra information about the blip. A blip record is actually stored inside + * the BSE record even though the BSE record isn't actually a container record. * * @author Glen Stampoultzis * @see EscherBlipRecord @@ -56,6 +56,7 @@ public class EscherBSERecord private byte field_9_name; private byte field_10_unused2; private byte field_11_unused3; + private EscherBlipRecord field_12_blipRecord; private byte[] remainingData; @@ -85,9 +86,28 @@ public class EscherBSERecord field_10_unused2 = data[pos + 34]; field_11_unused3 = data[pos + 35]; bytesRemaining -= 36; + int bytesRead = 0; + if (bytesRemaining > 0) + { + field_12_blipRecord = (EscherBlipRecord) recordFactory.createRecord( data, pos + 36 ); + bytesRead = field_12_blipRecord.fillFields( data, pos + 36, recordFactory ); + } + pos += 36 + bytesRead; + bytesRemaining -= bytesRead; +// if (field_1_blipTypeWin32 == BT_PNG) +// { +// byte[] uid = new byte[16]; +// System.arraycopy( data, pos + 36, uid, 0, 16 ); +// byte[] puid = new byte[16]; +// System.arraycopy( data, pos + 52, puid, 0, 16 ); +// byte tag = data[pos+68]; +// System.out.println( HexDump.dump( data, 0, 0 ) ); +// byte[] pngBytes = EscherBlipRecord.decompress( data, pos+69, bytesRemaining); +// } + remainingData = new byte[bytesRemaining]; - System.arraycopy( data, pos + 36, remainingData, 0, bytesRemaining ); - return bytesRemaining + 8 + 36; + System.arraycopy( data, pos, remainingData, 0, bytesRemaining ); + return bytesRemaining + 8 + 36 + (field_12_blipRecord == null ? 0 : field_12_blipRecord.getRecordSize()) ; } @@ -104,9 +124,14 @@ public class EscherBSERecord { 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 + 36; + if (remainingData == null) remainingData = new byte[0]; + int blipSize = field_12_blipRecord == null ? 0 : field_12_blipRecord.getRecordSize(); + int remainingBytes = remainingData.length + 36 + blipSize; LittleEndian.putInt( data, offset + 4, remainingBytes ); data[offset + 8] = field_1_blipTypeWin32; @@ -121,8 +146,15 @@ public class EscherBSERecord 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; + int bytesWritten = 0; + if (field_12_blipRecord != null) + { + bytesWritten = field_12_blipRecord.serialize( offset + 44, data, new NullEscherSerializationListener() ); + } + if (remainingData == null) + remainingData = new byte[0]; + System.arraycopy( remainingData, 0, data, offset + 44 + bytesWritten, remainingData.length ); + int pos = offset + 8 + 36 + remainingData.length + bytesWritten; listener.afterRecordSerialize(pos, getRecordId(), pos - offset, this); return pos - offset; @@ -135,7 +167,7 @@ public class EscherBSERecord */ public int getRecordSize() { - return 8 + 1 + 1 + 16 + 2 + 4 + 4 + 4 + 1 + 1 + 1 + 1 + remainingData.length; + return 8 + 1 + 1 + 16 + 2 + 4 + 4 + 4 + 1 + 1 + 1 + 1 + field_12_blipRecord.getRecordSize() + (remainingData == null ? 0 : remainingData.length); } /** @@ -312,6 +344,16 @@ public class EscherBSERecord this.field_11_unused3 = unused3; } + public EscherBlipRecord getBlipRecord() + { + return field_12_blipRecord; + } + + public void setBlipRecord( EscherBlipRecord field_12_blipRecord ) + { + this.field_12_blipRecord = field_12_blipRecord; + } + /** * Any remaining data in this record. */ @@ -360,9 +402,8 @@ public class EscherBSERecord " Name: " + field_9_name + nl + " Unused2: " + field_10_unused2 + nl + " Unused3: " + field_11_unused3 + nl + + " blipRecord: " + field_12_blipRecord + nl + " Extra Data:" + nl + extraData; - - } /** diff --git a/src/java/org/apache/poi/ddf/EscherBlipRecord.java b/src/java/org/apache/poi/ddf/EscherBlipRecord.java index dc000887d6..43d90141a9 100644 --- a/src/java/org/apache/poi/ddf/EscherBlipRecord.java +++ b/src/java/org/apache/poi/ddf/EscherBlipRecord.java @@ -17,24 +17,14 @@ 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 org.apache.poi.util.HexDump; -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 + * @version $Id$ */ public class EscherBlipRecord extends EscherRecord @@ -42,21 +32,14 @@ public class EscherBlipRecord 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; + protected byte[] field_pictureData; + public EscherBlipRecord() + { + } /** * This method deserializes the record from a byte array. @@ -66,44 +49,27 @@ public class EscherBlipRecord * @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 - ) + 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++; + field_pictureData = new byte[bytesAfterHeader]; + System.arraycopy(data, pos, field_pictureData, 0, bytesAfterHeader); - 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; + return bytesAfterHeader + 8; } - /** - * This method serializes this escher record into a byte array. - * - * @param offset The offset into data to start writing the record data to. - * @param data The byte array to serialize to. - * @param listener A listener to retrieve start and end callbacks. Use a NullEscherSerailizationListener to ignore these events. - * @return The number of bytes written. + * Serializes the record to an existing byte array. * - * @see NullEscherSerializationListener + * @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 int serialize( int offset, byte[] data, EscherSerializationListener listener ) { @@ -111,25 +77,11 @@ public class EscherBlipRecord 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; + System.arraycopy( field_pictureData, 0, data, offset + 4, field_pictureData.length ); - listener.afterRecordSerialize(pos, getRecordId(), pos - offset, this); - return pos - offset; + listener.afterRecordSerialize(offset + 4 + field_pictureData.length, getRecordId(), field_pictureData.length + 4, this); + return field_pictureData.length + 4; } /** @@ -139,7 +91,7 @@ public class EscherBlipRecord */ public int getRecordSize() { - return 58 + field_12_data.length; + return field_pictureData.length + 4; } /** @@ -150,203 +102,6 @@ public class EscherBlipRecord 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" ); @@ -355,7 +110,7 @@ public class EscherBlipRecord ByteArrayOutputStream b = new ByteArrayOutputStream(); try { - HexDump.dump( this.field_12_data, 0, b, 0 ); + HexDump.dump( this.field_pictureData, 0, b, 0 ); extraData = b.toString(); } catch ( Exception e ) @@ -365,70 +120,7 @@ public class EscherBlipRecord 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() ); - } + " Extra Data:" + nl + extraData; - return out.toByteArray(); } - - /** - * Decompresses a byte array. - * - * @param data The compressed byte array - * @param pos The starting position into the byte array - * @param length The number of compressed bytes to decompress - * @return An uncompressed byte array - * @see InflaterInputStream#read - */ - public static byte[] decompress( byte[] data, int pos, int length ) - { - byte[] compressedData = new byte[length]; - System.arraycopy( data, pos + 50, compressedData, 0, length ); - InputStream compressedInputStream = new ByteArrayInputStream( compressedData ); - InflaterInputStream inflaterInputStream = new InflaterInputStream( compressedInputStream ); - ByteArrayOutputStream out = new ByteArrayOutputStream(); - int c; - try - { - while ( ( c = inflaterInputStream.read() ) != -1 ) - out.write( c ); - } - catch ( IOException e ) - { - throw new RecordFormatException( e.toString() ); - } - return out.toByteArray(); - } - } diff --git a/src/java/org/apache/poi/ddf/EscherBlipWMFRecord.java b/src/java/org/apache/poi/ddf/EscherBlipWMFRecord.java new file mode 100644 index 0000000000..d4e9d19469 --- /dev/null +++ b/src/java/org/apache/poi/ddf/EscherBlipWMFRecord.java @@ -0,0 +1,435 @@ + +/* ==================================================================== + Copyright 2002-2004 Apache Software Foundation + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +==================================================================== */ + +package org.apache.poi.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 EscherBlipWMFRecord + extends EscherBlipRecord +{ +// public static final short RECORD_ID_START = (short) 0xF018; +// public static final short RECORD_ID_END = (short) 0xF117; + public static final String RECORD_DESCRIPTION = "msofbtBlip"; + private static final int HEADER_SIZE = 8; + + private byte[] field_1_secondaryUID; + private int field_2_cacheOfSize; + private int field_3_boundaryTop; + private int field_4_boundaryLeft; + private int field_5_boundaryWidth; + private int field_6_boundaryHeight; + private int field_7_width; + private int field_8_height; + private int field_9_cacheOfSavedSize; + private byte field_10_compressionFlag; + private byte field_11_filter; + private byte[] field_12_data; + + + /** + * This method deserializes the record from a byte array. + * + * @param data The byte array containing the escher record information + * @param offset The starting offset into data. + * @param recordFactory May be null since this is not a container record. + * @return The number of bytes read from the byte array. + */ + public int fillFields( byte[] data, int offset, + EscherRecordFactory recordFactory + ) + { + int bytesAfterHeader = readHeader( data, offset ); + int pos = offset + HEADER_SIZE; + + int size = 0; + field_1_secondaryUID = new byte[16]; + System.arraycopy( data, pos + size, field_1_secondaryUID, 0, 16 ); size += 16; + field_2_cacheOfSize = LittleEndian.getInt( data, pos + size );size+=4; + field_3_boundaryTop = LittleEndian.getInt( data, pos + size );size+=4; + field_4_boundaryLeft = LittleEndian.getInt( data, pos + size );size+=4; + field_5_boundaryWidth = LittleEndian.getInt( data, pos + size );size+=4; + field_6_boundaryHeight = LittleEndian.getInt( data, pos + size );size+=4; + field_7_width = LittleEndian.getInt( data, pos + size );size+=4; + field_8_height = LittleEndian.getInt( data, pos + size );size+=4; + field_9_cacheOfSavedSize = LittleEndian.getInt( data, pos + size );size+=4; + field_10_compressionFlag = data[pos + size]; size++; + field_11_filter = data[pos + size]; size++; + + int bytesRemaining = bytesAfterHeader - size; + field_12_data = new byte[bytesRemaining]; + System.arraycopy(data, pos + size, field_12_data, 0, bytesRemaining); + size += bytesRemaining; + + return HEADER_SIZE + size; + } + + + /** + * This method serializes this escher record into a byte array. + * + * @param offset The offset into data to start writing the record data to. + * @param data The byte array to serialize to. + * @param listener A listener to retrieve start and end callbacks. Use a NullEscherSerailizationListener to ignore these events. + * @return The number of bytes written. + * + * @see NullEscherSerializationListener + */ + public int serialize( int offset, byte[] data, EscherSerializationListener listener ) + { + listener.beforeRecordSerialize(offset, getRecordId(), this); + + LittleEndian.putShort( data, offset, getOptions() ); + LittleEndian.putShort( data, offset + 2, getRecordId() ); + int remainingBytes = field_12_data.length + 36; + LittleEndian.putInt( data, offset + 4, remainingBytes ); + + int pos = offset + HEADER_SIZE; + System.arraycopy(field_1_secondaryUID, 0, data, pos, 16 ); pos += 16; + LittleEndian.putInt( data, pos, field_2_cacheOfSize); pos += 4; + LittleEndian.putInt( data, pos, field_3_boundaryTop); pos += 4; + LittleEndian.putInt( data, pos, field_4_boundaryLeft); pos += 4; + LittleEndian.putInt( data, pos, field_5_boundaryWidth); pos += 4; + LittleEndian.putInt( data, pos, field_6_boundaryHeight); pos += 4; + LittleEndian.putInt( data, pos, field_7_width); pos += 4; + LittleEndian.putInt( data, pos, field_8_height); pos += 4; + LittleEndian.putInt( data, pos, field_9_cacheOfSavedSize); pos += 4; + data[pos++] = field_10_compressionFlag; + data[pos++] = field_11_filter; + System.arraycopy(field_12_data, 0, data, pos, field_12_data.length); pos += field_12_data.length; + + listener.afterRecordSerialize(pos, getRecordId(), pos - offset, this); + return pos - offset; + } + + /** + * Returns the number of bytes that are required to serialize this record. + * + * @return Number of bytes + */ + public int getRecordSize() + { + return 58 + field_12_data.length; + } + + /** + * The short name for this record + */ + public String getRecordName() + { + return "Blip"; + } + + /** + * Retrieve the secondary UID + */ + public byte[] getSecondaryUID() + { + return field_1_secondaryUID; + } + + /** + * Set the secondary UID + */ + public void setSecondaryUID( byte[] field_1_secondaryUID ) + { + this.field_1_secondaryUID = field_1_secondaryUID; + } + + /** + * Retrieve the cache of the metafile size + */ + public int getCacheOfSize() + { + return field_2_cacheOfSize; + } + + /** + * Set the cache of the metafile size + */ + public void setCacheOfSize( int field_2_cacheOfSize ) + { + this.field_2_cacheOfSize = field_2_cacheOfSize; + } + + /** + * Retrieve the top boundary of the metafile drawing commands + */ + public int getBoundaryTop() + { + return field_3_boundaryTop; + } + + /** + * Set the top boundary of the metafile drawing commands + */ + public void setBoundaryTop( int field_3_boundaryTop ) + { + this.field_3_boundaryTop = field_3_boundaryTop; + } + + /** + * Retrieve the left boundary of the metafile drawing commands + */ + public int getBoundaryLeft() + { + return field_4_boundaryLeft; + } + + /** + * Set the left boundary of the metafile drawing commands + */ + public void setBoundaryLeft( int field_4_boundaryLeft ) + { + this.field_4_boundaryLeft = field_4_boundaryLeft; + } + + /** + * Retrieve the boundary width of the metafile drawing commands + */ + public int getBoundaryWidth() + { + return field_5_boundaryWidth; + } + + /** + * Set the boundary width of the metafile drawing commands + */ + public void setBoundaryWidth( int field_5_boundaryWidth ) + { + this.field_5_boundaryWidth = field_5_boundaryWidth; + } + + /** + * Retrieve the boundary height of the metafile drawing commands + */ + public int getBoundaryHeight() + { + return field_6_boundaryHeight; + } + + /** + * Set the boundary height of the metafile drawing commands + */ + public void setBoundaryHeight( int field_6_boundaryHeight ) + { + this.field_6_boundaryHeight = field_6_boundaryHeight; + } + + /** + * Retrieve the width of the metafile in EMU's (English Metric Units). + */ + public int getWidth() + { + return field_7_width; + } + + /** + * Set the width of the metafile in EMU's (English Metric Units). + */ + public void setWidth( int width ) + { + this.field_7_width = width; + } + + /** + * Retrieve the height of the metafile in EMU's (English Metric Units). + */ + public int getHeight() + { + return field_8_height; + } + + /** + * Set the height of the metafile in EMU's (English Metric Units). + */ + public void setHeight( int height ) + { + this.field_8_height = height; + } + + /** + * Retrieve the cache of the saved size + */ + public int getCacheOfSavedSize() + { + return field_9_cacheOfSavedSize; + } + + /** + * Set the cache of the saved size + */ + public void setCacheOfSavedSize( int field_9_cacheOfSavedSize ) + { + this.field_9_cacheOfSavedSize = field_9_cacheOfSavedSize; + } + + /** + * Is the contents of the blip compressed? + */ + public byte getCompressionFlag() + { + return field_10_compressionFlag; + } + + /** + * Set whether the contents of the blip is compressed + */ + public void setCompressionFlag( byte field_10_compressionFlag ) + { + this.field_10_compressionFlag = field_10_compressionFlag; + } + + /** + * Filter should always be 0 + */ + public byte getFilter() + { + return field_11_filter; + } + + /** + * Filter should always be 0 + */ + public void setFilter( byte field_11_filter ) + { + this.field_11_filter = field_11_filter; + } + + /** + * The BLIP data + */ + public byte[] getData() + { + return field_12_data; + } + + /** + * The BLIP data + */ + public void setData( byte[] field_12_data ) + { + this.field_12_data = field_12_data; + } + + /** + * The string representation of this record. + * + * @return A string + */ + public String toString() + { + String nl = System.getProperty( "line.separator" ); + + String extraData; + ByteArrayOutputStream b = new ByteArrayOutputStream(); + try + { + HexDump.dump( this.field_12_data, 0, b, 0 ); + extraData = b.toString(); + } + catch ( Exception e ) + { + extraData = e.toString(); + } + return getClass().getName() + ":" + nl + + " RecordId: 0x" + HexDump.toHex( getRecordId() ) + nl + + " Options: 0x" + HexDump.toHex( getOptions() ) + nl + + " Secondary UID: " + HexDump.toHex( field_1_secondaryUID ) + nl + + " CacheOfSize: " + field_2_cacheOfSize + nl + + " BoundaryTop: " + field_3_boundaryTop + nl + + " BoundaryLeft: " + field_4_boundaryLeft + nl + + " BoundaryWidth: " + field_5_boundaryWidth + nl + + " BoundaryHeight: " + field_6_boundaryHeight + nl + + " X: " + field_7_width + nl + + " Y: " + field_8_height + nl + + " CacheOfSavedSize: " + field_9_cacheOfSavedSize + nl + + " CompressionFlag: " + field_10_compressionFlag + nl + + " Filter: " + field_11_filter + nl + + " Data:" + nl + extraData; + } + + /** + * Compress the contents of the provided array + * + * @param data An uncompressed byte array + * @see DeflaterOutputStream#write(int b) + */ + public static byte[] compress( byte[] data ) + { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + DeflaterOutputStream deflaterOutputStream = new DeflaterOutputStream( out ); + try + { + for ( int i = 0; i < data.length; i++ ) + deflaterOutputStream.write( data[i] ); + } + catch ( IOException e ) + { + throw new RecordFormatException( e.toString() ); + } + + return out.toByteArray(); + } + + /** + * Decompresses a byte array. + * + * @param data The compressed byte array + * @param pos The starting position into the byte array + * @param length The number of compressed bytes to decompress + * @return An uncompressed byte array + * @see InflaterInputStream#read + */ + public static byte[] decompress( byte[] data, int pos, int length ) + { + byte[] compressedData = new byte[length]; + System.arraycopy( data, pos + 50, compressedData, 0, length ); + InputStream compressedInputStream = new ByteArrayInputStream( compressedData ); + InflaterInputStream inflaterInputStream = new InflaterInputStream( compressedInputStream ); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + int c; + try + { + while ( ( c = inflaterInputStream.read() ) != -1 ) + out.write( c ); + } + catch ( IOException e ) + { + throw new RecordFormatException( e.toString() ); + } + return out.toByteArray(); + } + +} diff --git a/src/java/org/apache/poi/ddf/EscherBoolProperty.java b/src/java/org/apache/poi/ddf/EscherBoolProperty.java index 67bd0243d9..e4f4c8b50a 100644 --- a/src/java/org/apache/poi/ddf/EscherBoolProperty.java +++ b/src/java/org/apache/poi/ddf/EscherBoolProperty.java @@ -32,12 +32,12 @@ public class EscherBoolProperty /** * Create an instance of an escher boolean property. * - * @param propertyNumber The property number + * @param propertyNumber The property number (or id) * @param value The 32 bit value of this bool property */ public EscherBoolProperty( short propertyNumber, int value ) { - super( propertyNumber, false, false, value ); + super(propertyNumber, value); } /** diff --git a/src/java/org/apache/poi/ddf/EscherClientAnchorRecord.java b/src/java/org/apache/poi/ddf/EscherClientAnchorRecord.java index c2115eca79..d8bb4acfc1 100644 --- a/src/java/org/apache/poi/ddf/EscherClientAnchorRecord.java +++ b/src/java/org/apache/poi/ddf/EscherClientAnchorRecord.java @@ -154,7 +154,7 @@ public class EscherClientAnchorRecord } catch ( Exception e ) { - extraData = "error"; + extraData = "error\n"; } return getClass().getName() + ":" + nl + " RecordId: 0x" + HexDump.toHex(RECORD_ID) + nl + diff --git a/src/java/org/apache/poi/ddf/EscherClientDataRecord.java b/src/java/org/apache/poi/ddf/EscherClientDataRecord.java index 4eb3bdbc91..59cac729eb 100644 --- a/src/java/org/apache/poi/ddf/EscherClientDataRecord.java +++ b/src/java/org/apache/poi/ddf/EscherClientDataRecord.java @@ -119,7 +119,7 @@ public class EscherClientDataRecord } catch ( Exception e ) { - extraData = "error"; + extraData = "error\n"; } return getClass().getName() + ":" + nl + " RecordId: 0x" + HexDump.toHex(RECORD_ID) + nl + diff --git a/src/java/org/apache/poi/ddf/EscherDggRecord.java b/src/java/org/apache/poi/ddf/EscherDggRecord.java index 38d5c7eeb9..c10c58ac10 100644 --- a/src/java/org/apache/poi/ddf/EscherDggRecord.java +++ b/src/java/org/apache/poi/ddf/EscherDggRecord.java @@ -17,11 +17,10 @@ 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 org.apache.poi.hssf.record.RecordFormatException; -import java.lang.reflect.Array; import java.util.*; /** @@ -83,8 +82,8 @@ public class EscherDggRecord 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 = new FileIdCluster[(bytesRemaining-size) / 8]; // Can't rely on field_2_numIdClusters + for (int i = 0; i < field_5_fileIdClusters.length; i++) { field_5_fileIdClusters[i] = new FileIdCluster(LittleEndian.getInt( data, pos + size ), LittleEndian.getInt( data, pos + size + 4 )); size += 8; @@ -114,6 +113,7 @@ public class EscherDggRecord 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; @@ -200,6 +200,9 @@ public class EscherDggRecord this.field_1_shapeIdMax = field_1_shapeIdMax; } + /** + * Number of id clusters + 1 + */ public int getNumIdClusters() { return field_5_fileIdClusters.length + 1; diff --git a/src/java/org/apache/poi/ddf/EscherProperty.java b/src/java/org/apache/poi/ddf/EscherProperty.java index f07be8bed1..65097138a6 100644 --- a/src/java/org/apache/poi/ddf/EscherProperty.java +++ b/src/java/org/apache/poi/ddf/EscherProperty.java @@ -26,7 +26,7 @@ package org.apache.poi.ddf; */ abstract public class EscherProperty { - private short id; + protected short id; /** * The id is distinct from the actual property number. The id includes the property number the blip id diff --git a/src/java/org/apache/poi/ddf/EscherPropertyFactory.java b/src/java/org/apache/poi/ddf/EscherPropertyFactory.java index 35dc6331ba..b59f7bae01 100644 --- a/src/java/org/apache/poi/ddf/EscherPropertyFactory.java +++ b/src/java/org/apache/poi/ddf/EscherPropertyFactory.java @@ -18,11 +18,10 @@ 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; +import java.util.List; /** * Generates a property given a reference into the byte array storing that property. @@ -43,7 +42,7 @@ public class EscherPropertyFactory List results = new ArrayList(); int pos = offset; - int complexBytes = 0; + // while ( bytesRemaining >= 6 ) for (int i = 0; i < numProperties; i++) { @@ -54,21 +53,18 @@ public class EscherPropertyFactory 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 ) ); + results.add( new EscherBoolProperty( propId, propData ) ); else if ( propertyType == EscherPropertyMetaData.TYPE_RGB ) - results.add( new EscherRGBProperty( propNumber, propData ) ); + results.add( new EscherRGBProperty( propId, propData ) ); else if ( propertyType == EscherPropertyMetaData.TYPE_SHAPEPATH ) - results.add( new EscherShapePathProperty( propNumber, propData ) ); + results.add( new EscherShapePathProperty( propId, propData ) ); else { if ( !isComplex ) - results.add( new EscherSimpleProperty( propNumber, propData ) ); + results.add( new EscherSimpleProperty( propId, propData ) ); else { if ( propertyType == EscherPropertyMetaData.TYPE_ARRAY) diff --git a/src/java/org/apache/poi/ddf/EscherRGBProperty.java b/src/java/org/apache/poi/ddf/EscherRGBProperty.java index b38f0fd764..e7dde24568 100644 --- a/src/java/org/apache/poi/ddf/EscherRGBProperty.java +++ b/src/java/org/apache/poi/ddf/EscherRGBProperty.java @@ -28,7 +28,7 @@ public class EscherRGBProperty public EscherRGBProperty( short propertyNumber, int rgbColor ) { - super( propertyNumber, false, false, rgbColor ); + super( propertyNumber, rgbColor ); } public int getRgbColor() diff --git a/src/java/org/apache/poi/ddf/EscherSimpleProperty.java b/src/java/org/apache/poi/ddf/EscherSimpleProperty.java index de27ccadee..02d0595fe1 100644 --- a/src/java/org/apache/poi/ddf/EscherSimpleProperty.java +++ b/src/java/org/apache/poi/ddf/EscherSimpleProperty.java @@ -111,6 +111,7 @@ public class EscherSimpleProperty extends EscherProperty public String toString() { return "propNum: " + getPropertyNumber() + + ", RAW: 0x" + HexDump.toHex( getId() ) + ", propName: " + EscherProperties.getPropertyName( getPropertyNumber() ) + ", complex: " + isComplex() + ", blipId: " + isBlipId() diff --git a/src/java/org/apache/poi/hssf/dev/BiffViewer.java b/src/java/org/apache/poi/hssf/dev/BiffViewer.java index 754c1011ae..468cb63f43 100644 --- a/src/java/org/apache/poi/hssf/dev/BiffViewer.java +++ b/src/java/org/apache/poi/hssf/dev/BiffViewer.java @@ -112,6 +112,23 @@ public class BiffViewer { in.read(data); loc += recsize; Record record = createRecord(rectype, recsize, data ); +// if (record.getSid() == DrawingGroupRecord.sid) +// { +// if (activeRecord.getRecord().getSid() == DrawingGroupRecord.sid) +// { +// DrawingGroupRecord dg = (DrawingGroupRecord) activeRecord.getRecord(); +// System.out.println( "Joined" ); +// dg.join( (AbstractEscherHolderRecord) record ); +// } +// else +// { +// records.add(record); +// if (activeRecord != null) +// activeRecord.dump(); +// activeRecord = new RecordDetails(rectype, recsize, startloc, data, record); +// } +// } +// else if (record.getSid() != ContinueRecord.sid) { records.add(record); @@ -178,7 +195,6 @@ public class BiffViewer { } } - private static void dumpUnknownRecord(byte[] data) throws IOException { // record hex dump it! System.out.println( @@ -630,29 +646,40 @@ public class BiffViewer { */ public static void main(String[] args) { try { - BiffViewer viewer = new BiffViewer(args); + System.setProperty("poi.deserialize.escher", "true"); - if ((args.length > 1) && args[1].equals("on")) { - viewer.setDump(true); + if (args.length == 0) + { + System.out.println( "Biff viewer needs a filename" ); } - if ((args.length > 1) && args[1].equals("bfd")) { - POIFSFileSystem fs = - new POIFSFileSystem(new FileInputStream(args[0])); - InputStream stream = - fs.createDocumentInputStream("Workbook"); - int size = stream.available(); - byte[] data = new byte[size]; - - stream.read(data); - HexDump.dump(data, 0, System.out, 0); - } else { - viewer.run(); + else + { + BiffViewer viewer = new BiffViewer(args); + if ((args.length > 1) && args[1].equals("on")) { + viewer.setDump(true); + } + if ((args.length > 1) && args[1].equals("bfd")) { + POIFSFileSystem fs = + new POIFSFileSystem(new FileInputStream(args[0])); + InputStream stream = + fs.createDocumentInputStream("Workbook"); + int size = stream.available(); + byte[] data = new byte[size]; + + stream.read(data); + HexDump.dump(data, 0, System.out, 0); + } else { + viewer.run(); + } } } catch (Exception e) { e.printStackTrace(); } } + /** + * This record supports dumping of completed continue records. + */ static class RecordDetails { short rectype, recsize; diff --git a/src/java/org/apache/poi/hssf/model/AbstractShape.java b/src/java/org/apache/poi/hssf/model/AbstractShape.java index 41f4ee73bb..60f24401c1 100644 --- a/src/java/org/apache/poi/hssf/model/AbstractShape.java +++ b/src/java/org/apache/poi/hssf/model/AbstractShape.java @@ -34,7 +34,7 @@ public abstract class AbstractShape */ public static AbstractShape createShape( HSSFShape hssfShape, int shapeId ) { - AbstractShape shape = null; + AbstractShape shape; if (hssfShape instanceof HSSFTextbox) { shape = new TextboxShape( (HSSFTextbox)hssfShape, shapeId ); @@ -48,6 +48,9 @@ public abstract class AbstractShape HSSFSimpleShape simpleShape = (HSSFSimpleShape) hssfShape; switch ( simpleShape.getShapeType() ) { + case HSSFSimpleShape.OBJECT_TYPE_PICTURE: + shape = new PictureShape( simpleShape, shapeId ); + break; case HSSFSimpleShape.OBJECT_TYPE_LINE: shape = new LineShape( simpleShape, shapeId ); break; diff --git a/src/java/org/apache/poi/hssf/model/ConvertAnchor.java b/src/java/org/apache/poi/hssf/model/ConvertAnchor.java index 8f1943a81f..73e55209c2 100644 --- a/src/java/org/apache/poi/hssf/model/ConvertAnchor.java +++ b/src/java/org/apache/poi/hssf/model/ConvertAnchor.java @@ -37,7 +37,7 @@ public class ConvertAnchor EscherClientAnchorRecord anchor = new EscherClientAnchorRecord(); anchor.setRecordId( EscherClientAnchorRecord.RECORD_ID ); anchor.setOptions( (short) 0x0000 ); - anchor.setFlag( (short) 0 ); + anchor.setFlag( (short) a.getAnchorType() ); anchor.setCol1( (short) Math.min(a.getCol1(), a.getCol2()) ); anchor.setDx1( (short) Math.min(a.getDx1(), a.getDx2()) ); anchor.setRow1( (short) Math.min(a.getRow1(), a.getRow2()) ); diff --git a/src/java/org/apache/poi/hssf/model/Sheet.java b/src/java/org/apache/poi/hssf/model/Sheet.java index 99b26fe191..6d32288e31 100644 --- a/src/java/org/apache/poi/hssf/model/Sheet.java +++ b/src/java/org/apache/poi/hssf/model/Sheet.java @@ -19,17 +19,17 @@ package org.apache.poi.hssf.model; import org.apache.poi.hssf.record.*; +import org.apache.poi.hssf.record.aggregates.ColumnInfoRecordsAggregate; import org.apache.poi.hssf.record.aggregates.FormulaRecordAggregate; import org.apache.poi.hssf.record.aggregates.RowRecordsAggregate; import org.apache.poi.hssf.record.aggregates.ValueRecordsAggregate; -import org.apache.poi.hssf.record.aggregates.ColumnInfoRecordsAggregate; import org.apache.poi.hssf.record.formula.Ptg; import org.apache.poi.util.POILogFactory; import org.apache.poi.util.POILogger; import java.util.ArrayList; import java.util.Iterator; -import java.util.List; +import java.util.List; // normally I don't do this, buy we literally mean ALL /** * Low level model implementation of a Sheet (one workbook contains many sheets) @@ -768,6 +768,26 @@ public class Sheet implements Model } } + //// uncomment to test record sizes //// +// System.out.println( record.getClass().getName() ); +// byte[] data2 = new byte[record.getRecordSize()]; +// record.serialize(0, data2 ); // rec.length; +// if (LittleEndian.getUShort(data2, 2) != record.getRecordSize() - 4 +// && record instanceof RowRecordsAggregate == false +// && record instanceof ValueRecordsAggregate == false +// && record instanceof EscherAggregate == false) +// { +// throw new RuntimeException("Blah!!! Size off by " + ( LittleEndian.getUShort(data2, 2) - record.getRecordSize() - 4) + " records."); +// } + +//asd: int len = record.serialize(pos + offset, data ); + + ///// DEBUG BEGIN ///// +//asd: if (len != record.getRecordSize()) +//asd: throw new IllegalStateException("Record size does not match serialized bytes. Serialized size = " + len + " but getRecordSize() returns " + record.getRecordSize() + ". Record object is " + record.getClass()); + ///// DEBUG END ///// + +//asd: pos += len; // rec.length; } if (log.check( POILogger.DEBUG )) @@ -2023,9 +2043,9 @@ public class Sheet implements Model { int retval = 0; - for (int k = 0; k < records.size(); k++) + for ( int k = 0; k < records.size(); k++ ) { - retval += (( Record ) records.get(k)).getRecordSize(); + retval += ( (Record) records.get( k ) ).getRecordSize(); } //Add space for the IndexRecord if (rows != null) { @@ -2430,7 +2450,7 @@ public class Sheet implements Model return margins; } - public int aggregateDrawingRecords(DrawingManager drawingManager) + public int aggregateDrawingRecords(DrawingManager2 drawingManager) { int loc = findFirstRecordLocBySid(DrawingRecord.sid); boolean noDrawingRecordsFound = loc == -1; diff --git a/src/java/org/apache/poi/hssf/model/Workbook.java b/src/java/org/apache/poi/hssf/model/Workbook.java index c7d4ae1fd6..8ec742baff 100644 --- a/src/java/org/apache/poi/hssf/model/Workbook.java +++ b/src/java/org/apache/poi/hssf/model/Workbook.java @@ -29,7 +29,6 @@ import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Locale; -import java.io.UnsupportedEncodingException; /** * Low level model implementation of a Workbook. Provides creational methods @@ -99,7 +98,8 @@ public class Workbook implements Model protected int numfonts = 0; // hold the number of font records private short maxformatid = -1; // holds the max format id private boolean uses1904datewindowing = false; // whether 1904 date windowing is being used - private DrawingManager drawingManager; + private DrawingManager2 drawingManager; + private List escherBSERecords = new ArrayList(); // EscherBSERecord private static POILogger log = POILogFactory.getLogger(Workbook.class); @@ -748,7 +748,12 @@ public class Workbook implements Model { record = sst.createExtSSTRecord(sstPos + offset); } - pos += record.serialize( pos + offset, data ); // rec.length; + int len = record.serialize( pos + offset, data ); + ///// DEBUG BEGIN ///// +// if (len != record.getRecordSize()) +// throw new IllegalStateException("Record size does not match serialized bytes. Serialized size = " + len + " but getRecordSize() returns " + record.getRecordSize()); + ///// DEBUG END ///// + pos += len; // rec.length; } } if (log.check( POILogger.DEBUG )) @@ -2104,13 +2109,12 @@ public class Workbook implements Model } /** - * Creates a drawing group record. If it already exists then it's left - * alone. + * Creates a drawing group record. If it already exists then it's modified. */ public void createDrawingGroup() { - int dggLoc = findFirstRecordLocBySid(EscherContainerRecord.DGG_CONTAINER); - if (dggLoc == -1) + + if (drawingManager == null) { EscherContainerRecord dggContainer = new EscherContainerRecord(); EscherDggRecord dgg = new EscherDggRecord(); @@ -2125,11 +2129,23 @@ public class Workbook implements Model dgg.setNumShapesSaved(0); dgg.setDrawingsSaved(0); dgg.setFileIdClusters(new EscherDggRecord.FileIdCluster[] {} ); - drawingManager = new DrawingManager(dgg); + drawingManager = new DrawingManager2(dgg); + EscherContainerRecord bstoreContainer = null; + if (escherBSERecords.size() > 0) + { + bstoreContainer = new EscherContainerRecord(); + bstoreContainer.setRecordId( EscherContainerRecord.BSTORE_CONTAINER ); + bstoreContainer.setOptions( (short) ( (escherBSERecords.size() << 4) | 0xF ) ); + for ( Iterator iterator = escherBSERecords.iterator(); iterator.hasNext(); ) + { + EscherRecord escherRecord = (EscherRecord) iterator.next(); + bstoreContainer.addChildRecord( escherRecord ); + } + } opt.setRecordId((short) 0xF00B); opt.setOptions((short) 0x0033); opt.addEscherProperty( new EscherBoolProperty(EscherProperties.TEXT__SIZE_TEXT_TO_FIT_SHAPE, 524296) ); - opt.addEscherProperty( new EscherRGBProperty(EscherProperties.FILL__FILLCOLOR, 134217737) ); + opt.addEscherProperty( new EscherRGBProperty(EscherProperties.FILL__FILLCOLOR, 0x08000041) ); opt.addEscherProperty( new EscherRGBProperty(EscherProperties.LINESTYLE__COLOR, 134217792) ); splitMenuColors.setRecordId((short) 0xF11E); splitMenuColors.setOptions((short) 0x0040); @@ -2139,17 +2155,61 @@ public class Workbook implements Model splitMenuColors.setColor4(0x100000F7); dggContainer.addChildRecord(dgg); + if (bstoreContainer != null) + dggContainer.addChildRecord( bstoreContainer ); dggContainer.addChildRecord(opt); dggContainer.addChildRecord(splitMenuColors); - DrawingGroupRecord drawingGroup = new DrawingGroupRecord(); - drawingGroup.addEscherRecord(dggContainer); - int loc = findFirstRecordLocBySid(CountryRecord.sid); - getRecords().add(loc+1, drawingGroup); + int dgLoc = findFirstRecordLocBySid(DrawingGroupRecord.sid); + if (dgLoc == -1) + { + DrawingGroupRecord drawingGroup = new DrawingGroupRecord(); + drawingGroup.addEscherRecord(dggContainer); + int loc = findFirstRecordLocBySid(CountryRecord.sid); + + getRecords().add(loc+1, drawingGroup); + } + else + { + DrawingGroupRecord drawingGroup = new DrawingGroupRecord(); + drawingGroup.addEscherRecord(dggContainer); + getRecords().set(dgLoc, drawingGroup); + } + } + + } + + public int addBSERecord(EscherBSERecord e) + { + createDrawingGroup(); + + // maybe we don't need that as an instance variable anymore + escherBSERecords.add( e ); + + int dgLoc = findFirstRecordLocBySid(DrawingGroupRecord.sid); + DrawingGroupRecord drawingGroup = (DrawingGroupRecord) getRecords().get( dgLoc ); + + EscherContainerRecord dggContainer = (EscherContainerRecord) drawingGroup.getEscherRecord( 0 ); + EscherContainerRecord bstoreContainer; + if (dggContainer.getChild( 1 ).getRecordId() == EscherContainerRecord.BSTORE_CONTAINER ) + { + bstoreContainer = (EscherContainerRecord) dggContainer.getChild( 1 ); + } + else + { + bstoreContainer = new EscherContainerRecord(); + bstoreContainer.setRecordId( EscherContainerRecord.BSTORE_CONTAINER ); + dggContainer.getChildRecords().add( 1, bstoreContainer ); + } + bstoreContainer.setOptions( (short) ( (escherBSERecords.size() << 4) | 0xF ) ); + + bstoreContainer.addChildRecord( e ); + + return escherBSERecords.size(); } - public DrawingManager getDrawingManager() + public DrawingManager2 getDrawingManager() { return drawingManager; } diff --git a/src/java/org/apache/poi/hssf/record/AbstractEscherHolderRecord.java b/src/java/org/apache/poi/hssf/record/AbstractEscherHolderRecord.java index b2701e3fc4..a18724478e 100644 --- a/src/java/org/apache/poi/hssf/record/AbstractEscherHolderRecord.java +++ b/src/java/org/apache/poi/hssf/record/AbstractEscherHolderRecord.java @@ -88,7 +88,7 @@ public abstract class AbstractEscherHolderRecord { if (id != getSid()) { - throw new RecordFormatException("Not a Bar record"); + throw new RecordFormatException("Not an escher record"); } } @@ -102,15 +102,20 @@ public abstract class AbstractEscherHolderRecord } else { - EscherRecordFactory recordFactory = new DefaultEscherRecordFactory(); - int pos = offset; - while ( pos < offset + size ) - { - EscherRecord r = recordFactory.createRecord(data, pos); - int bytesRead = r.fillFields(data, pos, recordFactory ); - escherRecords.add(r); - pos += bytesRead; - } + convertToEscherRecords( offset, size, data ); + } + } + + private void convertToEscherRecords( int offset, int size, byte[] data ) + { + EscherRecordFactory recordFactory = new DefaultEscherRecordFactory(); + int pos = offset; + while ( pos < offset + size ) + { + EscherRecord r = recordFactory.createRecord(data, pos); + int bytesRead = r.fillFields(data, pos, recordFactory ); + escherRecords.add(r); + pos += bytesRead; } } @@ -120,6 +125,8 @@ public abstract class AbstractEscherHolderRecord final String nl = System.getProperty("line.separator"); buffer.append('[' + getRecordName() + ']' + nl); + if (escherRecords.size() == 0) + buffer.append("No Escher Records Decoded" + nl); for ( Iterator iterator = escherRecords.iterator(); iterator.hasNext(); ) { EscherRecord r = (EscherRecord) iterator.next(); @@ -138,10 +145,16 @@ public abstract class AbstractEscherHolderRecord LittleEndian.putShort( data, 2 + offset, (short) ( getRecordSize() - 4 ) ); if ( escherRecords.size() == 0 && rawData != null ) { - System.arraycopy( rawData, 0, data, offset + 4, rawData.length ); + LittleEndian.putShort(data, 0 + offset, getSid()); + LittleEndian.putShort(data, 2 + offset, (short)(getRecordSize() - 4)); + System.arraycopy( rawData, 0, data, 4 + offset, rawData.length); + return rawData.length + 4; } else { + LittleEndian.putShort(data, 0 + offset, getSid()); + LittleEndian.putShort(data, 2 + offset, (short)(getRecordSize() - 4)); + int pos = offset + 4; for ( Iterator iterator = escherRecords.iterator(); iterator.hasNext(); ) { @@ -255,6 +268,45 @@ public abstract class AbstractEscherHolderRecord return (EscherRecord) escherRecords.get(index); } + /** + * Big drawing group records are split but it's easier to deal with them + * as a whole group so we need to join them together. + */ + public void join( AbstractEscherHolderRecord record ) + { + int length = this.rawData.length + record.getRawData().length; + byte[] data = new byte[length]; + System.arraycopy( rawData, 0, data, 0, rawData.length ); + System.arraycopy( record.getRawData(), 0, data, rawData.length, record.getRawData().length ); + rawData = data; + } + + public void processContinueRecord( byte[] record ) + { + int length = this.rawData.length + record.length; + byte[] data = new byte[length]; + System.arraycopy( rawData, 0, data, 0, rawData.length ); + System.arraycopy( record, 0, data, rawData.length, record.length ); + rawData = data; + } + + public byte[] getRawData() + { + return rawData; + } + + public void setRawData( byte[] rawData ) + { + this.rawData = rawData; + } + + /** + * Convert raw data to escher records. + */ + public void decode() + { + convertToEscherRecords(0, rawData.length, rawData ); + } } // END OF CLASS diff --git a/src/java/org/apache/poi/hssf/record/DrawingGroupRecord.java b/src/java/org/apache/poi/hssf/record/DrawingGroupRecord.java index d114666d49..675a0cdba6 100644 --- a/src/java/org/apache/poi/hssf/record/DrawingGroupRecord.java +++ b/src/java/org/apache/poi/hssf/record/DrawingGroupRecord.java @@ -16,10 +16,22 @@ package org.apache.poi.hssf.record; +import org.apache.poi.ddf.EscherRecord; +import org.apache.poi.ddf.NullEscherSerializationListener; +import org.apache.poi.util.ArrayUtil; +import org.apache.poi.util.LittleEndian; + +import java.util.Iterator; +import java.util.List; + + public class DrawingGroupRecord extends AbstractEscherHolderRecord { public static final short sid = 0xEB; + static final int MAX_RECORD_SIZE = 8228; + private static final int MAX_DATA_SIZE = MAX_RECORD_SIZE - 4; + public DrawingGroupRecord() { } @@ -43,4 +55,92 @@ public class DrawingGroupRecord extends AbstractEscherHolderRecord { return sid; } + + public int serialize(int offset, byte[] data) + { + byte[] rawData = getRawData(); + if (getEscherRecords().size() == 0 && rawData != null) + { + return writeData( offset, data, rawData ); + } + else + { + byte[] buffer = new byte[getRawDataSize()]; + int pos = 0; + for ( Iterator iterator = getEscherRecords().iterator(); iterator.hasNext(); ) + { + EscherRecord r = (EscherRecord) iterator.next(); + pos += r.serialize(pos, buffer, new NullEscherSerializationListener() ); + } + + return writeData( offset, data, buffer ); + } + } + + /** + * Size of record (including 4 byte headers for all sections) + */ + public int getRecordSize() + { + return grossSizeFromDataSize( getRawDataSize() ); + } + + public int getRawDataSize() + { + List escherRecords = getEscherRecords(); + byte[] rawData = getRawData(); + if (escherRecords.size() == 0 && rawData != null) + { + return rawData.length; + } + else + { + int size = 0; + for ( Iterator iterator = escherRecords.iterator(); iterator.hasNext(); ) + { + EscherRecord r = (EscherRecord) iterator.next(); + size += r.getRecordSize(); + } + return size; + } + } + + static int grossSizeFromDataSize(int dataSize) + { + return dataSize + ( (dataSize - 1) / MAX_DATA_SIZE + 1 ) * 4; + } + + private int writeData( int offset, byte[] data, byte[] rawData ) + { + int writtenActualData = 0; + int writtenRawData = 0; + while (writtenRawData < rawData.length) + { + int segmentLength = Math.min( rawData.length - writtenRawData, MAX_DATA_SIZE); + if (writtenRawData / MAX_DATA_SIZE >= 2) + writeContinueHeader( data, offset, segmentLength ); + else + writeHeader( data, offset, segmentLength ); + writtenActualData += 4; + offset += 4; + ArrayUtil.arraycopy( rawData, writtenRawData, data, offset, segmentLength ); + offset += segmentLength; + writtenRawData += segmentLength; + writtenActualData += segmentLength; + } + return writtenActualData; + } + + private void writeHeader( byte[] data, int offset, int sizeExcludingHeader ) + { + LittleEndian.putShort(data, 0 + offset, getSid()); + LittleEndian.putShort(data, 2 + offset, (short) sizeExcludingHeader); + } + + private void writeContinueHeader( byte[] data, int offset, int sizeExcludingHeader ) + { + LittleEndian.putShort(data, 0 + offset, ContinueRecord.sid); + LittleEndian.putShort(data, 2 + offset, (short) sizeExcludingHeader); + } + } diff --git a/src/java/org/apache/poi/hssf/record/DrawingRecord.java b/src/java/org/apache/poi/hssf/record/DrawingRecord.java index c17588c833..b2bf298de2 100644 --- a/src/java/org/apache/poi/hssf/record/DrawingRecord.java +++ b/src/java/org/apache/poi/hssf/record/DrawingRecord.java @@ -68,6 +68,14 @@ public class DrawingRecord extends Record recordData = data; } + public void processContinueRecord( byte[] record ) + { + byte[] newBuffer = new byte[ recordData.length + record.length ]; + System.arraycopy( recordData, 0, newBuffer, 0, recordData.length ); + System.arraycopy( record, 0, newBuffer, recordData.length, record.length); + recordData = newBuffer; + } + public int serialize( int offset, byte[] data ) { if (recordData == null) diff --git a/src/java/org/apache/poi/hssf/record/EscherAggregate.java b/src/java/org/apache/poi/hssf/record/EscherAggregate.java index e3f65d0bd8..e9bf9f9333 100644 --- a/src/java/org/apache/poi/hssf/record/EscherAggregate.java +++ b/src/java/org/apache/poi/hssf/record/EscherAggregate.java @@ -20,7 +20,7 @@ import org.apache.poi.ddf.*; import org.apache.poi.hssf.usermodel.*; import org.apache.poi.hssf.model.AbstractShape; import org.apache.poi.hssf.model.TextboxShape; -import org.apache.poi.hssf.model.DrawingManager; +import org.apache.poi.hssf.model.DrawingManager2; import org.apache.poi.hssf.model.ConvertAnchor; import java.util.*; @@ -256,10 +256,10 @@ public class EscherAggregate extends AbstractEscherHolderRecord /** Maps shape container objects to their OBJ records */ private Map shapeToObj = new HashMap(); - private DrawingManager drawingManager; + private DrawingManager2 drawingManager; private short drawingGroupId; - public EscherAggregate( DrawingManager drawingManager ) + public EscherAggregate( DrawingManager2 drawingManager ) { this.drawingManager = drawingManager; } @@ -305,7 +305,7 @@ public class EscherAggregate extends AbstractEscherHolderRecord /** * Collapses the drawing records into an aggregate. */ - public static EscherAggregate createAggregate( List records, int locFirstDrawingRecord, DrawingManager drawingManager ) + public static EscherAggregate createAggregate( List records, int locFirstDrawingRecord, DrawingManager2 drawingManager ) { // Keep track of any shape records created so we can match them back to the object id's. // Textbox objects are also treated as shape objects. @@ -571,6 +571,9 @@ public class EscherAggregate extends AbstractEscherHolderRecord escherParent.addChildRecord( shapeModel.getSpContainer() ); } } +// drawingManager.newCluster( (short)1 ); +// drawingManager.newCluster( (short)2 ); + } private void convertGroup( HSSFShapeGroup shape, EscherContainerRecord escherParent, Map shapeToObj ) @@ -678,7 +681,7 @@ public class EscherAggregate extends AbstractEscherHolderRecord spContainer1.setRecordId( EscherContainerRecord.SP_CONTAINER ); spContainer1.setOptions( (short) 0x000F ); spgr.setRecordId( EscherSpgrRecord.RECORD_ID ); - spgr.setOptions( (short) 0x0001 ); // don't know what the 1 is for. + spgr.setOptions( (short) 0x0001 ); // version spgr.setRectX1( patriarch.getX1() ); spgr.setRectY1( patriarch.getY1() ); spgr.setRectX2( patriarch.getX2() ); diff --git a/src/java/org/apache/poi/hssf/record/ObjRecord.java b/src/java/org/apache/poi/hssf/record/ObjRecord.java index e5a3aa0b09..a2489be362 100644 --- a/src/java/org/apache/poi/hssf/record/ObjRecord.java +++ b/src/java/org/apache/poi/hssf/record/ObjRecord.java @@ -173,6 +173,14 @@ public class ObjRecord return subrecords.add( o ); } + // made public to satisfy biffviewer + + /* protected */ + public void processContinueRecord( byte[] record ) + { + super.processContinueRecord( record ); + } + public Object clone() { ObjRecord rec = new ObjRecord(); diff --git a/src/java/org/apache/poi/hssf/record/RecordFactory.java b/src/java/org/apache/poi/hssf/record/RecordFactory.java index 5d4bc9696d..b4f3697e2d 100644 --- a/src/java/org/apache/poi/hssf/record/RecordFactory.java +++ b/src/java/org/apache/poi/hssf/record/RecordFactory.java @@ -148,6 +148,7 @@ public class RecordFactory { short rectype = 0; + DrawingRecord lastDrawingRecord = new DrawingRecord( ); do { rectype = LittleEndian.readShort(in); @@ -176,7 +177,13 @@ public class RecordFactory if (record != null) { - if (rectype == ContinueRecord.sid && + if (rectype == DrawingGroupRecord.sid + && last_record instanceof DrawingGroupRecord) + { + DrawingGroupRecord lastDGRecord = (DrawingGroupRecord) last_record; + lastDGRecord.join((AbstractEscherHolderRecord) record); + } + else if (rectype == ContinueRecord.sid && ! (last_record instanceof ContinueRecord) && // include continuation records after ! (last_record instanceof UnknownRecord) ) // unknown records or previous continuation records { @@ -185,11 +192,18 @@ public class RecordFactory throw new RecordFormatException( "First record is a ContinueRecord??"); } - last_record.processContinueRecord(data); + + // Drawing records have a very strange continue behaviour. There can actually be OBJ records mixed between the continues. + if (last_record instanceof ObjRecord) + lastDrawingRecord.processContinueRecord( data ); + else + last_record.processContinueRecord(data); } else { last_record = record; + if (record instanceof DrawingRecord) + lastDrawingRecord = (DrawingRecord) record; records.add(record); } } diff --git a/src/java/org/apache/poi/hssf/record/aggregates/ColumnInfoRecordsAggregate.java b/src/java/org/apache/poi/hssf/record/aggregates/ColumnInfoRecordsAggregate.java index 6eb9b111da..a7f8126110 100644 --- a/src/java/org/apache/poi/hssf/record/aggregates/ColumnInfoRecordsAggregate.java +++ b/src/java/org/apache/poi/hssf/record/aggregates/ColumnInfoRecordsAggregate.java @@ -14,7 +14,7 @@ import java.util.List; public class ColumnInfoRecordsAggregate extends Record { - int size = 0; +// int size = 0; List records = null; public ColumnInfoRecordsAggregate() @@ -40,6 +40,9 @@ public class ColumnInfoRecordsAggregate public int getRecordSize() { + int size = 0; + for ( Iterator iterator = records.iterator(); iterator.hasNext(); ) + size += ( (ColumnInfoRecord) iterator.next() ).getRecordSize(); return size; } @@ -68,7 +71,6 @@ public class ColumnInfoRecordsAggregate */ public void insertColumn( ColumnInfoRecord col ) { - size += col.getRecordSize(); records.add( col ); } @@ -78,7 +80,6 @@ public class ColumnInfoRecordsAggregate */ public void insertColumn( int idx, ColumnInfoRecord col ) { - size += col.getRecordSize(); records.add( idx, col ); } diff --git a/src/java/org/apache/poi/hssf/record/aggregates/RowRecordsAggregate.java b/src/java/org/apache/poi/hssf/record/aggregates/RowRecordsAggregate.java index 85e98f62ad..6a8e3554dc 100644 --- a/src/java/org/apache/poi/hssf/record/aggregates/RowRecordsAggregate.java +++ b/src/java/org/apache/poi/hssf/record/aggregates/RowRecordsAggregate.java @@ -228,7 +228,7 @@ public class RowRecordsAggregate /** * called by the constructor, should set class level fields. Should throw - * runtime exception for bad/icomplete data. + * runtime exception for bad/incomplete data. * * @param data raw data * @param size size of data diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFClientAnchor.java b/src/java/org/apache/poi/hssf/usermodel/HSSFClientAnchor.java index 029cc77ff5..7be224b28a 100644 --- a/src/java/org/apache/poi/hssf/usermodel/HSSFClientAnchor.java +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFClientAnchor.java @@ -16,8 +16,7 @@ package org.apache.poi.hssf.usermodel; -import org.apache.poi.ddf.EscherClientAnchorRecord; -import org.apache.poi.ddf.EscherRecord; + /** @@ -33,6 +32,7 @@ public class HSSFClientAnchor int row1; short col2; int row2; + int anchorType; /** * Creates a new client anchor and defaults all the anchor positions to 0. @@ -213,6 +213,26 @@ public class HSSFClientAnchor return row1 > row2; } + /** + * Gets the anchor type + *

    + * 0 = Move and size with Cells, 2 = Move but don't size with cells, 3 = Don't move or size with cells. + */ + public int getAnchorType() + { + return anchorType; + } + + /** + * Sets the anchor type + *

    + * 0 = Move and size with Cells, 2 = Move but don't size with cells, 3 = Don't move or size with cells. + */ + public void setAnchorType( int anchorType ) + { + this.anchorType = anchorType; + } + private void checkRange( int value, int minRange, int maxRange, String varName ) { if (value < minRange || value > maxRange) diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFPatriarch.java b/src/java/org/apache/poi/hssf/usermodel/HSSFPatriarch.java index c80982baec..771f48d84c 100644 --- a/src/java/org/apache/poi/hssf/usermodel/HSSFPatriarch.java +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFPatriarch.java @@ -77,6 +77,23 @@ public class HSSFPatriarch return shape; } + /** + * Creates a picture. + * + * @param anchor the client anchor describes how this group is attached + * to the sheet. + * @return the newly created shape. + */ + public HSSFPicture createPicture(HSSFClientAnchor anchor, int pictureIndex) + { + HSSFPicture shape = new HSSFPicture(null, anchor); + shape.setPictureIndex( pictureIndex ); + shape.anchor = anchor; + shapes.add(shape); + return shape; + } + + /** * Creates a polygon * diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFSheet.java b/src/java/org/apache/poi/hssf/usermodel/HSSFSheet.java index 991d79ceb7..75880ce50d 100644 --- a/src/java/org/apache/poi/hssf/usermodel/HSSFSheet.java +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFSheet.java @@ -1220,19 +1220,22 @@ public class HSSFSheet * Aggregates the drawing records and dumps the escher record hierarchy * to the standard output. */ - public void dumpDrawingRecords() + public void dumpDrawingRecords(boolean fat) { sheet.aggregateDrawingRecords(book.getDrawingManager()); EscherAggregate r = (EscherAggregate) getSheet().findFirstRecordBySid(EscherAggregate.sid); List escherRecords = r.getEscherRecords(); + PrintWriter w = new PrintWriter(System.out); for ( Iterator iterator = escherRecords.iterator(); iterator.hasNext(); ) { EscherRecord escherRecord = (EscherRecord) iterator.next(); - PrintWriter w = new PrintWriter(System.out); - escherRecord.display(w, 0); - w.close(); + if (fat) + System.out.println(escherRecord.toString()); + else + escherRecord.display(w, 0); } + w.flush(); } /** diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFSimpleShape.java b/src/java/org/apache/poi/hssf/usermodel/HSSFSimpleShape.java index 9969cdcdad..3adaff9f8f 100644 --- a/src/java/org/apache/poi/hssf/usermodel/HSSFSimpleShape.java +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFSimpleShape.java @@ -34,7 +34,7 @@ public class HSSFSimpleShape // public final static short OBJECT_TYPE_CHART = 5; // public final static short OBJECT_TYPE_TEXT = 6; // public final static short OBJECT_TYPE_BUTTON = 7; -// public final static short OBJECT_TYPE_PICTURE = 8; + public final static short OBJECT_TYPE_PICTURE = 8; // public final static short OBJECT_TYPE_POLYGON = 9; // public final static short OBJECT_TYPE_CHECKBOX = 11; // public final static short OBJECT_TYPE_OPTION_BUTTON = 12; @@ -63,6 +63,7 @@ public class HSSFSimpleShape * @see #OBJECT_TYPE_LINE * @see #OBJECT_TYPE_OVAL * @see #OBJECT_TYPE_RECTANGLE + * @see #OBJECT_TYPE_PICTURE */ public int getShapeType() { return shapeType; } @@ -74,6 +75,7 @@ public class HSSFSimpleShape * @see #OBJECT_TYPE_LINE * @see #OBJECT_TYPE_OVAL * @see #OBJECT_TYPE_RECTANGLE + * @see #OBJECT_TYPE_PICTURE */ public void setShapeType( int shapeType ){ this.shapeType = shapeType; } diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFWorkbook.java b/src/java/org/apache/poi/hssf/usermodel/HSSFWorkbook.java index 8d6533252c..500b220f08 100644 --- a/src/java/org/apache/poi/hssf/usermodel/HSSFWorkbook.java +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFWorkbook.java @@ -22,6 +22,9 @@ */ package org.apache.poi.hssf.usermodel; +import org.apache.poi.ddf.EscherBSERecord; +import org.apache.poi.ddf.EscherBitmapBlip; +import org.apache.poi.ddf.EscherRecord; import org.apache.poi.hssf.eventmodel.EventRecordFactory; import org.apache.poi.hssf.model.Sheet; import org.apache.poi.hssf.model.Workbook; @@ -38,6 +41,7 @@ import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.io.PrintWriter; import java.util.ArrayList; import java.util.Iterator; import java.util.List; @@ -57,7 +61,6 @@ import java.util.Stack; */ public class HSSFWorkbook - extends java.lang.Object { private static final int DEBUG = POILogger.DEBUG; @@ -108,13 +111,31 @@ public class HSSFWorkbook */ private HSSFDataFormat formatter; + + /// NOT YET SUPPORTED: + /** Extended windows meta file */ + //public static final int PICTURE_TYPE_EMF = 2; + /** Windows Meta File */ + //public static final int PICTURE_TYPE_WMF = 3; + /** Mac PICT format */ + //public static final int PICTURE_TYPE_PICT = 4; + + /** JPEG format */ + public static final int PICTURE_TYPE_JPEG = 5; + /** PNG format */ + public static final int PICTURE_TYPE_PNG = 6; + /** Device independant bitmap */ + public static final int PICTURE_TYPE_DIB = 7; + + private static POILogger log = POILogFactory.getLogger(HSSFWorkbook.class); + + /** * Creates new HSSFWorkbook from scratch (start here!) * */ - public HSSFWorkbook() { this(Workbook.createWorkbook()); @@ -159,8 +180,6 @@ public class HSSFWorkbook EventRecordFactory factory = new EventRecordFactory(); - - List records = RecordFactory.createRecords(stream); workbook = Workbook.createWorkbook(records); @@ -796,8 +815,9 @@ public class HSSFWorkbook // byte[] sb = (byte[])sheetbytes.get(k); // System.arraycopy(sb, 0, retval, pos, sb.length); - pos += ((HSSFSheet) sheets.get(k)).getSheet().serialize(pos, - retval); // sb.length; + int len = ((HSSFSheet) sheets.get(k)).getSheet().serialize(pos, + retval); + pos += len; // sb.length; } /* for (int k = pos; k < totalsize; k++) { @@ -1067,6 +1087,68 @@ public class HSSFWorkbook workbook.getRecords().add(loc, r); } + /** + * Spits out a list of all the drawing records in the workbook. + */ + public void dumpDrawingGroupRecords(boolean fat) + { + DrawingGroupRecord r = (DrawingGroupRecord) workbook.findFirstRecordBySid( DrawingGroupRecord.sid ); + r.decode(); + List escherRecords = r.getEscherRecords(); + PrintWriter w = new PrintWriter(System.out); + for ( Iterator iterator = escherRecords.iterator(); iterator.hasNext(); ) + { + EscherRecord escherRecord = (EscherRecord) iterator.next(); + if (fat) + System.out.println(escherRecord.toString()); + else + escherRecord.display(w, 0); + } + w.flush(); + } + /** + * Adds a picture to the workbook. + * + * @param pictureData The bytes of the picture + * @param format The format of the picture. One of PICTURE_TYPE_* + * + * @return the index to this picture (1 based). + */ + public int addPicture(byte[] pictureData, int format) + { + byte[] uid = newUID(); + EscherBitmapBlip blipRecord = new EscherBitmapBlip(); + blipRecord.setRecordId( (short) ( EscherBitmapBlip.RECORD_ID_START + format ) ); + if (format == HSSFWorkbook.PICTURE_TYPE_PNG) + blipRecord.setOptions( (short) 0x6E00 ); // MSOBI + else if (format == HSSFWorkbook.PICTURE_TYPE_JPEG) + blipRecord.setOptions( (short) 0x46A0 ); // MSOBI + else if (format == HSSFWorkbook.PICTURE_TYPE_DIB) + blipRecord.setOptions( (short) 0x7A8 ); // MSOBI + + blipRecord.setUID( uid ); + blipRecord.setMarker( (byte) 0xFF ); + blipRecord.setPictureData( pictureData ); + + EscherBSERecord r = new EscherBSERecord(); + r.setRecordId( EscherBSERecord.RECORD_ID ); + r.setOptions( (short) ( 0x0002 | ( format << 4 ) ) ); + r.setBlipTypeMacOS( (byte) format ); + r.setBlipTypeWin32( (byte) format ); + r.setUid( uid ); + r.setTag( (short) 0xFF ); + r.setSize( pictureData.length + 25 ); + r.setRef( 1 ); + r.setOffset( 0 ); + r.setBlipRecord( blipRecord ); + + return workbook.addBSERecord( r ); + } + private byte[] newUID() + { + byte[] bytes = new byte[16]; + return bytes; + } } diff --git a/src/java/org/apache/poi/util/DrawingDump.java b/src/java/org/apache/poi/util/DrawingDump.java index 926d982ac9..5c801e1431 100644 --- a/src/java/org/apache/poi/util/DrawingDump.java +++ b/src/java/org/apache/poi/util/DrawingDump.java @@ -17,12 +17,11 @@ package org.apache.poi.util; -import org.apache.poi.hssf.usermodel.HSSFWorkbook; import org.apache.poi.hssf.usermodel.HSSFSheet; +import org.apache.poi.hssf.usermodel.HSSFWorkbook; import org.apache.poi.poifs.filesystem.POIFSFileSystem; import java.io.FileInputStream; -import java.io.FileNotFoundException; import java.io.IOException; /** @@ -35,7 +34,15 @@ public class DrawingDump POIFSFileSystem fs = new POIFSFileSystem(new FileInputStream(args[0])); HSSFWorkbook wb = new HSSFWorkbook(fs); - HSSFSheet sheet = wb.getSheetAt(0); - sheet.dumpDrawingRecords(); + System.out.println( "Drawing group:" ); + wb.dumpDrawingGroupRecords(true); + + for (int sheetNum = 1; sheetNum <= wb.getNumberOfSheets(); sheetNum++) + { + System.out.println( "Sheet " + sheetNum + ":" ); + HSSFSheet sheet = wb.getSheetAt(sheetNum - 1); + sheet.dumpDrawingRecords(true); + } + } } diff --git a/src/java/org/apache/poi/util/HexDump.java b/src/java/org/apache/poi/util/HexDump.java index f6aeb59799..f6caba85c4 100644 --- a/src/java/org/apache/poi/util/HexDump.java +++ b/src/java/org/apache/poi/util/HexDump.java @@ -75,7 +75,7 @@ public class HexDump { if (data.length == 0) { - stream.write( "No Data".getBytes() ); + stream.write( ("No Data" + System.getProperty( "line.separator")).getBytes() ); stream.flush(); return; } diff --git a/src/testcases/org/apache/poi/ddf/TestEscherBSERecord.java b/src/testcases/org/apache/poi/ddf/TestEscherBSERecord.java index 8025e839e5..ce18f7025e 100644 --- a/src/testcases/org/apache/poi/ddf/TestEscherBSERecord.java +++ b/src/testcases/org/apache/poi/ddf/TestEscherBSERecord.java @@ -98,8 +98,9 @@ public class TestEscherBSERecord extends TestCase " Name: 5" + nl + " Unused2: 6" + nl + " Unused3: 7" + nl + + " blipRecord: null" + nl + " Extra Data:" + nl + - "No Data", record.toString() ); + "No Data" + nl, record.toString() ); } } diff --git a/src/testcases/org/apache/poi/ddf/TestEscherBlipRecord.java b/src/testcases/org/apache/poi/ddf/TestEscherBlipWMFRecord.java similarity index 91% rename from src/testcases/org/apache/poi/ddf/TestEscherBlipRecord.java rename to src/testcases/org/apache/poi/ddf/TestEscherBlipWMFRecord.java index 8b82135c41..aff89c29e3 100644 --- a/src/testcases/org/apache/poi/ddf/TestEscherBlipRecord.java +++ b/src/testcases/org/apache/poi/ddf/TestEscherBlipWMFRecord.java @@ -21,7 +21,7 @@ import junit.framework.TestCase; import org.apache.poi.util.HexDump; import org.apache.poi.util.HexRead; -public class TestEscherBlipRecord extends TestCase +public class TestEscherBlipWMFRecord extends TestCase { private String dataStr; private byte[] data; @@ -37,7 +37,7 @@ public class TestEscherBlipRecord extends TestCase public void testSerialize() throws Exception { - EscherBlipRecord r = new EscherBlipRecord(); + EscherBlipWMFRecord r = new EscherBlipWMFRecord(); r.setBoundaryLeft(1); r.setBoundaryHeight(2); r.setBoundaryTop(3); @@ -52,7 +52,7 @@ public class TestEscherBlipRecord extends TestCase (byte)0x01, (byte)0x01, (byte)0x01, (byte)0x01, }); r.setWidth(10); r.setHeight(11); - r.setRecordId(EscherBlipRecord.RECORD_ID_START); + r.setRecordId(EscherBlipWMFRecord.RECORD_ID_START); r.setOptions((short)5420); r.setData(new byte[] { (byte)0x01, (byte)0x02 } ); @@ -79,10 +79,10 @@ public class TestEscherBlipRecord extends TestCase public void testFillFields() throws Exception { - EscherBlipRecord r = new EscherBlipRecord(); + EscherBlipWMFRecord r = new EscherBlipWMFRecord(); r.fillFields( data, 0, new DefaultEscherRecordFactory()); - assertEquals( EscherBlipRecord.RECORD_ID_START, r.getRecordId() ); + assertEquals( EscherBlipWMFRecord.RECORD_ID_START, r.getRecordId() ); assertEquals( 1, r.getBoundaryLeft() ); assertEquals( 2, r.getBoundaryHeight() ); assertEquals( 3, r.getBoundaryTop() ); @@ -100,12 +100,12 @@ public class TestEscherBlipRecord extends TestCase public void testToString() throws Exception { - EscherBlipRecord r = new EscherBlipRecord(); + EscherBlipWMFRecord r = new EscherBlipWMFRecord(); r.fillFields( data, 0, new DefaultEscherRecordFactory() ); String nl = System.getProperty("line.separator"); - assertEquals( "org.apache.poi.ddf.EscherBlipRecord:" + nl + + assertEquals( "org.apache.poi.ddf.EscherBlipWMFRecord:" + nl + " RecordId: 0xF018" + nl + " Options: 0x152C" + nl + " Secondary UID: [01, 01, 01, 01, 01, 01, 01, 01, 01, 01, 01, 01, 01, 01, 01, 01, ]" + nl + diff --git a/src/testcases/org/apache/poi/ddf/TestEscherBoolProperty.java b/src/testcases/org/apache/poi/ddf/TestEscherBoolProperty.java index 473e03cc62..3fe0c762f8 100644 --- a/src/testcases/org/apache/poi/ddf/TestEscherBoolProperty.java +++ b/src/testcases/org/apache/poi/ddf/TestEscherBoolProperty.java @@ -24,7 +24,7 @@ public class TestEscherBoolProperty extends TestCase public void testToString() throws Exception { EscherBoolProperty p = new EscherBoolProperty((short)1, 1); - assertEquals("propNum: 1, propName: unknown, complex: false, blipId: false, value: 1 (0x00000001)", p.toString()); + assertEquals("propNum: 1, RAW: 0x0001, propName: unknown, complex: false, blipId: false, value: 1 (0x00000001)", p.toString()); } } diff --git a/src/testcases/org/apache/poi/ddf/TestEscherClientDataRecord.java b/src/testcases/org/apache/poi/ddf/TestEscherClientDataRecord.java index 0491029d29..d8cdab4414 100644 --- a/src/testcases/org/apache/poi/ddf/TestEscherClientDataRecord.java +++ b/src/testcases/org/apache/poi/ddf/TestEscherClientDataRecord.java @@ -58,7 +58,7 @@ public class TestEscherClientDataRecord extends TestCase " RecordId: 0xF011" + nl + " Options: 0x0002" + nl + " Extra Data:" + nl + - "No Data" ; + "No Data" + nl ; assertEquals( expected, createRecord().toString() ); } diff --git a/src/testcases/org/apache/poi/ddf/TestEscherOptRecord.java b/src/testcases/org/apache/poi/ddf/TestEscherOptRecord.java index 8795de77ad..334eeb49df 100644 --- a/src/testcases/org/apache/poi/ddf/TestEscherOptRecord.java +++ b/src/testcases/org/apache/poi/ddf/TestEscherOptRecord.java @@ -150,7 +150,7 @@ public class TestEscherOptRecord extends TestCase " recordId: 0x" + HexDump.toHex(EscherOptRecord.RECORD_ID) + nl + " numchildren: 0" + nl + " properties:" + nl + - " propNum: 1, propName: unknown, complex: false, blipId: false, value: 1 (0x00000001)" + nl; + " propNum: 1, RAW: 0x0001, propName: unknown, complex: false, blipId: false, value: 1 (0x00000001)" + nl; assertEquals( expected, r.toString()); } diff --git a/src/testcases/org/apache/poi/hssf/model/TestSheet.java b/src/testcases/org/apache/poi/hssf/model/TestSheet.java index b11d93f015..8388788c49 100644 --- a/src/testcases/org/apache/poi/hssf/model/TestSheet.java +++ b/src/testcases/org/apache/poi/hssf/model/TestSheet.java @@ -19,16 +19,14 @@ package org.apache.poi.hssf.model; import junit.framework.TestCase; -import org.apache.poi.hssf.usermodel.HSSFSheet; -import org.apache.poi.hssf.record.BOFRecord; -import org.apache.poi.hssf.record.EOFRecord; -import org.apache.poi.hssf.record.DimensionsRecord; +import org.apache.poi.hssf.record.*; +import org.apache.poi.hssf.record.aggregates.ColumnInfoRecordsAggregate; import org.apache.poi.hssf.record.aggregates.RowRecordsAggregate; import org.apache.poi.hssf.record.aggregates.ValueRecordsAggregate; -import org.apache.poi.hssf.record.aggregates.ColumnInfoRecordsAggregate; -import java.util.List; import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; /** * Unit test for the Sheet class. @@ -55,4 +53,232 @@ public class TestSheet extends TestCase assertTrue( sheet.records.get(pos++) instanceof EOFRecord ); } + public void testAddMergedRegion() + { + Sheet sheet = Sheet.createSheet(); + int regionsToAdd = 4096; + int startRecords = sheet.getRecords().size(); + + //simple test that adds a load of regions + for (int n = 0; n < regionsToAdd; n++) + { + int index = sheet.addMergedRegion(0, (short) 0, 1, (short) 1); + assertTrue("Merged region index expected to be " + n + " got " + index, index == n); + } + + //test all the regions were indeed added + assertTrue(sheet.getNumMergedRegions() == regionsToAdd); + + //test that the regions were spread out over the appropriate number of records + int recordsAdded = sheet.getRecords().size() - startRecords; + int recordsExpected = regionsToAdd/1027; + if ((regionsToAdd % 1027) != 0) + recordsExpected++; + assertTrue("The " + regionsToAdd + " merged regions should have been spread out over " + recordsExpected + " records, not " + recordsAdded, recordsAdded == recordsExpected); + } + + public void testRemoveMergedRegion() + { + Sheet sheet = Sheet.createSheet(); + int regionsToAdd = 4096; + + for (int n = 0; n < regionsToAdd; n++) + sheet.addMergedRegion(0, (short) 0, 1, (short) 1); + + int records = sheet.getRecords().size(); + + //remove a third from the beginning + for (int n = 0; n < regionsToAdd/3; n++) + { + sheet.removeMergedRegion(0); + //assert they have been deleted + assertTrue("Num of regions should be " + (regionsToAdd - n - 1) + " not " + sheet.getNumMergedRegions(), sheet.getNumMergedRegions() == regionsToAdd - n - 1); + } + + //assert any record removing was done + int recordsRemoved = (regionsToAdd/3)/1027; //doesn't work for particular values of regionsToAdd + assertTrue("Expected " + recordsRemoved + " record to be removed from the starting " + records + ". Currently there are " + sheet.getRecords().size() + " records", records - sheet.getRecords().size() == recordsRemoved); + } + + /** + * Bug: 22922 (Reported by Xuemin Guan) + *

    + * Remove mergedregion fails when a sheet loses records after an initial CreateSheet + * fills up the records. + * + */ + public void testMovingMergedRegion() { + List records = new ArrayList(); + + MergeCellsRecord merged = new MergeCellsRecord(); + merged.addArea(0, (short)0, 1, (short)2); + records.add(new RowRecord()); + records.add(new RowRecord()); + records.add(new RowRecord()); + records.add(merged); + + Sheet sheet = Sheet.createSheet(records, 0); + sheet.records.remove(0); + + //stub object to throw off list INDEX operations + sheet.removeMergedRegion(0); + assertEquals("Should be no more merged regions", 0, sheet.getNumMergedRegions()); + } + + public void testGetMergedRegionAt() + { + //TODO + } + + public void testGetNumMergedRegions() + { + //TODO + } + + /** + * Makes sure all rows registered for this sheet are aggregated, they were being skipped + * + */ + public void testRowAggregation() { + List records = new ArrayList(); + RowRecord row = new RowRecord(); + row.setRowNumber(0); + records.add(row); + + row = new RowRecord(); + row.setRowNumber(1); + records.add(row); + + records.add(new StringRecord()); + + row = new RowRecord(); + row.setRowNumber(2); + records.add(row); + + + Sheet sheet = Sheet.createSheet(records, 0); + assertNotNull("Row [2] was skipped", sheet.getRow(2)); + + } + + /** + * Make sure page break functionality works (in memory) + * + */ + public void testRowPageBreaks(){ + short colFrom = 0; + short colTo = 255; + + Sheet sheet = Sheet.createSheet(); + sheet.setRowBreak(0, colFrom, colTo); + + assertTrue("no row break at 0", sheet.isRowBroken(0)); + assertEquals("1 row break available", 1, sheet.getNumRowBreaks()); + + sheet.setRowBreak(0, colFrom, colTo); + sheet.setRowBreak(0, colFrom, colTo); + + assertTrue("no row break at 0", sheet.isRowBroken(0)); + assertEquals("1 row break available", 1, sheet.getNumRowBreaks()); + + sheet.setRowBreak(10, colFrom, colTo); + sheet.setRowBreak(11, colFrom, colTo); + + assertTrue("no row break at 10", sheet.isRowBroken(10)); + assertTrue("no row break at 11", sheet.isRowBroken(11)); + assertEquals("3 row break available", 3, sheet.getNumRowBreaks()); + + + boolean is10 = false; + boolean is0 = false; + boolean is11 = false; + + Iterator iterator = sheet.getRowBreaks(); + while (iterator.hasNext()) { + PageBreakRecord.Break breakItem = (PageBreakRecord.Break)iterator.next(); + int main = (int)breakItem.main; + if (main != 0 && main != 10 && main != 11) fail("Invalid page break"); + if (main == 0) is0 = true; + if (main == 10) is10= true; + if (main == 11) is11 = true; + } + + assertTrue("one of the breaks didnt make it", is0 && is10 && is11); + + sheet.removeRowBreak(11); + assertFalse("row should be removed", sheet.isRowBroken(11)); + + sheet.removeRowBreak(0); + assertFalse("row should be removed", sheet.isRowBroken(0)); + + sheet.removeRowBreak(10); + assertFalse("row should be removed", sheet.isRowBroken(10)); + + assertEquals("no more breaks", 0, sheet.getNumRowBreaks()); + + + } + + /** + * Make sure column pag breaks works properly (in-memory) + * + */ + public void testColPageBreaks(){ + short rowFrom = 0; + short rowTo = (short)65535; + + Sheet sheet = Sheet.createSheet(); + sheet.setColumnBreak((short)0, rowFrom, rowTo); + + assertTrue("no col break at 0", sheet.isColumnBroken((short)0)); + assertEquals("1 col break available", 1, sheet.getNumColumnBreaks()); + + sheet.setColumnBreak((short)0, rowFrom, rowTo); + + assertTrue("no col break at 0", sheet.isColumnBroken((short)0)); + assertEquals("1 col break available", 1, sheet.getNumColumnBreaks()); + + sheet.setColumnBreak((short)1, rowFrom, rowTo); + sheet.setColumnBreak((short)10, rowFrom, rowTo); + sheet.setColumnBreak((short)15, rowFrom, rowTo); + + assertTrue("no col break at 1", sheet.isColumnBroken((short)1)); + assertTrue("no col break at 10", sheet.isColumnBroken((short)10)); + assertTrue("no col break at 15", sheet.isColumnBroken((short)15)); + assertEquals("4 col break available", 4, sheet.getNumColumnBreaks()); + + boolean is10 = false; + boolean is0 = false; + boolean is1 = false; + boolean is15 = false; + + Iterator iterator = sheet.getColumnBreaks(); + while (iterator.hasNext()) { + PageBreakRecord.Break breakItem = (PageBreakRecord.Break)iterator.next(); + int main = (int)breakItem.main; + if (main != 0 && main != 1 && main != 10 && main != 15) fail("Invalid page break"); + if (main == 0) is0 = true; + if (main == 1) is1 = true; + if (main == 10) is10= true; + if (main == 15) is15 = true; + } + + assertTrue("one of the breaks didnt make it", is0 && is1 && is10 && is15); + + sheet.removeColumnBreak((short)15); + assertFalse("column break should not be there", sheet.isColumnBroken((short)15)); + + sheet.removeColumnBreak((short)0); + assertFalse("column break should not be there", sheet.isColumnBroken((short)0)); + + sheet.removeColumnBreak((short)1); + assertFalse("column break should not be there", sheet.isColumnBroken((short)1)); + + sheet.removeColumnBreak((short)10); + assertFalse("column break should not be there", sheet.isColumnBroken((short)10)); + + assertEquals("no more breaks", 0, sheet.getNumColumnBreaks()); + } + + } diff --git a/src/testcases/org/apache/poi/hssf/record/TestDrawingGroupRecord.java b/src/testcases/org/apache/poi/hssf/record/TestDrawingGroupRecord.java index 20fbae92e3..f56bbddd73 100644 --- a/src/testcases/org/apache/poi/hssf/record/TestDrawingGroupRecord.java +++ b/src/testcases/org/apache/poi/hssf/record/TestDrawingGroupRecord.java @@ -23,6 +23,9 @@ import org.apache.poi.util.HexDump; public class TestDrawingGroupRecord extends TestCase { + static final int MAX_RECORD_SIZE = 8228; + private static final int MAX_DATA_SIZE = MAX_RECORD_SIZE - 4; + public void testGetRecordSize() throws Exception { @@ -48,5 +51,93 @@ public class TestDrawingGroupRecord extends TestCase assertEquals(28, size); assertEquals(24, dggContainer.getRecordSize()); + + + r = new DrawingGroupRecord( ); + r.setRawData( new byte[MAX_DATA_SIZE] ); + assertEquals( MAX_RECORD_SIZE, r.getRecordSize() ); + r.setRawData( new byte[MAX_DATA_SIZE+1] ); + assertEquals( MAX_RECORD_SIZE + 5, r.getRecordSize() ); + r.setRawData( new byte[MAX_DATA_SIZE*2] ); + assertEquals( MAX_RECORD_SIZE * 2, r.getRecordSize() ); + r.setRawData( new byte[MAX_DATA_SIZE*2 + 1] ); + assertEquals( MAX_RECORD_SIZE * 2 + 5, r.getRecordSize() ); + } + + public void testSerialize() throws Exception + { + // Check under max record size + DrawingGroupRecord r = new DrawingGroupRecord(); + byte[] rawData = new byte[100]; + rawData[0] = 100; + rawData[99] = (byte) 200; + r.setRawData( rawData ); + byte[] buffer = new byte[r.getRecordSize()]; + int size = r.serialize( 0, buffer ); + assertEquals( 104, size ); + assertEquals("[EB, 00, 64, 00, 64, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, C8, ]", HexDump.toHex(buffer)); + + // check at max record size + rawData = new byte[MAX_DATA_SIZE]; + r.setRawData( rawData ); + buffer = new byte[r.getRecordSize()]; + size = r.serialize( 0, buffer ); + assertEquals( MAX_RECORD_SIZE, size ); + + // check over max record size + rawData = new byte[MAX_DATA_SIZE+1]; + rawData[rawData.length-1] = (byte) 255; + r.setRawData( rawData ); + buffer = new byte[r.getRecordSize()]; + size = r.serialize( 0, buffer ); + assertEquals( MAX_RECORD_SIZE + 5, size ); + assertEquals( "[EB, 00, 20, 20, ]", HexDump.toHex(cut(buffer, 0, 4) )); + assertEquals( "[00, EB, 00, 01, 00, FF, ]", HexDump.toHex(cut(buffer, MAX_RECORD_SIZE - 1, MAX_RECORD_SIZE + 5) )); + + // check continue record + rawData = new byte[MAX_DATA_SIZE * 2 + 1]; + rawData[rawData.length-1] = (byte) 255; + r.setRawData( rawData ); + buffer = new byte[r.getRecordSize()]; + size = r.serialize( 0, buffer ); + assertEquals( MAX_RECORD_SIZE * 2 + 5, size ); + assertEquals( MAX_RECORD_SIZE * 2 + 5, r.getRecordSize() ); + assertEquals( "[EB, 00, 20, 20, ]", HexDump.toHex(cut(buffer, 0, 4) )); + assertEquals( "[EB, 00, 20, 20, ]", HexDump.toHex(cut(buffer, MAX_RECORD_SIZE, MAX_RECORD_SIZE + 4) )); + assertEquals( "[3C, 00, 01, 00, FF, ]", HexDump.toHex(cut(buffer, MAX_RECORD_SIZE * 2, MAX_RECORD_SIZE * 2 + 5) )); + + // check continue record + rawData = new byte[664532]; + r.setRawData( rawData ); + buffer = new byte[r.getRecordSize()]; + size = r.serialize( 0, buffer ); + assertEquals( 664856, size ); + assertEquals( 664856, r.getRecordSize() ); + } + + private byte[] cut( byte[] data, int fromInclusive, int toExclusive ) + { + int length = toExclusive - fromInclusive; + byte[] result = new byte[length]; + System.arraycopy( data, fromInclusive, result, 0, length); + return result; + } + + public void testGrossSizeFromDataSize() throws Exception + { + for (int i = 0; i < MAX_RECORD_SIZE * 4; i += 11) + { + System.out.print( "data size = " + i + ", gross size = " + DrawingGroupRecord.grossSizeFromDataSize( i ) ); + System.out.println( " Diff: " + (DrawingGroupRecord.grossSizeFromDataSize( i ) - i) ); + } + + assertEquals( 4, DrawingGroupRecord.grossSizeFromDataSize( 0 ) ); + assertEquals( 5, DrawingGroupRecord.grossSizeFromDataSize( 1 ) ); + assertEquals( MAX_RECORD_SIZE, DrawingGroupRecord.grossSizeFromDataSize( MAX_DATA_SIZE ) ); + assertEquals( MAX_RECORD_SIZE + 5, DrawingGroupRecord.grossSizeFromDataSize( MAX_DATA_SIZE + 1 ) ); + assertEquals( MAX_RECORD_SIZE * 2, DrawingGroupRecord.grossSizeFromDataSize( MAX_DATA_SIZE * 2 ) ); + assertEquals( MAX_RECORD_SIZE * 2 + 5, DrawingGroupRecord.grossSizeFromDataSize( MAX_DATA_SIZE * 2 + 1 ) ); } + + } diff --git a/src/testcases/org/apache/poi/hssf/record/TestEscherAggregate.java b/src/testcases/org/apache/poi/hssf/record/TestEscherAggregate.java index 248e1e41f0..53323b8d0c 100644 --- a/src/testcases/org/apache/poi/hssf/record/TestEscherAggregate.java +++ b/src/testcases/org/apache/poi/hssf/record/TestEscherAggregate.java @@ -17,14 +17,13 @@ package org.apache.poi.hssf.record; import junit.framework.TestCase; -import org.apache.poi.util.HexRead; -import org.apache.poi.util.HexDump; -import org.apache.poi.ddf.EscherContainerRecord; -import org.apache.poi.ddf.EscherSpRecord; import org.apache.poi.ddf.EscherClientDataRecord; +import org.apache.poi.ddf.EscherContainerRecord; import org.apache.poi.ddf.EscherDggRecord; -import org.apache.poi.hssf.model.DrawingManager; -import org.apache.poi.hssf.model.Sheet; +import org.apache.poi.ddf.EscherSpRecord; +import org.apache.poi.hssf.model.DrawingManager2; +import org.apache.poi.util.HexDump; +import org.apache.poi.util.HexRead; import java.util.ArrayList; import java.util.List; @@ -82,7 +81,7 @@ public class TestEscherAggregate extends TestCase records.add( d2 ); records.add( r2 ); - DrawingManager drawingManager = new DrawingManager(new EscherDggRecord() ); + DrawingManager2 drawingManager = new DrawingManager2(new EscherDggRecord() ); EscherAggregate aggregate = EscherAggregate.createAggregate( records, 0, drawingManager ); assertEquals( 1, aggregate.getEscherRecords().size() ); diff --git a/src/testcases/org/apache/poi/util/TestHexDump.java b/src/testcases/org/apache/poi/util/TestHexDump.java index 6189d3e441..d19b79fe70 100644 --- a/src/testcases/org/apache/poi/util/TestHexDump.java +++ b/src/testcases/org/apache/poi/util/TestHexDump.java @@ -23,6 +23,7 @@ import junit.framework.*; import java.io.*; /** + * @author Glen Stampoultzis (glens at apache.org) * @author Marc Johnson (mjohnson at apache dot org) */ @@ -281,7 +282,8 @@ public class TestHexDump // verify proper behaviour with empty byte array ByteArrayOutputStream os = new ByteArrayOutputStream( ); HexDump.dump( new byte[0], 0, os, 0 ); - assertEquals( "No Data", os.toString() ); + assertEquals( "No Data" + System.getProperty( "line.separator"), os.toString() ); + } public void testToHex() @@ -289,7 +291,7 @@ public class TestHexDump { assertEquals( "000A", HexDump.toHex((short)0xA)); assertEquals( "0A", HexDump.toHex((byte)0xA)); - assertEquals( "0000000A", HexDump.toHex((int)0xA)); + assertEquals( "0000000A", HexDump.toHex(0xA)); assertEquals( "FFFF", HexDump.toHex((short)0xFFFF)); diff --git a/src/testcases/org/apache/poi/util/TestLittleEndian.java b/src/testcases/org/apache/poi/util/TestLittleEndian.java index 4e63ac9685..98b8b63d7a 100644 --- a/src/testcases/org/apache/poi/util/TestLittleEndian.java +++ b/src/testcases/org/apache/poi/util/TestLittleEndian.java @@ -18,11 +18,12 @@ package org.apache.poi.util; +import junit.framework.TestCase; import org.apache.poi.util.LittleEndian.BufferUnderrunException; -import java.io.*; - -import junit.framework.*; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; /** * Class to test LittleEndian functionality @@ -39,7 +40,6 @@ public class TestLittleEndian * * @param name */ - public TestLittleEndian(String name) { super(name); -- 2.39.5