iTextSharp - Page Layout with Columns

I have now covered many of the basics involved in generating PDF files from ASP.NET using iTextSharp in the series of articles listed below. This article will look at additional ways to provide formatting to documents through the use of columns.

Create PDFs in ASP.NET - getting started with iTextSharp
iTextSharp - Working with Fonts
iTextSharp - Adding Text with Chunks, Phrases and Paragraphs
Lists with iTextSharp
iTextSharp - Links and Bookmarks
iTextSharp - Introducing Tables
iTextSharp - Working with Images
iTextSharp - Drawing shapes and Graphics

Most often, when working with columns you will want to add multiple columns for text layout, similar to a newspaper. iTextSharp has a MultiColumnText object which makes this pretty simple. All you need to tell it is where the leftmost column should start from along the X-axis, where that rightmost column should end, what space you would like between the columns and how many columns you want. The following adds 2 columns to a page, and then adds a paragraph 8 times:

  

string pdfpath = Server.MapPath("PDFs");

string imagepath = Server.MapPath("Columns");

Document doc = new Document();

try

{

    PdfWriter.GetInstance(doc, new FileStream(pdfpath + "/Columns.pdf", FileMode.Create));

   doc.Open();

    Paragraph heading = new Paragraph("Page Heading", new Font(Font.HELVETICA, 28f, Font.BOLD));

   heading.SpacingAfter = 18f;

   doc.Add(heading);

    string text = @"Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Suspendisse blandit blandit turpis. Nam in lectus ut dolor consectetuer bibendum. Morbi neque ipsum, laoreet id; dignissim et, viverra id, mauris. Nulla mauris elit, consectetuer sit amet, accumsan eget, congue ac, libero. Vivamus suscipit. Nunc dignissim consectetuer lectus. Fusce elit nisi; commodo non, facilisis quis, hendrerit eu, dolor? Suspendisse eleifend nisi ut magna. Phasellus id lectus! Vivamus laoreet enim et dolor. Integer arcu mauris, ultricies vel, porta quis, venenatis at, libero. Donec nibh est, adipiscing et, ullamcorper vitae, placerat at, diam. Integer ac turpis vel ligula rutrum auctor! Morbi egestas erat sit amet diam. Ut ut ipsum? Aliquam non sem. Nulla risus eros, mollis quis, blandit ut; luctus eget, urna. Vestibulum vestibulum dapibus erat. Proin egestas leo a metus?";

    MultiColumnText columns = new MultiColumnText();

    //float left, float right, float gutterwidth, int numcolumns

   columns.AddRegularColumns(36f, doc.PageSize.Width-36f, 24f, 2);

    Paragraph para = new Paragraph(text, new Font(Font.HELVETICA, 8f));

   para.SpacingAfter = 9f;

   para.Alignment = Element.ALIGN_JUSTIFIED;

    for (int i = 0; i < 8; i++)

    {

       columns.AddElement(para);

    }

    

   doc.Add(columns);

    

}

catch (Exception ex)

{

   //Log(ex.Message);

}

finally

{

   doc.Close();

}

  

 The result shows how the text just flowed from one column to another once the first column was filled.

AddRegularColumns() will set each column at exactly the same width. AddSimpleColumn() allows you to specify the width of individual columns, which means that you can work with irregularly sized columns:

  

columns.AddSimpleColumn(36f, 170f);

columns.AddSimpleColumn(194f, doc.PageSize.Width - 36f);

  

The code above replaces the AddRegularColumn() call in the preceding block and creates two separate column objects. The first starts at the left margin (36 points in from the left edge) and is 2 inches wide (170 - 36 = 144 points). The second column starts 24 points (1/3 of an inch) further over and butts up against the right hand margin.

Adding other elements such as images and tables is straightforward:

 

string pdfpath = Server.MapPath("PDFs");

string imagepath = Server.MapPath("Images");

Document doc = new Document();

try

{

  PdfWriter.GetInstance(doc, new FileStream(pdfpath + "/Columns.pdf", FileMode.Create));

  doc.Open();

  Paragraph heading = new Paragraph("Page Heading", new Font(Font.HELVETICA, 28f, Font.BOLD));

  heading.SpacingAfter = 18f;

  doc.Add(heading);

  string text = @"Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Suspendisse blandit blandit turpis. Nam in lectus ut dolor consectetuer bibendum. Morbi neque ipsum, laoreet id; dignissim et, viverra id, mauris. Nulla mauris elit, consectetuer sit amet, accumsan eget, congue ac, libero. Vivamus suscipit. Nunc dignissim consectetuer lectus. Fusce elit nisi; commodo non, facilisis quis, hendrerit eu, dolor? Suspendisse eleifend nisi ut magna. Phasellus id lectus! Vivamus laoreet enim et dolor. Integer arcu mauris, ultricies vel, porta quis, venenatis at, libero. Donec nibh est, adipiscing et, ullamcorper vitae, placerat at, diam. Integer ac turpis vel ligula rutrum auctor! Morbi egestas erat sit amet diam. Ut ut ipsum? Aliquam non sem. Nulla risus eros, mollis quis, blandit ut; luctus eget, urna. Vestibulum vestibulum dapibus erat. Proin egestas leo a metus?";

  MultiColumnText columns = new MultiColumnText();

  columns.AddSimpleColumn(36f, 336f);

  columns.AddSimpleColumn(360f, doc.PageSize.Width - 36f);

  

  Paragraph para = new Paragraph(text, new Font(Font.HELVETICA, 8f));

  para.SpacingAfter = 9f;

  para.Alignment = Element.ALIGN_JUSTIFIED;

  

  PdfPTable table = new PdfPTable(3);

  float[] widths = new float[] { 1f, 1f, 1f };

  table.TotalWidth = 300f;

  table.LockedWidth = true;

  table.SetWidths(widths);

  PdfPCell cell = new PdfPCell(new Phrase("Header spanning 3 columns"));

  cell.Colspan = 3;

  cell.HorizontalAlignment = 0;

  table.AddCell(cell);

  table.AddCell("Col 1 Row 1");

  table.AddCell("Col 2 Row 1");

  table.AddCell("Col 3 Row 1");

  table.AddCell("Col 1 Row 2");

  table.AddCell("Col 2 Row 2");

  table.AddCell("Col 3 Row 2");

  

  Image jpg = Image.GetInstance(imagepath + "/Sunset.jpg");

  jpg.ScaleToFit(300f, 300f);

  jpg.SpacingAfter = 12f;

  jpg.SpacingBefore = 12f;

  

  columns.AddElement(para);

  columns.AddElement(table);

  columns.AddElement(jpg);

  columns.AddElement(para);

  columns.AddElement(para);

  columns.AddElement(para);

  columns.AddElement(para);

  doc.Add(columns);

  

}

catch (Exception ex)

{

  //Log(ex.Message);

}

finally

{

  doc.Close();

}

 

 The example above takes a table and images from previous tutorials, and adds them together with the paragraph to the columns. the first column is 300 points wide, and the second one takes up the remainder of the page width.

MultiColumnText is great for creating columns without too much code, but it lacks an element of control. ColumnText provides much more, but requires more code. The following sample shows ColumnText being used to create irregular columns, in that the first one has chunk taken from the top left corner to accommodate an image which is shown here in the resulting PDF:

 

string pdfpath = Server.MapPath("PDFs");

string imagepath = Server.MapPath("Images");

FontFactory.RegisterDirectory("C:\\WINDOWS\\Fonts");

 

Document doc = new Document();

Font font1 = new Font(FontFactory.GetFont("adobe garamond pro", 36f, Color.GRAY));

Font font2 = new Font(Font.TIMES_ROMAN, 9f);

doc.SetMargins(45f, 45f, 60f, 60f);

try

{

  FileStream output = new FileStream(pdfpath + "/IrregularColumns.pdf", FileMode.Create);

  PdfWriter writer = PdfWriter.GetInstance(doc, output);

  doc.Open();

  PdfContentByte cb = writer.DirectContent;
  ColumnText ct = new ColumnText(cb);

  ct.Alignment = Element.ALIGN_JUSTIFIED;

 

  Paragraph heading = new Paragraph("Chapter 1", font1);

  heading.Leading = 40f;

  doc.Add(heading);

  Image L = Image.GetInstance(imagepath + "/l.gif");

  L.SetAbsolutePosition(doc.Left, doc.Top - 180);

  doc.Add(L);

 

  ct.AddText(new Phrase("orem ipsum dolor sit amet, consectetuer adipiscing elit. Suspendisse blandit blandit turpis. Nam in lectus ut dolor consectetuer bibendum. Morbi neque ipsum, laoreet id; dignissim et, viverra id, mauris. Nulla mauris elit, consectetuer sit amet, accumsan eget, congue ac, libero. Vivamus suscipit. Nunc dignissim consectetuer lectus. Fusce elit nisi; commodo non, facilisis quis, hendrerit eu, dolor? Suspendisse eleifend nisi ut magna. Phasellus id lectus! Vivamus laoreet enim et dolor. Integer arcu mauris, ultricies vel, porta quis, venenatis at, libero. Donec nibh est, adipiscing et, ullamcorper vitae, placerat at, diam. Integer ac turpis vel ligula rutrum auctor! Morbi egestas erat sit amet diam. Ut ut ipsum? Aliquam non sem. Nulla risus eros, mollis quis, blandit ut; luctus eget, urna. Vestibulum vestibulum dapibus erat. Proin egestas leo a metus?\n\n", font2));

  ct.AddText(new Phrase("Vivamus enim nisi, mollis in, sodales vel, convallis a, augue? Proin non enim. Nullam elementum euismod erat. Aliquam malesuada eleifend quam! Nulla facilisi. Aenean ut turpis ac est tempor malesuada. Maecenas scelerisque orci sit amet augue laoreet tempus. Duis interdum est ut eros. Fusce dictum dignissim elit. Morbi at dolor. Fusce magna. Nulla tellus turpis, mattis ut, eleifend a, adipiscing vitae, mauris. Pellentesque mattis lobortis mi.\n\n", font2));

  ct.AddText(new Phrase("Nullam sit amet metus scelerisque diam hendrerit porttitor. Aenean pellentesque, lorem a consectetuer consectetuer, nunc metus hendrerit quam, mattis ultrices lorem tellus lacinia massa. Aliquam sit amet odio. Proin mauris. Integer dictum quam a quam accumsan lacinia. Pellentesque pulvinar feugiat eros. Suspendisse rhoncus. Sed consectetuer leo eu nisi. Suspendisse massa! Sed suscipit lacus sit amet elit! Aliquam sollicitudin condimentum turpis. Nunc ut augue! Maecenas eu eros. Morbi in urna consectetuer ipsum vehicula tristique.\n\n", font2));

  ct.AddText(new Phrase("Donec imperdiet purus vel ligula. Vestibulum tempor, odio ut scelerisque eleifend, nulla sapien laoreet dui; vel aliquam arcu libero eu ante. Curabitur rutrum tristique mi. Sed lobortis iaculis arcu. Suspendisse mauris. Aliquam metus lacus, elementum quis, mollis non, consequat nec, tortor.\n", font2));

  ct.AddText(new Phrase("Quisque id diam. Ut egestas leo a elit. Nulla in metus. Aliquam iaculis turpis non augue. Donec a nunc? Phasellus eu eros. Nam luctus. Duis eu mi. Ut mollis. Nulla facilisi. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Aenean pede. Nulla facilisi. Vestibulum mattis adipiscing nulla. Praesent orci ante, mattis in, cursus eget, posuere sed, mauris.\n\n", font2));

  ct.AddText(new Phrase("Nulla facilisi. Nunc accumsan risus aliquet quam. Nam pellentesque! Aenean porttitor. Aenean congue ullamcorper velit. Phasellus suscipit placerat tellus. Vivamus diam odio, tempus quis, suscipit a, dictum eu; lectus. Sed vel nisl. Ut interdum urna eu nibh. Praesent vehicula, orci id venenatis ultrices, mauris urna mollis lacus, et blandit odio magna at enim. Pellentesque lorem felis, ultrices quis, gravida sed, pharetra vitae, quam. Mauris libero ipsum, pharetra a, faucibus aliquet, pellentesque in, mauris. Cras magna neque, interdum vel, varius nec; vulputate at, erat. Quisque vitae urna. Suspendisse potenti. Nulla luctus purus at turpis! Vestibulum vitae dui. Nullam odio.\n\n", font2));

  ct.AddText(new Phrase("Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Sed eget mi at sem iaculis hendrerit. Nulla facilisi. Etiam sed elit. In viverra dapibus sapien. Aliquam nisi justo, ornare non, ultricies vitae, aliquam sit amet, risus! Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Phasellus risus. Vestibulum pretium augue non mi. Sed magna. In hac habitasse platea dictumst. Quisque massa. Etiam viverra diam pharetra ante. Phasellus fringilla velit ut odio! Nam nec nulla.\n\n", font2));

  ct.AddText(new Phrase("Integer augue. Morbi orci. Sed quis nibh. Nullam ac magna id leo faucibus ornare. Vestibulum eget lectus sit amet nunc facilisis bibendum. Donec adipiscing convallis mi. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Vivamus enim. Mauris ligula lorem, pellentesque quis, semper sed, tristique sit amet, justo. Suspendisse potenti. Proin vitae enim. Morbi et nisi sit amet sapien ve.\n\n", font2));

 

  float gutter = 15f;

  float colwidth = (doc.Right - doc.Left - gutter) / 2;

  float[] left = { doc.Left + 90f , doc.Top - 80f,

                  doc.Left + 90f, doc.Top - 170f,

                  doc.Left, doc.Top - 170f,

                  doc.Left , doc.Bottom };

 

  float[] right = { doc.Left + colwidth, doc.Top - 80f,

                    doc.Left + colwidth, doc.Bottom };

 

  float[] left2 = { doc.Right - colwidth, doc.Top - 80f,

                    doc.Right - colwidth, doc.Bottom };

 

  float[] right2 = {doc.Right, doc.Top - 80f,

                    doc.Right, doc.Bottom };

 

  int status = 0;

  int i = 0;

  //Checks the value of status to determine if there is more text

  //If there is, status is 2, which is the value of NO_MORE_COLUMN

  while (ColumnText.HasMoreText(status))

  {

    if (i == 0)

    {

      //Writing the first column

      ct.SetColumns(left, right);

      i++;

    }

    else

    {

      //write the second column

      ct.SetColumns(left2, right2);

    }

    //Needs to be here to prevent app from hanging

    ct.YLine = doc.Top - 80f;

    //Commit the content of the ColumnText to the document

    //ColumnText.Go() returns NO_MORE_TEXT (1) and/or NO_MORE_COLUMN (2)

    //In other words, it fills the column until it has either run out of column, or text, or both

    status = ct.Go();

  }

}

catch (Exception ex)

{

  //Log(ex.Message);

}

finally

{

  doc.Close();

}

 

ColumnText requires that you need a PDFContentByte object, just as with graphics and drawing. This is nearly always the case when you want to place stuff at fixed positions. The code above starts by defining a document object, and then creating some fonts to work with, as well as setting the margins of the document (which needs to be done before the document is opened to take effect). A PdfWriter object is created so that the PDFContentByte object can be instantiated from its DirectContent property. Then a ColumnText object is created using the PDFContentByte. A heading is added, followed by the illustrated capital "L", which starts the Chapter. This is followed by a number of Phrases, set out as paragraphs.

So, now the ColumnText object has its text content, but nowhere to put it yet, so the two columns are created. The first array of floats defines the irregular shape of the left hand side fo the first column which has to the top corner chopped out of it to make way for the image. The second array defines the straight right hand edge. The third and fourth arrays take care of the regularly shaped second column. Two int variables are created to hold the value of the status of the column with regard to whether there is any more text to be added (status), and then the number of the column that is currently being worked on (i).

ColumnText.HasMoreText(status) is a convenience method that takes an int as a parameter. It examines the value of the parameter to see if there is any more text to be added and returns true if there is. The value that returns false is 1. It is initialised at 0 to start, so HasMoreText returns true. Since i is also 0, the code executes to add the first column, then the YLine is set to the top of the column. This must be done, otherwise the application will just spin until the execution timeout occurs. Finally, the ColumnText.Go() method is called. This actually commits the contents of the ColumnText to the document, and also returns a value to the variable status. It returns either NO_MORE_TEXT, which is 1, and/or NO_MORE_COLUMN (2). If NO_MORE_TEXT is returned, everything has been written, otherwise the second column is created and the text added to that. Once ColumnText.HasMoreText returns false, the page is written.

Building on this, if the amount of text spans more than one page, the routine can be rewritten to call Document.NewPage(), and if Go() is called after each Phrase is added to the ColumnText object, it is possible to obtain the current Y position on the page to establish how much room there is left. this enables you to manage widows and orphans - single lines of text that are belong to a paragraph that appear at the end of the preceding page or column (orphans) or appear at the top of a new page or column (widows). It is also possible to pass the parameter "false" into Go(), which does not commit the text to the column, but still allows you to obtain the position as if it was to allow you much finer control over layout.