/* * 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; import java.io.IOException; import java.io.OutputStream; import java.io.Writer; import org.apache.commons.io.output.CountingOutputStream; import org.apache.fop.util.CloseBlockerOutputStream; /** * This is an abstract base class for PDF streams. */ public abstract class AbstractPDFStream extends PDFDictionary { /** The filters that should be applied */ private PDFFilterList filters; /** * Constructor for AbstractPDFStream. */ public AbstractPDFStream() { super(); } /** * Sets up the default filters for this stream if they haven't been set * from outside. */ protected void setupFilterList() { if (!getFilterList().isInitialized()) { getFilterList().addDefaultFilters( getDocumentSafely().getFilterMap(), PDFFilterList.DEFAULT_FILTER); } prepareImplicitFilters(); getDocument().applyEncryption(this); } /** * Returns the associated filter list. * @return the filter list */ public PDFFilterList getFilterList() { if (this.filters == null) { if (getDocument() == null) { this.filters = new PDFFilterList(); } else { this.filters = new PDFFilterList(getDocument().isEncryptionActive()); } boolean hasFilterEntries = (get("Filter") != null); if (hasFilterEntries) { this.filters.setDisableAllFilters(true); } } return this.filters; } /** * Returns a value that hints at the size of the encoded stream. This is * used to optimize buffer allocation so fewer buffer reallocations are * necessary. * @return an estimated size (0 if no hint can be given) * @throws IOException in case of an I/O problem */ protected abstract int getSizeHint() throws IOException; /** * Sends the raw stream data to the target OutputStream. * @param out OutputStream to write to * @throws IOException In case of an I/O problem */ protected abstract void outputRawStreamData(OutputStream out) throws IOException; /** * Output just the stream data enclosed by stream/endstream markers * @param encodedStream already encoded/filtered stream to write * @param out OutputStream to write to * @return int number of bytes written * @throws IOException in case of an I/O problem */ protected int outputStreamData(StreamCache encodedStream, OutputStream out) throws IOException { int length = 0; byte[] p = encode("stream\n"); out.write(p); length += p.length; encodedStream.outputContents(out); length += encodedStream.getSize(); p = encode("\nendstream"); out.write(p); length += p.length; return length; } /** * Encodes the raw data stream for output to a PDF file. * @return the encoded stream * @throws IOException in case of an I/O problem */ protected StreamCache encodeStream() throws IOException { //Allocate a temporary buffer to find out the size of the encoded stream final StreamCache encodedStream = StreamCacheFactory.getInstance(). createStreamCache(getSizeHint()); OutputStream filteredOutput = getFilterList().applyFilters(encodedStream.getOutputStream()); outputRawStreamData(filteredOutput); filteredOutput.flush(); filteredOutput.close(); return encodedStream; } /** * Encodes and writes a stream directly to an OutputStream. The length of * the stream, in this case, is set on a PDFNumber object that has to be * prepared beforehand. * @param out OutputStream to write to * @param refLength PDFNumber object to receive the stream length * @return number of bytes written (header and trailer included) * @throws IOException in case of an I/O problem */ protected int encodeAndWriteStream(OutputStream out, PDFNumber refLength) throws IOException { int bytesWritten = 0; //Stream header byte[] buf = encode("stream\n"); out.write(buf); bytesWritten += buf.length; //Stream contents CloseBlockerOutputStream cbout = new CloseBlockerOutputStream(out); CountingOutputStream cout = new CountingOutputStream(cbout); OutputStream filteredOutput = getFilterList().applyFilters(cout); outputRawStreamData(filteredOutput); filteredOutput.close(); refLength.setNumber(new Integer(cout.getCount())); bytesWritten += cout.getCount(); //Stream trailer buf = encode("\nendstream"); out.write(buf); bytesWritten += buf.length; return bytesWritten; } /** * Overload the base object method so we don't have to copy * byte arrays around so much * {@inheritDoc} */ protected int output(OutputStream stream) throws IOException { setupFilterList(); CountingOutputStream cout = new CountingOutputStream(stream); Writer writer = PDFDocument.getWriterFor(cout); writer.write(getObjectID()); //int length = 0; StreamCache encodedStream = null; PDFNumber refLength = null; final Object lengthEntry; if (getDocument().isEncodingOnTheFly()) { refLength = new PDFNumber(); getDocumentSafely().registerObject(refLength); lengthEntry = refLength; } else { encodedStream = encodeStream(); lengthEntry = new Integer(encodedStream.getSize() + 1); } populateStreamDict(lengthEntry); writeDictionary(cout, writer); //Send encoded stream to target OutputStream writer.flush(); if (encodedStream == null) { encodeAndWriteStream(cout, refLength); } else { outputStreamData(encodedStream, cout); encodedStream.clear(); //Encoded stream can now be discarded } writer.write("\nendobj\n"); writer.flush(); return cout.getCount(); } /** * Populates the dictionary with all necessary entries for the stream. * Override this method if you need additional entries. * @param lengthEntry value for the /Length entry */ protected void populateStreamDict(Object lengthEntry) { put("Length", lengthEntry); if (!getFilterList().isDisableAllFilters()) { getFilterList().putFilterDictEntries(this); } } /** * Prepares implicit filters (such as the DCTFilter for JPEG images). You * must make sure that the appropriate filters are in the filter list at * the right places. */ protected void prepareImplicitFilters() { //nop: No default implicit filters } }