When creating a PDF table with iText5, it is possible to create a table background by implementing a PdfPTableEvent and a cell background by implementing a PdfPCellEvent.
But what about a row background? How can I create that?
The reason is because I want to create a calendar table like in the image below:
Actually, you have included the answer in your question: you have to use a table event. Take a look at the RowBackground example. It contains a table event RowBackgroundEvent that allows you to create a table event to draw the background of a single row.
public class RowBackgroundEvent implements PdfPTableEvent {
// the row number of the row that needs a background
protected int row;
// creates a background event for a specific row
public RowBackgroundEvent(int row) {
this.row = row;
}
/**
* Draws the background of a row.
*/
#Override
public void tableLayout(PdfPTable table, float[][] widths, float[] heights,
int headerRows, int rowStart, PdfContentByte[] canvases) {
float llx = widths[row][0];
float lly = heights[row];
float urx = widths[row][widths[row].length - 1];
float ury = heights[row - 1];
float h = ury - lly;
PdfContentByte canvas = canvases[PdfPTable.BASECANVAS];
canvas.saveState();
canvas.arc(llx - h / 2, lly, llx + h / 2, ury, 90, 180);
canvas.lineTo(urx, lly);
canvas.arc(urx - h / 2, lly, urx + h / 2, ury, 270, 180);
canvas.lineTo(llx, ury);
canvas.setColorFill(BaseColor.LIGHT_GRAY);
canvas.fill();
canvas.restoreState();
}
}
This is how this event is used:
public void createPdf(String filename) throws SQLException, DocumentException, IOException {
// step 1
Document document = new Document(PageSize.A4.rotate());
// step 2
PdfWriter.getInstance(document, new FileOutputStream(filename));
// step 3
document.open();
// step 4
PdfPTableEvent event = new RowBackgroundEvent(3);
PdfPTable table = new PdfPTable(7);
table.setTableEvent(event);
table.getDefaultCell().setBorder(Rectangle.NO_BORDER);
for (int i = 0; i < 10; i++) {
for (int j = 1; j < 8; j++) {
table.addCell(String.valueOf(j));
}
}
document.add(table);
// step 5
document.close();
}
As you can see, we want a background for the third row. The result looks like this:
You can tweak the llx, lly, urx, and ury value if you want to adapt the size of the background bar.
If you can draw the background of a single row, you can extend the code to draw the background of more than one row.
Related
I've spawned a tile as follows:
private GameObject SpawnTile(int col, int row, Color color, GameObject parent, string label)
{
GameObject g = new GameObject("C: " + col + " R: " + row);
g.transform.position = world_grid.GetWorldPosition(col, row);
g.transform.localScale = new Vector3(world_grid.cell_size, world_grid.cell_size);
g.transform.parent = parent.transform;
return g;
}
I then add a spriterenderer, and display a sprite of a given color.
tile.game_object = SpawnTile(col, row, color, parent, label);
tile.sprite_renderer = tile.game_object.AddComponent<SpriteRenderer>();
tile.sprite_renderer.sprite = tile_sprite;
//Bounds bounds = tile.sprite_renderer.bounds;
tile.sprite_renderer.color = color;
I have the ability to display grids using this mechanism. I can scale the size of a tile up by an integer amount. However when I try to scale the object down, the sprite does not scale down with it.
In the following, I have 3 1x1 grids. The grid on the right has a cell_size of 2. The grid in the middle has a cell_size of 1. The grid on the left has a cell_size of 0.5.
The "tile" object is of an appropriate size. As we see this object is shown as half size in Unity's Scene View. However in the game view on the right, it shows as the same size as the pink grid in the middle.
Further details, I have spent quite a bit of time trying to figure this out, and though I see a variety of posts that seem to relate to sprite sizing, none of them are clear for a relative unity beginner.
Some of the posts I've found seem to suggest that I need to use the sprite renderer bounds to ensure that the sprite is proeprly sized. But its not clear to me how.
This is my grid class. Notice that it is constructed with a cell_size (in units). Notice that when I create the object of a given size, I size the object, but not the sprite.
public class WorldGrid<TGridObject> : Grid<TGridObject>
{
public float cell_size { get; private set; }
// Defines the center point in world coordinates
public Vector3 center_point { get; private set; }
public WorldGrid(Vector3 _center_point, int _columns, int _rows, float _cell_size) : base(_columns, _rows)
{
cell_size = _cell_size;
center_point = _center_point;
}
public Vector3 GetWorldPosition(int col, int row)
{
float x_pos = col * cell_size + cell_size / 2f;
float y_pos = row * cell_size + cell_size / 2f;
// wp = cp + gp
return center_point + new Vector3(x_pos, y_pos);
}
public void GetGridPosition(Vector3 world_position, out int col, out int row)
{
// gp = wp - cp
// position in grid-centered reference frame
Vector3 gp = world_position - center_point;
row = Mathf.FloorToInt(gp.y / cell_size);
col = Mathf.FloorToInt(gp.x / cell_size);
}
public void SetValue(Vector3 world_position, TGridObject value)
{
int row, col;
GetGridPosition(world_position, out row, out col);
SetValue(row, col, value);
}
}
This references a lower level grid class (that is not in world units):
public class Grid<TGridObject>
{
public int columns { get; private set; }
public int rows { get; private set; }
public TGridObject[,] grid { get; private set; }
private int cnt = 0;
public event EventHandler<OnGridCellValueChangedEventArgs> OnGridCellValueChanged;
public class OnGridCellValueChangedEventArgs : EventArgs
{
public int cnt;
public int row;
public int column;
}
public Grid(int _columns, int _rows)
{
columns = _columns;
rows = _rows;
grid = new TGridObject[columns, rows];
}
public void SetValue(int col, int row, TGridObject value)
{
if((row >= 0 && row < rows) &&
(col >= 0 && col < columns))
{
grid[col, row] = value;
if(OnGridCellValueChanged != null)
{
cnt += 1;
Debug.LogError("Triggering [" + col + "," + row + "] of [" + columns + "," + rows + "]");
OnGridCellValueChanged(this,
new OnGridCellValueChangedEventArgs { row = row, column = col, cnt = cnt }
);
}
}
}
public TGridObject GetValue(int col, int row)
{
if ((row >= 0 && row < rows) &&
(col >= 0 && col < columns))
{
return grid[col, row];
} else
{
return default(TGridObject);
}
}
}
I instantiate grids using a GridManager class:
public class GridManager : MonoBehaviour
{
[System.Serializable]
private struct WorldGridDescriptor
{
public int rows;
public int columns;
public float cell_size;
public Vector3 center_point;
public bool randomize_start;
}
public Sprite tile_sprite;
public int world_size_factor = 1;
private int columns, rows;
private float unit_step_size = 1.0f;
private List<WorldGrid<float>> world_grids = new List<WorldGrid<float>>();
private List<GridView> world_grid_views = new List<GridView>();
[SerializeField] private List<WorldGridDescriptor> world_grid_descriptors;
// Start is called before the first frame update
void Start()
{
// The orthographic size specifies the camera units from
// the horizontal centerline to the top of screen
// The vertical_units are then the number of tile rows
// from the centerline to the top of screen
rows = (int)Camera.main.orthographicSize * world_size_factor * 2;
float aspect_ratio = ((float)Screen.width / (float)Screen.height);
columns = (int)(rows * aspect_ratio);
// Will parent the tiles in the empty Grid.
// Perhaps this container should be public.
// For now it is a hard-coded assumption, that it exists.
GameObject grid_container = this.gameObject.transform.Find("Grid").gameObject;
if (world_grid_descriptors != null)
{
foreach (WorldGridDescriptor wgd in world_grid_descriptors)
{
WorldGrid<float> wg = new WorldGrid<float>(wgd.center_point, wgd.columns, wgd.rows, wgd.cell_size);
if(wgd.randomize_start)
{
RandomizeState(wg);
}
GridView grid_view = new GridView(wg, tile_sprite, grid_container);
world_grids.Add(wg);
world_grid_views.Add(grid_view);
}
}
}
private void RandomizeState(WorldGrid<float> g)
{
for (int col = 0; col < g.columns; col++)
{
for (int row = 0; row < g.rows; row++)
{
g.SetValue(col, row, Random.Range(0.0f, 1.0f));
}
}
}
private void Update()
{
if (Input.GetMouseButtonDown(0)) {
Vector3 worldPosition = Camera.main.ScreenToWorldPoint(Input.mousePosition);
foreach (WorldGrid<float> wg in world_grids)
{
int c, r;
wg.GetGridPosition(worldPosition, out c, out r);
wg.SetValue(c, r, Random.Range(0.0f, 1.0f));
}
}
}
}
Lastly, the view into the grids are created as follows (this shows you exactly how I create the grid representation that uses a sprite renderer component to render sprites for each cell in the grid):
public class GridView
{
public struct Tile
{
public GameObject game_object;
public SpriteRenderer sprite_renderer;
public TextMesh text_mesh;
}
private Tile[,] tiles;
private WorldGrid<float> world_grid;
protected Sprite tile_sprite { get; private set; }
public GridView(WorldGrid<float> wg, Sprite ts, GameObject parent)
{
world_grid = wg;
tile_sprite = ts;
tiles = new Tile[wg.columns, wg.rows];
wg.OnGridCellValueChanged += OnGridCellValueChanged;
Create(parent);
}
private void Create(GameObject parent)
{
for (int col = 0; col < world_grid.columns; col++)
{
for (int row = 0; row < world_grid.rows; row++)
{
Tile tile = new Tile();
float r, g, b;
r = g = b = world_grid.grid[col, row];
float a = 1f;
Color color = new Color(r, g, b, a);
string label = (string)((int)(255 * world_grid.grid[col, row])).ToString("x");
tile.game_object = SpawnTile(col, row, color, parent, label);
tile.sprite_renderer = tile.game_object.AddComponent<SpriteRenderer>();
tile.sprite_renderer.sprite = tile_sprite;
//Bounds bounds = tile.sprite_renderer.bounds;
tile.sprite_renderer.color = color;
tile.text_mesh = UtilsClass.CreateWorldText(label, parent.transform,
tile.game_object.transform.position, 7, Color.red, TextAnchor.MiddleCenter);
tiles[col, row] = tile;
}
}
}
// Update is called once per frame
private GameObject SpawnTile(int col, int row, Color color, GameObject parent, string label)
{
GameObject g = new GameObject("C: " + col + " R: " + row);
g.transform.position = world_grid.GetWorldPosition(col, row);
g.transform.localScale = new Vector3(world_grid.cell_size, world_grid.cell_size);
g.transform.parent = parent.transform;
return g;
}
private void OnGridCellValueChanged(object sender, Grid<float>.OnGridCellValueChangedEventArgs e)
{
Debug.LogError(e.cnt + " - Updating [" + e.column + "," + e.row + "]");
Update(e.row, e.column);
}
private void Update(int row, int col)
{
if (row >= 0 && row < world_grid.rows && col >= 0 && col < world_grid.columns)
{
Tile tile = tiles[col, row];
float r, g, b;
r = g = b = world_grid.grid[col, row];
float a = 1f;
Color color = new Color(r, g, b, a);
string label = (string)((int)(255 * world_grid.grid[col, row])).ToString("x");
tile.text_mesh.text = label;
tile.sprite_renderer.color = color;
}
}
}
My sprites are a simple blank image that is 256 x 256 pixels in size. I then set the pixels per unit (in the unity editor) to 256.
How can I scale the sprite so that it aligns properly with the spawned tile?
Looks like my only issue was in the Display setting.
I had been using a 10x10 display.
When I changed it to something more reasonable, I realized that my gameobjects were being displayed and selected as expected.
I am trying to set the background color of my footer. I can't seem to find any code that can assist in doing this. Please see my code for the OnEndPage event.
I have tried cb.SetColorFill(BaseColor.LIGHT_GRAY);, but that does not work :/
Which property or method is used to add a background color to the footer?
public override void OnEndPage(iTextSharp.text.pdf.PdfWriter writer, iTextSharp.text.Document document)
{
base.OnEndPage(writer, document);
iTextSharp.text.Font baseFontNormal = new iTextSharp.text.Font(iTextSharp.text.Font.FontFamily.HELVETICA, 12f, iTextSharp.text.Font.NORMAL, iTextSharp.text.BaseColor.BLACK);
iTextSharp.text.Font baseFontBig = new iTextSharp.text.Font(iTextSharp.text.Font.FontFamily.HELVETICA, 12f, iTextSharp.text.Font.BOLD, iTextSharp.text.BaseColor.BLACK);
var headerImagePath = System.Web.HttpContext.Current.Server.MapPath("~/Content/misc/proactive-reg-form-header.jpg");
var headerImage = Image.GetInstance(headerImagePath);
if (headerImage.Height > headerImage.Width)
{
//Maximum height is 800 pixels.
float percentage = 0.0f;
percentage = 700 / headerImage.Height;
headerImage.ScalePercent(percentage * 100);
}
else
{
//Maximum width is 600 pixels.
float percentage = 0.0f;
percentage = 572 / headerImage.Width;
headerImage.ScalePercent(percentage * 100);
}
//Create PdfTable object
PdfPTable pdfTab = new PdfPTable(1);
//We will have to create separate cells to include image logo and 2 separate strings
//Row 1
//PdfPCell pdfCell1 = new PdfPCell();
PdfPCell pdfCell2 = new PdfPCell(headerImage);
//PdfPCell pdfCell3 = new PdfPCell();
String text = "Page " + writer.PageNumber + " of ";
//Add paging to footer
{
cb.BeginText();
cb.SetFontAndSize(bf, 10);
cb.SetTextMatrix(document.PageSize.GetRight(100), document.PageSize.GetBottom(30));
cb.ShowText(text);
cb.EndText();
float len = bf.GetWidthPoint(text, 10);
cb.AddTemplate(footerTemplate, document.PageSize.GetRight(100) + len, document.PageSize.GetBottom(30));
//add image to footer
writer.DirectContent.AddImage(footerImage);
}
pdfCell2.HorizontalAlignment = Element.ALIGN_CENTER;
pdfCell2.VerticalAlignment = Element.ALIGN_BOTTOM;
pdfCell2.Border = 0;
pdfTab.AddCell(pdfCell2);
pdfTab.TotalWidth = 100f;
pdfTab.WidthPercentage = 100f;
//call WriteSelectedRows of PdfTable. This writes rows from PdfWriter in PdfTable
//first param is start row. -1 indicates there is no end row and all the rows to be included to write
//Third and fourth param is x and y position to start writing
pdfTab.WriteSelectedRows(0, -1, 10, document.PageSize.Height - 10, writer.DirectContent);
//set pdfContent value
if (document.PageNumber != 1)
{
//Move the pointer and draw line to separate header section from rest of page
cb.MoveTo(10, document.PageSize.Height - 60);
cb.LineTo((document.PageSize.Width - 10), document.PageSize.Height - 60);
cb.Stroke();
}
}
Got it!
So I amended the following code:
//Add paging to footer
{
cb.BeginText();
cb.SetFontAndSize(bf, 10);
cb.SetTextMatrix(document.PageSize.GetRight(100), document.PageSize.GetBottom(30));
cb.ShowText(text);
cb.EndText();
float len = bf.GetWidthPoint(text, 10);
cb.AddTemplate(footerTemplate, document.PageSize.GetRight(100) + len, document.PageSize.GetBottom(30));
//add image to footer
writer.DirectContent.AddImage(footerImage);
}
with
//Add paging to footer
{
cb.BeginText();
cb.SetFontAndSize(bf, 10);
cb.SetTextMatrix(document.PageSize.GetRight(100), document.PageSize.GetBottom(30));
cb.ShowText(text);
cb.EndText();
float len = bf.GetWidthPoint(text, 10);
cb.AddTemplate(footerTemplate, document.PageSize.GetRight(100) + len, document.PageSize.GetBottom(30));
//this part adds the background color
cb.SetColorFill(BaseColor.LIGHT_GRAY);
cb.Rectangle(0, 0, document.PageSize.Width, 50);
cb.FillStroke();
//add image to footer
writer.DirectContent.AddImage(footerImage);
}
I added the function below to change the margins for the page
at every page change.
I found the forum a method that sets the size of the page:
document.SetPageSize (New Rectangle (36.0F, 36.0F, 52.0F, PageFooter.TotalHeight))
But I do not want to change the size of the page, but those margins.
thanks
public override void OnEndPage(PdfWriter writer, Document document)
{
try
{
DataSet dsReport = new DataSet();
foreach (DataSet obj in report.arrayDs)
{
dsReport = obj;
break;
}
Single topMargin = 0;
if (document.PageNumber != 1)
{
if (report.repeatHead) //ripete l'intestazione del report su tutte le pagine di stampa
{
repeatHead(writer, document);
topMargin = 60;
}
else
{
if (document.PageNumber == 2) //ripete l'intestazione del report solo sulla second pagina dopo la copertina
{
repeatHead(writer, document);
topMargin = 60;
}
else
{
topMargin = Convert.ToSingle(dsReport.Tables["REPORT_STYLE"].Rows[0]["topMargin"]) * 10;
}
}
document.SetMargins(Convert.ToSingle(dsReport.Tables["REPORT_STYLE"].Rows[0]["leftMargin"]) * 10,
Convert.ToSingle(dsReport.Tables["REPORT_STYLE"].Rows[0]["rightMargin"]) * 10,
topMargin,
Convert.ToSingle(dsReport.Tables["REPORT_STYLE"].Rows[0]["bottomMargin"]) * 10);
}
}
catch
{ throw; }
}
Based on your clarification in the comments, you want the top margin of the first page to be 60 and the top margin of the second page to be 0.
This is shown in the following screen shot:
The Java code to achieve this looks like this:
public void createPdf(String dest) throws IOException, DocumentException {
float left = 30;
float right = 30;
float top = 60;
float bottom = 0;
Document document = new Document(PageSize.A4, left, right, top, bottom);
PdfWriter.getInstance(document, new FileOutputStream(dest));
document.open();
document.setMargins(left, right, 0, bottom);
for (int i = 0; i < 60; i++) {
document.add(new Paragraph("This is a test"));
}
document.close();
}
If you want to port this to C#, you need to change some lower cases into upper cases. You already knew most of the methods, for instance: I see document.SetMargins(...) in your page event.
I'd like to add a Paragraph of text to pages in 2 columns. I understand that MultiColumnText has been eliminated. I know I can create column 1, write to it, and if there is more text create column 2 and write to it. If there is still more text, go to the next page and repeat.
However I always end up with either:
a long chunk of text orphaned in the left column.
a full left column and partially used right column.
How can I format my content in 2 columns while reducing white space, such as compressing the columns so I end with 2 full columns of equal length?
Thanks!
You should add your column twice, once in simulation mode and once for real.
I have adapted the ColumnTextParagraphs example to show what is meant by simulation mode. Take a look at the ColumnTextParagraphs2 example:
We add the column in simulation mode to obtain the total height needed for the column. This is done in the following method:
public float getNecessaryHeight(ColumnText ct) throws DocumentException {
ct.setSimpleColumn(new Rectangle(0, 0, COLUMN_WIDTH, -500000));
ct.go(true);
return -ct.getYLine();
}
We use this height when we add the left and right column:
Rectangle left;
float top = COLUMNS[0].getTop();
float middle = (COLUMNS[0].getLeft() + COLUMNS[1].getRight()) / 2;
float columnheight;
int status = ColumnText.START_COLUMN;
while (ColumnText.hasMoreText(status)) {
if (checkHeight(height)) {
columnheight = COLUMNS[0].getHeight();
left = COLUMNS[0];
}
else {
columnheight = (height / 2) + ERROR_MARGIN;
left = new Rectangle(
COLUMNS[0].getLeft(),
COLUMNS[0].getTop() - columnheight,
COLUMNS[0].getRight(),
COLUMNS[0].getTop()
);
}
// left half
ct.setSimpleColumn(left);
ct.go();
height -= COLUMNS[0].getTop() - ct.getYLine();
// separator
canvas.moveTo(middle, top - columnheight);
canvas.lineTo(middle, top);
canvas.stroke();
// right half
ct.setSimpleColumn(COLUMNS[1]);
status = ct.go();
height -= COLUMNS[1].getTop() - ct.getYLine();
// new page
document.newPage();
}
This is how we check the height:
public boolean checkHeight(float height) {
height -= COLUMNS[0].getHeight() + COLUMNS[1].getHeight() + ERROR_MARGIN;
return height > 0;
}
As you can see, we add the full columns as long as the height of both columns is smaller than the remaining height. When columns are added, we adjust the remaining height. As soon as the height is lower than the height of to columns, we adapt the height of the first column.
Note that we work with an ERROR_MARGIN because dividing by two often leads to a situation where the second column has one line more than the first column. It is better when it's the other way around.
This is the full example at your request:
/**
* Example written by Bruno Lowagie in answer to:
* http://stackoverflow.com/questions/29378407/how-can-you-eliminate-white-space-in-multiple-columns-using-itextsharp
*/
package sandbox.objects;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import com.itextpdf.text.Document;
import com.itextpdf.text.DocumentException;
import com.itextpdf.text.Paragraph;
import com.itextpdf.text.Rectangle;
import com.itextpdf.text.pdf.ColumnText;
import com.itextpdf.text.pdf.PdfContentByte;
import com.itextpdf.text.pdf.PdfWriter;
public class ColumnTextParagraphs2 {
public static final String DEST = "results/objects/column_paragraphs2.pdf";
public static final String TEXT = "This is some long paragraph that will be added over and over again to prove a point.";
public static final float COLUMN_WIDTH = 254;
public static final float ERROR_MARGIN = 16;
public static final Rectangle[] COLUMNS = {
new Rectangle(36, 36, 36 + COLUMN_WIDTH, 806),
new Rectangle(305, 36, 305 + COLUMN_WIDTH, 806)
};
public static void main(String[] args) throws IOException, DocumentException {
File file = new File(DEST);
file.getParentFile().mkdirs();
new ColumnTextParagraphs2().createPdf(DEST);
}
public void createPdf(String dest) throws IOException, DocumentException {
// step 1
Document document = new Document();
// step 2
PdfWriter writer = PdfWriter.getInstance(document, new FileOutputStream(dest));
// step 3
document.open();
// step 4
PdfContentByte canvas = writer.getDirectContent();
ColumnText ct = new ColumnText(canvas);
addContent(ct);
float height = getNecessaryHeight(ct);
addContent(ct);
Rectangle left;
float top = COLUMNS[0].getTop();
float middle = (COLUMNS[0].getLeft() + COLUMNS[1].getRight()) / 2;
float columnheight;
int status = ColumnText.START_COLUMN;
while (ColumnText.hasMoreText(status)) {
if (checkHeight(height)) {
columnheight = COLUMNS[0].getHeight();
left = COLUMNS[0];
}
else {
columnheight = (height / 2) + ERROR_MARGIN;
left = new Rectangle(
COLUMNS[0].getLeft(),
COLUMNS[0].getTop() - columnheight,
COLUMNS[0].getRight(),
COLUMNS[0].getTop()
);
}
// left half
ct.setSimpleColumn(left);
ct.go();
height -= COLUMNS[0].getTop() - ct.getYLine();
// separator
canvas.moveTo(middle, top - columnheight);
canvas.lineTo(middle, top);
canvas.stroke();
// right half
ct.setSimpleColumn(COLUMNS[1]);
status = ct.go();
height -= COLUMNS[1].getTop() - ct.getYLine();
// new page
document.newPage();
}
// step 5
document.close();
}
public void addContent(ColumnText ct) {
for (int i = 0; i < 35; i++) {
ct.addElement(new Paragraph(String.format("Paragraph %s: %s", i, TEXT)));
}
}
public float getNecessaryHeight(ColumnText ct) throws DocumentException {
ct.setSimpleColumn(new Rectangle(0, 0, COLUMN_WIDTH, -500000));
ct.go(true);
return -ct.getYLine();
}
public boolean checkHeight(float height) {
height -= COLUMNS[0].getHeight() + COLUMNS[1].getHeight() + ERROR_MARGIN;
return height > 0;
}
}
I'm trying to set up a simple 2 column page, write to the first column, then the second. However the code below places both paragraphs in the second column. The current column trace appears to be correct (first 0, then 1)
Any ideas what I'm doing wrong?
MultiColumnText columns = new MultiColumnText();
columns.AddSimpleColumn(0, 200);
columns.AddSimpleColumn(200, 400);
Paragraph para1 = new Paragraph("Para1");
columns.AddElement(para1);
Response.Write(columns.CurrentColumn);//traces 0
columns.NextColumn();
Response.Write(columns.CurrentColumn);//traces 1
Paragraph para2 = new Paragraph("Para2");
columns.AddElement(para2);
doc.Add(columns);
Many thanks
Oliver
I couldn't get NextColumn() to work wih a MultiColumnText object and I couldn't find any samples (in .NET) that do.
A MultiColumnText makes creating columns in a document relatively easy but in exchange you give up a lot control over layout. You can use the ColumnText object which gives you a great deal of control over column layout but requires more code.
Here's a simple but complete example of what you're trying to do using ColumnText:
private void TestColumnText() {
using (FileStream fs = new FileStream("ColumnTest.pdf", FileMode.Create)) {
Document doc = new Document();
PdfWriter writer = PdfWriter.GetInstance(doc, fs);
doc.Open();
PdfContentByte cb = writer.DirectContent;
ColumnText ct = new ColumnText(cb);
float columnWidth = 200f;
float[] left1 = { doc.Left + 90f, doc.Top - 80f, doc.Left + 90f, doc.Top - 170f, doc.Left, doc.Top - 170f, doc.Left, doc.Bottom };
float[] right1 = { doc.Left + columnWidth, doc.Top - 80f, doc.Left + columnWidth, doc.Bottom };
float[] left2 = { doc.Right - columnWidth, doc.Top - 80f, doc.Right - columnWidth, doc.Bottom };
float[] right2 = { doc.Right, doc.Top - 80f, doc.Right, doc.Bottom };
// Add content for left column.
ct.SetColumns(left1, right1);
ct.AddText(new Paragraph("Para 1"));
ct.Go();
// Add content for right column.
ct.SetColumns(left2, right2);
ct.AddText(new Paragraph("Para 2"));
ct.Go();
doc.Close();
}
}
Warning: As I mentioned, this is a simple example and won't even serve as a starting point for you in what you're trying to do. The samples on the sites below (especially the first one) will help you:
http://www.mikesdotnetting.com/Article/89/iTextSharp-Page-Layout-with-Columns
http://www.devshed.com/c/a/Java/Adding-Columns-With-iTextSharp
As I found that the latest versions of iTextSharp don't include the MultiColumnText class I created one of my own of sorts.
Public Class SimpleColumnText
Inherits ColumnText
Dim workingDocument As Document
Dim columns As New List(Of Rectangle)
Dim currentColumn As Integer = 0
Public Sub New(content As PdfContentByte, columnCount As Integer, columnSpacing As Single, document As Document)
MyBase.New(content)
workingDocument = document
CalculateColumnBoundries(columnCount, columnSpacing)
End Sub
Private Sub CalculateColumnBoundries(columnCount As Integer, columnSpacing As Single)
Dim columnWidth As Single
Dim columnHeight As Single
With workingDocument
columnWidth = ((.PageSize.Width - .LeftMargin - .RightMargin) - (columnSpacing * (columnCount - 1))) / columnCount
columnHeight = (.PageSize.Height - .TopMargin - .BottomMargin)
End With
For x = 0 To columnCount - 1
Dim llx As Single = ((columnWidth + columnSpacing) * x) + workingDocument.LeftMargin
Dim lly As Single = workingDocument.BottomMargin
Dim urx As Single = llx + columnWidth
Dim ury As Single = columnHeight
Dim newRectangle As New Rectangle(llx, lly, urx, ury)
columns.Add(newRectangle)
Next
End Sub
Public Shadows Sub AddElement(element As IElement)
MyBase.AddElement(element)
'we have to see if there is a column on the page before we begin
Dim status As Integer
If currentColumn = 0 Then
status = ColumnText.NO_MORE_COLUMN
End If
Do
If status = ColumnText.NO_MORE_COLUMN Then
If currentColumn = columns.Count Then
'we need a new page
workingDocument.NewPage()
'reset column count
currentColumn = 0
End If
MyBase.SetSimpleColumn(columns(currentColumn))
currentColumn += 1
End If
status = MyBase.Go()
Loop While ColumnText.HasMoreText(status)
End Sub
End Class
This could easily be expanded to Shadow the other functions in ColumnText.
Thanks cjbarth, this was useful. I have done a similar version in C# if this helps anyone.
public class ColumnTextSimple : ColumnText
{
Document document;
Rectangle[] columns;
public ColumnTextSimple(PdfContentByte writer, Document workingDocument, int columnCount, float columnSpacing) : base(writer)
{
document = workingDocument;
CalculateColumns(columnCount, columnSpacing);
}
private void CalculateColumns(int columnCount, float columnSpacing)
{
float columnWidth;
float columnHeight;
columnWidth = ((document.PageSize.Width - document.LeftMargin - document.RightMargin) - (columnSpacing * (columnCount - 1))) / columnCount;
columnHeight = (document.PageSize.Height - document.TopMargin - document.BottomMargin);
columns = new Rectangle[columnCount];
for (int c = 0; c < columnCount; c++)
{
float llx = ((columnWidth + columnSpacing) * c) + document.LeftMargin;
float lly = document.BottomMargin;
float urx = llx + columnWidth;
float ury = columnHeight;
columns[c] = new Rectangle(llx, lly, urx, ury);
}
}
public void produceColumns()
{
int column = 0;
int status = 0;
while (ColumnText.HasMoreText(status)) // same as while(status != 1)
{
if (column >= columns.Length)
{
column = 0;
document.NewPage();
}
this.SetSimpleColumn(columns[column]);
status = this.Go();
column++;
}
}
}
I have added a separate method for ProduceColumns as this allows AddElement to be called more than once. ProduceColumns must then be called once all content has been added.