2023-06-21 12:46:23 -04:00

1534 lines
56 KiB
C#

using System;
using System.Collections;
using System.util.collections;
using iTextSharp.text.pdf.draw;
/*
* Copyright 2001-2005 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/
*/
namespace iTextSharp.text.pdf {
/**
* 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.
* <P>
* Several parameters can be set like the first paragraph line indent and
* extra space between paragraphs.
* <P>
* A call to the method <CODE>go</CODE> will return one of the following
* situations: the column ended or the text ended.
* <P>
* I the column ended, a new column definition can be loaded with the method
* <CODE>setColumns</CODE> and the method <CODE>go</CODE> can be called again.
* <P>
* If the text ended, more text can be loaded with <CODE>addText</CODE>
* and the method <CODE>go</CODE> can be called again.<BR>
* The only limitation is that one or more complete paragraphs must be loaded
* each time.
* <P>
* Full bidirectional reordering is supported. If the run direction is
* <CODE>PdfWriter.RUN_DIRECTION_RTL</CODE> the meaning of the horizontal
* alignments and margins is mirrored.
* @author Paulo Soares (psoares@consiste.pt)
*/
public class ColumnText {
/** Eliminate the arabic vowels */
public int AR_NOVOWEL = ArabicLigaturizer.ar_novowel;
/** Compose the tashkeel in the ligatures. */
public const int AR_COMPOSEDTASHKEEL = ArabicLigaturizer.ar_composedtashkeel;
/** Do some extra double ligatures. */
public const int AR_LIG = ArabicLigaturizer.ar_lig;
/**
* Digit shaping option: Replace European digits (U+0030...U+0039) by Arabic-Indic digits.
*/
public const int DIGITS_EN2AN = ArabicLigaturizer.DIGITS_EN2AN;
/**
* Digit shaping option: Replace Arabic-Indic digits by European digits (U+0030...U+0039).
*/
public const 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 const 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 const int DIGITS_EN2AN_INIT_AL = ArabicLigaturizer.DIGITS_EN2AN_INIT_AL;
/**
* Digit type option: Use Arabic-Indic digits (U+0660...U+0669).
*/
public const int DIGIT_TYPE_AN = ArabicLigaturizer.DIGIT_TYPE_AN;
/**
* Digit type option: Use Eastern (Extended) Arabic-Indic digits (U+06f0...U+06f9).
*/
public const int DIGIT_TYPE_AN_EXTENDED = ArabicLigaturizer.DIGIT_TYPE_AN_EXTENDED;
protected int runDirection = PdfWriter.RUN_DIRECTION_DEFAULT;
public static float GLOBAL_SPACE_CHAR_RATIO = 0;
/** Signals that there is no more text available. */
public const int NO_MORE_TEXT = 1;
/** Signals that there is no more column. */
public const int NO_MORE_COLUMN = 2;
/** The column is valid. */
protected const int LINE_STATUS_OK = 0;
/** The line is out the column limits. */
protected const int LINE_STATUS_OFFLIMITS = 1;
/** The line cannot fit this column position. */
protected const 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 Element. Default is left Element. */
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 <CODE>PdfContent</CODE> 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 bool rectangularMode = false;
/** Holds value of property spaceCharRatio. */
private float spaceCharRatio = GLOBAL_SPACE_CHAR_RATIO;
private bool lastWasNewline = true;
/** Holds value of property linesWritten. */
private int linesWritten;
private float firstLineY;
private bool firstLineYDone = false;
/** Holds value of property arabicOptions. */
private int arabicOptions = 0;
protected float descender;
protected bool composite = false;
protected ColumnText compositeColumn;
protected internal ArrayList compositeElements;
protected int listIdx = 0;
private bool splittedRow;
protected Phrase waitPhrase;
/** if true, first line height is adjusted so that the max ascender touches the top */
private bool useAscender = false;
/**
* Creates a <CODE>ColumnText</CODE>.
* @param text the place where the text will be written to. Can
* be a template.
*/
public ColumnText(PdfContentByte canvas) {
this.canvas = canvas;
}
/** Creates an independent duplicated of the instance <CODE>org</CODE>.
* @param org the original <CODE>ColumnText</CODE>
* @return the duplicated
*/
public static ColumnText Duplicate(ColumnText org) {
ColumnText ct = new ColumnText(null);
ct.SetACopy(org);
return ct;
}
/** Makes this instance an independent copy of <CODE>org</CODE>.
* @param org the original <CODE>ColumnText</CODE>
* @return itself
*/
public ColumnText SetACopy(ColumnText org) {
SetSimpleVars(org);
if (org.bidiLine != null)
bidiLine = new BidiLine(org.bidiLine);
return this;
}
protected internal void SetSimpleVars(ColumnText org) {
maxY = org.maxY;
minY = org.minY;
alignment = org.alignment;
leftWall = null;
if (org.leftWall != null)
leftWall = new ArrayList(org.leftWall);
rightWall = null;
if (org.rightWall != null)
rightWall = new ArrayList(org.rightWall);
yLine = org.yLine;
currentLeading = org.currentLeading;
fixedLeading = org.fixedLeading;
multipliedLeading = org.multipliedLeading;
canvas = org.canvas;
canvases = org.canvases;
lineStatus = org.lineStatus;
indent = org.indent;
followingIndent = org.followingIndent;
rightIndent = org.rightIndent;
extraParagraphSpace = org.extraParagraphSpace;
rectangularWidth = org.rectangularWidth;
rectangularMode = org.rectangularMode;
spaceCharRatio = org.spaceCharRatio;
lastWasNewline = org.lastWasNewline;
linesWritten = org.linesWritten;
arabicOptions = org.arabicOptions;
runDirection = org.runDirection;
descender = org.descender;
composite = org.composite;
splittedRow = org.splittedRow;
if (org.composite) {
compositeElements = new ArrayList(org.compositeElements);
if (splittedRow) {
PdfPTable table = (PdfPTable)compositeElements[0];
compositeElements[0] = new PdfPTable(table);
}
if (org.compositeColumn != null)
compositeColumn = Duplicate(org.compositeColumn);
}
listIdx = org.listIdx;
firstLineY = org.firstLineY;
leftX = org.leftX;
rightX = org.rightX;
firstLineYDone = org.firstLineYDone;
waitPhrase = org.waitPhrase;
useAscender = org.useAscender;
filledWidth = org.filledWidth;
adjustFirstLine = org.adjustFirstLine;
}
private void AddWaitingPhrase() {
if (bidiLine == null && waitPhrase != null) {
bidiLine = new BidiLine();
foreach (Chunk ck in waitPhrase.Chunks) {
bidiLine.AddChunk(new PdfChunk(ck, null));
}
waitPhrase = null;
}
}
/**
* Adds a <CODE>Phrase</CODE> to the current text array.
* @param phrase the text
*/
public void AddText(Phrase phrase) {
if (phrase == null || composite)
return;
AddWaitingPhrase();
if (bidiLine == null) {
waitPhrase = phrase;
return;
}
foreach (Chunk c in phrase.Chunks) {
bidiLine.AddChunk(new PdfChunk(c, null));
}
}
/**
* Replaces the current text array with this <CODE>Phrase</CODE>.
* Anything added previously with AddElement() is lost.
* @param phrase the text
*/
public void SetText(Phrase phrase) {
bidiLine = null;
composite = false;
compositeColumn = null;
compositeElements = null;
listIdx = 0;
splittedRow = false;
waitPhrase = phrase;
}
/**
* Adds a <CODE>Chunk</CODE> to the current text array.
* Will not have any effect if AddElement() was called before.
* @param chunk the text
*/
public void AddText(Chunk chunk) {
if (chunk == null || composite)
return;
AddText(new Phrase(chunk));
}
/**
* Adds an element. Elements supported are <CODE>Paragraph</CODE>,
* <CODE>List</CODE>, <CODE>PdfPTable</CODE>, <CODE>Image</CODE> and
* <CODE>Graphic</CODE>.
* <p>
* It removes all the text placed with <CODE>addText()</CODE>.
* @param element the <CODE>Element</CODE>
*/
public void AddElement(IElement element) {
if (element == null)
return;
if (element is Image) {
Image img = (Image)element;
PdfPTable t = new PdfPTable(1);
float w = img.WidthPercentage;
if (w == 0) {
t.TotalWidth = img.ScaledWidth;
t.LockedWidth = true;
}
else
t.WidthPercentage = w;
t.SpacingAfter = img.SpacingAfter;
t.SpacingBefore = img.SpacingBefore;
switch (img.Alignment) {
case Image.LEFT_ALIGN:
t.HorizontalAlignment = Element.ALIGN_LEFT;
break;
case Image.RIGHT_ALIGN:
t.HorizontalAlignment = Element.ALIGN_RIGHT;
break;
default:
t.HorizontalAlignment = Element.ALIGN_CENTER;
break;
}
PdfPCell c = new PdfPCell(img, true);
c.Padding = 0;
c.Border = img.Border;
c.BorderColor = img.BorderColor;
c.BorderWidth = img.BorderWidth;
c.BackgroundColor = img.BackgroundColor;
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 is SimpleTable) {
try {
element = ((SimpleTable)element).CreatePdfPTable();
} catch (DocumentException) {
throw new ArgumentException("Element not allowed.");
}
}
else if (element.Type != Element.PARAGRAPH && element.Type != Element.LIST && element.Type != Element.PTABLE && element.Type != Element.YMARK)
throw new ArgumentException("Element not allowed.");
if (!composite) {
composite = true;
compositeElements = new ArrayList();
bidiLine = null;
waitPhrase = null;
}
compositeElements.Add(element);
}
/**
* Converts a sequence of lines representing one of the column bounds into
* an internal format.
* <p>
* Each array element will contain a <CODE>float[4]</CODE> representing
* the line x = ax + b.
* @param cLine the column array
* @return the converted array
*/
protected ArrayList ConvertColumn(float[] cLine) {
if (cLine.Length < 4)
throw new Exception("No valid column line found.");
ArrayList cc = new ArrayList();
for (int k = 0; k < cLine.Length - 2; k += 2) {
float x1 = cLine[k];
float y1 = cLine[k + 1];
float x2 = cLine[k + 2];
float y2 = cLine[k + 3];
if (y1 == y2)
continue;
// x = ay + b
float a = (x1 - x2) / (y1 - y2);
float b = x1 - a * y1;
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);
maxY = Math.Max(maxY, r[1]);
minY = Math.Min(minY, r[0]);
}
if (cc.Count == 0)
throw new Exception("No valid column line found.");
return cc;
}
/**
* Finds the intersection between the <CODE>yLine</CODE> and the column. It will
* set the <CODE>lineStatus</CODE> apropriatly.
* @param wall the column to intersect
* @return the x coordinate of the intersection
*/
protected float FindLimitsPoint(ArrayList wall) {
lineStatus = LINE_STATUS_OK;
if (yLine < minY || yLine > maxY) {
lineStatus = LINE_STATUS_OFFLIMITS;
return 0;
}
for (int k = 0; k < wall.Count; ++k) {
float[] r = (float[])wall[k];
if (yLine < r[0] || yLine > r[1])
continue;
return r[2] * yLine + r[3];
}
lineStatus = LINE_STATUS_NOLINE;
return 0;
}
/**
* Finds the intersection between the <CODE>yLine</CODE> and the two
* column bounds. It will set the <CODE>lineStatus</CODE> apropriatly.
* @return a <CODE>float[2]</CODE>with the x coordinates of the intersection
*/
protected float[] FindLimitsOneLine() {
float x1 = FindLimitsPoint(leftWall);
if (lineStatus == LINE_STATUS_OFFLIMITS || lineStatus == LINE_STATUS_NOLINE)
return null;
float x2 = FindLimitsPoint(rightWall);
if (lineStatus == LINE_STATUS_NOLINE)
return null;
return new float[]{x1, x2};
}
/**
* Finds the intersection between the <CODE>yLine</CODE>,
* the <CODE>yLine-leading</CODE>and the two
* column bounds. It will set the <CODE>lineStatus</CODE> apropriatly.
* @return a <CODE>float[4]</CODE>with the x coordinates of the intersection
*/
protected float[] FindLimitsTwoLines() {
bool repeat = false;
for (;;) {
if (repeat && currentLeading == 0)
return null;
repeat = true;
float[] x1 = FindLimitsOneLine();
if (lineStatus == LINE_STATUS_OFFLIMITS)
return null;
yLine -= currentLeading;
if (lineStatus == LINE_STATUS_NOLINE) {
continue;
}
float[] x2 = FindLimitsOneLine();
if (lineStatus == LINE_STATUS_OFFLIMITS)
return null;
if (lineStatus == LINE_STATUS_NOLINE) {
yLine -= 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
* <CODE>float[]</CODE> 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(float[] leftLine, float[] rightLine) {
maxY = -10e20f;
minY = 10e20f;
rightWall = ConvertColumn(rightLine);
leftWall = ConvertColumn(leftLine);
rectangularWidth = -1;
rectangularMode = false;
}
/**
* Simplified method for rectangular columns.
* @param phrase a <CODE>Phrase</CODE>
* @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(Phrase phrase, float llx, float lly, float urx, float ury, float leading, 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(float llx, float lly, float urx, float ury, float leading, int alignment) {
Leading = leading;
this.alignment = alignment;
SetSimpleColumn(llx, lly, urx, ury);
}
/**
* Simplified method for rectangular columns.
* @param llx
* @param lly
* @param urx
* @param ury
*/
public void SetSimpleColumn(float llx, float lly, float urx, float ury) {
leftX = Math.Min(llx, urx);
maxY = Math.Max(lly, ury);
minY = Math.Min(lly, ury);
rightX = Math.Max(llx, urx);
yLine = maxY;
rectangularWidth = rightX - leftX;
if (rectangularWidth < 0)
rectangularWidth = 0;
rectangularMode = true;
}
/**
* Sets the leading fixed and variable. The resultant leading will be
* fixedLeading+multipliedLeading*maxFontSize where maxFontSize is the
* size of the bigest font in the line.
* @param fixedLeading the fixed leading
* @param multipliedLeading the variable leading
*/
public void SetLeading(float fixedLeading, float multipliedLeading) {
this.fixedLeading = fixedLeading;
this.multipliedLeading = multipliedLeading;
}
/**
* Gets the fixed leading
* @return the leading
*/
public float Leading {
get {
return fixedLeading;
}
set {
this.fixedLeading = value;
this.multipliedLeading = 0;
}
}
/**
* Gets the variable leading
* @return the leading
*/
public float MultipliedLeading {
get {
return multipliedLeading;
}
}
/**
* Gets the yLine.
* @return the yLine
*/
public float YLine {
get {
return yLine;
}
set {
this.yLine = value;
}
}
/**
* Gets the Element.
* @return the alignment
*/
public int Alignment{
get {
return alignment;
}
set {
this.alignment = value;
}
}
/**
* Gets the first paragraph line indent.
* @return the indent
*/
public float Indent {
get {
return indent;
}
set {
this.indent = value;
lastWasNewline = true;
}
}
/**
* Gets the following paragraph lines indent.
* @return the indent
*/
public float FollowingIndent {
get {
return followingIndent;
}
set {
this.followingIndent = value;
lastWasNewline = true;
}
}
/**
* Gets the right paragraph lines indent.
* @return the indent
*/
public float RightIndent {
get {
return rightIndent;
}
set {
this.rightIndent = value;
lastWasNewline = true;
}
}
/**
* Outputs the lines to the document. It is equivalent to <CODE>go(false)</CODE>.
* @return returns the result of the operation. It can be <CODE>NO_MORE_TEXT</CODE>
* and/or <CODE>NO_MORE_COLUMN</CODE>
* @throws DocumentException on error
*/
public int Go() {
return Go(false);
}
/**
* Outputs the lines to the document. The output can be simulated.
* @param simulate <CODE>true</CODE> to simulate the writting to the document
* @return returns the result of the operation. It can be <CODE>NO_MORE_TEXT</CODE>
* and/or <CODE>NO_MORE_COLUMN</CODE>
* @throws DocumentException on error
*/
public int Go(bool simulate) {
if (composite)
return GoComposite(simulate);
AddWaitingPhrase();
if (bidiLine == null)
return NO_MORE_TEXT;
descender = 0;
linesWritten = 0;
bool dirty = false;
float ratio = spaceCharRatio;
Object[] currentValues = new Object[2];
PdfFont currentFont = null;
float lastBaseFactor = 0F;
currentValues[1] = lastBaseFactor;
PdfDocument pdf = null;
PdfContentByte graphics = null;
PdfContentByte text = null;
firstLineY = float.NaN;
int localRunDirection = PdfWriter.RUN_DIRECTION_NO_BIDI;
if (runDirection != PdfWriter.RUN_DIRECTION_DEFAULT)
localRunDirection = runDirection;
if (canvas != null) {
graphics = canvas;
pdf = canvas.PdfDocument;
text = canvas.Duplicate;
}
else if (!simulate)
throw new Exception("ColumnText.go with simulate==false and text==null.");
if (!simulate) {
if (ratio == GLOBAL_SPACE_CHAR_RATIO)
ratio = text.PdfWriter.SpaceCharRatio;
else if (ratio < 0.001f)
ratio = 0.001f;
}
float firstIndent = 0;
int status = 0;
if (rectangularMode) {
for (;;) {
firstIndent = (lastWasNewline ? indent : followingIndent);
if (rectangularWidth <= firstIndent + rightIndent) {
status = NO_MORE_COLUMN;
if (bidiLine.IsEmpty())
status |= NO_MORE_TEXT;
break;
}
if (bidiLine.IsEmpty()) {
status = NO_MORE_TEXT;
break;
}
PdfLine line = bidiLine.ProcessLine(leftX, rectangularWidth - firstIndent - rightIndent, alignment, localRunDirection, arabicOptions);
if (line == null) {
status = NO_MORE_TEXT;
break;
}
float maxSize = line.MaxSizeSimple;
if (UseAscender && float.IsNaN(firstLineY)) {
currentLeading = line.Ascender;
}
else {
currentLeading = fixedLeading + maxSize * multipliedLeading;
}
if (yLine > maxY || yLine - currentLeading < minY ) {
status = NO_MORE_COLUMN;
bidiLine.Restore();
break;
}
yLine -= currentLeading;
if (!simulate && !dirty) {
text.BeginText();
dirty = true;
}
if (float.IsNaN(firstLineY)) {
firstLineY = yLine;
}
UpdateFilledWidth(rectangularWidth - line.WidthLeft);
if (!simulate) {
currentValues[0] = currentFont;
text.SetTextMatrix(leftX + (line.RTL ? rightIndent : firstIndent) + line.IndentLeft, yLine);
pdf.WriteLineToContent(line, text, graphics, currentValues, ratio);
currentFont = (PdfFont)currentValues[0];
}
lastWasNewline = line.NewlineSplit;
yLine -= line.NewlineSplit ? extraParagraphSpace : 0;
++linesWritten;
descender = line.Descender;
}
}
else {
currentLeading = fixedLeading;
for (;;) {
firstIndent = (lastWasNewline ? indent : followingIndent);
float yTemp = yLine;
float[] xx = FindLimitsTwoLines();
if (xx == null) {
status = NO_MORE_COLUMN;
if (bidiLine.IsEmpty())
status |= NO_MORE_TEXT;
yLine = yTemp;
break;
}
if (bidiLine.IsEmpty()) {
status = NO_MORE_TEXT;
yLine = yTemp;
break;
}
float x1 = Math.Max(xx[0], xx[2]);
float x2 = Math.Min(xx[1], xx[3]);
if (x2 - x1 <= firstIndent + rightIndent)
continue;
if (!simulate && !dirty) {
text.BeginText();
dirty = true;
}
PdfLine line = bidiLine.ProcessLine(x1, x2 - x1 - firstIndent - rightIndent, alignment, localRunDirection, arabicOptions);
if (line == null) {
status = NO_MORE_TEXT;
yLine = yTemp;
break;
}
if (!simulate) {
currentValues[0] = currentFont;
text.SetTextMatrix(x1 + (line.RTL ? rightIndent : firstIndent) + line.IndentLeft, yLine);
pdf.WriteLineToContent(line, text, graphics, currentValues, ratio);
currentFont = (PdfFont)currentValues[0];
}
lastWasNewline = line.NewlineSplit;
yLine -= line.NewlineSplit ? extraParagraphSpace : 0;
++linesWritten;
descender = line.Descender;
}
}
if (dirty) {
text.EndText();
canvas.Add(text);
}
return status;
}
/**
* Sets the extra space between paragraphs.
* @return the extra space between paragraphs
*/
public float ExtraParagraphSpace {
get {
return extraParagraphSpace;
}
set {
this.extraParagraphSpace = value;
}
}
/**
* Clears the chunk array. A call to <CODE>go()</CODE> will always return
* NO_MORE_TEXT.
*/
public void ClearChunks() {
if (bidiLine != null)
bidiLine.ClearChunks();
}
/** Gets the space/character extra spacing ratio for
* fully justified text.
* @return the space/character extra spacing ratio
*/
public float SpaceCharRatio {
get {
return spaceCharRatio;
}
set {
this.spaceCharRatio = value;
}
}
/** Gets the run direction.
* @return the run direction
*/
public int RunDirection {
get {
return runDirection;
}
set {
if (value < PdfWriter.RUN_DIRECTION_DEFAULT || value > PdfWriter.RUN_DIRECTION_RTL)
throw new Exception("Invalid run direction: " + value);
this.runDirection = value;
}
}
/** Gets the number of lines written.
* @return the number of lines written
*/
public int LinesWritten {
get {
return this.linesWritten;
}
}
/** Sets the arabic shaping options. The option can be AR_NOVOWEL,
* AR_COMPOSEDTASHKEEL and AR_LIG.
* @param arabicOptions the arabic shaping options
*/
public int ArabicOptions {
set {
this.arabicOptions = value;
}
get {
return arabicOptions;
}
}
/** Gets the biggest descender value of the last line written.
* @return the biggest descender value of the last line written
*/
public float Descender {
get {
return descender;
}
}
/** Gets the width that the line will occupy after writing.
* Only the width of the first line is returned.
* @param phrase the <CODE>Phrase</CODE> 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(Phrase phrase, int runDirection, int arabicOptions) {
ColumnText ct = new ColumnText(null);
ct.AddText(phrase);
ct.AddWaitingPhrase();
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 <CODE>Phrase</CODE> containing the line
* @return the width of the line
*/
public static float GetWidth(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 <CODE>Phrase</CODE> 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(PdfContentByte canvas, int alignment, Phrase phrase, float x, float y, float rotation, int runDirection, int arabicOptions) {
if (alignment != Element.ALIGN_LEFT && alignment != Element.ALIGN_CENTER
&& alignment != Element.ALIGN_RIGHT)
alignment = Element.ALIGN_LEFT;
canvas.SaveState();
ColumnText ct = new ColumnText(canvas);
if (rotation == 0) {
if (alignment == Element.ALIGN_LEFT)
ct.SetSimpleColumn(phrase, x, y - 1, 20000 + x, y + 2, 2, alignment);
else if (alignment == Element.ALIGN_RIGHT)
ct.SetSimpleColumn(phrase, x-20000, y-1, x, y+2, 2, alignment);
else
ct.SetSimpleColumn(phrase, x-20000, y-1, x+20000, y+2, 2, alignment);
}
else {
double alpha = rotation * Math.PI / 180.0;
float cos = (float)Math.Cos(alpha);
float sin = (float)Math.Sin(alpha);
canvas.ConcatCTM(cos, sin, -sin, cos, x, y);
if (alignment == Element.ALIGN_LEFT)
ct.SetSimpleColumn(phrase, 0, -1, 20000, 2, 2, alignment);
else if (alignment == Element.ALIGN_RIGHT)
ct.SetSimpleColumn(phrase, -20000, -1, 0, 2, 2, alignment);
else
ct.SetSimpleColumn(phrase, -20000, -1, 20000, 2, 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.Alignment = alignment;
ct.ArabicOptions = arabicOptions;
ct.RunDirection = runDirection;
ct.Go();
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 <CODE>Phrase</CODE> 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(PdfContentByte canvas, int alignment, Phrase phrase, float x, float y, float rotation) {
ShowTextAligned(canvas, alignment, phrase, x, y, rotation, PdfWriter.RUN_DIRECTION_NO_BIDI, 0);
}
protected int GoComposite(bool simulate) {
if (!rectangularMode)
throw new DocumentException("Irregular columns are not supported in composite mode.");
linesWritten = 0;
descender = 0;
bool firstPass = adjustFirstLine;
main_loop:
while (true) {
if (compositeElements.Count == 0)
return NO_MORE_TEXT;
IElement element = (IElement)compositeElements[0];
if (element.Type == Element.PARAGRAPH) {
Paragraph para = (Paragraph)element;
int status = 0;
for (int keep = 0; keep < 2; ++keep) {
float lastY = yLine;
bool createHere = false;
if (compositeColumn == null) {
compositeColumn = new ColumnText(canvas);
compositeColumn.UseAscender = (firstPass ? useAscender : false);
compositeColumn.Alignment = para.Alignment;
compositeColumn.Indent = para.IndentationLeft + para.FirstLineIndent;
compositeColumn.ExtraParagraphSpace = para.ExtraParagraphSpace;
compositeColumn.FollowingIndent = para.IndentationLeft;
compositeColumn.RightIndent = para.IndentationRight;
compositeColumn.SetLeading(para.Leading, para.MultipliedLeading);
compositeColumn.RunDirection = runDirection;
compositeColumn.ArabicOptions = arabicOptions;
compositeColumn.SpaceCharRatio = spaceCharRatio;
compositeColumn.AddText(para);
if (!firstPass) {
yLine -= para.SpacingBefore;
}
createHere = true;
}
compositeColumn.leftX = leftX;
compositeColumn.rightX = rightX;
compositeColumn.yLine = yLine;
compositeColumn.rectangularWidth = rectangularWidth;
compositeColumn.rectangularMode = rectangularMode;
compositeColumn.minY = minY;
compositeColumn.maxY = maxY;
bool keepCandidate = (para.KeepTogether && createHere && !firstPass);
status = compositeColumn.Go(simulate || (keepCandidate && keep == 0));
UpdateFilledWidth(compositeColumn.filledWidth);
if ((status & NO_MORE_TEXT) == 0 && keepCandidate) {
compositeColumn = null;
yLine = lastY;
return NO_MORE_COLUMN;
}
if (simulate || !keepCandidate)
break;
if (keep == 0) {
compositeColumn = null;
yLine = lastY;
}
}
firstPass = false;
yLine = compositeColumn.yLine;
linesWritten += compositeColumn.linesWritten;
descender = compositeColumn.descender;
if ((status & NO_MORE_TEXT) != 0) {
compositeColumn = null;
compositeElements.RemoveAt(0);
yLine -= para.SpacingAfter;
}
if ((status & NO_MORE_COLUMN) != 0) {
return NO_MORE_COLUMN;
}
}
else if (element.Type == Element.LIST) {
List list = (List)element;
ArrayList items = list.Items;
ListItem item = null;
float listIndentation = list.IndentationLeft;
int count = 0;
Stack stack = new Stack();
for (int k = 0; k < items.Count; ++k) {
Object obj = items[k];
if (obj is ListItem) {
if (count == listIdx) {
item = (ListItem)obj;
break;
}
else ++count;
}
else if (obj is List) {
stack.Push(new Object[]{list, k, listIndentation});
list = (List)obj;
items = list.Items;
listIndentation += list.IndentationLeft;
k = -1;
continue;
}
if (k == items.Count - 1) {
if (stack.Count > 0) {
Object[] objs = (Object[])stack.Pop();
list = (List)objs[0];
items = list.Items;
k = (int)objs[1];
listIndentation = (float)objs[2];
}
}
}
int status = 0;
for (int keep = 0; keep < 2; ++keep) {
float lastY = yLine;
bool createHere = false;
if (compositeColumn == null) {
if (item == null) {
listIdx = 0;
compositeElements.RemoveAt(0);
goto main_loop;
}
compositeColumn = new ColumnText(canvas);
compositeColumn.UseAscender = (firstPass ? useAscender : false);
compositeColumn.Alignment = item.Alignment;
compositeColumn.Indent = item.IndentationLeft + listIndentation + item.FirstLineIndent;
compositeColumn.ExtraParagraphSpace = item.ExtraParagraphSpace;
compositeColumn.FollowingIndent = compositeColumn.Indent;
compositeColumn.RightIndent = item.IndentationRight + list.IndentationRight;
compositeColumn.SetLeading(item.Leading, item.MultipliedLeading);
compositeColumn.RunDirection = runDirection;
compositeColumn.ArabicOptions = arabicOptions;
compositeColumn.SpaceCharRatio = spaceCharRatio;
compositeColumn.AddText(item);
if (!firstPass) {
yLine -= item.SpacingBefore;
}
createHere = true;
}
compositeColumn.leftX = leftX;
compositeColumn.rightX = rightX;
compositeColumn.yLine = yLine;
compositeColumn.rectangularWidth = rectangularWidth;
compositeColumn.rectangularMode = rectangularMode;
compositeColumn.minY = minY;
compositeColumn.maxY = maxY;
bool keepCandidate = (item.KeepTogether && createHere && !firstPass);
status = compositeColumn.Go(simulate || (keepCandidate && keep == 0));
UpdateFilledWidth(compositeColumn.filledWidth);
if ((status & NO_MORE_TEXT) == 0 && keepCandidate) {
compositeColumn = null;
yLine = lastY;
return NO_MORE_COLUMN;
}
if (simulate || !keepCandidate)
break;
if (keep == 0) {
compositeColumn = null;
yLine = lastY;
}
}
firstPass = false;
yLine = compositeColumn.yLine;
linesWritten += compositeColumn.linesWritten;
descender = compositeColumn.descender;
if (!float.IsNaN(compositeColumn.firstLineY) && !compositeColumn.firstLineYDone) {
if (!simulate)
ShowTextAligned(canvas, Element.ALIGN_LEFT, new Phrase(item.ListSymbol), compositeColumn.leftX + listIndentation, compositeColumn.firstLineY, 0);
compositeColumn.firstLineYDone = true;
}
if ((status & NO_MORE_TEXT) != 0) {
compositeColumn = null;
++listIdx;
yLine -= item.SpacingAfter;
}
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 (yLine < minY || yLine > maxY)
return NO_MORE_COLUMN;
// get the PdfPTable element
PdfPTable table = (PdfPTable)element;
// we ignore tables without a body
if (table.Size <= table.HeaderRows) {
compositeElements.RemoveAt(0);
continue;
}
// offsets
float yTemp = yLine;
if (!firstPass && listIdx == 0) {
yTemp -= table.SpacingBefore;
}
float yLineWrite = yTemp;
// don't write anything in the current column if there's no more space available
if (yTemp < minY || yTemp > maxY) {
return NO_MORE_COLUMN;
}
// coordinates
currentLeading = 0;
float x1 = leftX;
float tableWidth;
if (table.LockedWidth) {
tableWidth = table.TotalWidth;
UpdateFilledWidth(tableWidth);
}
else {
tableWidth = rectangularWidth * table.WidthPercentage / 100f;
table.TotalWidth = tableWidth;
}
// how many header rows are real header rows; how many are footer rows?
int headerRows = table.HeaderRows;
int footerRows = table.FooterRows;
if (footerRows > headerRows)
footerRows = headerRows;
int realHeaderRows = headerRows - footerRows;
float headerHeight = table.HeaderHeight;
float footerHeight = table.FooterHeight;
// make sure the header and footer fit on the page
bool skipHeader = (!firstPass && table.SkipFirstHeader && listIdx <= headerRows);
if (!skipHeader) {
yTemp -= headerHeight;
if (yTemp < minY || yTemp > maxY) {
if (firstPass) {
compositeElements.RemoveAt(0);
continue;
}
return NO_MORE_COLUMN;
}
}
// how many real rows (not header or footer rows) fit on a page?
int k;
if (listIdx < headerRows) {
listIdx = headerRows;
}
if (!table.ElementComplete) {
yTemp -= footerHeight;
}
for (k = listIdx; k < table.Size; ++k) {
float rowHeight = table.GetRowHeight(k);
if (yTemp - rowHeight < minY)
break;
yTemp -= rowHeight;
}
if (!table.ElementComplete) {
yTemp += footerHeight;
}
// either k is the first row that doesn't fit on the page (break);
if (k < table.Size) {
if (table.SplitRows && (!table.SplitLate || (k == listIdx && firstPass))) {
if (!splittedRow) {
splittedRow = true;
table = new PdfPTable(table);
compositeElements[0] = table;
ArrayList rows = table.Rows;
for (int i = headerRows; i < listIdx; ++i)
rows[i] = null;
}
float h = yTemp - minY;
PdfPRow newRow = table.GetRow(k).SplitRow(h);
if (newRow == null) {
if (k == listIdx) {
return NO_MORE_COLUMN;
}
}
else {
yTemp = minY;
table.Rows.Insert(++k, newRow);
}
}
else if (!table.SplitRows && k == listIdx && firstPass) {
compositeElements.RemoveAt(0);
splittedRow = false;
continue;
}
else if (k == listIdx && !firstPass && (!table.SplitRows || table.SplitLate) && (table.FooterRows == 0 || table.ElementComplete)) {
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.HorizontalAlignment) {
case Element.ALIGN_LEFT:
break;
case Element.ALIGN_RIGHT:
x1 += rectangularWidth - tableWidth;
break;
default:
x1 += (rectangularWidth - tableWidth) / 2f;
break;
}
// copy the rows that fit on the page in a new table nt
PdfPTable nt = PdfPTable.ShallowCopy(table);
ArrayList rows = table.Rows;
ArrayList sub = nt.Rows;
// first we add the real header rows (if necessary)
if (!skipHeader) {
for (int j = 0; j < realHeaderRows; ++j) {
PdfPRow headerRow = (PdfPRow)rows[j];
sub.Add(headerRow);
}
}
else {
nt.HeaderRows = footerRows;
}
// then we add the real content
for (int j = listIdx; j < k; ++j) {
sub.Add(rows[j]);
}
// 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)
if (k < table.Size) {
nt.ElementComplete = true;
}
// we add the footer rows if necessary (not for incomplete tables)
for (int j = 0; j < footerRows && nt.ElementComplete; ++j) {
sub.Add(rows[j + realHeaderRows]);
}
// we need a correction if the last row needs to be extended
float rowHeight = 0;
if (table.ExtendLastRow) {
PdfPRow last = (PdfPRow)sub[sub.Count - 1 - footerRows];
rowHeight = last.MaxHeights;
last.MaxHeights = yTemp - minY + rowHeight;
yTemp = minY;
}
// now we render the rows of the new table
if (canvases != null)
nt.WriteSelectedRows(0, -1, x1, yLineWrite, canvases);
else
nt.WriteSelectedRows(0, -1, x1, yLineWrite, canvas);
if (table.ExtendLastRow) {
PdfPRow last = (PdfPRow)sub[sub.Count - 1 - footerRows];
last.MaxHeights = rowHeight;
}
}
else if (table.ExtendLastRow && minY > PdfPRow.BOTTOM_LIMIT) {
yTemp = minY;
}
yLine = yTemp;
if (!(skipHeader || table.ElementComplete)) {
yLine += footerHeight;
}
if (k >= table.Size) {
yLine -= table.SpacingAfter;
compositeElements.RemoveAt(0);
splittedRow = false;
listIdx = 0;
}
else {
if (splittedRow) {
ArrayList rows = table.Rows;
for (int i = listIdx; i < k; ++i)
rows[i] = null;
}
listIdx = k;
return NO_MORE_COLUMN;
}
}
else if (element.Type == Element.YMARK) {
if (!simulate) {
IDrawInterface zh = (IDrawInterface)element;
zh.Draw(canvas, leftX, minY, rightX, maxY, yLine);
}
compositeElements.RemoveAt(0);
}
else
compositeElements.RemoveAt(0);
}
}
/**
* Sets the canvas.
* @param canvas
*/
public PdfContentByte Canvas {
set {
canvas = value;
canvases = null;
if (compositeColumn != null)
compositeColumn.Canvas = value;
}
get {
return canvas;
}
}
/**
* Sets the canvases.
* @param canvas
*/
public PdfContentByte[] Canvases {
set {
canvases = value;
canvas = canvases[PdfPTable.TEXTCANVAS];
if (compositeColumn != null)
compositeColumn.Canvases = canvases;
}
get {
return canvases;
}
}
/**
* Checks if the element has a height of 0.
* @return true or false
* @since 2.1.2
*/
public bool ZeroHeightElement() {
return composite && compositeElements.Count != 0 && ((IElement)compositeElements[0]).Type == Element.YMARK;
}
/**
* Enables/Disables adjustment of first line height based on max ascender.
* @param use enable adjustment if true
*/
public bool UseAscender {
set {
useAscender = value;
}
get {
return useAscender;
}
}
/**
* Checks the status variable and looks if there's still some text.
*/
public static bool HasMoreText(int status) {
return (status & ColumnText.NO_MORE_TEXT) == 0;
}
/**
* Holds value of property filledWidth.
*/
private float 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 float FilledWidth {
set {
filledWidth = value;
}
get {
return filledWidth;
}
}
/**
* Replaces the <CODE>filledWidth</CODE> if greater than the existing one.
* @param w the new <CODE>filledWidth</CODE> if greater than the existing one
*/
public void UpdateFilledWidth(float w) {
if (w > filledWidth)
filledWidth = w;
}
private bool adjustFirstLine = true;
/**
* 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
* <CODE>true</CODE> 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 <CODE>true</CODE> to adjust the first line, <CODE>false</CODE> otherwise
*/
public bool AdjustFirstLine {
set {
adjustFirstLine = value;
}
get {
return adjustFirstLine;
}
}
}
}