001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017: package org.apache.poi.ddf;
018:
019: import org.apache.poi.util.HexDump;
020: import org.apache.poi.util.LittleEndian;
021: import org.apache.poi.util.POILogFactory;
022: import org.apache.poi.util.POILogger;
023:
024: import java.awt.Dimension;
025: import java.awt.Rectangle;
026: import java.io.ByteArrayInputStream;
027: import java.io.ByteArrayOutputStream;
028: import java.io.IOException;
029: import java.util.zip.InflaterInputStream;
030:
031: /**
032: * @author Daniel Noll
033: * @version $Id$
034: */
035: public class EscherPictBlip extends EscherBlipRecord {
036: private static final POILogger log = POILogFactory
037: .getLogger(EscherPictBlip.class);
038:
039: public static final short RECORD_ID_EMF = (short) 0xF018 + 2;
040: public static final short RECORD_ID_WMF = (short) 0xF018 + 3;
041: public static final short RECORD_ID_PICT = (short) 0xF018 + 4;
042:
043: private static final int HEADER_SIZE = 8;
044:
045: private byte[] field_1_UID;
046: private int field_2_cb;
047: private int field_3_rcBounds_x1;
048: private int field_3_rcBounds_y1;
049: private int field_3_rcBounds_x2;
050: private int field_3_rcBounds_y2;
051: private int field_4_ptSize_w;
052: private int field_4_ptSize_h;
053: private int field_5_cbSave;
054: private byte field_6_fCompression;
055: private byte field_7_fFilter;
056:
057: private byte[] raw_pictureData;
058:
059: /**
060: * This method deserializes the record from a byte array.
061: *
062: * @param data The byte array containing the escher record information
063: * @param offset The starting offset into <code>data</code>.
064: * @param recordFactory May be null since this is not a container record.
065: * @return The number of bytes read from the byte array.
066: */
067: public int fillFields(byte[] data, int offset,
068: EscherRecordFactory recordFactory) {
069: int bytesAfterHeader = readHeader(data, offset);
070: int pos = offset + HEADER_SIZE;
071:
072: field_1_UID = new byte[16];
073: System.arraycopy(data, pos, field_1_UID, 0, 16);
074: pos += 16;
075: field_2_cb = LittleEndian.getInt(data, pos);
076: pos += 4;
077: field_3_rcBounds_x1 = LittleEndian.getInt(data, pos);
078: pos += 4;
079: field_3_rcBounds_y1 = LittleEndian.getInt(data, pos);
080: pos += 4;
081: field_3_rcBounds_x2 = LittleEndian.getInt(data, pos);
082: pos += 4;
083: field_3_rcBounds_y2 = LittleEndian.getInt(data, pos);
084: pos += 4;
085: field_4_ptSize_w = LittleEndian.getInt(data, pos);
086: pos += 4;
087: field_4_ptSize_h = LittleEndian.getInt(data, pos);
088: pos += 4;
089: field_5_cbSave = LittleEndian.getInt(data, pos);
090: pos += 4;
091: field_6_fCompression = data[pos];
092: pos++;
093: field_7_fFilter = data[pos];
094: pos++;
095:
096: raw_pictureData = new byte[field_5_cbSave];
097: System.arraycopy(data, pos, raw_pictureData, 0, field_5_cbSave);
098:
099: // 0 means DEFLATE compression
100: // 0xFE means no compression
101: if (field_6_fCompression == 0) {
102: field_pictureData = inflatePictureData(raw_pictureData);
103: } else {
104: field_pictureData = raw_pictureData;
105: }
106:
107: return bytesAfterHeader + HEADER_SIZE;
108: }
109:
110: /**
111: * Serializes the record to an existing byte array.
112: *
113: * @param offset the offset within the byte array
114: * @param data the data array to serialize to
115: * @param listener a listener for begin and end serialization events. This
116: * is useful because the serialization is
117: * hierarchical/recursive and sometimes you need to be able
118: * break into that.
119: * @return the number of bytes written.
120: */
121: public int serialize(int offset, byte[] data,
122: EscherSerializationListener listener) {
123: listener.beforeRecordSerialize(offset, getRecordId(), this );
124:
125: int pos = offset;
126: LittleEndian.putShort(data, pos, getOptions());
127: pos += 2;
128: LittleEndian.putShort(data, pos, getRecordId());
129: pos += 2;
130: LittleEndian.putInt(data, getRecordSize() - HEADER_SIZE);
131: pos += 4;
132:
133: System.arraycopy(field_1_UID, 0, data, pos, 16);
134: pos += 16;
135: LittleEndian.putInt(data, pos, field_2_cb);
136: pos += 4;
137: LittleEndian.putInt(data, pos, field_3_rcBounds_x1);
138: pos += 4;
139: LittleEndian.putInt(data, pos, field_3_rcBounds_y1);
140: pos += 4;
141: LittleEndian.putInt(data, pos, field_3_rcBounds_x2);
142: pos += 4;
143: LittleEndian.putInt(data, pos, field_3_rcBounds_y2);
144: pos += 4;
145: LittleEndian.putInt(data, pos, field_4_ptSize_w);
146: pos += 4;
147: LittleEndian.putInt(data, pos, field_4_ptSize_h);
148: pos += 4;
149: LittleEndian.putInt(data, pos, field_5_cbSave);
150: pos += 4;
151: data[pos] = field_6_fCompression;
152: pos++;
153: data[pos] = field_7_fFilter;
154: pos++;
155:
156: System.arraycopy(raw_pictureData, 0, data, pos,
157: raw_pictureData.length);
158:
159: listener.afterRecordSerialize(offset + getRecordSize(),
160: getRecordId(), getRecordSize(), this );
161: return HEADER_SIZE + 16 + 1 + raw_pictureData.length;
162: }
163:
164: /**
165: * Decompresses the provided data, returning the inflated result.
166: *
167: * @param data the deflated picture data.
168: * @return the inflated picture data.
169: */
170: private static byte[] inflatePictureData(byte[] data) {
171: try {
172: InflaterInputStream in = new InflaterInputStream(
173: new ByteArrayInputStream(data));
174: ByteArrayOutputStream out = new ByteArrayOutputStream();
175: byte[] buf = new byte[4096];
176: int readBytes;
177: while ((readBytes = in.read(buf)) > 0) {
178: out.write(buf, 0, readBytes);
179: }
180: return out.toByteArray();
181: } catch (IOException e) {
182: log
183: .log(
184: POILogger.INFO,
185: "Possibly corrupt compression or non-compressed data",
186: e);
187: return data;
188: }
189: }
190:
191: /**
192: * Returns the number of bytes that are required to serialize this record.
193: *
194: * @return Number of bytes
195: */
196: public int getRecordSize() {
197: return 8 + 50 + raw_pictureData.length;
198: }
199:
200: public byte[] getUID() {
201: return field_1_UID;
202: }
203:
204: public void setUID(byte[] field_1_UID) {
205: this .field_1_UID = field_1_UID;
206: }
207:
208: public int getUncompressedSize() {
209: return field_2_cb;
210: }
211:
212: public void setUncompressedSize(int uncompressedSize) {
213: field_2_cb = uncompressedSize;
214: }
215:
216: public Rectangle getBounds() {
217: return new Rectangle(field_3_rcBounds_x1, field_3_rcBounds_y1,
218: field_3_rcBounds_x2 - field_3_rcBounds_x1,
219: field_3_rcBounds_y2 - field_3_rcBounds_y1);
220: }
221:
222: public void setBounds(Rectangle bounds) {
223: field_3_rcBounds_x1 = bounds.x;
224: field_3_rcBounds_y1 = bounds.y;
225: field_3_rcBounds_x2 = bounds.x + bounds.width;
226: field_3_rcBounds_y2 = bounds.y + bounds.height;
227: }
228:
229: public Dimension getSizeEMU() {
230: return new Dimension(field_4_ptSize_w, field_4_ptSize_h);
231: }
232:
233: public void setSizeEMU(Dimension sizeEMU) {
234: field_4_ptSize_w = sizeEMU.width;
235: field_4_ptSize_h = sizeEMU.height;
236: }
237:
238: public int getCompressedSize() {
239: return field_5_cbSave;
240: }
241:
242: public void setCompressedSize(int compressedSize) {
243: field_5_cbSave = compressedSize;
244: }
245:
246: public boolean isCompressed() {
247: return (field_6_fCompression == 0);
248: }
249:
250: public void setCompressed(boolean compressed) {
251: field_6_fCompression = compressed ? 0 : (byte) 0xFE;
252: }
253:
254: // filtering is always 254 according to available docs, so no point giving it a setter method.
255:
256: public String toString() {
257: String nl = System.getProperty("line.separator");
258:
259: String extraData;
260: ByteArrayOutputStream b = new ByteArrayOutputStream();
261: try {
262: HexDump.dump(this .field_pictureData, 0, b, 0);
263: extraData = b.toString();
264: } catch (Exception e) {
265: extraData = e.toString();
266: }
267: return getClass().getName() + ":" + nl + " RecordId: 0x"
268: + HexDump.toHex(getRecordId()) + nl + " Options: 0x"
269: + HexDump.toHex(getOptions()) + nl + " UID: 0x"
270: + HexDump.toHex(field_1_UID) + nl
271: + " Uncompressed Size: " + HexDump.toHex(field_2_cb)
272: + nl + " Bounds: " + getBounds() + nl
273: + " Size in EMU: " + getSizeEMU() + nl
274: + " Compressed Size: " + HexDump.toHex(field_5_cbSave)
275: + nl + " Compression: "
276: + HexDump.toHex(field_6_fCompression) + nl
277: + " Filter: " + HexDump.toHex(field_7_fFilter) + nl
278: + " Extra Data:" + nl + extraData;
279: }
280:
281: }
|