Вопрос

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.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top