Fully fledged PDF output
authorTero Marttila <terom@fixme.fi>
Mon, 20 Dec 2010 20:51:45 +0200
changeset 3 44122295656a
parent 2 e8b3f3884233
child 4 b3a1ab44f517
Fully fledged PDF output
svv/wsgi.py
--- a/svv/wsgi.py	Mon Dec 20 01:02:56 2010 +0200
+++ b/svv/wsgi.py	Mon Dec 20 20:51:45 2010 +0200
@@ -1,9 +1,17 @@
+# coding: utf-8
+
+"""
+    WSGI frontend/entry point
+"""
+
 import werkzeug
 from werkzeug import exceptions
 from werkzeug import Request, Response
 from werkzeug.routing import Map, Rule
 
 import logging
+            
+import datetime
 
 # logging
 log = logging.getLogger('svv.wsgi')
@@ -100,23 +108,308 @@
     def respond (self, url_values) :
         from reportlab import platypus as rlpp
         from reportlab.lib.units import inch
-        from reportlab.lib.styles import getSampleStyleSheet
+        from reportlab.lib import pagesizes, styles
 
         from cStringIO import StringIO
+        import itertools
 
         buf = StringIO()
-        styles = getSampleStyleSheet()
-        style = styles['Normal']
+
+        # dimensions
+        page_width, page_height = page_size = pagesizes.A4
+        header_height = 1 * inch
+        footer_height = 1 * inch
+
+        margin = inch
+        frame_width = page_width - 2 * margin
+        frame_height = page_height - footer_height - margin / 2 - margin / 2 - header_height
+        frame_bottom = footer_height + margin / 2
+        frame_left = margin
+        frame = rlpp.Frame(frame_left, frame_bottom, frame_width, frame_height)
 
         title = url_values.get('name', "Hello World")
 
-        tpl = rlpp.PageTemplate('test', [ rlpp.Frame(inch, inch, 6 * inch, 9 * inch) ])
-        doc = rlpp.BaseDocTemplate(buf, pageTemplates=[tpl], title=title, author="Test Author", showBoundary=True)
+        def draw_page_tpl (canvas, document) :
+            cols = 4
+            col_width = (page_width - 2 * margin) / 4 
+            
+            def draw_column (x, y, title, lines, gray=None) :
+                text = canvas.beginText(x, y)
+                
+                if gray:
+                    text.setFillGray(gray)
+                
+                # title + rest
+                text.setFont("Times-Bold", 8)
+                text.textLine(title)
+                text.setFont("Times-Roman", 8)
+                text.textLines(lines)
+
+                canvas.drawText(text)
+
+
+            # 
+            # header
+            #
+            x = margin
+            y = page_height - margin / 4
+
+            # spacer
+            y = page_height - footer_height
+
+            canvas.setLineWidth(0.5)
+            canvas.line(x - margin / 2, y, page_width - margin / 2, y)
+ 
+            # columns
+            x = margin + 3 * col_width
+            y = page_height - margin / 4
+
+            today = datetime.date.today()
+
+            draw_column(x, y - inch / 6, "Vuokrasopimus", str("%(title)s\n%(today)s" % dict(
+                today = today.strftime("%d / %m / %Y"),
+                title = title
+            ))), 
+           
+           
+            #
+            # footer
+            #
+            x = margin
+            y = footer_height
+
+            # spacer
+            canvas.setLineWidth(0.5)
+            canvas.line(x - margin / 2, y, page_width - margin / 2, y)
+
+            # column
+            draw_column(x, y - inch / 6, "Teekkarispeksi Ry", "www.teekkarispeksi.fi", 0.4)
+            x += col_width
+
+            draw_column(x, y - inch / 6, "Tekniikkavastaava", "Juha Kallas\n045 xxx yyzz\njskallas@cc.hut.fi", 0.4)
+            x += col_width
+
+            draw_column(x, y - inch / 6, "Varastovastaava", "Joel Pirttimaa\n045 xxx yyzz\njhpirtti@cc.hut.fi", 0.4)
+            x += col_width
+
+        tpl = rlpp.PageTemplate('page', [frame], onPage=draw_page_tpl, pagesize=page_size)
+        doc = rlpp.BaseDocTemplate(buf, pageTemplates=[tpl], title=title, author="Test Author", showBoundary=False, pageSize=page_size)
+
+        samplestyles = styles.getSampleStyleSheet()
+ 
+        # normal text
+        h1 = styles.ParagraphStyle('Heading1', samplestyles['h1'],
+                fontName        = 'Times-Bold',
+                fontSize        = 22,
+                spaceBefore     = 0,
+                spaceAfter      = 0,
+
+        )
+
+        h2 = styles.ParagraphStyle('Heading2', samplestyles['h2'],
+                fontName        = 'Times-Bold',
+                fontSize        = 14,
+                spaceBefore     = 6,
+                spaceAfter      = 0,
+        )
+        
+        h3 = styles.ParagraphStyle('Heading3', samplestyles['h3'],
+                fontName        = 'Times-Italic',
+                fontSize        = 12,
+                spaceBefore     = 0,
+                spaceAfter      = 0,
+        )
+
+        text = styles.ParagraphStyle('Text', samplestyles['Normal'],
+
+        )
+        
+        # standard indent
+        list_indent = inch / 4
+
+        # root title
+        list_h1 = styles.ParagraphStyle('ListHeading1', samplestyles['h1'],
+                bulletIndent    = 0,
+                leftIndent      = 0,
+        )
+
+        # section
+        list_h2 = styles.ParagraphStyle('ListHeading2', samplestyles['h2'],
+                bulletIndent    = 0,
+                leftIndent      = list_indent,
+                fontName        = 'Times-Bold',
+                fontSize        = 10,
+                leading         = 12,
+                spaceBefore     = 6,
+                spaceAfter      = 0,
+        )
+        
+        # segment
+        list_h3 = styles.ParagraphStyle('ListHeading3', samplestyles['h3'],
+                bulletIndent    = 0,
+                leftIndent      = list_indent,
+                fontName        = 'Times-Italic',
+                fontSize        = 10,
+                leading         = 12,
+                spaceBefore     = 0,
+                spaceAfter      = 0,
+        )
+
+        list_text = styles.ParagraphStyle('ListText', samplestyles['Normal'],
+                bulletIndent    = 0,
+                leftIndent      = list_indent,
+        )
+
+        def tree_seq () :
+            """
+                Tree list numbering
+            """
+            
+            for idx in itertools.count(1) :
+                yield "%d." % (idx, )
+
+        # tree root
+        tree = ("Sopimusehdot", h2, None, tree_seq(), [
+            ("Osapuolet", list_h2, None, tree_seq(), [
+                (None, None, "Teekkarispeksi ry (Y-tunnus 1888541-7), jäljempänä “Vuokranantaja”."),
+                (None, None, title + u", jäljempänä “Vuokraaja”. 1.1 ja 1.2 jäljempänä yhdessä “osapuolet”.")
+            ]),
+            ("Kaluston lainaaminen", list_h2, None, tree_seq(), [
+                ("Yleistä", list_h3, "Tässä sopimuksessa sovitaan toiminnasta Vuokranantajan lainatessa tanssimattoja Vuokraajalle"),
+                ("Vuokranantajan velvollisuudet", list_h3, "Vuokranantaja sitoutuu toimittamaan sovittuna ajankohtana Vuokraajalle erikseen sovittava (liite) määrä tanssimattoja."),
+                ("Blaa Blaa", list_h3, "Etc."),
+            ]),
+            ("Tätä sopimusta ja sitä koskevia erimielisyyksiä koskevat määräykset", list_h2, None, tree_seq(), [
+                ("Sopimuksen voimassaolo", list_h3, "Sopimus on osapuolia sitova sen jälkeen, kun osapuolet ovat sen allekirjoittaneet."),
+                ("Muutosten tekeminen", list_h3, "Muutokset sopimukseen on tehtävä kirjallisesti molempien osapuolten kesken."),
+                ("Blaa Blaa", list_h3, "Etc."),
+            ]),
+        ])
+
+        def fmt_tree (bullet, title, title_style, text, seq=None, sublist=[]) :
+            log.debug("bullet(%r) = %r", title, bullet)
+            
+            # fixed
+            text_style = list_text
+
+            # first line, with possible bullet
+            if title :
+                yield rlpp.Paragraph(title, title_style, bullet)
+
+            elif text :
+                yield rlpp.Paragraph(text, text_style, bullet)
+
+            if title and text :
+                # indented text after title
+                yield rlpp.Paragraph(text, text_style)
+
+            if sublist :
+                # following lines, indented
+                yield rlpp.Indenter(list_indent)
+
+                # sub-items
+                for item in sublist :
+                    # get bullet for item
+                    bullet = next(seq, None) if seq else None
+
+                    for element in fmt_tree(bullet, *item) :
+                        yield element
+
+                # de-dent
+                yield rlpp.Indenter(-list_indent)
+
+        class SignatureBlock (rlpp.Flowable) :
+
+            # vertical space per field
+            FIELD_HEIGHT = 2 * inch / 4
+
+            COL_WIDTH_MAX = 4 * inch
+
+            PADDING_BOTTOM = inch / 2
+
+            def __init__ (self, cols, fields, values) :
+                """
+                    cols    - Column titles
+                    fields  - Fields titles, describing the horizontal fields
+                    values  - Pre-filled values as a {(col, field): value} dict
+
+                    desc/value strings can contain formatting codes:
+
+                        column      - title of the current column
+                        today       - today's date in %d/%m/%Y format
+                """
+
+                self.cols = cols
+                self.fields = fields
+                self.values = values
+
+            def wrap (self, width, height) :
+                """
+                    Calculate how much space we use up, returning (width, height)
+                """
+
+                self.width = width
+
+                # consume all available height, to place us at the bottom
+                self.height = max(len(self.fields) * self.FIELD_HEIGHT, height)
+
+                return self.width, self.height
+            
+            def formatString (self, text, col_title) :
+                return text % dict(
+                        column      = col_title,
+                        today       = datetime.date.today().strftime("%d/%m/%Y"),
+                )
+
+            def draw (self) :
+                # target canvas
+                canvas = self.canv
+
+                col_width = min(self.width / len(self.cols), self.COL_WIDTH_MAX)
+                col_margin = col_width * 0.1
+                col_height = len(self.fields) * self.FIELD_HEIGHT + self.PADDING_BOTTOM
+
+                for field_idx, (field_title) in enumerate(self.fields) :
+                    h = self.FIELD_HEIGHT
+                    y = col_height - h * (field_idx + 1)
+
+                    for col_idx, (col_title) in enumerate(self.cols) :
+                        w = col_width
+                        x = w * col_idx
+                        value = self.values.get((col_title, field_title))
+                        title = field_title
+
+                        value = self.formatString(value, col_title) if value else None
+                        title = self.formatString(title, col_title) if title else None
+
+                        if value :
+                            canvas.setFont("Courier-Bold", 14)
+                            canvas.drawString(x + col_margin + inch / 12, y - h / 2 + 2, value)
+                        
+                        # field line
+                        canvas.line(x + col_margin, y - h / 2, x + w - col_margin, y - h / 2)
+
+                        # desc text
+                        canvas.setFont("Times-Italic", 10)
+                        canvas.drawString(x + col_margin + inch / 8, y - h / 2 - 10, title)
+
 
         elements = [
-                rlpp.Paragraph("Hello, World!\n" + title, style)
+                rlpp.Paragraph("Vuokrasopimus", h1),
+                rlpp.Paragraph("Teekkarispeksi ry AV-tekniikka", h3),
+        ] + list(fmt_tree(None, *tree)) + [
+                rlpp.Paragraph("Nouto", h2),
+                rlpp.Paragraph("\t\tAika: _______________\tPaikka: _______________", text),
+                rlpp.Paragraph("Palautus", h2),
+                rlpp.Paragraph("\t\tAika: _______________\tPaikka: _______________", text),
+                
+                SignatureBlock(("Vuokranantaja", "Vuokraaja"), ("%(column)s", "Nimen selvennys", "Aika ja paikka"), {
+                    ('Vuokranantaja', 'Nimen selvennys'): "Joel Pirttimaa",
+                    ('Vuokranantaja', 'Aika ja paikka'): 'Otaniemi, %(today)s',
+                    ('Vuokraaja', 'Aika ja paikka'): 'Otaniemi, %(today)s',
+                }),
         ]
-        
+       
         # render elements to buf as PDF code
         doc.build(elements)