com.aowagie.text.pdf.ColumnText Maven / Gradle / Ivy
/*
* $Id: ColumnText.java 3904 2009-04-24 10:09:01Z blowagie $
*
* Copyright 2001, 2002 by Paulo Soares.
*
* The contents of this file are subject to the Mozilla Public License Version 1.1
* (the "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the License.
*
* The Original Code is 'iText, a free JAVA-PDF library'.
*
* The Initial Developer of the Original Code is Bruno Lowagie. Portions created by
* the Initial Developer are Copyright (C) 1999, 2000, 2001, 2002 by Bruno Lowagie.
* All Rights Reserved.
* Co-Developer of the code is Paulo Soares. Portions created by the Co-Developer
* are Copyright (C) 2000, 2001, 2002 by Paulo Soares. All Rights Reserved.
*
* Contributor(s): all the names of the contributors are added in the source code
* where applicable.
*
* Alternatively, the contents of this file may be used under the terms of the
* LGPL license (the "GNU LIBRARY GENERAL PUBLIC LICENSE"), in which case the
* provisions of LGPL are applicable instead of those above. If you wish to
* allow use of your version of this file only under the terms of the LGPL
* License and not to allow others to use your version of this file under
* the MPL, indicate your decision by deleting the provisions above and
* replace them with the notice and other provisions required by the LGPL.
* If you do not delete the provisions above, a recipient may use your version
* of this file under either the MPL or the GNU LIBRARY GENERAL PUBLIC LICENSE.
*
* This library is free software; you can redistribute it and/or modify it
* under the terms of the MPL as stated above or under the terms of the GNU
* Library General Public License as published by the Free Software Foundation;
* either version 2 of the License, or any later version.
*
* This library 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 Library general Public License for more
* details.
*
* If you didn't download this code from the following link, you should check if
* you aren't using an obsolete version:
* http://www.lowagie.com/iText/
*/
package com.aowagie.text.pdf;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Stack;
import com.aowagie.text.Chunk;
import com.aowagie.text.DocumentException;
import com.aowagie.text.Element;
import com.aowagie.text.ExceptionConverter;
import com.aowagie.text.Image;
import com.aowagie.text.ListItem;
import com.aowagie.text.Paragraph;
import com.aowagie.text.Phrase;
import com.aowagie.text.SimpleTable;
import com.aowagie.text.pdf.draw.DrawInterface;
/**
* Formats text in a columnwise form. The text is bound
* on the left and on the right by a sequence of lines. This allows the column
* to have any shape, not only rectangular.
*
* Several parameters can be set like the first paragraph line indent and
* extra space between paragraphs.
*
* A call to the method go
will return one of the following
* situations: the column ended or the text ended.
*
* I the column ended, a new column definition can be loaded with the method
* setColumns
and the method go
can be called again.
*
* If the text ended, more text can be loaded with addText
* and the method go
can be called again.
* The only limitation is that one or more complete paragraphs must be loaded
* each time.
*
* Full bidirectional reordering is supported. If the run direction is
* PdfWriter.RUN_DIRECTION_RTL
the meaning of the horizontal
* alignments and margins is mirrored.
* @author Paulo Soares ([email protected])
*/
public class ColumnText {
/** Eliminate the arabic vowels */
public static final int AR_NOVOWEL = ArabicLigaturizer.ar_novowel;
/** Compose the tashkeel in the ligatures. */
public static final int AR_COMPOSEDTASHKEEL = ArabicLigaturizer.ar_composedtashkeel;
/** Do some extra double ligatures. */
public static final int AR_LIG = ArabicLigaturizer.ar_lig;
/**
* Digit shaping option: Replace European digits (U+0030...U+0039) by Arabic-Indic digits.
*/
public static final int DIGITS_EN2AN = ArabicLigaturizer.DIGITS_EN2AN;
/**
* Digit shaping option: Replace Arabic-Indic digits by European digits (U+0030...U+0039).
*/
public static final int DIGITS_AN2EN = ArabicLigaturizer.DIGITS_AN2EN;
/**
* Digit shaping option:
* Replace European digits (U+0030...U+0039) by Arabic-Indic digits
* if the most recent strongly directional character
* is an Arabic letter (its Bidi direction value is RIGHT_TO_LEFT_ARABIC).
* The initial state at the start of the text is assumed to be not an Arabic,
* letter, so European digits at the start of the text will not change.
* Compare to DIGITS_ALEN2AN_INIT_AL.
*/
public static final int DIGITS_EN2AN_INIT_LR = ArabicLigaturizer.DIGITS_EN2AN_INIT_LR;
/**
* Digit shaping option:
* Replace European digits (U+0030...U+0039) by Arabic-Indic digits
* if the most recent strongly directional character
* is an Arabic letter (its Bidi direction value is RIGHT_TO_LEFT_ARABIC).
* The initial state at the start of the text is assumed to be an Arabic,
* letter, so European digits at the start of the text will change.
* Compare to DIGITS_ALEN2AN_INT_LR.
*/
public static final int DIGITS_EN2AN_INIT_AL = ArabicLigaturizer.DIGITS_EN2AN_INIT_AL;
/**
* Digit type option: Use Arabic-Indic digits (U+0660...U+0669).
*/
public static final int DIGIT_TYPE_AN = ArabicLigaturizer.DIGIT_TYPE_AN;
/**
* Digit type option: Use Eastern (Extended) Arabic-Indic digits (U+06f0...U+06f9).
*/
public static final int DIGIT_TYPE_AN_EXTENDED = ArabicLigaturizer.DIGIT_TYPE_AN_EXTENDED;
protected int runDirection = PdfWriter.RUN_DIRECTION_DEFAULT;
/** the space char ratio */
public static final float GLOBAL_SPACE_CHAR_RATIO = 0;
/** Initial value of the status. */
public static final int START_COLUMN = 0;
/** Signals that there is no more text available. */
public static final int NO_MORE_TEXT = 1;
/** Signals that there is no more column. */
public static final int NO_MORE_COLUMN = 2;
/** The column is valid. */
protected static final int LINE_STATUS_OK = 0;
/** The line is out the column limits. */
protected static final int LINE_STATUS_OFFLIMITS = 1;
/** The line cannot fit this column position. */
protected static final int LINE_STATUS_NOLINE = 2;
/** Upper bound of the column. */
protected float maxY;
/** Lower bound of the column. */
protected float minY;
protected float leftX;
protected float rightX;
/** The column alignment. Default is left alignment. */
protected int alignment = Element.ALIGN_LEFT;
/** The left column bound. */
protected ArrayList leftWall;
/** The right column bound. */
protected ArrayList rightWall;
/** The chunks that form the text. */
// protected ArrayList chunks = new ArrayList();
protected BidiLine bidiLine;
/** The current y line location. Text will be written at this line minus the leading. */
protected float yLine;
/** The leading for the current line. */
protected float currentLeading = 16;
/** The fixed text leading. */
protected float fixedLeading = 16;
/** The text leading that is multiplied by the biggest font size in the line. */
protected float multipliedLeading = 0;
/** The PdfContent
where the text will be written to. */
protected PdfContentByte canvas;
protected PdfContentByte[] canvases;
/** The line status when trying to fit a line to a column. */
protected int lineStatus;
/** The first paragraph line indent. */
protected float indent = 0;
/** The following paragraph lines indent. */
protected float followingIndent = 0;
/** The right paragraph lines indent. */
protected float rightIndent = 0;
/** The extra space between paragraphs. */
protected float extraParagraphSpace = 0;
/** The width of the line when the column is defined as a simple rectangle. */
protected float rectangularWidth = -1;
protected boolean rectangularMode = false;
/** Holds value of property spaceCharRatio. */
private float spaceCharRatio = GLOBAL_SPACE_CHAR_RATIO;
private boolean lastWasNewline = true;
/** Holds value of property linesWritten. */
private int linesWritten;
private float firstLineY;
private boolean firstLineYDone = false;
/** Holds value of property arabicOptions. */
private int arabicOptions = 0;
protected float descender;
protected boolean composite = false;
protected ColumnText compositeColumn;
protected LinkedList compositeElements;
protected int listIdx = 0;
private boolean splittedRow;
protected Phrase waitPhrase;
/** if true, first line height is adjusted so that the max ascender touches the top */
private boolean useAscender = false;
/** Holds value of property filledWidth. */
private float filledWidth;
private boolean adjustFirstLine = true;
/**
* Creates a ColumnText
.
*
* @param canvas the place where the text will be written to. Can
* be a template.
*/
public ColumnText(final PdfContentByte canvas) {
this.canvas = canvas;
}
/**
* Creates an independent duplicated of the instance org
.
*
* @param org the original ColumnText
* @return the duplicated
*/
public static ColumnText duplicate(final ColumnText org) {
final ColumnText ct = new ColumnText(null);
ct.setACopy(org);
return ct;
}
/**
* Makes this instance an independent copy of org
.
*
* @param org the original ColumnText
* @return itself
*/
public ColumnText setACopy(final ColumnText org) {
setSimpleVars(org);
if (org.bidiLine != null) {
this.bidiLine = new BidiLine(org.bidiLine);
}
return this;
}
protected void setSimpleVars(final ColumnText org) {
this.maxY = org.maxY;
this.minY = org.minY;
this.alignment = org.alignment;
this.leftWall = null;
if (org.leftWall != null) {
this.leftWall = new ArrayList(org.leftWall);
}
this.rightWall = null;
if (org.rightWall != null) {
this.rightWall = new ArrayList(org.rightWall);
}
this.yLine = org.yLine;
this.currentLeading = org.currentLeading;
this.fixedLeading = org.fixedLeading;
this.multipliedLeading = org.multipliedLeading;
this.canvas = org.canvas;
this.canvases = org.canvases;
this.lineStatus = org.lineStatus;
this.indent = org.indent;
this.followingIndent = org.followingIndent;
this.rightIndent = org.rightIndent;
this.extraParagraphSpace = org.extraParagraphSpace;
this.rectangularWidth = org.rectangularWidth;
this.rectangularMode = org.rectangularMode;
this.spaceCharRatio = org.spaceCharRatio;
this.lastWasNewline = org.lastWasNewline;
this.linesWritten = org.linesWritten;
this.arabicOptions = org.arabicOptions;
this.runDirection = org.runDirection;
this.descender = org.descender;
this.composite = org.composite;
this.splittedRow = org.splittedRow;
if (org.composite) {
this.compositeElements = new LinkedList(org.compositeElements);
if (this.splittedRow) {
final PdfPTable table = (PdfPTable)this.compositeElements.getFirst();
this.compositeElements.set(0, new PdfPTable(table));
}
if (org.compositeColumn != null) {
this.compositeColumn = duplicate(org.compositeColumn);
}
}
this.listIdx = org.listIdx;
this.firstLineY = org.firstLineY;
this.leftX = org.leftX;
this.rightX = org.rightX;
this.firstLineYDone = org.firstLineYDone;
this.waitPhrase = org.waitPhrase;
this.useAscender = org.useAscender;
this.filledWidth = org.filledWidth;
this.adjustFirstLine = org.adjustFirstLine;
}
private void addWaitingPhrase() {
if (this.bidiLine == null && this.waitPhrase != null) {
this.bidiLine = new BidiLine();
for (final Iterator j = this.waitPhrase.getChunks().iterator(); j.hasNext();) {
this.bidiLine.addChunk(new PdfChunk((Chunk)j.next(), null));
}
this.waitPhrase = null;
}
}
/**
* Adds a Phrase
to the current text array.
* Will not have any effect if addElement() was called before.
*
* @param phrase the text
*/
public void addText(final Phrase phrase) {
if (phrase == null || this.composite) {
return;
}
addWaitingPhrase();
if (this.bidiLine == null) {
this.waitPhrase = phrase;
return;
}
for (final Iterator j = phrase.getChunks().iterator(); j.hasNext();) {
this.bidiLine.addChunk(new PdfChunk((Chunk)j.next(), null));
}
}
/**
* Replaces the current text array with this Phrase
.
* Anything added previously with addElement() is lost.
*
* @param phrase the text
*/
public void setText(final Phrase phrase) {
this.bidiLine = null;
this.composite = false;
this.compositeColumn = null;
this.compositeElements = null;
this.listIdx = 0;
this.splittedRow = false;
this.waitPhrase = phrase;
}
/**
* Adds a Chunk
to the current text array.
* Will not have any effect if addElement() was called before.
*
* @param chunk the text
*/
public void addText(final Chunk chunk) {
if (chunk == null || this.composite) {
return;
}
addText(new Phrase(chunk));
}
/**
* Adds an element. Elements supported are Paragraph
,
* List
, PdfPTable
, Image
and
* Graphic
.
*
* It removes all the text placed with addText()
.
*
* @param element the Element
*/
public void addElement(Element element) {
if (element == null) {
return;
}
if (element instanceof Image) {
final Image img = (Image)element;
final PdfPTable t = new PdfPTable(1);
final float w = img.getWidthPercentage();
if (w == 0) {
t.setTotalWidth(img.getScaledWidth());
t.setLockedWidth(true);
} else {
t.setWidthPercentage(w);
}
t.setSpacingAfter(img.getSpacingAfter());
t.setSpacingBefore(img.getSpacingBefore());
switch (img.getAlignment()) {
case Image.LEFT:
t.setHorizontalAlignment(Element.ALIGN_LEFT);
break;
case Image.RIGHT:
t.setHorizontalAlignment(Element.ALIGN_RIGHT);
break;
default:
t.setHorizontalAlignment(Element.ALIGN_CENTER);
break;
}
final PdfPCell c = new PdfPCell(img, true);
c.setPadding(0);
c.setBorder(img.getBorder());
c.setBorderColor(img.getBorderColor());
c.setBorderWidth(img.getBorderWidth());
c.setBackgroundColor(img.getBackgroundColor());
t.addCell(c);
element = t;
}
if (element.type() == Element.CHUNK) {
element = new Paragraph((Chunk)element);
}
else if (element.type() == Element.PHRASE) {
element = new Paragraph((Phrase)element);
}
if (element instanceof SimpleTable) {
try {
element = ((SimpleTable)element).createPdfPTable();
} catch (final DocumentException e) {
throw new IllegalArgumentException("Element not allowed.");
}
}
else if (element.type() != Element.PARAGRAPH && element.type() != Element.LIST && element.type() != Element.PTABLE && element.type() != Element.YMARK) {
throw new IllegalArgumentException("Element not allowed.");
}
if (!this.composite) {
this.composite = true;
this.compositeElements = new LinkedList();
this.bidiLine = null;
this.waitPhrase = null;
}
this.compositeElements.add(element);
}
/**
* Converts a sequence of lines representing one of the column bounds into
* an internal format.
*
* Each array element will contain a float[4]
representing
* the line x = ax + b.
*
* @param cLine the column array
* @return the converted array
*/
protected ArrayList convertColumn(final float cLine[]) {
if (cLine.length < 4) {
throw new RuntimeException("No valid column line found.");
}
final ArrayList cc = new ArrayList();
for (int k = 0; k < cLine.length - 2; k += 2) {
final float x1 = cLine[k];
final float y1 = cLine[k + 1];
final float x2 = cLine[k + 2];
final float y2 = cLine[k + 3];
if (y1 == y2) {
continue;
}
// x = ay + b
final float a = (x1 - x2) / (y1 - y2);
final float b = x1 - a * y1;
final float r[] = new float[4];
r[0] = Math.min(y1, y2);
r[1] = Math.max(y1, y2);
r[2] = a;
r[3] = b;
cc.add(r);
this.maxY = Math.max(this.maxY, r[1]);
this.minY = Math.min(this.minY, r[0]);
}
if (cc.isEmpty()) {
throw new RuntimeException("No valid column line found.");
}
return cc;
}
/**
* Finds the intersection between the yLine
and the column. It will
* set the lineStatus
appropriately.
*
* @param wall the column to intersect
* @return the x coordinate of the intersection
*/
protected float findLimitsPoint(final ArrayList wall) {
this.lineStatus = LINE_STATUS_OK;
if (this.yLine < this.minY || this.yLine > this.maxY) {
this.lineStatus = LINE_STATUS_OFFLIMITS;
return 0;
}
for (int k = 0; k < wall.size(); ++k) {
final float r[] = (float[])wall.get(k);
if (this.yLine < r[0] || this.yLine > r[1]) {
continue;
}
return r[2] * this.yLine + r[3];
}
this.lineStatus = LINE_STATUS_NOLINE;
return 0;
}
/**
* Finds the intersection between the yLine
and the two
* column bounds. It will set the lineStatus
appropriately.
*
* @return a float[2]
with the x coordinates of the intersection
*/
protected float[] findLimitsOneLine() {
final float x1 = findLimitsPoint(this.leftWall);
if (this.lineStatus == LINE_STATUS_OFFLIMITS || this.lineStatus == LINE_STATUS_NOLINE) {
return null;
}
final float x2 = findLimitsPoint(this.rightWall);
if (this.lineStatus == LINE_STATUS_NOLINE) {
return null;
}
return new float[]{x1, x2};
}
/**
* Finds the intersection between the yLine
,
* the yLine-leading
and the two column bounds.
* It will set the lineStatus
appropriately.
*
* @return a float[4]
with the x coordinates of the intersection
*/
protected float[] findLimitsTwoLines() {
boolean repeat = false;
for (;;) {
if (repeat && this.currentLeading == 0) {
return null;
}
repeat = true;
final float x1[] = findLimitsOneLine();
if (this.lineStatus == LINE_STATUS_OFFLIMITS) {
return null;
}
this.yLine -= this.currentLeading;
if (this.lineStatus == LINE_STATUS_NOLINE) {
continue;
}
final float x2[] = findLimitsOneLine();
if (this.lineStatus == LINE_STATUS_OFFLIMITS) {
return null;
}
if (this.lineStatus == LINE_STATUS_NOLINE) {
this.yLine -= this.currentLeading;
continue;
}
if (x1[0] >= x2[1] || x2[0] >= x1[1]) {
continue;
}
return new float[]{x1[0], x1[1], x2[0], x2[1]};
}
}
/**
* Sets the columns bounds. Each column bound is described by a
* float[]
with the line points [x1,y1,x2,y2,...].
* The array must have at least 4 elements.
*
* @param leftLine the left column bound
* @param rightLine the right column bound
*/
public void setColumns(final float leftLine[], final float rightLine[]) {
this.maxY = -10e20f;
this.minY = 10e20f;
setYLine(Math.max(leftLine[1], leftLine[leftLine.length - 1]));
this.rightWall = convertColumn(rightLine);
this.leftWall = convertColumn(leftLine);
this.rectangularWidth = -1;
this.rectangularMode = false;
}
/**
* Simplified method for rectangular columns.
*
* @param phrase a Phrase
* @param llx the lower left x corner
* @param lly the lower left y corner
* @param urx the upper right x corner
* @param ury the upper right y corner
* @param leading the leading
* @param alignment the column alignment
*/
public void setSimpleColumn(final Phrase phrase, final float llx, final float lly, final float urx, final float ury, final float leading, final int alignment) {
addText(phrase);
setSimpleColumn(llx, lly, urx, ury, leading, alignment);
}
/**
* Simplified method for rectangular columns.
*
* @param llx the lower left x corner
* @param lly the lower left y corner
* @param urx the upper right x corner
* @param ury the upper right y corner
* @param leading the leading
* @param alignment the column alignment
*/
public void setSimpleColumn(final float llx, final float lly, final float urx, final float ury, final float leading, final int alignment) {
setLeading(leading);
this.alignment = alignment;
setSimpleColumn(llx, lly, urx, ury);
}
/**
* Simplified method for rectangular columns.
*
* @param llx Position por llx
* @param lly Position por lly
* @param urx Position por urx
* @param ury Position por ury
*/
public void setSimpleColumn(final float llx, final float lly, final float urx, final float ury) {
this.leftX = Math.min(llx, urx);
this.maxY = Math.max(lly, ury);
this.minY = Math.min(lly, ury);
this.rightX = Math.max(llx, urx);
this.yLine = this.maxY;
this.rectangularWidth = this.rightX - this.leftX;
if (this.rectangularWidth < 0) {
this.rectangularWidth = 0;
}
this.rectangularMode = true;
}
/**
* Sets the leading to fixed.
*
* @param leading the leading
*/
public void setLeading(final float leading) {
this.fixedLeading = leading;
this.multipliedLeading = 0;
}
/**
* Sets the leading fixed and variable. The resultant leading will be
* fixedLeading+multipliedLeading*maxFontSize where maxFontSize is the
* size of the biggest font in the line.
*
* @param fixedLeading the fixed leading
* @param multipliedLeading the variable leading
*/
public void setLeading(final float fixedLeading, final float multipliedLeading) {
this.fixedLeading = fixedLeading;
this.multipliedLeading = multipliedLeading;
}
/**
* Gets the fixed leading.
*
* @return the leading
*/
public float getLeading() {
return this.fixedLeading;
}
/**
* Gets the variable leading.
*
* @return the leading
*/
public float getMultipliedLeading() {
return this.multipliedLeading;
}
/**
* Sets the yLine. The line will be written to yLine-leading.
*
* @param yLine the yLine
*/
public void setYLine(final float yLine) {
this.yLine = yLine;
}
/**
* Gets the yLine.
*
* @return the yLine
*/
public float getYLine() {
return this.yLine;
}
/**
* Sets the alignment.
*
* @param alignment the alignment
*/
public void setAlignment(final int alignment) {
this.alignment = alignment;
}
/**
* Gets the alignment.
*
* @return the alignment
*/
public int getAlignment() {
return this.alignment;
}
/**
* Sets the first paragraph line indent.
*
* @param indent the indent
*/
public void setIndent(final float indent) {
this.indent = indent;
this.lastWasNewline = true;
}
/**
* Gets the first paragraph line indent.
*
* @return the indent
*/
public float getIndent() {
return this.indent;
}
/**
* Sets the following paragraph lines indent.
*
* @param indent the indent
*/
public void setFollowingIndent(final float indent) {
this.followingIndent = indent;
this.lastWasNewline = true;
}
/**
* Gets the following paragraph lines indent.
*
* @return the indent
*/
public float getFollowingIndent() {
return this.followingIndent;
}
/**
* Sets the right paragraph lines indent.
*
* @param indent the indent
*/
public void setRightIndent(final float indent) {
this.rightIndent = indent;
this.lastWasNewline = true;
}
/**
* Gets the right paragraph lines indent.
*
* @return the indent
*/
public float getRightIndent() {
return this.rightIndent;
}
/**
* Outputs the lines to the document. It is equivalent to go(false)
.
*
* @return returns the result of the operation. It can be NO_MORE_TEXT
* and/or NO_MORE_COLUMN
* @throws DocumentException on error
*/
public int go() throws DocumentException {
return go(false);
}
/**
* Outputs the lines to the document. The output can be simulated.
* @param simulate true
to simulate the writing to the document
* @return returns the result of the operation. It can be NO_MORE_TEXT
* and/or NO_MORE_COLUMN
* @throws DocumentException on error
*/
public int go(final boolean simulate) throws DocumentException {
if (this.composite) {
return goComposite(simulate);
}
addWaitingPhrase();
if (this.bidiLine == null) {
return NO_MORE_TEXT;
}
this.descender = 0;
this.linesWritten = 0;
boolean dirty = false;
float ratio = this.spaceCharRatio;
final Object currentValues[] = new Object[2];
PdfFont currentFont = null;
final Float lastBaseFactor = Float.valueOf(0);
currentValues[1] = lastBaseFactor;
PdfDocument pdf = null;
PdfContentByte graphics = null;
PdfContentByte text = null;
this.firstLineY = Float.NaN;
int localRunDirection = PdfWriter.RUN_DIRECTION_NO_BIDI;
if (this.runDirection != PdfWriter.RUN_DIRECTION_DEFAULT) {
localRunDirection = this.runDirection;
}
if (this.canvas != null) {
graphics = this.canvas;
pdf = this.canvas.getPdfDocument();
text = this.canvas.getDuplicate();
}
else if (!simulate) {
throw new NullPointerException("ColumnText.go with simulate==false and text==null.");
}
if (!simulate) {
if (ratio == GLOBAL_SPACE_CHAR_RATIO) {
ratio = text.getPdfWriter().getSpaceCharRatio();
} else if (ratio < 0.001f) {
ratio = 0.001f;
}
}
float firstIndent = 0;
PdfLine line;
float x1;
int status = 0;
while(true) {
firstIndent = this.lastWasNewline ? this.indent : this.followingIndent; //
if (this.rectangularMode) {
if (this.rectangularWidth <= firstIndent + this.rightIndent) {
status = NO_MORE_COLUMN;
if (this.bidiLine.isEmpty()) {
status |= NO_MORE_TEXT;
}
break;
}
if (this.bidiLine.isEmpty()) {
status = NO_MORE_TEXT;
break;
}
line = this.bidiLine.processLine(this.leftX, this.rectangularWidth - firstIndent - this.rightIndent, this.alignment, localRunDirection, this.arabicOptions);
if (line == null) {
status = NO_MORE_TEXT;
break;
}
final float[] maxSize = line.getMaxSize();
if (isUseAscender() && Float.isNaN(this.firstLineY)) {
this.currentLeading = line.getAscender();
} else {
this.currentLeading = Math.max(this.fixedLeading + maxSize[0] * this.multipliedLeading, maxSize[1]);
}
if (this.yLine > this.maxY || this.yLine - this.currentLeading < this.minY ) {
status = NO_MORE_COLUMN;
this.bidiLine.restore();
break;
}
this.yLine -= this.currentLeading;
if (!simulate && !dirty) {
text.beginText();
dirty = true;
}
if (Float.isNaN(this.firstLineY)) {
this.firstLineY = this.yLine;
}
updateFilledWidth(this.rectangularWidth - line.widthLeft());
x1 = this.leftX;
}
else {
final float yTemp = this.yLine;
final float xx[] = findLimitsTwoLines();
if (xx == null) {
status = NO_MORE_COLUMN;
if (this.bidiLine.isEmpty()) {
status |= NO_MORE_TEXT;
}
this.yLine = yTemp;
break;
}
if (this.bidiLine.isEmpty()) {
status = NO_MORE_TEXT;
this.yLine = yTemp;
break;
}
x1 = Math.max(xx[0], xx[2]);
final float x2 = Math.min(xx[1], xx[3]);
if (x2 - x1 <= firstIndent + this.rightIndent) {
continue;
}
if (!simulate && !dirty) {
text.beginText();
dirty = true;
}
line = this.bidiLine.processLine(x1, x2 - x1 - firstIndent - this.rightIndent, this.alignment, localRunDirection, this.arabicOptions);
if (line == null) {
status = NO_MORE_TEXT;
this.yLine = yTemp;
break;
}
}
if (!simulate) {
currentValues[0] = currentFont;
text.setTextMatrix(x1 + (line.isRTL() ? this.rightIndent : firstIndent) + line.indentLeft(), this.yLine);
pdf.writeLineToContent(line, text, graphics, currentValues, ratio);
currentFont = (PdfFont)currentValues[0];
}
this.lastWasNewline = line.isNewlineSplit();
this.yLine -= line.isNewlineSplit() ? this.extraParagraphSpace : 0;
++this.linesWritten;
this.descender = line.getDescender();
}
if (dirty) {
text.endText();
this.canvas.add(text);
}
return status;
}
/**
* Sets the extra space between paragraphs.
*
* @return the extra space between paragraphs
*/
public float getExtraParagraphSpace() {
return this.extraParagraphSpace;
}
/**
* Sets the extra space between paragraphs.
*
* @param extraParagraphSpace the extra space between paragraphs
*/
public void setExtraParagraphSpace(final float extraParagraphSpace) {
this.extraParagraphSpace = extraParagraphSpace;
}
/**
* Clears the chunk array.
* A call to go()
will always return NO_MORE_TEXT.
*/
public void clearChunks() {
if (this.bidiLine != null) {
this.bidiLine.clearChunks();
}
}
/**
* Gets the space/character extra spacing ratio for fully justified text.
*
* @return the space/character extra spacing ratio
*/
public float getSpaceCharRatio() {
return this.spaceCharRatio;
}
/**
* Sets the ratio between the extra word spacing and the extra character
* spacing when the text is fully justified.
* Extra word spacing will grow spaceCharRatio
times more
* than extra character spacing.
* If the ratio is PdfWriter.NO_SPACE_CHAR_RATIO
then the
* extra character spacing will be zero.
*
* @param spaceCharRatio the ratio between the extra word spacing and the extra character spacing
*/
public void setSpaceCharRatio(final float spaceCharRatio) {
this.spaceCharRatio = spaceCharRatio;
}
/**
* Sets the run direction.
*
* @param runDirection the run direction
*/
public void setRunDirection(final int runDirection) {
if (runDirection < PdfWriter.RUN_DIRECTION_DEFAULT || runDirection > PdfWriter.RUN_DIRECTION_RTL) {
throw new RuntimeException("Invalid run direction: " + runDirection);
}
this.runDirection = runDirection;
}
/**
* Gets the run direction.
*
* @return the run direction
*/
public int getRunDirection() {
return this.runDirection;
}
/**
* Gets the number of lines written.
*
* @return the number of lines written
*/
public int getLinesWritten() {
return this.linesWritten;
}
/**
* Gets the arabic shaping options.
*
* @return the arabic shaping options
*/
public int getArabicOptions() {
return this.arabicOptions;
}
/**
* Sets the arabic shaping options. The option can be AR_NOVOWEL,
* AR_COMPOSEDTASHKEEL and AR_LIG.
*
* @param arabicOptions the arabic shaping options
*/
public void setArabicOptions(final int arabicOptions) {
this.arabicOptions = arabicOptions;
}
/**
* Gets the biggest descender value of the last line written.
*
* @return the biggest descender value of the last line written
*/
public float getDescender() {
return this.descender;
}
/**
* Gets the width that the line will occupy after writing.
* Only the width of the first line is returned.
*
* @param phrase the Phrase
containing the line
* @param runDirection the run direction
* @param arabicOptions the options for the arabic shaping
* @return the width of the line
*/
public static float getWidth(final Phrase phrase, final int runDirection, final int arabicOptions) {
final ColumnText ct = new ColumnText(null);
ct.addText(phrase);
ct.addWaitingPhrase();
final PdfLine line = ct.bidiLine.processLine(0, 20000, Element.ALIGN_LEFT, runDirection, arabicOptions);
if (line == null) {
return 0;
} else {
return 20000 - line.widthLeft();
}
}
/**
* Gets the width that the line will occupy after writing.
* Only the width of the first line is returned.
*
* @param phrase the Phrase
containing the line
* @return the width of the line
*/
public static float getWidth(final Phrase phrase) {
return getWidth(phrase, PdfWriter.RUN_DIRECTION_NO_BIDI, 0);
}
/**
* Shows a line of text. Only the first line is written.
*
* @param canvas where the text is to be written to
* @param alignment the alignment. It is not influenced by the run direction
* @param phrase the Phrase
with the text
* @param x the x reference position
* @param y the y reference position
* @param rotation the rotation to be applied in degrees counterclockwise
* @param runDirection the run direction
* @param arabicOptions the options for the arabic shaping
*/
public static void showTextAligned(final PdfContentByte canvas, int alignment, final Phrase phrase, final float x, final float y, final float rotation, final int runDirection, final int arabicOptions) {
if (alignment != Element.ALIGN_LEFT && alignment != Element.ALIGN_CENTER
&& alignment != Element.ALIGN_RIGHT) {
alignment = Element.ALIGN_LEFT;
}
canvas.saveState();
final ColumnText ct = new ColumnText(canvas);
float lly = -1;
float ury = 2;
float llx;
float urx;
switch (alignment) {
case Element.ALIGN_LEFT:
llx = 0;
urx = 20000;
break;
case Element.ALIGN_RIGHT:
llx = -20000;
urx = 0;
break;
default:
llx = -20000;
urx = 20000;
break;
}
if (rotation == 0) {
llx += x;
lly += y;
urx += x;
ury += y;
}
else {
final double alpha = rotation * Math.PI / 180.0;
final float cos = (float)Math.cos(alpha);
final float sin = (float)Math.sin(alpha);
canvas.concatCTM(cos, sin, -sin, cos, x, y);
}
ct.setSimpleColumn(phrase, llx, lly, urx, ury, 2, alignment);
if (runDirection == PdfWriter.RUN_DIRECTION_RTL) {
if (alignment == Element.ALIGN_LEFT) {
alignment = Element.ALIGN_RIGHT;
} else if (alignment == Element.ALIGN_RIGHT) {
alignment = Element.ALIGN_LEFT;
}
}
ct.setAlignment(alignment);
ct.setArabicOptions(arabicOptions);
ct.setRunDirection(runDirection);
try {
ct.go();
}
catch (final DocumentException e) {
throw new ExceptionConverter(e);
}
canvas.restoreState();
}
/**
* Shows a line of text. Only the first line is written.
*
* @param canvas where the text is to be written to
* @param alignment the alignment
* @param phrase the Phrase
with the text
* @param x the x reference position
* @param y the y reference position
* @param rotation the rotation to be applied in degrees counterclockwise
*/
public static void showTextAligned(final PdfContentByte canvas, final int alignment, final Phrase phrase, final float x, final float y, final float rotation) {
showTextAligned(canvas, alignment, phrase, x, y, rotation, PdfWriter.RUN_DIRECTION_NO_BIDI, 0);
}
protected int goComposite(final boolean simulate) throws DocumentException {
if (!this.rectangularMode) {
throw new DocumentException("Irregular columns are not supported in composite mode.");
}
this.linesWritten = 0;
this.descender = 0;
boolean firstPass = this.adjustFirstLine;
main_loop:
while (true) {
if (this.compositeElements.isEmpty()) {
return NO_MORE_TEXT;
}
final Element element = (Element)this.compositeElements.getFirst();
if (element.type() == Element.PARAGRAPH) {
final Paragraph para = (Paragraph)element;
int status = 0;
for (int keep = 0; keep < 2; ++keep) {
final float lastY = this.yLine;
boolean createHere = false;
if (this.compositeColumn == null) {
this.compositeColumn = new ColumnText(this.canvas);
this.compositeColumn.setUseAscender(firstPass ? this.useAscender : false);
this.compositeColumn.setAlignment(para.getAlignment());
this.compositeColumn.setIndent(para.getIndentationLeft() + para.getFirstLineIndent());
this.compositeColumn.setExtraParagraphSpace(para.getExtraParagraphSpace());
this.compositeColumn.setFollowingIndent(para.getIndentationLeft());
this.compositeColumn.setRightIndent(para.getIndentationRight());
this.compositeColumn.setLeading(para.getLeading(), para.getMultipliedLeading());
this.compositeColumn.setRunDirection(this.runDirection);
this.compositeColumn.setArabicOptions(this.arabicOptions);
this.compositeColumn.setSpaceCharRatio(this.spaceCharRatio);
this.compositeColumn.addText(para);
if (!firstPass) {
this.yLine -= para.getSpacingBefore();
}
createHere = true;
}
this.compositeColumn.leftX = this.leftX;
this.compositeColumn.rightX = this.rightX;
this.compositeColumn.yLine = this.yLine;
this.compositeColumn.rectangularWidth = this.rectangularWidth;
this.compositeColumn.rectangularMode = this.rectangularMode;
this.compositeColumn.minY = this.minY;
this.compositeColumn.maxY = this.maxY;
final boolean keepCandidate = para.getKeepTogether() && createHere && !firstPass;
status = this.compositeColumn.go(simulate || keepCandidate && keep == 0);
updateFilledWidth(this.compositeColumn.filledWidth);
if ((status & NO_MORE_TEXT) == 0 && keepCandidate) {
this.compositeColumn = null;
this.yLine = lastY;
return NO_MORE_COLUMN;
}
if (simulate || !keepCandidate) {
break;
}
if (keep == 0) {
this.compositeColumn = null;
this.yLine = lastY;
}
}
firstPass = false;
this.yLine = this.compositeColumn.yLine;
this.linesWritten += this.compositeColumn.linesWritten;
this.descender = this.compositeColumn.descender;
if ((status & NO_MORE_TEXT) != 0) {
this.compositeColumn = null;
this.compositeElements.removeFirst();
this.yLine -= para.getSpacingAfter();
}
if ((status & NO_MORE_COLUMN) != 0) {
return NO_MORE_COLUMN;
}
}
else if (element.type() == Element.LIST) {
com.aowagie.text.List list = (com.aowagie.text.List)element;
ArrayList items = list.getItems();
ListItem item = null;
float listIndentation = list.getIndentationLeft();
int count = 0;
final Stack stack = new Stack();
for (int k = 0; k < items.size(); ++k) {
final Object obj = items.get(k);
if (obj instanceof ListItem) {
if (count == this.listIdx) {
item = (ListItem)obj;
break;
} else {
++count;
}
}
else if (obj instanceof com.aowagie.text.List) {
stack.push(new Object[]{list, Integer.valueOf(k), Float.valueOf(listIndentation)});
list = (com.aowagie.text.List)obj;
items = list.getItems();
listIndentation += list.getIndentationLeft();
k = -1;
continue;
}
if (k == items.size() - 1) {
if (!stack.isEmpty()) {
final Object objs[] = (Object[])stack.pop();
list = (com.aowagie.text.List)objs[0];
items = list.getItems();
k = ((Integer)objs[1]).intValue();
listIndentation = ((Float)objs[2]).floatValue();
}
}
}
int status = 0;
for (int keep = 0; keep < 2; ++keep) {
final float lastY = this.yLine;
boolean createHere = false;
if (this.compositeColumn == null) {
if (item == null) {
this.listIdx = 0;
this.compositeElements.removeFirst();
continue main_loop;
}
this.compositeColumn = new ColumnText(this.canvas);
this.compositeColumn.setUseAscender(firstPass ? this.useAscender : false);
this.compositeColumn.setAlignment(item.getAlignment());
this.compositeColumn.setIndent(item.getIndentationLeft() + listIndentation + item.getFirstLineIndent());
this.compositeColumn.setExtraParagraphSpace(item.getExtraParagraphSpace());
this.compositeColumn.setFollowingIndent(this.compositeColumn.getIndent());
this.compositeColumn.setRightIndent(item.getIndentationRight() + list.getIndentationRight());
this.compositeColumn.setLeading(item.getLeading(), item.getMultipliedLeading());
this.compositeColumn.setRunDirection(this.runDirection);
this.compositeColumn.setArabicOptions(this.arabicOptions);
this.compositeColumn.setSpaceCharRatio(this.spaceCharRatio);
this.compositeColumn.addText(item);
if (!firstPass) {
this.yLine -= item.getSpacingBefore();
}
createHere = true;
}
this.compositeColumn.leftX = this.leftX;
this.compositeColumn.rightX = this.rightX;
this.compositeColumn.yLine = this.yLine;
this.compositeColumn.rectangularWidth = this.rectangularWidth;
this.compositeColumn.rectangularMode = this.rectangularMode;
this.compositeColumn.minY = this.minY;
this.compositeColumn.maxY = this.maxY;
final boolean keepCandidate = item.getKeepTogether() && createHere && !firstPass;
status = this.compositeColumn.go(simulate || keepCandidate && keep == 0);
updateFilledWidth(this.compositeColumn.filledWidth);
if ((status & NO_MORE_TEXT) == 0 && keepCandidate) {
this.compositeColumn = null;
this.yLine = lastY;
return NO_MORE_COLUMN;
}
if (simulate || !keepCandidate) {
break;
}
if (keep == 0) {
this.compositeColumn = null;
this.yLine = lastY;
}
}
firstPass = false;
this.yLine = this.compositeColumn.yLine;
this.linesWritten += this.compositeColumn.linesWritten;
this.descender = this.compositeColumn.descender;
if (!Float.isNaN(this.compositeColumn.firstLineY) && !this.compositeColumn.firstLineYDone) {
if (!simulate) {
showTextAligned(this.canvas, Element.ALIGN_LEFT, new Phrase(item.getListSymbol()), this.compositeColumn.leftX + listIndentation, this.compositeColumn.firstLineY, 0);
}
this.compositeColumn.firstLineYDone = true;
}
if ((status & NO_MORE_TEXT) != 0) {
this.compositeColumn = null;
++this.listIdx;
this.yLine -= item.getSpacingAfter();
}
if ((status & NO_MORE_COLUMN) != 0) {
return NO_MORE_COLUMN;
}
}
else if (element.type() == Element.PTABLE) {
// don't write anything in the current column if there's no more space available
if (this.yLine < this.minY || this.yLine > this.maxY) {
return NO_MORE_COLUMN;
}
// get the PdfPTable element
PdfPTable table = (PdfPTable)element;
// we ignore tables without a body
if (table.size() <= table.getHeaderRows()) {
this.compositeElements.removeFirst();
continue;
}
// offsets
float yTemp = this.yLine;
if (!firstPass && this.listIdx == 0) {
yTemp -= table.spacingBefore();
}
final float yLineWrite = yTemp;
// don't write anything in the current column if there's no more space available
if (yTemp < this.minY || yTemp > this.maxY) {
return NO_MORE_COLUMN;
}
// coordinates
this.currentLeading = 0;
float x1 = this.leftX;
float tableWidth;
if (table.isLockedWidth()) {
tableWidth = table.getTotalWidth();
updateFilledWidth(tableWidth);
}
else {
tableWidth = this.rectangularWidth * table.getWidthPercentage() / 100f;
table.setTotalWidth(tableWidth);
}
// how many header rows are real header rows; how many are footer rows?
final int headerRows = table.getHeaderRows();
int footerRows = table.getFooterRows();
if (footerRows > headerRows) {
footerRows = headerRows;
}
final int realHeaderRows = headerRows - footerRows;
final float headerHeight = table.getHeaderHeight();
final float footerHeight = table.getFooterHeight();
// make sure the header and footer fit on the page
final boolean skipHeader = !firstPass && table.isSkipFirstHeader() && this.listIdx <= headerRows;
if (!skipHeader) {
yTemp -= headerHeight;
if (yTemp < this.minY || yTemp > this.maxY) {
if (firstPass) {
this.compositeElements.removeFirst();
continue;
}
return NO_MORE_COLUMN;
}
}
// how many real rows (not header or footer rows) fit on a page?
int k;
if (this.listIdx < headerRows) {
this.listIdx = headerRows;
}
if (!table.isComplete()) {
yTemp -= footerHeight;
}
for (k = this.listIdx; k < table.size(); ++k) {
final float rowHeight = table.getRowHeight(k);
if (yTemp - rowHeight < this.minY) {
break;
}
yTemp -= rowHeight;
}
if (!table.isComplete()) {
yTemp += footerHeight;
}
// either k is the first row that doesn't fit on the page (break);
if (k < table.size()) {
if (table.isSplitRows() && (!table.isSplitLate() || k == this.listIdx && firstPass)) {
if (!this.splittedRow) {
this.splittedRow = true;
table = new PdfPTable(table);
this.compositeElements.set(0, table);
final ArrayList rows = table.getRows();
for (int i = headerRows; i < this.listIdx; ++i) {
rows.set(i, null);
}
}
final float h = yTemp - this.minY;
final PdfPRow newRow = table.getRow(k).splitRow(table, k, h);
if (newRow == null) {
if (k == this.listIdx) {
return NO_MORE_COLUMN;
}
}
else {
yTemp = this.minY;
table.getRows().add(++k, newRow);
}
}
else if (!table.isSplitRows() && k == this.listIdx && firstPass) {
this.compositeElements.removeFirst();
this.splittedRow = false;
continue;
}
else if (k == this.listIdx && !firstPass && (!table.isSplitRows() || table.isSplitLate()) && (table.getFooterRows() == 0 || table.isComplete())) {
return NO_MORE_COLUMN;
}
}
// or k is the number of rows in the table (for loop was done).
firstPass = false;
// we draw the table (for real now)
if (!simulate) {
// set the alignment
switch (table.getHorizontalAlignment()) {
case Element.ALIGN_LEFT:
break;
case Element.ALIGN_RIGHT:
x1 += this.rectangularWidth - tableWidth;
break;
default:
x1 += (this.rectangularWidth - tableWidth) / 2f;
}
// copy the rows that fit on the page in a new table nt
final PdfPTable nt = PdfPTable.shallowCopy(table);
final ArrayList sub = nt.getRows();
// first we add the real header rows (if necessary)
if (!skipHeader) {
for (int j = 0; j < realHeaderRows; ++j) {
final PdfPRow headerRow = table.getRow(j);
sub.add(headerRow);
}
} else {
nt.setHeaderRows(footerRows);
}
// then we add the real content
sub.addAll(table.getRows(this.listIdx, k));
// if k < table.size(), we must indicate that the new table is complete;
// otherwise no footers will be added (because iText thinks the table continues on the same page)
boolean showFooter = !table.isSkipLastFooter();
if (k < table.size()) {
nt.setComplete(true);
showFooter = true;
}
// we add the footer rows if necessary (not for incomplete tables)
for (int j = 0; j < footerRows && nt.isComplete() && showFooter; ++j) {
sub.add(table.getRow(j + realHeaderRows));
}
// we need a correction if the last row needs to be extended
float rowHeight = 0;
final PdfPRow last = (PdfPRow)sub.get(sub.size() - 1 - footerRows);
if (table.isExtendLastRow()) {
rowHeight = last.getMaxHeights();
last.setMaxHeights(yTemp - this.minY + rowHeight);
yTemp = this.minY;
}
// now we render the rows of the new table
if (this.canvases != null) {
nt.writeSelectedRows(0, -1, x1, yLineWrite, this.canvases);
} else {
nt.writeSelectedRows(0, -1, x1, yLineWrite, this.canvas);
}
if (table.isExtendLastRow()) {
last.setMaxHeights(rowHeight);
}
}
else if (table.isExtendLastRow() && this.minY > PdfPRow.BOTTOM_LIMIT) {
yTemp = this.minY;
}
this.yLine = yTemp;
if (!(skipHeader || table.isComplete())) {
this.yLine += footerHeight;
}
if (k >= table.size()) {
this.yLine -= table.spacingAfter();
this.compositeElements.removeFirst();
this.splittedRow = false;
this.listIdx = 0;
}
else {
if (this.splittedRow) {
final ArrayList rows = table.getRows();
for (int i = this.listIdx; i < k; ++i) {
rows.set(i, null);
}
}
this.listIdx = k;
return NO_MORE_COLUMN;
}
}
else if (element.type() == Element.YMARK) {
if (!simulate) {
final DrawInterface zh = (DrawInterface)element;
zh.draw(this.canvas, this.leftX, this.minY, this.rightX, this.maxY, this.yLine);
}
this.compositeElements.removeFirst();
} else {
this.compositeElements.removeFirst();
}
}
}
/**
* Gets the canvas.
* If a set of four canvases exists, the TEXTCANVAS is returned.
*
* @return a PdfContentByte.
*/
public PdfContentByte getCanvas() {
return this.canvas;
}
/**
* Sets the canvas.
* If before a set of four canvases was set, it is being unset.
*
* @param canvas canvas to set
*/
public void setCanvas(final PdfContentByte canvas) {
this.canvas = canvas;
this.canvases = null;
if (this.compositeColumn != null) {
this.compositeColumn.setCanvas(canvas);
}
}
/**
* Sets the canvases.
*
* @param canvases canvases to set
*/
public void setCanvases(final PdfContentByte[] canvases) {
this.canvases = canvases;
this.canvas = canvases[PdfPTable.TEXTCANVAS];
if (this.compositeColumn != null) {
this.compositeColumn.setCanvases(canvases);
}
}
/**
* Gets the canvases.
*
* @return an array of PdfContentByte
*/
public PdfContentByte[] getCanvases() {
return this.canvases;
}
/**
* Checks if the element has a height of 0.
*
* @return true or false
* @since 2.1.2
*/
public boolean zeroHeightElement() {
return this.composite && !this.compositeElements.isEmpty() && ((Element)this.compositeElements.getFirst()).type() == Element.YMARK;
}
/**
* Checks if UseAscender is enabled/disabled.
*
* @return true is the adjustment of the first line height is based on max ascender.
*/
public boolean isUseAscender() {
return this.useAscender;
}
/**
* Enables/Disables adjustment of first line height based on max ascender.
*
* @param useAscender enable adjustment if true
*/
public void setUseAscender(final boolean useAscender) {
this.useAscender = useAscender;
}
/**
* Checks the status variable and looks if there's still some text.
* @param status Index with status
* @return returns true if it has more text
*/
public static boolean hasMoreText(final int status) {
return (status & ColumnText.NO_MORE_TEXT) == 0;
}
/**
* Gets the real width used by the largest line.
*
* @return the real width used by the largest line
*/
public float getFilledWidth() {
return this.filledWidth;
}
/**
* Sets the real width used by the largest line.
* Only used to set it to zero to start another measurement.
*
* @param filledWidth the real width used by the largest line
*/
public void setFilledWidth(final float filledWidth) {
this.filledWidth = filledWidth;
}
/**
* Replaces the filledWidth
if greater than the existing one.
*
* @param w the new filledWidth
if greater than the existing one
*/
public void updateFilledWidth(final float w) {
if (w > this.filledWidth) {
this.filledWidth = w;
}
}
/**
* Gets the first line adjustment property.
*
* @return the first line adjustment property.
*/
public boolean isAdjustFirstLine() {
return this.adjustFirstLine;
}
/**
* Sets the first line adjustment.
* Some objects have properties, like spacing before, that behave
* differently if the object is the first to be written after go() or not.
* The first line adjustment is true
by default but can be
* changed if several objects are to be placed one after the other in the
* same column calling go() several times.
*
* @param adjustFirstLine true
to adjust the first line, false
otherwise
*/
public void setAdjustFirstLine(final boolean adjustFirstLine) {
this.adjustFirstLine = adjustFirstLine;
}
}