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}