The PDF format is one of the most commonly used formats in the enterprise documents exchanges. Each company has its software for invoices, maybe from a specific vendor or developed by its own development team. Many dedicated frameworks exist in the market to manipulate PDF files, they all aim to make the developer’s work easy.
In this tutorial, we will show how to create your printable invoices with PDF SDK. Bytescout’s PDF SDK contains many features to deal with PDF documents. You can create PDF files from scratch, on the fly, or modify an existing one, even exporting them to another file format like Microsoft Office word document without any format and data loss. You can find more details about that on the main website of Bytescout at https://bytescout.com
The rest of this tutorial is focusing on invoices PDF SDK. We’ll see three ways to generate an invoice:
.
All of the three methods are done on the server-side so they can be plugged into an automated invoicing software for future use. Each method is independent of the others and you can choose the most suitable one.
We will use the .NET framework 4.5, thus we have to use the net4.5 version of Bytescout.PDF.dll
It is located at C:\Program Files\Bytescout PDF SDK\net4.5\ folder
We will build the whole invoice by full use of Bytescout’s pdf SDK. The framework contains a lot of features that can be used to write and format texts, draw lines, arcs, tables, images, canvas, and many others. Those objects are then positioned on the page by using the (left, top) system coordinates where (0,0) is at the top left corner of the page.
We start with the default Visual Studio C# console application, name the project InvoiceSample01, add a reference to Bytescout.PDF.dll and System.Drawing.dll
In the file Program.cs we add the code below
Program.cs
using Bytescout.PDF; namespace InvoiceSample01 { class Program { static void Main(string[] args) { //Create the pdf document var invoicePdfDocument = new Document(); //Define the type of Page format var invoicePage = new Page(PaperFormat.A4); //Add page to the pdf document invoicePdfDocument.Pages.Add(invoicePage); //Define two types of font //Simple Arial font, size of ten em var font = new Font("Arial", 10); //Arial font of 12 em and bold var fontBold = new Font("Arial", 12, true, false, false, false); //Define a brush var brush = new SolidBrush(); /* Build each part (text, vertical/horizontal lines...) and place them in the page using their coordinates (left,top) */ invoicePage.Canvas.DrawString("Computer accessories Ltd", fontBold, brush, 20, 20); invoicePage.Canvas.DrawString("INVOICE", fontBold, brush, 500, 20); //Seller address invoicePage.Canvas.DrawString("27 Street WorldLand", font, brush, 20, 40); invoicePage.Canvas.DrawString("City Lake", font, brush, 20, 60); invoicePage.Canvas.DrawString("+00.00.00.00.54", font, brush, 20, 80); invoicePage.Canvas.DrawString("+00.00.00.00.55", font, brush, 20, 100); //Invoice header labels invoicePage.Canvas.DrawString("Date", font, brush, 400, 40); invoicePage.Canvas.DrawString("Invoice #", font, brush, 400, 60); invoicePage.Canvas.DrawString("Customer #", font, brush, 400, 80); //Invoice header values invoicePage.Canvas.DrawString("2016-30-11", font, brush, 500, 40); invoicePage.Canvas.DrawString("1234567", font, brush, 500, 60); invoicePage.Canvas.DrawString("CT-111-52", font, brush, 500, 80); //Buyer information invoicePage.Canvas.DrawString("Bill To", fontBold, new SolidBrush(new ColorRGB(0, 0, 255)), 20, 120); invoicePage.Canvas.DrawString("Ship Sea Land", font, brush, 20, 140); invoicePage.Canvas.DrawString("Albert Best Buyer", font, brush, 20, 160); invoicePage.Canvas.DrawString("42 Dolphin Blv", font, brush, 20, 180); invoicePage.Canvas.DrawString("River City", font, brush, 20, 200); invoicePage.Canvas.DrawString("+44.00.00.12.55", font, brush, 20, 220); //Add a grey background color in the grid header SolidBrush brushBackground = new SolidBrush(new ColorRGB(192, 192, 192)); invoicePage.Canvas.DrawRectangle(brushBackground, 20, 250, 560, 20); //Draw the grid lines //The type of pen SolidPen pen = new SolidPen(); //Horizontal lines invoicePage.Canvas.DrawLine(pen, 20, 250, 580, 250); invoicePage.Canvas.DrawLine(pen, 20, 270, 580, 270); invoicePage.Canvas.DrawLine(pen, 20, 370, 580, 370); //Vertical lines invoicePage.Canvas.DrawLine(pen, 20, 250, 20, 370); invoicePage.Canvas.DrawLine(pen, 370, 250, 370, 370); invoicePage.Canvas.DrawLine(pen, 440, 250, 440, 370); invoicePage.Canvas.DrawLine(pen, 510, 250, 510, 370); invoicePage.Canvas.DrawLine(pen, 580, 250, 580, 370); //Labels invoicePage.Canvas.DrawString("Description", fontBold, brush, 160, 255); { //Items under Description invoicePage.Canvas.DrawString("RAM SDRAM PC 1333 Mhz", font, brush, 25, 275); invoicePage.Canvas.DrawString("RJ45 LAN connector", font, brush, 25, 295); invoicePage.Canvas.DrawString("C19 power supply cable 16A ", font, brush, 25, 315); invoicePage.Canvas.DrawString("Headphones CZBluetooth 4", font, brush, 25, 335); } invoicePage.Canvas.DrawString("Unit price", fontBold, brush, 380, 255); { //Items under Unit price invoicePage.Canvas.DrawString("20,45", font, brush, 390, 275); invoicePage.Canvas.DrawString("2,50", font, brush, 390, 295); invoicePage.Canvas.DrawString("10,70", font, brush, 390, 315); invoicePage.Canvas.DrawString("30,50", font, brush, 390, 335); } invoicePage.Canvas.DrawString("Quantity", fontBold, brush, 450, 255); { //Items under Quantity invoicePage.Canvas.DrawString("3", font, brush, 470, 275); invoicePage.Canvas.DrawString("4", font, brush, 470, 295); invoicePage.Canvas.DrawString("5", font, brush, 470, 315); invoicePage.Canvas.DrawString("1", font, brush, 470, 335); } invoicePage.Canvas.DrawString("Amount", fontBold, brush, 520, 255); { //Items under Amount invoicePage.Canvas.DrawString("61,35", font, brush, 540, 275); invoicePage.Canvas.DrawString("10,00", font, brush, 540, 295); invoicePage.Canvas.DrawString("53,50", font, brush, 540, 315); invoicePage.Canvas.DrawString("30,50", font, brush, 540, 335); } invoicePage.Canvas.DrawString("Sub Total", font, brush, 450, 380); invoicePage.Canvas.DrawString("Taxable", font, brush, 450, 400); invoicePage.Canvas.DrawString("Tax Rate", font, brush, 450, 420); invoicePage.Canvas.DrawString("Tax Due", font, brush, 450, 440); invoicePage.Canvas.DrawString("Total", fontBold, brush, 450, 460); { //amount value invoicePage.Canvas.DrawString("0,001", font, brush, 540, 380); invoicePage.Canvas.DrawString("0,002", font, brush, 540, 400); invoicePage.Canvas.DrawString("0,003", font, brush, 540, 420); invoicePage.Canvas.DrawString("0,004", font, brush, 540, 440); invoicePage.Canvas.DrawString("0,005", font, brush, 540, 460); } //Save the pdf as invoice01.pdf invoicePdfDocument.Save("invoice01.pdf"); invoicePdfDocument.Dispose(); } } }
After running the program, the invoice01.pdf is created in the bin/Debug/ folder and looks like the figure below
As we’ve seen along with the Program.cs code, each piece of the final invoice has been inserted into the pdf document’s page object. The major part of the task is about positioning the elements at the right place on the page. We’ve used the Canvas property of the pdf document object to draw texts and graphics. You can picture a Canvas as a container where you can draw something. Although this method is very powerful – remember you have the full control of each element on the page, building the pdf file in this way may become tedious at the earlier stage of the development since you’ll be playing with a lot of coordinates. The logic and the presentation layer are also in the same place so it may be hard to develop and to maintain.
The next two sections will show you how to use a template file to separate the invoice design and the invoice data. We’ll use an HTML and a Microsoft Office document (.docx) template files.
Tips: When two elements have the same coordinate on the same page, the last added one is placed on the top of the other. In the above method, the Document.Save(string fileName) method writes the pdf file into the file system.
We start with the default C# console application in visual studio 2015 Community. We choose framework 4.5 and name the solution “InvoiceSample02”. We also add references to Bytescout.PDF.dll and Bytescout.PDF.Converters.dll. Those files can be found in the folder C:\Program Files\Bytescout PDK SDK\net4.5\
The figure below shows the snapshot of the solution at this stage
We’ve added the InvoiceTemplates folder where we’ll put the invoice template. The Model folder contains the definition of classes used to map with the template.
You can picture a template file as a generic file that is used as a base layout to generate different invoices. All generated invoices differ only by specific fields like the customer name, the invoice details, the total amounts, etc. The layout doesn’t change and remains the same in terms of design.
After creating the file template000.html in the InvoiceTemplates folder, copy and paste the following code into it.
<!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" data-wp-preserve="%3Cstyle%3E%0A%20%20%20%20%20%20%20%20body%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20font-family%3A%20Arial%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20font-size%3A%200.8em%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20width%3A%20800px%3B%0A%20%20%20%20%20%20%20%20%7D%0A%0A%20%20%20%20%20%20%20%20%23invoicePageContainer%20%7B%0A%20%20%20%20%20%20%20%20%7D%0A%0A%20%20%20%20%20%20%20%20%23invoiceHeader%2C%20%23invoiceDetail%2C%20%23invoiceFooter%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20display%3A%20block%3B%0A%20%20%20%20%20%20%20%20%7D%0A%0A%20%20%20%20%20%20%20%20%23invoiceHeader%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20min-height%3A%20100px%3B%0A%20%20%20%20%20%20%20%20%7D%0A%0A%20%20%20%20%20%20%20%20%23buyerInfoContainer%2C%20%23invoiceReferenceContainer%2C%20%23sellerInfoContainer%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20display%3A%20block%3B%0A%20%20%20%20%20%20%20%20%7D%0A%0A%20%20%20%20%20%20%20%20%23buyerInfoContainer%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20min-height%3A%20100px%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20top%3A%20100px%3B%0A%20%20%20%20%20%20%20%20%7D%0A%0A%20%20%20%20%20%20%20%20%23sellerInfoContainer%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20position%3A%20absolute%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20min-height%3A%20100px%3B%0A%0A%20%20%20%20%20%20%20%20%20%20%20%0A%20%20%20%20%20%20%20%20%7D%0A%0A%20%20%20%20%20%20%20%20%23invoiceDetail%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20position%3A%20absolute%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20top%3A%20200px%3B%0A%20%20%20%20%20%20%20%20%7D%0A%0A%20%20%20%20%20%20%20%20%23invoiceTable%20%7B%20%20%20%20%20%20%20%20%20%0A%20%20%20%20%20%20%20%20%20%20%20%20border-left%3A%201px%20solid%20black%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20border-right%3A%201px%20solid%20black%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20border-top%3A%201px%20solid%20black%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20border-bottom%3A%201px%20solid%20black%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20border-spacing%3A%200%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20width%3A%20950px%3B%0A%20%20%20%20%20%20%20%20%7D%0A%0A%20%20%20%20%20%20%20%20%23invoiceTableDetail%20tr%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20border-bottom%3A%20none%3B%0A%20%20%20%20%20%20%20%20%7D%0A%0A%20%20%20%20%20%20%20%20%23invoiceTableSubTotal%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20border-top%3A%201px%20solid%20black%3B%0A%20%20%20%20%20%20%20%20%7D%0A%0A%0A%20%20%20%20%20%20%20%20%23buyerInfoContainer%2C%20%23sellerInfoContainer%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20position%3A%20absolute%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20left%3A%2010px%3B%0A%20%20%20%20%20%20%20%20%7D%0A%0A%20%20%20%20%20%20%20%20%23invoiceReferenceContainer%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20position%3A%20absolute%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20left%3A%20700px%3B%0A%20%20%20%20%20%20%20%20%7D%0A%0A%20%20%20%20%20%20%20%20%23invoiceReferenceContainer_Labels%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20position%3A%20absolute%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20display%3A%20inline%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20min-width%3A%20200px%3B%0A%20%20%20%20%20%20%20%20%7D%0A%0A%20%20%20%20%20%20%20%20%23invoiceReferenceContainer_Entry%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20position%3A%20absolute%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20display%3A%20inline%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20width%3A%2064px%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20left%3A%20100px%3B%0A%20%20%20%20%20%20%20%20%7D%0A%0A%20%20%20%20%20%20%20%20%23invoice%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20font-size%3A%201.5em%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20font-weight%3A%20bold%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20display%3A%20block%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20text-align%3A%20right%3B%0A%20%20%20%20%20%20%20%20%7D%0A%0A%20%20%20%20%20%20%20%20.amount%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20text-align%3A%20right%3B%0A%20%20%20%20%20%20%20%20%7D%0A%0A%20%20%20%20%20%20%20%20.unitprice%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20text-align%3A%20center%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20border-left%3A%201px%20solid%20black%3B%0A%20%20%20%20%20%20%20%20%7D%0A%0A%20%20%20%20%20%20%20%20.quantity%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20text-align%3A%20center%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20border-left%3A%201px%20solid%20black%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20border-right%3A%201px%20solid%20black%3B%0A%20%20%20%20%20%20%20%20%7D%0A%0A%20%20%20%20%20%20%20%20.total%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20font-weight%3A%20bold%3B%0A%20%20%20%20%20%20%20%20%7D%0A%0A%20%20%20%20%20%20%20%20%23Tr2%20td%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20height%3A%20300px%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20vertical-align%3A%20text-top%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20border-bottom%3A%201px%20solid%20black%3B%0A%20%20%20%20%20%20%20%20%7D%0A%0A%20%20%20%20%20%20%20%20%23invoiceTableTotal%20.amount%2C%20%23invoiceTable%20thead%2C%20%23invoiceBillTo%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20background-color%3A%20%23032b64%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20color%3A%20white%3B%0A%20%20%20%20%20%20%20%20%7D%0A%0A%20%20%20%20%20%20%20%20%23companyName%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20font-weight%3A%20bolder%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20font-size%3A%201.2em%3B%0A%20%20%20%20%20%20%20%20%7D%0A%0A%20%20%20%20%20%20%20%20.padding-10%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20padding%3A%2010px%2010px%200px%200px%3B%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%3C%2Fstyle%3E" data-mce-resize="false" data-mce-placeholder="1" class="mce-object" width="20" height="20" alt="<style>" title="<style>" /> </head> <body> <div id="invoicePageContainer"> <div id="invoiceHeader"> <div id="sellerInfoContainer"> <div id="companyName">Computer accessories Ltd</div> <div id="companyAddress">27 Street WorldLand </div> <div id="companyCity">City Lake </div> <div id="companyPhone">+00.00.00.00.54</div> <div id="companyFax">+00.00.00.00.55</div> </div> <div id="invoiceReferenceContainer"> <div id="invoice">INVOICE</div> <div id="invoiceReferenceContainer_Labels"> <div id="">Date</div> <div id="Div1">Invoice #</div> <div id="Div2">Customer #</div> </div> <div id="invoiceReferenceContainer_Entry"> <div id="invoiceDate">{{INVOICE_DATE}}</div> <div id="invoiceId">{{INVOICE_ID}}</div> <div id="invoiceCustomerId">{{INVOICE_CUSTOMERID}}</div> </div> </div> <div id="buyerInfoContainer"> <div id="invoiceBillTo">Bill To :</div> <div id="invoiceHeader_billTo">{{INVOICE_CUSTOMER_TO}}</div> <div id="customerName">{{INVOICE_CUSTOMER_NAME}}</div> <div id="customerStreetAddress">{{INVOICE_CUSTOMER_STREET_ADDRESS}}</div> <div id="customerCity">{{INVOICE_CUSTOMER_CITY}}</div> <div id="customerPhone">{{INVOICE_CUSTOMER_PHONE_NUMBER}}</div> </div> </div> <div id="invoiceDetail"> <table id="invoiceTable"> <thead> <tr> <th style="width:700px">Description</th> <th style="width:100px">Unit price</th> <th style="width:50px;">Quantity</th> <th style="width:50px;">Amount</th> </tr> </thead> <tbody> {{INVOCE_DETAILS}} <tr id="invoiceTableSubTotal"> <td></td> <td></td> <td class="padding-10">SubTotal</td> <td class="amount padding-10">{{INVOICE_SUBTOTAL}}</td> </tr> <tr id="invoiceTableTaxable"> <td></td> <td></td> <td class="padding-10">Taxable</td> <td class="amount padding-10">{{INVOICE_TAXABLE}}</td> </tr> <tr id="invoiceTableTaxRate"> <td></td> <td></td> <td class="padding-10">Tax rate</td> <td class="amount padding-10">{{INVOICE_TAXRATE}}</td> </tr> <tr id="invoiceTableTaxDue"> <td></td> <td></td> <td class="padding-10">Tax due</td> <td class="amount padding-10">{{INVOICE_TAXDUE}}</td> </tr> <tr id="invoiceTableTotal"> <td></td> <td></td> <td class="total padding-10">Total</td> <td class="amount padding-10">{{INVOICE_TOTAL}}</td> </tr> </tbody> <tfoot> </tfoot> </table> </div> <div id="invoiceFooter"> </div> </div> </body> </html>
The figure below shows the HTML template file opened in the browser.
Static parts have been surrounded in blue. This is the case of the seller information which never changes across multiple invoices. We’ve used the double curly brackets to identity all dynamic fields. As you have noticed, those fields will be replaced by the real values during the mapping phase.
Tips: We’ve used the double curly brackets because Visual Studio highlights them in the editor.
The next major step is to create our model class. A model class is just an object where each property is associated to one field in the template model.
The figure below shows our five models.
We split the whole invoice model into five distinct parts. Each part corresponds to an independent set of data like the Buyer information, the item detail section, and so on.
Template000_Buyer.cs
using System.Text; namespace InvoiceSample02.Model { public class Template000_Buyer { // Map to {{INVOICE_CUSTOMER_TO}} public string InvoiceCustomerTo { get; set; } // Map to {{INVOICE_CUSTOMER_NAME}} public string InvoiceCustomerName { get; set; } // Map to {{INVOICE_CUSTOMER_STREET_ADDRESS}} public string InvoiceCustomerStreetAddress { get; set; } // Map to {{INVOICE_CUSTOMER_CITY}} public string InvoiceCustomerCity { get; set; } // Map to {{INVOICE_CUSTOMER_PHONE_NUMBER}} public string InvoiceCustomerPhoneNumber { get; set; } //This method takes a stringbuilder input then looks and replaces a specific pattern //by the final value public void MapAndGenerate(StringBuilder input) { input = input.Replace("{{INVOICE_CUSTOMER_TO}}", InvoiceCustomerTo); input = input.Replace("{{INVOICE_CUSTOMER_NAME}}", InvoiceCustomerName); input = input.Replace("{{INVOICE_CUSTOMER_STREET_ADDRESS}}", InvoiceCustomerStreetAddress); input = input.Replace("{{INVOICE_CUSTOMER_CITY}}", InvoiceCustomerCity); input = input.Replace("{{INVOICE_CUSTOMER_PHONE_NUMBER}}", InvoiceCustomerPhoneNumber); } } }
Template000_Header.cs
using System.Text; namespace InvoiceSample02.Model { public class Template000_Header { // Map to {{INVOICE_DATE}} public string InvoiceDate { get; set; } // Map to {{INVOICE_ID}} public string InvoiceId { get; set; } // Map to {{INVOICE_CUSTOMERID}} public string InvoiceCustomerId { get; set; } //This method takes a stringbuilder input then looks and replaces a specific pattern //by the final value public void MapAndGenerate(StringBuilder input) { input = input.Replace("{{INVOICE_DATE}}", InvoiceDate); input = input.Replace("{{INVOICE_ID}}", InvoiceId); input = input.Replace("{{INVOICE_CUSTOMERID}}", InvoiceCustomerId); } } }
Template000_Total.cs
using System.Text; namespace InvoiceSample02.Model { public class Template000_Total { // Map to {{INVOICE_SUBTOTAL}} public string InvoiceSubTotal { get; set; } // Map to {{INVOICE_TAXABLE}} public string InvoiceTaxable { get; set; } // Map to {{INVOICE_TAXRATE}} public string InvoiceTaxRate { get; set; } // Map to {{INVOICE_TAXDUE}} public string InvoiceTaxDue { get; set; } // Map to {{INVOICE_TOTAL}} public string InvoiceTotal { get; set; } //This method takes a stringbuilder input then looks and replaces a specific pattern //by the final value public void MapAndGenerate(StringBuilder input) { input = input.Replace("{{INVOICE_SUBTOTAL}}", InvoiceSubTotal); input = input.Replace("{{INVOICE_TAXABLE}}", InvoiceTaxable); input = input.Replace("{{INVOICE_TAXRATE}}", InvoiceTaxRate); input = input.Replace("{{INVOICE_TAXDUE}}", InvoiceTaxDue); input = input.Replace("{{INVOICE_TOTAL}}", InvoiceTotal); } } }
Template000_ItemDetail.cs
namespace InvoiceSample02.Model { public class Template000_ItemDetail { //The template of an item in the invoice detail section. public const string ItemHtml = @" <tr class='padding-10'> <td>{{INVOICEDETAILITEM_LIB}}</td> <td class='unitprice'>{{INVOICEDETAILITEM_UNITPRICE}}</td> <td class='quantity'>{{INVOICEDETAILITEM_QUANTITY}}</td> <td class='amount'>{{INVOICEDETAILITEM_AMOUNT}}</td> </tr> "; // Map to {{INVOICEDETAILITEM_LIB}} public string InvoiceDetailItemLib { get; set; } // Map to {{INVOICEDETAILITEM_UNITPRICE}} public string InvoiceDetailItemUnitPrice { get; set; } // Map to {{INVOICEDETAILITEM_QUANTITY}} public string InvoiceDetailItemQuantity { get; set; } // Map to {{INVOICEDETAILITEM_AMOUNT}} public string InvoiceDetailItemAmount { get; set; } //ToString function is now returning the item detail in html format public override string ToString() { string html = ItemHtml.Replace("{{INVOICEDETAILITEM_LIB}}", InvoiceDetailItemLib); html = html.Replace("{{INVOICEDETAILITEM_UNITPRICE}}", InvoiceDetailItemUnitPrice); html = html.Replace("{{INVOICEDETAILITEM_QUANTITY}}", InvoiceDetailItemQuantity); html = html.Replace("{{INVOICEDETAILITEM_AMOUNT}}", InvoiceDetailItemAmount); return html; } } }
Template000_Model.cs
using System.Collections.Generic; using System.IO; using System.Text; namespace InvoiceSample02.Model { // Template000_Model is the whole invoice, it contains the header information, the buyer and total and // also a list of items public class Template000_Model { //Reads and sets the template file private StringBuilder template000Html = new StringBuilder().Append( File.ReadAllText("../../InvoiceTemplates/template000.html")); public Template000_Model() { //Initialize all properties Header = new Template000_Header(); Buyer = new Template000_Buyer(); Details = new List&amp;amp;lt;Template000_ItemDetail&amp;amp;gt;(); Total = new Template000_Total(); } public Template000_Header Header { get; set; } public Template000_Buyer Buyer { get; set; } public List&amp;amp;lt;Template000_ItemDetail&amp;amp;gt; Details { get; set; } public Template000_Total Total { get; set; } //This method adds an item to the invoice model public void AddItem(Template000_ItemDetail item) { Details.Add(item); } //Genarete the invoice detail section private void GenerateDetailsHtml() { StringBuilder detailHtml = new StringBuilder(); foreach (var item in Details) { detailHtml.Append(item.ToString()); } template000Html = template000Html.Replace("{{INVOCE_DETAILS}}", detailHtml.ToString()); } //Process all mapping tasks and generate the final content in html string public StringBuilder MapAndGenerateHtml() { Header.MapAndGenerate(template000Html); Buyer.MapAndGenerate(template000Html); Total.MapAndGenerate(template000Html); GenerateDetailsHtml(); return template000Html; } } }
The Template000_Model class is where we reassemble all parts into one model. It contains the reference to the template file in the template000Html variable.
The main program below is divided into two parts, the first one is where we create the invoice object and the second is where we call the PDF SDK to generate the invoice pdf file. The final document is actually generated in memory by calling the method HtmlToPdfConverter.ConvertHtmlToPdf of Bytescout.PDF.Converters namespace and created in the file system by the MemoryStream.WriteTo method.
Main Program.cs
using Bytescout.PDF.Converters; using InvoiceSample02.Model; using System.IO; using System.Text; namespace InvoiceSample02 { class Program { static void Main(string[] args) { //Declare the invoice object Template000_Model model = new Template000_Model(); //Set value of each field in the invoice's header section model.Header.InvoiceDate = "2016-30-11"; model.Header.InvoiceId = "1234567"; model.Header.InvoiceCustomerId = "CT-111-52"; //Fill invoice's buyer section model.Buyer.InvoiceCustomerTo = "Ship Sea Land"; model.Buyer.InvoiceCustomerName = "Albert Best Buyer"; model.Buyer.InvoiceCustomerStreetAddress = "42 Dolphin Blv"; model.Buyer.InvoiceCustomerCity = "River City"; model.Buyer.InvoiceCustomerPhoneNumber = "+44.00.00.12.55"; //Add items to invoice model.AddItem(new Template000_ItemDetail() { InvoiceDetailItemLib = "RAM SDRAM PC 1333 Mhz", InvoiceDetailItemUnitPrice = "20,45", InvoiceDetailItemQuantity = "3", InvoiceDetailItemAmount = "61,35" }); model.AddItem(new Template000_ItemDetail() { InvoiceDetailItemLib = "RJ45 LAN connector", InvoiceDetailItemUnitPrice = "2,50", InvoiceDetailItemQuantity = "4", InvoiceDetailItemAmount = "10,00" }); model.AddItem(new Template000_ItemDetail() { InvoiceDetailItemLib = "C19 power supply cable 16A", InvoiceDetailItemUnitPrice = "10,70", InvoiceDetailItemQuantity = "5", InvoiceDetailItemAmount = "53,50" }); model.AddItem(new Template000_ItemDetail() { InvoiceDetailItemLib = "Headphones CZ Bluetooth 4", InvoiceDetailItemUnitPrice = "30,50", InvoiceDetailItemQuantity = "1", InvoiceDetailItemAmount = "30,50" }); model.AddItem(new Template000_ItemDetail() { InvoiceDetailItemLib = "Optical Mouse black edition", InvoiceDetailItemUnitPrice = "19,99", InvoiceDetailItemQuantity = "1", InvoiceDetailItemAmount = "19,99" }); //Fill the Total section model.Total.InvoiceSubTotal = "45,05"; model.Total.InvoiceTaxable = "45,05"; model.Total.InvoiceTaxRate = "20%"; model.Total.InvoiceTaxDue = "10,05"; model.Total.InvoiceTotal = "55,10"; //Generate the final string StringBuilder invoiceHtml = model.MapAndGenerateHtml(); // Generate the pdf with Bytescout's PDF SDK helper using (HtmlToPdfConverter converter = new HtmlToPdfConverter()) { // Create an in memory stream from the initial string var htmlSourceStream = new MemoryStream(System.Text.Encoding.UTF8.GetBytes(invoiceHtml.ToString())); // Create an in memory stream to hold the generated pdf file var destinationStream = new MemoryStream(); // Generates the pdf in memory converter.ConvertHtmlToPdf(htmlSourceStream, destinationStream); // And writes it to disk FileStream file = new FileStream("invoice.pdf", FileMode.Create, FileAccess.Write); destinationStream.WriteTo(file); // Finaly close of opened resources file.Close(); htmlSourceStream.Close(); destinationStream.Close(); } } } }
After running the program, the invoice.pdf file is created at the same folder as the executable file, in the bin/Debug/ directory in the actual case.
Section summary:
In this section, we’ve seen how to generate the invoice pdf file from the HTML template. The invoice layout was entirely made with HTML and CSS, independently of the invoice model object, and separated to the PDF process rendering. HTML and CSS are very popular and commonly used today so this method may be the most suitable one if you’re using them in your work.
Microsoft Office Word documents are largely used in the business for years. Primary versions had the extension .doc and the latest formats are in the .docx extension. They are based on the OpenXML standard. The template file will have the extension .docx thus we will use Microsoft’s OpenXML SDK to process it. To make the printable invoices PDF SDK tools will be added to our solution.
We start with a basic C# console program named “InvoiceSample03”.
Then we add the Open XML SDK.
Right-click the References items > Manage NuGet Packages …
In the browse field, enter OpenXML SDK and choose the Microsoft’s OpenXML SDK 2.5 in the search result then click the Install button
Add the reference to the Bytescout.PDF.Converters.dll (located under the folder C:\Program Files\Bytescout PDF SDK\net4.5\)
Add a reference to the assembly WindowsBase
We also create the Templates folder and copy the provided “InvoiceTemplate-000.docx” file inside it.
The solution should be similar to the figure below:
The invoicetemplate-000 is a normal word document apart from template fields which are identified by double curly brackets.
Copy the following content to the main Program.cs
Program.cs
using Bytescout.PDF.Converters; using DocumentFormat.OpenXml.Packaging; using System; using System.IO; namespace InvoiceSample03 { class Program { static void Main(string[] args) { //Path to the original template file string invoiceTemplate = "../../Templates/InvoiceTemplate-000.docx"; //The name of the template working copy file string invoicePrint = "Invoice001.docx"; //Create the working invoice from the template, overwrites if exist File.Copy(invoiceTemplate, invoicePrint,true); //Process the work document using (WordprocessingDocument wordDoc = WordprocessingDocument.Open(invoicePrint, true)) { //this variable will contains the original content of the template file string docText = null; //Get the original stream from the orignal content using (StreamReader sr = new StreamReader(wordDoc.MainDocumentPart.GetStream())) { docText = sr.ReadToEnd(); } docText = docText.Replace("{{DATE}}",""); docText = docText.Replace("{{INVOICE}}",""); docText = docText.Replace("{{CUSTOMER}}", ""); docText = docText.Replace("{{SELLERNAME}}",""); docText = docText.Replace("{{SELLERADDRESS}}", ""); docText = docText.Replace("{{SELLERSTREET}}", ""); docText = docText.Replace("{{SELLERCITY}}", ""); docText = docText.Replace("{{SELLERLINE}}", ""); { //Description column var descriptionColumnStr = String.Empty; descriptionColumnStr = "RAM SDRAM PC 1333 Mhz " + Environment.NewLine; descriptionColumnStr += "RJ45 LAN connector" + Environment.NewLine; descriptionColumnStr += "C19 power supply cable 16A" + Environment.NewLine; descriptionColumnStr += "Headphones CZBluetooth 4" + Environment.NewLine; descriptionColumnStr += "Optical Mouse black edition" + Environment.NewLine; docText = docText.Replace("{{ITEM_DESCRIPTION}}", descriptionColumnStr); } { //Unit price column var unitPriceColumnStr = String.Empty; unitPriceColumnStr = "20,45" + Environment.NewLine; unitPriceColumnStr += "2,50" + Environment.NewLine; unitPriceColumnStr += "10,70" + Environment.NewLine; unitPriceColumnStr += "30,50" + Environment.NewLine; unitPriceColumnStr += "19,99" + Environment.NewLine; docText = docText.Replace("{{UP}}", unitPriceColumnStr); } { //Quantity column var quantityColumnStr = String.Empty; quantityColumnStr = "3" + Environment.NewLine; quantityColumnStr += "4" + Environment.NewLine; quantityColumnStr += "5" + Environment.NewLine; quantityColumnStr += "1" + Environment.NewLine; quantityColumnStr += "1" + Environment.NewLine; docText = docText.Replace("{{QTY}}", quantityColumnStr); } { //Amount column var amountColumnStr = String.Empty; amountColumnStr = "61,35" + Environment.NewLine; amountColumnStr += "10,00" + Environment.NewLine; amountColumnStr += "53,50" + Environment.NewLine; amountColumnStr += "30,50" + Environment.NewLine; amountColumnStr += "19,99" + Environment.NewLine; docText = docText.Replace("{{PRICE}}", amountColumnStr); } {//Sub total part docText = docText.Replace("{{ST}}", "45,05"); docText = docText.Replace("{{TX}}", "45,05"); docText = docText.Replace("{{TR}}", "20%"); docText = docText.Replace("{{TD}}", "10,05"); docText = docText.Replace("{{TOTAL}}", "55,10"); } //Write to new content to the template file working copy using (StreamWriter sw = new StreamWriter(wordDoc.MainDocumentPart.GetStream(FileMode.Create))) { sw.Write(docText); } } // Create the object to do the DOCX to PDF file using (DocxToPdfConverter converter = new DocxToPdfConverter()) { // Perform conversion and save the pdf in the file "Invoice001.pdf" converter.ConvertDocxToPdf(invoicePrint, "Invoice001.pdf"); } Console.ReadLine(); } } }
You can run the program to the generated pdf file below.
The program can be divided into three major parts:
1 – Create a working copy of the invoice template .docx file and read it’s content.
2- Replace all template’s field identifiers to their actual values.
3- Call the PDF SDK helpers to generate the pdf version.
Step 1 is done by using the OpenXml SDK. You can use this library for almost all your OpenXML developments. It includes Microsoft Office Word, Excel, PowerPoint, and other applications using the Microsoft OpenXml standard format. We only use the WordprocessingDocument object to read the template file’s content.
Step 2 is where we’ve replaced the patterns to values and created the final .docx file.
Step 3 is where we call the PDF SDK DocxToPdfConverter utility to generate the invoice pdf from the .docx file created in step 2.
In terms of software design and architecture, your solution should keep the minimum of dependencies between layers. We advise you to always use the template file when it is possible. It avoids hard-coding and is very flexible since the template file is not embedded into any assembly. You can even place them in a shared folder in the network and update them in the fly without recompiling your program or restarting your application pool.
Performance indicators are also part of the game. We will do some metrics to show you how bad or well each method performs :
Average processing time / PDF SDK method (in ms) – base on the same pdf content. *Workstation with 2 x Intel Xeon e5620 – 8Cores/16 Threads – 2.4Ghz, 24GB Memory |
|
Hard-coding – section 1 Method: Document.Save |
15 |
Exporting HTML to PDF – Section 2 Method: HtmlToPdfConverter.ConvertHtmlToPdf |
369 |
Exporting DOCX to PDF – Section 3 Method: DocxToPdfConverter.ConvertDocxToPdf |
4550 |
As shown in the above table, the method shown in the first section performs very fast with only an average of 15ms to generate an invoice file while the processing time increases by 24 times for creating the invoice from an HTML template. The .docx template is 296 times slower than method 1.
Those metrics can guide you in the way you’ll use your invoice processing. If your business requires very fast processing, you may choose the method shown in section 1 (actually there’s a linear evolution between the number of generated invoices and the processing time). The hard-coding way can generate 1000 invoices in 16 seconds, 2000 invoices in 32 seconds and so on
If the time factor is not critical, the second method is a good alternative for an average processing time of ~370ms. This scenario offers a good mix of performance and maintenance.
The third case is very useful if your business requires a DOCX template, however, the latency time makes it very partial for synchronous processing.
We’ve seen along with this tutorial three ways to make printable invoices with PDF SDK. You can use the one which suits well your skills or the business requirements. The PDF SDK can be plugged into your actual invoice processing software. This can be done with only a couple of line codes and independently of any existing business logic rules, thus minimizing the risk of unexpected bugs. It can be also called from your invoice automation software as batch programs.