Itextsharp Creating a Footer Page # of #

Hyperlink at footer using itextSharp

The Document object generally lets you work with abstract things like Paragraph and Chunk but in doing so you lose absolute positioning. The PdfWriter and PdfContentByte objects give you absolute positioning but you need to work with lower level objects like raw text.

Luckily there is a happy middle-ground object called ColumnText that should do what you're looking for. You can think of the ColumnText as basically a table and most people use it as a single column table so you can actually just think of it as a rectangle that you add objects to. See the comments in the code below for any questions.

public class PdfHandlerEvents : PdfPageEventHelper {
private PdfContentByte _cb;
private BaseFont _bf;

public override void OnOpenDocument(PdfWriter writer, Document document) {
_cb = writer.DirectContent;
}

public override void OnEndPage(PdfWriter writer, Document document) {
base.OnEndPage(writer, document);

_bf = BaseFont.CreateFont(BaseFont.HELVETICA, BaseFont.CP1252, BaseFont.NOT_EMBEDDED);
iTextSharp.text.Rectangle pageSize = document.PageSize;

//Create our ColumnText bound to the canvas
var ct = new ColumnText(_cb);
//Set the dimensions of our "box"
ct.SetSimpleColumn(pageSize.GetRight(200), pageSize.GetBottom(30), pageSize.Right, pageSize.Bottom);
//Create a new chunk with our text and font
var c = new Chunk("More Information", new iTextSharp.text.Font(_bf, 10));
//Set the chunk's action to a remote URL
c.SetAction(new PdfAction("http://www.aol.com"));
//Add the chunk to the ColumnText
ct.AddElement(c);
//Tell the ColumnText to draw itself
ct.Go();

}
}

Add a page number and text aligned as footer to every pdf page

By default to decrease memory consumption a bit the pages of the PDF document you are creating are flushed as they fill up.

It means that when the content is written e.g. on page 3 of the document, the content of the page 1 is already written to the disk and it's too late to add more content there.

One option is to first create a document and close it, then open it with reader and writer, i.e.: new PdfDocument(PdfReader, PdfWriter), create Document around PdfDocument again and append content to the document as you do now:

public void FillPdf(string filename)
{
{
using var pdfWriter = new PdfWriter(tempFilename);
using var pdfDoc = new PdfDocument(pdfWriter);
using var doc = new Document(pdfDoc, PageSize.LETTER);

doc.SetMargins(12, 12, 36, 12);

AddData(doc);
doc.Close();
}

{
using var pdfWriter = new PdfWriter(filename);
using var pdfReader = new PdfReader(tempFilename);
using var pdfDoc = new PdfDocument(pdfReader, pdfWriter);
using var doc = new Document(pdfDoc, PageSize.LETTER);

doc.SetMargins(12, 12, 36, 12);

AddFooter(doc);
doc.Close();
}
}

Second option is not to flush the content as soon as possible and keep it in memory instead. You can do that by only making a slight modification in your code: pass third parameter to Document constructor

using var doc = new Document(pdfDoc, PageSize.LETTER, false);

Please keep in mind that second option might result in additional empty page being created at the end of your document in some corner cases (e.g. when you add such a large image to your document that it doesn't even fit into a full page), so use it carefully. It should not create any problems with you are dealing with regular simple content though

Header, footer and large tables with iTextSharp

In your OnEndPage method you have this line:

head.WriteSelectedRows(0, -1, 10, page.Height - cellHeight + head.TotalHeight - 30, writer.DirectContent);

That code correctly calculates where to put content based on the page's height and top margin but also includes a magical 30 in there which is causing the header to be drawn on top of the table. Change it to this and your header will be fine.

head.WriteSelectedRows(0, -1, 10, page.Height - cellHeight + head.TotalHeight, writer.DirectContent);

I'm guessing that that 30 is trying to include some padding between your header and the table itself. What I would recommend is actually changing the document's margins themselves in the main code:

document.SetMargins(document.LeftMargin, document.RightMargin, document.TopMargin + 30, document.BottomMargin);

And then accounting for that in the OnEndPage method:

float cellHeight = document.TopMargin - 30;

Your footer code doesn't actually account for the bottom margin and just draws it at 50 so this will always overlap. The quick fix would be to change it to:

footer.WriteSelectedRows(0, -1, 10, footer.TotalHeight, writer.DirectContent);

This will at least get the footer bottom-aligned. If you want some more padding like above just adjust the document margins again:

document.SetMargins(document.LeftMargin, document.RightMargin, document.TopMargin + 30, document.BottomMargin + 30);

Using iTextSharp for HTML to PDF with images on header, footer and body, Cannot access a closed Stream

Indeed, what you try to do is not supported by the XMLWorker architecture. (I'm actually a bit surprised about this; I think I even in some earlier comment here or there claimed it should work.)

In short

The XMLWorker architecture does not allow overlapping usages from the same thread. It does allow overlapping usages from different threads and non-overlapping usages in any threading situation, though.

As your headers and footers appear not to depend on the actual page contents, I'd recommend you switch to a two-pass process: In the first pass create the document body using a Document and a PdfWriter as above, merely without the event listener; in the second pass stamp the headers and footers onto it using a PdfReader and a PdfStamper. Alternatively you can try the work-around posted at the end of this answer.

In detail

The XMLWorker keeps a context for the current parsing operation in a Thread Data Slot (in Java in a ThreadLocal).

The contents of the context in the current thread are initialized at the start of XMLParser.ParseWithReader, and the whole context of the current thread is dropped at its end.

The method XMLParser.ParseWithReader eventually is used by each XMLParser.Parse overload and also by each XMLWorkerHelper.parseXHtml overload.

As soon as two parsing attempts using the XMLWorker architecture - e.g. parsing attempts in a page event listener and a parsing attempt filling the body (unless the latter is clearly limited to a single page) - overlap, therefore, they get into each other's way and the attempt finishing first removes the context also used by the other.

A work-around

There is a work-around for this issue, at least for situations like the one at hand, if one is ok with using reflection and one's runtime environment allows that:

Whenever one switches to another attempt, one can store the current context value and replace it with a value appropriate for the parsing coming up. As context is declared private, this requires reflection.

In the case at hand that would be:

public class HtmlPageEventHelper : PdfPageEventHelper
{
List<PDFFragmentViewModel> _fragments;
FieldInfo context;

public HtmlPageEventHelper(List<PDFFragmentViewModel> fragments)
{
this._fragments = fragments;
context = typeof(XMLWorker).GetField("context", BindingFlags.NonPublic | BindingFlags.Static);
}

public override void OnEndPage(PdfWriter writer, iTextSharp.text.Document document)
{
[...]
// parse
XMLWorker worker = new XMLWorker(cssPipeline, true);
XMLParser parser = new XMLParser(worker);

LocalDataStoreSlot contextSlot = (LocalDataStoreSlot) context.GetValue(worker);
object contextData = Thread.GetData(contextSlot);
Thread.SetData(contextSlot, null);

try
{
foreach (var _frag in _fragments)
{
ColumnText ct = new ColumnText(writer.DirectContent);

//using (var reader = new StringReader(_frag.Content))
//{
// parser.Parse(reader);
//}

_instance.ParseXHtml(new ColumnTextElementHandler(ct), new StringReader(_frag.Content));
//ct.SetSimpleColumn(document.Left, document.Top, document.Right, document.GetTop(-20), 10, Element.ALIGN_MIDDLE);
ct.SetSimpleColumn(
_frag.LLX.HasValue ? document.GetLeft(_frag.LLX.Value) : document.Left,
_frag.LLY.HasValue ? document.GetTop(_frag.LLY.Value) : document.Top,
_frag.URX.HasValue ? document.GetRight(_frag.URX.Value) : document.Right,
_frag.URY.HasValue ? document.GetBottom(_frag.URY.Value) : document.Bottom,
_frag.Leading, _frag.Alignment);
ct.Go();
}
}
finally
{
Thread.SetData(contextSlot, contextData);
}
}
}

VB.net add a header/footer to every page of a PDF using iText7

With some efforts, I could implement PAGE_END event in vb.net. Here is the code for you.

(A) In main module create pdf routine add:

*Dim HandlerRLA = New VariableHeaderEventHandlerRLA
PDFfile.AddEventHandler(PdfDocumentEvent.END_PAGE, HandlerRLA)*

(B) Add anlother class after End Class. You may add text/paragraph as per requirement. I have used image as Header and Footer on specific pages.

Public Class VariableHeaderEventHandlerRLA
Implements IEventHandler
Dim header As String
Dim doc As PdfDocument
Public Sub TextFooterEventHandler(ByRef doc As PdfDocument)
Me.doc = doc
End Sub
Public Sub HandleEvent([event2] As [Event]) Implements IEventHandler.HandleEvent
Dim docEvent1 As PdfDocumentEvent = event2
Dim canvas1 As PdfCanvas = New PdfCanvas(docEvent1.GetPage())
Dim pageSize1 As iText.Kernel.Geom.Rectangle = docEvent1.GetPage().GetPageSize()
'Dim canvas As Canvas = New Canvas(docEvent.GetPage(), New iText.Kernel.Geom.Rectangle(0, 0, pageSize.GetWidth(), pageSize.GetHeight))
Dim PDoc1 As PdfDocument = docEvent1.GetDocument()
Dim Page1 = docEvent1.GetPage()
Dim PageNo1 As Integer = PDoc1.GetPageNumber(Page1)

If PageNo1 > 1 Then
Dim imageFile, BottomImage As String
imageFile = "path to image folder\secondtop.bmp"
Dim data3 = ImageDataFactory.Create(imageFile)

BottomImage = "path to image folder\secondbottom2.bmp"
Dim data4 = ImageDataFactory.Create(BottomImage)

Dim Ratio = data3.GetHeight / data3.GetWidth
Dim rect As iText.Kernel.Geom.Rectangle = New iText.Kernel.Geom.Rectangle(0, 784, 595, 595 * Ratio)

With canvas1
.AddImage(data3, 0, 784, 595, 0)
'.AddImageFittedIntoRectangle(data3, rect, 0)

Ratio = data4.GetHeight / data4.GetWidth
rect = New iText.Kernel.Geom.Rectangle(0, 0, 595, 595 * Ratio)
'.AddImageFittedIntoRectangle(data4, rect, 0)
.AddImage(data4, 0, 0, 595, 0)
End With
End If
'Throw New NotImplementedException()
End Sub
End Class


Related Topics



Leave a reply



Submit