/* * $Id: PDFOutput.java,v 1.3 2007/09/22 12:48:16 gil1 Exp $ * * $Date: 2007/09/22 12:48:16 $ * * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * */ package gnu.jpdf; import java.io.*; import java.util.*; /** * This class is used to write a PDF document. It acts as a wrapper * to a real OutputStream, but is necessary for certain internal PDF * structures to be built correctly. * * @author Peter T. Mount * @author Eric Z. Beard, ericzbeard@hotmail.com * @version $Revision: 1.3 $, $Date: 2007/09/22 12:48:16 $ */ public class PDFOutput { /** * This is the actual OutputStream used to write to. */ protected OutputStream os; /** * This is the OutputStream used to write each object to. * * <p>We use a separate stream, because we need to keep track of how * many bytes have been written for each object for the xref table to * work correctly. */ protected ByteArrayOutputStream baos; /** * This is the current position within the stream */ protected int offset; /** * This vector contains offsets of each object */ protected Vector<PDFXref> offsets; /** * This is used to track the /Root object (catalog) */ protected PDFObject rootID; /** * This is used to track the /Info object (info) */ protected PDFObject infoID; /** * This creates a PDF OutputStream * * @param os The output stream to write the PDF file to. * @throws IOException if there is an I/O error. */ public PDFOutput(OutputStream os) throws IOException { this.os = os; offset = 0; offsets = new Vector<PDFXref>(); baos = new ByteArrayOutputStream(); // Now write the PDF header // // Note: As the encoding is fixed here, we use getBytes(). // baos.write("%PDF-1.2\n".getBytes()); // This second comment is advised in the PDF Reference manual // page 61 baos.write("%\342\343\317\323\n".getBytes()); offset = baos.size(); baos.writeTo(os); } /** * This method writes a PDFObject to the stream. * * @param ob PDFObject Obeject to write * @exception IOException on error */ protected void write(PDFObject ob) throws IOException { // Check the object to see if it's one that is needed in the trailer // object if(ob instanceof PDFCatalog) rootID=ob; if(ob instanceof PDFInfo) infoID=ob; offsets.addElement(new PDFXref(ob.getSerialID(),offset)); baos.reset(); ob.write(baos); offset+=baos.size(); baos.writeTo(os); } /** * This closes the Stream, writing the xref table */ protected void close() throws IOException { // Make sure everything is written os.flush(); // we use baos to speed things up a little. // Also, offset is preserved, and marks the begining of this block. // This is required by PDF at the end of the PDF file. baos.reset(); baos.write("xref\n".getBytes()); // Now a single subsection for object 0 //baos.write("0 1\n0000000000 65535 f \n".getBytes()); // Now scan through the offsets list. The should be in sequence, // but just in case: int firstid = 0; // First id in block int lastid = -1; // The last id used Vector<PDFXref> block = new Vector<PDFXref>(); // xrefs in this block // We need block 0 to exist block.addElement(new PDFXref(0,0,65535)); for(PDFXref x : offsets) { if(firstid==-1) firstid=x.id; // check to see if block is in range (-1 means empty) if(lastid>-1 && x.id != (lastid+1)) { // no, so write this block, and reset writeblock(firstid,block); block.removeAllElements(); firstid=-1; } // now add to block block.addElement(x); lastid = x.id; } // now write the last block if(firstid>-1) writeblock(firstid,block); // now the trailer object baos.write("trailer\n<<\n".getBytes()); // the number of entries (REQUIRED) baos.write("/Size ".getBytes()); baos.write(Integer.toString(offsets.size()+1).getBytes()); baos.write("\n".getBytes()); // the /Root catalog indirect reference (REQUIRED) if(rootID != null) { baos.write("/Root ".getBytes()); baos.write(rootID.toString().getBytes()); baos.write("\n".getBytes()); } else throw new IOException("Root object is not present in document"); // the /Info reference (OPTIONAL) if(infoID != null) { baos.write("/Info ".getBytes()); baos.write(infoID.toString().getBytes()); baos.write("\n".getBytes()); } // end the trailer object baos.write(">>\nstartxref\n".getBytes()); baos.write(Integer.toString(offset).getBytes()); baos.write("\n%%EOF\n".getBytes()); // now flush the stream baos.writeTo(os); os.flush(); } /** * Writes a block of references to the PDF file * @param firstid ID of the first reference in this block * @param block Vector containing the references in this block * @exception IOException on write error */ protected void writeblock(int firstid,Vector<PDFXref> block) throws IOException { baos.write(Integer.toString(firstid).getBytes()); baos.write(" ".getBytes()); baos.write(Integer.toString(block.size()).getBytes()); baos.write("\n".getBytes()); //baos.write("\n0000000000 65535 f\n".getBytes()); for(PDFXref x : block) { baos.write(x.toString().getBytes()); baos.write("\n".getBytes()); } } } // end class PDFOutput