Index: main/waeup.kofa/trunk/src/waeup/kofa/applicants/pdf.py
===================================================================
--- main/waeup.kofa/trunk/src/waeup/kofa/applicants/pdf.py	(revision 8090)
+++ main/waeup.kofa/trunk/src/waeup/kofa/applicants/pdf.py	(revision 8091)
@@ -56,15 +56,4 @@
             ar_translation, self.context.application_number)
 
-    def _setUpWidgets(self):
-        """Setup simple display widgets.
-
-        Returns the list of widgets.
-        """
-        request = TestRequest()
-        return setUpEditWidgets(
-            self.form_fields, 'form', self.context, request, {},
-            for_display=True, ignore_request=True
-            )
-
     def _getCourseAdmittedLink(self, view):
         """Return link, title and code in html format to the certificate
@@ -79,101 +68,18 @@
         return ''
 
-    def _addPassportImage(self, data):
-        """Add passport image to data stream.
+    def _getDeptAndFaculty(self):
+        """Return long titles of department and faculty.
 
-        If no image exists yet the default image is added.
+        Returns a list [department_title, faculty_title]
+
+        If the context applicant has no course admitted or dept. and
+        faculty cannot be determined, ``None`` is returned.
         """
-        img = getUtility(IExtFileStore).getFileByContext(self.context)
-        if img is None:
-            img = open(DEFAULT_PASSPORT_IMAGE_PATH, 'rb')
-        doc_img = Image(img.name, width=4*cm, height=3*cm, kind='bound')
-        data.append([doc_img])
-        data.append([Spacer(1, 18)])
-        return data
-
-    def _addDeptAndFaculty(self, data):
-        """If we have a valid course admitted, add dept. and faculty to data
-           stream.
-        """
-        style = getSampleStyleSheet()
         course_admitted = self.context.course_admitted
         dept = getattr(
                 getattr(course_admitted, '__parent__', None),
                 '__parent__', None)
-        if dept is None:
-            return data
-        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
-        dp_translation = translate(_('Department:'),
-            'waeup.kofa', target_language=portal_language)
-        f_label = '<font size=12>%s</font>' % dp_translation
-        f_text = '<font size=12>%s</font>' % dept.longtitle()
-        f_label = Paragraph(f_label, style["Normal"])
-        f_text = Paragraph(f_text, style["Normal"])
-        data.append([f_label,f_text])
-
         faculty = getattr(dept, '__parent__', None)
-        if faculty is None:
-            return data
-        fc_translation = translate(_('Faculty:'),
-            'waeup.kofa', target_language=portal_language)
-        f_label = '<font size=12>%s</font>' % fc_translation
-        f_text = '<font size=12>%s</font>' % faculty.longtitle()
-        f_label = Paragraph(f_label, style["Normal"])
-        f_text = Paragraph(f_text, style["Normal"])
-        data.append([f_label,f_text])
-        return data
-
-    def _getWidgetsTable(self, view):
-        """Return a reportlab table buildt of widgets.
-        """
-        style = getSampleStyleSheet()
-        table_data = []
-        table_style = [('LEFTPADDING', (0,0), (0,-1), 0),
-                       ('VALIGN', (0,0), (-1,-1), 'TOP'),
-                       ]
-        row_num = 0
-        widgets = self._setUpWidgets()
-        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
-        for widget in widgets:
-            separators = getUtility(IApplicantsUtils).SEPARATORS_DICT
-            if separators and separators.get(widget.name):
-                f_headline = ('<font size=12><strong>%s</strong></font>'
-                    % translate(separators[widget.name], 'waeup.kofa',
-                    target_language=portal_language))
-                f_headline = Paragraph(f_headline, style["Normal"])
-                table_data.append([f_headline ])
-                table_style.append(('SPAN', (0,row_num), (-1,row_num)),)
-                table_style.append(
-                    ('TOPPADDING', (0,row_num), (-1,row_num), 12),)
-                row_num += 1
-            f_label = '<font size=12>%s</font>:' % translate(
-                widget.label.strip(), 'waeup.kofa',
-                target_language=portal_language)
-            f_label = Paragraph(f_label, style["Normal"])
-            f_text = '<font size=12>%s</font>' % widget()
-            # Add br tag if widgets contain div tags
-            # which are not supported by reportlab
-            f_text = f_text.replace('</div>', '</div><br />')
-            f_text = f_text.replace('\n', '')
-            f_text = Paragraph(f_text, style["Normal"])
-            table_data.append([f_label,f_text])
-            row_num += 1
-        adm_translation = translate(_('Admitted Course of Study:'),
-                'waeup.kofa', target_language=portal_language)
-        f_label = '<font size=12>%s</font>' % adm_translation
-        f_text = '<font size=12>%s</font>' % (
-            self._getCourseAdmittedLink(view),)
-        f_label = Paragraph(f_label, style["Normal"])
-        f_text = Paragraph(f_text, style["Normal"])
-        table_data.append([f_label,f_text])
-        row_num += 1
-
-        # Add dept. and faculty if applicable
-        table_data = self._addDeptAndFaculty(table_data)
-
-        # Create table
-        table = Table(table_data,style=table_style)
-        table.hAlign = 'LEFT'
-        return table
+        return [x is not None and x.longtitle() or x for x in dept, faculty]
 
     def _addComments(self, data):
@@ -205,13 +111,13 @@
         principal.
         """
-        doc_title = self.title.replace('-', '\n')
+        doc_title = '\n'.join([x.strip() for x in self.title.split('-')])
         style = getSampleStyleSheet()
         data = []
+        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
+        separators = getUtility(IApplicantsUtils).SEPARATORS_DICT
+        creator = getUtility(IPDFCreator)
 
         # append history
-        for msg in self.context.history.messages:
-            f_msg = '<font face="Courier" size=10>%s</font>' % msg
-            data.append(Paragraph(f_msg, style["Normal"]))
-        data.append(Spacer(1, 20))
+        data.extend(creator.fromStringList(self.context.history.messages))
 
         # append photograph
@@ -219,14 +125,17 @@
             getUtility(IExtFileStore).getFileByContext(self.context),
             'name', DEFAULT_PASSPORT_IMAGE_PATH)
-        doc_img = Image(img_path, width=4*cm, height=3*cm, kind='bound')
-        doc_img.hAlign='LEFT'
-        data.append(doc_img)
+        data.append(creator.getImage(img_path))
+        data.append(Spacer(1, 12))
 
         # append widgets
-        data.append(self._getWidgetsTable(view))
+        dept, faculty = self._getDeptAndFaculty()
+        data.append(creator.getWidgetsTable(
+            self.form_fields, self.context, view, lang=portal_language,
+            domain='waeup.kofa', separators=separators,
+            course_label='Admitted Course of Study:',
+            course_link=self._getCourseAdmittedLink(view),
+            dept=dept, faculty=faculty))
 
         # append comments
         data = self._addComments(data)
-
-        creator = getUtility(IPDFCreator)
         return creator.create_pdf(data, None, doc_title)
Index: main/waeup.kofa/trunk/src/waeup/kofa/browser/pdf.py
===================================================================
--- main/waeup.kofa/trunk/src/waeup/kofa/browser/pdf.py	(revision 8090)
+++ main/waeup.kofa/trunk/src/waeup/kofa/browser/pdf.py	(revision 8091)
@@ -25,8 +25,57 @@
 from reportlab.lib.units import cm, inch
 from reportlab.lib.pagesizes import A4, landscape, portrait
-from reportlab.platypus import SimpleDocTemplate
+from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
+from reportlab.platypus import (
+    SimpleDocTemplate, Spacer, Paragraph, Image, Table)
+from zope.component import getUtility
+from zope.formlib.form import setUpEditWidgets
+from zope.i18n import translate
+from zope.publisher.browser import TestRequest
 from waeup.kofa.browser.interfaces import IPDFCreator
+from waeup.kofa.interfaces import MessageFactory as _
+
+#: A reportlab paragraph style for 'normal' output.
+NORMAL_STYLE = getSampleStyleSheet()['Normal']
+
+#: A reportlab paragraph style for output of 'code'.
+CODE_STYLE = ParagraphStyle(
+    name='Code',
+    parent=NORMAL_STYLE,
+    fontName='Courier',
+    fontSize=10,
+    )
+
+#: A reportlab paragraph style for regular form output.
+ENTRY1_STYLE = ParagraphStyle(
+    name='Entry1',
+    parent=NORMAL_STYLE,
+    fontSize=12,
+    )
+
+#: A reportlab paragraph style for headlines or bold text in form output.
+HEADLINE1_STYLE = ParagraphStyle(
+    name='Header1',
+    parent=NORMAL_STYLE,
+    fontName='Helvetica-Bold',
+    fontSize=12,
+    )
+
+def format_html(html):
+    """Make HTML code usable for use in reportlab paragraphs.
+
+    Main things fixed here:
+
+    - remove newlines (not visible in HTML but visible in PDF)
+    - add <br> tags after <div> (as divs break lines in HTML but not in PDF)
+    """
+    # Add br tag if widgets contain div tags
+    # which are not supported by reportlab
+    html = html.replace('</div>', '</div><br />')
+    html = html.replace('\n', '')
+    return html
 
 class PDFCreator(grok.GlobalUtility):
+    """A utility to help with generating PDF docs.
+    """
     grok.implements(IPDFCreator)
 
@@ -35,4 +84,137 @@
     watermark_path = os.path.join(
         os.path.dirname(__file__), 'static', 'pdf_watermark.jpg')
+
+    @classmethod
+    def _setUpWidgets(cls, form_fields, context):
+        """Setup simple display widgets.
+
+        Returns the list of widgets.
+        """
+        request = TestRequest()
+        return setUpEditWidgets(
+            form_fields, 'form', context, request, {},
+            for_display=True, ignore_request=True
+            )
+
+    @classmethod
+    def _addCourse(cls, table_data, row_num, course_label, course_link,
+                   lang, domain):
+        """Add course data to `table_data`.
+        """
+        if not course_label or not course_link:
+            return table_data, row_num
+        f_label= translate(
+            _(course_label), domain, target_language=lang)
+        f_label = Paragraph(f_label, ENTRY1_STYLE)
+        f_text = Paragraph(course_link, ENTRY1_STYLE)
+        table_data.append([f_label, f_text])
+        row_num += 1
+        return table_data, row_num
+
+    @classmethod
+    def _addDeptAndFaculty(cls, table_data, row_num, dept, faculty,
+                           lang, domain):
+        """Add `dept` and `faculty` as table rows to `table_data`.
+
+        `dept` and `faculty` are expected to be strings or None. In
+        latter case they are not put into the table.
+        """
+        for label, text in (('Department:', dept), ('Faculty:', faculty)):
+            if text is None:
+                continue
+            label = translate(_(label), domain, target_language=lang)
+            table_data.append([
+                Paragraph(label, ENTRY1_STYLE),
+                Paragraph(text, ENTRY1_STYLE)])
+            row_num += 1
+        return table_data, row_num
+
+    @classmethod
+    def fromStringList(cls, string_list):
+        """Generate a list of reportlab paragraphs out of a list of strings.
+
+        Strings are formatted with :data:`CODE_STYLE` and a spacer is
+        appended at end.
+        """
+        result = []
+        for msg in string_list:
+            result.append(Paragraph(msg, CODE_STYLE))
+        result.append(Spacer(1, 20))
+        return result
+
+    @classmethod
+    def getImage(cls, image_path, orientation='LEFT'):
+        """Get an image located at `image_path` as reportlab flowable.
+        """
+        img = Image(image_path, width=4*cm, height=3*cm, kind='bound')
+        img.hAlign=orientation
+        return img
+
+    def getWidgetsTable(self, form_fields, context, view, lang='en',
+                        domain='waeup.kofa', separators=None,
+                        course_label=None, course_link=None, dept=None,
+                        faculty=None):
+        """Return a reportlab `Table` instance, created from widgets
+        determined by `form_fields` and `context`.
+
+        - `form_fields`
+           is a list of schema fields as created by grok.AutoFields.
+        - `context`
+           is some object whose content is rendered here.
+        - `view`
+           is currently not used but supposed to be a view which is
+           actually rendering a PDF document.
+        - `lang`
+           the portal language. Used for translations of strings.
+        - `domain`
+           the translation domain used for translations of strings.
+        - `separators`
+           a list of separators.
+        - `course_label` and `course_link`
+           if a course should be added to the table, `course_label`
+           and `course_link` can be given, both being strings. They
+           will be rendered in an extra-row.
+        - `dept` and `faculty`
+           if these are given, we render extra rows with faculty and
+           department.
+        """
+        style = getSampleStyleSheet()
+        table_data = []
+        table_style = [('LEFTPADDING', (0,0), (0,-1), 0),
+                       ('VALIGN', (0,0), (-1,-1), 'TOP'),
+                       ]
+        row_num = 0
+        widgets = self._setUpWidgets(form_fields, context)
+        for widget in widgets:
+            if separators and separators.get(widget.name):
+                f_headline = translate(
+                    separators[widget.name], domain, target_language=lang)
+                f_headline = Paragraph(f_headline, HEADLINE1_STYLE)
+                table_data.append([f_headline ])
+                table_style.append(('SPAN', (0,row_num), (-1,row_num)),)
+                table_style.append(
+                    ('TOPPADDING', (0,row_num), (-1,row_num), 12),)
+                row_num += 1
+            f_label = translate(widget.label.strip(), domain,
+                                target_language=lang)
+            f_label = Paragraph(f_label, ENTRY1_STYLE)
+            f_text = format_html(widget())
+            f_text = Paragraph(f_text, ENTRY1_STYLE)
+            table_data.append([f_label,f_text])
+            row_num += 1
+
+        # Add course (admitted, etc.) if applicable
+        table_data, row_num = self._addCourse(
+            table_data, row_num, course_label, course_link, lang, domain,)
+
+        ## Add dept. and faculty if applicable
+        table_data, row_num = self._addDeptAndFaculty(
+            table_data, row_num, dept, faculty, lang, domain)
+
+        # Create table
+        table = Table(table_data,style=table_style)
+        table.hAlign = 'LEFT'
+        return table
+
 
     def paint_background(self, canvas, doc):
@@ -61,6 +243,7 @@
         # Header
         head_title = getattr(
-            doc, 'kofa_headtitle', getattr(grok.getSite()['configuration'], 'name',
-                                           u'Sample University'))
+            doc, 'kofa_headtitle', getattr(
+                grok.getSite()['configuration'], 'name',
+                u'Sample University'))
         canvas.setFont("Helvetica-Bold", 18)
         canvas.drawString(1.5*cm, height-1.7*cm, head_title)
