blob: 8152fcd8336aed443fabea027291026ca050fcd1 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/* $Id$ */
package org.apache.fop.pdf;
// Java
import java.io.IOException;
import java.io.OutputStream;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.TimeZone;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* generic PDF object.
*
* A PDF Document is essentially a collection of these objects. A PDF
* Object has a number and a generation (although the generation will always
* be 0 in new documents).
*/
public abstract class PDFObject implements PDFWritable {
/** logger for all PDFObjects (and descendants) */
protected static Log log = LogFactory.getLog(PDFObject.class.getName());
/**
* the object's number
*/
private int objnum;
/**
* the object's generation (0 in new documents)
*/
private int generation = 0;
/**
* the parent PDFDocument
*/
private PDFDocument document;
/**
* Returns the object's number.
* @return the PDF Object number
*/
public int getObjectNumber() {
if (this.objnum == 0) {
throw new IllegalStateException("Object has no number assigned: " + this.toString());
}
return this.objnum;
}
/**
* Indicates whether this PDFObject has already been assigned an
* object number.
* @return True if it has an object number
*/
public boolean hasObjectNumber() {
return this.objnum > 0;
}
/**
* Sets the object number
* @param objnum the object number
*/
public void setObjectNumber(int objnum) {
this.objnum = objnum;
log.trace("Assigning " + this + " object number " + objnum);
}
/**
* Returns the object's generation.
* @return the PDF Object generation
*/
public int getGeneration() {
return this.generation;
}
/**
* Returns the parent PDFDocument if assigned.
* @return the parent PDFDocument (May be null if the parent PDFDocument
* has not been assigned)
*/
public final PDFDocument getDocument() {
return this.document;
}
/**
* Returns the parent PDFDocument, but unlike <code>getDocument()</code>
* it throws an informative Exception if the parent document is unavailable
* instead of having a NullPointerException somewhere without a message.
* @return the parent PDFDocument
*/
public final PDFDocument getDocumentSafely() {
final PDFDocument doc = getDocument();
if (doc == null) {
throw new IllegalStateException("Parent PDFDocument is unavailable");
}
return doc;
}
/**
* Sets the parent PDFDocument.
* @param doc the PDFDocument.
*/
public void setDocument(PDFDocument doc) {
this.document = doc;
}
/**
* Returns the PDF representation of the Object ID.
* @return the Object ID
*/
public String getObjectID() {
return getObjectNumber() + " " + getGeneration() + " obj\n";
}
/**
* Returns the PDF representation of a reference to this object.
* @return the reference string
*/
public String referencePDF() {
if (!hasObjectNumber()) {
throw new IllegalArgumentException(
"Cannot reference this object. It doesn't have an object number");
}
String ref = getObjectNumber() + " " + getGeneration() + " R";
return ref;
}
/**
* Creates and returns a reference to this object.
* @return the object reference
*/
public PDFReference makeReference() {
return new PDFReference(this);
}
/**
* Write the PDF represention of this object
*
* @param stream the stream to write the PDF to
* @throws IOException if there is an error writing to the stream
* @return the number of bytes written
*/
protected int output(OutputStream stream) throws IOException {
byte[] pdf = this.toPDF();
stream.write(pdf);
return pdf.length;
}
/**
* Encodes the object as a byte array for output to a PDF file.
*
* @return PDF string
*/
protected byte[] toPDF() {
return encode(toPDFString());
}
/**
* This method returns a String representation of the PDF object. The result
* is normally converted/encoded to a byte array by toPDF(). Only use
* this method to implement the serialization if the object can be fully
* represented as text. If the PDF representation of the object contains
* binary content use toPDF() or output(OutputStream) instead.
* @return String the String representation
*/
protected String toPDFString() {
throw new UnsupportedOperationException("Not implemented. "
+ "Use output(OutputStream) instead.");
}
/**
* Returns a representation of this object for in-object placement, i.e. if the object
* has an object number its reference is returned. Otherwise, its PDF representation is
* returned.
* @return the String representation
*/
public String toInlinePDFString() {
if (hasObjectNumber()) {
return referencePDF();
} else {
return toPDFString();
}
}
/**
* Converts text to a byte array for writing to a PDF file.
* @param text text to convert/encode
* @return byte[] the resulting byte array
*/
public static final byte[] encode(String text) {
return PDFDocument.encode(text);
}
/**
* Encodes a Text String (3.8.1 in PDF 1.4 specs)
* @param text the text to encode
* @return byte[] the encoded text
*/
protected byte[] encodeText(String text) {
if (getDocumentSafely().isEncryptionActive()) {
final byte[] buf = PDFText.toUTF16(text);
return PDFText.escapeByteArray(
getDocument().getEncryption().encrypt(buf, this));
} else {
return encode(PDFText.escapeText(text, false));
}
}
/**
* Encodes a String (3.2.3 in PDF 1.4 specs)
* @param string the string to encode
* @return byte[] the encoded string
*/
protected byte[] encodeString(String string) {
return encodeText(string);
/*
final byte[] buf = encode(PDFText.escapeString(string));
if (getDocumentSafely().isEncryptionActive()) {
return PDFText.escapeByteArray(
getDocument().getEncryption().encrypt(buf, this));
} else {
return buf;
}*/
}
/**
* Formats an object for serialization to PDF.
* @param obj the object
* @param sb the StringBuffer to write to
*/
protected void formatObject(Object obj, StringBuffer sb) {
if (obj == null) {
sb.append("null");
} else if (obj instanceof PDFWritable) {
sb.append(((PDFWritable)obj).toInlinePDFString());
} else if (obj instanceof Number) {
if (obj instanceof Double || obj instanceof Float) {
sb.append(PDFNumber.doubleOut(((Number)obj).doubleValue()));
} else {
sb.append(obj);
}
} else if (obj instanceof Boolean) {
sb.append(obj);
} else {
sb.append("(").append(obj).append(")");
}
}
/** Formatting pattern for PDF date */
protected static final SimpleDateFormat DATE_FORMAT
= new SimpleDateFormat("'D:'yyyyMMddHHmmss");
/**
* Formats a date/time according to the PDF specification
* (D:YYYYMMDDHHmmSSOHH'mm').
* @param time date/time value to format
* @return the requested String representation
*/
protected String formatDateTime(Date time) {
StringBuffer sb = new StringBuffer();
sb.append(DATE_FORMAT.format(time));
TimeZone tz = TimeZone.getDefault();
Calendar cal = Calendar.getInstance();
cal.setTime(time);
int era = cal.get(Calendar.ERA);
int year = cal.get(Calendar.YEAR);
int month = cal.get(Calendar.MONTH);
int day = cal.get(Calendar.DAY_OF_MONTH);
int dayOfWeek = cal.get(Calendar.DAY_OF_WEEK);
int milliseconds = cal.get(Calendar.HOUR_OF_DAY) * 1000 * 60 * 60;
milliseconds += cal.get(Calendar.MINUTE) * 1000 * 60;
milliseconds += cal.get(Calendar.SECOND) * 1000;
milliseconds += cal.get(Calendar.MILLISECOND);
int offset = tz.getOffset(era, year, month, day, dayOfWeek, milliseconds);
if (offset == 0) {
sb.append('Z');
} else {
if (offset > 0) {
sb.append('+');
} else {
sb.append('-');
}
final int HOUR = (1000 * 60 * 60);
int offsetHour = Math.abs(offset / HOUR);
int offsetMinutes = (offset - (offsetHour * HOUR)) / (1000 * 60);
if (offsetHour < 10) {
sb.append('0');
}
sb.append(Integer.toString(offsetHour));
sb.append('\'');
if (offsetMinutes < 10) {
sb.append('0');
}
sb.append(Integer.toString(offsetMinutes));
sb.append('\'');
}
return sb.toString();
}
}