PDAppearanceContentStream.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 org.apache.pdfbox.contentstream.operator.OperatorName;
import org.apache.pdfbox.cos.COSArray;
import org.apache.pdfbox.cos.COSName;
import org.apache.pdfbox.pdmodel.graphics.color.PDColor;
import org.apache.pdfbox.pdmodel.interactive.annotation.PDAppearanceStream;
import org.apache.pdfbox.pdmodel.interactive.annotation.PDBorderStyleDictionary;
/**
* Provides the ability to write to an appearance content stream.
*
* @author Ben Litchfield
*/
public final class PDAppearanceContentStream extends PDAbstractContentStream implements Closeable
{
/**
* Create a new appearance stream.
*
* @param appearance
* The appearance stream to write to.
* @throws IOException If there is an error writing to the content stream.
*/
public PDAppearanceContentStream(PDAppearanceStream appearance) throws IOException
{
this(appearance, appearance.getStream().createOutputStream());
}
/**
* Create a new appearance stream.
*
* @param appearance The appearance stream to write to.
* @param compress whether the content stream is to be compressed. Set this to true when
* creating long content streams.
* @throws IOException If there is an error writing to the content stream.
*/
public PDAppearanceContentStream(PDAppearanceStream appearance, boolean compress) throws IOException
{
this(appearance, appearance.getStream().createOutputStream(compress ? COSName.FLATE_DECODE : null));
}
/**
* Create a new appearance stream.
*
* @param appearance
* The appearance stream to add to.
* @param outputStream
* The appearances output stream to write to.
*/
public PDAppearanceContentStream(PDAppearanceStream appearance, OutputStream outputStream)
{
super(null, outputStream, appearance.getResources());
}
/**
* Set the stroking color.
*
* <p>
* The command is only emitted if the color is not null and the number of
* components is > 0.
*
* @param color The colorspace to write.
* @throws IOException If there is an error writing to the content stream.
* @see PDAbstractContentStream#setStrokingColor(PDColor)
*/
public boolean setStrokingColorOnDemand(PDColor color) throws IOException
{
if (color != null)
{
float[] components = color.getComponents();
if (components.length > 0)
{
setStrokingColor(components);
return true;
}
}
return false;
}
/**
* Set the stroking color.
*
* @see PDAbstractContentStream#setStrokingColor(java.awt.Color)
* @param components
* the color components dependent on the color space being used.
* @throws IOException If there is an error writing to the content stream.
*/
public void setStrokingColor(float[] components) throws IOException
{
for (float value : components)
{
writeOperand(value);
}
int numComponents = components.length;
switch (numComponents)
{
case 1:
writeOperator(OperatorName.STROKING_COLOR_GRAY);
break;
case 3:
writeOperator(OperatorName.STROKING_COLOR_RGB);
break;
case 4:
writeOperator(OperatorName.STROKING_COLOR_CMYK);
break;
default:
break;
}
//TODO shouldn't we set the stack?
//Or call the appropriate setStrokingColor() method from the base class?
}
/**
* Set the non stroking color.
*
* <p>
* The command is only emitted if the color is not null and the number of
* components is > 0.
*
* @param color The colorspace to write.
* @throws IOException If there is an error writing to the content stream.
* @see PDAbstractContentStream#setNonStrokingColor(PDColor)
*/
public boolean setNonStrokingColorOnDemand(PDColor color) throws IOException
{
if (color != null)
{
float[] components = color.getComponents();
if (components.length > 0)
{
setNonStrokingColor(components);
return true;
}
}
return false;
}
/**
* Set the non stroking color.
*
* @see PDAbstractContentStream#setNonStrokingColor(java.awt.Color)
* @param components
* the color components dependent on the color space being used.
* @throws IOException If there is an error writing to the content stream.
*/
public void setNonStrokingColor(float[] components) throws IOException
{
for (float value : components)
{
writeOperand(value);
}
int numComponents = components.length;
switch (numComponents)
{
case 1:
writeOperator(OperatorName.NON_STROKING_GRAY);
break;
case 3:
writeOperator(OperatorName.NON_STROKING_RGB);
break;
case 4:
writeOperator(OperatorName.NON_STROKING_CMYK);
break;
default:
break;
}
//TODO shouldn't we set the stack?
//Or call the appropriate setNonStrokingColor() method from the base class?
}
/**
* Convenience method for annotations: sets the line with and dash style.
*
* @param lineWidth The line width.
* @param bs The border style, may be null.
* @param border The border array, must have at least three entries. This is
* only used if the border style is null.
*
* @throws IOException If there is an error writing to the content stream.
*/
public void setBorderLine(float lineWidth, PDBorderStyleDictionary bs,
COSArray border) throws IOException
{
// Can't use PDBorderStyleDictionary.getDashStyle() as
// this will return a default dash style if non is existing
if (bs != null && bs.getCOSObject().containsKey(COSName.D) &&
bs.getStyle().equals(PDBorderStyleDictionary.STYLE_DASHED))
{
setLineDashPattern(bs.getDashStyle().getDashArray(), 0);
}
else if (bs == null && border.size() > 3)
{
if (border.getObject(3) instanceof COSArray)
{
setLineDashPattern(((COSArray) border.getObject(3)).toFloatArray(), 0);
}
else
{
// PDFBOX-5266: invalid dash array, be invisible
setLineDashPattern(new float[1], 0);
}
}
setLineWidthOnDemand(lineWidth);
}
/**
* Sets the line width. The command is only emitted if the lineWidth is
* different to 1.
*
* @param lineWidth the line width of the path.
* @throws IOException If there is an error writing to the content stream.
* @see PDAbstractContentStream#setLineWidth(float)
*/
public void setLineWidthOnDemand(float lineWidth) throws IOException
{
// Acrobat doesn't write a line width command
// for a line width of 1 as this is default.
// Will do the same.
if (!(Math.abs(lineWidth - 1) < 1e-6))
{
setLineWidth(lineWidth);
}
}
/**
* Draw a shape.
*
* <p>
* Dependent on the lineWidth and whether or not there is a background to be generated there are
* different commands to be used for draw a shape.
*
* @param lineWidth the line width of the path.
* @param hasStroke shall there be a stroking color.
* @param hasFill shall there be a fill color.
* @throws IOException If there is an error writing to the content stream.
*/
public void drawShape(float lineWidth, boolean hasStroke, boolean hasFill) throws IOException
{
// initial setting if stroking shall be done
boolean resolvedHasStroke = hasStroke;
// no stroking for very small lines
if (lineWidth < 1e-6)
{
resolvedHasStroke = false;
}
if (hasFill && resolvedHasStroke)
{
fillAndStroke();
}
else if (resolvedHasStroke)
{
stroke();
}
else if (hasFill)
{
fill();
}
else
{
writeOperator(OperatorName.ENDPATH);
}
}
}