Reportlab - How to add margin between Tables? - python

So i am trying to create three tables per page, the following code will collide all three tables together with 0 margin between them. I would like some white space between two tables. Is there a configuration for that?
doc = SimpleDocTemplate("my.pdf", pagesize=A4)
elements = []
i = 0
for person in persons:
data = get_data()
t = Table(data, colWidths=col_widths, rowHeights=row_heights)
elements.append(t)
i = i + 1
if i % 3 == 0:
elements.append(PageBreak())
doc.build(elements)

You could try using the Spacer function to add space between the tables. An example of its use from the documentation is:
from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer
def go():
doc = SimpleDocTemplate("hello.pdf")
Story = [Spacer(1,2*inch)]
for i in range(100):
bogustext = ("This is Paragraph number %s. " % i) *20
p = Paragraph(bogustext, style)
Story.append(p)
Story.append(Spacer(1,0.2*inch))
doc.build(Story, onFirstPage=myFirstPage, onLaterPages=myLaterPages)

Related

Reportlab paragraph frame split doesn't work

I have the report lab document, which is a mix of regular strings drawed with drawstring method and frames. I will get the table data from request, and i need to have the possibility to split the table to another page, because i don't know the size of the page. It works fine when the table is not big, but when the amount of rows is too big, it dust doesn't display anyting on the page. As per the reportlab lib, I simple need to use frame.split(), but it doesn't work. The code:
def generate_pdf(request: Request):
buffer = io.BytesIO()
pdfmetrics.registerFont(TTFont("Poppins-bold", f"static/fonts/poppins-800.ttf"))
pdfmetrics.registerFont(TTFont("Poppins-medium", "static/fonts/poppins-medium.ttf"))
pdfmetrics.registerFont(TTFont("Poppins-semibold", "static/fonts/poppins-semibold.ttf"))
pdfmetrics.registerFont(TTFont("Poppins-regular", "static/fonts/poppins-regular.ttf"))
doc = canvas.Canvas(buffer)
customColor = colors.Color(red=(39.0 / 255), green=(71.0 / 255), blue=(114.0 / 255))
doc.setFillColor(customColor)
doc.setFont(psfontname="Poppins-bold", size=20)
if request.data["type"] == "Purchase Order":
doc.drawString(50, 780, "Purchase Order")
else:
doc.drawString(50, 780, "Work Order")
doc = add_image(doc)
doc = draw_invoice_start_end(doc, request)
doc = draw_order_by_order_to(doc, request)
table_data = []
for i in range(50):
table_data.append(["item"])
frame1 = Frame(width=800, height=300, showBoundary=1, x1=50, y1=200)
story = []
table = Table(data=table_data, colWidths=None, rowHeights=None, style=None, splitByRow=1, repeatRows=0,
repeatCols=0,
rowSplitRange=None, spaceBefore=None, spaceAfter=None, cornerRadii=None)
styles = getSampleStyleSheet()
styles.add(ParagraphStyle(name="Rectangles",
fontName="Poppins-semibold",
textColor=customColor,
fontSize=10,
leading=12,
backColor="white",
borderRadius=5)
)
styleN = styles["Rectangles"]
story.append(table)
paragraph = Paragraph(text="Lorem impsum" * 5000, style=styleN)
story.append(paragraph)
for el in story:
if frame1.add(el, doc) == 0:
frame1.split(el, doc)
doc.showPage()
frame1 = Frame(0.5 * inch, inch, 7 * inch, 10.5 * inch, showBoundary=1)
doc.save()
pdf = buffer.getvalue()
file_data = ContentFile(pdf)
buffer.close()
return file_data
The only way I can think of, is using the frame.split() method, but it didn't work. Also, it looks like the SimpleDocTemplate could help me, but I will need to change the whole code to make it work.
This is what i get with this code: outcome

Python-PPTX: Changing table style or adding borders to cells

I've started putting together some code to take Pandas data and put it into a PowerPoint slide. The template I'm using defaults to Medium Style 2 - Accent 1 which would be fine as changing the font and background are fairly easy, but there doesn't appear to be an implemented portion to python-pptx that allows for changing cell borders. Below is my code, open to any solution. (Altering the XML or changing the template default to populate a better style would be good options for me, but haven't found good documentation on how to do either). Medium Style 4 would be ideal for me as it has exactly the borders I'm looking for.
import pandas
import numpy
from pptx import Presentation
from pptx.util import Inches, Pt
from pptx.dml.color import RGBColor
#Template Location
tmplLoc = 'C:/Desktop/'
#Read in Template
prs = Presentation(tmplLoc+'Template.pptx')
#Import data as Pandas Dataframe - dummy data for now
df = pandas.DataFrame(numpy.random.randn(10,10),columns=list('ABCDEFGHIJ'))
#Determine Table Header
header = list(df.columns.values)
#Determine rows and columns
in_rows = df.shape[0]
in_cols = df.shape[1]
#Insert table from C1 template
slide_layout = prs.slide_layouts[11]
slide = prs.slides.add_slide(slide_layout)
#Set slide title
title_placeholder = slide.shapes.title
title_placeholder.text = "Slide Title"
#Augment placeholder to be a table
placeholder = slide.placeholders[1]
graphic_frame = placeholder.insert_table(rows = in_rows+1, cols = in_cols)
table = graphic_frame.table
#table.apply_style = 'MediumStyle4'
#table.apply_style = 'D7AC3CCA-C797-4891-BE02-D94E43425B78'
#Set column widths
table.columns[0].width = Inches(2.23)
table.columns[1].width = Inches(0.9)
table.columns[2].width = Inches(0.6)
table.columns[3].width = Inches(2)
table.columns[4].width = Inches(0.6)
table.columns[5].width = Inches(0.6)
table.columns[6].width = Inches(0.6)
table.columns[7].width = Inches(0.6)
table.columns[8].width = Inches(0.6)
table.columns[9].width = Inches(0.6)
#total_width = 2.23+0.9+0.6+2+0.6*6
#Insert data into table
for rows in xrange(in_rows+1):
for cols in xrange(in_cols):
#Write column titles
if rows == 0:
table.cell(rows, cols).text = header[cols]
table.cell(rows, cols).text_frame.paragraphs[0].font.size=Pt(14)
table.cell(rows, cols).text_frame.paragraphs[0].font.color.rgb = RGBColor(255, 255, 255)
table.cell(rows, cols).fill.solid()
table.cell(rows, cols).fill.fore_color.rgb=RGBColor(0, 58, 111)
#Write rest of table entries
else:
table.cell(rows, cols).text = str("{0:.2f}".format(df.iloc[rows-1,cols]))
table.cell(rows, cols).text_frame.paragraphs[0].font.size=Pt(10)
table.cell(rows, cols).text_frame.paragraphs[0].font.color.rgb = RGBColor(0, 0, 0)
table.cell(rows, cols).fill.solid()
table.cell(rows, cols).fill.fore_color.rgb=RGBColor(255, 255, 255)
#Write Table to File
prs.save('C:/Desktop/test.pptx')
Maybe not really clean code but allowed me to adjust all borders of all cells in a table:
from pptx.oxml.xmlchemy import OxmlElement
def SubElement(parent, tagname, **kwargs):
element = OxmlElement(tagname)
element.attrib.update(kwargs)
parent.append(element)
return element
def _set_cell_border(cell, border_color="000000", border_width='12700'):
tc = cell._tc
tcPr = tc.get_or_add_tcPr()
for lines in ['a:lnL','a:lnR','a:lnT','a:lnB']:
ln = SubElement(tcPr, lines, w=border_width, cap='flat', cmpd='sng', algn='ctr')
solidFill = SubElement(ln, 'a:solidFill')
srgbClr = SubElement(solidFill, 'a:srgbClr', val=border_color)
prstDash = SubElement(ln, 'a:prstDash', val='solid')
round_ = SubElement(ln, 'a:round')
headEnd = SubElement(ln, 'a:headEnd', type='none', w='med', len='med')
tailEnd = SubElement(ln, 'a:tailEnd', type='none', w='med', len='med')
Based on this post: https://groups.google.com/forum/#!topic/python-pptx/UTkdemIZICw
In case someone else comes across this issue again, some changes should be made to the solution posted by JuuLes87 to avoid that Microsoft Office PowerPoint requires to repair the generated presentation.
After carefully inspecting the xml string of the table generated by pptx, I found that the requirement to repair the presentation seemed to be due to the duplicated nodes of 'a:lnL' or 'a:lnR' or 'a:lnT' or 'a:lnB' in the children elements of 'a:tcPr'. So we only need to remove nodes of ['a:lnL','a:lnR','a:lnT','a:lnB'] before these nodes are inserted as below.
from pptx.oxml.xmlchemy import OxmlElement
def SubElement(parent, tagname, **kwargs):
element = OxmlElement(tagname)
element.attrib.update(kwargs)
parent.append(element)
return element
def _set_cell_border(cell, border_color="000000", border_width='12700'):
tc = cell._tc
tcPr = tc.get_or_add_tcPr()
for lines in ['a:lnL','a:lnR','a:lnT','a:lnB']:
# Every time before a node is inserted, the nodes with the same tag should be removed.
tag = lines.split(":")[-1]
for e in tcPr.getchildren():
if tag in str(e.tag):
tcPr.remove(e)
# end
ln = SubElement(tcPr, lines, w=border_width, cap='flat', cmpd='sng', algn='ctr')
solidFill = SubElement(ln, 'a:solidFill')
srgbClr = SubElement(solidFill, 'a:srgbClr', val=border_color)
prstDash = SubElement(ln, 'a:prstDash', val='solid')
round_ = SubElement(ln, 'a:round')
headEnd = SubElement(ln, 'a:headEnd', type='none', w='med', len='med')
tailEnd = SubElement(ln, 'a:tailEnd', type='none', w='med', len='med')
I had a hard time figuring out why this wasn't working. For anyone else struggling with this, I had to add the following to the end of the function:
return cell
When using, you want to use the function as such:
cell = _set_cell_border(cell)

How to add bold and normal text in one line using drawString method in reportlab (python)

I am new to reportlab lib, I am learning it simultaneously working on a college project.
I have created a desktop application in wxpython, which result in saving data in PDF.
I want to add 2 lines in my pdf. Where line starts with a user input called name, then some words, again at 2nd line some words, user name and then again some words...
I tried use some of Paragraph and canvas methods and classes but I wasn't able to get the desired output.
Desired output:
Alex is working on college project.
reportlab is very good lib, Alex liked it.
My code:
import os
import reportlab
from reportlab.pdfgen import canvas
from reportlab.lib.pagesizes import A4
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.pdfmetrics import registerFontFamily
from reportlab.pdfbase.ttfonts import TTFont
# Registered font family
pdfmetrics.registerFont(TTFont('Vera', 'Vera.ttf'))
pdfmetrics.registerFont(TTFont('VeraBd', 'VeraBd.ttf'))
pdfmetrics.registerFont(TTFont('VeraIt', 'VeraIt.ttf'))
pdfmetrics.registerFont(TTFont('VeraBI', 'VeraBI.ttf'))
# Registered fontfamily
registerFontFamily('Vera',normal='Vera',bold='VeraBd',italic='VeraIt',boldItalic='VeraBI')
# Output pdf file name.
can = canvas.Canvas("Bold_Trail.pdf", pagesize=A4)
# Setfont for whole pdf.
can.setFont('Vera', 12)
# student name variable.
student_name ="Alex"
# Content.
line1 = " is working on college project."
line2 = "Reportlab is very good lib, "
line3 = " liked it.<br>"
# Joining whole content together.
content = "<strong>" + student_name + "</strong>" + line1
content2 = line2 + "<strong>"+student_name + "</strong>" + line3
# drawString location calculation.
x = 0; y = 8.5 * 72
# First string.
can.drawString(x,y, content)
y = y - 72
# Second String.
can.drawString(x,y, content2)
# Create PDF.
can.save()
Is there any other way except using XML method <strong> or <b> that do not work in the above program?
The words should remain on one line.
You could use the setFont method of the canvas object, to set the font to Bold when needed, and Normal otherwise.
* UPDATE *
In order to calculate the right value for x, you can use the stringWidth method, that calculates the length of the string given its content, the font name and the font size. You will have to import it from reportlab.pdfbase.pdfmetrics:
[...]
from reportlab.pdfbase.pdfmetrics import stringWidth
[...]
# student name variable.
student_name = 'Alex'
# Content.
line1 = " is working on college project."
line2 = "Reportlab is very good lib, "
line3 = " liked it"
# drawString location calculation.
x = 0
y = 8.5 * 72
# First string.
can.setFont('Helvetica-Bold', 8)
can.drawString(x, y, student_name)
can.setFont('Helvetica', 8)
textWidth = stringWidth(student_name, 'Helvetica-Bold', 8)
x += textWidth + 1
can.drawString(x, y, line1)
y = y - 72
# Second String.
x = 0
can.setFont('Helvetica', 8)
can.drawString(x, y, line2)
textWidth = stringWidth(line2, 'Helvetica', 8)
x += textWidth + 1
can.setFont('Helvetica-Bold', 8)
can.drawString(x, y, student_name)
textWidth = stringWidth(student_name, 'Helvetica-Bold', 8)
x += textWidth + 1
can.setFont('Helvetica', 8)
can.drawString(x, y, line3)
# Create PDF.
can.save()
Or you could have a look at ParagraphStyle and Paragraph (from reportlab.lib.styles import ParagraphStyle, from reportlab.platypus import Paragraph) but I am not sure if you can concatenate two different styles in the same string.
I found a way to format the Paragraph text with XML tags. Firstly you need to register the font family then it should work. Below I downloaded a group of .ttf files as an example and registered them, after that, the XML tags worked properly.
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont
from reportlab.pdfbase.pdfmetrics import registerFontFamily
pdfmetrics.registerFont(TTFont('OpenSansR', 'OpenSans-Regular.ttf'))
pdfmetrics.registerFont(TTFont('OpenSansL', 'OpenSans-Light.ttf'))
pdfmetrics.registerFont(TTFont('OpenSansB', 'OpenSans-Bold.ttf'))
registerFontFamily('OpenSans', normal='OpenSansR', bold='OpenSansB', italic='OpenSansL', boldItalic='OpenSansB')

Split a table across more than 1 page in Reportlab

I'm trying to split a "full-width" table across 2 pages or even more. I use the Platypus library of ReportLab and the BaseDocTemplate class.
I've a "full width" table of elements and this should be drawn into a frame of the first page, if the table has enough rows It should be continued in the second page. My problem is that the frame of the first page has a different height and position than the others, because at the top of the first page I need to show more information (Yes... I'm talking about an invoice or order).
After thousands attempts, all that I've got is a pdf with a unique page with only 8 items/rows, It's exactly the space that they require at the first page, but if the table has more than 8 rows, then I get a pdf with only 1 page and without the table (that means an empty frame, although I see all data in the log).
I've used the methods split() and wrap() but probably in the wrong way, because I'm new with ReportLab. I show you the last version of my code:
from django.http import HttpResponse
from reportlab.pdfgen import canvas
from reportlab.lib.units import mm
from reportlab.lib import colors
from reportlab.lib.pagesizes import A4
from reportlab.platypus import BaseDocTemplate, PageTemplate, Table, Spacer, Frame, TableStyle,\
NextPageTemplate, PageBreak, FrameBreak
PAGE_WIDTH = A4[0]
PAGE_HEIGHT = A4[1]
MARGIN = 10*mm
class ThingPDF(BaseDocTemplate):
def header(self, canvas, subheader=True):
# print 'header()'
data = [('AAAA', 'Thing %s' % (self.thing.name)), ]
s = []
t = Table(data, colWidths=[95 * mm, 95 * mm], rowHeights=None, style=None, splitByRow=1,
repeatRows=0, repeatCols=0)
t.setStyle(TableStyle([
('BACKGROUND', (0, 0), (0, 0), colors.red),
('BACKGROUND', (1, 0), (1, 0), colors.blue),
('ALIGN', (1, 0), (1, 0), 'RIGHT'),
]))
s.append(t)
# if subheader:
# print 'subheader'
self.head.addFromList(s, canvas)
def data_table(self, canvas, items):
# print 'data_table()'
d = [[u'col0', u'col1', u'col2', u'col3', u'col4', ],]
for item in items:
d.append([item.col0, item.col1, item.col2, item.col3, item.col4])
s = []
t = Table(d, colWidths=[20*mm, 100*mm, 20*mm, 20*mm, 30*mm], rowHeights=20*mm, style=None,\
splitByRow=1, repeatRows=0, repeatCols=0)
t.setStyle([('BACKGROUND', (0,0), (-1,0), ('#eeeeee'))])
h=187*mm #TODO
w=A4[0] - (2*MARGIN)
splitter = t.split(w, h)
# print '\n\nresult of splitting: ', len(splitter)
for i in splitter:
print 'i: ', i
self.dataframeX.addFromList(s, canvas)
s.append(t)
self.dataframe0.addFromList(s, canvas)
def on_first_page(self, canvas, doc):
canvas.saveState()
self.header(canvas)
self.data_table(canvas, self.items)
canvas.restoreState()
def on_next_pages(self, canvas, doc):
canvas.saveState()
self.header(canvas, subheader=False)
canvas.restoreState()
def build_pdf(self, thing=None, items=None, user=None):
self.thing = thing
self.items = items
self.doc = BaseDocTemplate('%s.pdf' % (thing.name),
pagesize=A4,
pageTemplates=[self.first_page, self.next_pages,],
showBoundary=1,
rightMargin=MARGIN,
leftMargin=MARGIN,
bottomMargin=MARGIN,
topMargin=MARGIN,
allowSplitting=1,
title='%s' % 'title')
self.story.append(Spacer(0*mm, 2*mm))
self.doc.build(self.story)
response = HttpResponse(mimetype='application/pdf')
response['Content-Disposition'] = 'attachment; filename=%s.pdf' % (reference)
return response
def __init__(self):
self.thing = None
self.items = None
self.story = []
#========== FRAMES ==========
self.head = Frame(x1=MARGIN, y1=A4[1] - (2*MARGIN), width=A4[0] - (2*MARGIN), height=10*mm,
leftPadding=0, bottomPadding=0, rightPadding=0, topPadding=0, id='header',
showBoundary=1)#, overlapAttachedSpace=None, _debug=None)
self.dataframe0 = Frame(x1=MARGIN, y1=10*mm, width=A4[0] - (2*MARGIN), height=187*mm,
leftPadding=0, bottomPadding=0, rightPadding=0, topPadding=0,
id='body', showBoundary=1)
self.dataframeX = Frame(x1=MARGIN, y1=MARGIN, width=A4[0] - (2*MARGIN), height=257*mm,
leftPadding=0, bottomPadding=0, rightPadding=0, topPadding=0,
id='body', showBoundary=1)
#========== PAGES ==========
self.first_page = PageTemplate(id='firstpage', frames=[self.head, self.dataframe0], onPage=self.on_first_page)
self.next_pages = PageTemplate(id='nextpages', frames=[self.head, self.dataframeX], onPage=self.on_next_pages)
Thank you in advance!!
The code that you posted is lacking some data and is not by itself runnable, so i can't really tell if my answer will be correct. Please extend your code if this doesn't work!
First of all, you don't have to use the wrap and split methods at all! The table will split itself when doc.build consumes the story. Also, split doesn't split the table inline, but returns just a list of tables. So in your case splitter is a list of tables over which you iterate and then append an empty list to the frame. I would suggest that you skip that part. You are adding the different elements to individual frames. Because of this, you would add the splitted table to the dataframeX but dataframeX might never be used, because you are never using the next_pages PageTemplate. For this you have to add an NextPageTemplate() to the story, after you are finished with your first page. I would let platypus do that stuff for you: Just let your methods return lists with the generated elements and concatenate them before passing them to doc.build().

ReportLab variable nextPageTemplate's

I am in the process of converting the database from pfaf.org (plant for a future) into a pdf book.
I have hit a bit of a stumbling block regarding pageTemplates.
Each plant may begin on a left or a right aligned page; and may potentially be two or more pages.
I have two templates for the first plant page (left and right) and also two more templates for the potential subsequent pages.
Currently this is handled as follows (for instance):
for i, plant in enumerate(plants):
#the first plant is printed on a right-hand page
if i % 2 == 0:
template = 'left'
second = 'right'
else:
template = 'right'
second = 'left'
Story.append(nextPageTemplate(template,'*', 'secondary_'+template, 'secondary_'+second))
Story.append(PageBreak())
#append various paragraphs, jumping between frames, etc...
The code (as you can probably tell) works fine for single page plants.
It also works (semi) as expected for multi page plants.
However, as you can also probably see, a two (or four,etc) page plant will break the template arrangement, because the above code assumes page position based upon plant number, rather than page number.
I can't see a solution for this in the location of the code above (i.e during the Story.append cycle) - as at that point I can not tell whether the plant has used more than one page, and thus what page I am currently on, as such.
I hoped that I could perhaps tweak the nextPageTemplate structure from my custom docTemplate, but I can't figure out if this is possible.
Is it? Or is there another solution? Would really appreciate any help. Have been reading all over, but the best examples I can find don't quite cover this scenario.
Any questions, please ask.
Thank you
Thank you, Nitzle:
The trouble is that I don't know how many pages each plant will take up.
For instance - a new plant starts on an odd page so I give it a cycle of templates ('right', '*', 'secondaryLeft', 'secondaryRight'). [the secondary pages are just a single frame with appropriate margin.]
If that plant is one page long, no problem, the next plant will have the template cycle opposite to the above.
However, if the plant has, say, two pages, it will cause the following plant to fall again on an odd page again and thus the template cycle should not change... I hope this makes sense.
This is the circumstance I am having trouble solving... If I do as you say, it doesn't allow for multiple page plants. Most of my code is as follows; I have tried to slim it down a little though :) hopefully it still contains all relevant stuff and not too much unnecessary.
import os
import sys
import MySQLdb
from reportlab.platypus import Spacer, Image, Table, TableStyle, PageBreak, FrameBreak, paraparser
from reportlab.platypus.doctemplate import BaseDocTemplate, PageTemplate, NextPageTemplate, _doNothing
from reportlab.platypus.tableofcontents import TableOfContents
from reportlab.platypus.frames import Frame
from reportlab.platypus.flowables import KeepInFrame
from reportlab.platypus.paragraph import Paragraph
from reportlab.lib.units import mm, cm
from reportlab.lib.pagesizes import A4, A5
from reportlab.lib.enums import TA_JUSTIFY, TA_CENTER, TA_RIGHT
from reportlab.lib.styles import StyleSheet1, ParagraphStyle as PS
from reportlab.lib import colors
from reportlab.graphics.shapes import Drawing, Rect, String
from reportlab.pdfbase.pdfmetrics import registerFont, stringWidth
from reportlab.pdfbase.ttfonts import TTFont
from reportlab.rl_config import warnOnMissingFontGlyphs
warnOnMissingFontGlyphs = 0
registerFont(TTFont('Museo_', '/home/wuwei/.fonts/Museo300-Regular.ttf'))
registerFont(TTFont('Museo_M', '/home/wuwei/.fonts/Museo500-Regular.ttf'))
registerFont(TTFont('Trebuchet', '/usr/share/fonts/truetype/msttcorefonts/Trebuchet_MS.ttf'))
registerFont(TTFont('Trebuchet_I', '/usr/share/fonts/truetype/msttcorefonts/Trebuchet_MS_Italic.ttf'))
## SOME VARIABLE DEFINITIONS ##
titleFont = "Museo_M"
subtitleFont = "Museo_"
stdFont = "Trebuchet"
stdItalic = "Trebuchet_I"
#stdSize = 14
"""CREATE GLOBALS"""
_w, _h = A4
_head_w = 17.5*cm
_head_pad = 0.2*cm
_main_w = 17.2*cm
_budge = 0.3*cm
_left_margin = 1.5*cm
_right_margin = 2.0*cm
_top_margin = 1.5*cm
_bottom_margin = 2.0*cm
_latinFontS = 18
#reset superFraction to style 'common name' placement
paraparser.superFraction = 0.15
paraparser.sizeDelta = 0
###########################################################################################################
######################################### ###############################################
######################################## DB FUNCTIONS #############################################
######################################### ###############################################
###########################################################################################################
def connectToDB():
try:
connection = MySQLdb.connect (host = "localhost",
user = "root",
passwd = "****************",
db = "pfaf")
except MySQLdb.Error, e:
print "I guess, either you don't have a local copy of the pfaf db"
print "or something is wrong with your connection details."
print "Error %d: %s" % (e.args[0], e.args[1])
sys.exit (1)
return connection
def close(item, exit=0):
#used to close both database cursors and connections
item.close()
if exit == 1:
sys.exit (0)
def runQuery(q, conn):
results = conn.cursor(MySQLdb.cursors.DictCursor)
results.execute (q)
return results
def Fetch(results, fetchAll=0):
if fetchAll:
print "fetchAll"
# FETCHALL option:
rows = results.fetchall()
#cursor.close()
#conn.close()
'''for row in rows:
print "%s, %s" % (row["Latin Name"], row["Hardyness"])
print "%d rows were returned" % results.rowcount'''
return rows
else:
# FETCHONE option:
##--- Print some debug info to command line ---##
print "Latin Name - Common Name - Hardyness"
while (1):
row = results.fetchone()
if row == None:
break
latin_name = row["Latin Name"]
common_name = row["Common name"]
hardyness = row["Hardyness"]
family = row["Family"]
synonyms = row["Synonyms"]
##--- Print some more useful debug info to command line ---##
print "%s - %s - %s" % (latin_name, common_name, hardyness)
print row
if results.rowcount != 1:
print "%d rows were returned" % results.rowcount
else:
print "%d row was returned" % results.rowcount
return row
###########################################################################################################
######################################### ###############################################
######################################## STORY PROCESSING #############################################
######################################### ###############################################
###########################################################################################################
def drawBorders(canv, side):
canv.saveState()
d = Drawing(0,0)
#header border#
r = Rect( side-_budge, _h-(2.4*cm), _head_w+(_budge*2), 1.2*cm, rx=5, ry=5 )
r.strokeColor = colors.black
r.fillColor = colors.white
r.strokeWidth = 1.5
d.add(r)
#hardyness border#
rad = 5
hWidth = 1.4*cm
if side == _left_margin:
hPos = -rad
else:
hPos = _w - hWidth + rad
r = Rect( hPos, _h-(3.8*cm), hWidth, 1.2*cm, rx=rad, ry=rad )
r.strokeColor = colors.black
r.fillColor = colors.white
r.strokeWidth = 1.5
d.add(r)
d.drawOn(canv, 0, 0)
canv.restoreState()
def drawFooter(canv, doc):
canv.saveState()
canv.setFont(stdFont,10)
canv.drawCentredString((_w/2.0), 1.5*cm, "%d - %s" % (doc.page, doc.latinName))
canv.restoreState()
class LeftPageTemplate(PageTemplate):
def __init__(self):
#allow a bigger margin on the right for binding
latinF = Frame(_left_margin, 27.5*cm, _head_w, 0.8*cm, id='latinL', showBoundary=0,
rightPadding=0, leftPadding=0, topPadding=0, bottomPadding=0)
hardyF = Frame(0.1*cm, 26.05*cm, cm, cm, id='hardL', showBoundary=0,
rightPadding=0, leftPadding=0, topPadding=0, bottomPadding=0)
synF = Frame(_left_margin, 26.65*cm, _main_w, 0.55*cm, id='synL', showBoundary=0,
rightPadding=0, leftPadding=0, topPadding=0, bottomPadding=0)
otherF = Frame(_left_margin, 22.1*cm, 12.4*cm, 4.5*cm, id='otherL', showBoundary=1)
calF = Frame(14.2*cm, 22.1*cm, 4.5*cm, 4.5*cm, id='calL', showBoundary=0,
rightPadding=0, leftPadding=0, topPadding=0, bottomPadding=0)
flowF = Frame(_left_margin, 2.0*cm, _main_w, 19.85*cm, id='flowL', showBoundary=1)
PageTemplate.__init__(self,
id='left',
frames=[latinF, hardyF, synF, otherF, calF, flowF],
pagesize=A4)
def beforeDrawPage(self, canv, doc):
drawBorders(canv, _left_margin)
def afterDrawPage(self, canv, doc):
drawFooter(canv, doc)
class RightPageTemplate(PageTemplate):
def __init__(self):
#allow a bigger margin on the left for binding
latinF = Frame(_right_margin, 27.5*cm, _head_w, 0.8*cm, id='latinR', showBoundary=0,
rightPadding=0, leftPadding=0, topPadding=0, bottomPadding=0)
hardyF = Frame(_w-1.1*cm, 26.05*cm, cm, cm, id='hardR', showBoundary=0,
rightPadding=0, leftPadding=0, topPadding=0, bottomPadding=0)
synF = Frame(_right_margin+_budge, 26.65*cm, _main_w, 0.55*cm, id='synR', showBoundary=0,
rightPadding=0, leftPadding=0, topPadding=0, bottomPadding=0)
calF = Frame(_right_margin+_budge, 22.1*cm, 4.5*cm, 4.5*cm, id='calR', showBoundary=0,
rightPadding=0, leftPadding=0, topPadding=0, bottomPadding=0)
otherF = Frame(_right_margin+5.1*cm, 22.1*cm, 12.4*cm, 4.5*cm, id='otherR', showBoundary=1)
flowF = Frame(_right_margin+_budge, 2.0*cm, _main_w, 19.85*cm, id='flowR', showBoundary=1)
PageTemplate.__init__(self,
id='right',
frames=[latinF, hardyF, synF, otherF, calF, flowF],
pagesize=A4)
def beforeDrawPage(self, canv, doc):
drawBorders(canv, _right_margin)
def afterDrawPage(self, canv, doc):
drawFooter(canv, doc)
class MyDocTemplate(BaseDocTemplate):
_invalidInitArgs = ('pageTemplates',)
def __init__(self, filename, **kw):
self.allowSplitting = 0
BaseDocTemplate.__init__(self, filename, **kw)
self.latinName = "(none initially)"
self.latinWidth = 0 #(none initially)
def afterInit(self):
self._calc() #in case we have changed margin sizes etc
self.leftMargin = _left_margin
self.rightMargin = _right_margin
self.topMargin = _top_margin
self.bottomMargin = _bottom_margin
self.width = _w - self.leftMargin - self.rightMargin
self.height = _h - self.topMargin - self.bottomMargin
frameStd = Frame(cm, self.bottomMargin, (_w - 2*cm), (_h - 3*cm), id='cvr', showBoundary=0)
frameToC = Frame(self.rightMargin, self.bottomMargin, self.width, self.height, id='tocFrame', showBoundary=0)
frameL = Frame(self.leftMargin, self.bottomMargin, self.width, self.height, id='secLeftFrame', showBoundary=1)
frameR = Frame(self.leftMargin, self.bottomMargin, self.width, self.height, id='secRightFrame', showBoundary=1)
self.addPageTemplates( [PageTemplate(id='Cover', frames=frameStd, onPage=coverPage, pagesize=self.pagesize),
PageTemplate(id='ToC', frames=frameToC, onPage=tocPage, pagesize=self.pagesize),
PageTemplate(id='blank', frames=frameStd, onPage=_doNothing, pagesize=self.pagesize),
LeftPageTemplate(),
RightPageTemplate(),
PageTemplate(id='secondary_left', frames=frameL, onPage=_doNothing, pagesize=self.pagesize),
PageTemplate(id='secondary_right', frames=frameR, onPage=_doNothing, pagesize=self.pagesize)
] )
def afterFlowable(self, flowable):
"""Registers ToC entries - and captures latin name for footer"""
if isinstance(flowable, Paragraph):
style = flowable.style.name
key = None
firstWord = style.split('_',1)[0]
if (style == 'LatinName') or (style == 'LatinNameR') or (firstWord == 'LatinName'):
level = 0
key = 'latin-%s' % self.seq.nextf('LatinName')
self.canv.bookmarkPage(key)
wholeStr = flowable.getPlainText()
if self.page % 2 == 0: #even numbers are on left pages
latinOnly = wholeStr.split('\xc2\xa0\xc2\xa0')[0] #looks for '&nbsp&nbsp' as divider
else:
latinOnly = wholeStr.split('\xc2\xa0\xc2\xa0')[1]
self.latinName = latinOnly
E = [level, latinOnly, self.page]
if key is not None: E.append(key)
self.notify('TOCEntry', tuple(E))
'''elif (style == 'CommonName'):
self.commonName = flowable.getPlainText()
self.commonWidth = stringWidth(self.commonName, styles['common'].fontName, styles['common'].fontSize)'''
else:
return
""" coverPage and otherPages are intended for non-flowing (i.e standard) parts of the pages """
def coverPage(canvas, doc):
Title = "Plants for a Future"
pageinfo = "The full database collected as a printable book"
canvas.setTitle(Title + " : " + pageinfo)
print "creating cover page..."
canvas.saveState()
d = Drawing(0,0)
r = Rect( 0, 0, 12*cm, 4*cm, rx=5, ry=5 )
r.strokeColor = colors.black
r.fillColor = colors.white
r.strokeWidth = 3
d.add(r)
d.drawOn(canvas, (_w/2.0)-6*cm, _h-(6.2*cm))
canvas.setFont(stdFont, 30)
canvas.drawCentredString(_w/2.0, _h-108, Title)
canvas.setFont(stdItalic, 14)
canvas.drawCentredString(_w/2.0, _h-150, pageinfo)
canvas.restoreState()
def tocPage(canvas, doc):
canvas.saveState()
canvas.setFont(stdFont,10)
canvas.drawCentredString((_w/2.0), 1.5*cm, "Table of Contents")
canvas.restoreState()
def getMedicinal(plant):
p = plant
initial = p["Medicinal"]
return initial
""" Run after 'Story' has been fully populated """
def go():
doc = MyDocTemplate('result01.pdf')
passes = doc.multiBuild(Story)
########################################################################
"""Build StyleSheet"""
styles = buildStyle()
h1 = PS(name = 'HeadingOne',
fontName = stdFont,
fontSize = 14,
leading = 16)
h2 = PS(name = 'HeadingTwo',
fontName = stdFont,
fontSize = 12,
leading = 14,
leftIndent = 1*cm)
Story=[]
a = Story.append
a(NextPageTemplate('blank'))
a(PageBreak())
a(NextPageTemplate('ToC'))
a(PageBreak())
toc = TableOfContents()
toc.levelStyles = [ h1, h2 ]
a(toc)
a(NextPageTemplate('blank'))
a(PageBreak())
"""###LEFT PAGES SHOULD BE STYLED RIGHT-ALIGNED, AND RIGHT PAGES LEFT-ALIGNED###"""
#print type(plants)
for i, plant in enumerate(plants):
### THIS INITIAL CHECK BREAKS AS IT NEEDS TO BE BASED ON PAGE NUMBER, NOT PLANT NUMBER!!! ###
if i %2 == 0: #IF FIRST PLANT APPEARS ON A RIGHTSIDE PAGE, ELSE REVERSE THE R and L
page='R'
template = 'right'
second = 'left'
else:
page='L'
template ='left'
second = 'right'
#FIRST THINGS FIRST:
#Make sure the page templates flow nicely for each plant "chapter"
a(NextPageTemplate([template, '*', ('secondary_'+template), ('secondary_'+second) ]))
a(PageBreak())
'''CAPTURE PLANT INFO IN OBJECTS'''
p = plant
'''for info in plant:
print info, p[info]'''
'''Header'''
latin = p["Latin Name"]
common = p["Common name"]
family = p["Family"]
syn = p["Synonyms"]
"""X. congestum. (Lour.)Merrill. X. racemosum. Miq. Apactis japonica. Croton congestum.
Flacourtia japonica. Walp. Hisingera japonica. H. racemosa."""
hardy = str(p["Hardyness"])
'''Basic Info'''
author = p["Author"]
botanicalrefs = p["Botanical references"]
width = p["Width"]
height = p["Height"]
habit = p["Habit"]
planttype = clean("Deciduous/Evergreen", p)
plantrange = p["Range"]
habitat = p["Habitat"]
soil = clean("Soil", plant)
shade = p["Shade"]
moisture = p["Moisture"]
drained = p["Well-drained"]
nf = p["Nitrogen fixer"]
pH = p["pH"]
acid = p["Acid"]
alkaline = p["Alkaline"]
saline = p["Saline"]
wind = p["Wind"]
rate = clean("Growth rate", plant)
pollution = p["Pollution"]
poorsoil = p["Poor soil"]
drought = p["Drought"]
heavyclay = p["Heavy clay"]
tender = clean("FrostTender", plant)
inleaf = p["In leaf"]
flowering = p["Flowering time"]
seedripens = p["Seed ripens"]
flowertype = p["Flower Type"]
pollinators = p["Pollinators"]
selffertile = clean("Self-fertile", plant)
hazards = p["Known hazards"]
rating_edible = p["Rating"]
rating_med = p["Medicinal Rating"]
edibleuses = p["Edible uses"]
medicinaluses = getMedicinal(plant)
otheruses = p["Uses notes"]
#the following encoding allows for special characters such as degree symbol
cultivation = unicode(p["Cultivation details"], 'latin-1')#'ISO-8859-1')
propagation = p["Propagation 1"]
scented = p["Scented"] #boolean - requires further lookup in `ScentedPlants` table
string = '''%s is %s %s growing to %gm by %gm at a %s rate.<br/>
It's habitats are %s <br/><br/> Range: %s
<br/><br/>
Suitable for %s soils. <br/><br/>
Shade: %s, Moisture: %s <br/>
Well-drained: %d, Nitrogen fixer: %d <br/> ph: %s <br/>
Acid: %d, Alkaline: %d, Saline: %d <br/>
Wind: %s
<br/><br/>
Author: %s <br/> Botanical References: %s''' % (
latin, planttype, habit.lower(), width, height, rate,
habitat[0].lower()+habitat[1:], plantrange,
soil, shade, moisture, drained,
nf, pH, acid, alkaline, saline, wind, author, botanicalrefs )
string = unicode(string, 'latin-1')
latinW = stringWidth(latin, styles['latin'].fontName, styles['latin'].fontSize)
commonW = stringWidth(common, styles['common'].fontName, styles['common'].fontSize)
if (latinW + commonW + (_head_pad*3)) > _head_w:
styleName = "LatinName_" + str(i)
latinStyle = PS( name=styleName,
parent=styles['Normal'],
fontName=titleFont,
fontSize=_latinFontS,
leading=22,
spaceAfter=0)
j = 1
#should the latin string be too long, attempt to shrink until it fits
while (latinW + commonW + (_head_pad*3)) > _head_w:
#change the font size until ok...
latinStyle.fontSize = _latinFontS -j
latinW = stringWidth(latin, latinStyle.fontName, latinStyle.fontSize)
j += 0.2
else:
latinStyle = styles['LatinName']
if page == 'L':
headerText = '''<para align="left">
%s
<font face="%s" size="%d"> <super>%s</super></font>
</para>''' % (latin, subtitleFont, 12, common)
else:
headerText = '''<para align="right">
<font face="%s" size="%d"><super>%s</super> </font>
%s
</para>''' % (subtitleFont, 12, common, latin)
latinPara = Paragraph(headerText, latinStyle)
a(FrameBreak('latin'+page))
a(latinPara)
a(FrameBreak('syn'+page))
a(KeepInFrame(_main_w, 1.5*cm,
[Paragraph(syn, styles['syn'+page])],
mode="shrink")) #can be shrink, truncate or overflow
a(FrameBreak('hard'+page))
a(Paragraph(hardy, styles['hardy']))
a(FrameBreak('cal'+page))
#SHALL BE ULTIMATELY POPULATED VIA DATABASE#
greyOut = [ [0,0,1,1,1,1,1,0,0,0,0,0], [0,0,0,0,0,1,1,1,1,0,0,0], [0,0,0,0,0,0,0,0,1,1,1,0] ]
cal = drawCalendar(greyOut)
a(cal)
a(FrameBreak('flow'+page))
a(Paragraph(string, styles['Normal']))
a(Paragraph("Edible Uses", styles['title']))
a(Paragraph("Medicinal Uses", styles['title']))
a(Paragraph("Other Uses", styles['title']))
a(Paragraph("Cultivation", styles['title']))
a(Paragraph(cultivation, styles['Normal']))
a(Paragraph("Propagation", styles['title']))
a(Paragraph(propagation, styles['Normal']))
##ASSEMBLE PDF###
go()
If you're just switching between a "left" and a "right" template, you could try using the _handle_nextPageTemplate method of the BaseDocTemplate class. One way to keep track of page number would be to use the afterPage hook to increment a page number.
from reportlab.platypus import BaseDocTemplate
class MyDocTemplate(BaseDocTemplate):
def __init__(self, *args, **kwargs):
BaseDocTemplate.__init__(self, *args, **kwargs)
self.__pageNum = 1
def afterPage(self):
"""Called after all flowables have been drawn on a page"""
# Increment pageNum since the page has been completed
self.__pageNum += 1
# If the page number is even, force "left-side" template
if self.__pageNum % 2 == 0:
self._handle_nextPageTemplate('left_template')
else:
self._handle_nextPageTemplate('right_template')
I haven't tested the code above, but you may need to use beforePage instead depending on how it checks page template order.

Categories