Booklet page layout with ReportLab

798 Views Asked by At

I have a Python program that generates a PDF file with ReportLab, and I'd like to generate another PDF that is formatted to have each page cut in half, then folded and stapled together into a booklet.

For example, if my current document contains pages A, B, C, D, E, F, G, and H, I want the new document to have two pages that I can print double sided to come out like this:

BG|HA
DE|FC

I have seen this order referred to as 4-up or imposition.

My printer has an option for printing four pages to each sheet, but it doesn't reorder the pages. If I print the current document with that setting and double sided, it comes out like this:

AB|EF
CD|GH

My first preference is to generate a PDF with four pages per sheet as shown. If I can't figure that out, the next best thing would be to generate a PDF with reordered pages so that my printer can print them four pages to each sheet. In other words, reorder the pages to B, G, D, E, H, A, F, and C.

Here's a code example that prints eight pages:

from subprocess import call

from reportlab.lib import pagesizes
from reportlab.lib.styles import getSampleStyleSheet
from reportlab.lib.units import inch
from reportlab.platypus import SimpleDocTemplate, Paragraph


def main():
    pdf_path = 'booklet.pdf'
    doc = SimpleDocTemplate(pdf_path,
                            pagesize=pagesizes.letter,
                            topMargin=0.625*inch,
                            bottomMargin=0.625*inch)
    styles = getSampleStyleSheet()
    paragraph_style = styles['Normal']
    print(paragraph_style.fontSize, paragraph_style.leading)
    paragraph_style.fontSize = 300
    paragraph_style.leading = 360
    story = []
    for text in 'ABCDEFGH':
        flowable = Paragraph(text, paragraph_style)
        story.append(flowable)

    doc.build(story)

    # call(["evince", pdf_path])  # launch PDF viewer


main()
1

There are 1 best solutions below

0
Don Kirkby On

Thanks to this question, I could see how to collect canvas pages before actually writing them into the document. I added some code to reorder them, and now I can print them with my extra printer option for 4 pages to each sheet.

from subprocess import call

from reportlab.lib import pagesizes
from reportlab.lib.styles import getSampleStyleSheet
from reportlab.lib.units import inch
from reportlab.pdfgen.canvas import Canvas
from reportlab.platypus import SimpleDocTemplate, Paragraph


class BookletCanvas(Canvas):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.pages = []

    def showPage(self):
        self.pages.append(dict(self.__dict__))
        self._startPage()

    def save(self):
        while len(self.pages) % 8 != 0:
            self.showPage()
        original_pages = self.pages[:]
        reordered_pages = []
        while original_pages:
            reordered_pages.append(original_pages.pop(1))
            reordered_pages.append(original_pages.pop(-2))
            reordered_pages.append(original_pages.pop(2))
            reordered_pages.append(original_pages.pop(-3))
            reordered_pages.append(original_pages.pop(-1))
            reordered_pages.append(original_pages.pop(0))
            reordered_pages.append(original_pages.pop(-1))
            reordered_pages.append(original_pages.pop(0))
        for page in reordered_pages:
            self.__dict__.update(page)
            super().showPage()
        super().save()


def main():
    pdf_path = 'booklet.pdf'
    doc = SimpleDocTemplate(pdf_path,
                            pagesize=pagesizes.letter,
                            topMargin=0.625*inch,
                            bottomMargin=0.625*inch)
    styles = getSampleStyleSheet()
    paragraph_style = styles['Normal']
    print(paragraph_style.fontSize, paragraph_style.leading)
    paragraph_style.fontSize = 300
    paragraph_style.leading = 360
    story = []
    for text in 'ABCDEFGH':
        flowable = Paragraph(text, paragraph_style)
        story.append(flowable)

    doc.build(story, canvasmaker=BookletCanvas)

    # call(["evince", pdf_path])  # launch PDF viewer


main()

I'd still prefer to do the 4 pages to each sheet inside the PDF, but this will do for now.