Line 0
Link Here
|
|
|
1 |
/* |
2 |
* Licensed to the Apache Software Foundation (ASF) under one or more |
3 |
* contributor license agreements. See the NOTICE file distributed with |
4 |
* this work for additional information regarding copyright ownership. |
5 |
* The ASF licenses this file to You under the Apache License, Version 2.0 |
6 |
* (the "License"); you may not use this file except in compliance with |
7 |
* the License. You may obtain a copy of the License at |
8 |
* |
9 |
* http://www.apache.org/licenses/LICENSE-2.0 |
10 |
* |
11 |
* Unless required by applicable law or agreed to in writing, software |
12 |
* distributed under the License is distributed on an "AS IS" BASIS, |
13 |
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
14 |
* See the License for the specific language governing permissions and |
15 |
* limitations under the License. |
16 |
*/ |
17 |
package org.apache.poi.ddf; |
18 |
|
19 |
import org.apache.poi.util.HexDump; |
20 |
import org.apache.poi.util.LittleEndian; |
21 |
import org.apache.poi.util.POILogFactory; |
22 |
import org.apache.poi.util.POILogger; |
23 |
|
24 |
import java.awt.Dimension; |
25 |
import java.awt.Rectangle; |
26 |
import java.io.ByteArrayInputStream; |
27 |
import java.io.ByteArrayOutputStream; |
28 |
import java.io.IOException; |
29 |
import java.util.zip.InflaterInputStream; |
30 |
|
31 |
/** |
32 |
* @author Daniel Noll |
33 |
* @version $Id$ |
34 |
*/ |
35 |
public class EscherMetafileBlip |
36 |
extends EscherBlipRecord |
37 |
{ |
38 |
private static final POILogger log = POILogFactory.getLogger(EscherMetafileBlip.class); |
39 |
|
40 |
// TODO: What are the other two types? |
41 |
public static final short RECORD_ID_EMF = (short) 0xF018 + 2; |
42 |
public static final short RECORD_ID_WMF = (short) 0xF018 + 2; |
43 |
public static final short RECORD_ID_PICT = (short) 0xF018 + 2; |
44 |
|
45 |
private static final int HEADER_SIZE = 8; |
46 |
|
47 |
private byte[] field_1_UID; |
48 |
private int field_2_cb; |
49 |
private int field_3_rcBounds_x1; |
50 |
private int field_3_rcBounds_y1; |
51 |
private int field_3_rcBounds_x2; |
52 |
private int field_3_rcBounds_y2; |
53 |
private int field_4_ptSize_w; |
54 |
private int field_4_ptSize_h; |
55 |
private int field_5_cbSave; |
56 |
private byte field_6_fCompression; |
57 |
private byte field_7_fFilter; |
58 |
|
59 |
private byte[] raw_pictureData; |
60 |
|
61 |
/** |
62 |
* This method deserializes the record from a byte array. |
63 |
* |
64 |
* @param data The byte array containing the escher record information |
65 |
* @param offset The starting offset into <code>data</code>. |
66 |
* @param recordFactory May be null since this is not a container record. |
67 |
* @return The number of bytes read from the byte array. |
68 |
*/ |
69 |
public int fillFields( byte[] data, int offset, EscherRecordFactory recordFactory ) |
70 |
{ |
71 |
int bytesAfterHeader = readHeader( data, offset ); |
72 |
int pos = offset + HEADER_SIZE; |
73 |
|
74 |
field_1_UID = new byte[16]; |
75 |
System.arraycopy( data, pos, field_1_UID, 0, 16 ); pos += 16; |
76 |
field_2_cb = LittleEndian.getInt( data, pos ); pos += 4; |
77 |
field_3_rcBounds_x1 = LittleEndian.getInt( data, pos ); pos += 4; |
78 |
field_3_rcBounds_y1 = LittleEndian.getInt( data, pos ); pos += 4; |
79 |
field_3_rcBounds_x2 = LittleEndian.getInt( data, pos ); pos += 4; |
80 |
field_3_rcBounds_y2 = LittleEndian.getInt( data, pos ); pos += 4; |
81 |
field_4_ptSize_w = LittleEndian.getInt( data, pos ); pos += 4; |
82 |
field_4_ptSize_h = LittleEndian.getInt( data, pos ); pos += 4; |
83 |
field_5_cbSave = LittleEndian.getInt( data, pos ); pos += 4; |
84 |
field_6_fCompression = data[pos]; pos++; |
85 |
field_7_fFilter = data[pos]; pos++; |
86 |
|
87 |
raw_pictureData = new byte[field_5_cbSave]; |
88 |
System.arraycopy( data, pos, raw_pictureData, 0, field_5_cbSave ); |
89 |
|
90 |
// 0 means DEFLATE compression |
91 |
// 0xFE means no compression |
92 |
if (field_6_fCompression == 0) |
93 |
{ |
94 |
field_pictureData = inflatePictureData(raw_pictureData); |
95 |
} |
96 |
else |
97 |
{ |
98 |
field_pictureData = raw_pictureData; |
99 |
} |
100 |
|
101 |
return bytesAfterHeader + HEADER_SIZE; |
102 |
} |
103 |
|
104 |
/** |
105 |
* Serializes the record to an existing byte array. |
106 |
* |
107 |
* @param offset the offset within the byte array |
108 |
* @param data the data array to serialize to |
109 |
* @param listener a listener for begin and end serialization events. This |
110 |
* is useful because the serialization is |
111 |
* hierarchical/recursive and sometimes you need to be able |
112 |
* break into that. |
113 |
* @return the number of bytes written. |
114 |
*/ |
115 |
public int serialize( int offset, byte[] data, EscherSerializationListener listener ) |
116 |
{ |
117 |
listener.beforeRecordSerialize(offset, getRecordId(), this); |
118 |
|
119 |
int pos = offset; |
120 |
LittleEndian.putShort( data, pos, getOptions() ); pos += 2; |
121 |
LittleEndian.putShort( data, pos, getRecordId() ); pos += 2; |
122 |
LittleEndian.putInt( data, getRecordSize() - HEADER_SIZE ); pos += 4; |
123 |
|
124 |
System.arraycopy( field_1_UID, 0, data, pos, 16 ); pos += 16; |
125 |
LittleEndian.putInt( data, pos, field_2_cb ); pos += 4; |
126 |
LittleEndian.putInt( data, pos, field_3_rcBounds_x1 ); pos += 4; |
127 |
LittleEndian.putInt( data, pos, field_3_rcBounds_y1 ); pos += 4; |
128 |
LittleEndian.putInt( data, pos, field_3_rcBounds_x2 ); pos += 4; |
129 |
LittleEndian.putInt( data, pos, field_3_rcBounds_y2 ); pos += 4; |
130 |
LittleEndian.putInt( data, pos, field_4_ptSize_w ); pos += 4; |
131 |
LittleEndian.putInt( data, pos, field_4_ptSize_h ); pos += 4; |
132 |
LittleEndian.putInt( data, pos, field_5_cbSave ); pos += 4; |
133 |
data[pos] = field_6_fCompression; pos++; |
134 |
data[pos] = field_7_fFilter; pos++; |
135 |
|
136 |
System.arraycopy( raw_pictureData, 0, data, pos, raw_pictureData.length ); |
137 |
|
138 |
listener.afterRecordSerialize(offset + getRecordSize(), getRecordId(), getRecordSize(), this); |
139 |
return HEADER_SIZE + 16 + 1 + raw_pictureData.length; |
140 |
} |
141 |
|
142 |
/** |
143 |
* Decompresses the provided data, returning the inflated result. |
144 |
* |
145 |
* @param data the deflated picture data. |
146 |
* @return the inflated picture data. |
147 |
*/ |
148 |
private static byte[] inflatePictureData(byte[] data) |
149 |
{ |
150 |
try |
151 |
{ |
152 |
InflaterInputStream in = new InflaterInputStream( |
153 |
new ByteArrayInputStream( data ) ); |
154 |
ByteArrayOutputStream out = new ByteArrayOutputStream(); |
155 |
byte[] buf = new byte[4096]; |
156 |
int readBytes; |
157 |
while ((readBytes = in.read(buf)) > 0) |
158 |
{ |
159 |
out.write(buf, 0, readBytes); |
160 |
} |
161 |
return out.toByteArray(); |
162 |
} |
163 |
catch ( IOException e ) |
164 |
{ |
165 |
log.log(POILogger.INFO, "Possibly corrupt compression or non-compressed data", e); |
166 |
return data; |
167 |
} |
168 |
} |
169 |
|
170 |
/** |
171 |
* Returns the number of bytes that are required to serialize this record. |
172 |
* |
173 |
* @return Number of bytes |
174 |
*/ |
175 |
public int getRecordSize() |
176 |
{ |
177 |
return 8 + 50 + raw_pictureData.length; |
178 |
} |
179 |
|
180 |
public byte[] getUID() |
181 |
{ |
182 |
return field_1_UID; |
183 |
} |
184 |
|
185 |
public void setUID( byte[] field_1_UID ) |
186 |
{ |
187 |
this.field_1_UID = field_1_UID; |
188 |
} |
189 |
|
190 |
public int getUncompressedSize() |
191 |
{ |
192 |
return field_2_cb; |
193 |
} |
194 |
|
195 |
public void setUncompressedSize(int uncompressedSize) |
196 |
{ |
197 |
field_2_cb = uncompressedSize; |
198 |
} |
199 |
|
200 |
public Rectangle getBounds() |
201 |
{ |
202 |
return new Rectangle(field_3_rcBounds_x1, |
203 |
field_3_rcBounds_y1, |
204 |
field_3_rcBounds_x2 - field_3_rcBounds_x1, |
205 |
field_3_rcBounds_y2 - field_3_rcBounds_y1); |
206 |
} |
207 |
|
208 |
public void setBounds(Rectangle bounds) |
209 |
{ |
210 |
field_3_rcBounds_x1 = bounds.x; |
211 |
field_3_rcBounds_y1 = bounds.y; |
212 |
field_3_rcBounds_x2 = bounds.x + bounds.width; |
213 |
field_3_rcBounds_y2 = bounds.y + bounds.height; |
214 |
} |
215 |
|
216 |
public Dimension getSizeEMU() |
217 |
{ |
218 |
return new Dimension(field_4_ptSize_w, field_4_ptSize_h); |
219 |
} |
220 |
|
221 |
public void setSizeEMU(Dimension sizeEMU) |
222 |
{ |
223 |
field_4_ptSize_w = sizeEMU.width; |
224 |
field_4_ptSize_h = sizeEMU.height; |
225 |
} |
226 |
|
227 |
public int getCompressedSize() |
228 |
{ |
229 |
return field_5_cbSave; |
230 |
} |
231 |
|
232 |
public void setCompressedSize(int compressedSize) |
233 |
{ |
234 |
field_5_cbSave = compressedSize; |
235 |
} |
236 |
|
237 |
public boolean isCompressed() |
238 |
{ |
239 |
return (field_6_fCompression == 0); |
240 |
} |
241 |
|
242 |
public void setCompressed(boolean compressed) |
243 |
{ |
244 |
field_6_fCompression = compressed ? 0 : (byte)0xFE; |
245 |
} |
246 |
|
247 |
// filtering is always 254 according to available docs, so no point giving it a setter method. |
248 |
|
249 |
public String toString() |
250 |
{ |
251 |
String nl = System.getProperty( "line.separator" ); |
252 |
|
253 |
String extraData; |
254 |
ByteArrayOutputStream b = new ByteArrayOutputStream(); |
255 |
try |
256 |
{ |
257 |
HexDump.dump( this.field_pictureData, 0, b, 0 ); |
258 |
extraData = b.toString(); |
259 |
} |
260 |
catch ( Exception e ) |
261 |
{ |
262 |
extraData = e.toString(); |
263 |
} |
264 |
return getClass().getName() + ":" + nl + |
265 |
" RecordId: 0x" + HexDump.toHex( getRecordId() ) + nl + |
266 |
" Options: 0x" + HexDump.toHex( getOptions() ) + nl + |
267 |
" UID: 0x" + HexDump.toHex( field_1_UID ) + nl + |
268 |
" Uncompressed Size: " + HexDump.toHex( field_2_cb ) + nl + |
269 |
" Bounds: " + getBounds() + nl + |
270 |
" Size in EMU: " + getSizeEMU() + nl + |
271 |
" Compressed Size: " + HexDump.toHex( field_5_cbSave ) + nl + |
272 |
" Compression: " + HexDump.toHex( field_6_fCompression ) + nl + |
273 |
" Filter: " + HexDump.toHex( field_7_fFilter ) + nl + |
274 |
" Extra Data:" + nl + extraData; |
275 |
} |
276 |
|
277 |
} |