- /* ====================================================================
- 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.Collections;
- import java.util.LinkedHashMap;
- import java.util.Map;
- import java.util.function.Supplier;
- import java.util.zip.DeflaterOutputStream;
- import java.util.zip.InflaterInputStream;
-
- import org.apache.logging.log4j.LogManager;
- import org.apache.logging.log4j.Logger;
- import org.apache.poi.hssf.usermodel.HSSFPictureData;
- import org.apache.poi.util.IOUtils;
- import org.apache.poi.util.LittleEndian;
-
- import static org.apache.logging.log4j.util.Unbox.box;
-
- public final class EscherMetafileBlip extends EscherBlipRecord {
- private static final Logger LOGGER = LogManager.getLogger(EscherMetafileBlip.class);
- //arbitrarily selected; may need to increase
- private static final int MAX_RECORD_LENGTH = 100_000_000;
-
- public static final short RECORD_ID_EMF = EscherRecordTypes.BLIP_EMF.typeID;
- public static final short RECORD_ID_WMF = EscherRecordTypes.BLIP_WMF.typeID;
- public static final short RECORD_ID_PICT = EscherRecordTypes.BLIP_PICT.typeID;
-
- private static final int HEADER_SIZE = 8;
-
- private final byte[] field_1_UID = new byte[16];
- /**
- * The primary UID is only saved to disk if (blip_instance ^ blip_signature == 1)
- */
- 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;
-
- public EscherMetafileBlip() {}
-
- public EscherMetafileBlip(EscherMetafileBlip other) {
- super(other);
- System.arraycopy(other.field_1_UID, 0, field_1_UID, 0, field_1_UID.length);
- System.arraycopy(other.field_2_UID, 0, field_2_UID, 0, field_2_UID.length);
- field_2_cb = other.field_2_cb;
- field_3_rcBounds_x1 = other.field_3_rcBounds_x1;
- field_3_rcBounds_y1 = other.field_3_rcBounds_y1;
- field_3_rcBounds_x2 = other.field_3_rcBounds_x2;
- field_3_rcBounds_y2 = other.field_3_rcBounds_y2;
- field_4_ptSize_h = other.field_4_ptSize_h;
- field_4_ptSize_w = other.field_4_ptSize_w;
- field_5_cbSave = other.field_5_cbSave;
- field_6_fCompression = other.field_6_fCompression;
- field_7_fFilter = other.field_7_fFilter;
- raw_pictureData = (other.raw_pictureData == null) ? null : other.raw_pictureData.clone();
- remainingData = (other.remainingData == null) ? null : other.remainingData.clone();
- }
-
- @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.safelyClone(data, pos, field_5_cbSave, MAX_RECORD_LENGTH);
- pos += 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);
- }
-
- int remaining = bytesAfterHeader - pos + offset + HEADER_SIZE;
- if(remaining > 0) {
- remainingData = IOUtils.safelyClone(data, pos, remaining, MAX_RECORD_LENGTH);
- }
- 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();
- }
-
- /**
- * 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) {
- LOGGER.atWarn().withThrowable(e).log("Possibly corrupt compression or non-compressed data");
- 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;
- }
-
- /**
- * 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 second MD4, that specifies the unique identifier of the
- * uncompressed blip data
- *
- * @return the second MD4
- */
- public byte[] getPrimaryUID() {
- return field_2_UID;
- }
-
- /**
- * Sets the second MD4, that specifies the unique identifier of the
- * uncompressed blip data
- *
- * @param primaryUID the second MD4
- */
- 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);
- }
-
- /**
- * 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 metafile
- *
- * @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;
- }
-
-
-
- /**
- * Returns any remaining bytes
- *
- * @return any remaining bytes
- */
- public byte[] getRemainingData() {
- return remainingData;
- }
-
- /**
- * Return the blip signature
- *
- * @return the blip signature
- */
- public short getSignature() {
- switch (EscherRecordTypes.forTypeID(getRecordId())) {
- case BLIP_EMF: return HSSFPictureData.MSOBI_EMF;
- case BLIP_WMF: return HSSFPictureData.MSOBI_WMF;
- case BLIP_PICT: return HSSFPictureData.MSOBI_PICT;
- }
- LOGGER.atWarn().log("Unknown metafile: {}", box(getRecordId()));
- return 0;
- }
-
- @Override
- public void setPictureData(byte[] pictureData) {
- super.setPictureData(pictureData);
- setUncompressedSize(pictureData.length);
-
- // info of chicago project:
- // "... LZ compression algorithm in the format used by GNU Zip deflate/inflate with a 32k window ..."
- // not sure what to do, when lookup tables exceed 32k ...
-
- 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
- public Map<String, Supplier<?>> getGenericProperties() {
- final Map<String, Supplier<?>> m = new LinkedHashMap<>(super.getGenericProperties());
- m.put("uid", this::getUID);
- m.put("uncompressedSize", this::getUncompressedSize);
- m.put("bounds", this::getBounds);
- m.put("sizeInEMU", this::getSizeEMU);
- m.put("compressedSize", this::getCompressedSize);
- m.put("isCompressed", this::isCompressed);
- m.put("filter", this::getFilter);
- return Collections.unmodifiableMap(m);
- }
-
- @Override
- public EscherMetafileBlip copy() {
- return new EscherMetafileBlip(this);
- }
- }
|