Dynamic framesize in Python Reportlab
<p>I tried to generate a shipping list with <a href="/questions/tagged/reportlab" class="post-tag" title="show questions tagged &#39;reportlab&#39;" rel="tag">reportlab</a> in Python.
I was trying to put all parts (like senders address, receivers address, a table) in place by using Platypus <code>Frames</code>.</p>

<p><strong>The first problem</strong> that I ran into was that I needed a lot of <code>Frames</code> to position everything the right way, is there a better way using Platypus?
Because I want the senders address and my address to be on the same height and if I just add them to my <code>story = []</code> they get aligned one below the other.</p>

<p><strong>The next problem</strong> is that the table I'm drawing is dynamic in size and when I reach the end of the <code>Frame</code> ( space I want the table to go) it just does a <code>FrameBreak</code> and continuous in the next frame. So how can I make the <code>Frame</code> (space for my table ) dynamic?</p>

<p>Your use case is a really common one, so Reportlab has a system to help you out. </p>

<p>If you read the user guide about <code>platypus</code> it will introduce you to 4 main concepts: </p>

<blockquote>
  <p><code>DocTemplates</code> the outermost container for the document;</p>
  
  <p><code>PageTemplates</code> specifications for layouts of pages of various kinds;</p>
  
  <p><code>Frames</code> specifications of regions in pages that can contain flowing text or graphics.</p>
  
  <p><code>Flowables</code> 
  Using <code>PageTemplates</code> you can combine "static" content with dynamic on a page in a sensible way like for example logo's, addresses and such.</p>
</blockquote>

<p>You already discovered the <code>Flowables</code> and <code>Frames</code> but propably you did not start on fancy<code>PageTemplates</code> or <code>DocTemplates</code> yet. This makes sense because it isn't necessary for most simple documents. Saddly a shippinglist isn't a simple document, it holds address, logos and important info that has to be on every page. This is where <code>PageTemplates</code> come in.</p>

<p>So how do you use these templates? The concept is simple each page has a certain structure that might differ between pages, for example on page one you want to put the addresses and then start the table while on the second page you only want the table. This would be something like this:</p>

<p><strong>Page 1:</strong>
<a href="http://i.stack.imgur.com/M4t19.png" rel="nofollow"><img src="http://i.stack.imgur.com/M4t19.png" alt="enter image description here"></a></p>

<p><strong>Page 2:</strong>
<a href="http://i.stack.imgur.com/3T9HY.png" rel="nofollow"><img src="http://i.stack.imgur.com/3T9HY.png" alt="enter image description here"></a></p>

<p>A example would look like this: </p>

<p><em>(This would be perfect for the SO Documentation if there was one for Reportlab)</em></p>

<pre><code>from reportlab.lib.pagesizes import A4
from reportlab.lib.styles import getSampleStyleSheet
from reportlab.lib.units import cm
from reportlab.lib import colors
from reportlab.platypus import BaseDocTemplate, Frame, PageTemplate, NextPageTemplate, Paragraph, PageBreak, Table, \
    TableStyle


class ShippingListReport(BaseDocTemplate):
    def __init__(self, filename, their_adress, objects, **kwargs):
        super().__init__(filename, page_size=A4, _pageBreakQuick=0, **kwargs)
        self.their_adress = their_adress
        self.objects = objects

        self.page_width = (self.width + self.leftMargin * 2)
        self.page_height = (self.height + self.bottomMargin * 2)


        styles = getSampleStyleSheet()

        # Setting up the frames, frames are use for dynamic content not fixed page elements
        first_page_table_frame = Frame(self.leftMargin, self.bottomMargin, self.width, self.height - 6 * cm, id='small_table')
        later_pages_table_frame = Frame(self.leftMargin, self.bottomMargin, self.width, self.height, id='large_table')

        # Creating the page templates
        first_page = PageTemplate(id='FirstPage', frames=[first_page_table_frame], onPage=self.on_first_page)
        later_pages = PageTemplate(id='LaterPages', frames=[later_pages_table_frame], onPage=self.add_default_info)
        self.addPageTemplates([first_page, later_pages])

        # Tell Reportlab to use the other template on the later pages,
        # by the default the first template that was added is used for the first page.
        story = [NextPageTemplate(['*', 'LaterPages'])]

        table_grid = [["Product", "Quantity"]]
        # Add the objects
        for shipped_object in self.objects:
            table_grid.append([shipped_object, "42"])

        story.append(Table(table_grid, repeatRows=1, colWidths=[0.5 * self.width, 0.5 * self.width],
                           style=TableStyle([('GRID',(0,1),(-1,-1),0.25,colors.gray),
                                             ('BOX', (0,0), (-1,-1), 1.0, colors.black),
                                             ('BOX', (0,0), (1,0), 1.0, colors.black),
                                             ])))

        self.build(story)

    def on_first_page(self, canvas, doc):
        canvas.saveState()
        # Add the logo and other default stuff
        self.add_default_info(canvas, doc)

        canvas.drawString(doc.leftMargin, doc.height, "My address")
        canvas.drawString(0.5 * doc.page_width, doc.height, self.their_adress)

        canvas.restoreState()

    def add_default_info(self, canvas, doc):
        canvas.saveState()
        canvas.drawCentredString(0.5 * (doc.page_width), doc.page_height - 2.5 * cm, "Company Name")

        canvas.restoreState()


if __name__ == '__main__':
    ShippingListReport('example.pdf', "Their address", ["Product", "Product"] * 50)
</code></pre>

python pdf pdf-generation reportlab