I'm trying to make a PDF with a footer on it using iTextSharper. This is working fine, for the most part. I want to add a footer to each page.
My code does add the footer correctly, however, the text is overlapping with the content of the PDF as you can see in the image:
The controller action:
public ActionResult GeneratePDF(int id)
{
Order order = db.Orders.Where(x => x.ID == id).SingleOrDefault();
return new HFPdfResult(order, "OrderPDF");
}
The HFPdfResult (ViewResult
) class:
using System.Web.Mvc;
using RentproDC.Models.RazorPDF;
namespace RentproDC.Models.RazorPDF
{
public class HFPdfResult : ViewResult
{
//Constructors
public HFPdfResult(object model, string name)
{
ViewData = new ViewDataDictionary(model);
ViewName = name;
}
public HFPdfResult() : this(new ViewDataDictionary(), "Pdf")
{
}
public HFPdfResult(object model) : this(model, "Pdf")
{
}
//Override FindView to load PdfView
protected override ViewEngineResult FindView(ControllerContext context)
{
var result = base.FindView(context);
if (result.View == null)
return result;
var pdfView = new HFPdfView(result);
return new ViewEngineResult(pdfView, pdfView);
}
}
}
The HFPdfView class:
using System;
using System.Collections;
using System.IO;
using System.Text;
using System.Web.Mvc;
using System.Xml;
using iTextSharp.text;
using iTextSharp.text.html;
using iTextSharp.text.pdf;
using iTextSharp.text.xml;
using iTextSharp.text.html.simpleparser;
using System.Collections.Generic;
namespace RentproDC.Models.RazorPDF
{
public class HFPdfView : IView, IViewEngine
{
private readonly ViewEngineResult _result;
public HFPdfView(ViewEngineResult result)
{
_result = result;
}
public void Render(ViewContext viewContext, TextWriter writer)
{
// generate view into string
var sb = new System.Text.StringBuilder();
TextWriter tw = new System.IO.StringWriter(sb);
_result.View.Render(viewContext, tw);
var resultCache = sb.ToString();
// detect itext (or html) format of response
XmlParser parser;
using (var reader = GetXmlReader(resultCache))
{
while (reader.Read() && reader.NodeType != XmlNodeType.Element)
{
// no-op
}
if (reader.NodeType == XmlNodeType.Element && reader.Name == "itext")
parser = new XmlParser();
else
parser = new HtmlParser();
}
// Create a document processing context
var document = new Document(PageSize.A4, 36, 36, 36, 120);
document.Header = new HeaderFooter(new Phrase("Test header"), false);
document.Open();
// associate output with response stream
var pdfWriter = PdfWriter.GetInstance(document, viewContext.HttpContext.Response.OutputStream);
pdfWriter.PageEvent = new PdfFileEvents();
pdfWriter.CloseStream = false;
// this is as close as we can get to being "success" before writing output
// so set the content type now
viewContext.HttpContext.Response.ContentType = "application/pdf";
// parse memory through document into output
using (var reader = GetXmlReader(resultCache))
{
parser.Go(document, reader);
}
pdfWriter.Close();
}
private static XmlTextReader GetXmlReader(string source)
{
byte[] byteArray = Encoding.UTF8.GetBytes(source);
MemoryStream stream = new MemoryStream(byteArray);
var xtr = new XmlTextReader(stream);
xtr.WhitespaceHandling = WhitespaceHandling.None; // Helps iTextSharp parse
return xtr;
}
public ViewEngineResult FindPartialView(ControllerContext controllerContext, string partialViewName,
bool useCache)
{
throw new System.NotImplementedException();
}
public ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName,
bool useCache)
{
throw new System.NotImplementedException();
}
public void ReleaseView(ControllerContext controllerContext, IView view)
{
_result.ViewEngine.ReleaseView(controllerContext, _result.View);
}
}
public class PdfFileEvents : PdfPageEventHelper
{
public override void OnEndPage(PdfWriter pi, Document doc)
{
PdfContentByte cb = pi.DirectContent;
ColumnText ct = new ColumnText(cb);
List list = new List(List.ORDERED);
string text = "<ol><li><span style='color: rgb(34, 34, 34); font-family: arial, sans-serif; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; letter-spacing: normal; line-height: 16.12px; orphans: auto; text-align: left; text-indent: 0px; text-transform: none; white-space: normal; widows: 1; word-spacing: 0px; -webkit-text-stroke-width: 0px; display: inline !important; float: none; background-color: rgb(255, 255, 255); font-size: 9px;'>"+RentProModels.Models.Settings.Get("CompanyName") + "<br />" + RentProModels.Models.Settings.Get("CompanyTelephone") + "<br />" +
RentProModels.Models.Settings.Get("CompanyEmail") + "<br />" +
"KVK " + "1234568789" + "<br />" +
"Bank ABN01Blahh12341</span></li></ol>";
ArrayList htmlarraylist = HTMLWorker.ParseToList(new StringReader(text), null);
for (int k = 0; k < htmlarraylist.Count; k++)
{
list.Add((IElement)htmlarraylist[k]);
}
ct.AddElement(list);
ct.SetSimpleColumn(500, 79, 900, 5); //curPos = verder naar boven
ct.Go();
}
public override void OnStartPage(PdfWriter pi, Document doc)
{
PdfContentByte cb = pi.DirectContent;
ColumnText ct = new ColumnText(cb);
cb.BeginText();
cb.SetFontAndSize(BaseFont.CreateFont(BaseFont.TIMES_ROMAN, BaseFont.CP1252, BaseFont.NOT_EMBEDDED), 12.0f);
cb.SetTextMatrix(doc.LeftMargin, doc.PageSize.Height - doc.TopMargin);
cb.ShowText(String.Format("{0} {1}", "Dit is een", "Header"));
cb.EndText();
}
}
}
Order view (it loads the Order
object into this view, and generates a PDF based on that):
@model RentPro.Models.Tables.Order
@using RentProModels.Models
@using RentPro.Models.Tables
@{
Layout = null;
}
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title></title>
</head>
<body style="font-size: 12px;">
<table width="100%" widths="60;40">
<row>
<td>
<newline />
<newline />
<h2 style="text-decoration:underline;">Pakbon</h2>
</td>
<td>
@* <img url="@System.Web.Hosting.HostingEnvironment.MapPath("/")@Settings.Get("PicturesPath") /../Images/@Settings.Get("LogoFile")" width="239" height="83" />*@
<newline />
<newline />
</td>
</row>
</table>
<table width="100%" cellpadding="0.0" widths="50;17;30">
@if (@Model.Delivery.Company != "nvt")
{
<row>
<cell>
<p style="font-weight:bold;">@Model.Delivery.Company</p>
</cell>
<cell></cell>
<cell></cell>
</row>
}
<row>
<cell><p style="font-weight:bold;">@Model.Delivery.FirstName @Model.Delivery.LastName</p></cell>
<cell>Order Nr:</cell>
<cell>@Model.ID</cell>
</row>
<row>
<cell><p style="font-weight:bold;">@Model.Delivery.StreetName @Model.Delivery.HouseNumber</p></cell>
<cell>Order Datum:</cell>
<cell>@Model.PlaceDate.ToShortDateString()</cell>
</row>
<row>
<cell><p style="font-weight:bold;">@Model.Delivery.ZipCode, @Model.Delivery.City</p></cell>
<cell>Transport:</cell>
<cell>@Model.TransportCarrier.Name</cell>
</row>
<row>
<cell><p style="font-weight:bold;">@Model.Phonenumber</p></cell>
<cell>Start Datum:</cell>
<cell>@Model.StartDate.ToShortDateString()</cell>
</row>
<row>
@{ string KVKofBTW;
if (Model.KVKnummer != null && Model.KVKnummer != "")
{
if (Model.Billing.Country.Name == "Nederland")
{
KVKofBTW = "KVK:";
}
else
{
KVKofBTW = "BTW:";
}
}
else
{
KVKofBTW = "";
}
}
<cell><p style="font-weight:bold;">@KVKofBTW @Model.KVKnummer</p></cell>
<cell>Eind Datum:</cell>
<cell>@Model.EndDate.ToShortDateString()</cell>
</row>
<row>
<cell></cell>
<cell>Aantal Dagen:</cell>
<cell>@((Model.EndDate.Date - Model.StartDate.Date).Days + 1)</cell>
</row>
<row>
<cell>Opmerkingen:</cell>
<cell></cell>
<cell></cell>
</row>
<row>
<cell colspan="3">@Model.Note</cell>
</row>
</table>
<table width="100%" widths="10;60" cellpadding="2">
<row>
<cell>
<newline />
</cell>
<cell></cell>
</row>
<row>
<cell><p style="font-style:italic;">Aantal</p></cell>
<cell><p style="font-style:italic;">Artikel</p></cell>
</row>
@foreach (var item in Model.Items)
{
<row>
<cell>@item.ProductCode</cell>
<cell>@item.Amount</cell>
<cell>@item.ProductTitle</cell>
<cell> [ ]</cell>
<cell> [ ]</cell>
<cell> [ ]</cell>
</row>
if (RentProModels.Models.Settings.GetBool("PakBonAccessory"))
{
foreach (RentPro.Models.Tables.Accessory accesory in item.Product.Accessories)
{
<row>
<cell></cell>
<cell></cell>
<cell>@accesory.Name</cell>
<cell> [ ]</cell>
<cell> [ ]</cell>
<cell> [ ]</cell>
</row>
}
}
}
</table>
<table width="100%" widths="60;50">
<row>
<cell>
<newline />
<newline />
<newline />
<newline />
<newline />
<newline />
</cell>
<cell></cell>
</row>
<row>
<cell>Handtekening voor ontvangst:</cell>
<cell>Retour ontvangen door:</cell>
</row>
<row>
<cell>Naam Klant:</cell>
<cell>Naam:</cell>
</row>
<row>
<cell>Handtekening:</cell>
<cell>Handtekening:</cell>
</row>
</table>
</body>
</html>
I saw a SO post about setting the document's size, I do that, but it doesn't seem to work:
var document = new Document(PageSize.A4, 36, 36, 36, 120);
I gave it a margin of 120 user units on the bottom side, and I only use 110 so why is it still overlapping?
Any help would be greatly appreciated.
After some messing around I decided that it was indeed best to update my project to use a more recent version of iTextSharp.
I've completely gotten rid of the RazorPDF
library and now work with iTS 5.5.8. I thought I'd share my updated code incase everyone ever runs into this problem.
This has also fixed the problem in my OP about the footer overlapping with the PDF content.
New code:
public class RazorPdf
{
public static byte[] GeneratePdf(string html,
System.Collections.Generic.List<PdfPageContent> headerAndFooterContent = null)
{
Byte[] bytes;
using (var ms = new MemoryStream())
{
using (var doc = new Document(PageSize.A4, 40f, 40f, 30f, 50f))
{
using (var writer = PdfWriter.GetInstance(doc, ms))
{
doc.Open();
var example_css = @".headline{font-size:200%}"; //incase you want to parse css
writer.PageEvent = new PageEventHelper(headerAndFooterContent);
using (var msCss = new MemoryStream(System.Text.Encoding.UTF8.GetBytes(example_css)))
{
using (var msHtml = new MemoryStream(System.Text.Encoding.UTF8.GetBytes(html)))
{
iTextSharp.tool.xml.XMLWorkerHelper.GetInstance().ParseXHtml(writer, doc, msHtml, msCss);
}
}
doc.Close();
}
}
bytes = ms.ToArray();
}
return bytes;
}
}
public class PageEventHelper : PdfPageEventHelper
{
private PdfContentByte _cb;
private PdfTemplate _template;
private readonly System.Collections.Generic.List<PdfPageContent> _content = null;
public PageEventHelper(System.Collections.Generic.List<PdfPageContent> content)
{
_content = content;
}
public override void OnOpenDocument(PdfWriter writer, Document document)
{
_cb = writer.DirectContent;
_template = _cb.CreateTemplate(50, 50);
}
public override void OnEndPage(PdfWriter writer, Document document)
{
base.OnEndPage(writer, document);
/* Contents */
if (_content != null)
{
foreach (PdfPageContent ppc in _content)
{
ColumnText ct = new ColumnText(GetCb(writer));
Phrase phrase = new Phrase(new Chunk(ppc.Content, FontFactory.GetFont(FontFactory.HELVETICA, 12, Font.NORMAL)));
switch (ppc.Location)
{
case PdfPageLocation.Header:
ct.SetSimpleColumn(phrase, 40, 700, 530, 840, 25, ppc.Alignment);
break;
case PdfPageLocation.Footer:
ct.SetSimpleColumn(phrase, 40, 100, 530, 20, 25, ppc.Alignment);
break;
}
ct.Go();
}
}
/* Page number */
string text = writer.PageNumber.ToString();
BaseFont font = BaseFont.CreateFont();
float len = font.GetWidthPoint(text, 12);
Rectangle pageSize = document.PageSize;
GetCb(writer).SetRGBColorFill(100, 100, 100);
GetCb(writer).BeginText();
GetCb(writer).SetFontAndSize(font, 12);
GetCb(writer).SetTextMatrix(document.RightMargin, pageSize.GetBottom(document.BottomMargin));
GetCb(writer).ShowText(text);
GetCb(writer).EndText();
GetCb(writer).AddTemplate(GetTemplate(writer), document.RightMargin - len, 0);
}
private PdfContentByte GetCb(PdfWriter writer)
{
return _cb ?? (_cb = writer.DirectContent);
}
private PdfTemplate GetTemplate(PdfWriter writer)
{
return _template ?? (_template = GetCb(writer).CreateTemplate(50, 50));
}
}
public class PdfPageContent
{
public PdfPageLocation Location { get; set; }
public int Alignment { get; set; }
public string Content { get; set; }
}
public enum PdfPageLocation
{
Footer = 1,
Header = 2
}
ControllerExtension:
public class Controller : System.Web.Mvc.Controller
{
public FileContentResult PdfFileResult(string viewPath, object model = null)
{
List<PdfPageContent> content = new List<PdfPageContent>();
content.Add(new PdfPageContent() { Location = PdfPageLocation.Footer, Alignment = Element.ALIGN_LEFT, Content = "FOOTER LEFT" });
content.Add(new PdfPageContent() { Location = PdfPageLocation.Footer, Alignment = Element.ALIGN_RIGHT, Content = "FOOTER RIGHT" });
content.Add(new PdfPageContent() { Location = PdfPageLocation.Header, Alignment = Element.ALIGN_LEFT, Content = "HEADER LEFT" });
content.Add(new PdfPageContent() { Location = PdfPageLocation.Header, Alignment = Element.ALIGN_RIGHT, Content = "HEADER RIGHT" });
return new FileContentResult(RazorPdf.GeneratePdf(RenderViewToString(viewPath, model), content), "application/pdf");
}
public bool SavePdfFileResult(string viewPath, string relativeFilePath, object model = null)
{
byte[] data = RazorPdf.GeneratePdf(RenderViewToString(viewPath, model));
if (!string.IsNullOrWhiteSpace(relativeFilePath))
{
try
{
System.IO.File.WriteAllBytes(Server.MapPath(relativeFilePath), data);
}
catch (Exception e)
{ //In case of exception, file write has failed
return false;
}
return true;
}
return false;
}
public string RenderViewToString(string viewPath, object model = null, bool partial = false)
{
ViewEngineResult viewEngineResult = partial ? ViewEngines.Engines.FindPartialView(ControllerContext, viewPath) :
ViewEngines.Engines.FindView(ControllerContext, viewPath, null);
if (viewEngineResult == null)
throw new FileNotFoundException("View cannot be found.");
var view = viewEngineResult.View;
ControllerContext.Controller.ViewData.Model = model;
string result = null;
using (var sw = new StringWriter())
{
var ctx = new ViewContext(ControllerContext, view, ControllerContext.Controller.ViewData, ControllerContext.Controller.TempData, sw);
view.Render(ctx, sw);
result = sw.ToString();
}
return result;
}
}
Usage in controller:
public ActionResult GeneratePDF(int id)
{
Order order = db.Orders.SingleOrDefault(x => x.ID == id);
return PdfFileResult("/Views/Order/OrderPDF.cshtml", order);
}