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:
018: package org.apache.poi.ddf;
019:
020: import org.apache.poi.hssf.record.RecordFormatException;
021: import org.apache.poi.util.HexDump;
022: import org.apache.poi.util.LittleEndian;
023:
024: import java.io.ByteArrayInputStream;
025: import java.io.ByteArrayOutputStream;
026: import java.io.InputStream;
027: import java.io.IOException;
028: import java.util.zip.InflaterInputStream;
029: import java.util.zip.DeflaterOutputStream;
030:
031: /**
032: * The blip record is used to hold details about large binary objects that occur in escher such
033: * as JPEG, GIF, PICT and WMF files. The contents of the stream is usually compressed. Inflate
034: * can be used to decompress the data.
035: *
036: * @author Glen Stampoultzis
037: * @see java.util.zip.Inflater
038: */
039: public class EscherBlipWMFRecord extends EscherBlipRecord {
040: // public static final short RECORD_ID_START = (short) 0xF018;
041: // public static final short RECORD_ID_END = (short) 0xF117;
042: public static final String RECORD_DESCRIPTION = "msofbtBlip";
043: private static final int HEADER_SIZE = 8;
044:
045: private byte[] field_1_secondaryUID;
046: private int field_2_cacheOfSize;
047: private int field_3_boundaryTop;
048: private int field_4_boundaryLeft;
049: private int field_5_boundaryWidth;
050: private int field_6_boundaryHeight;
051: private int field_7_width;
052: private int field_8_height;
053: private int field_9_cacheOfSavedSize;
054: private byte field_10_compressionFlag;
055: private byte field_11_filter;
056: private byte[] field_12_data;
057:
058: /**
059: * This method deserializes the record from a byte array.
060: *
061: * @param data The byte array containing the escher record information
062: * @param offset The starting offset into <code>data</code>.
063: * @param recordFactory May be null since this is not a container record.
064: * @return The number of bytes read from the byte array.
065: */
066: public int fillFields(byte[] data, int offset,
067: EscherRecordFactory recordFactory) {
068: int bytesAfterHeader = readHeader(data, offset);
069: int pos = offset + HEADER_SIZE;
070:
071: int size = 0;
072: field_1_secondaryUID = new byte[16];
073: System.arraycopy(data, pos + size, field_1_secondaryUID, 0, 16);
074: size += 16;
075: field_2_cacheOfSize = LittleEndian.getInt(data, pos + size);
076: size += 4;
077: field_3_boundaryTop = LittleEndian.getInt(data, pos + size);
078: size += 4;
079: field_4_boundaryLeft = LittleEndian.getInt(data, pos + size);
080: size += 4;
081: field_5_boundaryWidth = LittleEndian.getInt(data, pos + size);
082: size += 4;
083: field_6_boundaryHeight = LittleEndian.getInt(data, pos + size);
084: size += 4;
085: field_7_width = LittleEndian.getInt(data, pos + size);
086: size += 4;
087: field_8_height = LittleEndian.getInt(data, pos + size);
088: size += 4;
089: field_9_cacheOfSavedSize = LittleEndian
090: .getInt(data, pos + size);
091: size += 4;
092: field_10_compressionFlag = data[pos + size];
093: size++;
094: field_11_filter = data[pos + size];
095: size++;
096:
097: int bytesRemaining = bytesAfterHeader - size;
098: field_12_data = new byte[bytesRemaining];
099: System.arraycopy(data, pos + size, field_12_data, 0,
100: bytesRemaining);
101: size += bytesRemaining;
102:
103: return HEADER_SIZE + size;
104: }
105:
106: /**
107: * This method serializes this escher record into a byte array.
108: *
109: * @param offset The offset into <code>data</code> to start writing the record data to.
110: * @param data The byte array to serialize to.
111: * @param listener A listener to retrieve start and end callbacks. Use a <code>NullEscherSerailizationListener</code> to ignore these events.
112: * @return The number of bytes written.
113: *
114: * @see NullEscherSerializationListener
115: */
116: public int serialize(int offset, byte[] data,
117: EscherSerializationListener listener) {
118: listener.beforeRecordSerialize(offset, getRecordId(), this );
119:
120: LittleEndian.putShort(data, offset, getOptions());
121: LittleEndian.putShort(data, offset + 2, getRecordId());
122: int remainingBytes = field_12_data.length + 36;
123: LittleEndian.putInt(data, offset + 4, remainingBytes);
124:
125: int pos = offset + HEADER_SIZE;
126: System.arraycopy(field_1_secondaryUID, 0, data, pos, 16);
127: pos += 16;
128: LittleEndian.putInt(data, pos, field_2_cacheOfSize);
129: pos += 4;
130: LittleEndian.putInt(data, pos, field_3_boundaryTop);
131: pos += 4;
132: LittleEndian.putInt(data, pos, field_4_boundaryLeft);
133: pos += 4;
134: LittleEndian.putInt(data, pos, field_5_boundaryWidth);
135: pos += 4;
136: LittleEndian.putInt(data, pos, field_6_boundaryHeight);
137: pos += 4;
138: LittleEndian.putInt(data, pos, field_7_width);
139: pos += 4;
140: LittleEndian.putInt(data, pos, field_8_height);
141: pos += 4;
142: LittleEndian.putInt(data, pos, field_9_cacheOfSavedSize);
143: pos += 4;
144: data[pos++] = field_10_compressionFlag;
145: data[pos++] = field_11_filter;
146: System.arraycopy(field_12_data, 0, data, pos,
147: field_12_data.length);
148: pos += field_12_data.length;
149:
150: listener.afterRecordSerialize(pos, getRecordId(), pos - offset,
151: this );
152: return pos - offset;
153: }
154:
155: /**
156: * Returns the number of bytes that are required to serialize this record.
157: *
158: * @return Number of bytes
159: */
160: public int getRecordSize() {
161: return 58 + field_12_data.length;
162: }
163:
164: /**
165: * The short name for this record
166: */
167: public String getRecordName() {
168: return "Blip";
169: }
170:
171: /**
172: * Retrieve the secondary UID
173: */
174: public byte[] getSecondaryUID() {
175: return field_1_secondaryUID;
176: }
177:
178: /**
179: * Set the secondary UID
180: */
181: public void setSecondaryUID(byte[] field_1_secondaryUID) {
182: this .field_1_secondaryUID = field_1_secondaryUID;
183: }
184:
185: /**
186: * Retrieve the cache of the metafile size
187: */
188: public int getCacheOfSize() {
189: return field_2_cacheOfSize;
190: }
191:
192: /**
193: * Set the cache of the metafile size
194: */
195: public void setCacheOfSize(int field_2_cacheOfSize) {
196: this .field_2_cacheOfSize = field_2_cacheOfSize;
197: }
198:
199: /**
200: * Retrieve the top boundary of the metafile drawing commands
201: */
202: public int getBoundaryTop() {
203: return field_3_boundaryTop;
204: }
205:
206: /**
207: * Set the top boundary of the metafile drawing commands
208: */
209: public void setBoundaryTop(int field_3_boundaryTop) {
210: this .field_3_boundaryTop = field_3_boundaryTop;
211: }
212:
213: /**
214: * Retrieve the left boundary of the metafile drawing commands
215: */
216: public int getBoundaryLeft() {
217: return field_4_boundaryLeft;
218: }
219:
220: /**
221: * Set the left boundary of the metafile drawing commands
222: */
223: public void setBoundaryLeft(int field_4_boundaryLeft) {
224: this .field_4_boundaryLeft = field_4_boundaryLeft;
225: }
226:
227: /**
228: * Retrieve the boundary width of the metafile drawing commands
229: */
230: public int getBoundaryWidth() {
231: return field_5_boundaryWidth;
232: }
233:
234: /**
235: * Set the boundary width of the metafile drawing commands
236: */
237: public void setBoundaryWidth(int field_5_boundaryWidth) {
238: this .field_5_boundaryWidth = field_5_boundaryWidth;
239: }
240:
241: /**
242: * Retrieve the boundary height of the metafile drawing commands
243: */
244: public int getBoundaryHeight() {
245: return field_6_boundaryHeight;
246: }
247:
248: /**
249: * Set the boundary height of the metafile drawing commands
250: */
251: public void setBoundaryHeight(int field_6_boundaryHeight) {
252: this .field_6_boundaryHeight = field_6_boundaryHeight;
253: }
254:
255: /**
256: * Retrieve the width of the metafile in EMU's (English Metric Units).
257: */
258: public int getWidth() {
259: return field_7_width;
260: }
261:
262: /**
263: * Set the width of the metafile in EMU's (English Metric Units).
264: */
265: public void setWidth(int width) {
266: this .field_7_width = width;
267: }
268:
269: /**
270: * Retrieve the height of the metafile in EMU's (English Metric Units).
271: */
272: public int getHeight() {
273: return field_8_height;
274: }
275:
276: /**
277: * Set the height of the metafile in EMU's (English Metric Units).
278: */
279: public void setHeight(int height) {
280: this .field_8_height = height;
281: }
282:
283: /**
284: * Retrieve the cache of the saved size
285: */
286: public int getCacheOfSavedSize() {
287: return field_9_cacheOfSavedSize;
288: }
289:
290: /**
291: * Set the cache of the saved size
292: */
293: public void setCacheOfSavedSize(int field_9_cacheOfSavedSize) {
294: this .field_9_cacheOfSavedSize = field_9_cacheOfSavedSize;
295: }
296:
297: /**
298: * Is the contents of the blip compressed?
299: */
300: public byte getCompressionFlag() {
301: return field_10_compressionFlag;
302: }
303:
304: /**
305: * Set whether the contents of the blip is compressed
306: */
307: public void setCompressionFlag(byte field_10_compressionFlag) {
308: this .field_10_compressionFlag = field_10_compressionFlag;
309: }
310:
311: /**
312: * Filter should always be 0
313: */
314: public byte getFilter() {
315: return field_11_filter;
316: }
317:
318: /**
319: * Filter should always be 0
320: */
321: public void setFilter(byte field_11_filter) {
322: this .field_11_filter = field_11_filter;
323: }
324:
325: /**
326: * The BLIP data
327: */
328: public byte[] getData() {
329: return field_12_data;
330: }
331:
332: /**
333: * The BLIP data
334: */
335: public void setData(byte[] field_12_data) {
336: this .field_12_data = field_12_data;
337: }
338:
339: /**
340: * The string representation of this record.
341: *
342: * @return A string
343: */
344: public String toString() {
345: String nl = System.getProperty("line.separator");
346:
347: String extraData;
348: ByteArrayOutputStream b = new ByteArrayOutputStream();
349: try {
350: HexDump.dump(this .field_12_data, 0, b, 0);
351: extraData = b.toString();
352: } catch (Exception e) {
353: extraData = e.toString();
354: }
355: return getClass().getName() + ":" + nl + " RecordId: 0x"
356: + HexDump.toHex(getRecordId()) + nl + " Options: 0x"
357: + HexDump.toHex(getOptions()) + nl
358: + " Secondary UID: "
359: + HexDump.toHex(field_1_secondaryUID) + nl
360: + " CacheOfSize: " + field_2_cacheOfSize + nl
361: + " BoundaryTop: " + field_3_boundaryTop + nl
362: + " BoundaryLeft: " + field_4_boundaryLeft + nl
363: + " BoundaryWidth: " + field_5_boundaryWidth + nl
364: + " BoundaryHeight: " + field_6_boundaryHeight + nl
365: + " X: " + field_7_width + nl + " Y: "
366: + field_8_height + nl + " CacheOfSavedSize: "
367: + field_9_cacheOfSavedSize + nl + " CompressionFlag: "
368: + field_10_compressionFlag + nl + " Filter: "
369: + field_11_filter + nl + " Data:" + nl + extraData;
370: }
371:
372: /**
373: * Compress the contents of the provided array
374: *
375: * @param data An uncompressed byte array
376: * @see DeflaterOutputStream#write(int b)
377: */
378: public static byte[] compress(byte[] data) {
379: ByteArrayOutputStream out = new ByteArrayOutputStream();
380: DeflaterOutputStream deflaterOutputStream = new DeflaterOutputStream(
381: out);
382: try {
383: for (int i = 0; i < data.length; i++)
384: deflaterOutputStream.write(data[i]);
385: } catch (IOException e) {
386: throw new RecordFormatException(e.toString());
387: }
388:
389: return out.toByteArray();
390: }
391:
392: /**
393: * Decompresses a byte array.
394: *
395: * @param data The compressed byte array
396: * @param pos The starting position into the byte array
397: * @param length The number of compressed bytes to decompress
398: * @return An uncompressed byte array
399: * @see InflaterInputStream#read
400: */
401: public static byte[] decompress(byte[] data, int pos, int length) {
402: byte[] compressedData = new byte[length];
403: System.arraycopy(data, pos + 50, compressedData, 0, length);
404: InputStream compressedInputStream = new ByteArrayInputStream(
405: compressedData);
406: InflaterInputStream inflaterInputStream = new InflaterInputStream(
407: compressedInputStream);
408: ByteArrayOutputStream out = new ByteArrayOutputStream();
409: int c;
410: try {
411: while ((c = inflaterInputStream.read()) != -1)
412: out.write(c);
413: } catch (IOException e) {
414: throw new RecordFormatException(e.toString());
415: }
416: return out.toByteArray();
417: }
418:
419: }
|