Ignore:
Timestamp:
18 Feb 2013, 16:32:57 (12 years ago)
Author:
uli
Message:

Support for more complex signature tables. Please use 'get_signature_tables()' only until further notice.

Location:
main/waeup.kofa/trunk/src/waeup/kofa/browser
Files:
1 added
1 edited

Legend:

Unmodified
Added
Removed
  • main/waeup.kofa/trunk/src/waeup/kofa/browser/pdf.py

    r9948 r9963  
    2121import grok
    2222import os
     23import pytz
    2324from cStringIO import StringIO
    2425from datetime import datetime
     26from reportlab.lib import colors
    2527from reportlab.lib.units import cm, inch, mm
    2628from reportlab.lib.pagesizes import A4, landscape, portrait
     
    3234from zope.i18n import translate
    3335from zope.publisher.browser import TestRequest
    34 from zope.component import getUtility
     36from zope.component import getUtility, queryUtility
    3537from waeup.kofa.browser.interfaces import IPDFCreator
    3638from waeup.kofa.utils.helpers import now
     
    9092    fontSize=10,
    9193    )
     94
     95#: Base style for signature tables
     96SIGNATURE_TABLE_STYLE = [
     97    ('VALIGN',(0,-1),(-1,-1),'TOP'),
     98    #('FONT', (0,0), (-1,-1), 'Helvetica-BoldOblique', 12),
     99    ('BOTTOMPADDING', (0,0), (-1,0), 36),
     100    ('TOPPADDING', (0,-1), (-1,-1), 0),
     101    ]
     102
    92103
    93104def format_html(html):
     
    109120        html = html.replace('\n', '<br />')
    110121    return html
     122
     123def normalize_signature(signature_tuple):
     124    """Normalize a signature tuple.
     125
     126    Returns a tuple ``(<PRE-TEXT>, <SIGNATURE>, <POST-TEXT>)`` from
     127    input tuple. The following rules apply::
     128
     129      (pre, sig, post)  --> (pre, sig, post)
     130      (pre, sig)        --> (pre, sig, None)
     131      (sig)             --> (None, sig, None)
     132
     133    Also simple strings are accepted as input::
     134
     135      sig               --> (None, sig, None)
     136
     137    If input is not a tuple nor a basestring or if the tuple contains
     138    an invalid number of elements, ``ValueError`` is raised.
     139    """
     140    if isinstance(signature_tuple, basestring):
     141        return (None, signature_tuple, None)
     142    if not isinstance(signature_tuple, tuple):
     143        raise ValueError("signature_tuple must be a string or tuple")
     144    if len(signature_tuple) < 1 or len(signature_tuple) > 3:
     145        raise ValueError("signature_tuple must have 1, 2, or 3 elements")
     146    elif len(signature_tuple) == 1:
     147        signature_tuple = (None, signature_tuple[0], None)
     148    elif len(signature_tuple) == 2:
     149        signature_tuple = (signature_tuple[0], signature_tuple[1], None)
     150    return signature_tuple
     151
     152def vert_signature_cell(signature, date_field=True, date_text=_('Date:'),
     153                        start_row=0, start_col=0, underline=True):
     154    """Generate a table part containing a vertical signature cell.
     155
     156    Returns the table data as list of lists and an according style.
     157
     158    `signature`:
     159       a signature tuple containing (<PRE-TEXT, SIGNATURE-TEXT, POST-TEXT>)
     160
     161    `date_field`:
     162       boolean indicating that a 'Date:' text should be rendered into this
     163       signature cell (or not).
     164
     165    `date_text`:
     166       the text to be rendered into the signature field as 'Date:' text.
     167
     168    `start_row`:
     169       starting row of the signature cell inside a broader table.
     170
     171    `start_col`:
     172       starting column of the signature cell inside a broader table.
     173
     174    `underline`:
     175       boolean indicating that the signature cell should provide a line on
     176       top (`True` by default).
     177
     178    Vertical signature cells look like this::
     179
     180      +------------+
     181      |Pre         |
     182      +------------+
     183      |Date:       |
     184      |            |
     185      +------------+
     186      | ---------- |
     187      | Signature  |
     188      +------------+
     189      |Post        |
     190      +------------+
     191    """
     192    # split signature parts, replacing None with empty string
     193    pre, sig, post = [x or '' for x in signature]
     194    style = ()
     195    x, y = start_col, start_row+2
     196    if underline:
     197        style += (('LINEABOVE', (x, y), (x, y), 1, colors.black),)
     198    d_text = date_field and date_text or ''
     199    data = [[pre], [d_text], [sig], [post]]
     200    col_widths = [1.0]
     201    return data, style, col_widths
     202
     203def horiz_signature_cell(signature, date_field=True, date_text=_('Date'),
     204                         start_row=0, start_col=0):
     205    """Generate a table part containing an horizontal signature cell
     206
     207    Returns the table data as list of lists and an according style.
     208
     209    `signature`:
     210       a signature tuple containing (<PRE-TEXT, SIGNATURE-TEXT, POST-TEXT>)
     211
     212    `date_field`:
     213       boolean indicating that a 'Date:' text should be rendered into this
     214       signature cell (or not).
     215
     216    `date_text`:
     217       the text to be rendered into the signature field as 'Date:' text.
     218
     219    `start_row`:
     220       starting row of the signature cell inside a broader table.
     221
     222    `start_col`:
     223       starting column of the signature cell inside a broader table.
     224
     225    Horizontal signature cells look like this::
     226
     227      +------------+---+-----------+
     228      |Pre text possibly filling   |
     229      |the whole box               |
     230      +------------+---+-----------+
     231      |            |   |           |
     232      |            |   |           |
     233      +------------+---+-----------+
     234      | ---------- |   | --------- |
     235      | Date       |   | Signature |
     236      +------------+---+-----------+
     237      |Post                        |
     238      +------------+---+-----------+
     239
     240    """
     241    pre, sig, post = signature
     242    if not date_field:
     243        data, style, cols = vert_signature_cell(signature, date_field=False)
     244        return data, style, cols
     245    style = (
     246        # let pre and post text span the whole signature cell
     247        ('SPAN', (start_col, start_row), (start_col+2, start_row)),
     248        ('SPAN', (start_col, start_row+3), (start_col+2, start_row+3)),
     249        )
     250    # horizontal cells are buildt from vertical ones chained together
     251    cell1 = vert_signature_cell(  # leftmost date col
     252        (pre, date_text, post), date_field=False,
     253        start_row=start_row, start_col=start_col)
     254    cell2 = vert_signature_cell(  # spacer col (between date and sig)
     255        ('', '', ''), date_field=False, underline=False,
     256        start_row=start_row, start_col=start_col+1)
     257    cell3 = vert_signature_cell(  # rightmost signature column
     258        ('', sig, ''), date_field=False,
     259        start_row=start_row, start_col=start_col+2)
     260    data = map(lambda x, y, z: x+y+z, cell1[0], cell2[0], cell3[0])
     261    style = style + cell1[1] + cell2[1] + cell3[1]
     262    col_widths  = [0.3, 0.03, 0.67] # sums up to 1.0
     263    return data, style, col_widths
     264
     265def signature_row(signatures, start_row=0, horizontal=None, max_per_row=3):
     266    data = [[], [], [], []]
     267    style = ()
     268    signatures = [normalize_signature(sig) for sig in signatures]
     269    start_col = 0
     270    col_widths = []
     271
     272    if horizontal is None:
     273        horizontal = len(signatures) == 1
     274    cell_maker = vert_signature_cell
     275    if horizontal:
     276        cell_maker = horiz_signature_cell
     277    main_cell_height = not horizontal and 36 or 18
     278
     279    for sig in signatures:
     280        sig_data, sig_style, sig_cols = cell_maker(
     281            sig, start_row=start_row, start_col=start_col)
     282        data = map(lambda x, y: x+y, data, sig_data)
     283        style += sig_style
     284        col_widths += sig_cols + [None,]
     285
     286        start_col += 1
     287        # add spacer
     288        spacer, spacer_style, cols = vert_signature_cell(
     289            ('', '', ''), date_field=False, underline=False,
     290            start_row=start_row, start_col=start_col)
     291        data = map(lambda x, y: x+y, data, spacer)
     292        style += spacer_style
     293        start_col += 1
     294
     295    y = start_row
     296    sig_row = start_row + 2
     297    style = style + (
     298        ('TOPPADDING', (0, y+2), (-1, y+2), 0), # signature row
     299        ('BOTTOMPADDING', (0, y+1), (-1, y+1), main_cell_height),
     300        ('LEFTPADDING', (0, y), (-1, y), 1), # pre row
     301        ('LEFTPADDING', (0, y+3), (-1, y+3), 1), # post row
     302        )
     303
     304    if len(signatures) == 1:
     305        # pre and post text should span whole table
     306        style += (('SPAN', (0, y), (-1, y)),
     307                  ('SPAN', (0, y+3), (-1, y+3)),
     308                  )
     309
     310    if data[0] == [''] * len(data[0]):
     311        # no pre text: hide pre row by minimizing padding
     312        style += (('TOPPADDING', (0,y), (-1, y), -6),
     313                  ('BOTTOMPADDING', (0,y), (-1, y), -6),
     314                  )
     315    if data[-1] == [''] * len(data[0]):
     316        # no post text: hide post row by minimizing padding
     317        style += (('TOPPADDING', (0,y+3), (-1, y+3), -6),
     318                  ('BOTTOMPADDING', (0,y+3), (-1, y+3), -6),
     319                  )
     320
     321    if len(signatures) > 1:
     322        data = [x[:-1] for x in data] # strip last spacer
     323        col_widths = col_widths[:-1]
     324    return data, style, col_widths
     325
     326def sig_table(signatures, lang='en', max_per_row=3, horizontal=None,
     327              single_table=False, start_row=0):
     328    space_width = 0.4  # width in cm of space between signatures
     329    table_width = 16.0 # supposed width of signature table in cms
     330    # width of signature cells in cm...
     331    sig_num = len(signatures)
     332    sig_col_width = (table_width - ((sig_num - 1) * space_width)) / sig_num
     333    if sig_num == 1:
     334        sig_col_width = 0.6 * table_width         # signature cell part
     335        space_width = table_width - sig_col_width # spacer part on the right
     336
     337    if sig_num > max_per_row:
     338        sigs_by_row = [signatures[x:x+max_per_row] for x in range(
     339            0, sig_num, max_per_row)]
     340        result = []
     341        curr_row = 0
     342        for num, row_sigs in enumerate(sigs_by_row):
     343            curr_row = 0
     344            if single_table:
     345                curr_row = num * 4
     346            result.append(
     347                sig_table(row_sigs, lang=lang, max_per_row=max_per_row,
     348                          horizontal=False, start_row=curr_row)[0])
     349        missing_num = len(result[-2][0][0]) - len(result[-1][0][0])
     350        if missing_num:
     351            # last row contained less cells, fix it...
     352            result[-1] = ([x + [''] * missing_num for x in result[-1][0]],
     353                          result[-1][1], result[-2][2])
     354        return result
     355
     356    data, style, cols = signature_row(signatures, horizontal=horizontal,
     357                                      start_row=start_row)
     358    style += (('VALIGN', (0,0), (-1,-1), 'TOP'),)
     359
     360    # compute col widths...
     361    col_widths = []
     362    for col in cols:
     363        if col is not None:
     364            col = col * sig_col_width * cm
     365        else:
     366            col = space_width * cm
     367        col_widths.append(col)
     368
     369    # replace strings by paragraphs and translate all contents
     370    for rnum, row in enumerate(data):
     371        for cnum, cell in enumerate(row):
     372            if cell:
     373                content = translate(cell, lang)
     374                data[rnum][cnum] = Paragraph(content, NORMAL_STYLE)
     375    return [(data, style, col_widths),]
     376
     377def get_sig_tables(signatures, lang='en', max_per_row=3, horizontal=None,
     378                   single_table=False):
     379    rows = sig_table(signatures, lang=lang, max_per_row=max_per_row,
     380                     horizontal=horizontal, single_table=single_table)
     381    if single_table:
     382        result_data = []
     383        result_style = ()
     384        for row in rows:
     385            data, style, col_widths = row
     386            result_data += data
     387            result_style += style
     388        return [(result_data, result_style, col_widths),]
     389    return rows
     390
     391def get_signature_tables(signatures, lang='en', max_per_row=3,
     392                         horizontal=None, single_table=False):
     393    """Get a list of reportlab flowables representing signature fields.
     394    """
     395    data_list = get_sig_tables(
     396        signatures, lang=lang, max_per_row=max_per_row,
     397        horizontal=horizontal, single_table=single_table)
     398    return [Table(row_data, style=row_style, colWidths=row_col_widths,
     399                  repeatRows=2)
     400            for row_data, row_style, row_col_widths in data_list]
     401
     402def format_signatures(signatures, max_per_row=3, lang='en',
     403                      single_table=False,
     404                      date_field=True, date_text=_('Date'),
     405                      base_style=SIGNATURE_TABLE_STYLE):
     406    result = []
     407    signature_tuples = [normalize_signature(sig) for sig in signatures]
     408    for pre, sig, post in signature_tuples:
     409        row = []
     410        if pre is not None:
     411            row.append([
     412                translate(pre, lang), '', '', ''])
     413        row.append([
     414            translate(_('Date'), lang), '',
     415            translate(sig, lang), ''
     416            ])
     417        if post is not None:
     418            row.append([
     419                translate(post, lang), '', '', ''])
     420        result.append((row, base_style))
     421    return result
     422
     423
    111424
    112425class NumberedCanvas(Canvas):
     
    346659
    347660        # Header
     661        site_config = None
     662        site = grok.getSite()
     663        if site is not None:
     664            site_config = site.get('configuration', None)
    348665        head_title = getattr(
    349666            doc, 'kofa_headtitle', getattr(
    350                 grok.getSite()['configuration'], 'name',
     667                site_config, 'name',
    351668                u'Sample University'))
    352669        canvas.setFont("Helvetica-Bold", 18)
     
    379696        canvas.setFont("Helvetica", 9)
    380697        if not getattr(doc, 'kofa_nodate', False):
    381             tz = getUtility(IKofaUtils).tzinfo
     698            tz = getattr(queryUtility(IKofaUtils), 'tzinfo', pytz.utc)
     699            #tz = getUtility(IKofaUtils).tzinfo
    382700            today = now(tz).strftime('%d/%m/%Y %H:%M:%S %Z')
    383701            canvas.drawString(2.2*cm, 0.5 * inch,
Note: See TracChangeset for help on using the changeset viewer.