package com.tom_roush.pdfbox.pdmodel;
import android.graphics.Path;
import android.util.Log;
import com.tom_roush.pdfbox.cos.COSArray;
import com.tom_roush.pdfbox.cos.COSBase;
import com.tom_roush.pdfbox.cos.COSName;
import com.tom_roush.pdfbox.pdfwriter.COSWriter;
import com.tom_roush.pdfbox.pdmodel.common.PDStream;
import com.tom_roush.pdfbox.pdmodel.documentinterchange.markedcontent.PDPropertyList;
import com.tom_roush.pdfbox.pdmodel.font.PDFont;
import com.tom_roush.pdfbox.pdmodel.graphics.PDXObject;
import com.tom_roush.pdfbox.pdmodel.graphics.color.PDColor;
import com.tom_roush.pdfbox.pdmodel.graphics.color.PDColorSpace;
import com.tom_roush.pdfbox.pdmodel.graphics.color.PDDeviceGray;
import com.tom_roush.pdfbox.pdmodel.graphics.color.PDDeviceRGB;
import com.tom_roush.pdfbox.pdmodel.graphics.form.PDFormXObject;
import com.tom_roush.pdfbox.pdmodel.graphics.image.PDImageXObject;
import com.tom_roush.pdfbox.pdmodel.graphics.shading.PDShading;
import com.tom_roush.pdfbox.pdmodel.graphics.state.PDExtendedGraphicsState;
import com.tom_roush.pdfbox.pdmodel.interactive.annotation.PDAppearanceStream;
import com.tom_roush.pdfbox.util.Charsets;
import com.tom_roush.pdfbox.util.Matrix;
import com.tom_roush.pdfbox.util.awt.AWTColor;
import com.tom_roush.pdfbox.util.awt.AffineTransform;
import java.io.Closeable;
import java.io.IOException;
import java.io.OutputStream;
import java.text.NumberFormat;
import java.util.Locale;
import java.util.Stack;
/**
* Provides the ability to write to a page content stream.
*
* @author Ben Litchfield
*/
public final class PDPageContentStream implements Closeable
{
// instance variables
private final PDDocument document;
private OutputStream output;
private PDResources resources;
private boolean inTextMode = false;
private final Stack<PDFont> fontStack = new Stack<PDFont>();
private final Stack<PDColorSpace> nonStrokingColorSpaceStack = new Stack<PDColorSpace>();
private Stack<PDColorSpace> strokingColorSpaceStack = new Stack<PDColorSpace>();
// number format
private final NumberFormat formatDecimal = NumberFormat.getNumberInstance(Locale.US);
/**
* Create a new PDPage content stream.
*
* @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, false, true);
}
/**
* 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. If false all previous
* content is deleted.
* @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, boolean 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. If false all previous
* content is deleted.
* @param compress Tell if the content stream should compress the page contents.
* @param resetContext Tell if the graphic context should be reseted.
* @throws IOException If there is an error writing to the page contents.
*/
public PDPageContentStream(PDDocument document, PDPage sourcePage, boolean appendContent,
boolean compress, boolean resetContext) throws IOException
{
this.document = document;
COSName filter = compress ? COSName.FLATE_DECODE : null;
// If request specifies the need to append to the document
if (appendContent && sourcePage.hasContents())
{
// Create a stream to append new content
PDStream contentsToAppend = new PDStream(document);
// 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;
array.add(contentsToAppend);
}
else
{
// Creates a new array and adds the current stream plus a new one to it
array = new COSArray();
array.add(contents);
array.add(contentsToAppend);
}
// save the initial/unmodified graphics context
if (resetContext)
{
// create a new stream to encapsulate the existing stream
PDStream saveGraphics = new PDStream(document);
output = saveGraphics.createOutputStream(filter);
// save the initial/unmodified graphics context
saveGraphicsState();
close();
// insert the new stream at the beginning
array.add(0, saveGraphics.getStream());
}
// Sets the compoundStream as page contents
sourcePage.getCOSObject().setItem(COSName.CONTENTS, array);
output = contentsToAppend.createOutputStream(filter);
// restore the initial/unmodified graphics context
if (resetContext)
{
restoreGraphicsState();
}
}
else
{
if (sourcePage.hasContents())
{
Log.w("PdfBox-Android", "You are overwriting an existing content, you should use the append mode");
}
PDStream contents = new PDStream(document);
sourcePage.setContents(contents);
output = contents.createOutputStream(filter);
}
// this has to be done here, as the resources will be set to null when resetting the content
// stream
resources = sourcePage.getResources();
if (resources == null)
{
resources = new PDResources();
sourcePage.setResources(resources);
}
// configure NumberFormat
formatDecimal.setMaximumFractionDigits(10);
formatDecimal.setGroupingUsed(false);
}
/**
* 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.document = doc;
output = appearance.getStream().createOutputStream();
this.resources = appearance.getResources();
formatDecimal.setMaximumFractionDigits(4);
formatDecimal.setGroupingUsed(false);
}
/**
* 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 doc The document the appearance is part of.
* @param outputStream The appearances output stream to write to.
* @throws IOException If there is an error writing to the page contents.
*/
public PDPageContentStream(PDDocument doc, PDAppearanceStream appearance, OutputStream outputStream)
throws IOException
{
this.document = doc;
output = outputStream;
this.resources = appearance.getResources();
formatDecimal.setMaximumFractionDigits(4);
formatDecimal.setGroupingUsed(false);
}
/**
* Begin some text operations.
*
* @throws IOException If there is an error writing to the stream or if you attempt to
* nest beginText calls.
* @throws IllegalStateException If the method was not allowed to be called at this time.
*/
public void beginText() throws IOException
{
if (inTextMode)
{
throw new IllegalStateException("Error: Nested beginText() calls are not allowed.");
}
writeOperator("BT");
inTextMode = true;
}
/**
* End some text operations.
*
* @throws IOException If there is an error writing to the stream or if you attempt to
* nest endText calls.
* @throws IllegalStateException If the method was not allowed to be called at this time.
*/
public void endText() throws IOException
{
if (!inTextMode)
{
throw new IllegalStateException("Error: You must call beginText() before calling endText.");
}
writeOperator("ET");
inTextMode = false;
}
/**
* Set the font to draw text with.
*
* @param font The font to use.
* @param fontSize The font size to draw the text.
* @throws IOException If there is an error writing the font information.
*/
public void setFont(PDFont font, float fontSize) throws IOException
{
if (fontStack.isEmpty())
{
fontStack.add(font);
}
else
{
fontStack.setElementAt(font, fontStack.size() - 1);
}
if (font.willBeSubset() && !document.getFontsToSubset().contains(font))
{
document.getFontsToSubset().add(font);
}
writeOperand(resources.add(font));
writeOperand(fontSize);
writeOperator("Tf");
}
/**
* This will draw a string at the current location on the screen.
*
* @param text The text to draw.
* @throws IOException If an io exception occurs.
* @deprecated Use {@link #showText} instead.
*/
@Deprecated
public void drawString(String text) throws IOException
{
showText(text);
}
/**
* Shows the given text at the location specified by the current text matrix.
*
* @param text The Unicode text to show.
* @throws IOException If an io exception occurs.
*/
public void showText(String text) throws IOException
{
if (!inTextMode)
{
throw new IllegalStateException("Must call beginText() before showText()");
}
if (fontStack.isEmpty())
{
throw new IllegalStateException("Must call setFont() before showText()");
}
PDFont font = fontStack.peek();
// Unicode code points to keep when subsetting
if (font.willBeSubset())
{
for (int offset = 0; offset < text.length(); )
{
int codePoint = text.codePointAt(offset);
font.addToSubset(codePoint);
offset += Character.charCount(codePoint);
}
}
COSWriter.writeString(font.encode(text), output);
write(" ");
writeOperator("Tj");
}
/**
* Sets the text leading.
*
* @param leading The leading in unscaled text units.
* @throws IOException If there is an error writing to the stream.
*/
public void setLeading(double leading) throws IOException
{
writeOperand((float) leading);
writeOperator("TL");
}
/**
* Move to the start of the next line of text. Requires the leading to have been set.
*
* @throws IOException If there is an error writing to the stream.
*/
public void newLine() throws IOException
{
if (!inTextMode)
{
throw new IllegalStateException("Must call beginText() before newLine()");
}
writeOperator("T*");
}
/**
* The Td operator.
* A current text matrix will be replaced with a new one (1 0 0 1 x y).
* @param tx The x translation.
* @param ty The y translation.
* @throws IOException If there is an error writing to the stream.
* @deprecated Use {@link #newLineAtOffset} instead.
*/
@Deprecated
public void moveTextPositionByAmount(float tx, float ty) throws IOException
{
newLineAtOffset(tx, ty);
}
/**
* The Td operator.
* Move to the start of the next line, offset from the start of the current line by (tx, ty).
*
* @param tx The x translation.
* @param ty The y translation.
* @throws IOException If there is an error writing to the stream.
* @throws IllegalStateException If the method was not allowed to be called at this time.
*/
public void newLineAtOffset(float tx, float ty) throws IOException
{
if (!inTextMode)
{
throw new IllegalStateException("Error: must call beginText() before newLineAtOffset()");
}
writeOperand(tx);
writeOperand(ty);
writeOperator("Td");
}
/**
* The Tm operator. Sets the text matrix to the given values.
* A current text matrix will be replaced with the new one.
* @param a The a value of the matrix.
* @param b The b value of the matrix.
* @param c The c value of the matrix.
* @param d The d value of the matrix.
* @param e The e value of the matrix.
* @param f The f value of the matrix.
* @throws IOException If there is an error writing to the stream.
* @deprecated Use {@link #setTextMatrix(Matrix)} instead.
*/
@Deprecated
public void setTextMatrix(double a, double b, double c, double d, double e, double f) throws IOException
{
setTextMatrix(new Matrix((float) a, (float) b, (float) c, (float) d, (float) e, (float) f));
}
/**
* The Tm operator. Sets the text matrix to the given values.
* A current text matrix will be replaced with the new one.
* @param matrix the transformation matrix
* @throws IOException If there is an error writing to the stream.
* @deprecated Use {@link #setTextMatrix(Matrix)} instead.
*/
@Deprecated
public void setTextMatrix(AffineTransform matrix) throws IOException
{
setTextMatrix(new Matrix(matrix));
}
/**
* The Tm operator. Sets the text matrix to the given values.
* A current text matrix will be replaced with the new one.
*
* @param matrix the transformation matrix
* @throws IOException If there is an error writing to the stream.
* @throws IllegalStateException If the method was not allowed to be called at this time.
*/
public void setTextMatrix(Matrix matrix) throws IOException
{
if (!inTextMode)
{
throw new IllegalStateException("Error: must call beginText() before setTextMatrix");
}
writeAffineTransform(matrix.createAffineTransform());
writeOperator("Tm");
}
/**
* The Tm operator. Sets the text matrix to the given scaling and translation values.
* A current text matrix will be replaced with the new one.
* @param sx The scaling factor in x-direction.
* @param sy The scaling factor in y-direction.
* @param tx The translation value in x-direction.
* @param ty The translation value in y-direction.
* @throws IOException If there is an error writing to the stream.
* @deprecated Use {@link #setTextMatrix(Matrix)} instead.
*/
@Deprecated
public void setTextScaling(double sx, double sy, double tx, double ty) throws IOException
{
setTextMatrix(new Matrix((float) sx, 0f, 0f, (float) sy, (float) tx, (float) ty));
}
/**
* The Tm operator. Sets the text matrix to the given translation values.
* A current text matrix will be replaced with the new one.
* @param tx The translation value in x-direction.
* @param ty The translation value in y-direction.
* @throws IOException If there is an error writing to the stream.
* @deprecated Use {@link #setTextMatrix(Matrix)} instead.
*/
@Deprecated
public void setTextTranslation(double tx, double ty) throws IOException
{
setTextMatrix(Matrix.getTranslateInstance((float) tx, (float) ty));
}
/**
* The Tm operator. Sets the text matrix to the given rotation and translation values.
* A current text matrix will be replaced with the new one.
* @param angle The angle used for the counterclockwise rotation in radians.
* @param tx The translation value in x-direction.
* @param ty The translation value in y-direction.
* @throws IOException If there is an error writing to the stream.
* @deprecated Use {@link #setTextMatrix(Matrix)} instead.
*/
@Deprecated
public void setTextRotation(double angle, double tx, double ty) throws IOException
{
setTextMatrix(Matrix.getRotateInstance(angle, (float) tx, (float) ty));
}
/**
* Draw an image at the x,y coordinates, with the default size of the image.
*
* @param image The image to draw.
* @param x The x-coordinate to draw the image.
* @param y The y-coordinate to draw the image.
*
* @throws IOException If there is an error writing to the stream.
*/
public void drawImage(PDImageXObject image, float x, float y) throws IOException
{
drawImage(image, x, y, image.getWidth(), image.getHeight());
}
/**
* Draw an image at the x,y coordinates, with the given size.
*
* @param image The image to draw.
* @param x The x-coordinate to draw the image.
* @param y The y-coordinate to draw the image.
* @param width The width to draw the image.
* @param height The height to draw the image.
*
* @throws IOException If there is an error writing to the stream.
* @throws IllegalStateException If the method was called within a text block.
*/
public void drawImage(PDImageXObject image, float x, float y, float width, float height) throws IOException
{
if (inTextMode)
{
throw new IllegalStateException("Error: drawImage is not allowed within a text block.");
}
saveGraphicsState();
AffineTransform transform = new AffineTransform(width, 0, 0, height, x, y);
transform(new Matrix(transform));
writeOperand(resources.add(image));
writeOperator("Do");
restoreGraphicsState();
}
/**
* Draw an inline image at the x,y coordinates, with the default size of the image.
*
* @param inlineImage The inline image to draw.
* @param x The x-coordinate to draw the inline image.
* @param y The y-coordinate to draw the inline image.
*
* @throws IOException If there is an error writing to the stream.
* @deprecated Use {@link #drawImage(PDInlineImage, float, float)} instead.
*/
// @Deprecated
// public void drawInlineImage(PDInlineImage inlineImage, float x, float y) throws IOException
// {
// drawImage(inlineImage, x, y, inlineImage.getWidth(), inlineImage.getHeight());
// } TODO: PdfBox-Android
/**
* Draw an inline image at the x,y coordinates, with the default size of the image.
*
* @param inlineImage The inline image to draw.
* @param x The x-coordinate to draw the inline image.
* @param y The y-coordinate to draw the inline image.
*
* @throws IOException If there is an error writing to the stream.
*/
// public void drawImage(PDInlineImage inlineImage, float x, float y) throws IOException
// {
// drawImage(inlineImage, x, y, inlineImage.getWidth(), inlineImage.getHeight());
// } TODO: PdfBox-Android
/**
* Draw an inline image at the x,y coordinates and a certain width and height.
*
* @param inlineImage The inline image to draw.
* @param x The x-coordinate to draw the inline image.
* @param y The y-coordinate to draw the inline image.
* @param width The width of the inline image to draw.
* @param height The height of the inline image to draw.
*
* @throws IOException If there is an error writing to the stream.
* @deprecated Use {@link #drawImage(PDInlineImage, float, float, float, float)} instead.
*/
// @Deprecated
// public void drawInlineImage(PDInlineImage inlineImage, float x, float y, float width, float height) throws IOException
// {
// drawImage(inlineImage, x, y, width, height);
// } TODO: PdfBox-Android
/**
* Draw an inline image at the x,y coordinates and a certain width and height.
*
* @param inlineImage The inline image to draw.
* @param x The x-coordinate to draw the inline image.
* @param y The y-coordinate to draw the inline image.
* @param width The width of the inline image to draw.
* @param height The height of the inline image to draw.
*
* @throws IOException If there is an error writing to the stream.
* @throws IllegalStateException If the method was called within a text block.
*/
// public void drawImage(PDInlineImage inlineImage, float x, float y, float width, float height) throws IOException
// {
// if (inTextMode)
// {
// throw new IllegalStateException("Error: drawImage is not allowed within a text block.");
// }
// saveGraphicsState();
// transform(new Matrix(width, 0, 0, height, x, y));
//
// // create the image dictionary
// StringBuilder sb = new StringBuilder();
// sb.append("BI");
//
// sb.append("\n /W ");
// sb.append(inlineImage.getWidth());
//
// sb.append("\n /H ");
// sb.append(inlineImage.getHeight());
//
// sb.append("\n /CS ");
// sb.append("/");
// sb.append(inlineImage.getColorSpace().getName());
//
// if (inlineImage.getDecode() != null && inlineImage.getDecode().size() > 0)
// {
// sb.append("\n /D ");
// sb.append("[");
// for (COSBase base : inlineImage.getDecode())
// {
// sb.append(((COSNumber)base).intValue());
// sb.append(" ");
// }
// sb.append("]");
// }
//
// if (inlineImage.isStencil())
// {
// sb.append("\n /IM true");
// }
// sb.append("\n /BPC ");
// sb.append(inlineImage.getBitsPerComponent());
//
// // image dictionary
// write(sb.toString());
// writeLine();
//
// // binary data
// writeOperator("ID");
// write(inlineImage.getData());
// writeLine();
// writeOperator("EI");
// restoreGraphicsState();
// } TODO: PdfBox-Android
/**
* Draw an xobject(form or image) at the x,y coordinates and a certain width and height.
*
* @param xobject The xobject to draw.
* @param x The x-coordinate to draw the image.
* @param y The y-coordinate to draw the image.
* @param width The width of the image to draw.
* @param height The height of the image to draw.
*
* @throws IOException If there is an error writing to the stream.
* @deprecated Use {@link #drawImage} instead.
*/
@Deprecated
public void drawXObject(PDXObject xobject, float x, float y, float width, float height) throws IOException
{
AffineTransform transform = new AffineTransform(width, 0, 0, height, x, y);
drawXObject(xobject, transform);
}
/**
* Draw an xobject(form or image) using the given {@link AffineTransform} to position
* the xobject.
*
* @param xobject The xobject to draw.
* @param transform the transformation matrix
* @throws IOException If there is an error writing to the stream.
* @throws IllegalStateException If the method was called within a text block.
* @deprecated Use {@link #drawImage} or {@link #drawForm} instead.
*/
@Deprecated
public void drawXObject(PDXObject xobject, AffineTransform transform) throws IOException
{
if (inTextMode)
{
throw new IllegalStateException("Error: drawXObject is not allowed within a text block.");
}
String xObjectPrefix;
if (xobject instanceof PDImageXObject)
{
xObjectPrefix = "Im";
}
else
{
xObjectPrefix = "Form";
}
COSName objMapping = resources.add(xobject, xObjectPrefix);
saveGraphicsState();
transform(new Matrix(transform));
writeOperand(objMapping);
writeOperator("Do");
restoreGraphicsState();
}
/**
* Draws the given Form XObject at the current location.
*
* @param form Form XObject
* @throws IOException if the content stream could not be written
* @throws IllegalStateException If the method was called within a text block.
*/
public void drawForm(PDFormXObject form) throws IOException
{
if (inTextMode)
{
throw new IllegalStateException("Error: drawForm is not allowed within a text block.");
}
writeOperand(resources.add(form));
writeOperator("Do");
}
/**
* The cm operator. Concatenates the current transformation matrix with the given values.
* @param a The a value of the matrix.
* @param b The b value of the matrix.
* @param c The c value of the matrix.
* @param d The d value of the matrix.
* @param e The e value of the matrix.
* @param f The f value of the matrix.
* @throws IOException If there is an error writing to the stream.
* @deprecated Use {@link #transform} instead.
*/
@Deprecated
public void concatenate2CTM(double a, double b, double c, double d, double e, double f) throws IOException
{
transform(new Matrix((float) a, (float) b, (float) c, (float) d, (float) e, (float) f));
}
/**
* The cm operator. Concatenates the current transformation matrix with the given
* {@link AffineTransform}.
* @param at the transformation matrix
* @throws IOException If there is an error writing to the stream.
* @deprecated Use {@link #transform} instead.
*/
@Deprecated
public void concatenate2CTM(AffineTransform at) throws IOException
{
transform(new Matrix(at));
}
/**
* The cm operator. Concatenates the given matrix with the CTM.
*
* @param matrix the transformation matrix
* @throws IOException If there is an error writing to the stream.
*/
public void transform(Matrix matrix) throws IOException
{
writeAffineTransform(matrix.createAffineTransform());
writeOperator("cm");
}
/**
* q operator. Saves the current graphics state.
* @throws IOException If an error occurs while writing to the stream.
*/
public void saveGraphicsState() throws IOException
{
if (!fontStack.isEmpty())
{
fontStack.push(fontStack.peek());
}
if (!strokingColorSpaceStack.isEmpty())
{
strokingColorSpaceStack.push(strokingColorSpaceStack.peek());
}
if (!nonStrokingColorSpaceStack.isEmpty())
{
nonStrokingColorSpaceStack.push(nonStrokingColorSpaceStack.peek());
}
writeOperator("q");
}
/**
* Q operator. Restores the current graphics state.
* @throws IOException If an error occurs while writing to the stream.
*/
public void restoreGraphicsState() throws IOException
{
if (!fontStack.isEmpty())
{
fontStack.pop();
}
if (!strokingColorSpaceStack.isEmpty())
{
strokingColorSpaceStack.pop();
}
if (!nonStrokingColorSpaceStack.isEmpty())
{
nonStrokingColorSpaceStack.pop();
}
writeOperator("Q");
}
/**
* Set the stroking color space. This will add the colorspace to the PDResources
* if necessary.
*
* @param colorSpace The colorspace to write.
* @throws IOException If there is an error writing the colorspace.
* @deprecated Use {@link #setStrokingColor} instead.
*/
@Deprecated
public void setStrokingColorSpace(PDColorSpace colorSpace) throws IOException
{
if (strokingColorSpaceStack.isEmpty())
{
strokingColorSpaceStack.add(colorSpace);
}
else
{
strokingColorSpaceStack.setElementAt(colorSpace, nonStrokingColorSpaceStack.size() - 1);
}
writeOperand(getName(colorSpace));
writeOperator("CS");
}
/**
* Set the stroking color space. This will add the colorspace to the PDResources
* if necessary.
*
* @param colorSpace The colorspace to write.
* @throws IOException If there is an error writing the colorspace.
* @deprecated Use {@link #setNonStrokingColor} instead.
*/
@Deprecated
public void setNonStrokingColorSpace(PDColorSpace colorSpace) throws IOException
{
if (nonStrokingColorSpaceStack.isEmpty())
{
nonStrokingColorSpaceStack.add(colorSpace);
}
else
{
nonStrokingColorSpaceStack.setElementAt(colorSpace, nonStrokingColorSpaceStack.size() - 1);
}
writeOperand(getName(colorSpace));
writeOperator("cs");
}
private COSName getName(PDColorSpace colorSpace) throws IOException
{
COSName key;
if (colorSpace instanceof PDDeviceGray ||
colorSpace instanceof PDDeviceRGB /*|| TODO: PdfBox-Android
colorSpace instanceof PDDeviceCMYK*/)
{
return COSName.getPDFName(colorSpace.getName());
}
else
{
return resources.add(colorSpace);
}
}
/**
* Sets the stroking color and, if necessary, the stroking color space.
*
* @param color Color in a specific color space.
* @throws IOException If an IO error occurs while writing to the stream.
*/
public void setStrokingColor(PDColor color) throws IOException
{
if (strokingColorSpaceStack.isEmpty() ||
strokingColorSpaceStack.peek() != color.getColorSpace())
{
writeOperand(getName(color.getColorSpace()));
writeOperator("CS");
if (strokingColorSpaceStack.isEmpty())
{
strokingColorSpaceStack.add(color.getColorSpace());
}
else
{
strokingColorSpaceStack.setElementAt(color.getColorSpace(),
nonStrokingColorSpaceStack.size() - 1);
}
}
for (float value : color.getComponents())
{
writeOperand(value);
}
// if (color.getColorSpace() instanceof PDPattern)
// {
// writeOperand(color.getPatternName());
// }
//
// if (color.getColorSpace() instanceof PDPattern ||
// color.getColorSpace() instanceof PDSeparation ||
// color.getColorSpace() instanceof PDDeviceN ||
// color.getColorSpace() instanceof PDICCBased)
// {
// writeOperator("SCN");
// }
// else
// {
writeOperator("SC");
// } TODO: PdfBox-Android
}
/**
* Set the stroking color using an AWT color. Conversion uses the default sRGB color space.
*
* @param color The color to set.
* @throws IOException If an IO error occurs while writing to the stream.
*/
public void setStrokingColor(AWTColor color) throws IOException
{
float[] components = new float[] {
color.getRed() / 255f, color.getGreen() / 255f, color.getBlue() / 255f };
PDColor pdColor = new PDColor(components, PDDeviceRGB.INSTANCE);
setStrokingColor(pdColor);
}
/**
* Set the color components of current stroking color space.
*
* @param components The components to set for the current color.
* @throws IOException If there is an error while writing to the stream.
* @deprecated Use {@link #setStrokingColor(PDColor)} instead.
*/
@Deprecated
public void setStrokingColor(float[] components) throws IOException
{
if (strokingColorSpaceStack.isEmpty())
{
throw new IllegalStateException("The color space must be set before setting a color");
}
for (int i = 0; i < components.length; i++)
{
writeOperand(components[i]);
}
PDColorSpace currentStrokingColorSpace = strokingColorSpaceStack.peek();
// if (currentStrokingColorSpace instanceof PDSeparation ||
// currentStrokingColorSpace instanceof PDPattern ||
// currentStrokingColorSpace instanceof PDICCBased)
// {
// writeOperator("SCN");
// }
// else
// {
writeOperator("SC");
// } TODO: PdfBox-Android
}
/**
* Set the stroking color in the DeviceRGB color space. Range is 0..255.
*
* @param r The red value.
* @param g The green value.
* @param b The blue value.
* @throws IOException If an IO error occurs while writing to the stream.
* @throws IllegalArgumentException If the parameters are invalid.
*/
public void setStrokingColor(int r, int g, int b) throws IOException
{
if (isOutside255Interval(r) || isOutside255Interval(g) || isOutside255Interval(b))
{
throw new IllegalArgumentException("Parameters must be within 0..255, but are "
+ String.format("(%d,%d,%d)", r, g, b));
}
writeOperand(r / 255f);
writeOperand(g / 255f);
writeOperand(b / 255f);
writeOperator("RG");
}
/**
* Set the stroking color in the DeviceCMYK color space. Range is 0..255.
*
* @param c The cyan value.
* @param m The magenta value.
* @param y The yellow value.
* @param k The black value.
* @throws IOException If an IO error occurs while writing to the stream.
* @throws IllegalArgumentException If the parameters are invalid.
* @deprecated Use {@link #setStrokingColor(float, float, float, float)} instead.
*/
@Deprecated
public void setStrokingColor(int c, int m, int y, int k) throws IOException
{
if (isOutside255Interval(c) || isOutside255Interval(m) || isOutside255Interval(y) || isOutside255Interval(k))
{
throw new IllegalArgumentException("Parameters must be within 0..255, but are "
+ String.format("(%d,%d,%d,%d)", c, m, y, k));
}
setStrokingColor(c / 255f, m / 255f, y / 255f, k / 255f);
}
/**
* Set the stroking color in the DeviceCMYK color space. Range is 0..1
*
* @param c The cyan value.
* @param m The magenta value.
* @param y The yellow value.
* @param k The black value.
* @throws IOException If an IO error occurs while writing to the stream.
* @throws IllegalArgumentException If the parameters are invalid.
*/
public void setStrokingColor(float c, float m, float y, float k) throws IOException
{
if (isOutsideOneInterval(c) || isOutsideOneInterval(m) || isOutsideOneInterval(y) || isOutsideOneInterval(k))
{
throw new IllegalArgumentException("Parameters must be within 0..1, but are "
+ String.format("(%.2f,%.2f,%.2f,%.2f)", c, m, y, k));
}
writeOperand(c);
writeOperand(m);
writeOperand(y);
writeOperand(k);
writeOperator("K");
}
/**
* Set the stroking color in the DeviceGray color space. Range is 0..255.
*
* @param g The gray value.
* @throws IOException If an IO error occurs while writing to the stream.
* @throws IllegalArgumentException If the parameter is invalid.
* @deprecated Use {@link #setStrokingColor(double)} instead.
*/
@Deprecated
public void setStrokingColor(int g) throws IOException
{
if (isOutside255Interval(g))
{
throw new IllegalArgumentException("Parameter must be within 0..255, but is " + g);
}
setStrokingColor(g / 255f);
}
/**
* Set the stroking color in the DeviceGray color space. Range is 0..1.
*
* @param g The gray value.
* @throws IOException If an IO error occurs while writing to the stream.
* @throws IllegalArgumentException If the parameter is invalid.
*/
public void setStrokingColor(double g) throws IOException
{
if (isOutsideOneInterval(g))
{
throw new IllegalArgumentException("Parameter must be within 0..1, but is " + g);
}
writeOperand((float) g);
writeOperator("G");
}
/**
* Sets the non-stroking color and, if necessary, the non-stroking color space.
*
* @param color Color in a specific color space.
* @throws IOException If an IO error occurs while writing to the stream.
*/
public void setNonStrokingColor(PDColor color) throws IOException
{
if (nonStrokingColorSpaceStack.isEmpty() ||
nonStrokingColorSpaceStack.peek() != color.getColorSpace())
{
writeOperand(getName(color.getColorSpace()));
writeOperator("cs");
if (nonStrokingColorSpaceStack.isEmpty())
{
nonStrokingColorSpaceStack.add(color.getColorSpace());
}
else
{
nonStrokingColorSpaceStack.setElementAt(color.getColorSpace(),
nonStrokingColorSpaceStack.size() - 1);
}
}
for (float value : color.getComponents())
{
writeOperand(value);
}
// if (color.getColorSpace() instanceof PDPattern)
// {
// writeOperand(color.getPatternName());
// }
//
// if (color.getColorSpace() instanceof PDPattern ||
// color.getColorSpace() instanceof PDSeparation ||
// color.getColorSpace() instanceof PDDeviceN ||
// color.getColorSpace() instanceof PDICCBased)
// {
// writeOperator("scn");
// }
// else
// {
writeOperator("sc");
// } TODO: PdfBox-Android
}
/**
* Set the non-stroking color using an AWT color. Conversion uses the default sRGB color space.
*
* @param color The color to set.
* @throws IOException If an IO error occurs while writing to the stream.
*/
public void setNonStrokingColor(AWTColor color) throws IOException
{
float[] components = new float[] {
color.getRed() / 255f, color.getGreen() / 255f, color.getBlue() / 255f };
PDColor pdColor = new PDColor(components, PDDeviceRGB.INSTANCE);
setNonStrokingColor(pdColor);
}
/**
* Set the color components of current non-stroking color space.
*
* @param components The components to set for the current color.
* @throws IOException If there is an error while writing to the stream.
* @deprecated Use {@link #setNonStrokingColor(PDColor)} instead.
*/
@Deprecated
public void setNonStrokingColor(float[] components) throws IOException
{
if (nonStrokingColorSpaceStack.isEmpty())
{
throw new IllegalStateException("The color space must be set before setting a color");
}
// for (int i = 0; i < components.length; i++)
// {
// writeOperator(components[i]);
// }
PDColorSpace currentNonStrokingColorSpace = nonStrokingColorSpaceStack.peek();
// if (currentNonStrokingColorSpace instanceof PDSeparation ||
// currentNonStrokingColorSpace instanceof PDPattern ||
// currentNonStrokingColorSpace instanceof PDICCBased)
// {
// writeOperator("scn");
// }
// else
// {
writeOperator("sc");
// } TODO: PdfBox-Android
}
/**
* Set the non-stroking color in the DeviceRGB color space. Range is 0..255.
*
* @param r The red value.
* @param g The green value.
* @param b The blue value.
* @throws IOException If an IO error occurs while writing to the stream.
* @throws IllegalArgumentException If the parameters are invalid.
*/
public void setNonStrokingColor(int r, int g, int b) throws IOException
{
if (isOutside255Interval(r) || isOutside255Interval(g) || isOutside255Interval(b))
{
throw new IllegalArgumentException("Parameters must be within 0..255, but are "
+ String.format("(%d,%d,%d)", r, g, b));
}
writeOperand(r / 255f);
writeOperand(g / 255f);
writeOperand(b / 255f);
writeOperator("rg");
}
/**
* Set the non-stroking color in the DeviceCMYK color space. Range is 0..255.
*
* @param c The cyan value.
* @param m The magenta value.
* @param y The yellow value.
* @param k The black value.
* @throws IOException If an IO error occurs while writing to the stream.
* @throws IllegalArgumentException If the parameters are invalid.
*/
public void setNonStrokingColor(int c, int m, int y, int k) throws IOException
{
if (isOutside255Interval(c) || isOutside255Interval(m) || isOutside255Interval(y) || isOutside255Interval(k))
{
throw new IllegalArgumentException("Parameters must be within 0..255, but are "
+ String.format("(%d,%d,%d,%d)", c, m, y, k));
}
setNonStrokingColor(c / 255f, m / 255f, y / 255f, k / 255f);
}
/**
* Set the non-stroking color in the DeviceRGB color space. Range is 0..1.
*
* @param c The cyan value.
* @param m The magenta value.
* @param y The yellow value.
* @param k The black value.
* @throws IOException If an IO error occurs while writing to the stream.
* @throws IllegalArgumentException If the parameters are invalid.
*/
public void setNonStrokingColor(double c, double m, double y, double k) throws IOException
{
if (isOutsideOneInterval(c) || isOutsideOneInterval(m) || isOutsideOneInterval(y) || isOutsideOneInterval(k))
{
throw new IllegalArgumentException("Parameters must be within 0..1, but are "
+ String.format("(%.2f,%.2f,%.2f,%.2f)", c, m, y, k));
}
writeOperand((float) c);
writeOperand((float) m);
writeOperand((float) y);
writeOperand((float) k);
writeOperator("k");
}
/**
* Set the non-stroking color in the DeviceGray color space. Range is 0..255.
*
* @param g The gray value.
* @throws IOException If an IO error occurs while writing to the stream.
* @throws IllegalArgumentException If the parameter is invalid.
*/
public void setNonStrokingColor(int g) throws IOException
{
if (isOutside255Interval(g))
{
throw new IllegalArgumentException("Parameter must be within 0..255, but is " + g);
}
setNonStrokingColor(g / 255f);
}
/**
* Set the non-stroking color in the DeviceGray color space. Range is 0..1.
*
* @param g The gray value.
* @throws IOException If an IO error occurs while writing to the stream.
* @throws IllegalArgumentException If the parameter is invalid.
*/
public void setNonStrokingColor(double g) throws IOException
{
if (isOutsideOneInterval(g))
{
throw new IllegalArgumentException("Parameter must be within 0..1, but is " + g);
}
writeOperand((float) g);
writeOperator("g");
}
/**
* Add a rectangle to the current path.
*
* @param x The lower left x coordinate.
* @param y The lower left y coordinate.
* @param width The width of the rectangle.
* @param height The height of the rectangle.
* @throws IOException If the content stream could not be written.
* @throws IllegalStateException If the method was called within a text block.
*/
public void addRect(float x, float y, float width, float height) throws IOException
{
if (inTextMode)
{
throw new IllegalStateException("Error: addRect is not allowed within a text block.");
}
writeOperand(x);
writeOperand(y);
writeOperand(width);
writeOperand(height);
writeOperator("re");
}
/**
* Draw a rectangle on the page using the current non stroking color.
*
* @param x The lower left x coordinate.
* @param y The lower left y coordinate.
* @param width The width of the rectangle.
* @param height The height of the rectangle.
* @throws IOException If there is an error while drawing on the screen.
* @throws IllegalStateException If the method was called within a text block.
* @deprecated Use {@link #addRect} followed by {@link #fill()} instead.
*/
@Deprecated
public void fillRect(float x, float y, float width, float height) throws IOException
{
if (inTextMode)
{
throw new IllegalStateException("Error: fillRect is not allowed within a text block.");
}
addRect(x, y, width, height);
fill();
}
/**
* Append a cubic Bézier curve to the current path. The curve extends from the current
* point to the point (x3 , y3 ), using (x1 , y1 ) and (x2 , y2 ) as the Bézier control points
* @param x1 x coordinate of the point 1
* @param y1 y coordinate of the point 1
* @param x2 x coordinate of the point 2
* @param y2 y coordinate of the point 2
* @param x3 x coordinate of the point 3
* @param y3 y coordinate of the point 3
* @throws IOException If there is an error while adding the .
* @deprecated Use {@link #curveTo} instead.
*/
@Deprecated
public void addBezier312(float x1, float y1, float x2, float y2, float x3, float y3) throws IOException
{
curveTo(x1, y1, x2, y2, x3, y3);
}
/**
* Append a cubic Bézier curve to the current path. The curve extends from the current point to
* the point (x3, y3), using (x1, y1) and (x2, y2) as the Bézier control points.
*
* @param x1 x coordinate of the point 1
* @param y1 y coordinate of the point 1
* @param x2 x coordinate of the point 2
* @param y2 y coordinate of the point 2
* @param x3 x coordinate of the point 3
* @param y3 y coordinate of the point 3
* @throws IOException If the content stream could not be written.
* @throws IllegalStateException If the method was called within a text block.
*/
public void curveTo(float x1, float y1, float x2, float y2, float x3, float y3) throws IOException
{
if (inTextMode)
{
throw new IllegalStateException("Error: curveTo is not allowed within a text block.");
}
writeOperand(x1);
writeOperand(y1);
writeOperand(x2);
writeOperand(y2);
writeOperand(x3);
writeOperand(y3);
writeOperator("c");
}
/**
* Append a cubic Bézier curve to the current path. The curve extends from the current
* point to the point (x3 , y3 ), using the current point and (x2 , y2 ) as the Bézier control points
* @param x2 x coordinate of the point 2
* @param y2 y coordinate of the point 2
* @param x3 x coordinate of the point 3
* @param y3 y coordinate of the point 3
* @throws IOException If there is an error while adding the .
* @deprecated Use {@link #curveTo2} instead.
*/
@Deprecated
public void addBezier32(float x2, float y2, float x3, float y3) throws IOException
{
curveTo2(x2, y2, x3, y3);
}
/**
* Append a cubic Bézier curve to the current path. The curve extends from the current point to
* the point (x3, y3), using the current point and (x2, y2) as the Bézier control points.
*
* @param x2 x coordinate of the point 2
* @param y2 y coordinate of the point 2
* @param x3 x coordinate of the point 3
* @param y3 y coordinate of the point 3
* @throws IOException If the content stream could not be written.
* @throws IllegalStateException If the method was called within a text block.
*/
public void curveTo2(float x2, float y2, float x3, float y3) throws IOException
{
if (inTextMode)
{
throw new IllegalStateException("Error: curveTo2 is not allowed within a text block.");
}
writeOperand(x2);
writeOperand(y2);
writeOperand(x3);
writeOperand(y3);
writeOperator("v");
}
/**
* Append a cubic Bézier curve to the current path. The curve extends from the current
* point to the point (x3 , y3 ), using (x1 , y1 ) and (x3 , y3 ) as the Bézier control points
* @param x1 x coordinate of the point 1
* @param y1 y coordinate of the point 1
* @param x3 x coordinate of the point 3
* @param y3 y coordinate of the point 3
* @throws IOException If there is an error while adding the .
* @deprecated Use {@link #curveTo1} instead.
*/
@Deprecated
public void addBezier31(float x1, float y1, float x3, float y3) throws IOException
{
curveTo1(x1, y1, x3, y3);
}
/**
* Append a cubic Bézier curve to the current path. The curve extends from the current point to
* the point (x3, y3), using (x1, y1) and (x3, y3) as the Bézier control points.
*
* @param x1 x coordinate of the point 1
* @param y1 y coordinate of the point 1
* @param x3 x coordinate of the point 3
* @param y3 y coordinate of the point 3
* @throws IOException If the content stream could not be written.
* @throws IllegalStateException If the method was called within a text block.
*/
public void curveTo1(float x1, float y1, float x3, float y3) throws IOException
{
if (inTextMode)
{
throw new IllegalStateException("Error: curveTo1 is not allowed within a text block.");
}
writeOperand(x1);
writeOperand(y1);
writeOperand(x3);
writeOperand(y3);
writeOperator("y");
}
/**
* Move the current position to the given coordinates.
*
* @param x The x coordinate.
* @param y The y coordinate.
* @throws IOException If the content stream could not be written.
* @throws IllegalStateException If the method was called within a text block.
*/
public void moveTo(float x, float y) throws IOException
{
if (inTextMode)
{
throw new IllegalStateException("Error: moveTo is not allowed within a text block.");
}
writeOperand(x);
writeOperand(y);
writeOperator("m");
}
/**
* Draw a line from the current position to the given coordinates.
*
* @param x The x coordinate.
* @param y The y coordinate.
* @throws IOException If the content stream could not be written.
* @throws IllegalStateException If the method was called within a text block.
*/
public void lineTo(float x, float y) throws IOException
{
if (inTextMode)
{
throw new IllegalStateException("Error: lineTo is not allowed within a text block.");
}
writeOperand(x);
writeOperand(y);
writeOperator("l");
}
/**
* add a line to the current path.
*
* @param xStart The start x coordinate.
* @param yStart The start y coordinate.
* @param xEnd The end x coordinate.
* @param yEnd The end y coordinate.
* @throws IOException If there is an error while adding the line.
* @throws IllegalStateException If the method was called within a text block.
* @deprecated Use {@link #moveTo} followed by {@link #lineTo}.
*/
@Deprecated
public void addLine(float xStart, float yStart, float xEnd, float yEnd) throws IOException
{
if (inTextMode)
{
throw new IllegalStateException("Error: addLine is not allowed within a text block.");
}
moveTo(xStart, yStart);
lineTo(xEnd, yEnd);
}
/**
* Draw a line on the page using the current non stroking color and the current line width.
*
* @param xStart The start x coordinate.
* @param yStart The start y coordinate.
* @param xEnd The end x coordinate.
* @param yEnd The end y coordinate.
* @throws IOException If there is an error while drawing on the screen.
* @throws IllegalStateException If the method was called within a text block.
* @deprecated Use {@link #moveTo} followed by {@link #lineTo} followed by {@link #stroke}.
*/
@Deprecated
public void drawLine(float xStart, float yStart, float xEnd, float yEnd) throws IOException
{
if (inTextMode)
{
throw new IllegalStateException("Error: drawLine is not allowed within a text block.");
}
moveTo(xStart, yStart);
lineTo(xEnd, yEnd);
stroke();
}
/**
* Add a polygon to the current path.
* @param x x coordinate of each points
* @param y y coordinate of each points
* @throws IOException If there is an error while drawing on the screen.
* @throws IllegalStateException If the method was called within a text block.
* @throws IllegalArgumentException If the two arrays have different lengths.
* @deprecated Use {@link #moveTo} and {@link #lineTo} methods instead.
*/
@Deprecated
public void addPolygon(float[] x, float[] y) throws IOException
{
if (inTextMode)
{
throw new IllegalStateException("Error: addPolygon is not allowed within a text block.");
}
if (x.length != y.length)
{
throw new IllegalArgumentException("Error: some points are missing coordinate");
}
for (int i = 0; i < x.length; i++)
{
if (i == 0)
{
moveTo(x[i], y[i]);
}
else
{
lineTo(x[i], y[i]);
}
}
closeSubPath();
}
/**
* Draw a polygon on the page using the current non stroking color.
* @param x x coordinate of each points
* @param y y coordinate of each points
* @throws IOException If there is an error while drawing on the screen.
* @throws IllegalStateException If the method was called within a text block.
* @deprecated Use {@link #moveTo} and {@link #lineTo} methods instead.
*/
@Deprecated
public void drawPolygon(float[] x, float[] y) throws IOException
{
if (inTextMode)
{
throw new IllegalStateException("Error: drawPolygon is not allowed within a text block.");
}
addPolygon(x, y);
stroke();
}
/**
* Draw and fill a polygon on the page using the current non stroking color.
* @param x x coordinate of each points
* @param y y coordinate of each points
* @throws IOException If there is an error while drawing on the screen.
* @throws IllegalStateException If the method was called within a text block.
* @deprecated Use {@link #moveTo} and {@link #lineTo} methods instead.
*/
@Deprecated
public void fillPolygon(float[] x, float[] y) throws IOException
{
if (inTextMode)
{
throw new IllegalStateException("Error: fillPolygon is not allowed within a text block.");
}
addPolygon(x, y);
fill();
}
/**
* Stroke the path.
*
* @throws IOException If the content stream could not be written
* @throws IllegalStateException If the method was called within a text block.
*/
public void stroke() throws IOException
{
if (inTextMode)
{
throw new IllegalStateException("Error: stroke is not allowed within a text block.");
}
writeOperator("S");
}
/**
* Close and stroke the path.
*
* @throws IOException If the content stream could not be written
* @throws IllegalStateException If the method was called within a text block.
*/
public void closeAndStroke() throws IOException
{
if (inTextMode)
{
throw new IllegalStateException("Error: closeAndStroke is not allowed within a text block.");
}
writeOperator("s");
}
/**
* Fill the path.
*
* @param windingRule the winding rule to be used for filling
* @throws IOException If the content stream could not be written
* @throws IllegalArgumentException If the parameter is not a valid winding rule.
* @deprecated Use {@link #fill()} or {@link #fillEvenOdd} instead.
*/
@Deprecated
public void fill(Path.FillType windingRule) throws IOException
{
if (windingRule == Path.FillType.WINDING)
{
fill();
}
else if (windingRule == Path.FillType.EVEN_ODD)
{
fillEvenOdd();
}
else
{
throw new IllegalArgumentException("Error: unknown value for winding rule");
}
}
/**
* Fills the path using the nonzero winding rule.
*
* @throws IOException If the content stream could not be written
* @throws IllegalStateException If the method was called within a text block.
*/
public void fill() throws IOException
{
if (inTextMode)
{
throw new IllegalStateException("Error: fill is not allowed within a text block.");
}
writeOperator("f");
}
/**
* Fills the path using the even-odd winding rule.
*
* @throws IOException If the content stream could not be written
* @throws IllegalStateException If the method was called within a text block.
*/
public void fillEvenOdd() throws IOException
{
if (inTextMode)
{
throw new IllegalStateException("Error: fill is not allowed within a text block.");
}
writeOperator("f*");
}
/**
* Fills the clipping area with the given shading.
*
* @param shading Shading resource
* @throws IOException If the content stream could not be written
* @throws IllegalStateException If the method was called within a text block.
*/
public void shadingFill(PDShading shading) throws IOException
{
if (inTextMode)
{
throw new IllegalStateException("Error: shadingFill is not allowed within a text block.");
}
writeOperand(resources.add(shading));
writeOperator("sh");
}
/**
* Closes the current subpath.
*
* @throws IOException If the content stream could not be written
* @deprecated Use {@link #closePath()} instead.
*/
@Deprecated
public void closeSubPath() throws IOException
{
closePath();
}
/**
* Closes the current subpath.
*
* @throws IOException If the content stream could not be written
* @throws IllegalStateException If the method was called within a text block.
*/
public void closePath() throws IOException
{
if (inTextMode)
{
throw new IllegalStateException("Error: closePath is not allowed within a text block.");
}
writeOperator("h");
}
/**
* Clip path.
*
* @param windingRule the winding rule to be used for clipping
*
* @throws IOException If there is an error while clipping the path.
* @throws IllegalStateException If the method was called within a text block.
* @deprecated Use {@link #clip()} or {@link #clipEvenOdd} instead.
*/
@Deprecated
public void clipPath(Path.FillType windingRule) throws IOException
{
if (inTextMode)
{
throw new IllegalStateException("Error: clipPath is not allowed within a text block.");
}
if (windingRule == Path.FillType.WINDING)
{
writeOperator("W");
}
else if (windingRule == Path.FillType.EVEN_ODD)
{
writeOperator("W");
}
else
{
throw new IllegalArgumentException("Error: unknown value for winding rule");
}
writeOperator("n");
}
/**
* Intersects the current clipping path with the current path, using the nonzero rule.
*
* @throws IOException If the content stream could not be written
* @throws IllegalStateException If the method was called within a text block.
*/
public void clip() throws IOException
{
if (inTextMode)
{
throw new IllegalStateException("Error: clip is not allowed within a text block.");
}
writeOperator("W");
// end path without filling or stroking
writeOperator("n");
}
/**
* Intersects the current clipping path with the current path, using the even-odd rule.
*
* @throws IOException If the content stream could not be written
* @throws IllegalStateException If the method was called within a text block.
*/
public void clipEvenOdd() throws IOException
{
if (inTextMode)
{
throw new IllegalStateException("Error: clipEvenOdd is not allowed within a text block.");
}
writeOperator("W*");
// end path without filling or stroking
writeOperator("n");
}
/**
* Set line width to the given value.
*
* @param lineWidth The width which is used for drwaing.
* @throws IOException If the content stream could not be written
* @throws IllegalStateException If the method was called within a text block.
*/
public void setLineWidth(float lineWidth) throws IOException
{
if (inTextMode)
{
throw new IllegalStateException("Error: setLineWidth is not allowed within a text block.");
}
writeOperand(lineWidth);
writeOperator("w");
}
/**
* Set the line join style.
* @param lineJoinStyle 0 for miter join, 1 for round join, and 2 for bevel join.
* @throws IOException If the content stream could not be written.
* @throws IllegalStateException If the method was called within a text block.
* @throws IllegalArgumentException If the parameter is not a valid line join style.
*/
public void setLineJoinStyle(int lineJoinStyle) throws IOException
{
if (inTextMode)
{
throw new IllegalStateException("Error: setLineJoinStyle is not allowed within a text block.");
}
if (lineJoinStyle >= 0 && lineJoinStyle <= 2)
{
writeOperand(lineJoinStyle);
writeOperator("j");
}
else
{
throw new IllegalArgumentException("Error: unknown value for line join style");
}
}
/**
* Set the line cap style.
* @param lineCapStyle 0 for butt cap, 1 for round cap, and 2 for projecting square cap.
* @throws IOException If the content stream could not be written.
* @throws IllegalStateException If the method was called within a text block.
* @throws IllegalArgumentException If the parameter is not a valid line cap style.
*/
public void setLineCapStyle(int lineCapStyle) throws IOException
{
if (inTextMode)
{
throw new IllegalStateException("Error: setLineCapStyle is not allowed within a text block.");
}
if (lineCapStyle >= 0 && lineCapStyle <= 2)
{
writeOperand(lineCapStyle);
writeOperator("J");
}
else
{
throw new IllegalArgumentException("Error: unknown value for line cap style");
}
}
/**
* Set the line dash pattern.
* @param pattern The pattern array
* @param phase The phase of the pattern
* @throws IOException If the content stream could not be written.
* @throws IllegalStateException If the method was called within a text block.
*/
public void setLineDashPattern(float[] pattern, float phase) throws IOException
{
if (inTextMode)
{
throw new IllegalStateException("Error: setLineDashPattern is not allowed within a text block.");
}
write("[");
for (float value : pattern)
{
writeOperand(value);
}
write("] ");
writeOperand(phase);
writeOperator("d");
}
/**
* Begin a marked content sequence.
*
* @param tag the tag
* @throws IOException if an I/O error occurs
* @deprecated Use {@link #beginMarkedContent} instead.
*/
@Deprecated
public void beginMarkedContentSequence(COSName tag) throws IOException
{
beginMarkedContent(tag);
}
/**
* Begin a marked content sequence.
*
* @param tag the tag
* @throws IOException If the content stream could not be written
*/
public void beginMarkedContent(COSName tag) throws IOException
{
writeOperand(tag);
writeOperator("BMC");
}
/**
* Begin a marked content sequence with a reference to an entry in the page resources'
* Properties dictionary.
*
* @param tag the tag
* @param propsName the properties reference
* @throws IOException if an I/O error occurs
* @deprecated Use {@link #beginMarkedContent(COSName, PDPropertyList)} instead.
*/
@Deprecated
public void beginMarkedContentSequence(COSName tag, COSName propsName) throws IOException
{
writeOperand(tag);
writeOperand(propsName);
writeOperator("BDC");
}
/**
* Begin a marked content sequence with a reference to an entry in the page resources'
* Properties dictionary.
*
* @param tag the tag
* @param propertyList property list
* @throws IOException If the content stream could not be written
*/
public void beginMarkedContent(COSName tag, PDPropertyList propertyList) throws IOException
{
writeOperand(tag);
writeOperand(resources.add(propertyList));
writeOperator("BDC");
}
/**
* End a marked content sequence.
*
* @throws IOException If the content stream could not be written
* @deprecated Use {@link #endMarkedContent} instead.
*/
@Deprecated
public void endMarkedContentSequence() throws IOException
{
endMarkedContent();
}
/**
* End a marked content sequence.
*
* @throws IOException If the content stream could not be written
*/
public void endMarkedContent() throws IOException
{
writeOperator("EMC");
}
/**
* 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 This method will be removed in a future release.
*/
@Deprecated
public void appendRawCommands(String commands) throws IOException
{
output.write(commands.getBytes(Charsets.US_ASCII));
}
/**
* 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 This method will be removed in a future release.
*/
@Deprecated
public void appendRawCommands(byte[] commands) throws IOException
{
output.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 This method will be removed in a future release.
*/
@Deprecated
public void appendRawCommands(int data) throws IOException
{
output.write(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 This method will be removed in a future release.
*/
@Deprecated
public void appendRawCommands(double data) throws IOException
{
output.write(formatDecimal.format(data).getBytes(Charsets.US_ASCII));
}
/**
* 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 This method will be removed in a future release.
*/
@Deprecated
public void appendRawCommands(float data) throws IOException
{
output.write(formatDecimal.format(data).getBytes(Charsets.US_ASCII));
}
/**
* This will append a {@link COSName} to the content stream.
*
* @param name the name
* @throws IOException If an error occurs while writing to the stream.
* @deprecated This method will be removed in a future release.
*/
@Deprecated
public void appendCOSName(COSName name) throws IOException
{
name.writePDF(output);
}
/**
* Set an extended graphics state,
*
* @param state The extended graphics state.
* @throws IOException If the content stream could not be written.
*/
public void setGraphicsStateParameters(PDExtendedGraphicsState state) throws IOException
{
writeOperand(resources.add(state));
writeOperator("gs");
}
/**
* Writes a real real to the content stream.
*/
private void writeOperand(float real) throws IOException
{
writeOperator(formatDecimal.format(real));
output.write(' ');
}
/**
* Writes a real number to the content stream.
*/
private void writeOperand(int integer) throws IOException
{
writeOperator(formatDecimal.format(integer));
output.write(' ');
}
/**
* Writes a COSName to the content stream.
*/
private void writeOperand(COSName name) throws IOException
{
name.writePDF(output);
output.write(' ');
}
/**
* Writes a string to the content stream as ASCII.
*/
private void writeOperator(String text) throws IOException
{
output.write(text.getBytes(Charsets.US_ASCII));
output.write('\n');
}
/**
* Writes a string to the content stream as ASCII.
*/
private void write(String text) throws IOException
{
output.write(text.getBytes(Charsets.US_ASCII));
}
/**
* Writes a string to the content stream as ASCII.
*/
private void writeLine() throws IOException
{
output.write('\n');
}
/**
* Writes binary data to the content stream.
*/
private void writeBytes(byte[] data) throws IOException
{
output.write(data);
}
/**
* Writes an AffineTransform to the content stream as an array.
*/
private void writeAffineTransform(AffineTransform transform) throws IOException
{
double[] values = new double[6];
transform.getMatrix(values);
for (double v : values)
{
writeOperand((float) v);
}
}
/**
* Close the content stream. This must be called when you are done with this object.
* @throws IOException If the underlying stream has a problem being written to.
*/
@Override
public void close() throws IOException
{
output.close();
}
private boolean isOutside255Interval(int val)
{
return val < 0 || val > 255;
}
private boolean isOutsideOneInterval(double val)
{
return val < 0 || val > 1;
}
}