1 31 package org.pdfbox.pdfwriter; 32 33 import java.io.IOException ; 34 import java.io.InputStream ; 35 import java.io.OutputStream ; 36 import java.security.MessageDigest ; 37 import java.security.NoSuchAlgorithmException ; 38 import java.text.DecimalFormat ; 39 import java.text.NumberFormat ; 40 import java.util.ArrayList ; 41 import java.util.Collections ; 42 import java.util.HashSet ; 43 import java.util.Hashtable ; 44 import java.util.Iterator ; 45 import java.util.List ; 46 import java.util.Locale ; 47 import java.util.Map ; 48 import java.util.Set ; 49 50 import org.pdfbox.cos.COSArray; 51 import org.pdfbox.cos.COSBase; 52 import org.pdfbox.cos.COSBoolean; 53 import org.pdfbox.cos.COSDictionary; 54 import org.pdfbox.cos.COSDocument; 55 import org.pdfbox.cos.COSFloat; 56 import org.pdfbox.cos.COSInteger; 57 import org.pdfbox.cos.COSName; 58 import org.pdfbox.cos.COSNull; 59 import org.pdfbox.cos.COSObject; 60 import org.pdfbox.cos.COSStream; 61 import org.pdfbox.cos.COSString; 62 import org.pdfbox.cos.ICOSVisitor; 63 import org.pdfbox.exceptions.COSVisitorException; 64 import org.pdfbox.exceptions.CryptographyException; 65 import org.pdfbox.pdmodel.PDDocument; 66 import org.pdfbox.pdmodel.encryption.SecurityHandler; 67 import org.pdfbox.persistence.util.COSObjectKey; 68 69 80 public class COSWriter implements ICOSVisitor 81 { 82 85 public static final byte[] DICT_OPEN = "<<".getBytes(); 86 89 public static final byte[] DICT_CLOSE = ">>".getBytes(); 90 93 public static final byte[] SPACE = " ".getBytes(); 94 97 public static final byte[] COMMENT = "%".getBytes(); 98 99 102 public static final byte[] VERSION = "PDF-1.4".getBytes(); 103 106 public static final byte[] GARBAGE = new byte[] {(byte)0xf6, (byte)0xe4, (byte)0xfc, (byte)0xdf}; 107 110 public static final byte[] EOF = "%%EOF".getBytes(); 111 113 116 public static final byte[] REFERENCE = "R".getBytes(); 117 120 public static final byte[] XREF = "xref".getBytes(); 121 124 public static final byte[] XREF_FREE = "f".getBytes(); 125 128 public static final byte[] XREF_USED = "n".getBytes(); 129 132 public static final byte[] TRAILER = "trailer".getBytes(); 133 136 public static final byte[] STARTXREF = "startxref".getBytes(); 137 140 public static final byte[] OBJ = "obj".getBytes(); 141 144 public static final byte[] ENDOBJ = "endobj".getBytes(); 145 148 public static final byte[] ARRAY_OPEN = "[".getBytes(); 149 152 public static final byte[] ARRAY_CLOSE = "]".getBytes(); 153 156 public static final byte[] STREAM = "stream".getBytes(); 157 160 public static final byte[] ENDSTREAM = "endstream".getBytes(); 161 162 private NumberFormat formatXrefOffset = new DecimalFormat ("0000000000"); 163 166 private NumberFormat formatXrefGeneration = new DecimalFormat ("00000"); 167 168 private NumberFormat formatDecimal = NumberFormat.getNumberInstance( Locale.US ); 169 170 private OutputStream output; 172 private COSStandardOutputStream standardOutput; 174 175 private long startxref = 0; 177 178 private long number = 0; 180 181 private Map objectKeys = new Hashtable (); 186 187 private List xRefEntries = new ArrayList (); 189 190 private List objectsToWrite = new ArrayList (); 192 193 private Set writtenObjects = new HashSet (); 195 private Set actualsAdded = new HashSet (); 202 203 private COSObjectKey currentObjectKey = null; 204 205 private PDDocument document = null; 206 207 private boolean willEncrypt = false; 208 209 214 public COSWriter(OutputStream os) 215 { 216 super(); 217 setOutput(os); 218 setStandardOutput(new COSStandardOutputStream(getOutput())); 219 formatDecimal.setMaximumFractionDigits( 10 ); 220 formatDecimal.setGroupingUsed( false ); 221 } 222 227 protected void addXRefEntry(COSWriterXRefEntry entry) 228 { 229 getXRefEntries().add(entry); 230 } 231 232 237 public void close() throws IOException 238 { 239 if (getStandardOutput() != null) 240 { 241 getStandardOutput().close(); 242 } 243 if (getOutput() != null) 244 { 245 getOutput().close(); 246 } 247 } 248 249 254 protected long getNumber() 255 { 256 return number; 257 } 258 259 264 public java.util.Map getObjectKeys() 265 { 266 return objectKeys; 267 } 268 269 274 protected java.io.OutputStream getOutput() 275 { 276 return output; 277 } 278 279 284 protected COSStandardOutputStream getStandardOutput() 285 { 286 return standardOutput; 287 } 288 289 294 protected long getStartxref() 295 { 296 return startxref; 297 } 298 303 protected java.util.List getXRefEntries() 304 { 305 return xRefEntries; 306 } 307 308 313 protected void setNumber(long newNumber) 314 { 315 number = newNumber; 316 } 317 318 323 private void setOutput( OutputStream newOutput ) 324 { 325 output = newOutput; 326 } 327 328 333 private void setStandardOutput(COSStandardOutputStream newStandardOutput) 334 { 335 standardOutput = newStandardOutput; 336 } 337 338 343 protected void setStartxref(long newStartxref) 344 { 345 startxref = newStartxref; 346 } 347 348 356 protected void doWriteBody(COSDocument doc) throws IOException , COSVisitorException 357 { 358 COSDictionary trailer = doc.getTrailer(); 359 COSDictionary root = (COSDictionary)trailer.getDictionaryObject( COSName.ROOT ); 360 COSDictionary info = (COSDictionary)trailer.getDictionaryObject( COSName.getPDFName( "Info" ) ); 361 COSDictionary encrypt = (COSDictionary)trailer.getDictionaryObject( COSName.getPDFName( "Encrypt" ) ); 362 if( root != null ) 363 { 364 addObjectToWrite( root ); 365 } 366 if( info != null ) 367 { 368 addObjectToWrite( info ); 369 } 370 371 372 while( objectsToWrite.size() > 0 ) 373 { 374 COSBase nextObject = (COSBase)objectsToWrite.remove( 0 ); 375 doWriteObject( nextObject ); 376 } 377 378 379 willEncrypt = false; 380 381 if( encrypt != null ) 382 { 383 addObjectToWrite( encrypt ); 384 } 385 386 while( objectsToWrite.size() > 0 ) 387 { 388 COSBase nextObject = (COSBase)objectsToWrite.remove( 0 ); 389 doWriteObject( nextObject ); 390 } 391 392 399 } 400 401 private void addObjectToWrite( COSBase object ) 402 { 403 COSBase actual = object; 404 if( actual instanceof COSObject ) 405 { 406 actual = ((COSObject)actual).getObject(); 407 } 408 409 if( !writtenObjects.contains( object ) && 410 !objectsToWrite.contains( object ) && 411 !actualsAdded.contains( actual ) ) 412 { 413 objectsToWrite.add( object ); 414 if( actual != null ) 415 { 416 actualsAdded.add( actual ); 417 } 418 } 419 } 420 421 428 public void doWriteObject( COSBase obj ) throws COSVisitorException 429 { 430 try 431 { 432 writtenObjects.add( obj ); 433 currentObjectKey = getObjectKey( obj ); 435 addXRefEntry( new COSWriterXRefEntry(getStandardOutput().getPos(), obj, currentObjectKey)); 437 getStandardOutput().write(String.valueOf(currentObjectKey.getNumber()).getBytes()); 439 getStandardOutput().write(SPACE); 440 getStandardOutput().write(String.valueOf(currentObjectKey.getGeneration()).getBytes()); 441 getStandardOutput().write(SPACE); 442 getStandardOutput().write(OBJ); 443 getStandardOutput().writeEOL(); 444 obj.accept( this ); 445 getStandardOutput().writeEOL(); 446 getStandardOutput().write(ENDOBJ); 447 getStandardOutput().writeEOL(); 448 } 449 catch (IOException e) 450 { 451 throw new COSVisitorException(e); 452 } 453 } 454 455 462 protected void doWriteHeader(COSDocument doc) throws IOException 463 { 464 getStandardOutput().write( doc.getHeaderString().getBytes() ); 465 getStandardOutput().writeEOL(); 466 getStandardOutput().write(COMMENT); 467 getStandardOutput().write(GARBAGE); 468 getStandardOutput().writeEOL(); 469 } 470 471 472 480 protected void doWriteTrailer(COSDocument doc) throws IOException , COSVisitorException 481 { 482 getStandardOutput().write(TRAILER); 483 getStandardOutput().writeEOL(); 484 485 COSDictionary trailer = doc.getTrailer(); 486 Collections.sort(getXRefEntries()); 488 COSWriterXRefEntry lastEntry = (COSWriterXRefEntry)getXRefEntries().get( getXRefEntries().size()-1); 489 trailer.setInt(COSName.getPDFName("Size"), (int)lastEntry.getKey().getNumber()+1); 490 trailer.removeItem( COSName.PREV ); 491 498 trailer.accept(this); 499 500 getStandardOutput().write(STARTXREF); 501 getStandardOutput().writeEOL(); 502 getStandardOutput().write(String.valueOf(getStartxref()).getBytes()); 503 getStandardOutput().writeEOL(); 504 getStandardOutput().write(EOF); 505 } 506 507 518 protected void doWriteXRef(COSDocument doc) throws IOException 519 { 520 String offset; 521 String generation; 522 523 Collections.sort(getXRefEntries()); 525 COSWriterXRefEntry lastEntry = (COSWriterXRefEntry)getXRefEntries().get( getXRefEntries().size()-1 ); 526 527 setStartxref(getStandardOutput().getPos()); 529 getStandardOutput().write(XREF); 531 getStandardOutput().writeEOL(); 532 getStandardOutput().write(String.valueOf(0).getBytes()); 535 getStandardOutput().write(SPACE); 536 getStandardOutput().write(String.valueOf(lastEntry.getKey().getNumber() + 1).getBytes()); 537 getStandardOutput().writeEOL(); 538 offset = formatXrefOffset.format(0); 540 generation = formatXrefGeneration.format(65535); 541 getStandardOutput().write(offset.getBytes()); 542 getStandardOutput().write(SPACE); 543 getStandardOutput().write(generation.getBytes()); 544 getStandardOutput().write(SPACE); 545 getStandardOutput().write(XREF_FREE); 546 getStandardOutput().writeCRLF(); 547 long lastObjectNumber = 0; 549 for (Iterator i = getXRefEntries().iterator(); i.hasNext();) 550 { 551 COSWriterXRefEntry entry = (COSWriterXRefEntry) i.next(); 552 while( lastObjectNumber<entry.getKey().getNumber()-1 ) 553 { 554 offset = formatXrefOffset.format(0); 555 generation = formatXrefGeneration.format(65535); 556 getStandardOutput().write(offset.getBytes()); 557 getStandardOutput().write(SPACE); 558 getStandardOutput().write(generation.getBytes()); 559 getStandardOutput().write(SPACE); 560 getStandardOutput().write(XREF_FREE); 561 getStandardOutput().writeCRLF(); 562 lastObjectNumber++; 563 } 564 lastObjectNumber = entry.getKey().getNumber(); 565 offset = formatXrefOffset.format(entry.getOffset()); 566 generation = formatXrefGeneration.format(entry.getKey().getGeneration()); 567 getStandardOutput().write(offset.getBytes()); 568 getStandardOutput().write(SPACE); 569 getStandardOutput().write(generation.getBytes()); 570 getStandardOutput().write(SPACE); 571 getStandardOutput().write(entry.isFree() ? XREF_FREE : XREF_USED); 572 getStandardOutput().writeCRLF(); 573 } 574 } 575 576 583 private COSObjectKey getObjectKey( COSBase obj ) 584 { 585 COSBase actual = obj; 586 if( actual instanceof COSObject ) 587 { 588 actual = ((COSObject)obj).getObject(); 589 } 590 COSObjectKey key = null; 591 if( actual != null ) 592 { 593 key = (COSObjectKey)objectKeys.get(actual); 594 } 595 if( key == null ) 596 { 597 key = (COSObjectKey)objectKeys.get(obj); 598 } 599 if (key == null) 600 { 601 setNumber(getNumber()+1); 602 key = new COSObjectKey(getNumber(),0); 603 objectKeys.put(obj, key); 604 if( actual != null ) 605 { 606 objectKeys.put(actual, key); 607 } 608 } 609 return key; 610 } 611 612 621 public Object visitFromArray( COSArray obj ) throws COSVisitorException 622 { 623 try 624 { 625 int count = 0; 626 getStandardOutput().write(ARRAY_OPEN); 627 for (Iterator i = obj.iterator(); i.hasNext();) 628 { 629 COSBase current = (COSBase) i.next(); 630 if( current instanceof COSDictionary ) 631 { 632 addObjectToWrite( current ); 633 writeReference( current ); 634 } 635 else if( current instanceof COSObject ) 636 { 637 COSBase subValue = ((COSObject)current).getObject(); 638 if( subValue instanceof COSDictionary || subValue == null ) 639 { 640 addObjectToWrite( current ); 641 writeReference( current ); 642 } 643 else 644 { 645 subValue.accept( this ); 646 } 647 } 648 else if( current == null ) 649 { 650 COSNull.NULL.accept( this ); 651 } 652 else 653 { 654 current.accept(this); 655 } 656 count++; 657 if (i.hasNext()) 658 { 659 if (count % 10 == 0) 660 { 661 getStandardOutput().writeEOL(); 662 } 663 else 664 { 665 getStandardOutput().write(SPACE); 666 } 667 } 668 } 669 getStandardOutput().write(ARRAY_CLOSE); 670 getStandardOutput().writeEOL(); 671 return null; 672 } 673 catch (IOException e) 674 { 675 throw new COSVisitorException(e); 676 } 677 } 678 679 688 public Object visitFromBoolean(COSBoolean obj) throws COSVisitorException 689 { 690 691 try 692 { 693 obj.writePDF( getStandardOutput() ); 694 return null; 695 } 696 catch (IOException e) 697 { 698 throw new COSVisitorException(e); 699 } 700 } 701 702 711 public Object visitFromDictionary(COSDictionary obj) throws COSVisitorException 712 { 713 try 714 { 715 getStandardOutput().write(DICT_OPEN); 716 getStandardOutput().writeEOL(); 717 for (Iterator i = obj.keyList().iterator(); i.hasNext();) 718 { 719 COSName name = (COSName) i.next(); 720 COSBase value = obj.getItem(name); 721 if (value != null) 722 { 723 name.accept(this); 724 getStandardOutput().write(SPACE); 725 if( value instanceof COSDictionary ) 726 { 727 addObjectToWrite( value ); 728 writeReference( value ); 729 } 730 else if( value instanceof COSObject ) 731 { 732 COSBase subValue = ((COSObject)value).getObject(); 733 if( subValue instanceof COSDictionary || subValue == null ) 734 { 735 addObjectToWrite( value ); 736 writeReference( value ); 737 } 738 else 739 { 740 subValue.accept( this ); 741 } 742 } 743 else 744 { 745 value.accept(this); 746 } 747 getStandardOutput().writeEOL(); 748 749 } 750 else 751 { 752 } 757 } 758 getStandardOutput().write(DICT_CLOSE); 759 getStandardOutput().writeEOL(); 760 return null; 761 } 762 catch( IOException e ) 763 { 764 throw new COSVisitorException(e); 765 } 766 } 767 768 777 public Object visitFromDocument(COSDocument doc) throws COSVisitorException 778 { 779 try 780 { 781 doWriteHeader(doc); 782 doWriteBody(doc); 783 doWriteXRef(doc); 784 doWriteTrailer(doc); 785 return null; 786 } 787 catch (IOException e) 788 { 789 throw new COSVisitorException(e); 790 } 791 } 792 793 802 public Object visitFromFloat(COSFloat obj) throws COSVisitorException 803 { 804 805 try 806 { 807 obj.writePDF( getStandardOutput() ); 808 return null; 809 } 810 catch (IOException e) 811 { 812 throw new COSVisitorException(e); 813 } 814 } 815 816 825 public Object visitFromInt(COSInteger obj) throws COSVisitorException 826 { 827 try 828 { 829 obj.writePDF( getStandardOutput() ); 830 return null; 831 } 832 catch (IOException e) 833 { 834 throw new COSVisitorException(e); 835 } 836 } 837 838 847 public Object visitFromName(COSName obj) throws COSVisitorException 848 { 849 try 850 { 851 obj.writePDF( getStandardOutput() ); 852 return null; 853 } 854 catch (IOException e) 855 { 856 throw new COSVisitorException(e); 857 } 858 } 859 860 869 public Object visitFromNull(COSNull obj) throws COSVisitorException 870 { 871 try 872 { 873 obj.writePDF( getStandardOutput() ); 874 return null; 875 } 876 catch (IOException e) 877 { 878 throw new COSVisitorException(e); 879 } 880 } 881 882 889 public void writeReference(COSBase obj) throws COSVisitorException 890 { 891 try 892 { 893 COSObjectKey key = getObjectKey(obj); 894 getStandardOutput().write(String.valueOf(key.getNumber()).getBytes()); 895 getStandardOutput().write(SPACE); 896 getStandardOutput().write(String.valueOf(key.getGeneration()).getBytes()); 897 getStandardOutput().write(SPACE); 898 getStandardOutput().write(REFERENCE); 899 } 900 catch (IOException e) 901 { 902 throw new COSVisitorException(e); 903 } 904 } 905 906 915 public Object visitFromStream(COSStream obj) throws COSVisitorException 916 { 917 try 918 { 919 if(willEncrypt) 920 { 921 document.getSecurityHandler().decryptStream( 922 obj, 923 currentObjectKey.getNumber(), 924 currentObjectKey.getGeneration()); 925 } 926 927 InputStream input = obj.getFilteredStream(); 928 COSObject lengthObject = new COSObject( null ); 930 931 obj.setItem(COSName.LENGTH, lengthObject); 932 visitFromDictionary( obj ); 935 getStandardOutput().write(STREAM); 936 getStandardOutput().writeCRLF(); 937 byte[] buffer = new byte[1024]; 938 int amountRead = 0; 939 int totalAmountWritten = 0; 940 while( (amountRead = input.read(buffer,0,1024)) != -1 ) 941 { 942 getStandardOutput().write( buffer, 0, amountRead ); 943 totalAmountWritten += amountRead; 944 } 945 lengthObject.setObject( new COSInteger( totalAmountWritten ) ); 946 getStandardOutput().writeCRLF(); 947 getStandardOutput().write(ENDSTREAM); 948 getStandardOutput().writeEOL(); 949 return null; 950 } 951 catch( Exception e ) 952 { 953 throw new COSVisitorException(e); 954 } 955 } 956 957 966 public Object visitFromString(COSString obj) throws COSVisitorException 967 { 968 try 969 { 970 if(willEncrypt) 971 { 972 document.getSecurityHandler().decryptString( 973 obj, 974 currentObjectKey.getNumber(), 975 currentObjectKey.getGeneration()); 976 } 977 978 obj.writePDF( getStandardOutput() ); 979 } 980 catch (Exception e) 981 { 982 throw new COSVisitorException(e); 983 } 984 return null; 985 } 986 987 994 public void write(COSDocument doc) throws COSVisitorException 995 { 996 PDDocument pdDoc = new PDDocument( doc ); 997 write( pdDoc ); 998 } 999 1000 1007 public void write(PDDocument doc) throws COSVisitorException 1008 { 1009 document = doc; 1010 1011 SecurityHandler securityHandler = document.getSecurityHandler(); 1012 if(securityHandler != null) 1013 { 1014 try 1015 { 1016 securityHandler.prepareDocumentForEncryption(document); 1017 this.willEncrypt = true; 1018 } 1019 catch(IOException e) 1020 { 1021 throw new COSVisitorException( e ); 1022 } 1023 catch(CryptographyException e) 1024 { 1025 throw new COSVisitorException( e ); 1026 } 1027 } 1028 else 1029 { 1030 this.willEncrypt = false; 1031 } 1032 1033 COSDocument cosDoc = document.getDocument(); 1034 COSDictionary trailer = cosDoc.getTrailer(); 1035 COSArray idArray = (COSArray)trailer.getDictionaryObject( "ID" ); 1036 if( idArray == null ) 1037 { 1038 try 1039 { 1040 1041 MessageDigest md = MessageDigest.getInstance( "MD5" ); 1044 md.update( Long.toString( System.currentTimeMillis()).getBytes() ); 1045 COSDictionary info = (COSDictionary)trailer.getDictionaryObject( "Info" ); 1046 if( info != null ) 1047 { 1048 Iterator values = info.getValues().iterator(); 1049 while( values.hasNext() ) 1050 { 1051 md.update( values.next().toString().getBytes() ); 1052 } 1053 } 1054 idArray = new COSArray(); 1055 COSString id = new COSString( md.digest() ); 1056 idArray.add( id ); 1057 idArray.add( id ); 1058 trailer.setItem( "ID", idArray ); 1059 } 1060 catch( NoSuchAlgorithmException e ) 1061 { 1062 throw new COSVisitorException( e ); 1063 } 1064 } 1065 1066 1084 cosDoc.accept(this); 1085 } 1086} | Popular Tags |