In a program I'm writing for a friend's business, I am using the reportlab module to build PDF documents for various reports. In most cases, the report can be contained on a single page, but it may span across two pages in some rare circumstances. In those rare situations, what I'd like to do is re-format the page with smaller top and bottom margins to see if I can get it to fit on a single page. If not, I'll just use minimal margins and let it span two pages.
To build a report, I'm creating an instance of the SimpleDocTemplate
class. After passing in all my flowables to the build
method, I found that I can query the page
attribute to see how many pages it used. Here's the first thing I tried:
parts = []
path = "/path/to/my/file.pdf"
# ... a bunch of code that appends various flowables to 'parts'
doc = SimpleDocTemplate(path, pagesize=landscape(letter))
doc.build(parts)
# Shrink the margins while it's more than one page and the margins are above zero
while doc.page > 1 and not any([doc.topMargin <= 0, doc.bottomMargin <= 0]):
# Decrease the page margins and rebuild the page
doc.topMargin -= inch * .125
doc.bottomMargin -= inch * .125
doc.build(parts)
# If the margins are nil and we're still at 2 or more pages, use minimal margins
if doc.page > 1:
doc.topMargin = inch * .25
doc.bottomMargin = inch * .25
doc.build(parts)
I assumed that calling the build
method with the same parts after changing the margins would recalculate everything. However, after much trial and error, I learned that the parts
list that's passed to the build
method is essentially stripped clean during the build process, leaving parts
as an empty list. Once this was passed back to the build
method again, it created a document with zero pages.
To work around that, I tried building the document using a copy of the parts
list:
doc.build(parts[:])
That led to some bizarre exception, so I tried a deep copy using the copy
module:
doc.build(copy.deepcopy(parts))
This didn't throw any exceptions, but didn't change the margins, either.
Getting a little desperate, I probed deeper into the SimpleDocTemplate
attributes and found a method named _calc
. Thinking that this might recalculate the page, I tried calling it after changing the margins. It didn't throw any exceptions, but didn't work, either.
The only way I've managed to make it work is to use the deepcopy
process and build brand new documents every time I tweak the margins:
doc = SimpleDocTemplate(path, pagesize=landscape(letter))
doc.build(copy.deepcopy(parts))
# Shrink the margins while it's more than one page and the margins are above zero
while doc.page > 1 and not any([doc.topMargin <= 0, doc.bottomMargin <= 0]):
doc.topMargin -= inch * .125
doc.bottomMargin -= inch * .125
doc = SimpleDocTemplate(path, pagesize=landscape(letter),
topMargin = doc.topMargin,
bottomMargin = doc.bottomMargin)
doc.build(copy.deepcopy(parts))
# If the margins are nil and we're still at 2 or more pages, use minimal margins
if doc.page > 1:
doc.topMargin = inch * .25
doc.bottomMargin = inch * .25
doc = SimpleDocTemplate(path, pagesize=landscape(letter),
topMargin = doc.topMargin,
bottomMargin = doc.bottomMargin)
doc.build(copy.deepcopy(parts))
However, it feels like a lot of unnecessary work to go that route. I'd prefer to just change the document margins and tell the document to rebuild itself using those new values, but I can't figure out how. Is that even possible?
A very interesting question. However, I believe you have already hit upon the correct way to do this in ReportLab. The build process is a one-time thing that you can do to a document because it produces side-effects that you cannot go back from. Thankfully, as you've already discovered, although somewhat annoying, it's not that hard to do what you want.