123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487 |
- /* ====================================================================
- 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.hslf.usermodel;
-
- import java.awt.Dimension;
- import java.io.IOException;
- import java.io.OutputStream;
- import java.security.MessageDigest;
- import java.util.Arrays;
- import java.util.Collections;
- import java.util.Comparator;
- import java.util.LinkedHashMap;
- import java.util.List;
- import java.util.Map;
- import java.util.Objects;
- import java.util.function.Supplier;
-
- import org.apache.logging.log4j.LogManager;
- import org.apache.logging.log4j.Logger;
- import org.apache.poi.common.usermodel.GenericRecord;
- import org.apache.poi.ddf.EscherBSERecord;
- import org.apache.poi.ddf.EscherContainerRecord;
- import org.apache.poi.ddf.EscherRecordTypes;
- import org.apache.poi.hslf.blip.DIB;
- import org.apache.poi.hslf.blip.EMF;
- import org.apache.poi.hslf.blip.JPEG;
- import org.apache.poi.hslf.blip.PICT;
- import org.apache.poi.hslf.blip.PNG;
- import org.apache.poi.hslf.blip.WMF;
- import org.apache.poi.poifs.crypt.CryptoFunctions;
- import org.apache.poi.poifs.crypt.HashAlgorithm;
- import org.apache.poi.sl.usermodel.PictureData;
- import org.apache.poi.util.Internal;
- import org.apache.poi.util.LittleEndian;
- import org.apache.poi.util.LittleEndianConsts;
- import org.apache.poi.util.Removal;
- import org.apache.poi.util.Units;
-
- /**
- * A class that represents image data contained in a slide show.
- */
- public abstract class HSLFPictureData implements PictureData, GenericRecord {
-
- private static final Logger LOGGER = LogManager.getLogger(HSLFPictureData.class);
-
- /**
- * Size of the image checksum calculated using MD5 algorithm.
- */
- protected static final int CHECKSUM_SIZE = 16;
-
- /**
- * Size of the image preamble in bytes.
- * <p>
- * The preamble describes how the image should be decoded. All image types have the same preamble format. The
- * preamble has little endian encoding. Below is a diagram of the preamble contents.
- *
- * <pre>
- * 0 1 2 3
- * 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7
- * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
- * | Signature | Picture Type |
- * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
- * | Formatted Length |
- * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
- * </pre>
- */
- static final int PREAMBLE_SIZE = 8;
-
- /**
- * Binary data of the picture, formatted as it will be stored in the {@link HSLFSlideShow}.
- * <p>
- * This does not include the {@link #PREAMBLE_SIZE preamble}.
- */
- private byte[] formattedData;
-
- /**
- * The instance type/signatures defines if one or two UID instances will be included
- */
- private int uidInstanceCount = 1;
-
- /**
- * The 1-based index within the pictures stream
- */
- private int index = -1;
-
- /**
- * {@link EscherRecordTypes#BSTORE_CONTAINER BStore} record tracking all pictures. Should be attached to the
- * slideshow that this picture is linked to.
- */
- final EscherContainerRecord bStore;
-
- /**
- * Record referencing this picture. Should be attached to the slideshow that this picture is linked to.
- */
- final EscherBSERecord bse;
-
- /**
- * @deprecated Use {@link HSLFSlideShow#addPicture(byte[], PictureType)} or one of it's overloads to create new
- * {@link HSLFPictureData}. This API led to detached {@link HSLFPictureData} instances (See Bugzilla
- * 46122) and prevented adding additional functionality.
- */
- @Deprecated
- @Removal(version = "5.3")
- public HSLFPictureData() {
- this(new EscherContainerRecord(), new EscherBSERecord());
- LOGGER.atWarn().log("The no-arg constructor is deprecated. Some functionality such as updating pictures won't " +
- "work.");
- }
-
- /**
- * Creates a new instance.
- *
- * @param bStore {@link EscherRecordTypes#BSTORE_CONTAINER BStore} record tracking all pictures. Should be attached
- * to the slideshow that this picture is linked to.
- * @param bse Record referencing this picture. Should be attached to the slideshow that this picture is linked to.
- */
- @Internal
- protected HSLFPictureData(EscherContainerRecord bStore, EscherBSERecord bse) {
- this.bStore = Objects.requireNonNull(bStore);
- this.bse = Objects.requireNonNull(bse);
- }
-
- /**
- * Blip signature.
- */
- protected abstract int getSignature();
-
- public abstract void setSignature(int signature);
-
- /**
- * The instance type/signatures defines if one or two UID instances will be included
- */
- protected int getUIDInstanceCount() {
- return uidInstanceCount;
- }
-
- /**
- * The instance type/signatures defines if one or two UID instances will be included
- *
- * @param uidInstanceCount the number of uid sequences
- */
- protected void setUIDInstanceCount(int uidInstanceCount) {
- this.uidInstanceCount = uidInstanceCount;
- }
-
- /**
- * Returns the formatted, binary data of this picture excluding the {@link #PREAMBLE_SIZE preamble} bytes.
- * <p>
- * Primarily intended for internal POI use. Use {@link #getData()} to retrieve the picture represented by this
- * object.
- *
- * @return Picture data formatted for the HSLF format.
- * @see #getData()
- * @see #formatImageForSlideshow(byte[])
- */
- public byte[] getRawData(){
- return formattedData;
- }
-
- /**
- * Sets the formatted data for this picture.
- * <p>
- * Primarily intended for internal POI use. Use {@link #setData(byte[])} to change the picture represented by this
- * object.
- *
- * @param data Picture data formatted for the HSLF format. Excludes the {@link #PREAMBLE_SIZE preamble}.
- * @see #setData(byte[])
- * @see #formatImageForSlideshow(byte[])
- * @deprecated Set image data using {@link #setData(byte[])}.
- */
- @Deprecated
- @Removal(version = "5.3")
- public void setRawData(byte[] data){
- formattedData = (data == null) ? null : data.clone();
- }
-
- /**
- * File offset in the 'Pictures' stream
- *
- * @return offset in the 'Pictures' stream
- */
- public int getOffset(){
- return bse.getOffset();
- }
-
- /**
- * Set offset of this picture in the 'Pictures' stream.
- * We need to set it when a new picture is created.
- *
- * @param offset in the 'Pictures' stream
- * @deprecated This function was only intended for POI internal use. If you have a use case you're concerned about,
- * please open an issue in the POI issue tracker.
- */
- @Deprecated
- @Removal(version = "5.3")
- public void setOffset(int offset){
- LOGGER.atWarn().log("HSLFPictureData#setOffset is deprecated.");
- }
-
- /**
- * Returns 16-byte checksum of this picture
- */
- public byte[] getUID(){
- return Arrays.copyOf(formattedData, CHECKSUM_SIZE);
- }
-
- @Override
- public byte[] getChecksum() {
- return getUID();
- }
-
- /**
- * Compute 16-byte checksum of this picture using MD5 algorithm.
- */
- public static byte[] getChecksum(byte[] data) {
- MessageDigest md5 = CryptoFunctions.getMessageDigest(HashAlgorithm.md5);
- md5.update(data);
- return md5.digest();
- }
-
- /**
- * Write this picture into <code>OutputStream</code>
- */
- public void write(OutputStream out) throws IOException {
- byte[] data;
-
- data = new byte[LittleEndianConsts.SHORT_SIZE];
- LittleEndian.putUShort(data, 0, getSignature());
- out.write(data);
-
- data = new byte[LittleEndianConsts.SHORT_SIZE];
- PictureType pt = getType();
- LittleEndian.putUShort(data, 0, pt.nativeId + EscherRecordTypes.BLIP_START.typeID);
- out.write(data);
-
- byte[] rd = getRawData();
-
- data = new byte[LittleEndianConsts.INT_SIZE];
- LittleEndian.putInt(data, 0, rd.length);
- out.write(data);
-
- out.write(rd);
- }
-
- /**
- * Create an instance of {@link HSLFPictureData} by type.
- *
- * @param type type of picture.
- * @return concrete instance of {@link HSLFPictureData}.
- * @deprecated Use {@link HSLFSlideShow#addPicture(byte[], PictureType)} or one of it's overloads to create new
- * {@link HSLFPictureData}. This API led to detached {@link HSLFPictureData} instances (See Bugzilla
- * 46122) and prevented adding additional functionality.
- */
- @Deprecated
- @Removal(version = "5.3")
- public static HSLFPictureData create(PictureType type){
- LOGGER.atWarn().log("HSLFPictureData#create(PictureType) is deprecated. Some functionality such " +
- "as updating pictures won't work.");
-
- // This record code is a stub. It exists only for API compatibility.
- EscherContainerRecord record = new EscherContainerRecord();
- EscherBSERecord bse = new EscherBSERecord();
- return new HSLFSlideShowImpl.PictureFactory(record, type, new byte[0], 0, 0)
- .setRecord(bse)
- .build();
- }
-
- /**
- * Creates a new instance of the given image type using data already formatted for storage inside the slideshow.
- * <p>
- * This function is most handy when parsing an existing slideshow, as the picture data are already formatted.
- * @param type Image type.
- * @param recordContainer Record tracking all pictures. Should be attached to the slideshow that this picture is
- * linked to.
- * @param bse Record referencing this picture. Should be attached to the slideshow that this picture is linked to.
- * @param data Image data formatted for storage in the slideshow. This does not include the
- * {@link #PREAMBLE_SIZE preamble}.
- * @param signature Image format-specific signature. See subclasses for signature details.
- * @return New instance.
- *
- * @see #createFromImageData(PictureType, EscherContainerRecord, EscherBSERecord, byte[])
- */
- static HSLFPictureData createFromSlideshowData(
- PictureType type,
- EscherContainerRecord recordContainer,
- EscherBSERecord bse,
- byte[] data,
- int signature
- ) {
- HSLFPictureData instance = newInstance(type, recordContainer, bse);
- instance.setSignature(signature);
- instance.formattedData = data;
- return instance;
- }
-
- /**
- * Creates a new instance of the given image type using data already formatted for storage inside the slideshow.
- * <p>
- * This function is most handy when adding new pictures to a slideshow, as the image data provided by users is not
- * yet formatted.
- *
- * @param type Image type.
- * @param recordContainer Record tracking all pictures. Should be attached to the slideshow that this picture is
- * linked to.
- * @param bse Record referencing this picture. Should be attached to the slideshow that this picture is linked to.
- * @param data Original image data. If these bytes were written to a disk, a common image viewer would be able to
- * render the image.
- * @return New instance.
- *
- * @see #createFromSlideshowData(PictureType, EscherContainerRecord, EscherBSERecord, byte[], int)
- * @see #setData(byte[])
- */
- static HSLFPictureData createFromImageData(
- PictureType type,
- EscherContainerRecord recordContainer,
- EscherBSERecord bse,
- byte[] data
- ) {
- HSLFPictureData instance = newInstance(type, recordContainer, bse);
- instance.formattedData = instance.formatImageForSlideshow(data);
- return instance;
- }
-
- private static HSLFPictureData newInstance(
- PictureType type,
- EscherContainerRecord recordContainer,
- EscherBSERecord bse
- ) {
- switch (type) {
- case EMF:
- return new EMF(recordContainer, bse);
- case WMF:
- return new WMF(recordContainer, bse);
- case PICT:
- return new PICT(recordContainer, bse);
- case JPEG:
- return new JPEG(recordContainer, bse);
- case PNG:
- return new PNG(recordContainer, bse);
- case DIB:
- return new DIB(recordContainer, bse);
- default:
- throw new IllegalArgumentException("Unsupported picture type: " + type);
- }
- }
-
- /**
- * Return 24 byte header which preceeds the actual picture data.
- * <p>
- * The header consists of 2-byte signature, 2-byte type,
- * 4-byte image size and 16-byte checksum of the image data.
- * </p>
- *
- * @return the 24 byte header which preceeds the actual picture data.
- */
- public byte[] getHeader() {
- byte[] header = new byte[CHECKSUM_SIZE + PREAMBLE_SIZE];
- LittleEndian.putInt(header, 0, getSignature());
- LittleEndian.putInt(header, 4, getRawData().length);
- System.arraycopy(formattedData, 0, header, PREAMBLE_SIZE, CHECKSUM_SIZE);
- return header;
- }
-
- /**
- * Returns the 1-based index of this picture.
- * @return the 1-based index of this pictures within the pictures stream
- */
- public int getIndex() {
- return index;
- }
-
- /**
- * @param index sets the 1-based index of this pictures within the pictures stream
- */
- public void setIndex(int index) {
- this.index = index;
- }
-
- /**
- * Formats the picture data for storage in the slideshow.
- * <p>
- * Images stored in {@link HSLFSlideShow}s are represented differently than when they are standalone files. The
- * exact formatting differs for each image type.
- *
- * @param data Original image data. If these bytes were written to a disk, a common image viewer would be able to
- * render the image.
- * @return Formatted image representation.
- */
- protected abstract byte[] formatImageForSlideshow(byte[] data);
-
- /**
- * @return Size of this picture when stored in the image stream inside the {@link HSLFSlideShow}.
- */
- int getBseSize() {
- return formattedData.length + PREAMBLE_SIZE;
- }
-
- @Override
- public final void setData(byte[] data) throws IOException {
- /*
- * When working with slideshow pictures, we need to be aware of 2 container units. The first is a list of
- * HSLFPictureData that are the programmatic reference for working with the pictures. The second is the
- * Blip Store. For the purposes of this function, you can think of the Blip Store as containing a list of
- * pointers (with a small summary) to the picture in the slideshow.
- *
- * When updating a picture, we need to update the in-memory data structure (this instance), but we also need to
- * update the stored pointer. When modifying the pointer, we also need to modify all subsequent pointers, since
- * they might shift based on a change in the byte count of the underlying image.
- */
- int oldSize = getBseSize();
- formattedData = formatImageForSlideshow(data);
- int newSize = getBseSize();
- int changeInSize = newSize - oldSize;
- byte[] newUid = getUID();
-
- boolean foundBseForOldImage = false;
-
- // Get the BSE records & sort the list by offset, so we can proceed to shift offsets
- @SuppressWarnings("unchecked") // The BStore only contains BSE records
- List<EscherBSERecord> bseRecords = (List<EscherBSERecord>) (Object) bStore.getChildRecords();
- bseRecords.sort(Comparator.comparingInt(EscherBSERecord::getOffset));
-
- for (EscherBSERecord bse : bseRecords) {
-
- if (foundBseForOldImage) {
-
- // The BSE for this picture was modified in a previous iteration, and we are now adjusting
- // subsequent offsets.
- bse.setOffset(bse.getOffset() + changeInSize);
-
- } else if (bse == this.bse) { // Reference equals is safe because these BSE belong to the same slideshow
-
- // This BSE matches the current image. Update the size and UID.
- foundBseForOldImage = true;
-
- bse.setUid(newUid);
-
- // Image byte count may have changed, so update the pointer.
- bse.setSize(newSize);
- }
- }
- }
-
- @Override
- public final String getContentType() {
- return getType().contentType;
- }
-
- @Override
- public Dimension getImageDimensionInPixels() {
- Dimension dim = getImageDimension();
- return new Dimension(
- Units.pointsToPixel(dim.getWidth()),
- Units.pointsToPixel(dim.getHeight())
- );
- }
-
- @Override
- public Map<String, Supplier<?>> getGenericProperties() {
- final Map<String,Supplier<?>> m = new LinkedHashMap<>();
- m.put("type", this::getType);
- m.put("imageDimension", this::getImageDimension);
- m.put("signature", this::getSignature);
- m.put("uidInstanceCount", this::getUIDInstanceCount);
- m.put("offset", this::getOffset);
- m.put("uid", this::getUID);
- m.put("checksum", this::getChecksum);
- m.put("index", this::getIndex);
- m.put("rawData", this::getRawData);
- return Collections.unmodifiableMap(m);
- }
- }
|