Anda di halaman 1dari 14

Creating PDF documents with iTextSharp

By Thomas Michael Koch, 2 Nov 2011 Editorial Note This article appears in the Third Party Product Reviews section. Articles in this section are for the members only and must not be used to promote or advertise products in any way, shape or form. Please report any spam or advertising.

Download demo project - 1.65 MB

Introduction
This article is about using iText to generate PDF files using a combination of images and dynamic text written on select locations on the image. The example project uses an ASP.NET MVC3 application to demonstrate the code but it should not be difficult to adapt it to your own needs.

Background
Me and some friends have been running a hobby web-site called malleus.dk dedicated to two role-playing games called Warhammer Fantasy and Dark Heresy. One of the applications on our site is a "character generator" where users can build, configure, and maintain their role-playing characters. We wanted to expand this tool with a print functionality allowing our users to have their characters printed to a nice looking character sheet. We decided that we really needed to generate a PDF document. PDF documents are supported in all browsers and seems to be the de-facto standard for documents on the web. There are a lot of PDF components available on the net, but we quickly settled on iText as it is a very mature product. iText comes with a free option under the GNU Affero Public License, or with a commercial option which gives you more freedom. I encourage you to check out the details at http://itextpdf.com.

Using the example solution


The example solution contains two projects - a Class Library and an ASP.NET MVC application. iText is used from the Class Library, and the web application is used for testing the Class Library. The example creates a "diploma" for a bicycle race. You can enter name, date, the name of the race, and the distance. The web application will produce a diploma with the entered text on top. The basic flow of the example application is illustrated with images in figure 1, 2, and 3:

Figure 1 - The background image

Figure 2 - Entering data in example app Clicking "Generate" will create a new PDF document:

s Figure 3 - The resulting diploma From here on, it is only a matter of adding more artwork, tweaking texts, fonts, etc. Even though the diploma application is very basic, it contains all the programmatic elements required for producing output that looks like figure 4. The artistic skill that goes into images and layout is a job for a designer.

Figure 4 - Character sheet in PDF generated using iText

Installing and setting up


Go to http://itextpdf.com/download.php and click "Download iTextSharp". In your Visual Studio project, you need to reference the itextsharp.dll that you just downloaded.

Document generation strategy


When you open up iText and begin examining the API, you will notice that it is capable of writing text, drawing figures, inserting images, creating PDF forms, etc.. For our task, we decided to use a combination of image fragments and text. The bottom layer will contain all the image fragments and the top layer will contain the text.

Creating a PDF document


When you create PDF documents in iText, the top-level abstraction is the Document class. It provides various document level information such as title, page count, etc.:
public void Create (Stream output) { Document document = new Document(); PdfWriter writer = PdfWriter.GetInstance(document, output); document.Open();

Collapse | Copy Code

The output stream is just a MemoryStream which is returned as a FileContentResult: Collapse | Copy Code
[HttpPost] public FileContentResult Generate(GenerateModel m) { DiplomaPrinter printer = new DiplomaPrinter(m.Name, m.Distance, m.Date, m.RaceName, m.ShowRulers); MemoryStream memoryStream = new MemoryStream(); printer.Create(memoryStream); return File(memoryStream.GetBuffer(), "application/pdf", "diploma.pdf"); }

When this controller action is invoked, it will send the contents of the MemoryStream back to the client as a PDF file called "diploma.pdf". To actually fill the document with content, you need to learn to use the PdfWriter, PdfReader, and PdfTemplate classes.

Bottom layer
The bottom layer of the page is filled with other PDF fragments and images. Figure 2 contains our background image. To load these elements into the document, you do the following: 1. Load the PDF fragment using a PdfReader. 2. Use the reader to create a PdfTemplate instance. 3. Add the PdfTemplate to your current Document instance.
PdfReader readerBicycle = new PdfReader(Resources.GetBicycle()); PdfTemplate background = writer.GetImportedPage(readerBicycle, 1);

Collapse | Copy Code

Create a new page in the document and add the PdfTemplate instance to it:
document.NewPage(); PdfContentByte pcb = writer.DirectContentUnder; pcb.AddTemplate(background, 0, 0); Resource.GetBicycle() simply returns a Stream instance to a resource embedded within the assembly. The document.NewPage() generates a new page in the Document instance and the writer.DirectContentUnder property returns a PdfContentByte instance for the bottom layer of this new page. The pcb.AddTemplate()

Collapse | Copy Code

call to

invocation adds the loaded PDF fragment to the document and positions it on the bottom left corner - coordinates x = 0 and y = 0. At first it seems odd that you have to use the PdfWriter instance to transform the contents of the PdfReader into a PdfTemplate. As Bruno Lowagie - the creator of iText - explained to me, this is done to ensure that shared objects are reused. For instance, if you embed your own fonts in the document, the best option is to reuse this font whenever you write some text. For more specific details, I encourage you to get a hold of the book "iText in Action - 2nd edition".

Top layer
The top layer is where I put the text. As with everything else in iText, you have several options. You can add entire paragraph objects or print text at specific points. Since I need to print on top of an image/PDF fragment, I went with the last option. Collapse | Copy Code
PdfContentByte pcb = writer.DirectContent; pcb.BeginText(); SetFont36(); PrintTextCentered(_raceName, 280, 680); PrintTextCentered(_name, 280, 190); SetFont18(); PrintTextCentered(_date, 280, 640); PrintTextCentered("has completed a distance of " + _distance, 280, 160); _pcb.EndText(); writer.Flush();

The numbers in this code fragment represent a position in a coordinate system. Setting the font and size: Collapse | Copy Code
private static readonly BaseFont font = BaseFont.CreateFont(BaseFont.HELVETICA, BaseFont.CP1252, false); private void SetFont36() { _pcb.SetFontAndSize(font, 36); }

Invoking this method will set the font to Helvetica with size 36. This will affect the invocations to the following method: Collapse | Copy Code
private void PrintTextCentered(string text, int x, int y) { _pcb.ShowTextAligned(PdfContentByte.ALIGN_CENTER, text, x, y, 0); }

The _name, _distance etc. fields are injected into the DiplomaPrinter class via the constructor.

Closing the document


To end the document, you should flush your writer and call Close on the document:
writer.Flush(); document.Close();

Collapse | Copy Code

This has the effect of pushing the PDF document down to the output stream that you used to create the document with in the first place.

Tricks
When generating documents this way, you often need to adjust the position of text and images. To help with this, I created two methods for adding a ruler to the generated PDF document. If you check the "add rulers" box in the example application, you should see something like the following:

Figure 5 - Diploma with rulers Having these rulers makes it much easier to find the positions of text and images. You can also play around with the code-page being used by iText when generating text. This is done by changing the value of the encoding parameter of the BaseFont.CreateFont() method. The example uses the value "Cp1252"

which is for Western Europe. Reading the source code for iText, I can see that it also supports the values "Cp1250" (Central- and Eastern Europe) and "Cp1257" (the three Baltic states) but I have yet to play around with these.

Conclusion
iText is a mature piece of software with tons of possibilities. I have only scratched the surface of what is possible. A quick scan of the API reveals that you can do PDF forms, drawing, encryption, etc. I once tried to read the PDF specification and found that it is one complex beast. I very much prefer delegating the work to an API such as iText.

History

1 November 2011 - First edition.

License
This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

TextSharp - Introducing Tables


Tables will probably be one of the most used elements in PDFs generated from ASP.NET applications to provide the structure for documents such as orders and invoices. This overview is not an exhaustive examination of tables, but provides an introduction to working with them through iTextSharp, and builds on the previous articles in this iTextSharp series:

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 Working with tables using iTextSharp is not that difficult, especially as many of the property names are so similar or identical to their counterparts within CSS and HTML. There is more than one class in iTextSharp that can be used to create tables, so for the avoidance of doubt, I will be using the PdfPTable class, which is designed specifically for use within PDF documents. At its most simplest, here is how to create a table and add it to a document:
PdfPTable table = new PdfPTable(3); PdfPCell cell = new PdfPCell(new Phrase("Header spanning 3 columns")); cell.Colspan = 3; cell.HorizontalAlignment = 1; //0=Left, 1=Centre, 2=Right 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"); doc.Add(table);

The PdfPTable object is instantiated as a three column table - the integer 3 being passed into the constructor. Cells can be added in a number of ways. The first cell is set as a PdfPCell object, which can take a Phrase object in one of its 7 constructors. The Colspan is set to 3, which means that the cell will occupy the full width of the table, just as in HTML. The horizontal position of the text within the cell is set using one of three possible values. All possible values are shown as a comment. Following that, two rows of cells are added using the AddCell() method and the table is finally committed to the currently open document.

The following effort queries a database, and presents the resulting data in a table. It also shows some other options that can be used for styling and presenting the table:
PdfPTable table = new PdfPTable(2); //actual width of table in points table.TotalWidth = 216f; //fix the absolute width of the table table.LockedWidth = true; //relative col widths in proportions - 1/3 and 2/3 float[] widths = new float[] { 1f, 2f }; table.SetWidths(widths); table.HorizontalAlignment = 0; //leave a gap before and after the table table.SpacingBefore = 20f; table.SpacingAfter = 30f; PdfPCell cell = new PdfPCell(new Phrase("Products")); cell.Colspan = 2; cell.Border = 0; cell.HorizontalAlignment = 1; table.AddCell(cell); string connect = "Server=.\\SQLEXPRESS;Database=Northwind;Trusted_Connection=True;"; using (SqlConnection conn = new SqlConnection(connect)) { string query = "SELECT ProductID, ProductName FROM Products"; SqlCommand cmd = new SqlCommand(query, conn);

try { conn.Open(); using (SqlDataReader rdr = cmd.ExecuteReader()) { while (rdr.Read()) { table.AddCell(rdr[0].ToString()); table.AddCell(rdr[1].ToString()); } } } catch(Exception ex) { Response.Write(ex.Message); } doc.Add(table); }

The table is initally created with 2 columns. Then the width of the table is set in points, and fixed. The width of the columns themselves are set relatively at one third and two thirds of the total table width. To set it a one fifth and 4 fifths, you would pass in 1f and 4f respectively. You can slo set the absolute widths by passing in values that together total the table width, for example:
float[] widths = new float[] { 100f, 116f };

A gap is created before and after the table by setting the SpacingBefore and SpacingAfter properties. This is useful if you have more than one table following on from another, as the default behaviour is to pin subsequent tables to the previous one, as in MS Word, where a quick tap of the Enter key has the same spacing effect. The border is removed from the first cell, which is treated as a header by setting the colspan to equal the number of columns in the table, and the text in the cell is centre-aligned, using the same value as that used for aligning the table in the document. Then the database is queried and the data returned in a SqlDataReader. As it is read, the data is consigned to cells which are added to the table:

The following snippet illustrates some of the options for formatting cells. As you will see, the creators of iTextSharp have followed the CSS names for properties as much as possible to make working with styling syntax as easy as possible (if you know your CSS, of course...)
PdfPTable table = new PdfPTable(3); table.AddCell("Cell 1"); PdfPCell cell = new PdfPCell(new Phrase("Cell 2", new Font(Font.HELVETICA, 8f, Font.NORMAL, Color.YELLOW))); cell.BackgroundColor = new Color(0, 150, 0); cell.BorderColor = new Color(255,242,0); cell.Border = Rectangle.BOTTOM_BORDER | Rectangle.TOP_BORDER; cell.BorderWidthBottom = 3f; cell.BorderWidthTop = 3f; cell.PaddingBottom = 10f; cell.PaddingLeft = 20f; cell.PaddingTop = 4f; table.AddCell(cell); table.AddCell("Cell 3"); doc.Add(table);

We have seen on a number of occasions how a cell can stretch horizontally through the use of the Colspan property. But what about vertically? In HTML you would use the Rowspan property, but there is no equivalent in iTextSharp. So the answer is nested tables. The following code creates a four column table, with the bottom right cell stretching horizontally across three columns, and vertically by three rows. Well, that's the final appearance, but what actually happens is that a single column, three row table is nested within the bottom left cell. The cell that the table is nested within has its padding removed so that the table occupies all of the available space within it.

PdfPTable table = new PdfPTable(4); table.TotalWidth = 400f; table.LockedWidth = true; PdfPCell header = new PdfPCell(new Phrase("Header")); header.Colspan = 4; table.AddCell(header); table.AddCell("Cell 1"); table.AddCell("Cell 2"); table.AddCell("Cell 3"); table.AddCell("Cell 4"); PdfPTable nested = new PdfPTable(1); nested.AddCell("Nested Row 1"); nested.AddCell("Nested Row 2"); nested.AddCell("Nested Row 3"); PdfPCell nesthousing = new PdfPCell(nested); nesthousing.Padding = 0f; table.AddCell(nesthousing); PdfPCell bottom = new PdfPCell(new Phrase("bottom")); bottom.Colspan = 3; table.AddCell(bottom); doc.Add(table);

Finally, in this look at tables, we see how the text content of a cell can be rotated (which is rather natty).
PdfPTable table = new PdfPTable(3); table.TotalWidth = 144f; table.LockedWidth = true; table.HorizontalAlignment = 0; PdfPCell left = new PdfPCell(new Paragraph("Rotated")); left.Rotation = 90; table.AddCell(left); PdfPCell middle = new PdfPCell(new Paragraph("Rotated")); middle.Rotation = -90; table.AddCell(middle); table.AddCell("Not Rotated"); doc.Add(table);

The Rotation property must be set to multiples of 90, or an error occurs. The middle cell is set to -90, but 270 would have had the same effect. The default direction that the content is rotated is anti-clockwise. The result is below:

There is an awful lot more to working with tables in iTextSharp, and I will cover additional functionality in future articles. In the meantime, Intellisense or the Object Browser within Visual Studio reveals a lot of methods and properties that are worth experimenting with to see their results.

Anda mungkin juga menyukai