1

I'm using reportlab for the first time as part of an application I'm building to produce business correspondence. I've got a toy example working as follows:

# -*- coding: utf-8 -*-
import sys
import time
from reportlab.lib.enums import TA_JUSTIFY, TA_LEFT, TA_RIGHT
from reportlab.lib.pagesizes import A4
from reportlab.pdfgen import canvas
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.units import mm
from reportlab.platypus import (
        Image,
        Paragraph,
        SimpleDocTemplate,
        Spacer,
        Table,
    )


class NumberedCanvas(canvas.Canvas):
    def __init__(self, *args, **kwargs):
        canvas.Canvas.__init__(self, *args, **kwargs)
        self._saved_page_states = []

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

    def save(self):
        """add page info to each page (page x of y)"""
        num_pages = len(self._saved_page_states)
        for state in self._saved_page_states:
            self.__dict__.update(state)
            self.draw_page_number(num_pages)
            canvas.Canvas.showPage(self)
        canvas.Canvas.save(self)

    def draw_page_number(self, page_count):
        # Change the position of this to wherever you want the page number to be
        self.drawRightString(
                195 * mm,
                15 * mm,
                "Page %d of %d" % (self._pageNumber, page_count)
            )


class MyPrint(object):
    def __init__(self):
        self.pagesize = A4
        self.width, self.height = self.pagesize

    @staticmethod
    def _first_page(canvas, doc):
        # Save the state of our canvas so we can draw on it
        canvas.saveState()
        styles = getSampleStyleSheet()

        # Footer
        footer = Paragraph('Return address, Where we live, City, Postcode', styles['Normal'])
        w, h = footer.wrap(doc.width, doc.bottomMargin)
        footer.drawOn(canvas, doc.leftMargin, h)

        # Release the canvas
        canvas.restoreState()

    @staticmethod
    def _subsequent_pages(canvas, doc):
        # Save the state of our canvas so we can draw on it
        canvas.saveState()
        styles = getSampleStyleSheet()

        # Header
        header = Paragraph('Letter type' * 5, styles['Normal'])
        w, h = header.wrap(doc.width, doc.topMargin)
        header.drawOn(canvas, doc.leftMargin, doc.height + doc.topMargin - h)

        # Footer
        footer = Paragraph('Return address, Where we live, City, Postcode' * 5, styles['Normal'])
        w, h = footer.wrap(doc.width, doc.bottomMargin)
        footer.drawOn(canvas, doc.leftMargin, h)

        # Release the canvas
        canvas.restoreState()

    def coord(self, x, y, unit=1):
        """
        # https://stackoverflow.com/questions/4726011/wrap-text-in-a-table-reportlab
        Helper class to help position flowables in Canvas objects
        """
        x, y = x * unit, self.height - y * unit
        return x, y

    def print_lipsum(self):
        doc = SimpleDocTemplate(
                "form_letter.pdf",
                rightMargin=72,
                leftMargin=72,
                topMargin=72,
                bottomMargin=72,
                pagesize=self.pagesize
            )

        # Our container for 'Flowable' objects
        elements = []

        # A large collection of style sheets pre-made for us
        styles = getSampleStyleSheet()
        styles.add(ParagraphStyle(
            name='align_right',
            fontName='Helvetica',
            alignment='TA_RIGHT')
        )

        # Draw things on the PDF. Here's where the PDF generation happens.
        # See the ReportLab documentation for the full list of functionality.

        # Logo block
        logo = Image(
                'http://upload.wikimedia.org/wikipedia/commons/thumb/4/47/PNG_transparency_demonstration_1.png/280px-PNG_transparency_demonstration_1.png',
            )
        logo.drawWidth = 70*mm
        logo.drawHeight = 50*mm

        # create recipient address
        address = """Jack Spratt\n
        Some address\n
        Some address\n
        Some address\n
        Some postcode\n
        """
        recipient_address = Paragraph(address, styles["align_right"])

        # create a table for our header elementsj
        header_elements = [[logo, address]]
        table = Table(header_elements)
        elements.append(table)

        lipsum = [
            'Donec nec nunc eu ante luctus sodales eu ac massa. Duis id auctor sapien. Sed vel faucibus sem. Suspendisse potenti. Proin ut augue condimentum, semper leo sed, laoreet odio. Nam lobortis vel elit sit amet egestas. Vestibulum congue nisi non semper sollicitudin.',
            ]

        elements.append(Paragraph('My User Names', styles['Heading1']))
        for i, par in enumerate(lipsum):
            elements.append(Paragraph(par, styles['Normal']))
            elements.append(Spacer (1,12))

        doc.build(
                elements,
                onFirstPage=self._first_page,
                onLaterPages=self._subsequent_pages,
                canvasmaker=NumberedCanvas
            )

       # # Get the value of the BytesIO buffer and write it to the response.
       # pdf = buffer.getvalue()
       # buffer.close()
       # return pdf


if __name__ == '__main__':
    test = MyPrint()
    test.print_lipsum()

The answer referenced in the code comments is useful for some positioning. But what I'd really like to do is define a 'printable area' for flowables in both the _first_page and _subsequent_page methods. That way I could build the main body of text with one build call and know the layout was taken care of. I haven't found a means of doing so in the docs. Is this possible? I'm using Python 3.4, if that's relevant.

EDIT: Frames seem to be a close match for what I'd like but I'm concerned about the warning not to use them in more than one document at the same time, because I'm building a multi-user web app. Can anyone here expand on what that warning means in practice?

Community
  • 1
  • 1
Garry Cairns
  • 3,005
  • 1
  • 18
  • 33

0 Answers0