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?