aboutsummaryrefslogtreecommitdiff
path: root/src/main/java/org/pdfbox/cos/COSStream.java
diff options
context:
space:
mode:
authortknall <tknall@7b5415b0-85f9-ee4d-85bd-d5d0c3b42d1c>2006-12-01 12:20:24 +0000
committertknall <tknall@7b5415b0-85f9-ee4d-85bd-d5d0c3b42d1c>2006-12-01 12:20:24 +0000
commit6025b6016517c6d898d8957d1d7e03ba71431912 (patch)
treeb15bd6fa5ffe9588a9bca3f2b8a7e358f83b6eba /src/main/java/org/pdfbox/cos/COSStream.java
parentd2c77e820ab4aba8235d71275755021347b3ad10 (diff)
downloadpdf-as-3-6025b6016517c6d898d8957d1d7e03ba71431912.tar.gz
pdf-as-3-6025b6016517c6d898d8957d1d7e03ba71431912.tar.bz2
pdf-as-3-6025b6016517c6d898d8957d1d7e03ba71431912.zip
Initial import of release 2.2.REL-2.2@923
git-svn-id: https://joinup.ec.europa.eu/svn/pdf-as/trunk@4 7b5415b0-85f9-ee4d-85bd-d5d0c3b42d1c
Diffstat (limited to 'src/main/java/org/pdfbox/cos/COSStream.java')
-rw-r--r--src/main/java/org/pdfbox/cos/COSStream.java495
1 files changed, 495 insertions, 0 deletions
diff --git a/src/main/java/org/pdfbox/cos/COSStream.java b/src/main/java/org/pdfbox/cos/COSStream.java
new file mode 100644
index 0000000..206a854
--- /dev/null
+++ b/src/main/java/org/pdfbox/cos/COSStream.java
@@ -0,0 +1,495 @@
+/**
+ * Copyright (c) 200-2005, www.pdfbox.org
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. 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.
+ * 3. Neither the name of pdfbox; 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 REGENTS 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.
+ *
+ * http://www.pdfbox.org
+ *
+ */
+package org.pdfbox.cos;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.RandomAccessFile;
+
+import java.util.List;
+
+import org.apache.log4j.Logger;
+
+import org.pdfbox.filter.Filter;
+import org.pdfbox.filter.FilterManager;
+
+import org.pdfbox.pdfparser.PDFStreamParser;
+
+import org.pdfbox.exceptions.COSVisitorException;
+
+import org.pdfbox.io.RandomAccessFileInputStream;
+import org.pdfbox.io.RandomAccessFileOutputStream;
+
+/**
+ * This class represents a stream object in a PDF document.
+ *
+ * @author Ben Litchfield (ben@benlitchfield.com)
+ * @version $Revision: 1.36 $
+ */
+public class COSStream extends COSDictionary
+{
+ private static final int BUFFER_SIZE=16384;
+ private static Logger log = Logger.getLogger(COSStream.class);
+
+ private RandomAccessFile file;
+ /**
+ * The stream with all of the filters applied.
+ */
+ private RandomAccessFileOutputStream filteredStream;
+
+ /**
+ * The stream with no filters, this contains the useful data.
+ */
+ private RandomAccessFileOutputStream unFilteredStream;
+
+ /**
+ * Constructor. Creates a new stream with an empty dictionary.
+ *
+ * @param storage The intermediate storage for the stream.
+ */
+ public COSStream( RandomAccessFile storage )
+ {
+ super();
+ file = storage;
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param dictionary The dictionary that is associated with this stream.
+ * @param storage The intermediate storage for the stream.
+ */
+ public COSStream( COSDictionary dictionary, RandomAccessFile storage )
+ {
+ super( dictionary );
+ file = storage;
+ }
+
+ /**
+ * This will replace this object with the data from the new object. This
+ * is used to easily maintain referential integrity when changing references
+ * to new objects.
+ *
+ * @param stream The stream that have the new values in it.
+ */
+ public void replaceWithStream( COSStream stream )
+ {
+ this.clear();
+ this.addAll( stream );
+ file = stream.file;
+ filteredStream = stream.filteredStream;
+ unFilteredStream = stream.unFilteredStream;
+ }
+
+ /**
+ * This will get the scratch file associated with this stream.
+ *
+ * @return The scratch file where this stream is being stored.
+ */
+ public RandomAccessFile getScratchFile()
+ {
+ return file;
+ }
+
+ /**
+ * This will get all the tokens in the stream.
+ *
+ * @return All of the tokens in the stream.
+ *
+ * @throws IOException If there is an error parsing the stream.
+ */
+ public List getStreamTokens() throws IOException
+ {
+ PDFStreamParser parser = new PDFStreamParser( this );
+ parser.parse();
+ return parser.getTokens();
+ }
+
+ /**
+ * This will get the stream with all of the filters applied.
+ *
+ * @return the bytes of the physical (endoced) stream
+ *
+ * @throws IOException when encoding/decoding causes an exception
+ */
+ public InputStream getFilteredStream() throws IOException
+ {
+ if( filteredStream == null )
+ {
+ doEncode();
+ }
+ long position = filteredStream.getPosition();
+ long length = filteredStream.getLength();
+
+ RandomAccessFileInputStream input =
+ new RandomAccessFileInputStream( file, position, length );
+ return new BufferedInputStream( input, BUFFER_SIZE );
+ }
+
+ /**
+ * This will get the logical content stream with none of the filters.
+ *
+ * @return the bytes of the logical (decoded) stream
+ *
+ * @throws IOException when encoding/decoding causes an exception
+ */
+ public InputStream getUnfilteredStream() throws IOException
+ {
+ InputStream retval = null;
+ if( unFilteredStream == null )
+ {
+ doDecode();
+ }
+
+ //if unFilteredStream is still null then this stream has not been
+ //created yet, so we should return null.
+ if( unFilteredStream != null )
+ {
+ long position = unFilteredStream.getPosition();
+ long length = unFilteredStream.getLength();
+ RandomAccessFileInputStream input =
+ new RandomAccessFileInputStream( file, position, length );
+ retval = new BufferedInputStream( input, BUFFER_SIZE );
+ }
+ else
+ {
+ // We should check if the COSStream contains data, maybe it
+ // has been created with a RandomAccessFile - which is not
+ // necessary empty.
+ // In this case, the creation was been done as an input, this should
+ // be the unfiltered file, since no filter has been applied yet.
+// if ( (file != null) &&
+// (file.length() > 0) )
+// {
+// retval = new RandomAccessFileInputStream( file,
+// 0,
+// file.length() );
+// }
+// else
+// {
+ //if there is no stream data then simply return an empty stream.
+ retval = new ByteArrayInputStream( new byte[0] );
+// }
+ }
+ return retval;
+ }
+
+ /**
+ * visitor pattern double dispatch method.
+ *
+ * @param visitor The object to notify when visiting this object.
+ * @return any object, depending on the visitor implementation, or null
+ * @throws COSVisitorException If an error occurs while visiting this object.
+ */
+ public Object accept(ICOSVisitor visitor) throws COSVisitorException
+ {
+ return visitor.visitFromStream(this);
+ }
+
+ /**
+ * This will decode the physical byte stream applying all of the filters to the stream.
+ *
+ * @throws IOException If there is an error applying a filter to the stream.
+ */
+ private void doDecode() throws IOException
+ {
+ if( log.isDebugEnabled() )
+ {
+ log.debug("doDecode() start");
+ }
+// FIXME: We shouldn't keep the same reference?
+ unFilteredStream = filteredStream;
+
+ COSBase filters = getFilters();
+ if( filters == null )
+ {
+ if( log.isDebugEnabled() )
+ {
+ log.debug("doDecode() - No filter to apply");
+ }
+ }
+ else if( filters instanceof COSName )
+ {
+ if( log.isDebugEnabled() )
+ {
+ log.debug("doDecode( COSName )");
+ }
+ doDecode( (COSName)filters );
+ }
+ else if( filters instanceof COSArray )
+ {
+ if( log.isDebugEnabled() )
+ {
+ log.debug("doDecode( COSArray )");
+ }
+ COSArray filterArray = (COSArray)filters;
+ for( int i=0; i<filterArray.size(); i++ )
+ {
+ COSName filterName = (COSName)filterArray.get( i );
+ doDecode( filterName );
+ }
+ }
+ else
+ {
+ throw new IOException( "Error: Unknown filter type:" + filters );
+ }
+ if( log.isDebugEnabled() )
+ {
+ log.debug("doDecode() end");
+ }
+ }
+
+ /**
+ * This will decode applying a single filter on the stream.
+ *
+ * @param filterName The name of the filter.
+ *
+ * @throws IOException If there is an error parsing the stream.
+ */
+ private void doDecode( COSName filterName ) throws IOException
+ {
+ long start = System.currentTimeMillis();
+ if( log.isDebugEnabled() )
+ {
+ log.debug("doDecode( " + filterName.getName() + " ) dic=" + this +
+ " read.length="+unFilteredStream.getLength() );
+ }
+
+ FilterManager manager = getFilterManager();
+ Filter filter = manager.getFilter( filterName );
+ InputStream input;
+
+ boolean done = false;
+ IOException exception = null;
+ long position = unFilteredStream.getPosition();
+ long length = unFilteredStream.getLength();
+
+ if( length == 0 )
+ {
+ //if the length is zero then don't bother trying to decode
+ //some filters don't work when attempting to decode
+ //with a zero length stream. See zlib_error_01.pdf
+ unFilteredStream = new RandomAccessFileOutputStream( file );
+ done = true;
+ }
+ else
+ {
+ //ok this is a simple hack, sometimes we read a couple extra
+ //bytes that shouldn't be there, so we encounter an error we will just
+ //try again with one less byte.
+ for( int tryCount=0; !done && tryCount<5; tryCount++ )
+ {
+ try
+ {
+ input = new BufferedInputStream(
+ new RandomAccessFileInputStream( file, position, length ), BUFFER_SIZE );
+ unFilteredStream = new RandomAccessFileOutputStream( file );
+ filter.decode( input, unFilteredStream, this );
+ done = true;
+ }
+ catch( IOException io )
+ {
+ length--;
+ exception = io;
+ }
+ }
+ }
+ if( !done )
+ {
+ throw exception;
+ }
+ long stop = System.currentTimeMillis();
+ if( log.isDebugEnabled() )
+ {
+ log.debug("doDecode( " + filterName.getName() + " ) done time=" + (stop-start) );
+ }
+ }
+
+ /**
+ * This will encode the logical byte stream applying all of the filters to the stream.
+ *
+ * @throws IOException If there is an error applying a filter to the stream.
+ */
+ private void doEncode() throws IOException
+ {
+ filteredStream = unFilteredStream;
+
+ COSBase filters = getFilters();
+ if( filters == null )
+ {
+ log.debug( "No filters for stream" );
+ //there is no filter to apply
+ }
+ else if( filters instanceof COSName )
+ {
+ doEncode( (COSName)filters );
+ }
+ else if( filters instanceof COSArray )
+ {
+ // apply filters in reverse order
+ COSArray filterArray = (COSArray)filters;
+ for( int i=filterArray.size()-1; i>=0; i-- )
+ {
+ COSName filterName = (COSName)filterArray.get( i );
+ doEncode( filterName );
+ }
+ }
+ }
+
+ /**
+ * This will encode applying a single filter on the stream.
+ *
+ * @param filterName The name of the filter.
+ *
+ * @throws IOException If there is an error parsing the stream.
+ */
+ private void doEncode( COSName filterName ) throws IOException
+ {
+ FilterManager manager = getFilterManager();
+ Filter filter = manager.getFilter( filterName );
+ InputStream input;
+
+ input = new BufferedInputStream(
+ new RandomAccessFileInputStream( file, filteredStream.getPosition(),
+ filteredStream.getLength() ), BUFFER_SIZE );
+ filteredStream = new RandomAccessFileOutputStream( file );
+ filter.encode( input, filteredStream, this );
+ }
+
+ /**
+ * This will return the filters to apply to the byte stream.
+ * The method will return
+ * - null if no filters are to be applied
+ * - a COSName if one filter is to be applied
+ * - a COSArray containing COSNames if multiple filters are to be applied
+ *
+ * @return the COSBase object representing the filters
+ */
+ public COSBase getFilters()
+ {
+ return getDictionaryObject(COSName.FILTER);
+ }
+
+ /**
+ * This will create a new stream for which filtered byte should be
+ * written to. You probably don't want this but want to use the
+ * createUnfilteredStream, which is used to write raw bytes to.
+ *
+ * @return A stream that can be written to.
+ *
+ * @throws IOException If there is an error creating the stream.
+ */
+ public OutputStream createFilteredStream() throws IOException
+ {
+ filteredStream = new RandomAccessFileOutputStream( file );
+ unFilteredStream = null;
+ return new BufferedOutputStream( filteredStream, BUFFER_SIZE );
+ }
+
+ /**
+ * This will create a new stream for which filtered byte should be
+ * written to. You probably don't want this but want to use the
+ * createUnfilteredStream, which is used to write raw bytes to.
+ *
+ * @param expectedLength An entry where a length is expected.
+ *
+ * @return A stream that can be written to.
+ *
+ * @throws IOException If there is an error creating the stream.
+ */
+ public OutputStream createFilteredStream( COSBase expectedLength ) throws IOException
+ {
+ filteredStream = new RandomAccessFileOutputStream( file );
+ filteredStream.setExpectedLength( expectedLength );
+ unFilteredStream = null;
+ return new BufferedOutputStream( filteredStream, BUFFER_SIZE );
+ }
+
+ /**
+ * set the filters to be applied to the stream.
+ *
+ * @param filters The filters to set on this stream.
+ *
+ * @throws IOException If there is an error clearing the old filters.
+ */
+ public void setFilters(COSBase filters) throws IOException
+ {
+ setItem(COSName.FILTER, filters);
+ // kill cached filtered streams
+ filteredStream = null;
+ }
+
+ /**
+ * This will create an output stream that can be written to.
+ *
+ * @return An output stream which raw data bytes should be written to.
+ *
+ * @throws IOException If there is an error creating the stream.
+ */
+ public OutputStream createUnfilteredStream() throws IOException
+ {
+ unFilteredStream = new RandomAccessFileOutputStream( file );
+ filteredStream = null;
+ return new BufferedOutputStream( unFilteredStream, BUFFER_SIZE );
+ }
+
+ /**
+ * This will print a byte array as a hex string to standard output.
+ *
+ * @param data The array to print.
+ */
+ private static void printHexString( byte[] data )
+ {
+ for( int i=0; i<data.length; i++ )
+ {
+ int nextByte = (data[i] + 256)%256;
+ String hexString = Integer.toHexString( nextByte );
+ if( hexString.length() < 2 )
+ {
+ hexString = "0" + hexString;
+ }
+ System.out.print( hexString );
+ if( i != 0 && (i+1) % 2 == 0 )
+ {
+ System.out.print( " " );
+ }
+ if( i!= 0 && i % 20 == 0 )
+ {
+ System.out.println();
+ }
+ }
+ System.out.println();
+ }
+} \ No newline at end of file