Python Reportlab PDF - Centering Text on page - python

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)

Related

python-docx Bilingual Document RTL and LTR

I want to create a bilingual MS word document; containing English and Arabic text.
As you may know, Arabic is an RTL language (written from right to left).
My most difficult problem was solved, Arabic text renders from right-to-left; yet the paragraph alignment stays Left, and I want it right (while keeping English left as it's the default).
Here is my working:
from docx import Document, enum
from docx.shared import Inches
from docx.enum.text import WD_ALIGN_PARAGRAPH, WD_PARAGRAPH_ALIGNMENT
document = Document()
document.add_heading('Document Title', 0)
p = document.add_paragraph('English text renders correctly; left to right; with default Left Alignment ')
p.add_run('bold').bold = True
p.add_run(' and some ')
p.add_run('italic.').italic = True
mystyle = document.styles.add_style('mystyle', enum.style.WD_STYLE_TYPE.CHARACTER)
paragraph = document.add_paragraph()
paragraph.alignment = WD_ALIGN_PARAGRAPH.RIGHT
p = document.add_paragraph().add_run('هذه فقرة باللغة العربية يجب أن تكتب من اليمين إلى اليسار.')
p.alignment = WD_PARAGRAPH_ALIGNMENT.RIGHT
font = p.font
font.rtl = True
p.style = mystyle
document.save('demo8.docx')
This is how it should be

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.

Python reportLab platypus: bottom image

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)

Creating a table in python docx and use small_caps (python-docx)

This is what I have tried:
from docx import Document
from docx.shared import Inches
from docx.shared import Pt
table = document.add_table(rows= 3, cols = 1)
row = table.rows[0]
runner = row.cells[0].paragraphs[0].add_run('Summary')
runner.bold = True
runner.small_caps = True
document.save('demo.docx')
I am trying to create a table and small caps the text but cannot get the syntax right
I have figured how to use small caps for text.
runner_font = runner.font
runner_font.size = Pt(14)
runner_font.small_caps = True
runner_font.bold = True
I believe bold and italic are properties that built in under both add_run and font. But for small_caps, all_caps, color can be called under font only.

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.

Categories

Resources