You are on page 1of 40

Create PDFs in ASP.

NET / C#

Michle Johl

Getting started with iTextSharp

The .NET framework does not contain any native way to work with PDF files. So, if you want to generate or work with PDF files as part of your ASP.NET web application, you will have to rely on one of the many third party components that are available. Google will help you to find one that fits your budget, as well as a range of open-source free components. One of the free components is iTextSharp, which is a port of a well known Java utility, iText. The main problem with iTextSharp is that it lacks documentation. There are some basic tutorials available, but most programmers have to resort to trying to wrestle with the documentation provided for the Java version - iText - to get going with the component, or you may want to purchase the book iText In Action. However, this only provides guidance in Java. Many of the code samples are transferable to C# without a lot of modification, but if you are relatively new to C#, you may frequently become frustrated with undocumented or inexplicable differences in classes and method names between the two versions. As a final resort, you can always use Reflector to pick the dll apart and examine its innards. So, as part of a series of How To articles, here's how to get started using iTextSharp with code samples in C#. First thing to do is to download the component from here. You will be presented with a .zip file containing itextsharp.dll. Unzip this, and save it somewhere on your machine. Now create a new Web Site from within Visual Studio or Visual Web Developer. Make sure that you have added the Bin folder from the options in the Add ASP.NET Folder option. Right-click on the Bin folder and select Add Reference. The dialogue box should appear. Choose the Browse tab, and locate the dll you saved earlier.

Then select it and click "OK". That's it. The dll will be copied to your Bin directory, and is now available to the web site or project.

I have also added a folder called PDFs in which I plan to store my generated files. To avoid typing full references to the dll, it is best to add a couple of using statements to the default ones in your code-behind: using iTextSharp.text; using iTextSharp.text.pdf; You will also want to reference System.IO, as you will be creating, opening and closing files, and classes in this namespace are required. The principal object within iTextSharp is the Document object. You need to create an instance of this to be able to work with your PDF in memory. So, the first thing to do is to instantiate one: var doc1 = new Document(); This creates a PDF document object in memory with the default settings. The size of the document by default is A4 (which measures 210mm x 297mm, or 8.26 inches x 11.69 inches). Margins are set at half an inch all round. The next thing to do is to commit the document to disk. The iTextSharp.text.pdf.PdfWriter class is required for this: //use a variable to let my code fit across the page... string path = Server.MapPath("PDFs"); PdfWriter.GetInstance(doc1, new FileStream(path + "/Doc1.pdf", FileMode.Create)); Now to begin actually working with the document, open it, and add a new paragraph, then close it: doc1.Open(); doc1.Add(new Paragraph("My first PDF")); doc1.Close(); That's it! If you refresh the PDFs folder in your web site, you will see that a new item - Doc1.pdf has been added, and opening it will reveal your success. It may be, however, that you don't want to always create a PDF with the default size and margins, so iTextSharp provides ways for you to customise these settings. There are 2 further constructors to the Document object: public Document(iTextSharp.text.Rectangle pageSize); public Document(iTextSharp.text.Rectangle pageSize, float, float, float, float); The first one can be used like this: var doc = new Document(PageSize.A5); The PageSize class contains a number of Rectangle objects representing the most common paper sizes from A0 to A10, B0 to B10, LEGAL, LEDGER, LETTER, POSTCARD, TABLOID and so on. If you want to apply a custom size that isn't available within the PageSize class, you define your own Rectangle object, set its properties and pass that into the constructor as an argument: var doc = new Document(new Rectangle(100f, 300f)); PdfWriter.GetInstance(doc, new FileStream(path + "/Doc2.pdf", FileMode.Create)); doc.Open(); doc.Add(new Paragraph("This is a custom size")); doc.Close(); In this case, a PDF document was created with the width being 100 points, and the height set at 300 points. There are 72 points to an inch, so this particular document isn't very large. It's in fact 1.39 inches x 4.17 inches. (You will probably find that as well as iTextSharp.dll, a calculator will be very handy....). The second constructor which takes a Rectangle object and a series of 4 float values allows you to set your custom margins through the floats. Again, these values are measured in points. The default half inch margins are 36 points. If you use the PageSize class constructor, or a Rectangle directly, you can also set the background colour of the document. This can be done using RGB colour values, or CMYK (Cyan - a kind of blue, Magenta - a pinkish red, Yellow and "Key", or black). It used to be that if you wanted to prepare a PDF for printing by a professional lithographic printer, you had to ensure that all colours were CMYK, but with greater adoption of digital printing by printing companies, RGB is becoming more acceptable. Certainly, for display on the web, RGB is preferred. To set the background colour, we use the BackgroundColorproperty of the Rectangle object:

r.BackgroundColor = new CMYKColor(25, 90, 25, 0); r.BackgroundColor = new Color(191, 64, 124); Both of the above result in a rather fetching pinkish colour for the document.

Working with Fonts

Following on from my introduction to iTextSharp, the free PDF utility that lets you work with PDF files within ASP.NET, this article looks at working with fonts in PDF documents that you create. If you haven't read the first article in this series, I recommend that you do so now. iTextSharp has built-in support for the 14 Type 1 fonts: Courier, Courier Bold, Courier Italic, Courier Bold and Italic, Helvetica, Helvetica Bold, Helvetica Italic, Helvetica Bold and Italic, Times Roman, Times Roman Bold, Times Roman Italic, Times Roman Bold and Italic, Symbol, ZapfDingBats. Helvetica translates more or less to Windows Arial font, while Times Roman has an equivalent in Times New Roman. The default font is Helvetica, 12pt, black in the style typically known as Normal. There are three principal ways to set the font to work with: one is to use the BaseFont.CreateFont() method, the second is to use the FontFactory.GetFont() method , and the third is to instantiate a new Font object. BaseFont.CreateFont() is a lot more limited, and only sets up the definition of a font. new Font() allows for propogation of font styles from one font to the next. FontFactory.GetFont() returns a valid and new Font object that you can work with directly. It also offers 14 overloaded constructors which gives you a lot more options to work with. For that reason, you are most likely going to use the FontFactory.GetFont() method. But we will get BaseFont.CreateFont() out of the way first. BaseFont bfTimes = BaseFont.CreateFont(BaseFont.TIMES_ROMAN, BaseFont.CP1252, false); Font times = new Font(bfTimes, 12, Font.ITALIC, Color.RED); The above lines create a BaseFont object and uses the built-in constant values to set the font family and encoding. It also specifies false for embedding the font within the PDF document. this decreases the overall size of the document, but should be set to true if you are using fonts that are unlikely to be on the user's system, or if you plan to get the PDF printed professionally. A new Font object is created using the BaseFont object, and further setting the font size in points, the style and the colour - again, using iTextSharp's constants for these values. Now the font is put to use in a paragraph: string path = Server.MapPath("PDFs"); Document doc = new Document(); PdfWriter.GetInstance(doc, new FileStream(path + "/Font.pdf", FileMode.Create)); doc.Open(); doc.Add(new Paragraph("This is a Red Font Test using Times Roman", times)); doc.Close();

And the result (if all goes well) is as follows:

Now to the FontFactory.GetFont() method. This method has 14 (count 'em!) overloads that allow you to specify anything from font family, size, colour, style, embedding, encoding and caching in pretty much any combination you like. Each time that you call FontFactory.GetFont(), a new Font object is created. This method will work directly with all fonts registered by iTextSharp, which includes all fonts found in the Windows default font directory. On Win XP Pro, that's usually C:/WINDOWS/Fonts. If you want a list of all the registered fonts, the FontFactory.RegisteredFonts collection holds them. This can be useful if you want to find the exact name of each font. int totalfonts = FontFactory.RegisterDirectory("C:\\WINDOWS\\Fonts"); StringBuilder sb = new StringBuilder(); foreach (string fontname in FontFactory.RegisteredFonts) {

sb.Append(fontname + "\n"); } doc.Add(new Paragraph("All Fonts:\n" + sb.ToString())); Here's a variety of ways to use the GetFont() method: Font arial = FontFactory.GetFont("Arial", 28, Color.GRAY); Font verdana = FontFactory.GetFont("Verdana", 16, Font.BOLDITALIC, new Color(125, 88, 15)); Font palatino = FontFactory.GetFont( "palatino linotype italique", BaseFont.CP1252, BaseFont.EMBEDDED, 10, Font.ITALIC, Color.GREEN ); Font smallfont = FontFactory.GetFont("Arial", 7); Font x = FontFactory.GetFont("nina fett"); x.Size = 10; x.SetStyle("Italic"); x.SetColor(100, 50, 200); As you can see, some of them use the iTextSharp Color object to set the colour using a constant, while others use the SetColor() method and pass in RGB values or create a new Color object passing in RGB values. Generally, they use a constant value for the font style, but you can pass in an int representing one of the values, or use the SetStyle() method passing in a string. There are also varying numbers of parameters passed, which ilustrates some of the different overloads available. Intellisense, Code Complete and the Object Browser will reveal the full panoply of options.

Registering Fonts
You may have a situation where you cannot install a font you want to use in the default font directory on the web server, so you have to register it explicitly with iTextSharp. string fontpath = Server.MapPath("."); BaseFont customfont = BaseFont.CreateFont(fontpath + "myspecial.ttf", BaseFont.CP1252, BaseFont.EMBEDDED); Font font = new Font(customfont, 12); string s = "My expensive custom font."; doc.Add(new Paragraph(s, font)); You may notice that the above example is embedded in the PDF file (BaseFont.EMBEDDED), as your expensive font is unlikely to exist on users' operating systems.

Adding Text with Chunks, Phrases and Paragraphs

This is the third in a series of articles that looks at using the open source component, iTextSharp from within ASP.NET to generate PDFs. Just as HTML and ASP.NET provide containers for varying ampounts of textual content, iTextSharp offers the Chunk, Phrase and Paragraph classes. Before going on, if you would like to read earlier articles, they are:

Create PDFs in ASP.NET - getting started with iTextSharp iTextSharp - Working with Fonts

Chunks
A Chunk is the smallest significant piece of text that you can work with. It's ASP.NET equivalent is the <asp:Label>. As with the Label, you need to be careful how you use Chunks. The following snippet shows how to set the text of a Chunk, then write it to the PDF document 3 times: string path = Server.MapPath("PDFs"); Rectangle r = new Rectangle(400, 300); Document doc = new Document(r); PdfWriter.GetInstance(doc, new FileStream(path + "/Blocks.pdf", FileMode.Create)); doc.Open(); Chunk c1 = new Chunk("A chunk represents an isolated string. "); for (int i = 1; i < 4; i++) { doc.Add(c1); } [Keep an eye on the following paragraph - we will come back to it] The result can be seen below, which shows the text having been written to the document but it looks a mess. Chunks have no concept of how to force a new line when the length exceeds the available width in the document. Really, all they should be used for is to change or set the style of a word or phrase inline. You can of course force a newline using "\n" or Environment.NewLine, or even Chunk.NEWLINE as part of the string you give a chunk.

The chunk has a number of methods to allow you to do this, such as setUnderLine(), setBackGround(), and setTextRise(), as well as a number of constructors that permit you to set the font and its styles. Chunk chunk = new Chunk("Setting the Font", FontFactory.GetFont("dax-black")); chunk.SetUnderline(0.5f, -1.5f);

Phrases
The Phrase is the next container in the hierarchy. A phrase is an array of chunks, and will force a newline when the length of its contents exceed the vertical margins of the document. The space between each line (actually the measurement taken between the baselines of each line, or "leading") is 1.5 times the font size. Since the default font-size was applied by iTextSharp (12pt), the code below will result in a leading of 16pt. You can set the leading or font as part of initiating a new phrase, as well as pass it a string or chunk to set its content through the phrase's various overloaded constructors. The following snippet shows how the earlier chunk is added to a phrase 3 times, and the result. Phrase phrase = new Phrase(); for (int i = 1; i < 4; i++) { phrase.Add(c1); }

Paragraphs
What we have seen so far is the very basic building blocks for text in PDFs. The object that you will use most often is a Paragraph, which is a sequence of Phrases and Chunks held together. Paragraphs derive from Phrase, so they autommatically fit text within the horizontal boundaries of the document, but they also force a new line for each paragraph (just as in any word processing application). The paragraph earlier in the Chunk section of this article is as good as any to experiment with. It has a number of sentences and some formatted inline text, so we can use that to build a paragraph from chunks and phrases: string path = Server.MapPath("PDFs"); Rectangle r = new Rectangle(400, 300); Document doc = new Document(r); try {

PdfWriter.GetInstance(doc, new FileStream(path + "/Blocks2.pdf", FileMode.Create)); doc.Open(); string text = @"The result can be seen below, which shows the text having been written to the document but it looks a mess. Chunks have no concept of how to force a new line when the length exceeds the available width in the document. Really, all they should be used for is to change or set the style of a word or phrase inline. "; text = text.Replace(Environment.NewLine, String.Empty).Replace(" ", String.Empty); Font brown = new Font(Font.COURIER, 9f, Font.NORMAL, new Color(163, 21, 21)); Font lightblue = new Font(Font.COURIER, 9f, Font.NORMAL, new Color(43, 145, 175)); Font courier = new Font(Font.COURIER, 9f); Font georgia = FontFactory.GetFont("georgia", 10f); georgia.Color = Color.GRAY; Chunk beginning = new Chunk(text, georgia); Phrase p1 = new Phrase(beginning); Chunk c1 = new Chunk("You can of course force a newline using \"", georgia); Chunk c2 = new Chunk(@"\n", brown); Chunk c3 = new Chunk("\" or ", georgia); Chunk c4 = new Chunk("Environment", lightblue); Chunk c5 = new Chunk(".NewLine", courier); Chunk c6 = new Chunk(", or even ", georgia); Chunk c7 = new Chunk("Chunk", lightblue); Chunk c8 = new Chunk(".NEWLINE", courier); Chunk c9 = new Chunk(" as part of the string you give a chunk.", georgia); Phrase p2 = new Phrase(); p2.Add(c1); p2.Add(c2); p2.Add(c3); p2.Add(c4); p2.Add(c5); p2.Add(c6); p2.Add(c7); p2.Add(c8); p2.Add(c9); Paragraph p = new Paragraph(); p.Add(p1); p.Add(p2); doc.Add(p); } catch (DocumentException dex) { throw (dex); } catch (IOException ioex) { throw (ioex); } finally { doc.Close(); } First, the result, then some notes about the code:

It didn't take long to start adding Exception handling to the code. Of course, you should always use try... catch when performing IO operations, and with iTextSharp Document objects, there is also a DocumentException object to manage. There is another source of exceptions that I found to be rather sneaky. When testing the code to generate the PDF file, I inadvertently transposed two arguments in the constructor for the font I called lightblue, in that I passed in the value Font.NORMAL before the size. This had the effect of setting the font size to 0, which is the value that the constant is set to. An exception is thrown when trying to call doc.Close(), and I have to shut down VS to release its hold on the document object. So, exception handling starts to make its appearance, so that at least the document object is released. You will also notice that the font size values are now passed in with the f suffix following them. That explicitly tells the compiler that the value is to be treated as a float, and prevents the sort of mistake I experienced happening again. The first block of text, which is @-quoted, or a verbatim string literal, needs to have all the whitespace and newlines removed from it, otherwise it will appear with them preserved in the resulting PDF. Other than that, each individually styled string is applied to its own Chunk object, and then added to a Phrase to ensure that lines are wrapped in the PDF. Finally both phrases are added to the single Paragraph object. It is also possible to set the alignment of the paragraph text, using the Paragraph.setAlignment() method. This accepts a string, with "Left", "Center", "Justify", and "Right" being valid values. The following shows the earlier example with p.setAlignment("Justify");

The Paragraph class has a number of other useful methods and properties for styling including: Paragraph.FirstLineIndent //allows you to apply a float value to indent the first line Paragraph.IndentationLeft //allows you to add space to the left hand side Paragraph.IndentationRight //allows you to add space to the right hand side Paragraph.setSpacingBefore //adds a specified amount of space above the paragraph Paragraph.setSpacingAfter //adds the specified amount of space after the paragraph

Lists with iTextSharp

Having already looked at how to create a PDF document with iTextSharp, set fonts and their styles and add text, this article turns its attention to lists. Ordered and unordered lists will be covered. You may want to review earlier articles in this series, if you haven't already done so. Create PDFs in ASP.NET - getting started with iTextSharp iTextSharp - Working with Fonts iTextSharp - Adding Text with Chunks, Phrases and Paragraphs Lists are generated from the iTextSharp.text.List object. A List is a collection of iTextSharp.text.ListItem objects. Actually, it is an ArrayList of ListItem objects. ListItem inherits from Paragraph (which inherits from Phrase, which inherits from ArrayList), so each item in the list is rendered on a new line. As with their html <ul> and <ol> counterparts, Lists can be UNORDERED or ORDERED (numbered). Diving straight into code, here's how to generate a List: string path = Server.MapPath("PDFs"); it.Document doc = new it.Document(); try { PdfWriter.GetInstance(doc, new FileStream(path + "/Lists.pdf", FileMode.Create)); doc.Open(); it.List list = new it.List(it.List.UNORDERED); list.Add(new it.ListItem("One")); list.Add("Two"); list.Add("Three"); list.Add("Four"); list.Add("Five"); it.Paragraph paragraph = new it.Paragraph(); string text = "Lists"; paragraph.Add(text); doc.Add(paragraph); doc.Add(list); } catch (it.DocumentException dex) { Response.Write(dex.Message); } catch (IOException ioex) { Response.Write(ioex.Message); } finally { doc.Close(); } If you are not familiar with the concept, the way that a List or ListItem is referenced in the above code needs explaining. As you can see, "it." is used as a prefix for some objects. If you are coding your PDF generation within an ASP.NET code-behind file, you will probably be working with the default set of namespaces that Visual Stuido references when the aspx.cs file is created. One of those namespaces is System.Web.UI.WebControls, which also includes a ListItem class. That means that attempting to simply add ListItem li = new ListItem(); to your code will result in a warning of ambiguity. The way to resolve this is to fully reference the ListItem class you are attempting to use by adding its namespace: iTextSharp.text.ListItem li = new iTextSharp.text.ListItem(); All that extra typing can get very boring, so you can shortcut the namespace by providing an alias to it: using it = iTextSharp.text; Now you can use the alias instead. Back to what the code actually does - the first thing is to create a new List object, and to pass in a bool to indicate whether it is an ordered, or numbered list or not. The default is false. The code

doesn't do much more except add 5 items to the list. The first is added by creating a new ListItem object, and setting its string value. The second and subsequent items are added directly to the List object as strings. Finally, a Paragraph is created and added to the document, followed by the List being added to the document. The result is this:

As you can see, each item acts like a Paragraph as promised, in that it occupies a new line. You will also note that the default settings provide a boring list with hyphens as symbols for each ListItem, and none of the usual indentation that you would get if you were using Microsoft Word, for example. iTextSharp provides ways to format the List so that it looks a lot better. it.List list = new it.List(it.List.UNORDERED, 10f); list.SetListSymbol("\u2022"); list.IndentationLeft = 30f; A second argument (float) is passed in to the List constructor, which sets the symbolIndent value at 10 points. This results in the space between the symbol and the item itself being increased. Secondly, the actual symbol is changed to the more traditional bullet point using the setListSymbol() method. Finally, the List is indented 30 points from the left margin of the document. The result looks a little more pleasing:

If you like your ordered lists with Roman numerals, you can use the RomanList class:

RomanList romanlist = new RomanList(true, 20); romanlist.IndentationLeft = 30f; romanlist.Add("One"); romanlist.Add("Two"); romanlist.Add("Three"); romanlist.Add("Four"); romanlist.Add("Five"); doc.Add(romanlist); For some odd reason, the symbolIdent value passed into the constructor is an int this time, instead of a float. The first argument is a bool that tells iTextSharp whether you want lowercase symbols or not.

A separate GreekList class supplies list symbols as Greek letters, and two further classes: ZapfDingbatsList and ZapfDingbatsNumberList offer more symbol formatting opportunities as they make use of the ZapfDingBats font. The Greek and Roman lists should never be used for lists that contain more than 24 and 26 items respectively, and the ZapfDingBatsNumberList can only cope with a maximum of 10 items before the numbering runs out and becomes 0. ZapfDingbatsList zlist = new it.ZapfDingbatsList(49, 15); zlist.Add("One"); zlist.Add("Two"); zlist.Add("Three"); zlist.Add("Four"); zlist.Add("Five"); doc.Add(zlist);

Lists can also be nested. Since the List.Add() method accepts an object, all you need to do is to pass in a valid List() object. The following code creates a RomanList first, then an ordered list. The RomanList is added to the ordered list, and iTextSharp indents the RomanList relative to the ordered list to which it belongs:

RomanList romanlist = new RomanList(true, 20); romanlist.IndentationLeft = 10f; romanlist.Add("One"); romanlist.Add("Two"); romanlist.Add("Three"); romanlist.Add("Four"); romanlist.Add("Five"); List list = new List(List.ORDERED, 20f); list.SetListSymbol("\u2022"); list.IndentationLeft = 20f; list.Add("One"); list.Add("Two"); list.Add("Three"); list.Add("Roman List"); list.Add(romanlist); list.Add("Four"); list.Add("Five"); doc.Add(paragraph); doc.Add(list);

Links and Bookmarks

Interactivity within PDF documents is enabled though Anchors (links) and Bookmarks. Following earlier articles in my iTextSharp series, this particular contribution will introduce the basics of linking and bookmarking PDF documents created via iTextSharp. You may want to review earlier articles in this series, if you haven't already done so.

Create PDFs in ASP.NET - getting started with iTextSharp iTextSharp - Working with Fonts iTextSharp - Adding Text with Chunks, Phrases and Paragraphs Lists with iTextSharp

Links
iTextSharp Anchor objects are very similar to their HTML counterparts, in that they permit you to create hyperlinks both externally from the document, and internally within the document. Where they diverge from the HTML <a> element is that by default, they do not adopt any special styling within a PDF. For that reason, I suggest applying underlining and a blue colour to the font, as this should help users identify an Anchor as providing some functionality: string path = Server.MapPath("PDFs"); Document doc = new Document(); try { PdfWriter.GetInstance(doc, new FileStream(path + "/Anchors.pdf", FileMode.Create)); doc.Open(); Font link = FontFactory.GetFont("Arial", 12, Font.UNDERLINE, new Color(0, 0, 255)); Anchor anchor = new Anchor("www.mikesdotnetting.com", link); anchor.Reference = "http://www.mikesdotnetting.com"; doc.Add(anchor); } catch (DocumentException dex) { Response.Write(dex.Message); } catch (IOException ioex) { Response.Write(ioex.Message); } finally { doc.Close(); } The code sample above creates an external link, which when clicked will open a browser at this site.

Internal links within an HTML document are specified by adding a NAME attribute to an <a> tag. iTextSharp adopts the same model:

Anchor click = new Anchor("Click to go to Target"); click.Reference = "#target"; Paragraph p1 = new Paragraph(); p1.Add(click); doc.Add(p1); Paragraph p2 = new Paragraph(); p2.Add(new Chunk("\n\n\n\n\n\n\n\n")); doc.Add(p2); Anchor target = new Anchor("This is the Target"); target.Name = "target"; Paragraph p3 = new Paragraph(); p3.Add(target); doc.Add(p3); The first paragraph contains the text "Click to go to Target", and its reference is set to "#target", just as with the HTML version. The second paragraph adds some empty lines and the final paragraph contains a new Anchor, with a Name attribute set to match the Reference value in the Anchor in the first paragraph. The result is that if you click on the "Click to go to the Target" text, the PDF will immediately reposition itself so that "This is the Target" will be at the top of whatever PDF viewer you are using.

An alternative to using Anchors to set internal bookmark targets is to use the SetLocalGoto(), and SetLocalDestination() methods of the Chunk class. Paragraph p4 = new Paragraph(); p4.Add(new Chunk("Click ")); p4.Add(new Chunk("here", link).SetLocalGoto("GOTO")); p4.Add(new Chunk(" to find local goto")); p4.Add(new Chunk("\n\n\n\n\n\n\n\n\n")); Paragraph p5 = new Paragraph(); p5.Add(new Chunk("Local Goto Destination").SetLocalDestination("GOTO")); doc.Add(p4); doc.Add(p5); The first chunk is added using the font that's set up to convey to users that the text should act as a hyperlink. The Chunk.SetLocalGoto() method accepts a string, which acts a a label for the target. A number of empty lines are added followed by another chunk. This one has its SetLocalDestination() method invoked, again with a string defining the location of the target. It's matches the one set in the SetLocalGoto() method earlier. When rendered to the PDF, the word "here" is underlined and in blue, and clicking on it brings "Local Goto Destination" to the top of the screen.

Bookmarks
Often when you open a PDF file, your PDF Viewer application displays a tree-view of the structure of the document, with each branch or leaf acting as a link to the corresponding chapter or section. iTextSharp provides the functionality to generate this tree-view through its Chapter and Section classes.

The top-level object is a Chapter, which will always begin on a new page. Sections cannot be added alone, but must be added to Chapter objects, or parent Section objects: Chapter chapter1 = new Chapter(new Paragraph("This is Chapter 1"),1); Section section1 = chapter1.AddSection(20f, "Section 1.1", 2); Section section2 = chapter1.AddSection(20f, "Section 1.2", 2); Section subsection1 = section2.AddSection(20f, "Subsection 1.2.1", 3); Section subsection2 = section2.AddSection(20f, "Subsection 1.2.2", 3); Section subsubsection = subsection2.AddSection(20f, "Sub Subsection 1.2.2.1", 4); Chapter chapter2 = new Chapter(new Paragraph("This is Chapter 2"), 1); Section section3 = chapter2.AddSection("Section 2.1", 2); Section subsection3 = section3.AddSection("Subsection 2.1.1", 3); Section section4 = chapter2.AddSection("Section 2.2", 2); chapter1.BookmarkTitle = "Changed Title"; chapter1.BookmarkOpen = true; chapter2.BookmarkOpen = false; doc.Add(chapter1); doc.Add(chapter2);

The image above helps to explain the preceding code. Initially, a Chapter object is created with a Paragraph passed in as the first argument. The second argument is the number of the Chapter - in this case 1. Nest, a Section object is added to the Chapter, with 3 arguments: a float specifying the left indentation in points; the title of the Section to appear on the page and in the Bookmars; and the indentation depth for the entry in the Bookmarks tree. In this case, Section 1.1 is set to appear as a second-level entry on the tree. Subsection1 is added to Section 2 and has been told to appear as a third-level entry in the tree. The rest of the code that adds Chapters and Sections should be straightforward. The final lines of code show that the actual entry in the Bookmarks can be changed from the title on the page by setting the BookMarkTitle property to another string value. Then the outline view for the tree is set as open for Chapter 1, but closed for Chapter 2. Finally, both Chapters are added to the document. Chapters and Sections are pretty memory hungry, so they should be used judiciously. If you need to create a document such as a manual on a regular basis, this might be a taks best scheduled for quieter times on your web server.

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.

Drawing shapes and Graphics

The previous iTextSharp article looked at bringing images into a PDF file and working with them. Sometimes, however, you may want to draw shapes and lines within the PDF and not rely on images. iTextSharp includes a lot of functionality that covers simple drawing to quite complex shapes. This article gets you started. Previous articles are listed below:

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 Up till now, all content added to the PDF documents in previous articles have relied on Simple iText, which takes care of positioning content within the flow of a document. It also looks after creating new pages to accept overflow text etc. Working with graphics needs a slightly different approach, in that we now need to use a PdfContentByte() object explicitly. This is obtained from the PdfWriter object's DirectContent. This also means that instead of just invoking the GetInstance method of the PdfWriter, we actually need to instantiate a PdfWriter object. string pdfpath = Server.MapPath("PDFs"); Document doc = new Document(); try { PdfWriter writer = PdfWriter.GetInstance(doc, new FileStream(pdfpath + "/Graphics.pdf", FileMode.Create)); doc.Open(); PdfContentByte cb = writer.DirectContent; ... Now that we have a working PdfContentByte object, we can use it to start drawing: cb.MoveTo(doc.PageSize.Width / 2, doc.PageSize.Height / 2); cb.LineTo(doc.PageSize.Width / 2, doc.PageSize.Height); cb.Stroke(); cb.MoveTo(0, doc.PageSize.Height/2); cb.LineTo(doc.PageSize.Width, doc.PageSize.Height / 2); cb.Stroke(); The first line moves to the x, y coordinates specified in the parameters passed in, which in this case is halfway across the document, and halfway up (or the center point). The next line draws a line from this point to the position specified in the LineTo() method, which is still halfway across the document, but at the top of it. Actually, it doesn't "draw" it, but really only describes our intention. The line is only actually committed to the document when Stroke() is called. The second line is drawn from the lefthand edge, at a position halfway up the document to the righthand edge at the same height - so we end up with the top two quarters of the document outlined.

Using the same methodology, we can add a square to the top left quarter: cb.MoveTo(100f, cb.LineTo(200f, cb.LineTo(200f, cb.LineTo(100f, cb.ClosePath(); cb.Stroke(); 700f); 700f); 600f); 600f);

We didn't need to explicitly specify the coordinates for the final side of the square. ClosePath() automatically provides a line from the current position that we are at to the original coordinates. However, there is a quicker way of delivering a square (or rectangle) using one of the convenience shapes provided by iTextSharp. The next bit shows that is action, placing a square in the top right quarter of the document: cb.Rectangle(doc.PageSize.Width-200f, 600f, 100f, 100f); cb.Stroke();

Four more squares are added to the page, each one illustrating the results of different methods other than Stroke(), used to consign them to the document. But first, the Stroke colour and the Fill colour are set. If you have worked with any graphics packages, such as PhotoShop or FireWorks, you will probably know that Stroke is the outline of an object, and Fill represents its internals bounded by the Stroke. I have used CMYK as the colour space, setting the Stroke colour to Cyan and the Fill colour to Yellow: cb.SetColorStroke(new CMYKColor(1f, 0f, 0f, 0f)); cb.SetColorFill(new CMYKColor(0f, 0f, 1f, 0f)); cb.MoveTo(70, 200); cb.LineTo(170, 200); cb.LineTo(170, 300); cb.LineTo(70, 300); //Path closed and stroked cb.ClosePathStroke(); cb.MoveTo(190, 200); cb.LineTo(290, 200); cb.LineTo(290, 300); cb.LineTo(190, 300); //Filled, but not stroked or closed cb.Fill(); cb.MoveTo(310, 200); cb.LineTo(410, 200); cb.LineTo(410, 300); cb.LineTo(310, 300); //Filled, stroked, but path not closed cb.FillStroke();

cb.MoveTo(430, 200); cb.LineTo(530, 200); cb.LineTo(530, 300); cb.LineTo(430, 300); //Path closed, stroked and filled cb.ClosePathFillStroke();

When using a Rectangle object to represent a square or rectangle instead of drawing it, the first two parameters represent the x and y coordinates of the bottom right hand corner. The final two parameters are the width and height. Other preset shapes include the Circle, but the x and y coordinates that are passed in represent the center point of the shape, followed by a value representing the radius. Looking at the first of the 4 squares above, it is relatively easy to determine that the width and height is 100 points, and that the center point is at 120 x, 250 y. To add a circle to the square so that it fits nicely, the following code will do: cb.SetCMYKColorStroke(255, 255, 0, 0); cb.SetCMYKColorFill(0, 255, 255, 0); cb.SetLineWidth(2f); cb.Circle(120f, 250f, 50f); cb.Fill(); Rather irritatingly, I found you have to guess at the actual values required by the two methods concerning the use of CMYK colour I have used so far. Neither was intuitive, given that generally, CMYK colours are represented as a series of percentages. For example, Warm Red is referenced as C: 0%, M: 100%, Y: 100%, K: 100%. The constructor for CMYKColor() requires four floats. Providing values that represent the percentages works fine. But I have provided values of 1f and 0f. In fact, I can provide any value I want, so long as it is a valid float. The values are treated as being relative to eachother, so for 100% blue (Cyan) i could just have validly provided 3000f, 0f, 0f, 0f. Strange, but workable if you stick to the usual CMYK percentages, and don't accidentally type in an extra zero. The SetCMYKColorFill() method used just above accepts ints, rather than floats. I thought maybe this method was working on percentages as I would expect with CMYK, so I originally supplied the value 0, 100, 100, 0 to represent Warm Red. All I got was a washy pinky colour. I thought perhaps there was a bug in there somewhere, and that something was being affected by the previous setting of the Fill colour, so I deployed the ResetCMYKColorFill() method in the hope that it did something. Nope. All it did was reset to the default colour of black. Eventually, I found out CMYKColor is built on the ExtendedColor class, and can accept ints up to 255 - which is reminiscent of the RGB colour values and really confusing unless you know. I applied the values you see above, and got my desired result. To build on the oddity of this approach, I also discovered that there is nothing to prevent you entering values above 255. If you do, it seems that iTextSharp subtracts 255 from whatever you supply, so 256 is the equivalent of 1, and 510 is the equivalent of 255 - or at least, that's the results I got when I tried it. Anyway, enough of the digression. Here's my red circle with a mauve border (2 points in width) in the first square:

Moving on, here's an example of using another preset shape, the ellipse. First, a rectangle is drawn, then an ellipse is added to fit neatly within it to illustrate how the positioning works through the parameters supplied to the constructor: // x, y of bottom left corner, width, height cb.Rectangle(100f, 200f, 300f, 100f); cb.Stroke(); //Bottom left and top right coordinates

cb.Ellipse(100f, 200f, 400f, 300f); cb.Stroke();

If the differences between the first and third parameter, and the second and fourth parameter are the same, you end up with a circle. The next example shows another preset shape, the rounded rectangle. When one of these are drawn, the values passed in are the x and y coordinates for the bottom left hand corner, followed by the width and height, and finally the radius of the rounded corners. I have also added a circle in the corner with the same radius as the corner itself to show how the radius of the corner works. I've placed a cross at the center of the circle: //Bottom left coordinates followed by width, height and radius of corners cb.RoundRectangle(100f, 500f, 200f, 200f, 20f); cb.Stroke(); cb.Circle(120f, 520f, 20f); cb.Stroke(); cb.MoveTo(118f, cb.LineTo(122f, cb.Stroke(); cb.MoveTo(120f, cb.LineTo(120f, cb.Stroke(); 520f); 520f); 518f); 522f);

Triangles are relatively simple to do, just by drawing the three lines they require. Here's an example of a right-angled triangle with the right angle shown: cb.MoveTo(350f, 450f); cb.LineTo(350f, 600f); cb.LineTo(500f, 450f); cb.ClosePathStroke(); cb.MoveTo(350f, 460f); cb.LineTo(360f, 460f); cb.LineTo(360f, 450f); cb.Stroke();

Curves and Arcs


Bezier curves are important in Vector Graphics, where they are based on mathematical equations rather than each individual point being specified along the path of the curve. They work by specifying a start point, an end point, and two control points. The first control point is specified in reference to the start point, and the second one is related to the end point. The curve will begin at the start point, and head towards the end point in the direction of the 1st control point. How far it gets near the control point will depend on the distance between the start point and its control point, and the end point and its control point, and the distance between the start and end point themselves. The control points are used for directional purposes, and are rarely hit by the path. If you have ever used a Vector graphics package, like Photoshop or Fireworks, you will have seen these control points, usually with little "handles" on the end of them which you can move to bend the curve. Here's an example of a curve, that starts at (200, 10), and ends at (350, 150). //Start Point cb.MoveTo(200f, 10f); //Control Point 1, Control Point 2, End Point cb.CurveTo(150f, 30f, 450f, 70f, 350f, 150f); cb.Stroke(); The curve heads towards the first control point at (150, 30), then bends away towards the second control point at (450, 70), before arriving at the end point. It looks like this:

I'm not really sure that shows a lot, so I will add in some "handles" that show the control points: cb.MoveTo(200f, 10f); cb.LineTo(150f, 30f); cb.Stroke(); cb.MoveTo(450f, 70f); cb.LineTo(350f, 150f); cb.Stroke(); cb.Circle(450f, 70f, 1f); cb.Stroke(); cb.Circle(150f, 30f, 1f); cb.Stroke();

The first control point has a relatively short handle, so the curve only begins to move towards it. The second control point has a longer handle, and this starts to exert its influence on the curve along with the ultimate destination at a relatively early point in its journey, which is reflected in the way the line is "pulled" towards it. This effect is probably best illustrated by changing the second control point so that it is further away, and the angle is altered: cb.SetColorStroke(Color.GREEN); //start point (x,y) cb.MoveTo(200f, 10f); //control point 1 (x,y), control point 2 (x,y), end point (x,y)

cb.CurveTo(150f, 30f, 550f, 100f, 350f, 150f); cb.Stroke(); cb.MoveTo(550f, 100f); cb.LineTo(350f, 150f); cb.Stroke(); cb.Circle(550f, 100f, 1f); cb.Stroke(); cb.SetColorStroke(Color.LIGHT_GRAY); //Bottom Left(x,y), Top Right(x,y), Start Angle, Extent cb.Arc(350f, 70f, 550f, 130f, 270f, 90f); cb.SetLineDash(3f, 3f); cb.Stroke(); cb.SetLineDash(0f); cb.MoveTo(550f, 100f); cb.LineTo(535f, 95f); cb.Stroke(); cb.MoveTo(550f, 100f); cb.LineTo(552f, 88f); cb.Stroke();

The original curve is shown in black, while the new one is shown in green. Now you can see that the increased length of the second "handle" has exerted its influence right from the start point, in that the green line starts just slightly beneath the original black one. Since the new second control point is further away from the end point, and is at a slightly more obtuse angle, the green line moves much further towards the second control point before finishing up at the end point. There are a couple of other points to note about the code for the second curve. One is the Arc object, which has been used in this instance to create the curved arrow illustrating the movement of the original and new second control points. An Arc is part of an ellipse. In this case, the ellipse will fit into a rectangle that has a bottom left corner at (350, 70) and a top right corner at (550, 130). The default direction that an ellipse is drawn is counter-clockwise. The angle at which the arc is started is 270, and only 90 of it are drawn. Secondly, the SetLineDash() method is shown for the first time in this series of articles, which allows me to draw the arrow as a dashed line.

Working with images

The seventh article in my iTextSharp series looks at working with images. This article builds on the previous six which are listed below. 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 supports all the main image types: jpg, tif, gif, bmp, png and wmf. There are a number of ways to create images with iTextSharp using the Image.GetInstance() method. Probably the most used option will be to pass a filesystem path and file name into the method: string pdfpath = Server.MapPath("PDFs"); string imagepath = Server.MapPath("Images"); Document doc = new Document(); try { PdfWriter.GetInstance(doc, new FileStream(pdfpath + "/Images.pdf", FileMode.Create)); doc.Open(); doc.Add(new Paragraph("GIF")); Image gif = Image.GetInstance(imagepath + "/mikesdotnetting.gif"); doc.Add(gif); } catch (DocumentException dex) { Response.Write(dex.Message); } catch (IOException ioex) { Response.Write(ioex.Message); } catch (Exception ex) { Response.Write(ex.Message); } finally { doc.Close(); }

Alternative constructors that you may use include passing a URL or a System.Drawing.Image object (as opposed to an iTextSharp.text.Image). Note - the following snippet that System.Drawing.Image.FromStream() method shows the use of namespace aliasing again

(sd.Image.FromStream(fs)), as was highlighted in the article Lists with iTextSharp to avoid clashes with the two different types of Image object: doc.Add(new Paragraph("JPG")); string url = "http://localhost:1805/PDF/Images/mikesdotnetting.jpg"; Image jpg = Image.GetInstance(new Uri(url)); doc.Add(jpg); doc.Add(new Paragraph("PNG")); using (FileStream fs = new FileStream(imagepath + "/mikesdotnetting.png", FileMode.Open)) { Image png = Image.GetInstance(sd.Image.FromStream(fs),ImageFormat.Png); doc.Add(png); }

It's difficult to tell from the images I have provided so far, but the resolution of the resulting images in the PDF file is not that great. By default, images are embedded at 72 dpi (Dots Per Inch) which coincidentally, matches the number of points in an inch. If this file was being prepared for printing, the final job would be a bit nasty. Generally, commercial printers require that colour images for printing have a resolution of 300 dpi. To achieve this, you can scale the image to 24%. What you are actually trying to do is squeeze 300 pixels into the space that 72 normally occupies. 72/300 * 100 = 24%. The image stays the same in terms of file size but occupies less space in the document. doc.Add(new Paragraph("TIF Scaled to 300dpi")); Image tif = Image.GetInstance(imagepath + "/mikesdotnetting.tif"); tif.ScalePercent(24f); doc.Add(tif);

Now, I have a large tif file that I want to use as a logo on an A4 letterhead. It measures 300 x 890 pixels. So at the default 72 dpi, it will measure 4.17 inches wide by12.36 inches deep. Increasing the resolution to 300 dpi will reduce the width to 1 inch, and the depth to 2.97 inches (72 points by 213.6 points). That part is fine. We can do that using the code above. Now I want to place the

300 dpi image in a particular position on the page. I have in mind the top right hand corner. The SetAbsolutePosition() method will do this, but I need to get a calculator out. SetAbsolutePosition() accepts 2 floats as parameters. The first represents the co-ordinate along the X-axis, which starts at the left hand edge of the document and finishes at the right hand edge of the document. The second represents the Y-axis co-ordinate which starts from the bottom of the document and goes to the top. An A4 document is 595 points wide and 842 pixels high with a 36 point margin all around by default. The actual co-ordinate that needs to be passed in is the bottom left of the image position. This image will butt right up to the right-hand margin. The image is 72 points wide (1 inch) + the margin at 36 points (total 108 points) from the right hand edge of the document, or 595 - 108 = 487 points along the X-axis. The Y-axis co-ordinate is the height of the image + the margin away from the top of the document, or 842 - (213.6 + 36) = 592.4 points from the bottom. Cripes. I don't really want to have to remember all the various sizes of different documents and do these calculations every time I want to set the absolute position of an element. And fortunately, I don't have to. I can use the Document.PageSize object to do the work for me. Image tif = Image.GetInstance(imagepath + "/verticallogo.tif"); tif.ScalePercent(24f); tif.SetAbsolutePosition(doc.PageSize.Width - 36f - 72f, doc.PageSize.Height - 36f - 216.6f); doc.Add(tif); doc.PageSize.Width gives me the width in points of the document, and I just remove the margin (36 points) and the width of the image (72 points) for the X-axis co-ordinate, and the margin and height of the image from the total height of the document for the Y-axis co-ordinate.

Got my company logo in :-) Another scenario that might need to be catered for is to fit a user-supplied image into a fixed size box somewhere on a form. I've pinched the Sunset image that I found in the Sample Images folder in My Pictures that comes as part of the default install of WindowsXP to illustrate how to use the ScaleToFit() method to achieve this. The following code takes an image of 800 x 600 and forces it to resize, maintaining its aspect ratio, into a rectangle measuring 250 points square. Image jpg = Image.GetInstance(imagepath + "/Sunset.jpg"); jpg.ScaleToFit(250f, 250f); jpg.Border = Rectangle.BOX; jpg.BorderColor = Color.YELLOW; jpg.BorderWidth = 5f; doc.Add(jpg); doc.Add(new Paragraph("Original Width: " + jpg.Width.ToString())); doc.Add(new Paragraph("Original Height " + jpg.Height.ToString())); doc.Add(new Paragraph("Scaled Width: " + jpg.ScaledWidth.ToString())); doc.Add(new Paragraph("Scaled Height " + jpg.ScaledHeight.ToString()));

float Resolution = jpg.Width / jpg.ScaledWidth * 72f; doc.Add(new Paragraph("Resolution: " + Resolution)); I have also taken the opportunity to add a yellow border to the embedded image, which is 5 points wide, and then shown the original dimensions, followed by the scaled dimensions, and the resulting resolution of the image. And here's the result:

If you use SetAbsolutePosition() you end up with the same effect as if you had set the Alignment property of the image to Image.UNDERLYING, in that text added to the document will run over the top of it. Unless you want to achieve this kind of watermark effect, you need to use Image.TEXTWRAP instead. Image jpg = Image.GetInstance(imagepath + "/Sunset.jpg"); Paragraph paragraph = new Paragraph(@"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?"); paragraph.Alignment = Element.ALIGN_JUSTIFIED; jpg.ScaleToFit(250f, 250f); jpg.Alignment = Image.TEXTWRAP | Image.ALIGN_RIGHT; jpg.IndentationLeft = 9f; jpg.SpacingAfter = 9f; jpg.BorderWidthTop = 36f; jpg.BorderColorTop = Color.WHITE; doc.Add(jpg); doc.Add(paragraph);

In this instance, I added a white border to the image to align it with the top of the text, and a bit of padding to the left and bottom of the image to stop the text running right up to its edge. Left and Right padding can be added using the IndentationLeft and IndentationRight properties, while SpacingBefore and SpacingAfter is used to pad the top and bottom. You may ask why I didn't use SapcingBefore instead of adding a white border, and that's a good question. The fact is that whatever I set the value to for SpacingBefore, it seemed to have no effect whatsover in this instance. I have no idea why this should be, but if anyone does, I'll be pleased to hear from them. One final thing with images - you can rotate them if you need to. One way to do this is to set the Rotation property, which takes a float. The float value represents an angle measured in Radians. If you did more Mathematics than I did at school, you may be comfortable with this, but if you are like me and ask "What??", you can either read this article on Radians and work out that a quarter turn (90) is Pi / 2, or simply set the RotationDegrees property instead. Rotation by default is anticlockwise. The following are equally valid and equivalent: jpg.Rotation = (float)Math.PI / 2; jpg.RotationDegrees = 90f;

You might also like