Python reportLab platypus: bottom image - python

I'm working with the reportlab library and I have doubts about adding an image using a SimpleDocTemplate.
I have dynamic content and I don't know how much space it occupies. What happens is that I want to add a logo at the bottom of the page (always in the same place). The way I'm doing it is to add things to a list: e.g [text, spacer, table, spacer, logo] and then build it. The place of the logo depends on other variables.
Could you help me to accomplish this behavior?
I know that this can be done using absolute positioning (e.g using drawImage in a canvas class) but I don't know how to combine the way I'm doing it with this.
Thanks in advance

The chances are you want to put the image in a footer (closer to axel_ande's answer). That way the image will go in the same place on every page, but only be defined once.
If you want to put an image at the bottom of a page but not in the footer, you could try the TopPadder wrapper object:
from reportlab.platypus.doctemplate import SimpleDocTemplate
from reportlab.platypus.flowables import TopPadder
from reportlab.platypus import Table, Paragraph
from reportlab.lib.styles import getSampleStyleSheet
from reportlab.lib import colors
import numpy as np
document = SimpleDocTemplate('padding_test.pdf')
table = Table(np.random.rand(2,2).tolist(),
style=[('GRID', (0, 0), (-1, -1), 0.5, colors.black)])
styles = getSampleStyleSheet()
paragraph = Paragraph('Some paragraphs', style=styles['Normal'])
document.build([
paragraph,
TopPadder(table),
])
# This should generate a single pdf page with text at the top and a table at the bottom.
I stumbled across this when looking through the code, the only documentation I could find on it was in the release notes. In my example I wrap a table simply so the example code is stand-alone.
Hope this helps!

I got a heading for the reports that I generate that I produce like this code (where the PageTemplate generates the heading for each paper.
from reportlab.platypus import Table, TableStyle, Paragraph
from reportlab.platypus.frames import Frame
from reportlab.platypus.doctemplate import PageTemplate, BaseDocTemplate
class MyDocTemplate(BaseDocTemplate):
def __init__(self, filename, tr, param1, param2, plugin_dir, **kw):
self.allowSplitting = 0
BaseDocTemplate.__init__(self, filename, **kw)
self.tr = tr
self.plugin_dir = plugin_dir
frame = Frame(self.leftMargin, self.bottomMargin, self.width, self.height - 2 * cm, id='normal')
template = PageTemplate(id='test', frames=frame, onPage=partial(self.header, param1=param1, param2=param2))
self.addPageTemplates(template)
def header(self, canvas, doc, param1, param2):
canvas.saveState()
canvas.drawString(30, 750, self.tr('Simple report from GeoDataFarm'))
canvas.drawString(30, 733, self.tr('For the growing season of ') + str(param1))
canvas.drawImage(self.plugin_dir + '\\img\\icon.png', 500, 765, width=50, height=50)
canvas.drawString(500, 750, 'Generated:')
canvas.drawString(500, 733, param2)
canvas.line(30, 723, 580, 723)
#w, h = content.wrap(doc.width, doc.topMargin)
#content.drawOn(canvas, doc.leftMargin, doc.height + doc.topMargin - h)
canvas.restoreState()
doc = MyDocTemplate(report_name, self.tr, self.plugin_dir, '2018', '2018-09-21')
story = []
data_tbl = [['col1', 'col2'],[1, 2],[3, 4]]
table = Table(data_tbl, repeatRows=1, hAlign='LEFT', colWidths=[380/l_heading] * l_heading)
table.setStyle(TableStyle([('FONTSIZE', (0, 0), (l_heading, 0), 16)]))
story.append(table)
doc.build(story)

Related

Booklet page layout with ReportLab

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()
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.

How does platypus "guess" bold and italic styles?

Here's my code. My first function is based on the /Lib/site-packages/reportlab/lib/styles.py source code, to create an array of style:
def create_styles(p_tuples):
retour = StyleSheet1()
parent = None
for p_name, font_name, font in p_tuples:
# (!) change path if Linux:
ttf_file = "C:/Windows/Fonts/{}.ttf".format(font)
pdfmetrics.registerFont(TTFont(font_name, ttf_file))
if parent is None:
p = ParagraphStyle(name=p_name,
fontName=font_name,
fontSize=10,
leading=12)
retour.add(p)
parent = p
else:
retour.add(ParagraphStyle(name=p_name,
parent=parent,
fontName=font_name,
fontSize=10,
leading=12))
return retour
Then I build my own array with my installed fonts:
def render_to_response(self, context, **response_kwargs):
# this is a response for Django, but the question is about styles
response = HttpResponse(content_type='application/pdf; charset=utf-8')
# ! Hint : no filename -> the browser extracts it from the URL!
# -> create URLs like http://pdfreportlab.com/extract/myfilename.pdf
# this is the way to go to have 100% working UTF-8 filenames!
response['Content-Disposition'] = 'attachment; filename=""'
my_styles = self.create_styles([
('ms-regular', 'montserrat-regular',
'Montserrat-Regular'),
('ms-black', 'montserrat-black',
'Montserrat-Black'),
('ms-black-italic', 'montserrat-black-italic',
'Montserrat-BlackItalic'),
('ms-bold', 'montserrat-bold',
'Montserrat-Bold'),
('ms-bold-italic', 'montserrat-bold-italic',
'Montserrat-BoldItalic'),
])
doc = SimpleDocTemplate(response)
elements = []
c = canvas.Canvas(response, pagesize=A4, )
for idx in my_styles.byName:
p = Paragraph("Hello World <i>italic</i> <b>bold</b>",
style=my_styles[idx])
width, height = p.wrapOn(c, A4[0], A4[1])
elements.append(p)
doc.build(elements)
return response
Everything is working except the (very annoying) fact that the <i></i> and <b></b> tags are ignored! It only uses the current font in the style.
How could you modify my code so that it takes in account the tags and I finally get styles with the tags in the text itself?
You need to register a font family if you want Platypus to auto-select fonts - you're creating a different style for every font variant so of course the <i> and <b> tags have no effect - it doesn't know what fonts to use for them as the passed style references a single font.
Here's how to build a style using a font family:
from reportlab.lib.styles import ParagraphStyle
from reportlab.pdfbase.pdfmetrics import registerFont, registerFontFamily
from reportlab.pdfbase.ttfonts import TTFont
def create_paragraph_style(name, font_name, **kwargs):
ttf_path = "C:/Windows/Fonts/{}.ttf"
family_args = {} # store arguments for the font family creation
for font_type in ("normal", "bold", "italic", "boldItalic"): # recognized font variants
if font_type in kwargs: # if this type was passed...
font_variant = "{}-{}".format(font_name, font_type) # create font variant name
registerFont(TTFont(font_variant, ttf_path.format(kwargs[font_type])))
family_args[font_type] = font_variant # add it to font family arguments
registerFontFamily(font_name, **family_args) # register a font family
return ParagraphStyle(name=name, fontName=font_name, fontSize=10, leading=12)
Then you can create your paragraph style as:
pstyle = create_paragraph_style("ms", "montserrat",
normal="Montserrat-Regular",
bold="Montserrat-Bold",
boldItalic="Montserrat-BoldItalic")
assuming, of course, that you have TTF files with those names in your fonts directory.
You can then add it to your stylesheet (especially if you want parent inheritance - make sure you add it as an argument to be forwarded to the ParagraphStyle creation) or directly use it as:
p = Paragraph("Hello World <i>italic</i> <b>bold</b> <b><i>boldItalic</i></b>", style=pstyle)
(the standalone italic won't be affected as we didn't define it in the family, tho).

Two different pages with reportlab SimpleDocTemplate and Django

I'm using django and generating reports following this example, I need to generate a last page but without headers or footers and different content.
I'm trying to do this:
def print_example(self):
buffer = self.buffer
doc = SimpleDocTemplate(buffer,
rightMargin=72,
leftMargin=72,
topMargin=72,
bottomMargin=72,
pagesize=self.pagesize)
elements = []
elements.append(Paragraph('Content for all pages'), my_custom_style)
# ...
doc.build(elements, onFirstPage=self._header_footer, onLaterPages=self._header_footer,
canvasmaker=NumberedCanvas)
doc2 = SimpleDocTemplate(buffer,
rightMargin=72,
leftMargin=72,
topMargin=72,
bottomMargin=72,
pagesize=self.pagesize)
elements2 = []
elements2.append(Paragraph('Content for the last page only'), my_custom_style)
doc2.build(elements2, canvasmaker=NumberedCanvas)
# Get the value of the BytesIO buffer and write it to the response.
pdf = buffer.getvalue()
buffer.close()
return pdf
Then only the last content appears and previous content dissapears.
How can I generate the last page with different content?
I don't think it's possible using SimpleDocTemplate but you can achieve this by using BaseDocTemplate and defining your own templates.
Basic example
from reportlab.platypus import PageTemplate, BaseDocTemplate, NextPageTemplate, PageBreak
def headerFooterLayout(canvas, doc):
canvas.saveState()
canvas.setPageSize(self.pagesize)
# add header/footer
canvas.restoreState()
def emptyLayout(canvas, doc):
canvas.saveState()
canvas.setPageSize(self.pagesize)
canvas.restoreState()
pHeight, pWidth = self.pagesize
myFrame = Frame(0, 0, pHeight, pWidth, id='myFrame')
headerFooterTemplate = PageTemplate(id='headerFooterTemplate',
frames=[myFrame],
onPage=headerFooterLayout)
emptyTemplate = PageTemplate(id='emptyTemplate',
frames=[myFrame],
onPage=emptyLayout)
elements = []
elements.append(Paragraph('blah', style))
elements.append(NextPageTemplate('emptyTemplate'))
elements.append(PageBreak())
elements.append(Paragraph('last page', style))
doc = BaseDocTemplate(buffer,
rightMargin=72,
leftMargin=72,
topMargin=72,
bottomMargin=72)
doc.addPageTemplates([headerFooterTemplate, emptyTemplate])
doc.build(elements)
It's been quite a while since I used this so there may well be some issues but comment if something doesn't work.
This is all in the user guide but can be hard to find what you're looking for.

ReportLab: Flowable too large on page 1 in frame 'normal' of template 'First'

I build PDF using ReportLab. My program has a MyDocTemplate(SimpleDocTemplate) class with two methods: beforePage(self) and afterPage(self) which add header and footer (as PNG image) on every page. There is also a MyDocStyle class which describe ParagraphStyle.
Main method looks like this:
TITLE = Paragraph(Title, MyDocStyle.h1)
TO = Paragraph(To, MyDocStyle.h2)
FROM = Paragraph(From, MyDocStyle.h2)
SUBJECT = Paragraph(Subject, MyDocStyle.h2)
LONG_PARAGRAPH = Paragraph(Text, MyDocStyle.h3)
...
Elements = [TITLE, TO, FROM, SUBJECT, LONG_PARAGRAPH, ...]
doc = MyDocTemplete('output.pdf', pagesize=A4,
leftMargin=2*cm, rightMargin=2*cm,
topMargin=4*cm, bottomMargin=4*cm)
doc.build(Elements)
Data comes from CSV files and GUI. From time to time (depends on data length) I receive an error:
Flowable <Spacer at 0x2631120 frame=normal>...(1 x 5.66929133858) too large
on page 1 in frame 'normal'(469.88976378 x 603.118110236) of template 'First'
This exception stop my program. For short Paragraphs I set in MyDocStyle class h2.keepWithNext = 1 however it's not perfect solution. ReportLab split correctly long paragraph if end of paragraph does not "coincide" with end of page (text area).
How can I deal with it?
This error occurs when ReportLab try to split a Spacer over two pages. It seems that the only way to workaround this issue is wrap your Spacer into a KeepTogether element:
elements.append(KeepTogether(Spacer(width, height)))
Solved. Don't use Spacer (e.g. Spacer(1, 0.2*cm)) as a separator for Paragraph. Instead, define spaceBefore and spaceAfter in ParagraphStyle, for example:
ParagraphStyle(name = 'Normal',
fontName = "Verdana",
fontSize = 11,
leading = 15,
alignment = TA_JUSTIFY,
allowOrphans = 0,
spaceBefore = 20,
spaceAfter = 20,
wordWrap = 1)

Python Reportlab PDF - Centering Text on page

I am using ReportLab to generate a pdf dynamically with python.
I would like a line of text to be centered on a page. Here is the specific code I currently have, but do not know how to center the text horizontally.
header = p.beginText(190, 740)
header.textOut("Title of Page Here")
# I know i can use TextLine etc in place of textOut
p.drawText(header)
The text displays and I can manually move the left position so the text looks centered, but I need this to be centered programmatically since the text will be dynamic and I don't know how much text there will be.
The reportlab canvas has a drawCentredString method. And yes, they spell it like that.
We’re British, dammit, and proud of
our spelling!
Edit:
As for text objects, I'm afraid you don't. You can do something along those lines, though:
from reportlab.pdfbase.pdfmetrics import stringWidth
from reportlab.rl_config import defaultPageSize
PAGE_WIDTH = defaultPageSize[0]
PAGE_HEIGHT = defaultPageSize[1]
text = "foobar foobar foobar"
text_width = stringWidth(text)
y = 1050 # wherever you want your text to appear
pdf_text_object = canvas.beginText((PAGE_WIDTH - text_width) / 2.0, y)
pdf_text_object.textOut(text) # or: pdf_text_object.textLine(text) etc.
You can use other page sizes, obviously.
I just needed this too, and wrote this:
def createTextObject(canv, x, y, text, style, centered=False):
font = (style.fontName, style.fontSize, style.leading)
lines = text.split("\n")
offsets = []
if centered:
maxwidth = 0
for line in lines:
offsets.append(canv.stringWidth(line, *font[:2]))
maxwidth = max(*offsets)
offsets = [(maxwidth - i)/2 for i in offsets]
else:
offsets = [0] * len(lines)
tx = canv.beginText(x, y)
tx.setFont(*font)
for offset, line in zip(offsets, lines):
tx.setXPos(offset)
tx.textLine(line)
tx.setXPos(-offset)
return tx
You can use Flowable object like Paragraph and assign alignment value to 1:
styles = getSampleStyleSheet()
title_style = styles['Heading1']
title_style.alignment = 1
title = Paragraph("Hello Reportlab", title_style)
story.append(title)
This example will create a pdf document with centered text:
from flask import make_response
import io
from reportlab.platypus import SimpleDocTemplate, Paragraph
from reportlab.lib.styles import getSampleStyleSheet
story=[]
pdf_doc = io.BytesIO()
doc = SimpleDocTemplate(pdf_doc)
styles = getSampleStyleSheet()
title_style = styles['Heading1']
title_style.alignment = 1
title = Paragraph("Hello Reportlab", title_style)
story.append(title)
doc.build(story)
content = pdf_doc.getvalue()
#output to browser
response = make_response(content)
response.mimetype = 'application/pdf'
return response
If you want the text to be floaten to the left, you need to change alignment to 0:
title_style.alignment = 0
If you want the text to be floaten to the right, you need to change alignment to 2:
title_style.alignment = 2
Try:
<para alignment="center">
As Per Reference : http://two.pairlist.net/pipermail/reportlab-users/2006-June/005092.html
In your case:
header.textOut("<"para alignment='center'>"Title of Page Here")
from reportlab.lib.pagesizes import letter
from reportlab.platypus import SimpleDocTemplate, Paragraph
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
doc = SimpleDocTemplate("form_letter.pdf",pagesize=letter,
rightMargin=72,leftMargin=72,
topMargin=72,bottomMargin=18)
Story = []
styles = getSampleStyleSheet()
title_style = styles['Heading1']
title_style.alignment = 1
title = Paragraph("Welcome to india", title_style)
Story.append(title)
doc.build(Story)

Categories

Resources