package org.apache.poi.ddf;
import java.awt.Dimension;
import java.awt.Rectangle;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.zip.DeflaterOutputStream;
import java.util.zip.InflaterInputStream;
import org.apache.poi.hssf.usermodel.HSSFPictureData;
import org.apache.poi.util.IOUtils;
import org.apache.poi.util.LittleEndian;
import org.apache.poi.util.POILogFactory;
import org.apache.poi.util.POILogger;
public final class EscherMetafileBlip extends EscherBlipRecord {
private static final POILogger log = POILogFactory.getLogger(EscherMetafileBlip.class);
private static final int MAX_RECORD_LENGTH = 100_000_000;
public static final short RECORD_ID_EMF = (short) 0xF018 + 2;
public static final short RECORD_ID_WMF = (short) 0xF018 + 3;
public static final short RECORD_ID_PICT = (short) 0xF018 + 4;
private static final int = 8;
private final byte[] field_1_UID = new byte[16];
private final byte[] field_2_UID = new byte[16];
private int field_2_cb;
private int field_3_rcBounds_x1;
private int field_3_rcBounds_y1;
private int field_3_rcBounds_x2;
private int field_3_rcBounds_y2;
private int field_4_ptSize_w;
private int field_4_ptSize_h;
private int field_5_cbSave;
private byte field_6_fCompression;
private byte field_7_fFilter;
private byte[] raw_pictureData;
private byte[] remainingData;
@Override
public int fillFields(byte[] data, int offset, EscherRecordFactory recordFactory) {
int bytesAfterHeader = readHeader( data, offset );
int pos = offset + HEADER_SIZE;
System.arraycopy( data, pos, field_1_UID, 0, 16 ); pos += 16;
if((getOptions() ^ getSignature()) == 0x10){
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;
field_3_rcBounds_x2 = LittleEndian.getInt( data, pos ); pos += 4;
field_3_rcBounds_y2 = LittleEndian.getInt( data, pos ); pos += 4;
field_4_ptSize_w = LittleEndian.getInt( data, pos ); pos += 4;
field_4_ptSize_h = LittleEndian.getInt( data, pos ); pos += 4;
field_5_cbSave = LittleEndian.getInt( data, pos ); pos += 4;
field_6_fCompression = data[pos]; pos++;
field_7_fFilter = data[pos]; pos++;
raw_pictureData = IOUtils.safelyAllocate(field_5_cbSave, MAX_RECORD_LENGTH);
System.arraycopy( data, pos, raw_pictureData, 0, field_5_cbSave );
pos += field_5_cbSave;
if (field_6_fCompression == 0) {
super.setPictureData(inflatePictureData(raw_pictureData));
} else {
super.setPictureData(raw_pictureData);
}
int remaining = bytesAfterHeader - pos + offset + HEADER_SIZE;
if(remaining > 0) {
remainingData = IOUtils.safelyAllocate(remaining, MAX_RECORD_LENGTH);
System.arraycopy( data, pos, remainingData, 0, remaining );
}
return bytesAfterHeader + HEADER_SIZE;
}
@Override
public int serialize(int offset, byte[] data, EscherSerializationListener listener) {
listener.beforeRecordSerialize(offset, getRecordId(), this);
int pos = offset;
LittleEndian.putShort( data, pos, getOptions() ); pos += 2;
LittleEndian.putShort( data, pos, getRecordId() ); pos += 2;
LittleEndian.putInt( data, pos, getRecordSize() - HEADER_SIZE ); pos += 4;
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;
LittleEndian.putInt( data, pos, field_3_rcBounds_x2 ); pos += 4;
LittleEndian.putInt( data, pos, field_3_rcBounds_y2 ); pos += 4;
LittleEndian.putInt( data, pos, field_4_ptSize_w ); pos += 4;
LittleEndian.putInt( data, pos, field_4_ptSize_h ); pos += 4;
LittleEndian.putInt( data, pos, field_5_cbSave ); pos += 4;
data[pos] = field_6_fCompression; pos++;
data[pos] = field_7_fFilter; pos++;
System.arraycopy( raw_pictureData, 0, data, pos, raw_pictureData.length ); pos += raw_pictureData.length;
if(remainingData != null) {
System.arraycopy( remainingData, 0, data, pos, remainingData.length ); pos += remainingData.length;
}
listener.afterRecordSerialize(offset + getRecordSize(), getRecordId(), getRecordSize(), this);
return getRecordSize();
}
private static byte[] inflatePictureData(byte[] data) {
try {
InflaterInputStream in = new InflaterInputStream(
new ByteArrayInputStream( data ) );
ByteArrayOutputStream out = new ByteArrayOutputStream();
byte[] buf = new byte[4096];
int readBytes;
while ((readBytes = in.read(buf)) > 0) {
out.write(buf, 0, readBytes);
}
return out.toByteArray();
} catch (IOException e) {
log.log(POILogger.WARN, "Possibly corrupt compression or non-compressed data", e);
return data;
}
}
@Override
public int getRecordSize() {
int size = 8 + 50 + raw_pictureData.length;
if(remainingData != null) {
size += remainingData.length;
}
if((getOptions() ^ getSignature()) == 0x10){
size += field_2_UID.length;
}
return size;
}
public byte[] getUID() {
return field_1_UID;
}
public void setUID(byte[] uid) {
if (uid == null || uid.length != 16) {
throw new IllegalArgumentException("uid must be byte[16]");
}
System.arraycopy(uid, 0, field_1_UID, 0, field_1_UID.length);
}
public byte[] getPrimaryUID() {
return field_2_UID;
}
public void setPrimaryUID(byte[] primaryUID) {
if (primaryUID == null || primaryUID.length != 16) {
throw new IllegalArgumentException("primaryUID must be byte[16]");
}
System.arraycopy(primaryUID, 0, field_2_UID, 0, field_2_UID.length);
}
public int getUncompressedSize() {
return field_2_cb;
}
public void setUncompressedSize(int uncompressedSize) {
field_2_cb = uncompressedSize;
}
public Rectangle getBounds() {
return new Rectangle(field_3_rcBounds_x1,
field_3_rcBounds_y1,
field_3_rcBounds_x2 - field_3_rcBounds_x1,
field_3_rcBounds_y2 - field_3_rcBounds_y1);
}
public void setBounds(Rectangle bounds) {
field_3_rcBounds_x1 = bounds.x;
field_3_rcBounds_y1 = bounds.y;
field_3_rcBounds_x2 = bounds.x + bounds.width;
field_3_rcBounds_y2 = bounds.y + bounds.height;
}
public Dimension getSizeEMU() {
return new Dimension(field_4_ptSize_w, field_4_ptSize_h);
}
public void setSizeEMU(Dimension sizeEMU) {
field_4_ptSize_w = sizeEMU.width;
field_4_ptSize_h = sizeEMU.height;
}
public int getCompressedSize() {
return field_5_cbSave;
}
public void setCompressedSize(int compressedSize) {
field_5_cbSave = compressedSize;
}
public boolean isCompressed() {
return (field_6_fCompression == 0);
}
public void setCompressed(boolean compressed) {
field_6_fCompression = compressed ? 0 : (byte)0xFE;
}
public byte getFilter() {
return field_7_fFilter;
}
public void setFilter(byte filter) {
field_7_fFilter = filter;
}
public byte[] getRemainingData() {
return remainingData;
}
public short getSignature() {
switch (getRecordId()) {
case RECORD_ID_EMF: return HSSFPictureData.MSOBI_EMF;
case RECORD_ID_WMF: return HSSFPictureData.MSOBI_WMF;
case RECORD_ID_PICT: return HSSFPictureData.MSOBI_PICT;
}
if (log.check(POILogger.WARN)) {
log.log(POILogger.WARN, "Unknown metafile: " + getRecordId());
}
return 0;
}
@Override
public void setPictureData(byte[] pictureData) {
super.setPictureData(pictureData);
setUncompressedSize(pictureData.length);
try {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
DeflaterOutputStream dos = new DeflaterOutputStream(bos);
dos.write(pictureData);
dos.close();
raw_pictureData = bos.toByteArray();
} catch (IOException e) {
throw new RuntimeException("Can't compress metafile picture data", e);
}
setCompressedSize(raw_pictureData.length);
setCompressed(true);
}
@Override
protected Object[][] getAttributeMap() {
return new Object[][]{
{ "UID", field_1_UID, "UID2", field_2_UID },
{ "Uncompressed Size", field_2_cb },
{ "Bounds", getBounds().toString() },
{ "Size in EMU", getSizeEMU().toString() },
{ "Compressed Size", field_5_cbSave },
{ "Compression", field_6_fCompression },
{ "Filter", field_7_fFilter },
{ "Extra Data", "" },
{ "Remaining Data", remainingData }
};
}
}