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.pdfwriter; 18 19 import java.io.IOException; 20 import java.io.InputStream; 21 import java.io.OutputStream; 22 import java.security.MessageDigest; 23 import java.security.NoSuchAlgorithmException; 24 import java.text.DecimalFormat; 25 import java.text.NumberFormat; 26 import java.util.ArrayList; 27 import java.util.Collections; 28 import java.util.HashSet; 29 import java.util.Hashtable; 30 import java.util.Iterator; 31 import java.util.List; 32 import java.util.Locale; 33 import java.util.Map; 34 import java.util.Set; 35 36 import org.apache.pdfbox.cos.COSArray; 37 import org.apache.pdfbox.cos.COSBase; 38 import org.apache.pdfbox.cos.COSBoolean; 39 import org.apache.pdfbox.cos.COSDictionary; 40 import org.apache.pdfbox.cos.COSDocument; 41 import org.apache.pdfbox.cos.COSFloat; 42 import org.apache.pdfbox.cos.COSInteger; 43 import org.apache.pdfbox.cos.COSName; 44 import org.apache.pdfbox.cos.COSNull; 45 import org.apache.pdfbox.cos.COSObject; 46 import org.apache.pdfbox.cos.COSStream; 47 import org.apache.pdfbox.cos.COSString; 48 import org.apache.pdfbox.cos.ICOSVisitor; 49 import org.apache.pdfbox.exceptions.COSVisitorException; 50 import org.apache.pdfbox.exceptions.CryptographyException; 51 import org.apache.pdfbox.pdmodel.PDDocument; 52 import org.apache.pdfbox.pdmodel.encryption.SecurityHandler; 53 import org.apache.pdfbox.persistence.util.COSObjectKey; 54 55 /** 56 * this class acts on a in-memory representation of a pdf document. 57 * 58 * todo no support for incremental updates 59 * todo single xref section only 60 * todo no linearization 61 * 62 * @author Michael Traut 63 * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a> 64 * @version $Revision: 1.36 $ 65 */ 66 public class COSWriter implements ICOSVisitor 67 { 68 /** 69 * The dictionary open token. 70 */ 71 public static final byte[] DICT_OPEN = "<<".getBytes(); 72 /** 73 * The dictionary close token. 74 */ 75 public static final byte[] DICT_CLOSE = ">>".getBytes(); 76 /** 77 * space character. 78 */ 79 public static final byte[] SPACE = " ".getBytes(); 80 /** 81 * The start to a PDF comment. 82 */ 83 public static final byte[] COMMENT = "%".getBytes(); 84 85 /** 86 * The output version of the PDF. 87 */ 88 public static final byte[] VERSION = "PDF-1.4".getBytes(); 89 /** 90 * Garbage bytes used to create the PDF header. 91 */ 92 public static final byte[] GARBAGE = new byte[] {(byte)0xf6, (byte)0xe4, (byte)0xfc, (byte)0xdf}; 93 /** 94 * The EOF constant. 95 */ 96 public static final byte[] EOF = "%%EOF".getBytes(); 97 // pdf tokens 98 99 /** 100 * The reference token. 101 */ 102 public static final byte[] REFERENCE = "R".getBytes(); 103 /** 104 * The XREF token. 105 */ 106 public static final byte[] XREF = "xref".getBytes(); 107 /** 108 * The xref free token. 109 */ 110 public static final byte[] XREF_FREE = "f".getBytes(); 111 /** 112 * The xref used token. 113 */ 114 public static final byte[] XREF_USED = "n".getBytes(); 115 /** 116 * The trailer token. 117 */ 118 public static final byte[] TRAILER = "trailer".getBytes(); 119 /** 120 * The start xref token. 121 */ 122 public static final byte[] STARTXREF = "startxref".getBytes(); 123 /** 124 * The starting object token. 125 */ 126 public static final byte[] OBJ = "obj".getBytes(); 127 /** 128 * The end object token. 129 */ 130 public static final byte[] ENDOBJ = "endobj".getBytes(); 131 /** 132 * The array open token. 133 */ 134 public static final byte[] ARRAY_OPEN = "[".getBytes(); 135 /** 136 * The array close token. 137 */ 138 public static final byte[] ARRAY_CLOSE = "]".getBytes(); 139 /** 140 * The open stream token. 141 */ 142 public static final byte[] STREAM = "stream".getBytes(); 143 /** 144 * The close stream token. 145 */ 146 public static final byte[] ENDSTREAM = "endstream".getBytes(); 147 148 private NumberFormat formatXrefOffset = new DecimalFormat("0000000000"); 149 /** 150 * The decimal format for the xref object generation number data. 151 */ 152 private NumberFormat formatXrefGeneration = new DecimalFormat("00000"); 153 154 private NumberFormat formatDecimal = NumberFormat.getNumberInstance( Locale.US ); 155 156 // the stream where we create the pdf output 157 private OutputStream output; 158 // the stream used to write standard cos data 159 private COSStandardOutputStream standardOutput; 160 161 // the start position of the x ref section 162 private long startxref = 0; 163 164 // the current object number 165 private long number = 0; 166 167 // maps the object to the keys generated in the writer 168 // these are used for indirect refrences in other objects 169 //A hashtable is used on purpose over a hashmap 170 //so that null entries will not get added. 171 private Map objectKeys = new Hashtable(); 172 173 // the list of x ref entries to be made so far 174 private List xRefEntries = new ArrayList(); 175 176 //A list of objects to write. 177 private List objectsToWrite = new ArrayList(); 178 179 //a list of objects already written 180 private Set writtenObjects = new HashSet(); 181 //An 'actual' is any COSBase that is not a COSObject. 182 //need to keep a list of the actuals that are added 183 //as well as the objects because there is a problem 184 //when adding a COSObject and then later adding 185 //the actual for that object, so we will track 186 //actuals separately. 187 private Set actualsAdded = new HashSet(); 188 189 private COSObjectKey currentObjectKey = null; 190 191 private PDDocument document = null; 192 193 private boolean willEncrypt = false; 194 195 /** 196 * COSWriter constructor comment. 197 * 198 * @param os The wrapped output stream. 199 */ 200 public COSWriter(OutputStream os) 201 { 202 super(); 203 setOutput(os); 204 setStandardOutput(new COSStandardOutputStream(getOutput())); 205 formatDecimal.setMaximumFractionDigits( 10 ); 206 formatDecimal.setGroupingUsed( false ); 207 } 208 /** 209 * add an entry in the x ref table for later dump. 210 * 211 * @param entry The new entry to add. 212 */ 213 protected void addXRefEntry(COSWriterXRefEntry entry) 214 { 215 getXRefEntries().add(entry); 216 } 217 218 /** 219 * This will close the stream. 220 * 221 * @throws IOException If the underlying stream throws an exception. 222 */ 223 public void close() throws IOException 224 { 225 if (getStandardOutput() != null) 226 { 227 getStandardOutput().close(); 228 } 229 if (getOutput() != null) 230 { 231 getOutput().close(); 232 } 233 } 234 235 /** 236 * This will get the current object number. 237 * 238 * @return The current object number. 239 */ 240 protected long getNumber() 241 { 242 return number; 243 } 244 245 /** 246 * This will get all available object keys. 247 * 248 * @return A map of all object keys. 249 */ 250 public java.util.Map getObjectKeys() 251 { 252 return objectKeys; 253 } 254 255 /** 256 * This will get the output stream. 257 * 258 * @return The output stream. 259 */ 260 protected java.io.OutputStream getOutput() 261 { 262 return output; 263 } 264 265 /** 266 * This will get the standard output stream. 267 * 268 * @return The standard output stream. 269 */ 270 protected COSStandardOutputStream getStandardOutput() 271 { 272 return standardOutput; 273 } 274 275 /** 276 * This will get the current start xref. 277 * 278 * @return The current start xref. 279 */ 280 protected long getStartxref() 281 { 282 return startxref; 283 } 284 /** 285 * This will get the xref entries. 286 * 287 * @return All available xref entries. 288 */ 289 protected java.util.List getXRefEntries() 290 { 291 return xRefEntries; 292 } 293 294 /** 295 * This will set the current object number. 296 * 297 * @param newNumber The new object number. 298 */ 299 protected void setNumber(long newNumber) 300 { 301 number = newNumber; 302 } 303 304 /** 305 * This will set the output stream. 306 * 307 * @param newOutput The new output stream. 308 */ 309 private void setOutput( OutputStream newOutput ) 310 { 311 output = newOutput; 312 } 313 314 /** 315 * This will set the standard output stream. 316 * 317 * @param newStandardOutput The new standard output stream. 318 */ 319 private void setStandardOutput(COSStandardOutputStream newStandardOutput) 320 { 321 standardOutput = newStandardOutput; 322 } 323 324 /** 325 * This will set the start xref. 326 * 327 * @param newStartxref The new start xref attribute. 328 */ 329 protected void setStartxref(long newStartxref) 330 { 331 startxref = newStartxref; 332 } 333 334 /** 335 * This will write the body of the document. 336 * 337 * @param doc The document to write the body for. 338 * 339 * @throws IOException If there is an error writing the data. 340 * @throws COSVisitorException If there is an error generating the data. 341 */ 342 protected void doWriteBody(COSDocument doc) throws IOException, COSVisitorException 343 { 344 COSDictionary trailer = doc.getTrailer(); 345 COSDictionary root = (COSDictionary)trailer.getDictionaryObject( COSName.ROOT ); 346 COSDictionary info = (COSDictionary)trailer.getDictionaryObject( COSName.INFO ); 347 COSDictionary encrypt = (COSDictionary)trailer.getDictionaryObject( COSName.ENCRYPT ); 348 if( root != null ) 349 { 350 addObjectToWrite( root ); 351 } 352 if( info != null ) 353 { 354 addObjectToWrite( info ); 355 } 356 357 358 while( objectsToWrite.size() > 0 ) 359 { 360 COSBase nextObject = (COSBase)objectsToWrite.remove( 0 ); 361 doWriteObject( nextObject ); 362 } 363 364 365 willEncrypt = false; 366 367 if( encrypt != null ) 368 { 369 addObjectToWrite( encrypt ); 370 } 371 372 while( objectsToWrite.size() > 0 ) 373 { 374 COSBase nextObject = (COSBase)objectsToWrite.remove( 0 ); 375 doWriteObject( nextObject ); 376 } 377 378 // write all objects 379 /** 380 for (Iterator i = doc.getObjects().iterator(); i.hasNext();) 381 { 382 COSObject obj = (COSObject) i.next(); 383 doWriteObject(obj); 384 }**/ 385 } 386 387 private void addObjectToWrite( COSBase object ) 388 { 389 COSBase actual = object; 390 if( actual instanceof COSObject ) 391 { 392 actual = ((COSObject)actual).getObject(); 393 } 394 395 if( !writtenObjects.contains( object ) && 396 !objectsToWrite.contains( object ) && 397 !actualsAdded.contains( actual ) ) 398 { 399 objectsToWrite.add( object ); 400 if( actual != null ) 401 { 402 actualsAdded.add( actual ); 403 } 404 } 405 } 406 407 /** 408 * This will write a COS object. 409 * 410 * @param obj The object to write. 411 * 412 * @throws COSVisitorException If there is an error visiting objects. 413 */ 414 public void doWriteObject( COSBase obj ) throws COSVisitorException 415 { 416 try 417 { 418 writtenObjects.add( obj ); 419 // find the physical reference 420 currentObjectKey = getObjectKey( obj ); 421 // add a x ref entry 422 addXRefEntry( new COSWriterXRefEntry(getStandardOutput().getPos(), obj, currentObjectKey)); 423 // write the object 424 getStandardOutput().write(String.valueOf(currentObjectKey.getNumber()).getBytes()); 425 getStandardOutput().write(SPACE); 426 getStandardOutput().write(String.valueOf(currentObjectKey.getGeneration()).getBytes()); 427 getStandardOutput().write(SPACE); 428 getStandardOutput().write(OBJ); 429 getStandardOutput().writeEOL(); 430 obj.accept( this ); 431 getStandardOutput().writeEOL(); 432 getStandardOutput().write(ENDOBJ); 433 getStandardOutput().writeEOL(); 434 } 435 catch (IOException e) 436 { 437 throw new COSVisitorException(e); 438 } 439 } 440 441 /** 442 * This will write the header to the PDF document. 443 * 444 * @param doc The document to get the data from. 445 * 446 * @throws IOException If there is an error writing to the stream. 447 */ 448 protected void doWriteHeader(COSDocument doc) throws IOException 449 { 450 getStandardOutput().write( doc.getHeaderString().getBytes() ); 451 getStandardOutput().writeEOL(); 452 getStandardOutput().write(COMMENT); 453 getStandardOutput().write(GARBAGE); 454 getStandardOutput().writeEOL(); 455 } 456 457 458 /** 459 * This will write the trailer to the PDF document. 460 * 461 * @param doc The document to create the trailer for. 462 * 463 * @throws IOException If there is an IOError while writing the document. 464 * @throws COSVisitorException If there is an error while generating the data. 465 */ 466 protected void doWriteTrailer(COSDocument doc) throws IOException, COSVisitorException 467 { 468 getStandardOutput().write(TRAILER); 469 getStandardOutput().writeEOL(); 470 471 COSDictionary trailer = doc.getTrailer(); 472 //sort xref, needed only if object keys not regenerated 473 Collections.sort(getXRefEntries()); 474 COSWriterXRefEntry lastEntry = (COSWriterXRefEntry)getXRefEntries().get( getXRefEntries().size()-1); 475 trailer.setInt(COSName.SIZE, (int)lastEntry.getKey().getNumber()+1); 476 trailer.removeItem( COSName.PREV ); 477 /** 478 COSObject catalog = doc.getCatalog(); 479 if (catalog != null) 480 { 481 trailer.setItem(COSName.getPDFName("Root"), catalog); 482 } 483 */ 484 trailer.accept(this); 485 486 getStandardOutput().write(STARTXREF); 487 getStandardOutput().writeEOL(); 488 getStandardOutput().write(String.valueOf(getStartxref()).getBytes()); 489 getStandardOutput().writeEOL(); 490 getStandardOutput().write(EOF); 491 } 492 493 /** 494 * write the x ref section for the pdf file 495 * 496 * currently, the pdf is reconstructed from the scratch, so we write a single section 497 * 498 * todo support for incremental writing? 499 * 500 * @param doc The document to write the xref from. 501 * 502 * @throws IOException If there is an error writing the data to the stream. 503 */ 504 protected void doWriteXRef(COSDocument doc) throws IOException 505 { 506 String offset; 507 String generation; 508 509 // sort xref, needed only if object keys not regenerated 510 Collections.sort(getXRefEntries()); 511 COSWriterXRefEntry lastEntry = (COSWriterXRefEntry)getXRefEntries().get( getXRefEntries().size()-1 ); 512 513 // remember the position where x ref is written 514 setStartxref(getStandardOutput().getPos()); 515 // 516 getStandardOutput().write(XREF); 517 getStandardOutput().writeEOL(); 518 // write start object number and object count for this x ref section 519 // we assume starting from scratch 520 getStandardOutput().write(String.valueOf(0).getBytes()); 521 getStandardOutput().write(SPACE); 522 getStandardOutput().write(String.valueOf(lastEntry.getKey().getNumber() + 1).getBytes()); 523 getStandardOutput().writeEOL(); 524 // write initial start object with ref to first deleted object and magic generation number 525 offset = formatXrefOffset.format(0); 526 generation = formatXrefGeneration.format(65535); 527 getStandardOutput().write(offset.getBytes()); 528 getStandardOutput().write(SPACE); 529 getStandardOutput().write(generation.getBytes()); 530 getStandardOutput().write(SPACE); 531 getStandardOutput().write(XREF_FREE); 532 getStandardOutput().writeCRLF(); 533 // write entry for every object 534 long lastObjectNumber = 0; 535 for (Iterator i = getXRefEntries().iterator(); i.hasNext();) 536 { 537 COSWriterXRefEntry entry = (COSWriterXRefEntry) i.next(); 538 while( lastObjectNumber<entry.getKey().getNumber()-1 ) 539 { 540 offset = formatXrefOffset.format(0); 541 generation = formatXrefGeneration.format(65535); 542 getStandardOutput().write(offset.getBytes()); 543 getStandardOutput().write(SPACE); 544 getStandardOutput().write(generation.getBytes()); 545 getStandardOutput().write(SPACE); 546 getStandardOutput().write(XREF_FREE); 547 getStandardOutput().writeCRLF(); 548 lastObjectNumber++; 549 } 550 lastObjectNumber = entry.getKey().getNumber(); 551 offset = formatXrefOffset.format(entry.getOffset()); 552 generation = formatXrefGeneration.format(entry.getKey().getGeneration()); 553 getStandardOutput().write(offset.getBytes()); 554 getStandardOutput().write(SPACE); 555 getStandardOutput().write(generation.getBytes()); 556 getStandardOutput().write(SPACE); 557 getStandardOutput().write(entry.isFree() ? XREF_FREE : XREF_USED); 558 getStandardOutput().writeCRLF(); 559 } 560 } 561 562 /** 563 * This will get the object key for the object. 564 * 565 * @param obj The object to get the key for. 566 * 567 * @return The object key for the object. 568 */ 569 private COSObjectKey getObjectKey( COSBase obj ) 570 { 571 COSBase actual = obj; 572 if( actual instanceof COSObject ) 573 { 574 actual = ((COSObject)obj).getObject(); 575 } 576 COSObjectKey key = null; 577 if( actual != null ) 578 { 579 key = (COSObjectKey)objectKeys.get(actual); 580 } 581 if( key == null ) 582 { 583 key = (COSObjectKey)objectKeys.get(obj); 584 } 585 if (key == null) 586 { 587 setNumber(getNumber()+1); 588 key = new COSObjectKey(getNumber(),0); 589 objectKeys.put(obj, key); 590 if( actual != null ) 591 { 592 objectKeys.put(actual, key); 593 } 594 } 595 return key; 596 } 597 598 /** 599 * visitFromArray method comment. 600 * 601 * @param obj The object that is being visited. 602 * 603 * @throws COSVisitorException If there is an exception while visiting this object. 604 * 605 * @return null 606 */ 607 public Object visitFromArray( COSArray obj ) throws COSVisitorException 608 { 609 try 610 { 611 int count = 0; 612 getStandardOutput().write(ARRAY_OPEN); 613 for (Iterator i = obj.iterator(); i.hasNext();) 614 { 615 COSBase current = (COSBase) i.next(); 616 if( current instanceof COSDictionary ) 617 { 618 addObjectToWrite( current ); 619 writeReference( current ); 620 } 621 else if( current instanceof COSObject ) 622 { 623 COSBase subValue = ((COSObject)current).getObject(); 624 if( subValue instanceof COSDictionary || subValue == null ) 625 { 626 addObjectToWrite( current ); 627 writeReference( current ); 628 } 629 else 630 { 631 subValue.accept( this ); 632 } 633 } 634 else if( current == null ) 635 { 636 COSNull.NULL.accept( this ); 637 } 638 else 639 { 640 current.accept(this); 641 } 642 count++; 643 if (i.hasNext()) 644 { 645 if (count % 10 == 0) 646 { 647 getStandardOutput().writeEOL(); 648 } 649 else 650 { 651 getStandardOutput().write(SPACE); 652 } 653 } 654 } 655 getStandardOutput().write(ARRAY_CLOSE); 656 getStandardOutput().writeEOL(); 657 return null; 658 } 659 catch (IOException e) 660 { 661 throw new COSVisitorException(e); 662 } 663 } 664 665 /** 666 * visitFromBoolean method comment. 667 * 668 * @param obj The object that is being visited. 669 * 670 * @throws COSVisitorException If there is an exception while visiting this object. 671 * 672 * @return null 673 */ 674 public Object visitFromBoolean(COSBoolean obj) throws COSVisitorException 675 { 676 677 try 678 { 679 obj.writePDF( getStandardOutput() ); 680 return null; 681 } 682 catch (IOException e) 683 { 684 throw new COSVisitorException(e); 685 } 686 } 687 688 /** 689 * visitFromDictionary method comment. 690 * 691 * @param obj The object that is being visited. 692 * 693 * @throws COSVisitorException If there is an exception while visiting this object. 694 * 695 * @return null 696 */ 697 public Object visitFromDictionary(COSDictionary obj) throws COSVisitorException 698 { 699 try 700 { 701 getStandardOutput().write(DICT_OPEN); 702 getStandardOutput().writeEOL(); 703 for (Map.Entry<COSName, COSBase> entry : obj.entrySet()) 704 { 705 COSBase value = entry.getValue(); 706 if (value != null) 707 { 708 entry.getKey().accept(this); 709 getStandardOutput().write(SPACE); 710 if( value instanceof COSDictionary ) 711 { 712 addObjectToWrite( value ); 713 writeReference( value ); 714 } 715 else if( value instanceof COSObject ) 716 { 717 COSBase subValue = ((COSObject)value).getObject(); 718 if( subValue instanceof COSDictionary || subValue == null ) 719 { 720 addObjectToWrite( value ); 721 writeReference( value ); 722 } 723 else 724 { 725 subValue.accept( this ); 726 } 727 } 728 else 729 { 730 value.accept(this); 731 } 732 getStandardOutput().writeEOL(); 733 734 } 735 else 736 { 737 //then we won't write anything, there are a couple cases 738 //were the value of an entry in the COSDictionary will 739 //be a dangling reference that points to nothing 740 //so we will just not write out the entry if that is the case 741 } 742 } 743 getStandardOutput().write(DICT_CLOSE); 744 getStandardOutput().writeEOL(); 745 return null; 746 } 747 catch( IOException e ) 748 { 749 throw new COSVisitorException(e); 750 } 751 } 752 753 /** 754 * The visit from document method. 755 * 756 * @param doc The object that is being visited. 757 * 758 * @throws COSVisitorException If there is an exception while visiting this object. 759 * 760 * @return null 761 */ 762 public Object visitFromDocument(COSDocument doc) throws COSVisitorException 763 { 764 try 765 { 766 doWriteHeader(doc); 767 doWriteBody(doc); 768 doWriteXRef(doc); 769 doWriteTrailer(doc); 770 return null; 771 } 772 catch (IOException e) 773 { 774 throw new COSVisitorException(e); 775 } 776 } 777 778 /** 779 * visitFromFloat method comment. 780 * 781 * @param obj The object that is being visited. 782 * 783 * @throws COSVisitorException If there is an exception while visiting this object. 784 * 785 * @return null 786 */ 787 public Object visitFromFloat(COSFloat obj) throws COSVisitorException 788 { 789 790 try 791 { 792 obj.writePDF( getStandardOutput() ); 793 return null; 794 } 795 catch (IOException e) 796 { 797 throw new COSVisitorException(e); 798 } 799 } 800 801 /** 802 * visitFromFloat method comment. 803 * 804 * @param obj The object that is being visited. 805 * 806 * @throws COSVisitorException If there is an exception while visiting this object. 807 * 808 * @return null 809 */ 810 public Object visitFromInt(COSInteger obj) throws COSVisitorException 811 { 812 try 813 { 814 obj.writePDF( getStandardOutput() ); 815 return null; 816 } 817 catch (IOException e) 818 { 819 throw new COSVisitorException(e); 820 } 821 } 822 823 /** 824 * visitFromName method comment. 825 * 826 * @param obj The object that is being visited. 827 * 828 * @throws COSVisitorException If there is an exception while visiting this object. 829 * 830 * @return null 831 */ 832 public Object visitFromName(COSName obj) throws COSVisitorException 833 { 834 try 835 { 836 obj.writePDF( getStandardOutput() ); 837 return null; 838 } 839 catch (IOException e) 840 { 841 throw new COSVisitorException(e); 842 } 843 } 844 845 /** 846 * visitFromNull method comment. 847 * 848 * @param obj The object that is being visited. 849 * 850 * @throws COSVisitorException If there is an exception while visiting this object. 851 * 852 * @return null 853 */ 854 public Object visitFromNull(COSNull obj) throws COSVisitorException 855 { 856 try 857 { 858 obj.writePDF( getStandardOutput() ); 859 return null; 860 } 861 catch (IOException e) 862 { 863 throw new COSVisitorException(e); 864 } 865 } 866 867 /** 868 * visitFromObjRef method comment. 869 * 870 * @param obj The object that is being visited. 871 * 872 * @throws COSVisitorException If there is an exception while visiting this object. 873 */ 874 public void writeReference(COSBase obj) throws COSVisitorException 875 { 876 try 877 { 878 COSObjectKey key = getObjectKey(obj); 879 getStandardOutput().write(String.valueOf(key.getNumber()).getBytes()); 880 getStandardOutput().write(SPACE); 881 getStandardOutput().write(String.valueOf(key.getGeneration()).getBytes()); 882 getStandardOutput().write(SPACE); 883 getStandardOutput().write(REFERENCE); 884 } 885 catch (IOException e) 886 { 887 throw new COSVisitorException(e); 888 } 889 } 890 891 /** 892 * visitFromStream method comment. 893 * 894 * @param obj The object that is being visited. 895 * 896 * @throws COSVisitorException If there is an exception while visiting this object. 897 * 898 * @return null 899 */ 900 public Object visitFromStream(COSStream obj) throws COSVisitorException 901 { 902 try 903 { 904 if(willEncrypt) 905 { 906 document.getSecurityHandler().decryptStream( 907 obj, 908 currentObjectKey.getNumber(), 909 currentObjectKey.getGeneration()); 910 } 911 912 InputStream input = obj.getFilteredStream(); 913 // set the length of the stream and write stream dictionary 914 COSObject lengthObject = new COSObject( null ); 915 916 obj.setItem(COSName.LENGTH, lengthObject); 917 //obj.accept(this); 918 // write the stream content 919 visitFromDictionary( obj ); 920 getStandardOutput().write(STREAM); 921 getStandardOutput().writeCRLF(); 922 byte[] buffer = new byte[1024]; 923 int amountRead = 0; 924 int totalAmountWritten = 0; 925 while( (amountRead = input.read(buffer,0,1024)) != -1 ) 926 { 927 getStandardOutput().write( buffer, 0, amountRead ); 928 totalAmountWritten += amountRead; 929 } 930 lengthObject.setObject( COSInteger.get( totalAmountWritten ) ); 931 getStandardOutput().writeCRLF(); 932 getStandardOutput().write(ENDSTREAM); 933 getStandardOutput().writeEOL(); 934 return null; 935 } 936 catch( Exception e ) 937 { 938 throw new COSVisitorException(e); 939 } 940 } 941 942 /** 943 * visitFromString method comment. 944 * 945 * @param obj The object that is being visited. 946 * 947 * @return null 948 * 949 * @throws COSVisitorException If there is an exception while visiting this object. 950 */ 951 public Object visitFromString(COSString obj) throws COSVisitorException 952 { 953 try 954 { 955 if(willEncrypt) 956 { 957 document.getSecurityHandler().decryptString( 958 obj, 959 currentObjectKey.getNumber(), 960 currentObjectKey.getGeneration()); 961 } 962 963 obj.writePDF( getStandardOutput() ); 964 } 965 catch (Exception e) 966 { 967 throw new COSVisitorException(e); 968 } 969 return null; 970 } 971 972 /** 973 * This will write the pdf document. 974 * 975 * @param doc The document to write. 976 * 977 * @throws COSVisitorException If an error occurs while generating the data. 978 */ 979 public void write(COSDocument doc) throws COSVisitorException 980 { 981 PDDocument pdDoc = new PDDocument( doc ); 982 write( pdDoc ); 983 } 984 985 /** 986 * This will write the pdf document. 987 * 988 * @param doc The document to write. 989 * 990 * @throws COSVisitorException If an error occurs while generating the data. 991 */ 992 public void write(PDDocument doc) throws COSVisitorException 993 { 994 document = doc; 995 996 // if the document says we should remove encryption, then we shouldn't encrypt 997 if(doc.isAllSecurityToBeRemoved()) 998 { 999 this.willEncrypt = false; 1000 // also need to get rid of the "Encrypt" in the trailer so readers 1001 // don't try to decrypt a document which is not encrypted 1002 COSDocument cosDoc = doc.getDocument(); 1003 COSDictionary trailer = cosDoc.getTrailer(); 1004 trailer.removeItem(COSName.ENCRYPT); 1005 } 1006 else 1007 { 1008 SecurityHandler securityHandler = document.getSecurityHandler(); 1009 if(securityHandler != null) 1010 { 1011 try 1012 { 1013 securityHandler.prepareDocumentForEncryption(document); 1014 this.willEncrypt = true; 1015 } 1016 catch(IOException e) 1017 { 1018 throw new COSVisitorException( e ); 1019 } 1020 catch(CryptographyException e) 1021 { 1022 throw new COSVisitorException( e ); 1023 } 1024 } 1025 else 1026 { 1027 this.willEncrypt = false; 1028 } 1029 } 1030 1031 COSDocument cosDoc = document.getDocument(); 1032 COSDictionary trailer = cosDoc.getTrailer(); 1033 COSArray idArray = (COSArray)trailer.getDictionaryObject( COSName.ID ); 1034 if( idArray == null ) 1035 { 1036 try 1037 { 1038 1039 //algothim says to use time/path/size/values in doc to generate 1040 //the id. We don't have path or size, so do the best we can 1041 MessageDigest md = MessageDigest.getInstance( "MD5" ); 1042 md.update( Long.toString( System.currentTimeMillis()).getBytes() ); 1043 COSDictionary info = (COSDictionary)trailer.getDictionaryObject( COSName.INFO ); 1044 if( info != null ) 1045 { 1046 Iterator values = info.getValues().iterator(); 1047 while( values.hasNext() ) 1048 { 1049 md.update( values.next().toString().getBytes() ); 1050 } 1051 } 1052 idArray = new COSArray(); 1053 COSString id = new COSString( md.digest() ); 1054 idArray.add( id ); 1055 idArray.add( id ); 1056 trailer.setItem( COSName.ID, idArray ); 1057 } 1058 catch( NoSuchAlgorithmException e ) 1059 { 1060 throw new COSVisitorException( e ); 1061 } 1062 } 1063 1064 /* 1065 List objects = doc.getObjects(); 1066 Iterator iter = objects.iterator(); 1067 long maxNumber = 0; 1068 while( iter.hasNext() ) 1069 { 1070 COSObject object = (COSObject)iter.next(); 1071 if( object.getObjectNumber() != null && 1072 object.getGenerationNumber() != null ) 1073 { 1074 COSObjectKey key = new COSObjectKey( object.getObjectNumber().longValue(), 1075 object.getGenerationNumber().longValue() ); 1076 objectKeys.put( object.getObject(), key ); 1077 objectKeys.put( object, key ); 1078 maxNumber = Math.max( key.getNumber(), maxNumber ); 1079 setNumber( maxNumber ); 1080 } 1081 }*/ 1082 cosDoc.accept(this); 1083 } 1084 }