PDPageContentStream.java
/*
* 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.
*/
package org.apache.pdfbox.pdmodel;
import java.io.Closeable;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.pdfbox.cos.COSArray;
import org.apache.pdfbox.cos.COSBase;
import org.apache.pdfbox.cos.COSName;
import org.apache.pdfbox.pdmodel.common.PDStream;
import org.apache.pdfbox.pdmodel.interactive.annotation.PDAppearanceStream;
/**
* Provides the ability to write to a page content stream.
*
* @author Ben Litchfield
*/
public final class PDPageContentStream extends PDAbstractContentStream implements Closeable
{
/**
* This is to choose what to do with the stream: overwrite, append or prepend.
*/
public enum AppendMode
{
/**
* Overwrite the existing page content streams.
*/
OVERWRITE,
/**
* Append the content stream after all existing page content streams.
*/
APPEND,
/**
* Insert before all other page content streams.
*/
PREPEND;
public boolean isOverwrite()
{
return this == OVERWRITE;
}
public boolean isPrepend()
{
return this == PREPEND;
}
}
private static final Log LOG = LogFactory.getLog(PDPageContentStream.class);
private boolean sourcePageHadContents = false;
/**
* Create a new PDPage content stream. This constructor overwrites all existing content streams
* of this page.
*
* @param document The document the page is part of.
* @param sourcePage The page to write the contents to.
* @throws IOException If there is an error writing to the page contents.
*/
public PDPageContentStream(PDDocument document, PDPage sourcePage) throws IOException
{
this(document, sourcePage, AppendMode.OVERWRITE, true, false);
if (sourcePageHadContents)
{
LOG.warn("You are overwriting an existing content, you should use the append mode");
}
}
/**
* Create a new PDPage content stream. If the appendContent parameter is set to
* {@link AppendMode#APPEND}, you may want to use
* {@link #PDPageContentStream(PDDocument, PDPage, PDPageContentStream.AppendMode, boolean, boolean)}
* instead, with the fifth parameter set to true.
*
* @param document The document the page is part of.
* @param sourcePage The page to write the contents to.
* @param appendContent Indicates whether content will be overwritten, appended or prepended.
* @param compress Tell if the content stream should compress the page contents.
* @throws IOException If there is an error writing to the page contents.
*/
public PDPageContentStream(PDDocument document, PDPage sourcePage, AppendMode appendContent,
boolean compress) throws IOException
{
this(document, sourcePage, appendContent, compress, false);
}
/**
* Create a new PDPage content stream.
*
* @param document The document the page is part of.
* @param sourcePage The page to write the contents to.
* @param appendContent Indicates whether content will be overwritten, appended or prepended.
* @param compress Tell if the content stream should compress the page contents.
* @param resetContext Tell if the graphic context should be reset. This is only relevant when
* the appendContent parameter is set to {@link AppendMode#APPEND}. You should use this when
* appending to an existing stream, because the existing stream may have changed graphic
* properties (e.g. scaling, rotation).
* @throws IOException If there is an error writing to the page contents.
*/
public PDPageContentStream(PDDocument document, PDPage sourcePage, AppendMode appendContent,
boolean compress, boolean resetContext) throws IOException
{
this(document, sourcePage, appendContent, compress, resetContext, new PDStream(document),
sourcePage.getResources() != null ? sourcePage.getResources() : new PDResources());
}
private PDPageContentStream(PDDocument document, PDPage sourcePage, AppendMode appendContent,
boolean compress, boolean resetContext,PDStream stream,
PDResources resources) throws IOException
{
super(document, stream.createOutputStream(compress ? COSName.FLATE_DECODE : null), resources);
// propagate resources to the page
if (sourcePage.getResources() == null)
{
sourcePage.setResources(resources);
}
// If request specifies the need to append/prepend to the document
if (!appendContent.isOverwrite() && sourcePage.hasContents())
{
// Add new stream to contents array
COSBase contents = sourcePage.getCOSObject().getDictionaryObject(COSName.CONTENTS);
COSArray array;
if (contents instanceof COSArray)
{
// If contents is already an array, a new stream is simply appended to it
array = (COSArray) contents;
}
else
{
// Creates a new array and adds the current stream plus a new one to it
array = new COSArray();
array.add(contents);
}
if (appendContent.isPrepend())
{
array.add(0, stream.getCOSObject());
}
else
{
array.add(stream);
}
// save the initial/unmodified graphics context
if (resetContext)
{
// create a new stream to prefix existing stream
PDStream prefixStream = new PDStream(document);
// save the pre-append graphics state
try (OutputStream prefixOut = prefixStream.createOutputStream())
{
prefixOut.write("q".getBytes(StandardCharsets.US_ASCII));
prefixOut.write('\n');
}
// insert the new stream at the beginning
array.add(0, prefixStream.getCOSObject());
}
// Sets the compoundStream as page contents
sourcePage.getCOSObject().setItem(COSName.CONTENTS, array);
// restore the pre-append graphics state
if (resetContext)
{
restoreGraphicsState();
}
}
else
{
sourcePageHadContents = sourcePage.hasContents();
sourcePage.setContents(stream);
}
// configure NumberFormat
setMaximumFractionDigits(5);
}
/**
* Create a new appearance stream. Note that this is not actually a "page" content stream.
*
* @param doc The document the page is part of.
* @param appearance The appearance stream to write to.
* @throws IOException If there is an error writing to the page contents.
*/
public PDPageContentStream(PDDocument doc, PDAppearanceStream appearance) throws IOException
{
this (doc, appearance, appearance.getStream().createOutputStream());
}
/**
* Create a new appearance stream. Note that this is not actually a "page" content stream.
*
* @param doc The document the appearance is part of.
* @param appearance The appearance stream to add to.
* @param outputStream The appearances output stream to write to.
*/
public PDPageContentStream(PDDocument doc, PDAppearanceStream appearance, OutputStream outputStream)
{
super(doc, outputStream, appearance.getResources());
}
/**
* This will append raw commands to the content stream.
*
* @param commands The commands to append to the stream.
* @throws IOException If an error occurs while writing to the stream.
* @deprecated Usage of this method is discouraged.
*/
@Deprecated
public void appendRawCommands(String commands) throws IOException
{
write(commands);
}
/**
* This will append raw commands to the content stream.
*
* @param commands The commands to append to the stream.
* @throws IOException If an error occurs while writing to the stream.
* @deprecated Usage of this method is discouraged.
*/
@Deprecated
public void appendRawCommands(byte[] commands) throws IOException
{
write(commands);
}
/**
* This will append raw commands to the content stream.
*
* @param data Append a raw byte to the stream.
* @throws IOException If an error occurs while writing to the stream.
* @deprecated Usage of this method is discouraged.
*/
@Deprecated
public void appendRawCommands(int data) throws IOException
{
writeOperand(data);
}
/**
* This will append raw commands to the content stream.
*
* @param data Append a formatted double value to the stream.
* @throws IOException If an error occurs while writing to the stream.
* @deprecated Usage of this method is discouraged.
*/
@Deprecated
public void appendRawCommands(double data) throws IOException
{
writeOperand((float) data);
}
/**
* This will append raw commands to the content stream.
*
* @param data Append a formatted float value to the stream.
* @throws IOException If an error occurs while writing to the stream.
* @deprecated Usage of this method is discouraged.
*/
@Deprecated
public void appendRawCommands(float data) throws IOException
{
writeOperand(data);
}
}