0

I'm working with iText 7 on building pdf documents programatically. One of the features that I need to support is the ability to have barcodes in the footer that contains the current page and the total number of pages.

My approach so far is to introduce a page end event handler that will leave a placeholder on the documents footer with a pdf form x object. This event handler also keeps track of all the barcode placeholders that were added to the document and what page they were added on.

When the document is finished writing out all the content, it then loops over all the placeholders, regenerating the barcode with the final content (page x of y) and adds it to where the original barcode was placed.

This seems to work well for the first few pages, but when it hits about 8 / 10 pages the barcodes stop rendering.

It appears that the loop that handles regenerating the barcodes is being handled before last 2 page end events are handled. This seems to be a known thing where some events are being called with the document closed (itext7 end_page events are called when document is closed).

I've tried calling document.flush on the document object to see if I could get the events to fire before the loop is called, but this doesn't appear to have any difference.

I've also tried not immediately flushing the document (both with flushing and not flushing document and the individual pages), but I wasn't sure how to trigger the events, so no barcodes showed up when I did this.

I was hoping that calling flush after all the content has been added would force the events through the handler. Is there some other way to force the events through before closing the document? Or is there a better way to handle this requirement?

Here is the full code sample that I am working with for dotnet and itext7.2.5 nuget package. Any idea if there is a work around for being able to add a different barcode image to each of the footers?

void Main()
{
    var path = System.IO.Path.GetTempFileName();
    using var ms = new FileStream(path, FileMode.Create);

    var pdfDocument = new PdfDocument(new PdfWriter(ms)).SetTagged();
    var document = new Document(pdfDocument);
    
    List<BarcodePlaceHolder> placeholders = new();
    
    var pageEndHandler = new PageEndEventHandler(placeholders);
    pdfDocument.AddEventHandler(PdfDocumentEvent.END_PAGE, pageEndHandler);
    
    // render the content
    for (int i = 0; i < 10; i++)
    {
        if (i > 0)
            document.Add(new AreaBreak(AreaBreakType.NEXT_PAGE));
            
        var paragraph = new Paragraph("page content");
        document.Add(paragraph);
    }

    // handle all the page counts
    foreach (var placeholder in placeholders)
    {
        var pdfCanvas = new PdfCanvas(placeholder.Placeholder, pdfDocument);
        placeholder.Barcode.SetCode($"page {placeholder.PageNumber} of {pdfDocument.GetNumberOfPages()}");
        
        var rectangle = new Rectangle(0, 0, placeholder.Placeholder.GetWidth(), placeholder.Placeholder.GetHeight());
        pdfCanvas.AddXObjectFittedIntoRectangle(placeholder.Barcode.CreateFormXObject(pdfDocument), rectangle);
    }
    
    document.Close();
}

public class BarcodePlaceHolder {
    public PdfFormXObject Placeholder {get; set;}
    public int PageNumber {get; set;}
    public Barcode128 Barcode {get; set;}
}

public class PageEndEventHandler : IEventHandler
{
    List<BarcodePlaceHolder> _placeHolders;
    
    public PageEndEventHandler(List<BarcodePlaceHolder> placeholders)
    {
        _placeHolders = placeholders;
    }
    
    public void HandleEvent(Event @event)
    {
        if (@event is not PdfDocumentEvent docEvent) return;

        PdfDocument pdf = docEvent.GetDocument();
        PdfPage page = docEvent.GetPage();
        
        Barcode128 barcode = new Barcode128(pdf);
        barcode.SetCode("page 0 of 0");
        barcode.SetSize(12);
        Rectangle rect = barcode.GetBarcodeSize();
        
        PdfFormXObject formXObject = new PdfFormXObject(new Rectangle(rect.GetWidth(), rect.GetHeight() + 10));
        Image barcodeImage = new Image(formXObject);
        
        // create the canvas that we can add the details to
        Canvas canvas = new Canvas(page, new Rectangle(0, 0, page.GetPageSize().GetWidth(), 25));
        
        canvas.Add(barcodeImage);
        _placeHolders.Add(new BarcodePlaceHolder() { PageNumber = pdf.GetPageNumber(page), Placeholder = formXObject, Barcode = barcode });
    }
}
Zach C.
  • 21
  • 5
  • Thanks for the feedback @KJ. I'm not sure I follow your questions, but there shouldn't be any limitation on pages. For example, if I expand my loop out to 1000 pages, all but pages 999 and 1000 show the barcode as expected. I'm also letting itext handle creating all the new pages for me. The canvas is created fresh in the HandleEvent and within the for each loop. It almost seems like you're working with a printed media. I'm working directly with pdfs and c# code to create the pdfs. (No html or javascript or printed media). Maybe I'm not following your questions right? – Zach C. Aug 10 '23 at 00:44
  • Exactly, n pages isn't known till the end. There are a few examples around of how to do the x of y (https://stackoverflow.com/questions/38588704/how-to-add-a-page-x-of-y-footer-in-itext-7), but the approach would require a single PdfFormX object that would be used by all the pages. Since the content is a barcode that will be different on each page, that approach won't work. I think there are other work arounds that could be taken, but seem those seem inefficient. For instance rendering it twice, once to get total pages without the barcodes, and once with them. – Zach C. Aug 10 '23 at 01:05
  • I'll review that question. The problem I'm having isn't getting the number of pages though. The barcodes are all correct displaying the correct pageNumber/pageCount. The problem is that the last 2 pages don't have a barcode on them at all. – Zach C. Aug 10 '23 at 01:06

1 Answers1

1

It looks like I was really close to the answer with trying to flush the document manually. Here is the full code sample that shows it working. The key was to flush the document manually, and then after adding the content, flush the document, and the pages and then it displays the barcode on all pages as expected.

public void Main()
{
var path = "../../../resources/fd59580/out.pdf";
using var ms = new FileStream(path, FileMode.Create);

var pdfDocument = new PdfDocument(new PdfWriter(ms)).SetTagged();
// set 'immediateFlush' parameter to false 
var document = new Document(pdfDocument,PageSize.A4,false);
List<BarcodePlaceHolder> placeholders = new();

var pageEndHandler = new PageEndEventHandler(placeholders);
pdfDocument.AddEventHandler(PdfDocumentEvent.END_PAGE, pageEndHandler);

// render the content
for (int i = 0; i < 10; i++)
{
    if (i > 0)
        document.Add(new AreaBreak(AreaBreakType.NEXT_PAGE));

    var paragraph = new Paragraph("page content");
    document.Add(paragraph);
    
} 
document.Flush();
for (int i = 1; i <= pdfDocument.GetNumberOfPages(); i++)

    pdfDocument.GetPage(i).Flush();

// handle all the page counts
foreach (var placeholder in placeholders)
{
    var pdfCanvas = new PdfCanvas(placeholder.Placeholder, pdfDocument);
    placeholder.Barcode.SetCode($"page {placeholder.PageNumber} of {pdfDocument.GetNumberOfPages()}");

    var rectangle = new Rectangle(0, 0, placeholder.Placeholder.GetWidth(),
        placeholder.Placeholder.GetHeight());
    pdfCanvas.AddXObjectFittedIntoRectangle(placeholder.Barcode.CreateFormXObject(pdfDocument), rectangle);
}

document.Close();
}
Zach C.
  • 21
  • 5