/*
* Copyright (c) Ian F. Darwin, http://www.darwinsys.com/, 1996-2002.
* All rights reserved. Software written by Ian F. Darwin and others.
* $Id: LICENSE,v 1.8 2004/02/09 03:33:38 ian Exp $
*
* 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.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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.
*
* Java, the Duke mascot, and all variants of Sun's Java "steaming coffee
* cup" logo are trademarks of Sun Microsystems. Sun's, and James Gosling's,
* pioneering role in inventing and promulgating (and standardizing) the Java
* language and environment is gratefully acknowledged.
*
* The pioneering role of Dennis Ritchie and Bjarne Stroustrup, of AT&T, for
* inventing predecessor languages C and C++ is also gratefully acknowledged.
*/
import java.io.*;
import java.text.*;
import java.util.*;
/** A simple text test of SPDF package
*/
public class PDFDemo {
public static void main(String[] argv) throws IOException {
PrintWriter pout;
if (argv.length == 0) {
pout = new PrintWriter(System.out);
} else {
if (new File(argv[0]).exists()) {
throw new IOException(
"Output file " + argv[0] + " already exists");
}
pout = new PrintWriter(new FileWriter(argv[0]));
}
PDF p = new PDF(pout);
Page p1 = new Page(p);
p1.add(new MoveTo(p, 100, 600));
p1.add(new Text(p, "Hello world, live on the web."));
p1.add(new Text(p, "Hello world, line 2 on the web."));
p.add(p1);
p.setAuthor("Ian F. Darwin");
p.writePDF();
}
}
/** The main class for the Darwin Open Systems
* {Simple,Stupid,Simplistic} PDF API.
* PDF is Adobe's Portable Document Format, and is probably a trademark
* of Adobe Systems Inc, Mountain View, California.
* The Adobe PDF Specification which they publish grants everyone
* permission to write code to generate and/or process PDF files.
* A PDF Object represents one PDF file.
* @author Ian F. Darwin, http://www.darwinsys.com/
* @version $Id: PDF.java,v 1.6 2004/02/09 03:34:02 ian Exp $
*/
class PDF {
/** The output writer */
protected PrintWriter out;
/** The list of pages */
protected ArrayList pages;
/** The list of object xrefs */
protected ArrayList xrefs;
/** The root object */
PDFObject rootObj = new RootObject(this);
/** The Info object */
InfoObject infoObj = new InfoObject(this);
/** The outlines (outline font) object */
OutlinesObject outlinesObj = new OutlinesObject(this);
/** The Pages object */
PagesObject pagesObj = new PagesObject(this);
/** The Font Dictionary */
FontDict fontDict = new FontDict(this);
/** The object number of the current object */
protected int currObj = 1;
/** A flag to avoid writing twice */
protected boolean startedWriting = false;
/** A magic number that identifies the output as a PDF file */
protected final static String PDF_MAGIC = "%PDF-1.0";
/** Constructor */
public PDF(PrintWriter o) {
out = o;
pages = new ArrayList();
xrefs = new ArrayList();
}
public void add(Page p) {
pages.add(p);
}
public void insertPage(int where, Page p) {
pages.add(where, p);
}
// OUTPUT METHODS -- we provide our own print/println, so we
// can keep track of file offsets (it was either that, or kludgily
// use a RandomAccessFile and the getFilePointer() method).
long offset = 0;
/** Print a String */
protected void print(String s){
out.print(s);
offset += s.length();
}
/** Println a String */
protected void println(String s) {
print(s);
print("\n");
}
/** Print an Object */
protected void print(Object o) {
print(o.toString());
}
/** Println an Object */
protected void println(Object o) {
println(o.toString());
}
/** Print an int */
protected void print(int i) {
String s = Integer.toString(i);
print(s);
}
/** Println an int */
protected void println(int i) {
String s = Integer.toString(i);
print(s);
}
/** Println with no args */
protected void println() {
print("\n");
}
// END OF OUTPUT METHODS
/** Add an entry into the offset table */
protected void addXref() {
xrefs.add(new Long(offset));
}
/** Write the entire output */
public void writePDF() {
if (startedWriting) {
throw new IllegalStateException(
"writePDF() can only be called once.");
}
startedWriting = true;
writePDFHeader();
writePDFbody();
writeXrefs();
writePDFTrailer();
out.flush();
out.close();
}
protected void writePDFHeader() {
println(PDF_MAGIC);
rootObj.print(); // 1
infoObj.print(); // 2
outlinesObj.print(); // 3
pagesObj.print(); // 4
}
protected void writePDFbody() {
for (int i=0; i<pages.size(); i++) {
((Page)pages.get(i)).print(); // 5, 6
}
addXref();
print(currObj++); println(" 0 obj");
println("[/PDF /Text]");
println("endobj");
fontDict.print(); // 8
}
DecimalFormat nf10 = new DecimalFormat("0000000000");
DecimalFormat nf5 = new DecimalFormat("00000");
/** Write one Xref, in the format 0000000000 65535 f */
protected void printXref(long n, int where, char inUse) {
println(nf10.format(n) + " " + nf5.format(where) + " " + inUse);
}
long xrefStart;
/** Write all the xrefs, using the format above */
protected void writeXrefs() {
xrefStart = offset;
println("xref");
print(0);
print(" ");
print(xrefs.size() + 1);
println();
// "fake" entry at 0, always 0, -1, f(free).
printXref(0, 65535, 'f');
// Remaining xref entries are for real objects.
for (int i=0; i<xrefs.size(); i++) {
Long lo = (Long)xrefs.get(i);
long l = lo.longValue();
printXref(l, 0, 'n');
}
}
protected void writePDFTrailer() {
println("trailer");
println("<<");
println("/Size " + (xrefs.size() + 1));
println("/Root 1 0 R");
println("/Info 2 0 R");
println(">>");
println("% startxref");
println("% " + xrefStart);
println("%%EOF");
}
class RootObject extends PDFDict {
protected RootObject(PDF m) {
super(m);
dict.put("Type", "/Catalog");
dict.put("Outlines", "3 0 R");
dict.put("Pages", "4 0 R");
}
}
class InfoObject extends PDFDict {
protected InfoObject(PDF m) {
super(m);
dict.put("Title", "(Sample PDF by SPDF)");
dict.put("Creator", "(Darwin Open Systems SPDF Software)");
dict.put("Created", "(D:20000516010203)");
}
}
public void setAuthor(String au) {
infoObj.dict.put("Author", "(" + au + ")");
}
class OutlinesObject extends PDFDict {
protected OutlinesObject(PDF m) {
super(m);
dict.put("Type", "/Outlines");
dict.put("Count", "0");
}
}
class PagesObject extends PDFDict {
protected PagesObject(PDF m) {
super(m);
dict.put("Type", "/Pages");
dict.put("Count", "1");
dict.put("Kids", "[5 0 R]");
}
}
class FontDict extends PDFDict {
protected FontDict(PDF m) {
super(m);
dict.put("Type", "/Font");
dict.put("Subtype", "/Type1");
dict.put("Name", "/F1");
dict.put("BaseFont", "/Helvetica");
dict.put("Encoding", "/MacRomanEncoding");
}
}
}
/** A PDFDict ias a PDFObject that is all, or mostly, a Dictionary.
* @author Ian Darwin, http://www.darwinsys.com/
*/
abstract class PDFDict extends PDFObject {
/** The Dictionary is a HashTable. Put the keys without a
* leading slash, since they always have it. Values can
* be /names, (strings), or whatever.
*/
protected Hashtable dict;
PDFDict(PDF m) {
super(m);
dict = new Hashtable();
}
/** Write the object to the Output Writer. The default implementation
* of this method in PDFDict just calls startObj, printDict, and endObj.
*/
protected void print() {
startObj();
printDict();
endObj();
}
protected void startObj() {
// Record the starting position of this Obj in the xref table
master.addXref();
// Print out e.g., "42 0 obj"
master.print(master.currObj++);
master.print(" 0 obj");
master.println();
}
protected void endObj() {
master.println("endobj");
}
protected void printDict() {
master.println("<<");
Enumeration e = dict.keys();
while (e.hasMoreElements()) {
master.print("\t/");
String key = (String)e.nextElement();
master.print(key);
master.print(" ");
master.print(dict.get(key));
master.println();
}
master.println(">>");
}
}
/** Represent one Text object in a PDF file. */
class Text extends PDFObject {
protected int x, y;
protected String text;
public Text(PDF m, String s) {
super(m);
text = s;
}
public void print() {
throw new IllegalStateException("print() called on a Text obj");
}
public void print(StringBuffer sb) {
sb.append("0 -18 Td (");
sb.append(text); // TODO must substitute escaped characters
sb.append(") Tj\n");
}
}
/** A PDFObject represents one node in the tree of a PDF file.
* @author Ian Darwin, http://www.darwinsys.com/
*/
abstract class PDFObject extends java.lang.Object {
/** The containing PDF file */
protected PDF master;
PDFObject(PDF m) {
master = m;
}
/** Write the object to the Output Writer */
protected abstract void print();
protected void startObj() {
// Record the starting position of this Obj in the xref table
master.addXref();
// Print out e.g., "42 0 obj"
master.print(master.currObj++);
master.print(" 0 obj");
master.println();
}
protected void endObj() {
master.println("endobj");
}
}
/** Represent one Move object ("moveto") in a PDF file. */
class MoveTo extends PDFObject {
protected int x, y;
public MoveTo(PDF m, int x, int y) {
super(m);
this.x = x;
this.y = y;
}
public void print() {
throw new IllegalStateException("print() called on a Text obj");
}
public void print(StringBuffer sb) {
sb.append(x);
sb.append(' ');
sb.append(y);
sb.append(" Td\n");
}
}
/** Represent one Page of a PDF file. */
class Page extends PDFDict {
protected ArrayList objects = new ArrayList();
public Page(PDF m) {
super(m);
dict.put("Type", "/Page");
dict.put("Parent", "4 0 R");
dict.put("Resources", "<< /Font << /F1 8 0 R >> /ProcSet 7 0 R >>");
dict.put("MediaBox", "[0 0 612 792]");
dict.put("Contents", "6 0 R");
}
public void add(PDFObject po) {
objects.add(po);
}
/** Print all the objects on the page.
* For now, just print all the Text objects, as one Stream.
*/
protected void print() {
// Print the Page object
super.print();
// Now do the Text objects as one PDF obj
master.addXref();
startObj();
StringBuffer sb = new StringBuffer();
sb.append("BT\n");
sb.append("/F1 12 Tf\n");
for (int i=0; i<objects.size(); i++) {
PDFObject po = (PDFObject)objects.get(i);
if (po instanceof Text)
((Text)po).print(sb);
else if (po instanceof MoveTo)
((MoveTo)po).print(sb);
// else if (po instanceof Font)
// ...
else
System.err.println("PDFPage: ignoring " + po);
}
sb.append("ET\n");
master.println("<< /Length " + sb.length() + " >>");
master.println("stream");
master.print(sb);
master.println("endstream");
endObj();
}
}
|