1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one or more 3 * contributor license agreements. See the NOTICE file distributed with 4 * this work for additional information regarding copyright ownership. 5 * The ASF licenses this file to You under the Apache License, Version 2.0 6 * (the "License"); you may not use this file except in compliance with 7 * the License. You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 package org.apache.pdfbox.pdmodel.common; 18 19 import java.io.ByteArrayInputStream; 20 import java.io.ByteArrayOutputStream; 21 import java.io.IOException; 22 import java.io.InputStream; 23 import java.io.OutputStream; 24 25 import java.util.ArrayList; 26 import java.util.List; 27 import java.util.Map; 28 29 import org.apache.pdfbox.cos.COSArray; 30 import org.apache.pdfbox.cos.COSBase; 31 import org.apache.pdfbox.cos.COSDictionary; 32 import org.apache.pdfbox.cos.COSName; 33 import org.apache.pdfbox.cos.COSStream; 34 35 import org.apache.pdfbox.filter.Filter; 36 import org.apache.pdfbox.filter.FilterManager; 37 38 import org.apache.pdfbox.pdmodel.PDDocument; 39 40 import org.apache.pdfbox.pdmodel.common.filespecification.PDFileSpecification; 41 42 /** 43 * A PDStream represents a stream in a PDF document. Streams are tied to a single 44 * PDF document. 45 * 46 * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a> 47 * @version $Revision: 1.17 $ 48 */ 49 public class PDStream implements COSObjectable 50 { 51 private COSStream stream; 52 53 /** 54 * This will create a new PDStream object. 55 */ 56 protected PDStream() 57 { 58 //should only be called by PDMemoryStream 59 } 60 61 /** 62 * This will create a new PDStream object. 63 * 64 * @param document The document that the stream will be part of. 65 */ 66 public PDStream( PDDocument document ) 67 { 68 stream = new COSStream( document.getDocument().getScratchFile() ); 69 } 70 71 /** 72 * Constructor. 73 * 74 * @param str The stream parameter. 75 */ 76 public PDStream( COSStream str ) 77 { 78 stream = str; 79 } 80 81 /** 82 * Constructor. Reads all data from the input stream and embeds it into the 83 * document, this will close the InputStream. 84 * 85 * @param doc The document that will hold the stream. 86 * @param str The stream parameter. 87 * @throws IOException If there is an error creating the stream in the document. 88 */ 89 public PDStream( PDDocument doc, InputStream str ) throws IOException 90 { 91 this( doc, str, false ); 92 } 93 94 /** 95 * Constructor. Reads all data from the input stream and embeds it into the 96 * document, this will close the InputStream. 97 * 98 * @param doc The document that will hold the stream. 99 * @param str The stream parameter. 100 * @param filtered True if the stream already has a filter applied. 101 * @throws IOException If there is an error creating the stream in the document. 102 */ 103 public PDStream( PDDocument doc, InputStream str, boolean filtered ) throws IOException 104 { 105 OutputStream output = null; 106 try 107 { 108 stream = new COSStream( doc.getDocument().getScratchFile() ); 109 if( filtered ) 110 { 111 output = stream.createFilteredStream(); 112 } 113 else 114 { 115 output = stream.createUnfilteredStream(); 116 } 117 byte[] buffer = new byte[ 1024 ]; 118 int amountRead = -1; 119 while( (amountRead = str.read(buffer)) != -1 ) 120 { 121 output.write( buffer, 0, amountRead ); 122 } 123 } 124 finally 125 { 126 if( output != null ) 127 { 128 output.close(); 129 } 130 if( str != null ) 131 { 132 str.close(); 133 } 134 } 135 } 136 137 /** 138 * If there are not compression filters on the current stream then this 139 * will add a compression filter, flate compression for example. 140 */ 141 public void addCompression() 142 { 143 List filters = getFilters(); 144 if( filters == null ) 145 { 146 filters = new ArrayList(); 147 filters.add( COSName.FLATE_DECODE ); 148 setFilters( filters ); 149 } 150 } 151 152 /** 153 * Create a pd stream from either a regular COSStream on a COSArray of cos streams. 154 * @param base Either a COSStream or COSArray. 155 * @return A PDStream or null if base is null. 156 * @throws IOException If there is an error creating the PDStream. 157 */ 158 public static PDStream createFromCOS( COSBase base ) throws IOException 159 { 160 PDStream retval = null; 161 if( base instanceof COSStream ) 162 { 163 retval = new PDStream( (COSStream)base ); 164 } 165 else if( base instanceof COSArray ) 166 { 167 retval = new PDStream( new COSStreamArray( (COSArray)base ) ); 168 } 169 else 170 { 171 if( base != null ) 172 { 173 throw new IOException( "Contents are unknown type:" + base.getClass().getName() ); 174 } 175 } 176 return retval; 177 } 178 179 180 /** 181 * Convert this standard java object to a COS object. 182 * 183 * @return The cos object that matches this Java object. 184 */ 185 public COSBase getCOSObject() 186 { 187 return stream; 188 } 189 190 /** 191 * This will get a stream that can be written to. 192 * 193 * @return An output stream to write data to. 194 * 195 * @throws IOException If an IO error occurs during writing. 196 */ 197 public OutputStream createOutputStream() throws IOException 198 { 199 return stream.createUnfilteredStream(); 200 } 201 202 /** 203 * This will get a stream that can be read from. 204 * 205 * @return An input stream that can be read from. 206 * 207 * @throws IOException If an IO error occurs during reading. 208 */ 209 public InputStream createInputStream() throws IOException 210 { 211 return stream.getUnfilteredStream(); 212 } 213 214 /** 215 * This will get a stream with some filters applied but not others. This is useful 216 * when doing images, ie filters = [flate,dct], we want to remove flate but leave dct 217 * 218 * @param stopFilters A list of filters to stop decoding at. 219 * @return A stream with decoded data. 220 * @throws IOException If there is an error processing the stream. 221 */ 222 public InputStream getPartiallyFilteredStream( List stopFilters ) throws IOException 223 { 224 FilterManager manager = stream.getFilterManager(); 225 InputStream is = stream.getFilteredStream(); 226 ByteArrayOutputStream os = new ByteArrayOutputStream(); 227 List filters = getFilters(); 228 String nextFilter = null; 229 boolean done = false; 230 for( int i=0; i<filters.size() && !done; i++ ) 231 { 232 os.reset(); 233 nextFilter = (String)filters.get( i ); 234 if( stopFilters.contains( nextFilter ) ) 235 { 236 done = true; 237 } 238 else 239 { 240 Filter filter = manager.getFilter( COSName.getPDFName(nextFilter) ); 241 filter.decode( is, os, stream, i ); 242 is = new ByteArrayInputStream( os.toByteArray() ); 243 } 244 } 245 return is; 246 } 247 248 /** 249 * Get the cos stream associated with this object. 250 * 251 * @return The cos object that matches this Java object. 252 */ 253 public COSStream getStream() 254 { 255 return stream; 256 } 257 258 /** 259 * This will get the length of the filtered/compressed stream. This is readonly in the 260 * PD Model and will be managed by this class. 261 * 262 * @return The length of the filtered stream. 263 */ 264 public int getLength() 265 { 266 return stream.getInt( "Length", 0 ); 267 } 268 269 /** 270 * This will get the list of filters that are associated with this stream. Or 271 * null if there are none. 272 * @return A list of all encoding filters to apply to this stream. 273 */ 274 public List getFilters() 275 { 276 List retval = null; 277 COSBase filters = stream.getFilters(); 278 if( filters instanceof COSName ) 279 { 280 COSName name = (COSName)filters; 281 retval = new COSArrayList( name.getName(), name, stream, COSName.FILTER ); 282 } 283 else if( filters instanceof COSArray ) 284 { 285 retval = COSArrayList.convertCOSNameCOSArrayToList( (COSArray)filters ); 286 } 287 return retval; 288 } 289 290 /** 291 * This will set the filters that are part of this stream. 292 * 293 * @param filters The filters that are part of this stream. 294 */ 295 public void setFilters( List filters ) 296 { 297 COSBase obj = COSArrayList.convertStringListToCOSNameCOSArray( filters ); 298 stream.setItem( COSName.FILTER, obj ); 299 } 300 301 /** 302 * Get the list of decode parameters. Each entry in the list will refer to 303 * an entry in the filters list. 304 * 305 * @return The list of decode parameters. 306 * 307 * @throws IOException if there is an error retrieving the parameters. 308 */ 309 public List getDecodeParms() throws IOException 310 { 311 List retval = null; 312 313 COSBase dp = stream.getDictionaryObject( COSName.DECODE_PARMS ); 314 if( dp == null ) 315 { 316 //See PDF Ref 1.5 implementation note 7, the DP is sometimes used instead. 317 dp = stream.getDictionaryObject( COSName.DP ); 318 } 319 if( dp instanceof COSDictionary ) 320 { 321 Map map = COSDictionaryMap.convertBasicTypesToMap( (COSDictionary)dp ); 322 retval = new COSArrayList(map, dp, stream, COSName.DECODE_PARMS ); 323 } 324 else if( dp instanceof COSArray ) 325 { 326 COSArray array = (COSArray)dp; 327 List actuals = new ArrayList(); 328 for( int i=0; i<array.size(); i++ ) 329 { 330 actuals.add( 331 COSDictionaryMap.convertBasicTypesToMap( 332 (COSDictionary)array.getObject( i ) ) ); 333 } 334 retval = new COSArrayList(actuals, array); 335 } 336 337 return retval; 338 } 339 340 /** 341 * This will set the list of decode parameterss. 342 * 343 * @param decodeParams The list of decode parameterss. 344 */ 345 public void setDecodeParms( List decodeParams ) 346 { 347 stream.setItem( 348 COSName.DECODE_PARMS, COSArrayList.converterToCOSArray( decodeParams ) ); 349 } 350 351 /** 352 * This will get the file specification for this stream. This is only 353 * required for external files. 354 * 355 * @return The file specification. 356 * 357 * @throws IOException If there is an error creating the file spec. 358 */ 359 public PDFileSpecification getFile() throws IOException 360 { 361 COSBase f = stream.getDictionaryObject( COSName.F ); 362 PDFileSpecification retval = PDFileSpecification.createFS( f ); 363 return retval; 364 } 365 366 /** 367 * Set the file specification. 368 * @param f The file specification. 369 */ 370 public void setFile( PDFileSpecification f ) 371 { 372 stream.setItem( COSName.F, f ); 373 } 374 375 /** 376 * This will get the list of filters that are associated with this stream. Or 377 * null if there are none. 378 * @return A list of all encoding filters to apply to this stream. 379 */ 380 public List getFileFilters() 381 { 382 List retval = null; 383 COSBase filters = stream.getDictionaryObject( COSName.F_FILTER ); 384 if( filters instanceof COSName ) 385 { 386 COSName name = (COSName)filters; 387 retval = new COSArrayList( name.getName(), name, stream, COSName.F_FILTER ); 388 } 389 else if( filters instanceof COSArray ) 390 { 391 retval = COSArrayList.convertCOSNameCOSArrayToList( (COSArray)filters ); 392 } 393 return retval; 394 } 395 396 /** 397 * This will set the filters that are part of this stream. 398 * 399 * @param filters The filters that are part of this stream. 400 */ 401 public void setFileFilters( List filters ) 402 { 403 COSBase obj = COSArrayList.convertStringListToCOSNameCOSArray( filters ); 404 stream.setItem( COSName.F_FILTER, obj ); 405 } 406 407 /** 408 * Get the list of decode parameters. Each entry in the list will refer to 409 * an entry in the filters list. 410 * 411 * @return The list of decode parameters. 412 * 413 * @throws IOException if there is an error retrieving the parameters. 414 */ 415 public List getFileDecodeParams() throws IOException 416 { 417 List retval = null; 418 419 COSBase dp = stream.getDictionaryObject( COSName.F_DECODE_PARMS ); 420 if( dp instanceof COSDictionary ) 421 { 422 Map map = COSDictionaryMap.convertBasicTypesToMap( (COSDictionary)dp ); 423 retval = new COSArrayList(map, dp, stream, COSName.F_DECODE_PARMS ); 424 } 425 else if( dp instanceof COSArray ) 426 { 427 COSArray array = (COSArray)dp; 428 List actuals = new ArrayList(); 429 for( int i=0; i<array.size(); i++ ) 430 { 431 actuals.add( 432 COSDictionaryMap.convertBasicTypesToMap( 433 (COSDictionary)array.getObject( i ) ) ); 434 } 435 retval = new COSArrayList(actuals, array); 436 } 437 438 return retval; 439 } 440 441 /** 442 * This will set the list of decode params. 443 * 444 * @param decodeParams The list of decode params. 445 */ 446 public void setFileDecodeParams( List decodeParams ) 447 { 448 stream.setItem( 449 "FDecodeParams", COSArrayList.converterToCOSArray( decodeParams ) ); 450 } 451 452 /** 453 * This will copy the stream into a byte array. 454 * 455 * @return The byte array of the filteredStream 456 * @throws IOException When getFilteredStream did not work 457 */ 458 public byte[] getByteArray() throws IOException 459 { 460 ByteArrayOutputStream output = new ByteArrayOutputStream(); 461 byte[] buf = new byte[1024]; 462 InputStream is = null; 463 try 464 { 465 is = createInputStream(); 466 int amountRead = -1; 467 while( (amountRead = is.read( buf )) != -1) 468 { 469 output.write( buf, 0, amountRead ); 470 } 471 } 472 finally 473 { 474 if( is != null ) 475 { 476 is.close(); 477 } 478 } 479 return output.toByteArray(); 480 } 481 482 /** 483 * A convenience method to get this stream as a string. Uses 484 * the default system encoding. 485 * 486 * @return a String representation of this (input) stream, with the 487 * platform default encoding. 488 * 489 * @throws IOException if there is an error while converting the stream 490 * to a string. 491 */ 492 public String getInputStreamAsString() throws IOException 493 { 494 byte[] bStream = getByteArray(); 495 return new String(bStream); 496 } 497 498 /** 499 * Get the metadata that is part of the document catalog. This will 500 * return null if there is no meta data for this object. 501 * 502 * @return The metadata for this object. 503 */ 504 public PDMetadata getMetadata() 505 { 506 PDMetadata retval = null; 507 COSStream mdStream = (COSStream)stream.getDictionaryObject( COSName.METADATA ); 508 if( mdStream != null ) 509 { 510 retval = new PDMetadata( mdStream ); 511 } 512 return retval; 513 } 514 515 /** 516 * Set the metadata for this object. This can be null. 517 * 518 * @param meta The meta data for this object. 519 */ 520 public void setMetadata( PDMetadata meta ) 521 { 522 stream.setItem( COSName.METADATA, meta ); 523 } 524 525 /** 526 * Get the decoded stream length. 527 * 528 * @since Apache PDFBox 1.1.0 529 * @see <a href="https://issues.apache.org/jira/browse/PDFBOX-636">PDFBOX-636</a> 530 * @return the decoded stream length 531 */ 532 public int getDecodedStreamLength() 533 { 534 return this.stream.getInt(COSName.DL); 535 } 536 537 /** 538 * Set the decoded stream length. 539 * 540 * @since Apache PDFBox 1.1.0 541 * @see <a href="https://issues.apache.org/jira/browse/PDFBOX-636">PDFBOX-636</a> 542 * @param decodedStreamLength the decoded stream length 543 */ 544 public void setDecodedStreamLength(int decodedStreamLength) 545 { 546 this.stream.setInt(COSName.DL, decodedStreamLength); 547 } 548 549 }