606 lines
21 KiB
C#
606 lines
21 KiB
C#
using System;
|
|
using System.Collections;
|
|
using iTextSharp.text;
|
|
/*
|
|
* $Id: MultiColumnText.cs,v 1.13 2008/05/13 11:25:18 psoares33 Exp $
|
|
*
|
|
*
|
|
* Copyright 2004 Steve Appling
|
|
*
|
|
* 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-2005 by Bruno Lowagie.
|
|
* All Rights Reserved.
|
|
* Co-Developer of the code is Paulo Soares. Portions created by the Co-Developer
|
|
* are Copyright (C) 2000-2005 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 content into one or more columns bounded by a
|
|
* rectangle. The columns may be simple rectangles or
|
|
* more complicated shapes. Add all of the columns before
|
|
* adding content. Column continuation is supported. A MultiColumnText object may be added to
|
|
* a document using <CODE>Document.add</CODE>.
|
|
* @author Steve Appling
|
|
*/
|
|
public class MultiColumnText : IElement {
|
|
|
|
/** special constant for automatic calculation of height */
|
|
public const float AUTOMATIC = -1f;
|
|
|
|
/**
|
|
* total desiredHeight of columns. If <CODE>AUTOMATIC</CODE>, this means fill pages until done.
|
|
* This may be larger than one page
|
|
*/
|
|
private float desiredHeight;
|
|
|
|
/**
|
|
* total height of element written out so far
|
|
*/
|
|
private float totalHeight;
|
|
|
|
/**
|
|
* true if all the text could not be written out due to height restriction
|
|
*/
|
|
private bool overflow;
|
|
|
|
/**
|
|
* Top of the columns - y position on starting page.
|
|
* If <CODE>AUTOMATIC</CODE>, it means current y position when added to document
|
|
*/
|
|
private float top;
|
|
|
|
/**
|
|
* used to store the y position of the bottom of the page
|
|
*/
|
|
private float pageBottom;
|
|
|
|
/**
|
|
* ColumnText object used to do all the real work. This same object is used for all columns
|
|
*/
|
|
private ColumnText columnText;
|
|
|
|
/**
|
|
* Array of <CODE>ColumnDef</CODE> objects used to define the columns
|
|
*/
|
|
private ArrayList columnDefs;
|
|
|
|
/**
|
|
* true if all columns are simple (rectangular)
|
|
*/
|
|
private bool simple = true;
|
|
|
|
private int currentColumn = 0;
|
|
|
|
private float nextY = AUTOMATIC;
|
|
|
|
private bool columnsRightToLeft = false;
|
|
|
|
private PdfDocument document;
|
|
/**
|
|
* Default constructor. Sets height to <CODE>AUTOMATIC</CODE>.
|
|
* Columns will repeat on each page as necessary to accomodate content length.
|
|
*/
|
|
public MultiColumnText() : this(AUTOMATIC) {
|
|
}
|
|
|
|
/**
|
|
* Construct a MultiColumnText container of the specified height.
|
|
* If height is <CODE>AUTOMATIC</CODE>, fill complete pages until done.
|
|
* If a specific height is used, it may span one or more pages.
|
|
*
|
|
* @param height
|
|
*/
|
|
public MultiColumnText(float height) {
|
|
columnDefs = new ArrayList();
|
|
desiredHeight = height;
|
|
top = AUTOMATIC;
|
|
// canvas will be set later
|
|
columnText = new ColumnText(null);
|
|
totalHeight = 0f;
|
|
}
|
|
|
|
/**
|
|
* Construct a MultiColumnText container of the specified height
|
|
* starting at the specified Y position.
|
|
*
|
|
* @param height
|
|
* @param top
|
|
*/
|
|
public MultiColumnText(float top, float height) {
|
|
columnDefs = new ArrayList();
|
|
desiredHeight = height;
|
|
this.top = top;
|
|
nextY = top;
|
|
// canvas will be set later
|
|
columnText = new ColumnText(null);
|
|
totalHeight = 0f;
|
|
}
|
|
|
|
/**
|
|
* Indicates that all of the text did not fit in the
|
|
* specified height. Note that isOverflow will return
|
|
* false before the MultiColumnText object has been
|
|
* added to the document. It will always be false if
|
|
* the height is AUTOMATIC.
|
|
*
|
|
* @return true if there is still space left in the column
|
|
*/
|
|
public bool IsOverflow() {
|
|
return overflow;
|
|
}
|
|
|
|
/**
|
|
* Copy the parameters from the specified ColumnText to use
|
|
* when rendering. Parameters like <CODE>setArabicOptions</CODE>
|
|
* must be set in this way.
|
|
*
|
|
* @param sourceColumn
|
|
*/
|
|
public void UseColumnParams(ColumnText sourceColumn) {
|
|
// note that canvas will be overwritten later
|
|
columnText.SetSimpleVars(sourceColumn);
|
|
}
|
|
|
|
/**
|
|
* Add a new column. The parameters are limits for each column
|
|
* wall in the format of a sequence of points (x1,y1,x2,y2,...).
|
|
*
|
|
* @param left limits for left column
|
|
* @param right limits for right column
|
|
*/
|
|
public void AddColumn(float[] left, float[] right) {
|
|
ColumnDef nextDef = new ColumnDef(left, right, this);
|
|
simple = nextDef.IsSimple();
|
|
columnDefs.Add(nextDef);
|
|
}
|
|
|
|
/**
|
|
* Add a simple rectangular column with specified left
|
|
* and right x position boundaries.
|
|
*
|
|
* @param left left boundary
|
|
* @param right right boundary
|
|
*/
|
|
public void AddSimpleColumn(float left, float right) {
|
|
ColumnDef newCol = new ColumnDef(left, right, this);
|
|
columnDefs.Add(newCol);
|
|
}
|
|
|
|
/**
|
|
* Add the specified number of evenly spaced rectangular columns.
|
|
* Columns will be seperated by the specified gutterWidth.
|
|
*
|
|
* @param left left boundary of first column
|
|
* @param right right boundary of last column
|
|
* @param gutterWidth width of gutter spacing between columns
|
|
* @param numColumns number of columns to add
|
|
*/
|
|
public void AddRegularColumns(float left, float right, float gutterWidth, int numColumns) {
|
|
float currX = left;
|
|
float width = right - left;
|
|
float colWidth = (width - (gutterWidth * (numColumns - 1))) / numColumns;
|
|
for (int i = 0; i < numColumns; i++) {
|
|
AddSimpleColumn(currX, currX + colWidth);
|
|
currX += colWidth + gutterWidth;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Add an element to be rendered in a column.
|
|
* Note that you can only add a <CODE>Phrase</CODE>
|
|
* or a <CODE>Chunk</CODE> if the columns are
|
|
* not all simple. This is an underlying restriction in
|
|
* {@link com.lowagie.text.pdf.ColumnText}
|
|
*
|
|
* @param element element to add
|
|
* @throws DocumentException if element can't be added
|
|
*/
|
|
public void AddElement(IElement element) {
|
|
if (simple) {
|
|
columnText.AddElement(element);
|
|
} else if (element is Phrase) {
|
|
columnText.AddText((Phrase) element);
|
|
} else if (element is Chunk) {
|
|
columnText.AddText((Chunk) element);
|
|
} else {
|
|
throw new DocumentException("Can't add " + element.GetType().ToString() + " to MultiColumnText with complex columns");
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Write out the columns. After writing, use
|
|
* {@link #isOverflow()} to see if all text was written.
|
|
* @param canvas PdfContentByte to write with
|
|
* @param document document to write to (only used to get page limit info)
|
|
* @param documentY starting y position to begin writing at
|
|
* @return the current height (y position) after writing the columns
|
|
* @throws DocumentException on error
|
|
*/
|
|
public float Write(PdfContentByte canvas, PdfDocument document, float documentY) {
|
|
this.document = document;
|
|
columnText.Canvas = canvas;
|
|
if (columnDefs.Count == 0) {
|
|
throw new DocumentException("MultiColumnText has no columns");
|
|
}
|
|
overflow = false;
|
|
pageBottom = document.Bottom;
|
|
float currentHeight = 0;
|
|
bool done = false;
|
|
while (!done) {
|
|
if (top == AUTOMATIC) {
|
|
top = document.GetVerticalPosition(true);
|
|
}
|
|
else if (nextY == AUTOMATIC) {
|
|
nextY = document.GetVerticalPosition(true); // RS - 07/07/2005 - - Get current doc writing position for top of columns on new page.
|
|
}
|
|
|
|
ColumnDef currentDef = (ColumnDef) columnDefs[CurrentColumn];
|
|
columnText.YLine = top;
|
|
|
|
float[] left = currentDef.ResolvePositions(Rectangle.LEFT_BORDER);
|
|
float[] right = currentDef.ResolvePositions(Rectangle.RIGHT_BORDER);
|
|
if (document.IsMarginMirroring() && document.PageNumber % 2 == 0){
|
|
float delta = document.RightMargin - document.Left;
|
|
left = (float[])left.Clone();
|
|
right = (float[])right.Clone();
|
|
for (int i = 0; i < left.Length; i += 2) {
|
|
left[i] -= delta;
|
|
}
|
|
for (int i = 0; i < right.Length; i += 2) {
|
|
right[i] -= delta;
|
|
}
|
|
}
|
|
currentHeight = Math.Max(currentHeight, GetHeight(left, right));
|
|
|
|
if (currentDef.IsSimple()) {
|
|
columnText.SetSimpleColumn(left[2], left[3], right[0], right[1]);
|
|
} else {
|
|
columnText.SetColumns(left, right);
|
|
}
|
|
|
|
int result = columnText.Go();
|
|
if ((result & ColumnText.NO_MORE_TEXT) != 0) {
|
|
done = true;
|
|
top = columnText.YLine;
|
|
} else if (ShiftCurrentColumn()) {
|
|
top = nextY;
|
|
} else { // check if we are done because of height
|
|
totalHeight += currentHeight;
|
|
|
|
if ((desiredHeight != AUTOMATIC) && (totalHeight >= desiredHeight)) {
|
|
overflow = true;
|
|
break;
|
|
} else { // need to start new page and reset the columns
|
|
documentY = nextY;
|
|
NewPage();
|
|
currentHeight = 0;
|
|
}
|
|
}
|
|
}
|
|
if (desiredHeight == AUTOMATIC && columnDefs.Count == 1) {
|
|
currentHeight = documentY - columnText.YLine;
|
|
}
|
|
return currentHeight;
|
|
}
|
|
|
|
private void NewPage() {
|
|
ResetCurrentColumn();
|
|
if (desiredHeight == AUTOMATIC) {
|
|
top = nextY = AUTOMATIC;
|
|
}
|
|
else {
|
|
top = nextY;
|
|
}
|
|
totalHeight = 0;
|
|
if (document != null) {
|
|
document.NewPage();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Figure out the height of a column from the border extents
|
|
*
|
|
* @param left left border
|
|
* @param right right border
|
|
* @return height
|
|
*/
|
|
private float GetHeight(float[] left, float[] right) {
|
|
float max = float.MinValue;
|
|
float min = float.MaxValue;
|
|
for (int i = 0; i < left.Length; i += 2) {
|
|
min = Math.Min(min, left[i + 1]);
|
|
max = Math.Max(max, left[i + 1]);
|
|
}
|
|
for (int i = 0; i < right.Length; i += 2) {
|
|
min = Math.Min(min, right[i + 1]);
|
|
max = Math.Max(max, right[i + 1]);
|
|
}
|
|
return max - min;
|
|
}
|
|
|
|
|
|
/**
|
|
* Processes the element by adding it to an
|
|
* <CODE>ElementListener</CODE>.
|
|
*
|
|
* @param listener an <CODE>ElementListener</CODE>
|
|
* @return <CODE>true</CODE> if the element was processed successfully
|
|
*/
|
|
public bool Process(IElementListener listener) {
|
|
try {
|
|
return listener.Add(this);
|
|
} catch (DocumentException) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Gets the type of the text element.
|
|
*
|
|
* @return a type
|
|
*/
|
|
|
|
public int Type {
|
|
get {
|
|
return Element.MULTI_COLUMN_TEXT;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns null - not used
|
|
*
|
|
* @return null
|
|
*/
|
|
|
|
public ArrayList Chunks {
|
|
get {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @see com.lowagie.text.Element#isContent()
|
|
* @since iText 2.0.8
|
|
*/
|
|
public bool IsContent() {
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* @see com.lowagie.text.Element#isNestable()
|
|
* @since iText 2.0.8
|
|
*/
|
|
public bool IsNestable() {
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Calculates the appropriate y position for the bottom
|
|
* of the columns on this page.
|
|
*
|
|
* @return the y position of the bottom of the columns
|
|
*/
|
|
private float GetColumnBottom() {
|
|
if (desiredHeight == AUTOMATIC) {
|
|
return pageBottom;
|
|
} else {
|
|
return Math.Max(top - (desiredHeight - totalHeight), pageBottom);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Moves the text insertion point to the beginning of the next column, issuing a page break if
|
|
* needed.
|
|
* @throws DocumentException on error
|
|
*/
|
|
public void NextColumn() {
|
|
currentColumn = (currentColumn + 1) % columnDefs.Count;
|
|
top = nextY;
|
|
if (currentColumn == 0) {
|
|
NewPage();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Gets the current column.
|
|
* @return the current column
|
|
*/
|
|
public int CurrentColumn {
|
|
get {
|
|
if (columnsRightToLeft) {
|
|
return (columnDefs.Count - currentColumn - 1);
|
|
}
|
|
return currentColumn;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Resets the current column.
|
|
*/
|
|
public void ResetCurrentColumn() {
|
|
currentColumn = 0;
|
|
}
|
|
|
|
/**
|
|
* Shifts the current column.
|
|
* @return true if the currentcolumn has changed
|
|
*/
|
|
public bool ShiftCurrentColumn() {
|
|
if (currentColumn + 1 < columnDefs.Count) {
|
|
currentColumn++;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Sets the direction of the columns.
|
|
* @param direction true = right2left; false = left2right
|
|
*/
|
|
public void SetColumnsRightToLeft(bool direction) {
|
|
columnsRightToLeft = direction;
|
|
}
|
|
|
|
/** Sets the ratio between the extra word spacing and the extra character spacing
|
|
* when the text is fully justified.
|
|
* Extra word spacing will grow <CODE>spaceCharRatio</CODE> times more than extra character spacing.
|
|
* If the ratio is <CODE>PdfWriter.NO_SPACE_CHAR_RATIO</CODE> then the extra character spacing
|
|
* will be zero.
|
|
* @param spaceCharRatio the ratio between the extra word spacing and the extra character spacing
|
|
*/
|
|
public float SpaceCharRatio {
|
|
set {
|
|
columnText.SpaceCharRatio = value;
|
|
}
|
|
}
|
|
|
|
/** Sets the run direction.
|
|
* @param runDirection the run direction
|
|
*/
|
|
public int RunDirection {
|
|
set {
|
|
columnText.RunDirection = value;
|
|
}
|
|
}
|
|
|
|
/** 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 {
|
|
columnText.ArabicOptions = value;
|
|
}
|
|
}
|
|
|
|
/** Sets the default alignment
|
|
* @param alignment the default alignment
|
|
*/
|
|
public int Alignment {
|
|
set {
|
|
columnText.Alignment = value;
|
|
}
|
|
}
|
|
|
|
public override string ToString() {
|
|
return base.ToString();
|
|
}
|
|
|
|
/**
|
|
* Inner class used to define a column
|
|
*/
|
|
internal class ColumnDef {
|
|
private float[] left;
|
|
private float[] right;
|
|
private MultiColumnText mc;
|
|
|
|
internal ColumnDef(float[] newLeft, float[] newRight, MultiColumnText mc) {
|
|
this.mc = mc;
|
|
left = newLeft;
|
|
right = newRight;
|
|
}
|
|
|
|
internal ColumnDef(float leftPosition, float rightPosition, MultiColumnText mc) {
|
|
this.mc = mc;
|
|
left = new float[4];
|
|
left[0] = leftPosition; // x1
|
|
left[1] = mc.top; // y1
|
|
left[2] = leftPosition; // x2
|
|
if (mc.desiredHeight == AUTOMATIC || mc.top == AUTOMATIC) {
|
|
left[3] = AUTOMATIC;
|
|
} else {
|
|
left[3] = mc.top - mc.desiredHeight;
|
|
}
|
|
|
|
right = new float[4];
|
|
right[0] = rightPosition; // x1
|
|
right[1] = mc.top; // y1
|
|
right[2] = rightPosition; // x2
|
|
if (mc.desiredHeight == AUTOMATIC || mc.top == AUTOMATIC) {
|
|
right[3] = AUTOMATIC;
|
|
} else {
|
|
right[3] = mc.top - mc.desiredHeight;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Resolves the positions for the specified side of the column
|
|
* into real numbers once the top of the column is known.
|
|
*
|
|
* @param side either <CODE>Rectangle.LEFT_BORDER</CODE>
|
|
* or <CODE>Rectangle.RIGHT_BORDER</CODE>
|
|
* @return the array of floats for the side
|
|
*/
|
|
internal float[] ResolvePositions(int side) {
|
|
if (side == Rectangle.LEFT_BORDER) {
|
|
return ResolvePositions(left);
|
|
} else {
|
|
return ResolvePositions(right);
|
|
}
|
|
}
|
|
|
|
internal float[] ResolvePositions(float[] positions) {
|
|
if (!IsSimple()) {
|
|
return positions;
|
|
}
|
|
if (mc.top == AUTOMATIC) {
|
|
// this is bad - must be programmer error
|
|
throw new Exception("resolvePositions called with top=AUTOMATIC (-1). " +
|
|
"Top position must be set befure lines can be resolved");
|
|
}
|
|
positions[1] = mc.top;
|
|
positions[3] = mc.GetColumnBottom();
|
|
return positions;
|
|
}
|
|
|
|
/**
|
|
* Checks if column definition is a simple rectangle
|
|
* @return true if it is a simple column
|
|
*/
|
|
internal bool IsSimple() {
|
|
return (left.Length == 4 && right.Length == 4) && (left[0] == left[2] && right[0] == right[2]);
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|