10.3 Creating a Custom Printed Report

If you are interested in custom printed reports you should begin by opening the file pyp_reports.py and reading through the code for the pdfPedigreeMetadata() report. It has been heavily commented so that it can be used as a template for developing other reports. ReportLab provides fairly low-level tools that you can use to assemble documents. The basic idea is that you create a canvas on which your image will be drawn. You then create text objects and draw them on the canvas. When your report is assembled you save the canvas on which it's drawn to a file. PyPedal provides a few convenience functions for such commonly-used layouts as title pages and page "frames". In the following sections of code I will discuss the creation of a pdfInbreedingByYear() printed report to accompany the inbreedingByYear() internal report written in Section 10.2. First, we import ReportLab and check to see if the user provided an output file name. If they didn't, revert to a default.
def pdfInbreedingByYear(pedobj,results,titlepage=0,reporttitle='',reportauthor='', \
    reportfile=''):
    import reportlab
    if reportfile == '':
        _pdfOutfile = '%s_inbreeding_by_year.pdf' % ( pedobj.kw['default_report'] )
    else:
        _pdfOutfile = reportfile
Next call _pdfInitialize(), which returns a dictionary of settings, mostly related to page size and margin locations, that is used throughout the routine. _pdfInitialize() uses the paper_size keyword in the pedigree's options dictionary, which is either `letter' or `A4', and the default_unit, which is either `inch' or `cm' to populate the returned structure. This should allow users to move between paper sizes without little or no work. Once the PDF settings have been computed we instantiate a canvas object on which to draw.
_pdfSettings = _pdfInitialize(pedobj)
canv = canvas.Canvas(_pdfOutfile, invariant=1)
canv.setPageCompression(1)
There is a hook in the code to toggle cover pages on and off. It is arguably rather pointless to put a cover page on a one-page document, but all TPS reports require new coversheets. The call to _pdfDrawPageFrame() frames the page with a header and footer that includes the pedigree name, date and time the report was created, and the page number.
if titlepage:
    if reporttitle == '':
        reporttitle = 'meanMetricBy Report for Pedigree\n%s' \
            % (pedobj.kw['pedname'])
    _pdfCreateTitlePage(canv, _pdfSettings, reporttitle, reportauthor)
_pdfDrawPageFrame(canv, _pdfSettings)
The largest chunk of code in pdfInbreedingByYear() is dedicated to looping over the input dictionary, results, and writing its contents to text objects. If you want to change the typeface for the rendered text, you need to make the appropriate changes to all calls to canv.setFont("Times-Bold", 12). The ReportLab documentation includes a discussion of available typefaces.
canv.setFont("Times-Bold", 12)
tx = canv.beginText( _pdfSettings['_pdfCalcs']['_left_margin'],
    _pdfSettings['_pdfCalcs']['_top_margin'] - 0.5 * \
        _pdfSettings['_pdfCalcs']['_unit'] )
Every printed report will have a section of code in which the input is processed and written to text objects. In this case, the code loops over the key-and-value pairs in results, determines the width of the key, and creates a string with the proper spacing between the key and its value. That string is then written to a tx.textLine() object.
# This is where the actual content is written to a text object that
# will be displayed on a canvas.
for _k, _v in results.iteritems():
    if len(str(_k)) <= 14:
        _line = '\t%s:\t\t%s' % (_k, _v)
    else:
        _line = '\t%s:\t%s' % (_k, _v)
    tx.textLine(_line)
ReportLab's text objects do not automatically paginate themselves. If you write, say, ten pages of material to a text object and render it without manually paginating the object you're going to get a single page of chopped-off text. The following section of code is where the actual pagination occurs, so careful cutting-and-pasting should make pagination seamless.
    # Paginate the document if the contents of a textLine are longer than one page.
    if tx.getY() < _pdfSettings['_pdfCalcs']['_bottom_margin'] + \
        0.5 * _pdfSettings['_pdfCalcs']['_unit']:
        canv.drawText(tx)
        canv.showPage()
        _pdfDrawPageFrame(canv, _pdfSettings)
        canv.setFont('Times-Roman', 12)
        tx = canv.beginText( _pdfSettings['_pdfCalcs']['_left_margin'],
            _pdfSettings['_pdfCalcs']['_top_margin'] -
            0.5 * _pdfSettings['_pdfCalcs']['_unit'] )
Once we're done writing our text to text objects we need to draw the text object on the canvas and make the canvas visible. If you omit this step, perhaps because of the kind of horrible cutting-and-pasting accident to which I am prone, your PDF will not be written to a file.
if tx:
    canv.drawText(tx)
    canv.showPage()
canv.save()
While PyPedal does not yet have any standard reports that include graphics, ReportLab does support adding graphics, such as a pedigree drawing, to a canvas. Interested readers should refer to the ReportLab documentation.
See About this document... for information on suggesting changes.