Search code examples
python-3.xpdf-generationpypdf

Python/PyPDF4: How do I specify the /PageLabels in the created PDF?


I am using PyPDF4 to create an offline-readable version of the journal "Nature".

I use PyPDF4 PdfFileReader to read the individual article PDFs and PdfFileWriter to create a single, merged ouput.

The problem that I am trying to solve is that the page numbers of some issues do not start at 1, for example, issue 7805 starts with page 563.

How do I specify the desired /PageLabels in the document catalog?

    for pdf_file in pdf_files:
        input_pdf = PdfFileReader(open(pdf_file, 'rb'))
        page_indices = file_page_dictionary[pdf_file]
        for page_index in page_indices:
            page = input_pdf.getPage(page_index)

            # Specify actual page number here:
            # page.setPageNumber(actual_page_numbers[page_index])

            output.addPage(page)

    with open(pdf_output_name, 'wb') as f:
        output.write(f)

Solution

  • After exploring the PDF standard and a bit of hacking, I found that the following function will add a single PageLabels entry that creates page lables starting from offset (i.e. the first page will be labelled the offset, the second page, offset+1, etc.).

    # output_pdf is an instance of PdfFileWriter().
    # offset is the desired page offset.
    def add_pagelabels(output_pdf, offset):
        number_type = PDF.DictionaryObject()
        number_type.update({PDF.NameObject("/S"):PDF.NameObject("/D")})
        number_type.update({PDF.NameObject("/St"):PDF.NumberObject(offset)})
    
        nums_array = PDF.ArrayObject()
        nums_array.append(PDF.NumberObject(0)) # physical page index
        nums_array.append(number_type)
    
        page_numbers = PDF.DictionaryObject()
        page_numbers.update({PDF.NameObject("/Nums"):nums_array})
    
        page_labels = PDF.DictionaryObject()
        page_labels.update({PDF.NameObject("/PageLabels"): page_numbers})
    
        root_obj = output_pdf._root_object
        root_obj.update(page_labels)
    

    Additional page label entries can be created (i.e. with different offsets or different numbering styles).

    Note that the first PDF page has an index of 0.

    # Use PyPDF to manipulate pages
    from PyPDF4 import PdfFileWriter, PdfFileReader 
    
    # To manipulate the PDF dictionary
    import PyPDF4.pdf as PDF
    
    def pdf_pagelabels_roman():
        number_type = PDF.DictionaryObject()
        number_type.update({PDF.NameObject("/S"):PDF.NameObject("/r")})
        return number_type
    
    def pdf_pagelabels_decimal():
        number_type = PDF.DictionaryObject()
        number_type.update({PDF.NameObject("/S"):PDF.NameObject("/D")})
        return number_type
    
    def pdf_pagelabels_decimal_with_offset(offset):
        number_type = pdf_pagelabels_decimal()
        number_type.update({PDF.NameObject("/St"):PDF.NumberObject(offset)})
        return number_type
    
    ...
        nums_array = PDF.ArrayObject()
        # Each entry consists of an index followed by a page label...
        nums_array.append(PDF.NumberObject(0))  # Page 0:
        nums_array.append(pdf_pagelabels_roman()) # Roman numerals
    
        # Each entry consists of an index followed by a page label...
        nums_array.append(PDF.NumberObject(1)) # Page 1 -- 10:
        nums_array.append(pdf_pagelabels_decimal_with_offset(first_offset)) # Decimal numbers, with Offset
    
        # Each entry consists of an index followed by a page label...
        nums_array.append(PDF.NumberObject(10)) # Page 11 --> :
        nums_array.append(pdf_pagelabels_decimal_with_offset(second_offset))
    
    
        page_numbers = PDF.DictionaryObject()
        page_numbers.update({PDF.NameObject("/Nums"):nums_array})
    
        page_labels = PDF.DictionaryObject()
        page_labels.update({PDF.NameObject("/PageLabels"): page_numbers})
    
        root_obj = output._root_object
        root_obj.update(page_labels)