diff --git a/src/java/org/apache/poi/ddf/EscherMetafileBlip.java b/src/java/org/apache/poi/ddf/EscherMetafileBlip.java index 99faa61bc..75c282ea5 100644 --- a/src/java/org/apache/poi/ddf/EscherMetafileBlip.java +++ b/src/java/org/apache/poi/ddf/EscherMetafileBlip.java @@ -41,9 +41,20 @@ public class EscherMetafileBlip public static final short RECORD_ID_WMF = (short) 0xF018 + 3; public static final short RECORD_ID_PICT = (short) 0xF018 + 4; + /** + * BLIP signatures as defined in the escher spec + */ + public static final short SIGNATURE_EMF = 0x3D40; + public static final short SIGNATURE_WMF = 0x2160; + public static final short SIGNATURE_PICT = 0x5420; + private static final int HEADER_SIZE = 8; private byte[] field_1_UID; + /** + * The primary UID is only saved to disk if (blip_instance ^ blip_signature == 1) + */ + private byte[] field_2_UID; private int field_2_cb; private int field_3_rcBounds_x1; private int field_3_rcBounds_y1; @@ -72,6 +83,12 @@ public class EscherMetafileBlip field_1_UID = new byte[16]; System.arraycopy( data, pos, field_1_UID, 0, 16 ); pos += 16; + + if((getOptions() ^ getSignature()) == 0x10){ + field_2_UID = new byte[16]; + System.arraycopy( data, pos, field_2_UID, 0, 16 ); pos += 16; + } + field_2_cb = LittleEndian.getInt( data, pos ); pos += 4; field_3_rcBounds_x1 = LittleEndian.getInt( data, pos ); pos += 4; field_3_rcBounds_y1 = LittleEndian.getInt( data, pos ); pos += 4; @@ -83,11 +100,8 @@ public class EscherMetafileBlip field_6_fCompression = data[pos]; pos++; field_7_fFilter = data[pos]; pos++; - // Bit of a snag - trusting field_5_cbSave results in inconsistent - // record size in some cases. So, just check the data left - int remainingBytes = bytesAfterHeader - 50; - raw_pictureData = new byte[remainingBytes]; - System.arraycopy( data, pos, raw_pictureData, 0, remainingBytes ); + raw_pictureData = new byte[field_5_cbSave]; + System.arraycopy( data, pos, raw_pictureData, 0, field_5_cbSave ); // 0 means DEFLATE compression // 0xFE means no compression @@ -121,9 +135,12 @@ public class EscherMetafileBlip int pos = offset; LittleEndian.putShort( data, pos, getOptions() ); pos += 2; LittleEndian.putShort( data, pos, getRecordId() ); pos += 2; - LittleEndian.putInt( data, getRecordSize() - HEADER_SIZE ); pos += 4; + LittleEndian.putInt( data, pos, getRecordSize() - HEADER_SIZE ); pos += 4; - System.arraycopy( field_1_UID, 0, data, pos, 16 ); pos += 16; + System.arraycopy( field_1_UID, 0, data, pos, field_1_UID.length ); pos += field_1_UID.length; + if((getOptions() ^ getSignature()) == 0x10){ + System.arraycopy( field_2_UID, 0, data, pos, field_2_UID.length ); pos += field_2_UID.length; + } LittleEndian.putInt( data, pos, field_2_cb ); pos += 4; LittleEndian.putInt( data, pos, field_3_rcBounds_x1 ); pos += 4; LittleEndian.putInt( data, pos, field_3_rcBounds_y1 ); pos += 4; @@ -138,7 +155,7 @@ public class EscherMetafileBlip System.arraycopy( raw_pictureData, 0, data, pos, raw_pictureData.length ); listener.afterRecordSerialize(offset + getRecordSize(), getRecordId(), getRecordSize(), this); - return HEADER_SIZE + 16 + 1 + raw_pictureData.length; + return getRecordSize(); } /** @@ -164,7 +181,7 @@ public class EscherMetafileBlip } catch ( IOException e ) { - log.log(POILogger.INFO, "Possibly corrupt compression or non-compressed data", e); + log.log(POILogger.WARN, "Possibly corrupt compression or non-compressed data", e); return data; } } @@ -176,7 +193,11 @@ public class EscherMetafileBlip */ public int getRecordSize() { - return 8 + 50 + raw_pictureData.length; + int size = 8 + 50 + raw_pictureData.length; + if((getOptions() ^ getSignature()) == 0x10){ + size += field_2_UID.length; + } + return size; } public byte[] getUID() @@ -189,6 +210,16 @@ public class EscherMetafileBlip this.field_1_UID = field_1_UID; } + public byte[] getPrimaryUID() + { + return field_2_UID; + } + + public void setPrimaryUID( byte[] field_2_UID ) + { + this.field_2_UID = field_2_UID; + } + public int getUncompressedSize() { return field_2_cb; @@ -267,6 +298,7 @@ public class EscherMetafileBlip " RecordId: 0x" + HexDump.toHex( getRecordId() ) + nl + " Options: 0x" + HexDump.toHex( getOptions() ) + nl + " UID: 0x" + HexDump.toHex( field_1_UID ) + nl + + (field_2_UID == null ? "" : (" UID2: 0x" + HexDump.toHex( field_2_UID ) + nl)) + " Uncompressed Size: " + HexDump.toHex( field_2_cb ) + nl + " Bounds: " + getBounds() + nl + " Size in EMU: " + getSizeEMU() + nl + @@ -276,4 +308,19 @@ public class EscherMetafileBlip " Extra Data:" + nl + extraData; } + /** + * Return the blip signature + * + * @return the blip signature + */ + public short getSignature(){ + short sig = 0; + switch(getRecordId()){ + case RECORD_ID_EMF: sig = SIGNATURE_EMF; break; + case RECORD_ID_WMF: sig = SIGNATURE_WMF; break; + case RECORD_ID_PICT: sig = SIGNATURE_PICT; break; + default: log.log(POILogger.WARN, "Unknown metafile: " + getRecordId()); break; + } + return sig; + } } diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFPictureData.java b/src/java/org/apache/poi/hssf/usermodel/HSSFPictureData.java index 439100a53..487c277f3 100644 --- a/src/java/org/apache/poi/hssf/usermodel/HSSFPictureData.java +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFPictureData.java @@ -19,6 +19,7 @@ package org.apache.poi.hssf.usermodel; import org.apache.poi.ddf.EscherBitmapBlip; import org.apache.poi.ddf.EscherBlipRecord; +import org.apache.poi.ddf.EscherMetafileBlip; /** * Represents binary data stored in the file. Eg. A GIF, JPEG etc... @@ -69,19 +70,19 @@ public class HSSFPictureData */ public String suggestFileExtension() { - switch (blip.getOptions() & FORMAT_MASK) + switch (blip.getRecordId()) { - case MSOBI_WMF: + case EscherMetafileBlip.RECORD_ID_WMF: return "wmf"; - case MSOBI_EMF: + case EscherMetafileBlip.RECORD_ID_EMF: return "emf"; - case MSOBI_PICT: + case EscherMetafileBlip.RECORD_ID_PICT: return "pict"; - case MSOBI_PNG: + case EscherBitmapBlip.RECORD_ID_PNG: return "png"; - case MSOBI_JPEG: + case EscherBitmapBlip.RECORD_ID_JPEG: return "jpeg"; - case MSOBI_DIB: + case EscherBitmapBlip.RECORD_ID_DIB: return "dib"; default: return ""; diff --git a/src/testcases/org/apache/poi/ddf/AllPOIDDFTests.java b/src/testcases/org/apache/poi/ddf/AllPOIDDFTests.java index 9b5fc0bfe..ca82ac83c 100755 --- a/src/testcases/org/apache/poi/ddf/AllPOIDDFTests.java +++ b/src/testcases/org/apache/poi/ddf/AllPOIDDFTests.java @@ -42,6 +42,7 @@ public final class AllPOIDDFTests { result.addTestSuite(TestEscherSplitMenuColorsRecord.class); result.addTestSuite(TestEscherSpRecord.class); result.addTestSuite(TestUnknownEscherRecord.class); + result.addTestSuite(TestEscherBlipRecord.class); return result; } } diff --git a/src/testcases/org/apache/poi/ddf/TestEscherBlipRecord.java b/src/testcases/org/apache/poi/ddf/TestEscherBlipRecord.java new file mode 100755 index 000000000..f7fecb666 --- /dev/null +++ b/src/testcases/org/apache/poi/ddf/TestEscherBlipRecord.java @@ -0,0 +1,156 @@ + +/* ==================================================================== + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +==================================================================== */ + +package org.apache.poi.ddf; + +import junit.framework.TestCase; +import org.apache.poi.util.HexRead; +import org.apache.poi.util.HexDump; + +import java.io.IOException; +import java.io.File; +import java.io.FileInputStream; +import java.util.Iterator; +import java.util.Arrays; + +/** + * Test read/serialize of escher blip records + * + * @author Yegor Kozlov + */ +public class TestEscherBlipRecord extends TestCase +{ + protected String cwd = System.getProperty("DDF.testdata.path"); + + //test reading/serializing of a PNG blip + public void testReadPNG() throws IOException { + //provided in bug-44886 + byte[] data = read(new File(cwd, "Container.dat")); + + EscherContainerRecord record = new EscherContainerRecord(); + record.fillFields(data, 0, new DefaultEscherRecordFactory()); + EscherContainerRecord bstore = (EscherContainerRecord)record.getChildRecords().get(1); + EscherBSERecord bse1 = (EscherBSERecord)bstore.getChildRecords().get(0); + assertEquals(EscherBSERecord.BT_PNG, bse1.getBlipTypeWin32()); + assertEquals(EscherBSERecord.BT_PNG, bse1.getBlipTypeMacOS()); + assertTrue(Arrays.equals(new byte[]{ + 0x65, 0x07, 0x4A, (byte)0x8D, 0x3E, 0x42, (byte)0x8B, (byte)0xAC, + 0x1D, (byte)0x89, 0x35, 0x4F, 0x48, (byte)0xFA, 0x37, (byte)0xC2 + }, bse1.getUid())); + assertEquals(255, bse1.getTag()); + assertEquals(32308, bse1.getSize()); + + EscherBitmapBlip blip1 = (EscherBitmapBlip)bse1.getBlipRecord(); + assertEquals(0x6E00, blip1.getOptions()); + assertEquals(EscherBitmapBlip.RECORD_ID_PNG, blip1.getRecordId()); + assertTrue(Arrays.equals(new byte[]{ + 0x65, 0x07, 0x4A, (byte)0x8D, 0x3E, 0x42, (byte)0x8B, (byte)0xAC, + 0x1D, (byte)0x89, 0x35, 0x4F, 0x48, (byte)0xFA, 0x37, (byte)0xC2 + }, blip1.getUID())); + + //serialize and read again + byte[] ser = bse1.serialize(); + EscherBSERecord bse2 = new EscherBSERecord(); + bse2.fillFields(ser, 0, new DefaultEscherRecordFactory()); + assertEquals(bse1.getRecordId(), bse2.getRecordId()); + assertEquals(bse1.getBlipTypeWin32(), bse2.getBlipTypeWin32()); + assertEquals(bse1.getBlipTypeMacOS(), bse2.getBlipTypeMacOS()); + assertTrue(Arrays.equals(bse1.getUid(), bse2.getUid())); + assertEquals(bse1.getTag(), bse2.getTag()); + assertEquals(bse1.getSize(), bse2.getSize()); + + EscherBitmapBlip blip2 = (EscherBitmapBlip)bse1.getBlipRecord(); + assertEquals(blip1.getOptions(), blip2.getOptions()); + assertEquals(blip1.getRecordId(), blip2.getRecordId()); + assertEquals(blip1.getUID(), blip2.getUID()); + + assertTrue(Arrays.equals(blip1.getPicturedata(), blip1.getPicturedata())); + } + + //test reading/serializing of a PICT metafile + public void testReadPICT() throws IOException { + //provided in bug-44886 + byte[] data = read(new File(cwd, "Container.dat")); + + EscherContainerRecord record = new EscherContainerRecord(); + record.fillFields(data, 0, new DefaultEscherRecordFactory()); + EscherContainerRecord bstore = (EscherContainerRecord)record.getChildRecords().get(1); + EscherBSERecord bse1 = (EscherBSERecord)bstore.getChildRecords().get(1); + //System.out.println(bse1); + assertEquals(EscherBSERecord.BT_WMF, bse1.getBlipTypeWin32()); + assertEquals(EscherBSERecord.BT_PICT, bse1.getBlipTypeMacOS()); + assertTrue(Arrays.equals(new byte[]{ + (byte)0xC7, 0x15, 0x69, 0x2D, (byte)0xE5, (byte)0x89, (byte)0xA3, 0x6F, + 0x66, 0x03, (byte)0xD6, 0x24, (byte)0xF7, (byte)0xDB, 0x1D, 0x13 + }, bse1.getUid())); + assertEquals(255, bse1.getTag()); + assertEquals(1133, bse1.getSize()); + + EscherMetafileBlip blip1 = (EscherMetafileBlip)bse1.getBlipRecord(); + assertEquals(0x5430, blip1.getOptions()); + assertEquals(EscherMetafileBlip.RECORD_ID_PICT, blip1.getRecordId()); + assertTrue(Arrays.equals(new byte[]{ + 0x57, 0x32, 0x7B, (byte)0x91, 0x23, 0x5D, (byte)0xDB, 0x36, + 0x7A, (byte)0xDB, (byte)0xFF, 0x17, (byte)0xFE, (byte)0xF3, (byte)0xA7, 0x05 + }, blip1.getUID())); + assertTrue(Arrays.equals(new byte[]{ + (byte)0xC7, 0x15, 0x69, 0x2D, (byte)0xE5, (byte)0x89, (byte)0xA3, 0x6F, + 0x66, 0x03, (byte)0xD6, 0x24, (byte)0xF7, (byte)0xDB, 0x1D, 0x13 + }, blip1.getPrimaryUID())); + + //serialize and read again + byte[] ser = bse1.serialize(); + EscherBSERecord bse2 = new EscherBSERecord(); + bse2.fillFields(ser, 0, new DefaultEscherRecordFactory()); + assertEquals(bse1.getRecordId(), bse2.getRecordId()); + assertEquals(bse1.getOptions(), bse2.getOptions()); + assertEquals(bse1.getBlipTypeWin32(), bse2.getBlipTypeWin32()); + assertEquals(bse1.getBlipTypeMacOS(), bse2.getBlipTypeMacOS()); + assertTrue(Arrays.equals(bse1.getUid(), bse2.getUid())); + assertEquals(bse1.getTag(), bse2.getTag()); + assertEquals(bse1.getSize(), bse2.getSize()); + + EscherMetafileBlip blip2 = (EscherMetafileBlip)bse1.getBlipRecord(); + assertEquals(blip1.getOptions(), blip2.getOptions()); + assertEquals(blip1.getRecordId(), blip2.getRecordId()); + assertEquals(blip1.getUID(), blip2.getUID()); + assertEquals(blip1.getPrimaryUID(), blip2.getPrimaryUID()); + + assertTrue(Arrays.equals(blip1.getPicturedata(), blip1.getPicturedata())); + } + + //integral test: check that the read-write-read round trip is consistent + public void testContainer() throws IOException { + byte[] data = read(new File(cwd, "Container.dat")); + + EscherContainerRecord record = new EscherContainerRecord(); + record.fillFields(data, 0, new DefaultEscherRecordFactory()); + + byte[] ser = record.serialize(); + assertTrue(Arrays.equals(data, ser)); + } + + private byte[] read(File file) throws IOException { + byte[] data = new byte[(int)file.length()]; + FileInputStream is = new FileInputStream(file); + is.read(data); + is.close(); + return data; + } + +}