001/*
002 * $Id: PdfStream.java 4784 2011-03-15 08:33:00Z blowagie $
003 *
004 * This file is part of the iText (R) project.
005 * Copyright (c) 1998-2011 1T3XT BVBA
006 * Authors: Bruno Lowagie, Paulo Soares, et al.
007 *
008 * This program is free software; you can redistribute it and/or modify
009 * it under the terms of the GNU Affero General Public License version 3
010 * as published by the Free Software Foundation with the addition of the
011 * following permission added to Section 15 as permitted in Section 7(a):
012 * FOR ANY PART OF THE COVERED WORK IN WHICH THE COPYRIGHT IS OWNED BY 1T3XT,
013 * 1T3XT DISCLAIMS THE WARRANTY OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
014 *
015 * This program is distributed in the hope that it will be useful, but
016 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
017 * or FITNESS FOR A PARTICULAR PURPOSE.
018 * See the GNU Affero General Public License for more details.
019 * You should have received a copy of the GNU Affero General Public License
020 * along with this program; if not, see http://www.gnu.org/licenses or write to
021 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
022 * Boston, MA, 02110-1301 USA, or download the license from the following URL:
023 * http://itextpdf.com/terms-of-use/
024 *
025 * The interactive user interfaces in modified source and object code versions
026 * of this program must display Appropriate Legal Notices, as required under
027 * Section 5 of the GNU Affero General Public License.
028 *
029 * In accordance with Section 7(b) of the GNU Affero General Public License,
030 * a covered work must retain the producer line in every PDF that is created
031 * or manipulated using iText.
032 *
033 * You can be released from the requirements of the license by purchasing
034 * a commercial license. Buying such a license is mandatory as soon as you
035 * develop commercial activities involving the iText software without
036 * disclosing the source code of your own applications.
037 * These activities include: offering paid services to customers as an ASP,
038 * serving PDFs on the fly in a web application, shipping iText with a closed
039 * source product.
040 *
041 * For more information, please contact iText Software Corp. at this
042 * address: sales@itextpdf.com
043 */
044package com.itextpdf.text.pdf;
045
046import java.io.ByteArrayOutputStream;
047import java.io.IOException;
048import java.io.InputStream;
049import java.io.OutputStream;
050import java.util.zip.Deflater;
051import java.util.zip.DeflaterOutputStream;
052import com.itextpdf.text.error_messages.MessageLocalization;
053
054import com.itextpdf.text.DocWriter;
055import com.itextpdf.text.Document;
056import com.itextpdf.text.ExceptionConverter;
057
058/**
059 * <CODE>PdfStream</CODE> is the Pdf stream object.
060 * <P>
061 * A stream, like a string, is a sequence of characters. However, an application can
062 * read a small portion of a stream at a time, while a string must be read in its entirety.
063 * For this reason, objects with potentially large amounts of data, such as images and
064 * page descriptions, are represented as streams.<BR>
065 * A stream consists of a dictionary that describes a sequence of characters, followed by
066 * the keyword <B>stream</B>, followed by zero or more lines of characters, followed by
067 * the keyword <B>endstream</B>.<BR>
068 * All streams must be <CODE>PdfIndirectObject</CODE>s. The stream dictionary must be a direct
069 * object. The keyword <B>stream</B> that follows the stream dictionary should be followed by
070 * a carriage return and linefeed or just a linefeed.<BR>
071 * Remark: In this version only the FLATEDECODE-filter is supported.<BR>
072 * This object is described in the 'Portable Document Format Reference Manual version 1.7'
073 * section 3.2.7 (page 60-63).<BR>
074 *
075 * @see         PdfObject
076 * @see         PdfDictionary
077 */
078
079public class PdfStream extends PdfDictionary {
080    
081    // membervariables
082
083        /**
084         * A possible compression level.
085         * @since       2.1.3
086         */
087        public static final int DEFAULT_COMPRESSION = -1;
088        /**
089         * A possible compression level.
090         * @since       2.1.3
091         */
092        public static final int NO_COMPRESSION = 0;
093        /**
094         * A possible compression level.
095         * @since       2.1.3
096         */
097        public static final int BEST_SPEED = 1;
098        /**
099         * A possible compression level.
100         * @since       2.1.3
101         */
102        public static final int BEST_COMPRESSION = 9;
103        
104        
105/** is the stream compressed? */
106    protected boolean compressed = false;
107    /**
108     * The level of compression.
109     * @since   2.1.3
110     */
111    protected int compressionLevel = NO_COMPRESSION;
112    
113    protected ByteArrayOutputStream streamBytes = null;
114    protected InputStream inputStream;
115    protected PdfIndirectReference ref;
116    protected int inputStreamLength = -1;
117    protected PdfWriter writer;
118    protected int rawLength;
119        
120    static final byte STARTSTREAM[] = DocWriter.getISOBytes("stream\n");
121    static final byte ENDSTREAM[] = DocWriter.getISOBytes("\nendstream");
122    static final int SIZESTREAM = STARTSTREAM.length + ENDSTREAM.length;
123
124    // constructors
125    
126/**
127 * Constructs a <CODE>PdfStream</CODE>-object.
128 *
129 * @param               bytes                   content of the new <CODE>PdfObject</CODE> as an array of <CODE>byte</CODE>.
130 */
131 
132    public PdfStream(byte[] bytes) {
133        super();
134        type = STREAM;
135        this.bytes = bytes;
136        rawLength = bytes.length;
137        put(PdfName.LENGTH, new PdfNumber(bytes.length));
138    }
139  
140    /**
141     * Creates an efficient stream. No temporary array is ever created. The <CODE>InputStream</CODE>
142     * is totally consumed but is not closed. The general usage is:
143     * <p>
144     * <pre>
145     * InputStream in = ...;
146     * PdfStream stream = new PdfStream(in, writer);
147     * stream.flateCompress();
148     * writer.addToBody(stream);
149     * stream.writeLength();
150     * in.close();
151     * </pre>
152     * @param inputStream the data to write to this stream
153     * @param writer the <CODE>PdfWriter</CODE> for this stream
154     */    
155    public PdfStream(InputStream inputStream, PdfWriter writer) {
156        super();
157        type = STREAM;
158        this.inputStream = inputStream;
159        this.writer = writer;
160        ref = writer.getPdfIndirectReference();
161        put(PdfName.LENGTH, ref);
162    }
163  
164/**
165 * Constructs a <CODE>PdfStream</CODE>-object.
166 */
167    
168    protected PdfStream() {
169        super();
170        type = STREAM;
171    }
172    
173    /**
174     * Writes the stream length to the <CODE>PdfWriter</CODE>.
175     * <p>
176     * This method must be called and can only be called if the constructor {@link #PdfStream(InputStream,PdfWriter)}
177     * is used to create the stream.
178     * @throws IOException on error
179     * @see #PdfStream(InputStream,PdfWriter)
180     */
181    public void writeLength() throws IOException {
182        if (inputStream == null)
183            throw new UnsupportedOperationException(MessageLocalization.getComposedMessage("writelength.can.only.be.called.in.a.contructed.pdfstream.inputstream.pdfwriter"));
184        if (inputStreamLength == -1)
185            throw new IOException(MessageLocalization.getComposedMessage("writelength.can.only.be.called.after.output.of.the.stream.body"));
186        writer.addToBody(new PdfNumber(inputStreamLength), ref, false);
187    }
188    
189    /**
190     * Gets the raw length of the stream.
191     * @return the raw length of the stream
192     */
193    public int getRawLength() {
194        return rawLength;
195    }
196    
197    /**
198     * Compresses the stream.
199     */
200    public void flateCompress() {
201        flateCompress(DEFAULT_COMPRESSION);
202    }
203    
204    /**
205     * Compresses the stream.
206         * @param compressionLevel the compression level (0 = best speed, 9 = best compression, -1 is default)
207         * @since       2.1.3
208     */
209    public void flateCompress(int compressionLevel) {
210        if (!Document.compress)
211            return;
212        // check if the flateCompress-method has already been
213        if (compressed) {
214            return;
215        }
216        this.compressionLevel = compressionLevel;
217        if (inputStream != null) {
218            compressed = true;
219            return;
220        }
221        // check if a filter already exists
222        PdfObject filter = PdfReader.getPdfObject(get(PdfName.FILTER));
223        if (filter != null) {
224            if (filter.isName()) {
225                if (PdfName.FLATEDECODE.equals(filter))
226                    return;
227            }
228            else if (filter.isArray()) {
229                if (((PdfArray) filter).contains(PdfName.FLATEDECODE))
230                    return;
231            }
232            else {
233                throw new RuntimeException(MessageLocalization.getComposedMessage("stream.could.not.be.compressed.filter.is.not.a.name.or.array"));
234            }
235        }
236        try {
237            // compress
238            ByteArrayOutputStream stream = new ByteArrayOutputStream();
239            Deflater deflater = new Deflater(compressionLevel);
240            DeflaterOutputStream zip = new DeflaterOutputStream(stream, deflater);
241            if (streamBytes != null)
242                streamBytes.writeTo(zip);
243            else
244                zip.write(bytes);
245            zip.close();
246            deflater.end();
247            // update the object
248            streamBytes = stream;
249            bytes = null;
250            put(PdfName.LENGTH, new PdfNumber(streamBytes.size()));
251            if (filter == null) {
252                put(PdfName.FILTER, PdfName.FLATEDECODE);
253            }
254            else {
255                PdfArray filters = new PdfArray(filter);
256                filters.add(PdfName.FLATEDECODE);
257                put(PdfName.FILTER, filters);
258            }
259            compressed = true;
260        }
261        catch(IOException ioe) {
262            throw new ExceptionConverter(ioe);
263        }
264    }
265
266//    public int getStreamLength(PdfWriter writer) {
267//        if (dicBytes == null)
268//            toPdf(writer);
269//        if (streamBytes != null)
270//            return streamBytes.size() + dicBytes.length + SIZESTREAM;
271//        else
272//            return bytes.length + dicBytes.length + SIZESTREAM;
273//    }
274    
275    protected void superToPdf(PdfWriter writer, OutputStream os) throws IOException {
276        super.toPdf(writer, os);
277    }
278    
279    /**
280     * @see com.itextpdf.text.pdf.PdfDictionary#toPdf(com.itextpdf.text.pdf.PdfWriter, java.io.OutputStream)
281     */
282    public void toPdf(PdfWriter writer, OutputStream os) throws IOException {
283        if (inputStream != null && compressed)
284            put(PdfName.FILTER, PdfName.FLATEDECODE);
285        PdfEncryption crypto = null;
286        if (writer != null)
287            crypto = writer.getEncryption();
288        if (crypto != null) {
289            PdfObject filter = get(PdfName.FILTER);
290            if (filter != null) {
291                if (PdfName.CRYPT.equals(filter))
292                    crypto = null;
293                else if (filter.isArray()) {
294                    PdfArray a = (PdfArray)filter;
295                    if (!a.isEmpty() && PdfName.CRYPT.equals(a.getPdfObject(0)))
296                        crypto = null;
297                }
298            }
299        }
300        PdfObject nn = get(PdfName.LENGTH);
301        if (crypto != null && nn != null && nn.isNumber()) {
302            int sz = ((PdfNumber)nn).intValue();
303            put(PdfName.LENGTH, new PdfNumber(crypto.calculateStreamSize(sz)));
304            superToPdf(writer, os);
305            put(PdfName.LENGTH, nn);
306        }
307        else
308            superToPdf(writer, os);
309        os.write(STARTSTREAM);
310        if (inputStream != null) {
311            rawLength = 0;
312            DeflaterOutputStream def = null;
313            OutputStreamCounter osc = new OutputStreamCounter(os);
314            OutputStreamEncryption ose = null;
315            OutputStream fout = osc;
316            if (crypto != null && !crypto.isEmbeddedFilesOnly())
317                fout = ose = crypto.getEncryptionStream(fout);
318            Deflater deflater = null;
319            if (compressed) {
320                deflater = new Deflater(compressionLevel);
321                fout = def = new DeflaterOutputStream(fout, deflater, 0x8000);
322            }
323            
324            byte buf[] = new byte[4192];
325            while (true) {
326                int n = inputStream.read(buf);
327                if (n <= 0)
328                    break;
329                fout.write(buf, 0, n);
330                rawLength += n;
331            }
332            if (def != null) {
333                def.finish();
334                deflater.end();
335            }
336            if (ose != null)
337                ose.finish();
338            inputStreamLength = osc.getCounter();
339        }
340        else {
341            if (crypto != null && !crypto.isEmbeddedFilesOnly()) {
342                byte b[];
343                if (streamBytes != null) {
344                    b = crypto.encryptByteArray(streamBytes.toByteArray());
345                }
346                else {
347                    b = crypto.encryptByteArray(bytes);
348                }
349                os.write(b);
350            }
351            else {
352                if (streamBytes != null)
353                    streamBytes.writeTo(os);
354                else
355                    os.write(bytes);
356            }
357        }
358        os.write(ENDSTREAM);
359    }
360    
361    /**
362     * Writes the data content to an <CODE>OutputStream</CODE>.
363     * @param os the destination to write to
364     * @throws IOException on error
365     */    
366    public void writeContent(OutputStream os) throws IOException {
367        if (streamBytes != null)
368            streamBytes.writeTo(os);
369        else if (bytes != null)
370            os.write(bytes);
371    }
372    
373    /**
374     * @see com.itextpdf.text.pdf.PdfObject#toString()
375     */
376    public String toString() {
377        if (get(PdfName.TYPE) == null) return "Stream";
378        return "Stream of type: " + get(PdfName.TYPE);
379    }
380}