From 6025b6016517c6d898d8957d1d7e03ba71431912 Mon Sep 17 00:00:00 2001 From: tknall Date: Fri, 1 Dec 2006 12:20:24 +0000 Subject: Initial import of release 2.2. git-svn-id: https://joinup.ec.europa.eu/svn/pdf-as/trunk@4 7b5415b0-85f9-ee4d-85bd-d5d0c3b42d1c --- .../pdfbox/pdfwriter/COSStandardOutputStream.java | 180 ++++ src/main/java/org/pdfbox/pdfwriter/COSWriter.java | 1091 ++++++++++++++++++++ .../org/pdfbox/pdfwriter/COSWriterXRefEntry.java | 165 +++ .../org/pdfbox/pdfwriter/ContentStreamWriter.java | 200 ++++ src/main/java/org/pdfbox/pdfwriter/package.html | 9 + 5 files changed, 1645 insertions(+) create mode 100644 src/main/java/org/pdfbox/pdfwriter/COSStandardOutputStream.java create mode 100644 src/main/java/org/pdfbox/pdfwriter/COSWriter.java create mode 100644 src/main/java/org/pdfbox/pdfwriter/COSWriterXRefEntry.java create mode 100644 src/main/java/org/pdfbox/pdfwriter/ContentStreamWriter.java create mode 100644 src/main/java/org/pdfbox/pdfwriter/package.html (limited to 'src/main/java/org/pdfbox/pdfwriter') diff --git a/src/main/java/org/pdfbox/pdfwriter/COSStandardOutputStream.java b/src/main/java/org/pdfbox/pdfwriter/COSStandardOutputStream.java new file mode 100644 index 0000000..1881bf2 --- /dev/null +++ b/src/main/java/org/pdfbox/pdfwriter/COSStandardOutputStream.java @@ -0,0 +1,180 @@ +/** + * Copyright (c) 2003, www.pdfbox.org + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of pdfbox; nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * http://www.pdfbox.org + * + */ +package org.pdfbox.pdfwriter; + + + +import java.io.FilterOutputStream; +import java.io.IOException; +import java.io.OutputStream; + +/** + * simple output stream with some minor features for generating "pretty" + * pdf files. + * + * @author Michael Traut + * @version $Revision: 1.4 $ + */ +public class COSStandardOutputStream extends FilterOutputStream +{ + + /** + * To be used when 2 byte sequence is enforced. + */ + public static final byte[] CRLF = "\r\n".getBytes(); + + /** + * Line feed character. + */ + public static final byte[] LF = "\n".getBytes(); + + /** + * standard line separator on this platform. + */ + public static final byte[] EOL = System.getProperty("line.separator").getBytes(); + + // current byte pos in the output stream + private long pos = 0; + // flag to prevent generating two newlines in sequence + private boolean onNewLine = false; + + /** + * COSOutputStream constructor comment. + * + * @param out The underlying stream to write to. + */ + public COSStandardOutputStream(OutputStream out) + { + super(out); + } + /** + * This will get the current position in the stream. + * + * @return The current position in the stream. + */ + public long getPos() + { + return pos; + } + /** + * This will tell if we are on a newling. + * + * @return true If we are on a newline. + */ + public boolean isOnNewLine() + { + return onNewLine; + } + /** + * This will set a flag telling if we are on a newline. + * + * @param newOnNewLine The new value for the onNewLine attribute. + */ + public void setOnNewLine(boolean newOnNewLine) + { + onNewLine = newOnNewLine; + } + /** + * This will set the position in the stream. + * + * @param newPos The new position in the stream. + */ + private void setPos(int newPos) + { + pos = newPos; + } + + /** + * This will write some byte to the stream. + * + * @param b The source byte array. + * @param off The offset into the array to start writing. + * @param len The number of bytes to write. + * + * @throws IOException If the underlying stream throws an exception. + */ + public void write(byte[] b, int off, int len) throws IOException + { + setOnNewLine(false); + out.write(b, off, len); + pos += len; + } + + /** + * This will write a single byte to the stream. + * + * @param b The byte to write to the stream. + * + * @throws IOException If there is an error writing to the underlying stream. + */ + public void write(int b) throws IOException + { + setOnNewLine(false); + out.write(b); + pos++; + } + + /** + * This will write a CRLF to the stream. + * + * @throws IOException If there is an error writing the data to the stream. + */ + public void writeCRLF() throws IOException + { + write(CRLF); + // setOnNewLine(true); + } + + /** + * This will write an EOL to the stream. + * + * @throws IOException If there is an error writing to the stream + */ + public void writeEOL() throws IOException + { + if (!isOnNewLine()) + { + write(EOL); + setOnNewLine(true); + } + } + + /** + * This will write a Linefeed to the stream. + * + * @throws IOException If there is an error writing to the underlying stream. + */ + public void writeLF() throws IOException + { + write(LF); + // setOnNewLine(true); + } +} \ No newline at end of file diff --git a/src/main/java/org/pdfbox/pdfwriter/COSWriter.java b/src/main/java/org/pdfbox/pdfwriter/COSWriter.java new file mode 100644 index 0000000..fa9cf48 --- /dev/null +++ b/src/main/java/org/pdfbox/pdfwriter/COSWriter.java @@ -0,0 +1,1091 @@ +/** + * Copyright (c) 2003-2005, www.pdfbox.org + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of pdfbox; nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * http://www.pdfbox.org + * + */ +package org.pdfbox.pdfwriter; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.Hashtable; +import java.util.Iterator; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Set; + +import java.text.DecimalFormat; +import java.text.NumberFormat; + +import org.pdfbox.persistence.util.COSObjectKey; + +import org.pdfbox.cos.COSBase; +import org.pdfbox.cos.COSFloat; +import org.pdfbox.cos.ICOSVisitor; +import org.pdfbox.cos.COSName; +import org.pdfbox.cos.COSString; +import org.pdfbox.cos.COSBoolean; +import org.pdfbox.cos.COSArray; +import org.pdfbox.cos.COSDocument; +import org.pdfbox.cos.COSStream; +import org.pdfbox.cos.COSObject; +import org.pdfbox.encryption.DocumentEncryption; +import org.pdfbox.exceptions.COSVisitorException; +import org.pdfbox.exceptions.CryptographyException; +import org.pdfbox.cos.COSDictionary; +import org.pdfbox.cos.COSInteger; +import org.pdfbox.cos.COSNull; + +import org.pdfbox.pdmodel.PDDocument; + +import org.apache.log4j.Logger; + +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +/** + * this class acts on a in-memory representation of a pdf document. + * + * todo no support for incremental updates + * todo single xref section only + * todo no linearization + * + * @author Michael Traut + * @author Ben Litchfield (ben@benlitchfield.com) + * @version $Revision: 1.32 $ + */ +public class COSWriter implements ICOSVisitor +{ + + private static Logger log = Logger.getLogger( COSWriter.class ); + /** + * The dictionary open token. + */ + public static final byte[] DICT_OPEN = "<<".getBytes(); + /** + * The dictionary close token. + */ + public static final byte[] DICT_CLOSE = ">>".getBytes(); + /** + * space character. + */ + public static final byte[] SPACE = " ".getBytes(); + /** + * The start to a PDF comment. + */ + public static final byte[] COMMENT = "%".getBytes(); + + /** + * The output version of the PDF. + */ + public static final byte[] VERSION = "PDF-1.4".getBytes(); + /** + * Garbage bytes used to create the PDF header. + */ + public static final byte[] GARBAGE = new byte[] {(byte)0xf6, (byte)0xe4, (byte)0xfc, (byte)0xdf}; + /** + * The EOF constant. + */ + public static final byte[] EOF = "%%EOF".getBytes(); + // pdf tokens + + /** + * The reference token. + */ + public static final byte[] REFERENCE = "R".getBytes(); + /** + * The XREF token. + */ + public static final byte[] XREF = "xref".getBytes(); + /** + * The xref free token. + */ + public static final byte[] XREF_FREE = "f".getBytes(); + /** + * The xref used token. + */ + public static final byte[] XREF_USED = "n".getBytes(); + /** + * The trailer token. + */ + public static final byte[] TRAILER = "trailer".getBytes(); + /** + * The start xref token. + */ + public static final byte[] STARTXREF = "startxref".getBytes(); + /** + * The starting object token. + */ + public static final byte[] OBJ = "obj".getBytes(); + /** + * The end object token. + */ + public static final byte[] ENDOBJ = "endobj".getBytes(); + /** + * The array open token. + */ + public static final byte[] ARRAY_OPEN = "[".getBytes(); + /** + * The array close token. + */ + public static final byte[] ARRAY_CLOSE = "]".getBytes(); + /** + * The open stream token. + */ + public static final byte[] STREAM = "stream".getBytes(); + /** + * The close stream token. + */ + public static final byte[] ENDSTREAM = "endstream".getBytes(); + + private NumberFormat formatXrefOffset = new DecimalFormat("0000000000"); + /** + * The decimal format for the xref object generation number data. + */ + private NumberFormat formatXrefGeneration = new DecimalFormat("00000"); + + private NumberFormat formatDecimal = NumberFormat.getNumberInstance( Locale.US ); + + // the stream where we create the pdf output + private OutputStream output; + // the stream used to write standard cos data + private COSStandardOutputStream standardOutput; + + // the start position of the x ref section + private long startxref = 0; + + // the current object number + private long number = 0; + + // maps the object to the keys generated in the writer + // these are used for indirect refrences in other objects + //A hashtable is used on purpose over a hashmap + //so that null entries will not get added. + private Map objectKeys = new Hashtable(); + + // the list of x ref entries to be made so far + private List xRefEntries = new ArrayList(); + + //A list of objects to write. + private List objectsToWrite = new ArrayList(); + + //a list of objects already written + private Set writtenObjects = new HashSet(); + //An 'actual' is any COSBase that is not a COSObject. + //need to keep a list of the actuals that are added + //as well as the objects because there is a problem + //when adding a COSObject and then later adding + //the actual for that object, so we will track + //actuals separately. + private Set actualsAdded = new HashSet(); + + private COSObjectKey currentObjectKey = null; + private PDDocument document = null; + private DocumentEncryption enc = null; + + /** + * COSWriter constructor comment. + * + * @param os The wrapped output stream. + */ + public COSWriter(OutputStream os) + { + super(); + setOutput(os); + setStandardOutput(new COSStandardOutputStream(getOutput())); + formatDecimal.setMaximumFractionDigits( 10 ); + formatDecimal.setGroupingUsed( false ); + } + /** + * add an entry in the x ref table for later dump. + * + * @param entry The new entry to add. + */ + protected void addXRefEntry(COSWriterXRefEntry entry) + { + getXRefEntries().add(entry); + } + + /** + * This will close the stream. + * + * @throws IOException If the underlying stream throws an exception. + */ + public void close() throws IOException + { + if (getStandardOutput() != null) + { + getStandardOutput().close(); + } + if (getOutput() != null) + { + getOutput().close(); + } + } + + /** + * This will get the current object number. + * + * @return The current object number. + */ + protected long getNumber() + { + return number; + } + + /** + * This will get all available object keys. + * + * @return A map of all object keys. + */ + public java.util.Map getObjectKeys() + { + return objectKeys; + } + + /** + * This will get the output stream. + * + * @return The output stream. + */ + protected java.io.OutputStream getOutput() + { + return output; + } + + /** + * This will get the standard output stream. + * + * @return The standard output stream. + */ + protected COSStandardOutputStream getStandardOutput() + { + return standardOutput; + } + + /** + * This will get the current start xref. + * + * @return The current start xref. + */ + protected long getStartxref() + { + return startxref; + } + /** + * This will get the xref entries. + * + * @return All available xref entries. + */ + protected java.util.List getXRefEntries() + { + return xRefEntries; + } + + /** + * This will set the current object number. + * + * @param newNumber The new object number. + */ + protected void setNumber(long newNumber) + { + number = newNumber; + } + + /** + * This will set the output stream. + * + * @param newOutput The new output stream. + */ + private void setOutput( OutputStream newOutput ) + { + output = newOutput; + } + + /** + * This will set the standard output stream. + * + * @param newStandardOutput The new standard output stream. + */ + private void setStandardOutput(COSStandardOutputStream newStandardOutput) + { + standardOutput = newStandardOutput; + } + + /** + * This will set the start xref. + * + * @param newStartxref The new start xref attribute. + */ + protected void setStartxref(long newStartxref) + { + startxref = newStartxref; + } + + /** + * This will write the body of the document. + * + * @param doc The document to write the body for. + * + * @throws IOException If there is an error writing the data. + * @throws COSVisitorException If there is an error generating the data. + */ + protected void doWriteBody(COSDocument doc) throws IOException, COSVisitorException + { + COSDictionary trailer = doc.getTrailer(); + COSDictionary root = (COSDictionary)trailer.getDictionaryObject( COSName.ROOT ); + COSDictionary info = (COSDictionary)trailer.getDictionaryObject( COSName.getPDFName( "Info" ) ); + COSDictionary encrypt = (COSDictionary)trailer.getDictionaryObject( COSName.getPDFName( "Encrypt" ) ); + if( root != null ) + { + addObjectToWrite( root ); + } + if( info != null ) + { + addObjectToWrite( info ); + } + + + while( objectsToWrite.size() > 0 ) + { + COSBase nextObject = (COSBase)objectsToWrite.remove( 0 ); + doWriteObject( nextObject ); + } + + document.clearWillEncryptWhenSaving(); + if( encrypt != null ) + { + addObjectToWrite( encrypt ); + } + + while( objectsToWrite.size() > 0 ) + { + COSBase nextObject = (COSBase)objectsToWrite.remove( 0 ); + doWriteObject( nextObject ); + } + + // write all objects + /** + for (Iterator i = doc.getObjects().iterator(); i.hasNext();) + { + COSObject obj = (COSObject) i.next(); + doWriteObject(obj); + }**/ + } + + private void addObjectToWrite( COSBase object ) + { + COSBase actual = object; + if( actual instanceof COSObject ) + { + actual = ((COSObject)actual).getObject(); + } + + if( !writtenObjects.contains( object ) && + !objectsToWrite.contains( object ) && + !actualsAdded.contains( actual ) ) + { + objectsToWrite.add( object ); + if( actual != null ) + { + actualsAdded.add( actual ); + } + } + } + + /** + * This will write a COS object. + * + * @param obj The object to write. + * + * @throws COSVisitorException If there is an error visiting objects. + */ + public void doWriteObject( COSBase obj ) throws COSVisitorException + { + try + { + writtenObjects.add( obj ); + // find the physical reference + currentObjectKey = getObjectKey( obj ); + // add a x ref entry + addXRefEntry( new COSWriterXRefEntry(getStandardOutput().getPos(), obj, currentObjectKey)); + // write the object + getStandardOutput().write(String.valueOf(currentObjectKey.getNumber()).getBytes()); + getStandardOutput().write(SPACE); + getStandardOutput().write(String.valueOf(currentObjectKey.getGeneration()).getBytes()); + getStandardOutput().write(SPACE); + getStandardOutput().write(OBJ); + getStandardOutput().writeEOL(); + obj.accept( this ); + getStandardOutput().writeEOL(); + getStandardOutput().write(ENDOBJ); + getStandardOutput().writeEOL(); + } + catch (IOException e) + { + throw new COSVisitorException(e); + } + } + + /** + * This will write the header to the PDF document. + * + * @param doc The document to get the data from. + * + * @throws IOException If there is an error writing to the stream. + */ + protected void doWriteHeader(COSDocument doc) throws IOException + { + getStandardOutput().write( doc.getHeaderString().getBytes() ); + getStandardOutput().writeEOL(); + getStandardOutput().write(COMMENT); + getStandardOutput().write(GARBAGE); + getStandardOutput().writeEOL(); + } + + + /** + * This will write the trailer to the PDF document. + * + * @param doc The document to create the trailer for. + * + * @throws IOException If there is an IOError while writing the document. + * @throws COSVisitorException If there is an error while generating the data. + */ + protected void doWriteTrailer(COSDocument doc) throws IOException, COSVisitorException + { + getStandardOutput().write(TRAILER); + getStandardOutput().writeEOL(); + + COSDictionary trailer = doc.getTrailer(); + //sort xref, needed only if object keys not regenerated + Collections.sort(getXRefEntries()); + COSWriterXRefEntry lastEntry = (COSWriterXRefEntry)getXRefEntries().get( getXRefEntries().size()-1); + trailer.setInt(COSName.getPDFName("Size"), (int)lastEntry.getKey().getNumber()+1); + trailer.removeItem( COSName.PREV ); + /** + COSObject catalog = doc.getCatalog(); + if (catalog != null) + { + trailer.setItem(COSName.getPDFName("Root"), catalog); + } + */ + trailer.accept(this); + + getStandardOutput().write(STARTXREF); + getStandardOutput().writeEOL(); + getStandardOutput().write(String.valueOf(getStartxref()).getBytes()); + getStandardOutput().writeEOL(); + getStandardOutput().write(EOF); + } + + /** + * write the x ref section for the pdf file + * + * currently, the pdf is reconstructed from the scratch, so we write a single section + * + * todo support for incremental writing? + * + * @param doc The document to write the xref from. + * + * @throws IOException If there is an error writing the data to the stream. + */ + protected void doWriteXRef(COSDocument doc) throws IOException + { + String offset; + String generation; + + // sort xref, needed only if object keys not regenerated + Collections.sort(getXRefEntries()); + COSWriterXRefEntry lastEntry = (COSWriterXRefEntry)getXRefEntries().get( getXRefEntries().size()-1 ); + + // remember the position where x ref is written + setStartxref(getStandardOutput().getPos()); + // + getStandardOutput().write(XREF); + getStandardOutput().writeEOL(); + // write start object number and object count for this x ref section + // we assume starting from scratch + getStandardOutput().write(String.valueOf(0).getBytes()); + getStandardOutput().write(SPACE); + getStandardOutput().write(String.valueOf(lastEntry.getKey().getNumber() + 1).getBytes()); + getStandardOutput().writeEOL(); + // write initial start object with ref to first deleted object and magic generation number + offset = formatXrefOffset.format(0); + generation = formatXrefGeneration.format(65535); + getStandardOutput().write(offset.getBytes()); + getStandardOutput().write(SPACE); + getStandardOutput().write(generation.getBytes()); + getStandardOutput().write(SPACE); + getStandardOutput().write(XREF_FREE); + getStandardOutput().writeCRLF(); + // write entry for every object + long lastObjectNumber = 0; + for (Iterator i = getXRefEntries().iterator(); i.hasNext();) + { + COSWriterXRefEntry entry = (COSWriterXRefEntry) i.next(); + while( lastObjectNumber + + + + + +This is the persistence layer used to write the PDFBox documents to a stream. + + -- cgit v1.2.3