com.itextpdf.text.pdf.PdfContentByte Maven / Gradle / Ivy
Show all versions of itextpdf Show documentation
/*
* $Id: PdfContentByte.java 6434 2014-06-24 15:21:19Z achingarev $
*
* This file is part of the iText (R) project.
* Copyright (c) 1998-2014 iText Group NV
* Authors: Bruno Lowagie, Paulo Soares, et al.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License version 3
* as published by the Free Software Foundation with the addition of the
* following permission added to Section 15 as permitted in Section 7(a):
* FOR ANY PART OF THE COVERED WORK IN WHICH THE COPYRIGHT IS OWNED BY
* ITEXT GROUP. ITEXT GROUP DISCLAIMS THE WARRANTY OF NON INFRINGEMENT
* OF THIRD PARTY RIGHTS
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU Affero General Public License for more details.
* You should have received a copy of the GNU Affero General Public License
* along with this program; if not, see http://www.gnu.org/licenses or write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA, 02110-1301 USA, or download the license from the following URL:
* http://itextpdf.com/terms-of-use/
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License.
*
* In accordance with Section 7(b) of the GNU Affero General Public License,
* a covered work must retain the producer line in every PDF that is created
* or manipulated using iText.
*
* You can be released from the requirements of the license by purchasing
* a commercial license. Buying such a license is mandatory as soon as you
* develop commercial activities involving the iText software without
* disclosing the source code of your own applications.
* These activities include: offering paid services to customers as an ASP,
* serving PDFs on the fly in a web application, shipping iText with a closed
* source product.
*
* For more information, please contact iText Software Corp. at this
* address: [email protected]
*/
package com.itextpdf.text.pdf;
import com.itextpdf.awt.FontMapper;
import com.itextpdf.awt.PdfGraphics2D;
import com.itextpdf.awt.PdfPrinterGraphics2D;
import com.itextpdf.awt.geom.AffineTransform;
import com.itextpdf.awt.geom.Point2D;
import com.itextpdf.text.*;
import com.itextpdf.text.error_messages.MessageLocalization;
import com.itextpdf.text.exceptions.IllegalPdfSyntaxException;
import com.itextpdf.text.pdf.interfaces.IAccessibleElement;
import com.itextpdf.text.pdf.internal.PdfAnnotationsImp;
import com.itextpdf.text.pdf.internal.PdfIsoKeys;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
/**
* PdfContentByte
is an object containing the user positioned
* text and graphic contents of a page. It knows how to apply the proper
* font encoding.
*/
public class PdfContentByte {
/**
* This class keeps the graphic state of the current page
*/
static public class GraphicState {
/** This is the font in use */
FontDetails fontDetails;
/** This is the color in use */
ColorDetails colorDetails;
/** This is the font size in use */
float size;
/** The x position of the text line matrix. */
protected float xTLM = 0;
/** The y position of the text line matrix. */
protected float yTLM = 0;
protected float aTLM = 1;
protected float bTLM = 0;
protected float cTLM = 0;
protected float dTLM = 1;
protected float tx = 0;
/** The current text leading. */
protected float leading = 0;
/** The current horizontal scaling */
protected float scale = 100;
/** The current character spacing */
protected float charSpace = 0;
/** The current word spacing */
protected float wordSpace = 0;
protected BaseColor textColorFill = new GrayColor(0);
protected BaseColor colorFill = new GrayColor(0);
protected BaseColor textColorStroke = new GrayColor(0);
protected BaseColor colorStroke = new GrayColor(0);
protected int textRenderMode = TEXT_RENDER_MODE_FILL;
protected AffineTransform CTM = new AffineTransform();
protected PdfObject extGState = null;
GraphicState() {
}
GraphicState(final GraphicState cp) {
copyParameters(cp);
}
void copyParameters(final GraphicState cp) {
fontDetails = cp.fontDetails;
colorDetails = cp.colorDetails;
size = cp.size;
xTLM = cp.xTLM;
yTLM = cp.yTLM;
aTLM = cp.aTLM;
bTLM = cp.bTLM;
cTLM = cp.cTLM;
dTLM = cp.dTLM;
tx = cp.tx;
leading = cp.leading;
scale = cp.scale;
charSpace = cp.charSpace;
wordSpace = cp.wordSpace;
textColorFill = cp.textColorFill;
colorFill = cp.colorFill;
textColorStroke = cp.textColorStroke;
colorStroke = cp.colorStroke;
CTM = new AffineTransform(cp.CTM);
textRenderMode = cp.textRenderMode;
extGState = cp.extGState;
}
void restore(final GraphicState restore) {
copyParameters(restore);
}
}
/** The alignment is center */
public static final int ALIGN_CENTER = Element.ALIGN_CENTER;
/** The alignment is left */
public static final int ALIGN_LEFT = Element.ALIGN_LEFT;
/** The alignment is right */
public static final int ALIGN_RIGHT = Element.ALIGN_RIGHT;
/** A possible line cap value */
public static final int LINE_CAP_BUTT = 0;
/** A possible line cap value */
public static final int LINE_CAP_ROUND = 1;
/** A possible line cap value */
public static final int LINE_CAP_PROJECTING_SQUARE = 2;
/** A possible line join value */
public static final int LINE_JOIN_MITER = 0;
/** A possible line join value */
public static final int LINE_JOIN_ROUND = 1;
/** A possible line join value */
public static final int LINE_JOIN_BEVEL = 2;
/** A possible text rendering value */
public static final int TEXT_RENDER_MODE_FILL = 0;
/** A possible text rendering value */
public static final int TEXT_RENDER_MODE_STROKE = 1;
/** A possible text rendering value */
public static final int TEXT_RENDER_MODE_FILL_STROKE = 2;
/** A possible text rendering value */
public static final int TEXT_RENDER_MODE_INVISIBLE = 3;
/** A possible text rendering value */
public static final int TEXT_RENDER_MODE_FILL_CLIP = 4;
/** A possible text rendering value */
public static final int TEXT_RENDER_MODE_STROKE_CLIP = 5;
/** A possible text rendering value */
public static final int TEXT_RENDER_MODE_FILL_STROKE_CLIP = 6;
/** A possible text rendering value */
public static final int TEXT_RENDER_MODE_CLIP = 7;
private static final float[] unitRect = {0, 0, 0, 1, 1, 0, 1, 1};
// membervariables
/** This is the actual content */
protected ByteBuffer content = new ByteBuffer();
protected int markedContentSize = 0;
/** This is the writer */
protected PdfWriter writer;
/** This is the PdfDocument */
protected PdfDocument pdf;
/** This is the GraphicState in use */
protected GraphicState state = new GraphicState();
/** The list were we save/restore the state */
protected ArrayList stateList = new ArrayList();
/** The list were we save/restore the layer depth */
protected ArrayList layerDepth;
/** The separator between commands.
*/
protected int separator = '\n';
private int mcDepth = 0;
private boolean inText = false;
private static HashMap abrev = new HashMap();
private ArrayList mcElements = new ArrayList();
protected PdfContentByte duplicatedFrom = null;
static {
abrev.put(PdfName.BITSPERCOMPONENT, "/BPC ");
abrev.put(PdfName.COLORSPACE, "/CS ");
abrev.put(PdfName.DECODE, "/D ");
abrev.put(PdfName.DECODEPARMS, "/DP ");
abrev.put(PdfName.FILTER, "/F ");
abrev.put(PdfName.HEIGHT, "/H ");
abrev.put(PdfName.IMAGEMASK, "/IM ");
abrev.put(PdfName.INTENT, "/Intent ");
abrev.put(PdfName.INTERPOLATE, "/I ");
abrev.put(PdfName.WIDTH, "/W ");
}
// constructors
/**
* Constructs a new PdfContentByte
-object.
*
* @param wr the writer associated to this content
*/
public PdfContentByte(final PdfWriter wr) {
if (wr != null) {
writer = wr;
pdf = writer.getPdfDocument();
}
}
// methods to get the content of this object
/**
* Returns the String
representation of this PdfContentByte
-object.
*
* @return a String
*/
@Override
public String toString() {
return content.toString();
}
/**
* Checks if the content needs to be tagged.
* @return false if no tags need to be added
*/
public boolean isTagged() {
return writer != null && writer.isTagged();
}
/**
* Gets the internal buffer.
* @return the internal buffer
*/
public ByteBuffer getInternalBuffer() {
return content;
}
/** Returns the PDF representation of this PdfContentByte
-object.
*
* @param writer the PdfWriter
* @return a byte
array with the representation
*/
public byte[] toPdf(final PdfWriter writer) {
sanityCheck();
return content.toByteArray();
}
// methods to add graphical content
/**
* Adds the content of another PdfContentByte
-object to this object.
*
* @param other another PdfByteContent
-object
*/
public void add(final PdfContentByte other) {
if (other.writer != null && writer != other.writer)
throw new RuntimeException(MessageLocalization.getComposedMessage("inconsistent.writers.are.you.mixing.two.documents"));
content.append(other.content);
markedContentSize += other.markedContentSize;
}
/**
* Gets the x position of the text line matrix.
*
* @return the x position of the text line matrix
*/
public float getXTLM() {
return state.xTLM;
}
/**
* Gets the y position of the text line matrix.
*
* @return the y position of the text line matrix
*/
public float getYTLM() {
return state.yTLM;
}
/**
* Gets the current text leading.
*
* @return the current text leading
*/
public float getLeading() {
return state.leading;
}
/**
* Gets the current character spacing.
*
* @return the current character spacing
*/
public float getCharacterSpacing() {
return state.charSpace;
}
/**
* Gets the current word spacing.
*
* @return the current word spacing
*/
public float getWordSpacing() {
return state.wordSpace;
}
/**
* Gets the current character spacing.
*
* @return the current character spacing
*/
public float getHorizontalScaling() {
return state.scale;
}
/**
* Changes the Flatness.
*
* Flatness sets the maximum permitted distance in device pixels between the
* mathematically correct path and an approximation constructed from straight line segments.
*
* @param flatness a value
*/
public void setFlatness(final float flatness) {
if (flatness >= 0 && flatness <= 100) {
content.append(flatness).append(" i").append_i(separator);
}
}
/**
* Changes the Line cap style.
*
* The line cap style specifies the shape to be used at the end of open subpaths
* when they are stroked.
* Allowed values are LINE_CAP_BUTT, LINE_CAP_ROUND and LINE_CAP_PROJECTING_SQUARE.
*
* @param style a value
*/
public void setLineCap(final int style) {
if (style >= 0 && style <= 2) {
content.append(style).append(" J").append_i(separator);
}
}
/**
* Set the rendering intent, possible values are: PdfName.ABSOLUTECOLORIMETRIC,
* PdfName.RELATIVECOLORIMETRIC, PdfName.SATURATION, PdfName.PERCEPTUAL.
* @param ri
*/
public void setRenderingIntent(PdfName ri) {
content.append(ri.getBytes()).append(" ri").append_i(separator);
}
/**
* Changes the value of the line dash pattern.
*
* The line dash pattern controls the pattern of dashes and gaps used to stroke paths.
* It is specified by an array and a phase. The array specifies the length
* of the alternating dashes and gaps. The phase specifies the distance into the dash
* pattern to start the dash.
*
* @param phase the value of the phase
*/
public void setLineDash(final float phase) {
content.append("[] ").append(phase).append(" d").append_i(separator);
}
/**
* Changes the value of the line dash pattern.
*
* The line dash pattern controls the pattern of dashes and gaps used to stroke paths.
* It is specified by an array and a phase. The array specifies the length
* of the alternating dashes and gaps. The phase specifies the distance into the dash
* pattern to start the dash.
*
* @param phase the value of the phase
* @param unitsOn the number of units that must be 'on' (equals the number of units that must be 'off').
*/
public void setLineDash(final float unitsOn, final float phase) {
content.append("[").append(unitsOn).append("] ").append(phase).append(" d").append_i(separator);
}
/**
* Changes the value of the line dash pattern.
*
* The line dash pattern controls the pattern of dashes and gaps used to stroke paths.
* It is specified by an array and a phase. The array specifies the length
* of the alternating dashes and gaps. The phase specifies the distance into the dash
* pattern to start the dash.
*
* @param phase the value of the phase
* @param unitsOn the number of units that must be 'on'
* @param unitsOff the number of units that must be 'off'
*/
public void setLineDash(final float unitsOn, final float unitsOff, final float phase) {
content.append("[").append(unitsOn).append(' ').append(unitsOff).append("] ").append(phase).append(" d").append_i(separator);
}
/**
* Changes the value of the line dash pattern.
*
* The line dash pattern controls the pattern of dashes and gaps used to stroke paths.
* It is specified by an array and a phase. The array specifies the length
* of the alternating dashes and gaps. The phase specifies the distance into the dash
* pattern to start the dash.
*
* @param array length of the alternating dashes and gaps
* @param phase the value of the phase
*/
public final void setLineDash(final float[] array, final float phase) {
content.append("[");
for (int i = 0; i < array.length; i++) {
content.append(array[i]);
if (i < array.length - 1) content.append(' ');
}
content.append("] ").append(phase).append(" d").append_i(separator);
}
/**
* Changes the Line join style.
*
* The line join style specifies the shape to be used at the corners of paths
* that are stroked.
* Allowed values are LINE_JOIN_MITER (Miter joins), LINE_JOIN_ROUND (Round joins) and LINE_JOIN_BEVEL (Bevel joins).
*
* @param style a value
*/
public void setLineJoin(final int style) {
if (style >= 0 && style <= 2) {
content.append(style).append(" j").append_i(separator);
}
}
/**
* Changes the line width.
*
* The line width specifies the thickness of the line used to stroke a path and is measured
* in user space units.
*
* @param w a width
*/
public void setLineWidth(final float w) {
content.append(w).append(" w").append_i(separator);
}
/**
* Changes the Miter limit.
*
* When two line segments meet at a sharp angle and mitered joins have been specified as the
* line join style, it is possible for the miter to extend far beyond the thickness of the line
* stroking path. The miter limit imposes a maximum on the ratio of the miter length to the line
* witdh. When the limit is exceeded, the join is converted from a miter to a bevel.
*
* @param miterLimit a miter limit
*/
public void setMiterLimit(final float miterLimit) {
if (miterLimit > 1) {
content.append(miterLimit).append(" M").append_i(separator);
}
}
/**
* Modify the current clipping path by intersecting it with the current path, using the
* nonzero winding number rule to determine which regions lie inside the clipping
* path.
*/
public void clip() {
if (inText && isTagged()) {
endText();
}
content.append("W").append_i(separator);
}
/**
* Modify the current clipping path by intersecting it with the current path, using the
* even-odd rule to determine which regions lie inside the clipping path.
*/
public void eoClip() {
if (inText && isTagged()) {
endText();
}
content.append("W*").append_i(separator);
}
/**
* Changes the currentgray tint for filling paths (device dependent colors!).
*
* Sets the color space to DeviceGray (or the DefaultGray color space),
* and sets the gray tint to use for filling paths.
*
* @param gray a value between 0 (black) and 1 (white)
*/
public void setGrayFill(final float gray) {
saveColor(new GrayColor(gray), true);
content.append(gray).append(" g").append_i(separator);
}
/**
* Changes the current gray tint for filling paths to black.
*/
public void resetGrayFill() {
saveColor(new GrayColor(0), true);
content.append("0 g").append_i(separator);
}
/**
* Changes the currentgray tint for stroking paths (device dependent colors!).
*
* Sets the color space to DeviceGray (or the DefaultGray color space),
* and sets the gray tint to use for stroking paths.
*
* @param gray a value between 0 (black) and 1 (white)
*/
public void setGrayStroke(final float gray) {
saveColor(new GrayColor(gray), false);
content.append(gray).append(" G").append_i(separator);
}
/**
* Changes the current gray tint for stroking paths to black.
*/
public void resetGrayStroke() {
saveColor(new GrayColor(0), false);
content.append("0 G").append_i(separator);
}
/**
* Helper to validate and write the RGB color components
* @param red the intensity of red. A value between 0 and 1
* @param green the intensity of green. A value between 0 and 1
* @param blue the intensity of blue. A value between 0 and 1
*/
private void HelperRGB(float red, float green, float blue) {
if (red < 0)
red = 0.0f;
else if (red > 1.0f)
red = 1.0f;
if (green < 0)
green = 0.0f;
else if (green > 1.0f)
green = 1.0f;
if (blue < 0)
blue = 0.0f;
else if (blue > 1.0f)
blue = 1.0f;
content.append(red).append(' ').append(green).append(' ').append(blue);
}
/**
* Changes the current color for filling paths (device dependent colors!).
*
* Sets the color space to DeviceRGB (or the DefaultRGB color space),
* and sets the color to use for filling paths.
*
* Following the PDF manual, each operand must be a number between 0 (minimum intensity) and
* 1 (maximum intensity).
*
* @param red the intensity of red. A value between 0 and 1
* @param green the intensity of green. A value between 0 and 1
* @param blue the intensity of blue. A value between 0 and 1
*/
public void setRGBColorFillF(final float red, final float green, final float blue) {
saveColor(new BaseColor(red, green, blue), true);
HelperRGB(red, green, blue);
content.append(" rg").append_i(separator);
}
/**
* Changes the current color for filling paths to black.
*/
public void resetRGBColorFill() {
resetGrayFill();
}
/**
* Changes the current color for stroking paths (device dependent colors!).
*
* Sets the color space to DeviceRGB (or the DefaultRGB color space),
* and sets the color to use for stroking paths.
*
* Following the PDF manual, each operand must be a number between 0 (miniumum intensity) and
* 1 (maximum intensity).
*
* @param red the intensity of red. A value between 0 and 1
* @param green the intensity of green. A value between 0 and 1
* @param blue the intensity of blue. A value between 0 and 1
*/
public void setRGBColorStrokeF(final float red, final float green, final float blue) {
saveColor(new BaseColor(red, green, blue), false);
HelperRGB(red, green, blue);
content.append(" RG").append_i(separator);
}
/**
* Changes the current color for stroking paths to black.
*
*/
public void resetRGBColorStroke() {
resetGrayStroke();
}
/**
* Helper to validate and write the CMYK color components.
*
* @param cyan the intensity of cyan. A value between 0 and 1
* @param magenta the intensity of magenta. A value between 0 and 1
* @param yellow the intensity of yellow. A value between 0 and 1
* @param black the intensity of black. A value between 0 and 1
*/
private void HelperCMYK(float cyan, float magenta, float yellow, float black) {
if (cyan < 0)
cyan = 0.0f;
else if (cyan > 1.0f)
cyan = 1.0f;
if (magenta < 0)
magenta = 0.0f;
else if (magenta > 1.0f)
magenta = 1.0f;
if (yellow < 0)
yellow = 0.0f;
else if (yellow > 1.0f)
yellow = 1.0f;
if (black < 0)
black = 0.0f;
else if (black > 1.0f)
black = 1.0f;
content.append(cyan).append(' ').append(magenta).append(' ').append(yellow).append(' ').append(black);
}
/**
* Changes the current color for filling paths (device dependent colors!).
*
* Sets the color space to DeviceCMYK (or the DefaultCMYK color space),
* and sets the color to use for filling paths.
*
* Following the PDF manual, each operand must be a number between 0 (no ink) and
* 1 (maximum ink).
*
* @param cyan the intensity of cyan. A value between 0 and 1
* @param magenta the intensity of magenta. A value between 0 and 1
* @param yellow the intensity of yellow. A value between 0 and 1
* @param black the intensity of black. A value between 0 and 1
*/
public void setCMYKColorFillF(final float cyan, final float magenta, final float yellow, final float black) {
saveColor(new CMYKColor(cyan, magenta, yellow, black), true);
HelperCMYK(cyan, magenta, yellow, black);
content.append(" k").append_i(separator);
}
/**
* Changes the current color for filling paths to black.
*
*/
public void resetCMYKColorFill() {
saveColor(new CMYKColor(0, 0, 0, 1), true);
content.append("0 0 0 1 k").append_i(separator);
}
/**
* Changes the current color for stroking paths (device dependent colors!).
*
* Sets the color space to DeviceCMYK (or the DefaultCMYK color space),
* and sets the color to use for stroking paths.
*
* Following the PDF manual, each operand must be a number between 0 (miniumum intensity) and
* 1 (maximum intensity).
*
* @param cyan the intensity of cyan. A value between 0 and 1
* @param magenta the intensity of magenta. A value between 0 and 1
* @param yellow the intensity of yellow. A value between 0 and 1
* @param black the intensity of black. A value between 0 and 1
*/
public void setCMYKColorStrokeF(final float cyan, final float magenta, final float yellow, final float black) {
saveColor(new CMYKColor(cyan, magenta, yellow, black), false);
HelperCMYK(cyan, magenta, yellow, black);
content.append(" K").append_i(separator);
}
/**
* Changes the current color for stroking paths to black.
*
*/
public void resetCMYKColorStroke() {
saveColor(new CMYKColor(0, 0, 0, 1), false);
content.append("0 0 0 1 K").append_i(separator);
}
/**
* Move the current point (x, y), omitting any connecting line segment.
*
* @param x new x-coordinate
* @param y new y-coordinate
*/
public void moveTo(final float x, final float y) {
if (inText) {
if (isTagged()) {
endText();
} else {
throw new IllegalPdfSyntaxException(MessageLocalization.getComposedMessage("path.construction.operator.inside.text.object"));
}
}
content.append(x).append(' ').append(y).append(" m").append_i(separator);
}
/**
* Appends a straight line segment from the current point (x, y). The new current
* point is (x, y).
*
* @param x new x-coordinate
* @param y new y-coordinate
*/
public void lineTo(final float x, final float y) {
if (inText) {
if (isTagged()) {
endText();
} else {
throw new IllegalPdfSyntaxException(MessageLocalization.getComposedMessage("path.construction.operator.inside.text.object"));
}
}
content.append(x).append(' ').append(y).append(" l").append_i(separator);
}
/**
* Appends a Bêzier curve to the path, starting from the current point.
*
* @param x1 x-coordinate of the first control point
* @param y1 y-coordinate of the first control point
* @param x2 x-coordinate of the second control point
* @param y2 y-coordinate of the second control point
* @param x3 x-coordinate of the ending point (= new current point)
* @param y3 y-coordinate of the ending point (= new current point)
*/
public void curveTo(final float x1, final float y1, final float x2, final float y2, final float x3, final float y3) {
if (inText) {
if (isTagged()) {
endText();
} else {
throw new IllegalPdfSyntaxException(MessageLocalization.getComposedMessage("path.construction.operator.inside.text.object"));
}
}
content.append(x1).append(' ').append(y1).append(' ').append(x2).append(' ').append(y2).append(' ').append(x3).append(' ').append(y3).append(" c").append_i(separator);
}
/**
* Appends a Bêzier curve to the path, starting from the current point.
*
* @param x2 x-coordinate of the second control point
* @param y2 y-coordinate of the second control point
* @param x3 x-coordinate of the ending point (= new current point)
* @param y3 y-coordinate of the ending point (= new current point)
*/
public void curveTo(final float x2, final float y2, final float x3, final float y3) {
if (inText) {
if (isTagged()) {
endText();
} else {
throw new IllegalPdfSyntaxException(MessageLocalization.getComposedMessage("path.construction.operator.inside.text.object"));
}
}
content.append(x2).append(' ').append(y2).append(' ').append(x3).append(' ').append(y3).append(" v").append_i(separator);
}
/**
* Appends a Bêzier curve to the path, starting from the current point.
*
* @param x1 x-coordinate of the first control point
* @param y1 y-coordinate of the first control point
* @param x3 x-coordinate of the ending point (= new current point)
* @param y3 y-coordinate of the ending point (= new current point)
*/
public void curveFromTo(final float x1, final float y1, final float x3, final float y3) {
if (inText) {
if (isTagged()) {
endText();
} else {
throw new IllegalPdfSyntaxException(MessageLocalization.getComposedMessage("path.construction.operator.inside.text.object"));
}
}
content.append(x1).append(' ').append(y1).append(' ').append(x3).append(' ').append(y3).append(" y").append_i(separator);
}
/** Draws a circle. The endpoint will (x+r, y).
*
* @param x x center of circle
* @param y y center of circle
* @param r radius of circle
*/
public void circle(final float x, final float y, final float r) {
float b = 0.5523f;
moveTo(x + r, y);
curveTo(x + r, y + r * b, x + r * b, y + r, x, y + r);
curveTo(x - r * b, y + r, x - r, y + r * b, x - r, y);
curveTo(x - r, y - r * b, x - r * b, y - r, x, y - r);
curveTo(x + r * b, y - r, x + r, y - r * b, x + r, y);
}
/**
* Adds a rectangle to the current path.
*
* @param x x-coordinate of the starting point
* @param y y-coordinate of the starting point
* @param w width
* @param h height
*/
public void rectangle(final float x, final float y, final float w, final float h) {
if (inText) {
if (isTagged()) {
endText();
} else {
throw new IllegalPdfSyntaxException(MessageLocalization.getComposedMessage("path.construction.operator.inside.text.object"));
}
}
content.append(x).append(' ').append(y).append(' ').append(w).append(' ').append(h).append(" re").append_i(separator);
}
private boolean compareColors(final BaseColor c1, final BaseColor c2) {
if (c1 == null && c2 == null)
return true;
if (c1 == null || c2 == null)
return false;
if (c1 instanceof ExtendedColor)
return c1.equals(c2);
return c2.equals(c1);
}
/**
* Adds a variable width border to the current path.
* Only use if {@link com.itextpdf.text.Rectangle#isUseVariableBorders() Rectangle.isUseVariableBorders}
* = true.
* @param rect a Rectangle
*/
public void variableRectangle(final Rectangle rect) {
float t = rect.getTop();
float b = rect.getBottom();
float r = rect.getRight();
float l = rect.getLeft();
float wt = rect.getBorderWidthTop();
float wb = rect.getBorderWidthBottom();
float wr = rect.getBorderWidthRight();
float wl = rect.getBorderWidthLeft();
BaseColor ct = rect.getBorderColorTop();
BaseColor cb = rect.getBorderColorBottom();
BaseColor cr = rect.getBorderColorRight();
BaseColor cl = rect.getBorderColorLeft();
saveState();
setLineCap(PdfContentByte.LINE_CAP_BUTT);
setLineJoin(PdfContentByte.LINE_JOIN_MITER);
float clw = 0;
boolean cdef = false;
BaseColor ccol = null;
boolean cdefi = false;
BaseColor cfil = null;
// draw top
if (wt > 0) {
setLineWidth(clw = wt);
cdef = true;
if (ct == null)
resetRGBColorStroke();
else
setColorStroke(ct);
ccol = ct;
moveTo(l, t - wt / 2f);
lineTo(r, t - wt / 2f);
stroke();
}
// Draw bottom
if (wb > 0) {
if (wb != clw)
setLineWidth(clw = wb);
if (!cdef || !compareColors(ccol, cb)) {
cdef = true;
if (cb == null)
resetRGBColorStroke();
else
setColorStroke(cb);
ccol = cb;
}
moveTo(r, b + wb / 2f);
lineTo(l, b + wb / 2f);
stroke();
}
// Draw right
if (wr > 0) {
if (wr != clw)
setLineWidth(clw = wr);
if (!cdef || !compareColors(ccol, cr)) {
cdef = true;
if (cr == null)
resetRGBColorStroke();
else
setColorStroke(cr);
ccol = cr;
}
boolean bt = compareColors(ct, cr);
boolean bb = compareColors(cb, cr);
moveTo(r - wr / 2f, bt ? t : t - wt);
lineTo(r - wr / 2f, bb ? b : b + wb);
stroke();
if (!bt || !bb) {
cdefi = true;
if (cr == null)
resetRGBColorFill();
else
setColorFill(cr);
cfil = cr;
if (!bt) {
moveTo(r, t);
lineTo(r, t - wt);
lineTo(r - wr, t - wt);
fill();
}
if (!bb) {
moveTo(r, b);
lineTo(r, b + wb);
lineTo(r - wr, b + wb);
fill();
}
}
}
// Draw Left
if (wl > 0) {
if (wl != clw)
setLineWidth(wl);
if (!cdef || !compareColors(ccol, cl)) {
if (cl == null)
resetRGBColorStroke();
else
setColorStroke(cl);
}
boolean bt = compareColors(ct, cl);
boolean bb = compareColors(cb, cl);
moveTo(l + wl / 2f, bt ? t : t - wt);
lineTo(l + wl / 2f, bb ? b : b + wb);
stroke();
if (!bt || !bb) {
if (!cdefi || !compareColors(cfil, cl)) {
if (cl == null)
resetRGBColorFill();
else
setColorFill(cl);
}
if (!bt) {
moveTo(l, t);
lineTo(l, t - wt);
lineTo(l + wl, t - wt);
fill();
}
if (!bb) {
moveTo(l, b);
lineTo(l, b + wb);
lineTo(l + wl, b + wb);
fill();
}
}
}
restoreState();
}
/**
* Adds a border (complete or partially) to the current path..
*
* @param rectangle a Rectangle
*/
public void rectangle(final Rectangle rectangle) {
// the coordinates of the border are retrieved
float x1 = rectangle.getLeft();
float y1 = rectangle.getBottom();
float x2 = rectangle.getRight();
float y2 = rectangle.getTop();
// the backgroundcolor is set
BaseColor background = rectangle.getBackgroundColor();
if (background != null) {
saveState();
setColorFill(background);
rectangle(x1, y1, x2 - x1, y2 - y1);
fill();
restoreState();
}
// if the element hasn't got any borders, nothing is added
if (! rectangle.hasBorders()) {
return;
}
// if any of the individual border colors are set
// we draw the borders all around using the
// different colors
if (rectangle.isUseVariableBorders()) {
variableRectangle(rectangle);
}
else {
// the width is set to the width of the element
if (rectangle.getBorderWidth() != Rectangle.UNDEFINED) {
setLineWidth(rectangle.getBorderWidth());
}
// the color is set to the color of the element
BaseColor color = rectangle.getBorderColor();
if (color != null) {
setColorStroke(color);
}
// if the box is a rectangle, it is added as a rectangle
if (rectangle.hasBorder(Rectangle.BOX)) {
rectangle(x1, y1, x2 - x1, y2 - y1);
}
// if the border isn't a rectangle, the different sides are added apart
else {
if (rectangle.hasBorder(Rectangle.RIGHT)) {
moveTo(x2, y1);
lineTo(x2, y2);
}
if (rectangle.hasBorder(Rectangle.LEFT)) {
moveTo(x1, y1);
lineTo(x1, y2);
}
if (rectangle.hasBorder(Rectangle.BOTTOM)) {
moveTo(x1, y1);
lineTo(x2, y1);
}
if (rectangle.hasBorder(Rectangle.TOP)) {
moveTo(x1, y2);
lineTo(x2, y2);
}
}
stroke();
if (color != null) {
resetRGBColorStroke();
}
}
}
/**
* Closes the current subpath by appending a straight line segment from the current point
* to the starting point of the subpath.
*/
public void closePath() {
if (inText) {
if (isTagged()) {
endText();
} else {
throw new IllegalPdfSyntaxException(MessageLocalization.getComposedMessage("path.construction.operator.inside.text.object"));
}
}
content.append("h").append_i(separator);
}
/**
* Ends the path without filling or stroking it.
*/
public void newPath() {
if (inText) {
if (isTagged()) {
endText();
} else {
throw new IllegalPdfSyntaxException(MessageLocalization.getComposedMessage("path.construction.operator.inside.text.object"));
}
}
content.append("n").append_i(separator);
}
/**
* Strokes the path.
*/
public void stroke() {
if (inText) {
if (isTagged()) {
endText();
} else {
throw new IllegalPdfSyntaxException(MessageLocalization.getComposedMessage("path.construction.operator.inside.text.object"));
}
}
PdfWriter.checkPdfIsoConformance(writer, PdfIsoKeys.PDFISOKEY_COLOR, state.colorStroke);
PdfWriter.checkPdfIsoConformance(writer, PdfIsoKeys.PDFISOKEY_GSTATE, state.extGState);
content.append("S").append_i(separator);
}
/**
* Closes the path and strokes it.
*/
public void closePathStroke() {
if (inText) {
if (isTagged()) {
endText();
} else {
throw new IllegalPdfSyntaxException(MessageLocalization.getComposedMessage("path.construction.operator.inside.text.object"));
}
}
PdfWriter.checkPdfIsoConformance(writer, PdfIsoKeys.PDFISOKEY_COLOR, state.colorStroke);
PdfWriter.checkPdfIsoConformance(writer, PdfIsoKeys.PDFISOKEY_GSTATE, state.extGState);
content.append("s").append_i(separator);
}
/**
* Fills the path, using the non-zero winding number rule to determine the region to fill.
*/
public void fill() {
if (inText) {
if (isTagged()) {
endText();
} else {
throw new IllegalPdfSyntaxException(MessageLocalization.getComposedMessage("path.construction.operator.inside.text.object"));
}
}
PdfWriter.checkPdfIsoConformance(writer, PdfIsoKeys.PDFISOKEY_COLOR, state.colorFill);
PdfWriter.checkPdfIsoConformance(writer, PdfIsoKeys.PDFISOKEY_GSTATE, state.extGState);
content.append("f").append_i(separator);
}
/**
* Fills the path, using the even-odd rule to determine the region to fill.
*/
public void eoFill() {
if (inText) {
if (isTagged()) {
endText();
} else {
throw new IllegalPdfSyntaxException(MessageLocalization.getComposedMessage("path.construction.operator.inside.text.object"));
}
}
PdfWriter.checkPdfIsoConformance(writer, PdfIsoKeys.PDFISOKEY_COLOR, state.colorFill);
PdfWriter.checkPdfIsoConformance(writer, PdfIsoKeys.PDFISOKEY_GSTATE, state.extGState);
content.append("f*").append_i(separator);
}
/**
* Fills the path using the non-zero winding number rule to determine the region to fill and strokes it.
*/
public void fillStroke() {
if (inText) {
if (isTagged()) {
endText();
} else {
throw new IllegalPdfSyntaxException(MessageLocalization.getComposedMessage("path.construction.operator.inside.text.object"));
}
}
PdfWriter.checkPdfIsoConformance(writer, PdfIsoKeys.PDFISOKEY_COLOR, state.colorFill);
PdfWriter.checkPdfIsoConformance(writer, PdfIsoKeys.PDFISOKEY_COLOR, state.colorStroke);
PdfWriter.checkPdfIsoConformance(writer, PdfIsoKeys.PDFISOKEY_GSTATE, state.extGState);
content.append("B").append_i(separator);
}
/**
* Closes the path, fills it using the non-zero winding number rule to determine the region to fill and strokes it.
*/
public void closePathFillStroke() {
if (inText) {
if (isTagged()) {
endText();
} else {
throw new IllegalPdfSyntaxException(MessageLocalization.getComposedMessage("path.construction.operator.inside.text.object"));
}
}
PdfWriter.checkPdfIsoConformance(writer, PdfIsoKeys.PDFISOKEY_COLOR, state.colorFill);
PdfWriter.checkPdfIsoConformance(writer, PdfIsoKeys.PDFISOKEY_COLOR, state.colorStroke);
PdfWriter.checkPdfIsoConformance(writer, PdfIsoKeys.PDFISOKEY_GSTATE, state.extGState);
content.append("b").append_i(separator);
}
/**
* Fills the path, using the even-odd rule to determine the region to fill and strokes it.
*/
public void eoFillStroke() {
if (inText) {
if (isTagged()) {
endText();
} else {
throw new IllegalPdfSyntaxException(MessageLocalization.getComposedMessage("path.construction.operator.inside.text.object"));
}
}
PdfWriter.checkPdfIsoConformance(writer, PdfIsoKeys.PDFISOKEY_COLOR, state.colorFill);
PdfWriter.checkPdfIsoConformance(writer, PdfIsoKeys.PDFISOKEY_COLOR, state.colorStroke);
PdfWriter.checkPdfIsoConformance(writer, PdfIsoKeys.PDFISOKEY_GSTATE, state.extGState);
content.append("B*").append_i(separator);
}
/**
* Closes the path, fills it using the even-odd rule to determine the region to fill and strokes it.
*/
public void closePathEoFillStroke() {
if (inText) {
if (isTagged()) {
endText();
} else {
throw new IllegalPdfSyntaxException(MessageLocalization.getComposedMessage("path.construction.operator.inside.text.object"));
}
}
PdfWriter.checkPdfIsoConformance(writer, PdfIsoKeys.PDFISOKEY_COLOR, state.colorFill);
PdfWriter.checkPdfIsoConformance(writer, PdfIsoKeys.PDFISOKEY_COLOR, state.colorStroke);
PdfWriter.checkPdfIsoConformance(writer, PdfIsoKeys.PDFISOKEY_GSTATE, state.extGState);
content.append("b*").append_i(separator);
}
/**
* Adds an Image
to the page. The Image
must have
* absolute positioning.
* @param image the Image
object
* @throws DocumentException if the Image
does not have absolute positioning
*/
public void addImage(final Image image) throws DocumentException {
addImage(image, false);
}
/**
* Adds an Image
to the page. The Image
must have
* absolute positioning. The image can be placed inline.
* @param image the Image
object
* @param inlineImage true
to place this image inline, false
otherwise
* @throws DocumentException if the Image
does not have absolute positioning
*/
public void addImage(final Image image, final boolean inlineImage) throws DocumentException {
if (!image.hasAbsoluteY())
throw new DocumentException(MessageLocalization.getComposedMessage("the.image.must.have.absolute.positioning"));
float matrix[] = image.matrix();
matrix[Image.CX] = image.getAbsoluteX() - matrix[Image.CX];
matrix[Image.CY] = image.getAbsoluteY() - matrix[Image.CY];
addImage(image, matrix[0], matrix[1], matrix[2], matrix[3], matrix[4], matrix[5], inlineImage);
}
/**
* Adds an Image
to the page. The positioning of the Image
* is done with the transformation matrix. To position an image
at (x,y)
* use addImage(image, image_width, 0, 0, image_height, x, y).
* @param image the Image
object
* @param a an element of the transformation matrix
* @param b an element of the transformation matrix
* @param c an element of the transformation matrix
* @param d an element of the transformation matrix
* @param e an element of the transformation matrix
* @param f an element of the transformation matrix
* @throws DocumentException on error
*/
public void addImage(final Image image, final float a, final float b, final float c, final float d, final float e, final float f) throws DocumentException {
addImage(image, a, b, c, d, e, f, false);
}
/**
* adds an image with the given matrix.
* @param image image to add
* @param transform transform to apply to the template prior to adding it.
*/
public void addImage(final Image image, final AffineTransform transform) throws DocumentException {
double matrix[] = new double[6];
transform.getMatrix(matrix);
addImage( image, (float)matrix[0], (float)matrix[1], (float)matrix[2],
(float)matrix[3], (float)matrix[4],(float) matrix[5], false );
}
/**
* Adds an Image
to the page. The positioning of the Image
* is done with the transformation matrix. To position an image
at (x,y)
* use addImage(image, image_width, 0, 0, image_height, x, y). The image can be placed inline.
* @param image the Image
object
* @param a an element of the transformation matrix
* @param b an element of the transformation matrix
* @param c an element of the transformation matrix
* @param d an element of the transformation matrix
* @param e an element of the transformation matrix
* @param f an element of the transformation matrix
* @param inlineImage true
to place this image inline, false
otherwise
* @throws DocumentException on error
*/
public void addImage(final Image image, final float a, final float b, final float c, final float d, final float e, final float f, final boolean inlineImage) throws DocumentException {
try {
if (image.getLayer() != null)
beginLayer(image.getLayer());
if (isTagged()) {
if (inText)
endText();
AffineTransform transform = new AffineTransform(a, b, c, d, e, f);
Point2D[] src = new Point2D.Float[] {new Point2D.Float(0, 0), new Point2D.Float(1, 0), new Point2D.Float(1, 1), new Point2D.Float(0, 1)};
Point2D[] dst = new Point2D.Float[4];
transform.transform(src, 0, dst, 0, 4);
float left = Float.MAX_VALUE;
float right = -Float.MAX_VALUE;
float bottom = Float.MAX_VALUE;
float top = -Float.MAX_VALUE;
for (int i = 0; i < 4; i++) {
if (dst[i].getX() < left)
left = (float)dst[i].getX();
if (dst[i].getX() > right)
right = (float)dst[i].getX();
if (dst[i].getY() < bottom)
bottom = (float)dst[i].getY();
if (dst[i].getY() > top)
top = (float)dst[i].getY();
}
image.setAccessibleAttribute(PdfName.BBOX, new PdfArray(new float[] {left, bottom, right, top}));
}
if (writer != null && image.isImgTemplate()) {
writer.addDirectImageSimple(image);
PdfTemplate template = image.getTemplateData();
if (image.getAccessibleAttributes() != null) {
for (PdfName key : image.getAccessibleAttributes().keySet()) {
template.setAccessibleAttribute(key, image.getAccessibleAttribute(key));
}
}
float w = template.getWidth();
float h = template.getHeight();
addTemplate(template, a / w, b / w, c / h, d / h, e, f);
}
else {
content.append("q ");
content.append(a).append(' ');
content.append(b).append(' ');
content.append(c).append(' ');
content.append(d).append(' ');
content.append(e).append(' ');
content.append(f).append(" cm");
if (inlineImage) {
content.append("\nBI\n");
PdfImage pimage = new PdfImage(image, "", null);
if (image instanceof ImgJBIG2) {
byte[] globals = ((ImgJBIG2)image).getGlobalBytes();
if (globals != null) {
PdfDictionary decodeparms = new PdfDictionary();
decodeparms.put(PdfName.JBIG2GLOBALS, writer.getReferenceJBIG2Globals(globals));
pimage.put(PdfName.DECODEPARMS, decodeparms);
}
}
PdfWriter.checkPdfIsoConformance(writer, PdfIsoKeys.PDFISOKEY_INLINE_IMAGE, pimage);
for (Object element : pimage.getKeys()) {
PdfName key = (PdfName)element;
PdfObject value = pimage.get(key);
String s = abrev.get(key);
if (s == null)
continue;
content.append(s);
boolean check = true;
if (key.equals(PdfName.COLORSPACE) && value.isArray()) {
PdfArray ar = (PdfArray)value;
if (ar.size() == 4
&& PdfName.INDEXED.equals(ar.getAsName(0))
&& ar.getPdfObject(1).isName()
&& ar.getPdfObject(2).isNumber()
&& ar.getPdfObject(3).isString()
) {
check = false;
}
}
if (check && key.equals(PdfName.COLORSPACE) && !value.isName()) {
PdfName cs = writer.getColorspaceName();
PageResources prs = getPageResources();
prs.addColor(cs, writer.addToBody(value).getIndirectReference());
value = cs;
}
value.toPdf(null, content);
content.append('\n');
}
ByteArrayOutputStream baos = new ByteArrayOutputStream();
pimage.writeContent(baos);
byte[] imageBytes = baos.toByteArray();
content.append(String.format("/L %s\n", imageBytes.length));
/*
// The following restriction will be normative in PDF 2.0
if (imageBytes.length > 4096)
throw new DocumentException("Inline images must be 4 KB or less");
*/
content.append("ID\n");
content.append(imageBytes);
content.append("\nEI\nQ").append_i(separator);
}
else {
PdfName name;
PageResources prs = getPageResources();
Image maskImage = image.getImageMask();
if (maskImage != null) {
name = writer.addDirectImageSimple(maskImage);
prs.addXObject(name, writer.getImageReference(name));
}
name = writer.addDirectImageSimple(image);
name = prs.addXObject(name, writer.getImageReference(name));
content.append(' ').append(name.getBytes()).append(" Do Q").append_i(separator);
}
}
if (image.hasBorders()) {
saveState();
float w = image.getWidth();
float h = image.getHeight();
concatCTM(a / w, b / w, c / h, d / h, e, f);
rectangle(image);
restoreState();
}
if (image.getLayer() != null)
endLayer();
Annotation annot = image.getAnnotation();
if (annot == null)
return;
float[] r = new float[unitRect.length];
for (int k = 0; k < unitRect.length; k += 2) {
r[k] = a * unitRect[k] + c * unitRect[k + 1] + e;
r[k + 1] = b * unitRect[k] + d * unitRect[k + 1] + f;
}
float llx = r[0];
float lly = r[1];
float urx = llx;
float ury = lly;
for (int k = 2; k < r.length; k += 2) {
llx = Math.min(llx, r[k]);
lly = Math.min(lly, r[k + 1]);
urx = Math.max(urx, r[k]);
ury = Math.max(ury, r[k + 1]);
}
annot = new Annotation(annot);
annot.setDimensions(llx, lly, urx, ury);
PdfAnnotation an = PdfAnnotationsImp.convertAnnotation(writer, annot, new Rectangle(llx, lly, urx, ury));
if (an == null)
return;
addAnnotation(an);
}
catch (Exception ee) {
throw new DocumentException(ee);
}
}
/**
* Makes this PdfContentByte
empty.
* Calls reset( true )
*/
public void reset() {
reset( true );
}
/**
* Makes this PdfContentByte
empty.
* @param validateContent will call sanityCheck()
if true.
* @since 2.1.6
*/
public void reset( final boolean validateContent ) {
content.reset();
markedContentSize = 0;
if (validateContent) {
sanityCheck();
}
state = new GraphicState();
stateList = new ArrayList();
}
/**
* Starts the writing of text.
* @param restoreTM indicates if to restore text matrix of the previous text block.
*/
protected void beginText(boolean restoreTM) {
if (inText) {
if (isTagged()) {
} else {
throw new IllegalPdfSyntaxException(MessageLocalization.getComposedMessage("unbalanced.begin.end.text.operators"));
}
} else {
inText = true;
content.append("BT").append_i(separator);
if (restoreTM) {
float xTLM = state.xTLM;
float tx = state.tx;
setTextMatrix(state.aTLM, state.bTLM, state.cTLM, state.dTLM, state.tx, state.yTLM);
state.xTLM = xTLM;
state.tx = tx;
} else {
state.xTLM = 0;
state.yTLM = 0;
state.tx = 0;
}
if (isTagged()) {
try {
restoreColor();
} catch (IOException ioe) {
}
}
}
}
/**
* Starts the writing of text.
*/
public void beginText() {
beginText(false);
}
/**
* Ends the writing of text and makes the current font invalid.
*/
public void endText() {
if (!inText) {
if (isTagged()) {
} else {
throw new IllegalPdfSyntaxException(MessageLocalization.getComposedMessage("unbalanced.begin.end.text.operators"));
}
} else {
inText = false;
content.append("ET").append_i(separator);
if (isTagged()) {
try {
restoreColor();
} catch (IOException ioe) {
}
}
}
}
/**
* Saves the graphic state. saveState
and
* restoreState
must be balanced.
*/
public void saveState() {
PdfWriter.checkPdfIsoConformance(writer, PdfIsoKeys.PDFISOKEY_CANVAS, "q");
if (inText && isTagged()) {
endText();
}
content.append("q").append_i(separator);
stateList.add(new GraphicState(state));
}
/**
* Restores the graphic state. saveState
and
* restoreState
must be balanced.
*/
public void restoreState() {
PdfWriter.checkPdfIsoConformance(writer, PdfIsoKeys.PDFISOKEY_CANVAS, "Q");
if (inText && isTagged()) {
endText();
}
content.append("Q").append_i(separator);
int idx = stateList.size() - 1;
if (idx < 0)
throw new IllegalPdfSyntaxException(MessageLocalization.getComposedMessage("unbalanced.save.restore.state.operators"));
state.restore(stateList.get(idx));
stateList.remove(idx);
}
/**
* Sets the character spacing parameter.
*
* @param charSpace a parameter
*/
public void setCharacterSpacing(final float charSpace) {
if (!inText && isTagged()) {
beginText(true);
}
state.charSpace = charSpace;
content.append(charSpace).append(" Tc").append_i(separator);
}
/**
* Sets the word spacing parameter.
*
* @param wordSpace a parameter
*/
public void setWordSpacing(final float wordSpace) {
if (!inText && isTagged()) {
beginText(true);
}
state.wordSpace = wordSpace;
content.append(wordSpace).append(" Tw").append_i(separator);
}
/**
* Sets the horizontal scaling parameter.
*
* @param scale a parameter
*/
public void setHorizontalScaling(final float scale) {
if (!inText && isTagged()) {
beginText(true);
}
state.scale = scale;
content.append(scale).append(" Tz").append_i(separator);
}
/**
* Sets the text leading parameter.
*
* The leading parameter is measured in text space units. It specifies the vertical distance
* between the baselines of adjacent lines of text.
*
* @param leading the new leading
*/
public void setLeading(final float leading) {
if (!inText && isTagged()) {
beginText(true);
}
state.leading = leading;
content.append(leading).append(" TL").append_i(separator);
}
/**
* Set the font and the size for the subsequent text writing.
*
* @param bf the font
* @param size the font size in points
*/
public void setFontAndSize(final BaseFont bf, final float size) {
if (!inText && isTagged()) {
beginText(true);
}
checkWriter();
if (size < 0.0001f && size > -0.0001f)
throw new IllegalArgumentException(MessageLocalization.getComposedMessage("font.size.too.small.1", String.valueOf(size)));
state.size = size;
state.fontDetails = writer.addSimple(bf);
PageResources prs = getPageResources();
PdfName name = state.fontDetails.getFontName();
name = prs.addFont(name, state.fontDetails.getIndirectReference());
content.append(name.getBytes()).append(' ').append(size).append(" Tf").append_i(separator);
}
/**
* Sets the text rendering parameter.
*
* @param rendering a parameter
*/
public void setTextRenderingMode(final int rendering) {
if (!inText && isTagged()) {
beginText(true);
}
state.textRenderMode = rendering;
content.append(rendering).append(" Tr").append_i(separator);
}
/**
* Sets the text rise parameter.
*
* This allows to write text in subscript or superscript mode.
*
* @param rise a parameter
*/
public void setTextRise(final float rise) {
if (!inText && isTagged()) {
beginText(true);
}
content.append(rise).append(" Ts").append_i(separator);
}
/**
* A helper to insert into the content stream the text
* converted to bytes according to the font's encoding.
*
* @param text the text to write
*/
private void showText2(final String text) {
if (state.fontDetails == null)
throw new NullPointerException(MessageLocalization.getComposedMessage("font.and.size.must.be.set.before.writing.any.text"));
byte b[] = state.fontDetails.convertToBytes(text);
escapeString(b, content);
}
/**
* Shows the text
.
*
* @param text the text to write
*/
public void showText(final String text) {
checkState();
if (!inText && isTagged()) {
beginText(true);
}
showText2(text);
updateTx(text, 0);
content.append("Tj").append_i(separator);
}
/**
* Constructs a kern array for a text in a certain font
* @param text the text
* @param font the font
* @return a PdfTextArray
*/
public static PdfTextArray getKernArray(final String text, final BaseFont font) {
PdfTextArray pa = new PdfTextArray();
StringBuffer acc = new StringBuffer();
int len = text.length() - 1;
char c[] = text.toCharArray();
if (len >= 0)
acc.append(c, 0, 1);
for (int k = 0; k < len; ++k) {
char c2 = c[k + 1];
int kern = font.getKerning(c[k], c2);
if (kern == 0) {
acc.append(c2);
}
else {
pa.add(acc.toString());
acc.setLength(0);
acc.append(c, k + 1, 1);
pa.add(-kern);
}
}
pa.add(acc.toString());
return pa;
}
/**
* Shows the text
kerned.
*
* @param text the text to write
*/
public void showTextKerned(final String text) {
if (state.fontDetails == null)
throw new NullPointerException(MessageLocalization.getComposedMessage("font.and.size.must.be.set.before.writing.any.text"));
BaseFont bf = state.fontDetails.getBaseFont();
if (bf.hasKernPairs())
showText(getKernArray(text, bf));
else {
showText(text);
}
}
/**
* Moves to the next line and shows text
.
*
* @param text the text to write
*/
public void newlineShowText(final String text) {
checkState();
if (!inText && isTagged()) {
beginText(true);
}
state.yTLM -= state.leading;
showText2(text);
content.append("'").append_i(separator);
state.tx = state.xTLM;
updateTx(text, 0);
}
/**
* Moves to the next line and shows text string, using the given values of the character and word spacing parameters.
*
* @param wordSpacing a parameter
* @param charSpacing a parameter
* @param text the text to write
*/
public void newlineShowText(final float wordSpacing, final float charSpacing, final String text) {
checkState();
if (!inText && isTagged()) {
beginText(true);
}
state.yTLM -= state.leading;
content.append(wordSpacing).append(' ').append(charSpacing);
showText2(text);
content.append("\"").append_i(separator);
// The " operator sets charSpace and wordSpace into graphics state
// (cfr PDF reference v1.6, table 5.6)
state.charSpace = charSpacing;
state.wordSpace = wordSpacing;
state.tx = state.xTLM;
updateTx(text, 0);
}
/**
* Changes the text matrix.
*
* Remark: this operation also initializes the current point position.
*
* @param a operand 1,1 in the matrix
* @param b operand 1,2 in the matrix
* @param c operand 2,1 in the matrix
* @param d operand 2,2 in the matrix
* @param x operand 3,1 in the matrix
* @param y operand 3,2 in the matrix
*/
public void setTextMatrix(final float a, final float b, final float c, final float d, final float x, final float y) {
if (!inText && isTagged()) {
beginText(true);
}
state.xTLM = x;
state.yTLM = y;
state.aTLM = a;
state.bTLM = b;
state.cTLM = c;
state.dTLM = d;
state.tx = state.xTLM;
content.append(a).append(' ').append(b).append_i(' ')
.append(c).append_i(' ').append(d).append_i(' ')
.append(x).append_i(' ').append(y).append(" Tm").append_i(separator);
}
/**
* Changes the text matrix.
*
* @param transform overwrite the current text matrix with this one
*/
public void setTextMatrix(final AffineTransform transform) {
double matrix[] = new double[6];
transform.getMatrix(matrix);
setTextMatrix((float) matrix[0], (float) matrix[1], (float) matrix[2],
(float) matrix[3], (float) matrix[4], (float) matrix[5]);
}
/**
* Changes the text matrix. The first four parameters are {1,0,0,1}.
*
* Remark: this operation also initializes the current point position.
*
* @param x operand 3,1 in the matrix
* @param y operand 3,2 in the matrix
*/
public void setTextMatrix(final float x, final float y) {
setTextMatrix(1, 0, 0, 1, x, y);
}
/**
* Moves to the start of the next line, offset from the start of the current line.
*
* @param x x-coordinate of the new current point
* @param y y-coordinate of the new current point
*/
public void moveText(final float x, final float y) {
if (!inText && isTagged()) {
beginText(true);
}
state.xTLM += x;
state.yTLM += y;
if (isTagged() && state.xTLM != state.tx) {
setTextMatrix(state.aTLM, state.bTLM, state.cTLM, state.dTLM, state.xTLM, state.yTLM);
} else {
content.append(x).append(' ').append(y).append(" Td").append_i(separator);
}
}
/**
* Moves to the start of the next line, offset from the start of the current line.
*
* As a side effect, this sets the leading parameter in the text state.
*
* @param x offset of the new current point
* @param y y-coordinate of the new current point
*/
public void moveTextWithLeading(final float x, final float y) {
if (!inText && isTagged()) {
beginText(true);
}
state.xTLM += x;
state.yTLM += y;
state.leading = -y;
if (isTagged() && state.xTLM != state.tx) {
setTextMatrix(state.aTLM, state.bTLM, state.cTLM, state.dTLM, state.xTLM, state.yTLM);
} else {
content.append(x).append(' ').append(y).append(" TD").append_i(separator);
}
}
/**
* Moves to the start of the next line.
*/
public void newlineText() {
if (!inText && isTagged()) {
beginText(true);
}
if (isTagged() && state.xTLM != state.tx) {
setTextMatrix(state.aTLM, state.bTLM, state.cTLM, state.dTLM, state.xTLM, state.yTLM);
}
state.yTLM -= state.leading;
content.append("T*").append_i(separator);
}
/**
* Gets the size of this content.
*
* @return the size of the content
*/
int size() {
return size(true);
}
int size(boolean includeMarkedContentSize) {
if (includeMarkedContentSize)
return content.size();
else
return content.size() - markedContentSize;
}
/**
* Escapes a byte
array according to the PDF conventions.
*
* @param b the byte
array to escape
* @return an escaped byte
array
*/
static byte[] escapeString(final byte b[]) {
ByteBuffer content = new ByteBuffer();
escapeString(b, content);
return content.toByteArray();
}
/**
* Escapes a byte
array according to the PDF conventions.
*
* @param b the byte
array to escape
* @param content the content
*/
static void escapeString(final byte b[], final ByteBuffer content) {
content.append_i('(');
for (int k = 0; k < b.length; ++k) {
byte c = b[k];
switch (c) {
case '\r':
content.append("\\r");
break;
case '\n':
content.append("\\n");
break;
case '\t':
content.append("\\t");
break;
case '\b':
content.append("\\b");
break;
case '\f':
content.append("\\f");
break;
case '(':
case ')':
case '\\':
content.append_i('\\').append_i(c);
break;
default:
content.append_i(c);
}
}
content.append(")");
}
/**
* Adds a named outline to the document.
*
* @param outline the outline
* @param name the name for the local destination
*/
public void addOutline(final PdfOutline outline, final String name) {
checkWriter();
pdf.addOutline(outline, name);
}
/**
* Gets the root outline.
*
* @return the root outline
*/
public PdfOutline getRootOutline() {
checkWriter();
return pdf.getRootOutline();
}
/**
* Computes the width of the given string taking in account
* the current values of "Character spacing", "Word Spacing"
* and "Horizontal Scaling".
* The additional spacing is not computed for the last character
* of the string.
* @param text the string to get width of
* @param kerned the kerning option
* @return the width
*/
public float getEffectiveStringWidth(final String text, final boolean kerned) {
BaseFont bf = state.fontDetails.getBaseFont();
float w;
if (kerned)
w = bf.getWidthPointKerned(text, state.size);
else
w = bf.getWidthPoint(text, state.size);
if (state.charSpace != 0.0f && text.length() > 1) {
w += state.charSpace * (text.length() -1);
}
if (state.wordSpace != 0.0f && !bf.isVertical()) {
for (int i = 0; i < text.length() -1; i++) {
if (text.charAt(i) == ' ')
w += state.wordSpace;
}
}
if (state.scale != 100.0)
w = w * state.scale / 100.0f;
//System.out.println("String width = " + Float.toString(w));
return w;
}
/**
* Computes the width of the given string taking in account
* the current values of "Character spacing", "Word Spacing"
* and "Horizontal Scaling".
* The spacing for the last character is also computed.
* It also takes into account kerning that can be specified within TJ operator (e.g. [(Hello) 123 (World)] TJ)
* @param text the string to get width of
* @param kerned the kerning option
* @param kerning the kerning option from TJ array
* @return the width
*/
private float getEffectiveStringWidth(final String text, final boolean kerned, final float kerning) {
BaseFont bf = state.fontDetails.getBaseFont();
float w;
if (kerned)
w = bf.getWidthPointKerned(text, state.size);
else
w = bf.getWidthPoint(text, state.size);
if (state.charSpace != 0.0f && text.length() > 0) {
w += state.charSpace * (text.length());
}
if (state.wordSpace != 0.0f && !bf.isVertical()) {
for (int i = 0; i < text.length(); i++) {
if (text.charAt(i) == ' ')
w += state.wordSpace;
}
}
w -= kerning / 1000.f * state.size;
if (state.scale != 100.0)
w = w * state.scale / 100.0f;
return w;
}
/**
* Shows text right, left or center aligned with rotation.
* @param alignment the alignment can be ALIGN_CENTER, ALIGN_RIGHT or ALIGN_LEFT
* @param text the text to show
* @param x the x pivot position
* @param y the y pivot position
* @param rotation the rotation to be applied in degrees counterclockwise
*/
public void showTextAligned(final int alignment, final String text, final float x, final float y, final float rotation) {
showTextAligned(alignment, text, x, y, rotation, false);
}
private void showTextAligned(final int alignment, final String text, float x, float y, final float rotation, final boolean kerned) {
if (state.fontDetails == null)
throw new NullPointerException(MessageLocalization.getComposedMessage("font.and.size.must.be.set.before.writing.any.text"));
if (rotation == 0) {
switch (alignment) {
case ALIGN_CENTER:
x -= getEffectiveStringWidth(text, kerned) / 2;
break;
case ALIGN_RIGHT:
x -= getEffectiveStringWidth(text, kerned);
break;
}
setTextMatrix(x, y);
if (kerned)
showTextKerned(text);
else
showText(text);
}
else {
double alpha = rotation * Math.PI / 180.0;
float cos = (float)Math.cos(alpha);
float sin = (float)Math.sin(alpha);
float len;
switch (alignment) {
case ALIGN_CENTER:
len = getEffectiveStringWidth(text, kerned) / 2;
x -= len * cos;
y -= len * sin;
break;
case ALIGN_RIGHT:
len = getEffectiveStringWidth(text, kerned);
x -= len * cos;
y -= len * sin;
break;
}
setTextMatrix(cos, sin, -sin, cos, x, y);
if (kerned)
showTextKerned(text);
else
showText(text);
setTextMatrix(0f, 0f);
}
}
/**
* Shows text kerned right, left or center aligned with rotation.
* @param alignment the alignment can be ALIGN_CENTER, ALIGN_RIGHT or ALIGN_LEFT
* @param text the text to show
* @param x the x pivot position
* @param y the y pivot position
* @param rotation the rotation to be applied in degrees counterclockwise
*/
public void showTextAlignedKerned(final int alignment, final String text, final float x, final float y, final float rotation) {
showTextAligned(alignment, text, x, y, rotation, true);
}
/**
* Concatenate a matrix to the current transformation matrix.
*
* Common transformations:
*
*
* - Translation: [1 0 0 1 tx ty]
* - Scaling: [sx 0 0 sy 0 0] (if sx or sy is negative, it will flip the coordinate system)
* - Rotation: [cos(q) sin(q) -sin(q) cos(q) 0 0] where q is angle of counter-clockwise rotation (rotated around positive z-axis - use Right Hand Rule)
*
* - Rotate 90 degrees CCW: [0 1 -1 0 0 0]
* - Rotate 180 degrees: [-1 0 0 -1 0 0]
* - Rotate 270 degrees: [0 -1 1 0 0 0]
*
* - Skew: [1 tan(a) tan(b) 1 0 0] where a is x-axis skew angle and b is y-axis skew angle
*
*
* @param a an element of the transformation matrix
* @param b an element of the transformation matrix
* @param c an element of the transformation matrix
* @param d an element of the transformation matrix
* @param e an element of the transformation matrix
* @param f an element of the transformation matrix
**/
public void concatCTM(final float a, final float b, final float c, final float d, final float e, final float f) {
if (inText && isTagged()) {
endText();
}
state.CTM.concatenate(new AffineTransform(a, b, c, d, e, f));
content.append(a).append(' ').append(b).append(' ').append(c).append(' ');
content.append(d).append(' ').append(e).append(' ').append(f).append(" cm").append_i(separator);
}
/**
* Concatenate a matrix to the current transformation matrix.
* @param transform added to the Current Transformation Matrix
*/
public void concatCTM(final AffineTransform transform) {
double matrix[] = new double[6];
transform.getMatrix(matrix);
concatCTM((float) matrix[0], (float) matrix[1], (float) matrix[2],
(float) matrix[3], (float) matrix[4], (float) matrix[5]);
}
/**
* Generates an array of bezier curves to draw an arc.
*
* (x1, y1) and (x2, y2) are the corners of the enclosing rectangle.
* Angles, measured in degrees, start with 0 to the right (the positive X
* axis) and increase counter-clockwise. The arc extends from startAng
* to startAng+extent. I.e. startAng=0 and extent=180 yields an openside-down
* semi-circle.
*
* The resulting coordinates are of the form float[]{x1,y1,x2,y2,x3,y3, x4,y4}
* such that the curve goes from (x1, y1) to (x4, y4) with (x2, y2) and
* (x3, y3) as their respective Bezier control points.
*
* Note: this code was taken from ReportLab (www.reportlab.org), an excellent
* PDF generator for Python (BSD license: http://www.reportlab.org/devfaq.html#1.3 ).
*
* @param x1 a corner of the enclosing rectangle
* @param y1 a corner of the enclosing rectangle
* @param x2 a corner of the enclosing rectangle
* @param y2 a corner of the enclosing rectangle
* @param startAng starting angle in degrees
* @param extent angle extent in degrees
* @return a list of float[] with the bezier curves
*/
public static ArrayList bezierArc(float x1, float y1, float x2, float y2, final float startAng, final float extent) {
float tmp;
if (x1 > x2) {
tmp = x1;
x1 = x2;
x2 = tmp;
}
if (y2 > y1) {
tmp = y1;
y1 = y2;
y2 = tmp;
}
float fragAngle;
int Nfrag;
if (Math.abs(extent) <= 90f) {
fragAngle = extent;
Nfrag = 1;
}
else {
Nfrag = (int)Math.ceil(Math.abs(extent)/90f);
fragAngle = extent / Nfrag;
}
float x_cen = (x1+x2)/2f;
float y_cen = (y1+y2)/2f;
float rx = (x2-x1)/2f;
float ry = (y2-y1)/2f;
float halfAng = (float)(fragAngle * Math.PI / 360.);
float kappa = (float)Math.abs(4. / 3. * (1. - Math.cos(halfAng)) / Math.sin(halfAng));
ArrayList pointList = new ArrayList();
for (int i = 0; i < Nfrag; ++i) {
float theta0 = (float)((startAng + i*fragAngle) * Math.PI / 180.);
float theta1 = (float)((startAng + (i+1)*fragAngle) * Math.PI / 180.);
float cos0 = (float)Math.cos(theta0);
float cos1 = (float)Math.cos(theta1);
float sin0 = (float)Math.sin(theta0);
float sin1 = (float)Math.sin(theta1);
if (fragAngle > 0f) {
pointList.add(new float[]{x_cen + rx * cos0,
y_cen - ry * sin0,
x_cen + rx * (cos0 - kappa * sin0),
y_cen - ry * (sin0 + kappa * cos0),
x_cen + rx * (cos1 + kappa * sin1),
y_cen - ry * (sin1 - kappa * cos1),
x_cen + rx * cos1,
y_cen - ry * sin1});
}
else {
pointList.add(new float[]{x_cen + rx * cos0,
y_cen - ry * sin0,
x_cen + rx * (cos0 + kappa * sin0),
y_cen - ry * (sin0 - kappa * cos0),
x_cen + rx * (cos1 - kappa * sin1),
y_cen - ry * (sin1 + kappa * cos1),
x_cen + rx * cos1,
y_cen - ry * sin1});
}
}
return pointList;
}
/**
* Draws a partial ellipse inscribed within the rectangle x1,y1,x2,y2,
* starting at startAng degrees and covering extent degrees. Angles
* start with 0 to the right (+x) and increase counter-clockwise.
*
* @param x1 a corner of the enclosing rectangle
* @param y1 a corner of the enclosing rectangle
* @param x2 a corner of the enclosing rectangle
* @param y2 a corner of the enclosing rectangle
* @param startAng starting angle in degrees
* @param extent angle extent in degrees
*/
public void arc(final float x1, final float y1, final float x2, final float y2, final float startAng, final float extent) {
ArrayList ar = bezierArc(x1, y1, x2, y2, startAng, extent);
if (ar.isEmpty())
return;
float pt[] = ar.get(0);
moveTo(pt[0], pt[1]);
for (int k = 0; k < ar.size(); ++k) {
pt = ar.get(k);
curveTo(pt[2], pt[3], pt[4], pt[5], pt[6], pt[7]);
}
}
/**
* Draws an ellipse inscribed within the rectangle x1,y1,x2,y2.
*
* @param x1 a corner of the enclosing rectangle
* @param y1 a corner of the enclosing rectangle
* @param x2 a corner of the enclosing rectangle
* @param y2 a corner of the enclosing rectangle
*/
public void ellipse(final float x1, final float y1, final float x2, final float y2) {
arc(x1, y1, x2, y2, 0f, 360f);
}
/**
* Create a new colored tiling pattern.
*
* @param width the width of the pattern
* @param height the height of the pattern
* @param xstep the desired horizontal spacing between pattern cells.
* May be either positive or negative, but not zero.
* @param ystep the desired vertical spacing between pattern cells.
* May be either positive or negative, but not zero.
* @return the PdfPatternPainter
where the pattern will be created
*/
public PdfPatternPainter createPattern(final float width, final float height, final float xstep, final float ystep) {
checkWriter();
if ( xstep == 0.0f || ystep == 0.0f )
throw new RuntimeException(MessageLocalization.getComposedMessage("xstep.or.ystep.can.not.be.zero"));
PdfPatternPainter painter = new PdfPatternPainter(writer);
painter.setWidth(width);
painter.setHeight(height);
painter.setXStep(xstep);
painter.setYStep(ystep);
writer.addSimplePattern(painter);
return painter;
}
/**
* Create a new colored tiling pattern. Variables xstep and ystep are set to the same values
* of width and height.
* @param width the width of the pattern
* @param height the height of the pattern
* @return the PdfPatternPainter
where the pattern will be created
*/
public PdfPatternPainter createPattern(final float width, final float height) {
return createPattern(width, height, width, height);
}
/**
* Create a new uncolored tiling pattern.
*
* @param width the width of the pattern
* @param height the height of the pattern
* @param xstep the desired horizontal spacing between pattern cells.
* May be either positive or negative, but not zero.
* @param ystep the desired vertical spacing between pattern cells.
* May be either positive or negative, but not zero.
* @param color the default color. Can be null
* @return the PdfPatternPainter
where the pattern will be created
*/
public PdfPatternPainter createPattern(final float width, final float height, final float xstep, final float ystep, final BaseColor color) {
checkWriter();
if ( xstep == 0.0f || ystep == 0.0f )
throw new RuntimeException(MessageLocalization.getComposedMessage("xstep.or.ystep.can.not.be.zero"));
PdfPatternPainter painter = new PdfPatternPainter(writer, color);
painter.setWidth(width);
painter.setHeight(height);
painter.setXStep(xstep);
painter.setYStep(ystep);
writer.addSimplePattern(painter);
return painter;
}
/**
* Create a new uncolored tiling pattern.
* Variables xstep and ystep are set to the same values
* of width and height.
* @param width the width of the pattern
* @param height the height of the pattern
* @param color the default color. Can be null
* @return the PdfPatternPainter
where the pattern will be created
*/
public PdfPatternPainter createPattern(final float width, final float height, final BaseColor color) {
return createPattern(width, height, width, height, color);
}
/**
* Creates a new template.
*
* Creates a new template that is nothing more than a form XObject. This template can be included
* in this PdfContentByte
or in another template. Templates are only written
* to the output when the document is closed permitting things like showing text in the first page
* that is only defined in the last page.
*
* @param width the bounding box width
* @param height the bounding box height
* @return the created template
*/
public PdfTemplate createTemplate(final float width, final float height) {
return createTemplate(width, height, null);
}
PdfTemplate createTemplate(final float width, final float height, final PdfName forcedName) {
checkWriter();
PdfTemplate template = new PdfTemplate(writer);
template.setWidth(width);
template.setHeight(height);
writer.addDirectTemplateSimple(template, forcedName);
return template;
}
/**
* Creates a new appearance to be used with form fields.
*
* @param width the bounding box width
* @param height the bounding box height
* @return the appearance created
*/
public PdfAppearance createAppearance(final float width, final float height) {
return createAppearance(width, height, null);
}
PdfAppearance createAppearance(final float width, final float height, final PdfName forcedName) {
checkWriter();
PdfAppearance template = new PdfAppearance(writer);
template.setWidth(width);
template.setHeight(height);
writer.addDirectTemplateSimple(template, forcedName);
return template;
}
/**
* Adds a PostScript XObject to this content.
*
* @param psobject the object
*/
public void addPSXObject(final PdfPSXObject psobject) {
if (inText && isTagged()) {
endText();
}
checkWriter();
PdfName name = writer.addDirectTemplateSimple(psobject, null);
PageResources prs = getPageResources();
name = prs.addXObject(name, psobject.getIndirectReference());
content.append(name.getBytes()).append(" Do").append_i(separator);
}
/**
* Adds a template to this content.
*
* @param template the template
* @param a an element of the transformation matrix
* @param b an element of the transformation matrix
* @param c an element of the transformation matrix
* @param d an element of the transformation matrix
* @param e an element of the transformation matrix
* @param f an element of the transformation matrix
*/
public void addTemplate(final PdfTemplate template, final float a, final float b, final float c, final float d, final float e, final float f) {
addTemplate(template, a, b, c, d, e, f, false);
}
/**
* Adds a template to this content.
*
* @param template the template
* @param a an element of the transformation matrix
* @param b an element of the transformation matrix
* @param c an element of the transformation matrix
* @param d an element of the transformation matrix
* @param e an element of the transformation matrix
* @param f an element of the transformation matrix
* @param tagContent true
- template content will be tagged(all that will be added after), false
- only a Do operator will be tagged.
* taken into account only if isTagged()
- true
.
*/
public void addTemplate(final PdfTemplate template, final float a, final float b, final float c, final float d, final float e, final float f, boolean tagContent) {
checkWriter();
checkNoPattern(template);
PdfWriter.checkPdfIsoConformance(writer, PdfIsoKeys.PDFISOKEY_FORM_XOBJ, template);
PdfName name = writer.addDirectTemplateSimple(template, null);
PageResources prs = getPageResources();
name = prs.addXObject(name, template.getIndirectReference());
if (isTagged()) {
if (inText)
endText();
if (template.isContentTagged() || (template.getPageReference() != null && tagContent)) {
throw new RuntimeException(MessageLocalization.getComposedMessage("template.with.tagged.could.not.be.used.more.than.once"));
}
template.setPageReference(writer.getCurrentPage());
if (tagContent) {
template.setContentTagged(true);
ArrayList allMcElements = getMcElements();
if (allMcElements != null && allMcElements.size() > 0)
template.getMcElements().add(allMcElements.get(allMcElements.size() - 1));
} else {
openMCBlock(template);
}
}
content.append("q ");
content.append(a).append(' ');
content.append(b).append(' ');
content.append(c).append(' ');
content.append(d).append(' ');
content.append(e).append(' ');
content.append(f).append(" cm ");
content.append(name.getBytes()).append(" Do Q").append_i(separator);
if (isTagged() && !tagContent) {
closeMCBlock(template);
template.setId(null);
}
}
/**
* Adds a form XObject to this content.
*
* @param formXObj the form XObject
* @param name the name of form XObject in content stream
* @param a an element of the transformation matrix
* @param b an element of the transformation matrix
* @param c an element of the transformation matrix
* @param d an element of the transformation matrix
* @param e an element of the transformation matrix
* @param f an element of the transformation matrix
*/
public void addFormXObj(final PdfStream formXObj, final PdfName name, final float a, final float b, final float c, final float d, final float e, final float f) throws IOException {
checkWriter();
PdfWriter.checkPdfIsoConformance(writer, PdfIsoKeys.PDFISOKEY_STREAM, formXObj);
PageResources prs = getPageResources();
prs.addXObject(name, writer.addToBody(formXObj).getIndirectReference());
PdfArtifact artifact = null;
if (isTagged()) {
if (inText)
endText();
artifact = new PdfArtifact();
openMCBlock(artifact);
}
content.append("q ");
content.append(a).append(' ');
content.append(b).append(' ');
content.append(c).append(' ');
content.append(d).append(' ');
content.append(e).append(' ');
content.append(f).append(" cm ");
content.append(name.getBytes()).append(" Do Q").append_i(separator);
if (isTagged()) {
closeMCBlock(artifact);
}
}
/**
* adds a template with the given matrix.
* @param template template to add
* @param transform transform to apply to the template prior to adding it.
*/
public void addTemplate(final PdfTemplate template, final AffineTransform transform) {
addTemplate(template, transform, false);
}
/**
* adds a template with the given matrix.
* @param template template to add
* @param transform transform to apply to the template prior to adding it.
* @param tagContent true
- template content will be tagged(all that will be added after), false
- only a Do operator will be tagged.
* taken into account only if isTagged()
- true
.
*/
public void addTemplate(final PdfTemplate template, final AffineTransform transform, boolean tagContent) {
double matrix[] = new double[6];
transform.getMatrix(matrix);
addTemplate(template, (float) matrix[0], (float) matrix[1], (float) matrix[2],
(float) matrix[3], (float) matrix[4], (float) matrix[5], tagContent);
}
void addTemplateReference(final PdfIndirectReference template, PdfName name, final float a, final float b, final float c, final float d, final float e, final float f) {
if (inText && isTagged()) {
endText();
}
checkWriter();
PageResources prs = getPageResources();
name = prs.addXObject(name, template);
content.append("q ");
content.append(a).append(' ');
content.append(b).append(' ');
content.append(c).append(' ');
content.append(d).append(' ');
content.append(e).append(' ');
content.append(f).append(" cm ");
content.append(name.getBytes()).append(" Do Q").append_i(separator);
}
/**
* Adds a template to this content.
*
* @param template the template
* @param x the x location of this template
* @param y the y location of this template
*/
public void addTemplate(final PdfTemplate template, final float x, final float y) {
addTemplate(template, 1, 0, 0, 1, x, y);
}
public void addTemplate(final PdfTemplate template, final float x, final float y, boolean tagContent) {
addTemplate(template, 1, 0, 0, 1, x, y, tagContent);
}
/**
* Changes the current color for filling paths (device dependent colors!).
*
* Sets the color space to DeviceCMYK (or the DefaultCMYK color space),
* and sets the color to use for filling paths.
*
* This method is described in the 'Portable Document Format Reference Manual version 1.3'
* section 8.5.2.1 (page 331).
*
* Following the PDF manual, each operand must be a number between 0 (no ink) and
* 1 (maximum ink). This method however accepts only integers between 0x00 and 0xFF.
*
* @param cyan the intensity of cyan
* @param magenta the intensity of magenta
* @param yellow the intensity of yellow
* @param black the intensity of black
*/
public void setCMYKColorFill(final int cyan, final int magenta, final int yellow, final int black) {
saveColor(new CMYKColor(cyan, magenta, yellow, black), true);
content.append((float)(cyan & 0xFF) / 0xFF);
content.append(' ');
content.append((float)(magenta & 0xFF) / 0xFF);
content.append(' ');
content.append((float)(yellow & 0xFF) / 0xFF);
content.append(' ');
content.append((float)(black & 0xFF) / 0xFF);
content.append(" k").append_i(separator);
}
/**
* Changes the current color for stroking paths (device dependent colors!).
*
* Sets the color space to DeviceCMYK (or the DefaultCMYK color space),
* and sets the color to use for stroking paths.
*
* This method is described in the 'Portable Document Format Reference Manual version 1.3'
* section 8.5.2.1 (page 331).
* Following the PDF manual, each operand must be a number between 0 (minimum intensity) and
* 1 (maximum intensity). This method however accepts only integers between 0x00 and 0xFF.
*
* @param cyan the intensity of red
* @param magenta the intensity of green
* @param yellow the intensity of blue
* @param black the intensity of black
*/
public void setCMYKColorStroke(final int cyan, final int magenta, final int yellow, final int black) {
saveColor(new CMYKColor(cyan, magenta, yellow, black), false);
content.append((float)(cyan & 0xFF) / 0xFF);
content.append(' ');
content.append((float)(magenta & 0xFF) / 0xFF);
content.append(' ');
content.append((float)(yellow & 0xFF) / 0xFF);
content.append(' ');
content.append((float) (black & 0xFF) / 0xFF);
content.append(" K").append_i(separator);
}
/**
* Changes the current color for filling paths (device dependent colors!).
*
* Sets the color space to DeviceRGB (or the DefaultRGB color space),
* and sets the color to use for filling paths.
*
* This method is described in the 'Portable Document Format Reference Manual version 1.3'
* section 8.5.2.1 (page 331).
*
* Following the PDF manual, each operand must be a number between 0 (minimum intensity) and
* 1 (maximum intensity). This method however accepts only integers between 0x00 and 0xFF.
*
* @param red the intensity of red
* @param green the intensity of green
* @param blue the intensity of blue
*/
public void setRGBColorFill(final int red, final int green, final int blue) {
saveColor(new BaseColor(red, green, blue), true);
HelperRGB((float) (red & 0xFF) / 0xFF, (float) (green & 0xFF) / 0xFF, (float) (blue & 0xFF) / 0xFF);
content.append(" rg").append_i(separator);
}
/**
* Changes the current color for stroking paths (device dependent colors!).
*
* Sets the color space to DeviceRGB (or the DefaultRGB color space),
* and sets the color to use for stroking paths.
*
* This method is described in the 'Portable Document Format Reference Manual version 1.3'
* section 8.5.2.1 (page 331).
* Following the PDF manual, each operand must be a number between 0 (minimum intensity) and
* 1 (maximum intensity). This method however accepts only integers between 0x00 and 0xFF.
*
* @param red the intensity of red
* @param green the intensity of green
* @param blue the intensity of blue
*/
public void setRGBColorStroke(final int red, final int green, final int blue) {
saveColor(new BaseColor(red, green, blue), false);
HelperRGB((float) (red & 0xFF) / 0xFF, (float) (green & 0xFF) / 0xFF, (float) (blue & 0xFF) / 0xFF);
content.append(" RG").append_i(separator);
}
/** Sets the stroke color. color
can be an
* ExtendedColor
.
* @param color the color
*/
public void setColorStroke(final BaseColor color) {
int type = ExtendedColor.getType(color);
switch (type) {
case ExtendedColor.TYPE_GRAY: {
setGrayStroke(((GrayColor) color).getGray());
break;
}
case ExtendedColor.TYPE_CMYK: {
CMYKColor cmyk = (CMYKColor)color;
setCMYKColorStrokeF(cmyk.getCyan(), cmyk.getMagenta(), cmyk.getYellow(), cmyk.getBlack());
break;
}
case ExtendedColor.TYPE_SEPARATION: {
SpotColor spot = (SpotColor)color;
setColorStroke(spot.getPdfSpotColor(), spot.getTint());
break;
}
case ExtendedColor.TYPE_PATTERN: {
PatternColor pat = (PatternColor) color;
setPatternStroke(pat.getPainter());
break;
}
case ExtendedColor.TYPE_SHADING: {
ShadingColor shading = (ShadingColor) color;
setShadingStroke(shading.getPdfShadingPattern());
break;
}
case ExtendedColor.TYPE_DEVICEN: {
DeviceNColor devicen = (DeviceNColor)color;
setColorStroke(devicen.getPdfDeviceNColor(), devicen.getTints());
break;
}
case ExtendedColor.TYPE_LAB: {
LabColor lab = (LabColor)color;
setColorStroke(lab.getLabColorSpace(), lab.getL(), lab.getA(), lab.getB());
break;
}
default:
setRGBColorStroke(color.getRed(), color.getGreen(), color.getBlue());
}
}
/** Sets the fill color. color
can be an
* ExtendedColor
.
* @param color the color
*/
public void setColorFill(final BaseColor color) {
int type = ExtendedColor.getType(color);
switch (type) {
case ExtendedColor.TYPE_GRAY: {
setGrayFill(((GrayColor) color).getGray());
break;
}
case ExtendedColor.TYPE_CMYK: {
CMYKColor cmyk = (CMYKColor)color;
setCMYKColorFillF(cmyk.getCyan(), cmyk.getMagenta(), cmyk.getYellow(), cmyk.getBlack());
break;
}
case ExtendedColor.TYPE_SEPARATION: {
SpotColor spot = (SpotColor)color;
setColorFill(spot.getPdfSpotColor(), spot.getTint());
break;
}
case ExtendedColor.TYPE_PATTERN: {
PatternColor pat = (PatternColor) color;
setPatternFill(pat.getPainter());
break;
}
case ExtendedColor.TYPE_SHADING: {
ShadingColor shading = (ShadingColor) color;
setShadingFill(shading.getPdfShadingPattern());
break;
}
case ExtendedColor.TYPE_DEVICEN: {
DeviceNColor devicen = (DeviceNColor)color;
setColorFill(devicen.getPdfDeviceNColor(), devicen.getTints());
break;
}
case ExtendedColor.TYPE_LAB: {
LabColor lab = (LabColor)color;
setColorFill(lab.getLabColorSpace(), lab.getL(), lab.getA(), lab.getB());
break;
}
default:
setRGBColorFill(color.getRed(), color.getGreen(), color.getBlue());
}
}
/** Sets the fill color to a spot color.
* @param sp the spot color
* @param tint the tint for the spot color. 0 is no color and 1
* is 100% color
*/
public void setColorFill(final PdfSpotColor sp, final float tint) {
checkWriter();
state.colorDetails = writer.addSimple(sp);
PageResources prs = getPageResources();
PdfName name = state.colorDetails.getColorSpaceName();
name = prs.addColor(name, state.colorDetails.getIndirectReference());
saveColor(new SpotColor(sp, tint), true);
content.append(name.getBytes()).append(" cs ").append(tint).append(" scn").append_i(separator);
}
public void setColorFill(final PdfDeviceNColor dn, final float[] tints) {
checkWriter();
state.colorDetails = writer.addSimple(dn);
PageResources prs = getPageResources();
PdfName name = state.colorDetails.getColorSpaceName();
name = prs.addColor(name, state.colorDetails.getIndirectReference());
saveColor(new DeviceNColor(dn, tints), true);
content.append(name.getBytes()).append(" cs ");
for (float tint : tints)
content.append(tint + " ");
content.append("scn").append_i(separator);
}
public void setColorFill(final PdfLabColor lab, float l, float a, float b) {
checkWriter();
state.colorDetails = writer.addSimple(lab);
PageResources prs = getPageResources();
PdfName name = state.colorDetails.getColorSpaceName();
name = prs.addColor(name, state.colorDetails.getIndirectReference());
saveColor(new LabColor(lab, l, a, b), true);
content.append(name.getBytes()).append(" cs ");
content.append(l + " " + a + " " + b + " ");
content.append("scn").append_i(separator);
}
/** Sets the stroke color to a spot color.
* @param sp the spot color
* @param tint the tint for the spot color. 0 is no color and 1
* is 100% color
*/
public void setColorStroke(final PdfSpotColor sp, final float tint) {
checkWriter();
state.colorDetails = writer.addSimple(sp);
PageResources prs = getPageResources();
PdfName name = state.colorDetails.getColorSpaceName();
name = prs.addColor(name, state.colorDetails.getIndirectReference());
saveColor(new SpotColor(sp, tint), false);
content.append(name.getBytes()).append(" CS ").append(tint).append(" SCN").append_i(separator);
}
public void setColorStroke(final PdfDeviceNColor sp, final float[] tints) {
checkWriter();
state.colorDetails = writer.addSimple(sp);
PageResources prs = getPageResources();
PdfName name = state.colorDetails.getColorSpaceName();
name = prs.addColor(name, state.colorDetails.getIndirectReference());
saveColor(new DeviceNColor(sp, tints), true);
content.append(name.getBytes()).append(" CS ");
for (float tint : tints)
content.append(tint + " ");
content.append("SCN").append_i(separator);
}
public void setColorStroke(final PdfLabColor lab, float l, float a, float b) {
checkWriter();
state.colorDetails = writer.addSimple(lab);
PageResources prs = getPageResources();
PdfName name = state.colorDetails.getColorSpaceName();
name = prs.addColor(name, state.colorDetails.getIndirectReference());
saveColor(new LabColor(lab, l, a, b), true);
content.append(name.getBytes()).append(" CS ");
content.append(l + " " + a + " " + b + " ");
content.append("SCN").append_i(separator);
}
/** Sets the fill color to a pattern. The pattern can be
* colored or uncolored.
* @param p the pattern
*/
public void setPatternFill(final PdfPatternPainter p) {
if (p.isStencil()) {
setPatternFill(p, p.getDefaultColor());
return;
}
checkWriter();
PageResources prs = getPageResources();
PdfName name = writer.addSimplePattern(p);
name = prs.addPattern(name, p.getIndirectReference());
saveColor(new PatternColor(p), true);
content.append(PdfName.PATTERN.getBytes()).append(" cs ").append(name.getBytes()).append(" scn").append_i(separator);
}
/** Outputs the color values to the content.
* @param color The color
* @param tint the tint if it is a spot color, ignored otherwise
*/
void outputColorNumbers(final BaseColor color, final float tint) {
PdfWriter.checkPdfIsoConformance(writer, PdfIsoKeys.PDFISOKEY_COLOR, color);
int type = ExtendedColor.getType(color);
switch (type) {
case ExtendedColor.TYPE_RGB:
content.append((float)color.getRed() / 0xFF);
content.append(' ');
content.append((float)color.getGreen() / 0xFF);
content.append(' ');
content.append((float)color.getBlue() / 0xFF);
break;
case ExtendedColor.TYPE_GRAY:
content.append(((GrayColor)color).getGray());
break;
case ExtendedColor.TYPE_CMYK: {
CMYKColor cmyk = (CMYKColor)color;
content.append(cmyk.getCyan()).append(' ').append(cmyk.getMagenta());
content.append(' ').append(cmyk.getYellow()).append(' ').append(cmyk.getBlack());
break;
}
case ExtendedColor.TYPE_SEPARATION:
content.append(tint);
break;
default:
throw new RuntimeException(MessageLocalization.getComposedMessage("invalid.color.type"));
}
}
/** Sets the fill color to an uncolored pattern.
* @param p the pattern
* @param color the color of the pattern
*/
public void setPatternFill(final PdfPatternPainter p, final BaseColor color) {
if (ExtendedColor.getType(color) == ExtendedColor.TYPE_SEPARATION)
setPatternFill(p, color, ((SpotColor)color).getTint());
else
setPatternFill(p, color, 0);
}
/** Sets the fill color to an uncolored pattern.
* @param p the pattern
* @param color the color of the pattern
* @param tint the tint if the color is a spot color, ignored otherwise
*/
public void setPatternFill(final PdfPatternPainter p, final BaseColor color, final float tint) {
checkWriter();
if (!p.isStencil())
throw new RuntimeException(MessageLocalization.getComposedMessage("an.uncolored.pattern.was.expected"));
PageResources prs = getPageResources();
PdfName name = writer.addSimplePattern(p);
name = prs.addPattern(name, p.getIndirectReference());
ColorDetails csDetail = writer.addSimplePatternColorspace(color);
PdfName cName = prs.addColor(csDetail.getColorSpaceName(), csDetail.getIndirectReference());
saveColor(new UncoloredPattern(p, color, tint), true);
content.append(cName.getBytes()).append(" cs").append_i(separator);
outputColorNumbers(color, tint);
content.append(' ').append(name.getBytes()).append(" scn").append_i(separator);
}
/** Sets the stroke color to an uncolored pattern.
* @param p the pattern
* @param color the color of the pattern
*/
public void setPatternStroke(final PdfPatternPainter p, final BaseColor color) {
if (ExtendedColor.getType(color) == ExtendedColor.TYPE_SEPARATION)
setPatternStroke(p, color, ((SpotColor)color).getTint());
else
setPatternStroke(p, color, 0);
}
/** Sets the stroke color to an uncolored pattern.
* @param p the pattern
* @param color the color of the pattern
* @param tint the tint if the color is a spot color, ignored otherwise
*/
public void setPatternStroke(final PdfPatternPainter p, final BaseColor color, final float tint) {
checkWriter();
if (!p.isStencil())
throw new RuntimeException(MessageLocalization.getComposedMessage("an.uncolored.pattern.was.expected"));
PageResources prs = getPageResources();
PdfName name = writer.addSimplePattern(p);
name = prs.addPattern(name, p.getIndirectReference());
ColorDetails csDetail = writer.addSimplePatternColorspace(color);
PdfName cName = prs.addColor(csDetail.getColorSpaceName(), csDetail.getIndirectReference());
saveColor(new UncoloredPattern(p, color, tint), false);
content.append(cName.getBytes()).append(" CS").append_i(separator);
outputColorNumbers(color, tint);
content.append(' ').append(name.getBytes()).append(" SCN").append_i(separator);
}
/** Sets the stroke color to a pattern. The pattern can be
* colored or uncolored.
* @param p the pattern
*/
public void setPatternStroke(final PdfPatternPainter p) {
if (p.isStencil()) {
setPatternStroke(p, p.getDefaultColor());
return;
}
checkWriter();
PageResources prs = getPageResources();
PdfName name = writer.addSimplePattern(p);
name = prs.addPattern(name, p.getIndirectReference());
saveColor(new PatternColor(p), false);
content.append(PdfName.PATTERN.getBytes()).append(" CS ").append(name.getBytes()).append(" SCN").append_i(separator);
}
/**
* Paints using a shading object.
* @param shading the shading object
*/
public void paintShading(final PdfShading shading) {
writer.addSimpleShading(shading);
PageResources prs = getPageResources();
PdfName name = prs.addShading(shading.getShadingName(), shading.getShadingReference());
content.append(name.getBytes()).append(" sh").append_i(separator);
ColorDetails details = shading.getColorDetails();
if (details != null)
prs.addColor(details.getColorSpaceName(), details.getIndirectReference());
}
/**
* Paints using a shading pattern.
* @param shading the shading pattern
*/
public void paintShading(final PdfShadingPattern shading) {
paintShading(shading.getShading());
}
/**
* Sets the shading fill pattern.
* @param shading the shading pattern
*/
public void setShadingFill(final PdfShadingPattern shading) {
writer.addSimpleShadingPattern(shading);
PageResources prs = getPageResources();
PdfName name = prs.addPattern(shading.getPatternName(), shading.getPatternReference());
saveColor(new ShadingColor(shading), true);
content.append(PdfName.PATTERN.getBytes()).append(" cs ").append(name.getBytes()).append(" scn").append_i(separator);
ColorDetails details = shading.getColorDetails();
if (details != null)
prs.addColor(details.getColorSpaceName(), details.getIndirectReference());
}
/**
* Sets the shading stroke pattern
* @param shading the shading pattern
*/
public void setShadingStroke(final PdfShadingPattern shading) {
writer.addSimpleShadingPattern(shading);
PageResources prs = getPageResources();
PdfName name = prs.addPattern(shading.getPatternName(), shading.getPatternReference());
saveColor(new ShadingColor(shading), false);
content.append(PdfName.PATTERN.getBytes()).append(" CS ").append(name.getBytes()).append(" SCN").append_i(separator);
ColorDetails details = shading.getColorDetails();
if (details != null)
prs.addColor(details.getColorSpaceName(), details.getIndirectReference());
}
/** Check if we have a valid PdfWriter.
*
*/
protected void checkWriter() {
if (writer == null)
throw new NullPointerException(MessageLocalization.getComposedMessage("the.writer.in.pdfcontentbyte.is.null"));
}
/**
* Show an array of text.
* @param text array of text
*/
public void showText(final PdfTextArray text) {
checkState();
if (!inText && isTagged()) {
beginText(true);
}
if (state.fontDetails == null)
throw new NullPointerException(MessageLocalization.getComposedMessage("font.and.size.must.be.set.before.writing.any.text"));
content.append("[");
ArrayList