All Downloads are FREE. Search and download functionalities are using the official Maven repository.

de.intarsys.pdf.cos.COSStream Maven / Gradle / Ivy

Go to download

This is a fork of http://sourceforge.net/projects/jpodlib/ as development seems to be frozen. We're providing some bug fixes along with deployments to maven.

There is a newer version: 2.0
Show newest version
/*
 * Copyright (c) 2007, intarsys consulting GmbH
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * - Redistributions of source code must retain the above copyright notice,
 *   this list of conditions and the following disclaimer.
 *
 * - Redistributions in binary form must reproduce the above copyright notice,
 *   this list of conditions and the following disclaimer in the documentation
 *   and/or other materials provided with the distribution.
 *
 * - Neither the name of intarsys nor the names of its contributors may be used
 *   to endorse or promote products derived from this software without specific
 *   prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */
package de.intarsys.pdf.cos;

import de.intarsys.pdf.filter.FilterFactory;
import de.intarsys.pdf.filter.IFilter;
import de.intarsys.tools.collection.SingleObjectIterator;
import de.intarsys.tools.file.FileTools;

import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.Iterator;
import java.util.Map;

/**
 * An object representing stream data in a PDF document. Unlike a string, stream
 * data is not restricted in length. Stream data may be encoded with the filter
 * implementation.
 */
public class COSStream extends COSCompositeObject {
    public static final COSName DK_DecodeParms = COSName.constant("DecodeParms"); //$NON-NLS-1$

    public static final COSName DK_DP = COSName.constant("DP"); //$NON-NLS-1$
    public static final COSName DK_F = COSName.constant("F"); //$NON-NLS-1$
    public static final COSName DK_FDecodeParms = COSName.constant("FDecodeParms"); //$NON-NLS-1$
    public static final COSName DK_FFilter = COSName.constant("FFilter"); //$NON-NLS-1$
    public static final COSName DK_Filter = COSName.constant("Filter"); //$NON-NLS-1$
    public static final COSName DK_Length = COSName.constant("Length"); //$NON-NLS-1$
    public static final COSName DK_Resources = COSName.constant("Resources"); //$NON-NLS-1$

    public static final Object SLOT_BYTES = new Object();

    /**
     * Create a new {@link COSStream}.
     *
     * @param dict An optional dictionary to be used as the streams dictionary.
     * @return Create a new {@link COSStream}.
     */
    public static COSStream create(COSDictionary dict) {
        COSStream result = new COSStream(dict);
        result.beIndirect();
        return result;
    }

    /**
     * The options or an array of options for filtering.
     *
     * @return The options or an array of options for filtering.
     */
    public static COSObject getDecodeParams(COSDictionary dict) {
        if (isExternal(dict)) {
            return dict.get(DK_FDecodeParms);
        }
        COSObject result = dict.get(DK_DP);
        if (!result.isNull()) {
            return result;
        }
        return dict.get(DK_DecodeParms);
    }

    /**
     * The options corresponding to the first occurence of the filter
     * {@code name}.
     *
     * @return The options corresponding to the first occurence of the filter
     * {@code name}.
     */
    public static COSDictionary getDecodeParams(COSDictionary dict, COSName name) {
        COSObject basicFilters = getFilters(dict);
        if (basicFilters instanceof COSName) {
            COSName filter = basicFilters.asName();
            if (filter.equals(name)) {
                return dict.get(COSStream.DK_DecodeParms).asDictionary();
            }
        } else if (basicFilters instanceof COSArray) {
            COSArray filters = basicFilters.asArray();
            int i = 0;
            Iterator it = filters.iterator();
            while (it.hasNext()) {
                COSName filter = ((COSObject) it.next()).asName();
                if (filter != null && filter.equals(name)) {
                    COSArray decodeParamsArray = dict.get(COSStream.DK_DecodeParms).asArray();
                    if (decodeParamsArray != null) {
                        return decodeParamsArray.get(i).asDictionary();
                    }
                }
                i++;
            }
        }
        return null;
    }

    /**
     * Return the filter or the collection of filters for the stream.
     *
     * @return The filter or the collection of filters for the stream.
     */
    public static COSObject getFilters(COSDictionary dict) {
        if (isExternal(dict)) {
            return dict.get(DK_FFilter);
        }
        COSObject result = dict.get(DK_F);
        if (!result.isNull()) {
            return result;
        }
        return dict.get(DK_Filter);
    }

    /**
     * {@code true} if the given stream dictionary has declared a filter
     * {@code name}.
     *
     * @param dict a stream dictionary
     * @param name a filter name
     * @return {@code true} if the given stream dictionary has declared a
     * filter {@code name}.
     */
    public static boolean hasFilter(COSDictionary dict, COSName name) {
        COSObject filters = getFilters(dict);
        if (filters.isNull()) {
            return false;
        }
        if (filters instanceof COSName) {
            return filters.equals(name);
        }
        if (filters instanceof COSArray) {
            for (Iterator i = filters.iterator(); i.hasNext(); ) {
                COSName filterName = ((COSObject) i.next()).asName();
                if (filterName != null && filterName.equals(name)) {
                    return true;
                }
            }
        }
        return false;
    }

    /**
     * {@code true} if the stream dictionary contains the F key.
     *
     * @return {@code true} if the stream dictionary contains the F key.
     */
    public static boolean isExternal(COSDictionary dict) {
        // check for F key
        // if it is a Array or Name, it is used as an abbreviation for Filter
        COSObject result = dict.get(DK_F);
        if (!result.isNull()) {
            return !(result instanceof COSName || result instanceof COSArray);
        }
        return false;
    }

    /**
     * The logical byte stream
     */
    private byte[] decodedBytes;

    /**
     * the dictionary describing the stream
     */
    private COSDictionary dict;

    /**
     * The physical byte stream
     */
    private byte[] encodedBytes;

    protected COSStream() {
        super();
    }

    /**
     * COSStream constructor.
     *
     * @param newDict The stream dictionary for the new stream. Can be null, a new
     *                dictionary will be created.
     */
    protected COSStream(COSDictionary newDict) {
        super();
        if (newDict == null) {
            newDict = COSDictionary.create();
        }
        setDict(newDict);
    }

    /*
     * (non-Javadoc)
     *
     * @see
     * de.intarsys.pdf.cos.COSObject#accept(de.intarsys.pdf.cos.ICOSObjectVisitor
     * )
     */
    @Override
    public java.lang.Object accept(ICOSObjectVisitor visitor) throws COSVisitorException {
        return visitor.visitFromStream(this);
    }

    /**
     * Add a new filter declaration to the filters collection. If necessary
     * convert the Filter entry to a collection first.
     *
     * @param name The logical name of the filter.
     */
    public void addFilter(COSName name) {
        addFilter(getFilterSize(), name, null);
    }

    /**
     * Add a new filter declaration to the filters collection. If necessary
     * convert the Filter entry to a collection first.
     *
     * @param name       The logical name of the filter.
     * @param dictionary The corresponding decode parameters
     */
    public void addFilter(COSName name, COSDictionary dictionary) {
        addFilter(getFilterSize(), name, dictionary);
    }

    /**
     * Add a new filter declaration to the filters collection at the specified
     * index. If necessary convert the Filter entry to a collection first.
     *
     * @param index The index to add the filter at.
     * @param name  The logical name of the filter.
     */
    public void addFilter(int index, COSName name) {
        addFilter(index, name, null);
    }

    /**
     * Add a new filter declaration to the filters collection at the specified
     * index. If necessary convert the Filter entry to a collection first.
     *
     * @param index      The index to add the filter at.
     * @param name       The logical name of the filter.
     * @param dictionary The corresponding decode parameters
     */
    public void addFilter(int index, COSName name, COSDictionary dictionary) {
        // be sure decoded stream is available
        getDecodedBytes();
        encodedBytes = null;
        COSObject filters = getFilters();
        if (filters.isNull()) {
            getDict().put(DK_Filter, name);
            getDict().put(DK_DecodeParms, dictionary);
        } else {
            COSName filterName;
            COSArray newFilterArray;
            COSObject decodeParms;
            COSArray newDecodeParmsArray;

            filterName = filters.asName();
            if (filterName != null) {
                getDict().remove(DK_Filter);
                newFilterArray = COSArray.create(2);
                newFilterArray.add(filterName);
            } else {
                newFilterArray = filters.asArray();
            }
            newFilterArray.add(index, name);
            getDict().put(DK_Filter, newFilterArray);

            decodeParms = getDecodeParams();
            newDecodeParmsArray = null;
            if (decodeParms.isNull() && dictionary != null) {
                newDecodeParmsArray = COSArray.create(newFilterArray.size());
                // add one less
                for (int count = 0; count < newFilterArray.size() - 1; count++) {
                    newDecodeParmsArray.add(COSNull.NULL);
                }
            } else {
                COSDictionary decodeParmsDictionary;

                decodeParmsDictionary = decodeParms.asDictionary();
                if (decodeParmsDictionary != null) {
                    getDict().remove(DK_DecodeParms);
                    newDecodeParmsArray = COSArray.create(newFilterArray.size());
                    newDecodeParmsArray.add(decodeParmsDictionary);
                } else {
                    newDecodeParmsArray = decodeParms.asArray();
                }
            }
            if (newDecodeParmsArray != null) {
                if (dictionary == null) {
                    newDecodeParmsArray.add(index, COSNull.NULL);
                } else {
                    newDecodeParmsArray.add(index, dictionary);
                }
            }
            getDict().put(DK_DecodeParms, newDecodeParmsArray);
        }
    }

    /*
     * (non-Javadoc)
     *
     * @see
     * de.intarsys.pdf.cos.COSCompositeObject#addObjectListener(de.intarsys.
     * pdf.cos.ICOSObjectListener)
     */
    @Override
    public void addObjectListener(ICOSObjectListener listener) {
        super.addObjectListener(listener);
        if (dict != null) {
            dict.addObjectListener(listener);
        }
    }

    /*
     * (non-Javadoc)
     *
     * @see de.intarsys.pdf.cos.COSObject#asStream()
     */
    @Override
    public COSStream asStream() {
        return this;
    }

    /*
     * (non-Javadoc)
     *
     * @see de.intarsys.pdf.cos.COSObject#basicIterator()
     */
    @Override
    public Iterator basicIterator() {
        return new SingleObjectIterator(getDict());
    }

    /**
     * Set the streams logical content.
     *
     * @param newBytes the logical content for the stream
     */
    public void basicSetDecodedBytes(byte[] newBytes) {
        decodedBytes = newBytes;
        encodedBytes = null;
    }

    /**
     * Set the streams physical content.
     *
     * @param newBytes the physical content for the stream
     */
    public void basicSetEncodedBytes(byte[] newBytes) {
        encodedBytes = newBytes;
        decodedBytes = null;
        int length = (encodedBytes == null) ? 0 : encodedBytes.length;
        // no update propagation, please!!
        getDict().basicPutSilent(DK_Length, COSInteger.create(length));
    }

    /*
     * (non-Javadoc)
     *
     * @see de.intarsys.pdf.cos.COSObject#basicToString()
     */
    @Override
    protected String basicToString() {
        byte[] decoded = getDecodedBytes();
        if (decoded == null) {
            return null;
        }
        return new String(decoded);
    }

    /*
     * (non-Javadoc)
     *
     * @see de.intarsys.pdf.cos.COSObject#copyBasic()
     */
    @Override
    protected COSObject copyBasic() {
        COSStream result = new COSStream();
        result.beIndirect();
        // aggregated dictionary takes care of itself
        result.encodedBytes = this.encodedBytes;
        result.decodedBytes = this.decodedBytes;
        return result;
    }

    /**
     * A copy of this, bytes decoded.
     *
     * @return A copy of this, bytes decoded.
     * @throws IOException
     */
    public COSStream copyDecodeFirst() throws IOException {
        COSStream newStream;
        COSName filter;
        COSArray filters;
        COSArray decodeParmss;
        byte[] bytes;

        // prepare new stream
        newStream = (COSStream) copyShallow();
        if (getFilters().isNull()) {
            return newStream;
        }
        newStream.basicSetDecodedBytes(null);
        newStream.getDict().remove(DK_DecodeParms);
        newStream.getDict().remove(DK_Filter);
        newStream.getDict().remove(DK_Length);
        // prepare content
        bytes = getEncodedBytes();
        if ((filter = getFirstFilter()) != null) {
            bytes = doDecode(filter, getFirstDecodeParam(), bytes, 0, getAnyLength());
        }
        filters = getFilters().asArray();
        if (filters != null) {
            decodeParmss = getDecodeParams().asArray();
            for (int index = filters.size() - 1; index > 0; index--) {
                if (decodeParmss != null) {
                    COSDictionary dictionary;

                    dictionary = decodeParmss.get(index).asDictionary();
                    if (dictionary != null) {
                        dictionary = (COSDictionary) dictionary.copyShallow();
                    }
                    newStream.filter((COSName) filters.get(index).copyShallow(), dictionary);
                } else {
                    newStream.filter((COSName) filters.get(index).copyShallow());
                }
            }
        }
        newStream.setEncodedBytes(bytes);
        return newStream;
    }

    /*
     * (non-Javadoc)
     *
     * @see de.intarsys.pdf.cos.COSCompositeObject#copyDeep(java.util.Map)
     */
    @Override
    public COSObject copyDeep(Map copied) {
        COSStream result = (COSStream) super.copyDeep(copied);
        result.setDict((COSDictionary) getDict().copyDeep(copied));
        if (encodedBytes != null) {
            result.setEncodedBytes(encodedBytes);
        } else {
            result.setDecodedBytes(decodedBytes);
        }
        return result;
    }

    /*
     * (non-Javadoc)
     *
     * @see de.intarsys.pdf.cos.COSObject#copyShallow()
     */
    @Override
    public COSObject copyShallow() {
        COSStream result = (COSStream) super.copyShallow();
        result.setDict((COSDictionary) getDict().copyShallow());
        if (encodedBytes != null) {
            result.setEncodedBytes(encodedBytes);
        } else {
            result.setDecodedBytes(decodedBytes);
        }
        return result;
    }

    /*
     * (non-Javadoc)
     *
     * @see de.intarsys.pdf.cos.COSObject#copySubGraph(java.util.Map)
     */
    @Override
    protected COSObject copySubGraph(Map copied) {
        COSStream result = (COSStream) super.copySubGraph(copied);
        result.setDict((COSDictionary) getDict().copySubGraph(copied));
        if (encodedBytes != null) {
            result.setEncodedBytes(encodedBytes);
        } else {
            result.setDecodedBytes(decodedBytes);
        }
        return result;
    }

    /**
     * Decode the filtered stream content using the filters defined in the
     * /Filter entry in the stream dictionary
     *
     * @return The decoded bytes.
     * @throws IOException
     */
    protected byte[] doDecode() throws IOException {
        byte[] newBytes;

        if (isExternal() && isBytesArrayEmpty(encodedBytes)) {
            // reset the encodedbytes because of later null checks
            encodedBytes = null;
            parseFKeyedFile();
        }
        if (encodedBytes == null) {
            return null;
        }

        // get the filters
        COSObject filters = getFilters();
        if (filters.isNull()) {
            int length = getLength();
            if ((length != -1) && (encodedBytes.length > length)) {
                newBytes = new byte[length];
                System.arraycopy(encodedBytes, 0, newBytes, 0, length);
            } else {
                newBytes = encodedBytes;
            }
            return newBytes;
        }

        // get the options
        COSObject options = getDecodeParams();

        // decode
        if (filters instanceof COSName) {
            newBytes = doDecode((COSName) filters, options.asDictionary(), encodedBytes, 0, getAnyLength());
        } else {
            byte[] temp = encodedBytes;
            int length = getAnyLength();
            for (int i = 0; i < ((COSArray) filters).size(); i++) {
                COSObject option = COSNull.NULL;
                if (!options.isNull()) {
                    option = ((COSArray) options).get(i);
                }
                COSName filter = ((COSArray) filters).get(i).asName();
                temp = doDecode(filter, option.asDictionary(), temp, 0, length);
                if (temp != null) {
                    length = temp.length;
                }
            }
            newBytes = temp;
        }
        return newBytes;
    }

    /**
     * Perform the decoding process of the underlying byte stream.
     *
     * @param filterName The name of a filter to use for this step.
     * @param options    The options to use for the filter.
     * @param bytes      The bytes to decode.
     * @param offset     The offset to start.
     * @param length     The length to be decoded.
     * @return The decoded bytes.
     * @throws IOException
     */
    protected byte[] doDecode(COSName filterName, COSDictionary options, byte[] bytes, int offset, int length)
            throws IOException {
        if (bytes == null) {
            return new byte[0];
        }
        IFilter filter = FilterFactory.get().createFilter(filterName, options);
        filter.setStream(this);
        return filter.decode(bytes, offset, length);
    }

    /**
     * encode the filtered stream content using the filters defined in the
     * /Filter entry in the stream dictionary in reverse order
     *
     * @throws IOException
     */
    protected void doEncode() throws IOException {
        if (decodedBytes == null) {
            return;
        }

        // get the filters
        COSObject filters = getFilters();
        if (filters.isNull()) {
            encodedBytes = decodedBytes;
            return;
        }

        // get the options
        COSObject options = getDecodeParams();

        // encode
        if (filters instanceof COSName) {
            encodedBytes = doEncode((COSName) filters, options.asDictionary(), decodedBytes, 0, decodedBytes.length);
        } else {
            byte[] temp = decodedBytes;
            int length = decodedBytes.length;
            for (int i = ((COSArray) filters).size() - 1; i >= 0; i--) {
                COSDictionary option = null;
                if (!options.isNull()) {
                    option = ((COSArray) options).get(i).asDictionary();
                }

                COSName filter = ((COSArray) filters).get(i).asName();
                temp = doEncode(filter, option, temp, 0, length);
                length = temp.length;
            }
            encodedBytes = temp;
        }
    }

    /**
     * Perform the encoding process of the underlying byte stream.
     *
     * @param filterName The name of a filter to use for this step.
     * @param options    The options to use for the filter.
     * @param bytes      The bytes to encode .
     * @param offset     The offset to start.
     * @param length     The length to be encoded.
     * @return The encoded bytes.
     * @throws IOException
     */
    protected byte[] doEncode(COSName filterName, COSDictionary options, byte[] bytes, int offset, int length)
            throws IOException {
        if (bytes == null) {
            return new byte[0];
        }
        IFilter filter = FilterFactory.get().createFilter(filterName, options);
        filter.setStream(this);
        return filter.encode(bytes, offset, length);
    }

    @Override
    public boolean equals(Object o) {
        return this.equals(o, new PairRegister());
    }

    @Override
    protected boolean equals(Object o, PairRegister visited) {
        if (this == o) {
            return true;
        }

        if (o == null || this.getClass() != o.getClass()) {
            return false;
        }

        COSStream other = (COSStream) o;
        if (visited.check(this, other)) {
            // We've already seen this pair.
            return true;
        }

        return this.getDict().equals(other.getDict(), visited) && Arrays.equals(this.getEncodedBytes(),
                                                                                other.getEncodedBytes());
    }

    /**
     * Add a new filter. If the stream is already filtered, the new filter will
     * be applied to the current encoded bytes.
     *
     * @param name The logical name of the filter.
     */
    public void filter(COSName name) {
        addFilter(0, name, null);
    }

    /**
     * Add a new filter. If the stream is already filtered, the new filter will
     * be applied to the current encoded bytes.
     *
     * @param name       The logical name of the filter.
     * @param dictionary The corresponding decode parameters
     */
    public void filter(COSName name, COSDictionary dictionary) {
        addFilter(0, name, dictionary);
    }

    /**
     * The declared or real length for this.
     *
     * @return The declared or real length for this.
     */
    public int getAnyLength() {
        int result = getLength();
        if (result == -1) {
            return encodedBytes.length;
        }
        return result;
    }

    /**
     * The unfiltered (logical) stream content. It is not intended to manipulate
     * the byte array directly.
     *
     * @return The unfiltered (logical) stream content
     * @throws IOException
     */
    public byte[] getDecodedBytes() {
        if (decodedBytes == null) {
            try {
                decodedBytes = doDecode();
            } catch (IOException e) {
                handleException(new COSRuntimeException("error decoding stream", e)); //$NON-NLS-1$
            }
        }
        return decodedBytes;
    }

    /**
     * The unfiltered content as in getDecodedBytes, but allow the caller to
     * manipulate the result by copying/not caching the returned bytes
     *
     * @return The unfiltered content as in getDecodedBytes
     */
    public byte[] getDecodedBytesWritable() {
        byte[] bytes;
        byte[] copiedBytes;

        try {
            bytes = doDecode();
            // take care; doDecode does not always create a new array
            if (bytes != encodedBytes) {
                return bytes;
            }
        } catch (IOException e) {
            handleException(new COSRuntimeException("error decoding stream", e)); //$NON-NLS-1$
            return new byte[0];
        }
        copiedBytes = new byte[bytes.length];
        System.arraycopy(bytes, 0, copiedBytes, 0, bytes.length);
        return copiedBytes;
    }

    /**
     * The options or an array of options for filtering.
     *
     * @return The options or an array of options for filtering.
     */
    public COSObject getDecodeParams() {
        return getDecodeParams(getDict());
    }

    /**
     * The options corresponding to the first occurrence of the filter
     * {@code name}.
     *
     * @return The options corresponding to the first occurrence of the filter
     * {@code name}.
     */
    public COSDictionary getDecodeParams(COSName name) {
        return getDecodeParams(getDict(), name);
    }

    /**
     * The stream dictionary
     *
     * @return The stream dictionary
     */
    public COSDictionary getDict() {
        return dict;
    }

    /**
     * The filtered (physical) stream content. If it must be generated first,
     * then the content length is adjusted as a side effect. It is not intended
     * to manipulate the byte array directly.
     *
     * @return The filtered (physical) stream content
     */
    public byte[] getEncodedBytes() {
        if (encodedBytes == null) {
            try {
                doEncode();
            } catch (IOException e) {
                handleException(new COSRuntimeException("error encoding stream", e)); //$NON-NLS-1$
            }
            int length = (encodedBytes == null) ? 0 : encodedBytes.length;
            getDict().basicPutSilent(DK_Length, COSInteger.create(length));
        }
        return encodedBytes;
    }

    /**
     * Return the filter or the collection of filters for the stream.
     *
     * @return The filter or the collection of filters for the stream.
     */
    public COSObject getFilters() {
        return getFilters(getDict());
    }

    /**
     * Return the number of filters.
     *
     * @return The number of filters.
     */
    public int getFilterSize() {
        COSObject filter;

        filter = getFilters();
        if (filter.isNull()) {
            return 0;
        }
        if (filter.asName() != null) {
            return 1;
        }
        return filter.asArray().size();
    }

    /**
     * A dictionary with filter options or the first element of an array of such
     * dictionaries for each filter.
     *
     * @return A dictionary with filter options or the first element of an array
     * of such dictionaries for each filter.
     */
    public COSDictionary getFirstDecodeParam() {
        COSObject dictionaryOrArray;

        dictionaryOrArray = getDecodeParams();
        if (dictionaryOrArray.isNull()) {
            return null;
        }
        if (dictionaryOrArray instanceof COSDictionary) {
            return (COSDictionary) dictionaryOrArray;
        }
        if (dictionaryOrArray instanceof COSArray) {
            return ((COSArray) dictionaryOrArray).get(0).asDictionary();
        }
        return null;
    }

    /**
     * The filter or the first element of the collection of filters for the
     * stream.
     *
     * @return The filter or the first element of the collection of filters for
     * the stream.
     */
    public COSName getFirstFilter() {
        COSObject nameOrArray;

        nameOrArray = getFilters();
        if (nameOrArray.isNull()) {
            return null;
        }
        if (nameOrArray instanceof COSName) {
            return (COSName) nameOrArray;
        }
        if (nameOrArray instanceof COSArray) {
            return ((COSArray) nameOrArray).get(0).asName();
        }
        return null;
    }

    /**
     * The length of the encoded content. Be aware that this is the /Length
     * written in the stream dictionary, which is not necessarily a meaningful
     * value...
     *
     * @return The length of the encoded content
     */
    public int getLength() {
        COSNumber length = dict.get(DK_Length).asInteger();
        if (length != null) {
            return length.intValue();
        }
        return -1;
    }

    /**
     * {@code true} if this stream has declared a filter {@code name}.
     *
     * @param name a filter name
     * @return {@code true} if the stream has declared a filter
     * {@code name}.
     */
    public boolean hasFilter(COSName name) {
        return hasFilter(getDict(), name);
    }

    /**
     * tests a byte array for null, length=0 or crlf emptiness
     *
     * @param toTest
     * @return
     */
    private boolean isBytesArrayEmpty(byte[] toTest) {
        if ((toTest == null) || (toTest.length == 0)) {
            return true;
        }
        return (toTest.length == 2) && (toTest[0] == 13) && (toTest[1] == 10);
    }

    /**
     * {@code true} if the stream dictionary contains the F key.
     *
     * @return {@code true} if the stream dictionary contains the F key.
     */
    public boolean isExternal() {
        return isExternal(getDict());
    }

    /*
     * (non-Javadoc)
     *
     * @see de.intarsys.pdf.cos.COSObject#iterator()
     */
    @Override
    public Iterator iterator() {
        return new SingleObjectIterator(getDict());
    }

    /**
     * Parse the file referenced by the F key in this stream and set as the
     * filtered content.
     */
    protected void parseFKeyedFile() {
        COSObject fileSpec = dict.get(DK_F);
        String filepath = ""; //$NON-NLS-1$
        if (fileSpec instanceof COSString) {
            filepath = fileSpec.stringValue();
        } else {
            // todo 2 implement PDF fileSpecification logic
            return;
        }
        File externalFile = new File(filepath);
        if (!externalFile.exists()) {
            return;
        }
        byte[] content;
        try {
            content = FileTools.toBytes(externalFile);
        } catch (IOException ignored) {
            return;
        }
        if (content != null) {
            encodedBytes = content;
        }
    }

    /**
     * Remove all filters from this.
     */
    public void removeFilters() {
        // be sure decoded stream is available
        getDecodedBytes();
        encodedBytes = null;
        getDict().remove(DK_Filter);
    }

    /*
     * (non-Javadoc)
     *
     * @see
     * de.intarsys.pdf.cos.COSCompositeObject#removeObjectListener(de.intarsys
     * .pdf.cos.ICOSObjectListener)
     */
    @Override
    public void removeObjectListener(ICOSObjectListener listener) {
        super.removeObjectListener(listener);
        if (dict != null) {
            dict.removeObjectListener(listener);
        }
    }

    /*
     * (non-Javadoc)
     *
     * @see de.intarsys.pdf.cos.COSObject#restoreState(java.lang.Object)
     */
    @Override
    public void restoreState(Object object) {
        super.restoreState(object);
        COSStream stream = (COSStream) object;
        encodedBytes = stream.encodedBytes;
        decodedBytes = stream.decodedBytes;
        triggerChanged(null, null, null);
    }

    /*
     * (non-Javadoc)
     *
     * @see de.intarsys.tools.objectsession.ISaveStateSupport#saveState()
     */
    @Override
    public Object saveState() {
        COSStream result = new COSStream();
        // aggregated dictionary takes care of itself
        result.encodedBytes = this.encodedBytes;
        result.decodedBytes = this.decodedBytes;
        result.container = this.container.saveStateContainer();
        return result;
    }

    /**
     * Set the streams logical content
     *
     * @param newBytes The logical content for the stream
     */
    public void setDecodedBytes(byte[] newBytes) {
        willChange(this);
        basicSetDecodedBytes(newBytes);
        getDict().remove(DK_Length);
        if (objectListeners != null) {
            triggerChanged(SLOT_BYTES, null, null);
        }
    }

    /**
     * Give private access to dictionary to ease copying.
     *
     * @param dictionary dictionary part of the stream
     * @throws IllegalArgumentException if the stream is indirect
     */
    private void setDict(COSDictionary dictionary) {
        if (dictionary.isIndirect()) {
            throw new IllegalArgumentException("stream dictionary cannot be indirect"); //$NON-NLS-1$
        }
        dict = dictionary;
        dict.addContainer(this);
    }

    /**
     * Set the stream physical content.
     *
     * @param newBytes the physical content for the stream
     */
    public void setEncodedBytes(byte[] newBytes) {
        willChange(this);
        basicSetEncodedBytes(newBytes);
        if (objectListeners != null) {
            triggerChanged(SLOT_BYTES, null, null);
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy