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
Cssrewriteurltransform with or Without Virtual Directory
Connect to Active Directory via Ldap
How to Get a List<String> Collection of Values from App.Config in Wpf
How to Get Byte Array Properly from an Web API Method in C#
Wpf - Set Focus When a Button Is Clicked - No Code Behind
How to Get Status Code from Webclient
How to Extend C# Built-In Types, Like String
How to Make Observablecollection Thread-Safe
C# Iterate Through Class Properties
Password Encryption/Decryption Code in .Net
Enumerating Collections That Are Not Inherently Ienumerable
How to Fix Wpf Error: "Program Does Not Contain a Static 'Main' Method Suitable for an Entry Point"
Check If All Items Are the Same in a List
Lowering Priority of Task.Factory.Startnew Thread
How to Show a Image in Database in the Image Control of ASP.NET