| /* ==================================================================== |
| 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 java.awt.Dimension; |
| import java.awt.Rectangle; |
| import java.io.ByteArrayInputStream; |
| import java.io.ByteArrayOutputStream; |
| import java.io.IOException; |
| import java.util.zip.InflaterInputStream; |
| |
| 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 EscherPictBlip extends EscherBlipRecord { |
| private static final POILogger log = POILogFactory.getLogger(EscherPictBlip.class); |
| //arbitrarily selected; may need to increase |
| private static final int MAX_RECORD_LENGTH = 100_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 HEADER_SIZE = 8; |
| |
| private final byte[] field_1_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; |
| |
| @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; |
| 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 ); |
| |
| // 0 means DEFLATE compression |
| // 0xFE means no compression |
| if (field_6_fCompression == 0) |
| { |
| super.setPictureData(inflatePictureData(raw_pictureData)); |
| } |
| else |
| { |
| super.setPictureData(raw_pictureData); |
| } |
| |
| 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, 0, getRecordSize() - HEADER_SIZE ); pos += 4; |
| |
| System.arraycopy( field_1_UID, 0, data, pos, 16 ); pos += 16; |
| 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 ); |
| |
| listener.afterRecordSerialize(offset + getRecordSize(), getRecordId(), getRecordSize(), this); |
| return HEADER_SIZE + 16 + 1 + raw_pictureData.length; |
| } |
| |
| /** |
| * Decompresses the provided data, returning the inflated result. |
| * |
| * @param data the deflated picture data. |
| * @return the inflated picture data. |
| */ |
| 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.INFO, "Possibly corrupt compression or non-compressed data", e); |
| return data; |
| } |
| } |
| |
| @Override |
| public int getRecordSize() { |
| return 8 + 50 + raw_pictureData.length; |
| } |
| |
| /** |
| * Gets the first MD4, that specifies the unique identifier of the |
| * uncompressed blip data |
| * |
| * @return the first MD4 |
| */ |
| public byte[] getUID() { |
| return field_1_UID; |
| } |
| |
| /** |
| * Sets the first MD4, that specifies the unique identifier of the |
| * uncompressed blip data |
| * |
| * @param uid the first MD4 |
| */ |
| 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); |
| } |
| |
| /** |
| * Gets the uncompressed size (in bytes) |
| * |
| * @return the uncompressed size |
| */ |
| public int getUncompressedSize() { |
| return field_2_cb; |
| } |
| |
| /** |
| * Sets the uncompressed size (in bytes) |
| * |
| * @param uncompressedSize the uncompressed size |
| */ |
| public void setUncompressedSize(int uncompressedSize) { |
| field_2_cb = uncompressedSize; |
| } |
| |
| /** |
| * Get the clipping region of the pict file |
| * |
| * @return the clipping region |
| */ |
| 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); |
| } |
| |
| /** |
| * Sets the clipping region |
| * |
| * @param bounds the clipping region |
| */ |
| 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; |
| } |
| |
| /** |
| * Gets the dimensions of the metafile |
| * |
| * @return the dimensions of the metafile |
| */ |
| public Dimension getSizeEMU() { |
| return new Dimension(field_4_ptSize_w, field_4_ptSize_h); |
| } |
| |
| /** |
| * Gets the dimensions of the metafile |
| * |
| * @param sizeEMU the dimensions of the metafile |
| */ |
| public void setSizeEMU(Dimension sizeEMU) { |
| field_4_ptSize_w = sizeEMU.width; |
| field_4_ptSize_h = sizeEMU.height; |
| } |
| |
| /** |
| * Gets the compressed size of the metafile (in bytes) |
| * |
| * @return the compressed size |
| */ |
| public int getCompressedSize() { |
| return field_5_cbSave; |
| } |
| |
| /** |
| * Sets the compressed size of the metafile (in bytes) |
| * |
| * @param compressedSize the compressed size |
| */ |
| public void setCompressedSize(int compressedSize) { |
| field_5_cbSave = compressedSize; |
| } |
| |
| /** |
| * Gets the compression of the metafile |
| * |
| * @return true, if the metafile is compressed |
| */ |
| public boolean isCompressed() { |
| return (field_6_fCompression == 0); |
| } |
| |
| /** |
| * Sets the compression of the metafile |
| * |
| * @param compressed the compression state, true if it's compressed |
| */ |
| public void setCompressed(boolean compressed) { |
| field_6_fCompression = compressed ? 0 : (byte)0xFE; |
| } |
| |
| /** |
| * Gets the filter byte - this is usually 0xFE |
| * |
| * @return the filter byte |
| */ |
| public byte getFilter() { |
| return field_7_fFilter; |
| } |
| |
| /** |
| * Sets the filter byte - this is usually 0xFE |
| * |
| * @param filter the filter byte |
| */ |
| public void setFilter(byte filter) { |
| field_7_fFilter = filter; |
| } |
| |
| @Override |
| protected Object[][] getAttributeMap() { |
| return new Object[][]{ |
| { "UID", field_1_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", getPicturedata() }, |
| }; |
| } |
| } |