Commits

Michael P. Jung  committed fbb9296

Add QR code rendered as image to bank transfer form

By using a box_size of 1 the file size becomes as small as possible and
all PDF viewers seam to scale images with nearest neighbor filtering.
The previous experiments with a Table and QrCodeWidget did not yield
better results and only casued a bigger file size.

  • Participants
  • Parent commits 2a1ad9e

Comments (0)

Files changed (3)

File dinbrief/contrib/qrcode.py

 from __future__ import absolute_import
 
-from reportlab.lib import colors
-from reportlab.lib.units import mm, cm
-from reportlab.platypus import Flowable
-from reportlab.platypus.tables import Table
-from reportlab.platypus.tables import TableStyle
-from reportlab.graphics import renderPDF
-from reportlab.graphics.barcode.qr import QrCodeWidget
-from reportlab.graphics.shapes import Drawing
-
-from ..styles import styles
+try:
+    from io import BytesIO
+except ImportError:
+    from StringIO import StringIO as BytesIO
 
+import qrcode
+from reportlab.platypus.flowables import Image
 
-class QRCode(Flowable):
+from ..styles import styles
 
-    def __init__(self, data, color=colors.black):
-        Flowable.__init__(self)
-        self.data = data
-        self.widget = QrCodeWidget(data, barLevel='M')
 
-    def wrap(self, availWidth, availHeight):
-        size = min(availWidth, availHeight)
-        size = max(size, 30*mm)
-        self.width = self.height = size
-        return (size, size)
+def qrcode_image(data):
+    # create QRCode object
+    code = qrcode.QRCode(
+            box_size=1,
+            border=0,
+            error_correction=qrcode.constants.ERROR_CORRECT_M)
+    code.add_data(data)
+    code.make()
+    # render QRCode as PNG into memory
+    img = code.make_image()
+    img_data = BytesIO()
+    img.save(img_data, 'PNG')
+    img_data.seek(0)
+    img_data.__repr__ = lambda: 'qrcode.png'
+    # create image floatable
+    return Image(img_data)
 
-    def draw(self):
-        bounds = self.widget.getBounds()
-        drawing = Drawing(
-                self.width, self.height,
-                transform=[self.width/bounds[2], 0, 0,
-                           self.height/bounds[3], 0, 0])
-        drawing.add(self.widget)
-        renderPDF.draw(drawing, self.canv, 0, 0)
-        self.widget.draw()
 
-    # purpose: http://www.hettwer-beratung.de/sepa-spezialwissen/sepa-technische-anforderungen/sepa-purpose-codes-vs-dta-textschl%C3%BCssel/
-    # SCVE = dienstleistungen
-    @classmethod
-    def sepa_credit_transfer(cls, account_holder, iban, bic, amount, reference,
-            purpose='', currency='EUR', color=colors.black):
-        '''
-        Create QRCode object according to EPC069-12
-        http://www.europeanpaymentscouncil.eu/knowledge_bank_detail.cfm?documents_id=607
-        '''
-        assert 1 <= len(account_holder) < 70
-        assert len(currency) == 3
-        assert len(bic) in (7, 11)
-        assert ' ' not in bic
-        assert 1 <= len(iban) <= 34
-        assert ' ' not in iban
-        assert len(purpose) <= 4
-        assert len(reference) <= 35
-        data = '\n'.join((
-            # Service Tag
-            'BCD',
-            # Version
-            '001',
-            # Character set (1=utf-8)
-            '1',
-            # identification code
-            'SCT',
-            # BIC
-            bic,
-            # name
-            account_holder,
-            # IBAN
-            iban,
-            # Amount
-            '%s%.2f' % (currency, amount),
-            # Purpose
-            purpose,
-            # Reference
-            reference,
-            # Unstructured Remittance Information
-            '',
-            # Beneficiary to originator information
-            ''))
-        return cls(data, color)
+def sepa_credit_transfer(account_holder, iban, bic, amount, reference,
+        purpose='', currency='EUR'):
+    '''
+    Create QRCode object according to EPC069-12:
+    http://www.europeanpaymentscouncil.eu/knowledge_bank_detail.cfm?documents_id=607
+    A list of purpose options can be found online:
+    http://www.hettwer-beratung.de/sepa-spezialwissen/sepa-technische-anforderungen/sepa-purpose-codes-vs-dta-textschl%C3%BCssel/
+    '''
+    assert 1 <= len(account_holder) < 70
+    assert len(currency) == 3
+    assert len(bic) in (8, 11)
+    assert ' ' not in bic
+    assert 1 <= len(iban) <= 34
+    assert ' ' not in iban
+    assert len(purpose) <= 4
+    assert len(reference) <= 35
+    return qrcode_image('\n'.join((
+        # Service Tag
+        'BCD',
+        # Version
+        '001',
+        # Character set (1=utf-8)
+        '1',
+        # identification code
+        'SCT',
+        # BIC
+        bic,
+        # name
+        account_holder,
+        # IBAN
+        iban,
+        # Amount
+        '%s%.2f' % (currency, amount),
+        # Purpose
+        purpose,
+        # Reference
+        reference,
+        # Unstructured Remittance Information
+        '',
+        # Beneficiary to originator information
+        '')))

File dinbrief/invoice/bank_transfer_form.py

 from ..styles import styles
 
 
-def BankTransferForm(account_holder, iban, bic, reference, amount, currency=u'€'):
+def BankTransferForm(account_holder, iban, bic, reference, amount,
+        currency=u'EUR', currency_label=u'€', show_qrcode=False):
 
-    table_style = [
+    col_widths = [40*mm, 60*mm]
+    row_heights = [7*mm, 6*mm, 6*mm, 6*mm, 7*mm]
+
+    table_style = []
+
+    if show_qrcode:
+        from ..contrib import qrcode
+        table_style += [
+            ('SPAN', (2, 0), (2, 4)),
+        ]
+        qrcode_size = sum(row_heights) - 4*mm
+        col_widths += [qrcode_size + 4*mm]
+        qrcode_image = qrcode.sepa_credit_transfer(
+            account_holder=account_holder,
+            iban=''.join(iban.split()),
+            bic=''.join(bic.split()),
+            reference=reference,
+            amount=amount,
+            currency=currency)
+        qrcode_image.drawWidth = qrcode_size
+        qrcode_image.drawHeight = qrcode_size
+
+    table_style += [
         ('ALIGN', (0, 0), (-1, -1), 'LEFT'),
         ('VALIGN', (0, 0), (-1, -1), 'TOP'),
         ('TOPPADDING', (0, 0), (-1, -1), 1*mm),
         ('LINEBELOW', (0, -1), (-1, -1), 0.2*mm, colors.black),
     ]
 
-    col_widths = [40*mm, 60*mm]
-
     def data_generator():
         yield (
             Paragraph(_(u'Account holder') + ':', styles['TableCell']),
-            Paragraph(account_holder, styles['TableCell']),
-        )
+            Paragraph(account_holder, styles['TableCell'])
+        ) + ((qrcode_image,) if show_qrcode else ())
         yield (
             Paragraph(_(u'IBAN') + ':', styles['TableCell']),
             Paragraph(iban, styles['TableCell']),
         )
         yield (
             Paragraph(_(u'Amount') + ':', styles['TableCell']),
-            Paragraph(u'%s %s' % (number_format(amount), currency), styles['TableCell']),
+            Paragraph(u'%s %s' % (number_format(amount, 2), currency_label), styles['TableCell']),
         )
         yield (
             Paragraph(_(u'Reference') + ':', styles['TableCell']),
     return Table(
         data=list(data_generator()),
         colWidths=col_widths,
+        rowHeights=row_heights,
         style=TableStyle(table_style),
         hAlign='LEFT')
 from reportlab.platypus.flowables import Spacer
 
 from dinbrief.constants import CONTENT_WIDTH
-from dinbrief.contrib.qrcode import QRCode
 from dinbrief.document import Document
 from dinbrief.invoice import Invoice, Item, ItemTable, TotalTable
 from dinbrief.invoice import BankTransferForm
             Spacer(CONTENT_WIDTH, 10*mm),
             BankTransferForm(
                 account_holder='Muster AG',
-                iban='DE00000000000000000000',
-                bic='XXXXDE00XXX',
-                amount=Decimal('6.66'),
-                reference='2012-0815'),
-            QRCode.sepa_credit_transfer(
-                account_holder='Muster AG',
-                iban='DE00000000000000000000',
-                bic='XXXXDE00XXX',
-                amount=Decimal(invoice.gross),
-                reference='2012-0815'),
+                iban='DE36 0000 0000 0000 0000 00',
+                bic='XXXXDEXX',
+                amount=invoice.gross,
+                reference='2012-0815',
+                show_qrcode=True),
         ])
     template = BriefTemplate(fh, document)
     template.build(document.content)