diff --git a/opentech/apply/funds/templates/funds/applicationsubmission_simplified_detail.html b/opentech/apply/funds/templates/funds/applicationsubmission_simplified_detail.html
index f0999f261d0a79375803958472e8751276b24a0f..b0810bc01f9104f9c2c96fdf3beeeeef27ef410a 100644
--- a/opentech/apply/funds/templates/funds/applicationsubmission_simplified_detail.html
+++ b/opentech/apply/funds/templates/funds/applicationsubmission_simplified_detail.html
@@ -1,4 +1,5 @@
 {% extends "base-apply.html" %}
+{% load static %}
 
 {% block title %}{{ object.title }}{% endblock %}
 
@@ -18,6 +19,12 @@
                 <span>{{ object.round }}</span>
                 <span>Lead: {{ object.lead }}</span>
             </h5>
+            <a
+                class="button button--primary simplified__button"
+                href="{% url "apply:submissions:download" pk=object.pk %}"
+            >
+                Download PDF
+            </a>
         </div>
     </div>
 
@@ -34,3 +41,7 @@
     </div>
 </div>
 {% endblock %}
+
+{% block extra_js %}
+    <script src="{% static 'js/apply/submission-text-cleanup.js' %}"></script>
+{% endblock %}
diff --git a/opentech/apply/funds/urls.py b/opentech/apply/funds/urls.py
index 706fddede0349dcf01515894b85a659f4e23e46e..122472c123c1d140a63e006fb13dbddedac2c044 100644
--- a/opentech/apply/funds/urls.py
+++ b/opentech/apply/funds/urls.py
@@ -15,7 +15,8 @@ from .views import (
     SubmissionSealedView,
     SubmissionDeleteView,
     SubmissionPrivateMediaView,
-    SubmissionDetailSimplifiedView
+    SubmissionDetailPDFView,
+    SubmissionDetailSimplifiedView,
 )
 from .api_views import (
     CommentEdit,
@@ -45,6 +46,7 @@ submission_urls = ([
         path('edit/', SubmissionEditView.as_view(), name="edit"),
         path('sealed/', SubmissionSealedView.as_view(), name="sealed"),
         path('simplified/', SubmissionDetailSimplifiedView.as_view(), name="simplified"),
+        path('download/', SubmissionDetailPDFView.as_view(), name="download"),
         path('delete/', SubmissionDeleteView.as_view(), name="delete"),
         path(
             'documents/<uuid:field_id>/<str:file_name>',
diff --git a/opentech/apply/funds/views.py b/opentech/apply/funds/views.py
index 3347d8621c28eafb0387d2fe77059671e4727d66..c757e048335a60a1954748851c71d341c4d4a027 100644
--- a/opentech/apply/funds/views.py
+++ b/opentech/apply/funds/views.py
@@ -5,13 +5,15 @@ from django.contrib.auth.mixins import UserPassesTestMixin
 from django.contrib import messages
 from django.core.exceptions import PermissionDenied
 from django.db.models import Count, F, Q
-from django.http import HttpResponseRedirect, Http404
+from django.http import FileResponse, HttpResponseRedirect, Http404
 from django.shortcuts import get_object_or_404
 from django.urls import reverse_lazy
 from django.utils.decorators import method_decorator
 from django.utils.safestring import mark_safe
 from django.utils.translation import ugettext_lazy as _
+from django.views import View
 from django.views.generic import CreateView, DetailView, FormView, ListView, UpdateView, DeleteView
+from django.views.generic.detail import SingleObjectMixin
 
 from django_filters.views import FilterView
 from django_tables2.views import SingleTableMixin
@@ -29,6 +31,7 @@ from opentech.apply.projects.forms import CreateProjectForm
 from opentech.apply.projects.models import Project
 from opentech.apply.review.views import ReviewContextMixin
 from opentech.apply.users.decorators import staff_required
+from opentech.apply.utils.pdfs import make_pdf
 from opentech.apply.utils.storage import PrivateMediaView
 from opentech.apply.utils.views import DelegateableListView, DelegateableView, ViewDispatcher
 
@@ -947,3 +950,34 @@ class SubmissionDetailSimplifiedView(DetailView):
             raise Http404
 
         return obj
+
+
+@method_decorator(staff_required, name='dispatch')
+class SubmissionDetailPDFView(SingleObjectMixin, View):
+    model = ApplicationSubmission
+
+    def get_object(self, queryset=None):
+        obj = super().get_object(queryset)
+
+        if not hasattr(obj, 'project'):
+            raise Http404
+
+        return obj
+
+    def get(self, request, *args, **kwargs):
+        self.object = self.get_object()
+        pdf = make_pdf(
+            title=self.object.title,
+            meta=[
+                self.object.stage,
+                self.object.page,
+                self.object.round,
+                f"Lead: { self.object.lead }",
+            ],
+            content=self.object.output_text_answers()
+        )
+        return FileResponse(
+            pdf,
+            as_attachment=True,
+            filename=self.object.title + '.pdf',
+        )
diff --git a/opentech/apply/stream_forms/templates/stream_forms/render_field.html b/opentech/apply/stream_forms/templates/stream_forms/render_field.html
index 01afc0c0ce157203f85b21803c275c113c4fefca..47387630e685bad0de9aa5b70894e96dcdcdfbe4 100644
--- a/opentech/apply/stream_forms/templates/stream_forms/render_field.html
+++ b/opentech/apply/stream_forms/templates/stream_forms/render_field.html
@@ -1,9 +1,11 @@
 {% if include_question %}
 <section>
-    <h4>{{ value.field_label }}</h4>
+    <h4 class="question">{{ value.field_label }}</h4>
 {% endif %}
 
-    {% block data_display %}<span>{{ data }}</span>{% endblock %}
+<div class="answer">
+{% block data_display %}<p>{{ data }}</p>{% endblock %}
+</div>
 
 {% if include_question %}
 </section>
diff --git a/opentech/apply/stream_forms/templates/stream_forms/render_unsafe_field.html b/opentech/apply/stream_forms/templates/stream_forms/render_unsafe_field.html
index 04ea0ffc4395d43f00b90a1fe36a9ec95e039e65..2f11036840264deaa73e001b739e680cdff2b849 100644
--- a/opentech/apply/stream_forms/templates/stream_forms/render_unsafe_field.html
+++ b/opentech/apply/stream_forms/templates/stream_forms/render_unsafe_field.html
@@ -2,7 +2,7 @@
 {% load bleach_tags %}
 {% block data_display %}
     {% if data %}
-        {{ data|bleach }}
+        <p>{{ data|bleach }}</p>
     {% else %}
         {{ block.super }}
     {% endif %}
diff --git a/opentech/apply/utils/media/fonts/Montserrat-Bold.ttf b/opentech/apply/utils/media/fonts/Montserrat-Bold.ttf
new file mode 100644
index 0000000000000000000000000000000000000000..527ff38befb64d492f55cd117027dade2b0d2475
Binary files /dev/null and b/opentech/apply/utils/media/fonts/Montserrat-Bold.ttf differ
diff --git a/opentech/apply/utils/media/fonts/Montserrat-BoldItalic.ttf b/opentech/apply/utils/media/fonts/Montserrat-BoldItalic.ttf
new file mode 100644
index 0000000000000000000000000000000000000000..79bf375d1790ac5f9e16a4de985c931cf082d5eb
Binary files /dev/null and b/opentech/apply/utils/media/fonts/Montserrat-BoldItalic.ttf differ
diff --git a/opentech/apply/utils/media/fonts/Montserrat-Italic.ttf b/opentech/apply/utils/media/fonts/Montserrat-Italic.ttf
new file mode 100644
index 0000000000000000000000000000000000000000..f8e361c19fffb63fbd216bcf1554522bf6f531f6
Binary files /dev/null and b/opentech/apply/utils/media/fonts/Montserrat-Italic.ttf differ
diff --git a/opentech/apply/utils/media/fonts/Montserrat-Regular.ttf b/opentech/apply/utils/media/fonts/Montserrat-Regular.ttf
new file mode 100644
index 0000000000000000000000000000000000000000..17b0ba8663453d75028971e33e30fba0033e6e04
Binary files /dev/null and b/opentech/apply/utils/media/fonts/Montserrat-Regular.ttf differ
diff --git a/opentech/apply/utils/media/fonts/NotoSans-Bold.ttf b/opentech/apply/utils/media/fonts/NotoSans-Bold.ttf
new file mode 100644
index 0000000000000000000000000000000000000000..ab11d316397b9652ce96c2675f3b7a46cf6de13e
Binary files /dev/null and b/opentech/apply/utils/media/fonts/NotoSans-Bold.ttf differ
diff --git a/opentech/apply/utils/media/fonts/NotoSans-BoldItalic.ttf b/opentech/apply/utils/media/fonts/NotoSans-BoldItalic.ttf
new file mode 100644
index 0000000000000000000000000000000000000000..6dfd1e614fd2af31a86ee1e381ab24e7185b3b1e
Binary files /dev/null and b/opentech/apply/utils/media/fonts/NotoSans-BoldItalic.ttf differ
diff --git a/opentech/apply/utils/media/fonts/NotoSans-Italic.ttf b/opentech/apply/utils/media/fonts/NotoSans-Italic.ttf
new file mode 100644
index 0000000000000000000000000000000000000000..1639ad7d402a6df3c944da1688392c0d28f0f1e9
Binary files /dev/null and b/opentech/apply/utils/media/fonts/NotoSans-Italic.ttf differ
diff --git a/opentech/apply/utils/media/fonts/NotoSans-Regular.ttf b/opentech/apply/utils/media/fonts/NotoSans-Regular.ttf
new file mode 100644
index 0000000000000000000000000000000000000000..a1b8994edeacd70067de843a4691b15a0ce5921b
Binary files /dev/null and b/opentech/apply/utils/media/fonts/NotoSans-Regular.ttf differ
diff --git a/opentech/apply/utils/pdfs.py b/opentech/apply/utils/pdfs.py
new file mode 100644
index 0000000000000000000000000000000000000000..e44e6898eee8eb71832867afd06ee6e6a2e63a10
--- /dev/null
+++ b/opentech/apply/utils/pdfs.py
@@ -0,0 +1,273 @@
+import os
+import io
+
+from reportlab.lib import pagesizes
+from reportlab.lib.colors import Color, white
+from reportlab.lib.styles import ParagraphStyle as PS
+from reportlab.lib.utils import simpleSplit
+from reportlab.pdfbase import pdfmetrics
+from reportlab.pdfbase.ttfonts import TTFont
+from reportlab.platypus import (
+    KeepTogether,
+    ListFlowable,
+    ListItem,
+    Paragraph,
+    SimpleDocTemplate,
+    Spacer,
+    Table,
+    TableStyle,
+)
+
+
+from bs4 import BeautifulSoup, NavigableString
+
+styles = {
+    'Question': PS(fontName='MontserratBold', fontSize=14, name='Question', spaceAfter=0, spaceBefore=18, leading=21),
+    'Normal': PS(fontName='NotoSans', name='Normal'),
+    'Heading1': PS(fontName='NotoSansBold', fontSize=12, name='Heading1', spaceAfter=4, spaceBefore=12, leading=18),
+    'Heading2': PS(fontName='NotoSansBold', fontSize=10, name='Heading2', spaceAfter=4, spaceBefore=10, leading=15),
+    'Heading3': PS(fontName='NotoSansBold', fontSize=10, name='Heading3', spaceAfter=4, spaceBefore=10, leading=15),
+    'Heading4': PS(fontName='NotoSansBold', fontSize=10, name='Heading4', spaceAfter=4, spaceBefore=10, leading=15),
+    'Heading5': PS(fontName='NotoSansBold', fontSize=10, name='Heading5', spaceAfter=4, spaceBefore=10, leading=15),
+}
+
+font_location = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'media', 'fonts')
+
+
+def font(font_name):
+    return os.path.join(font_location, font_name)
+
+
+pdfmetrics.registerFont(TTFont('Montserrat', font('Montserrat-Regular.ttf')))
+pdfmetrics.registerFont(TTFont('MontserratBold', font('Montserrat-Bold.ttf')))
+pdfmetrics.registerFont(TTFont('MontserratItalic', font('Montserrat-Italic.ttf')))
+pdfmetrics.registerFont(TTFont('MontserratBoldItalic', font('Montserrat-BoldItalic.ttf')))
+pdfmetrics.registerFontFamily(
+    'Montserrat',
+    normal='Montserrat',
+    bold='MontserratBold',
+    italic='MontserratItalic',
+    boldItalic='MontserratBoldItalic'
+)
+
+pdfmetrics.registerFont(TTFont('NotoSans', font('NotoSans-Regular.ttf')))
+pdfmetrics.registerFont(TTFont('NotoSansBold', font('NotoSans-Bold.ttf')))
+pdfmetrics.registerFont(TTFont('NotoSansItalic', font('NotoSans-Italic.ttf')))
+pdfmetrics.registerFont(TTFont('NotoSansBoldItalic', font('NotoSans-BoldItalic.ttf')))
+pdfmetrics.registerFontFamily(
+    'NotoSans',
+    normal='NotoSans',
+    bold='NotoSansBold',
+    italic='NotoSansItalic',
+    boldItalic='NotoSansBoldItalic'
+)
+
+DARK_GREY = Color(0.0154, 0.0154, 0, 0.7451)
+
+PAGE_WIDTH, PAGE_HEIGHT = pagesizes.legal
+
+# default value from https://github.com/MrBitBucket/reportlab-mirror/blob/58fb7bd37ee956cea45477c8b5aef723f1cb82e5/src/reportlab/platypus/frames.py#L70
+FRAME_PADDING = 6
+
+
+def make_pdf(title, meta, content):
+    buffer = io.BytesIO()
+    doc = SimpleDocTemplate(
+        buffer,
+        pagesize=(PAGE_WIDTH, PAGE_HEIGHT),
+        title=title,
+    )
+
+    blocks = []
+    extra_content = draw_content(content)
+    blocks = [*blocks, *extra_content]
+
+    def title_page(canvas, doc):
+        canvas.saveState()
+        title_spacer = draw_title_block(canvas, doc, title, meta)
+        canvas.restoreState()
+        blocks.insert(0, title_spacer)
+
+    def later_page(canvas, doc):
+        canvas.saveState()
+        draw_header(canvas, doc, title)
+        canvas.restoreState()
+
+    doc.build(blocks, onFirstPage=title_page, onLaterPages=later_page)
+
+    buffer.seek(0)
+    return buffer
+
+
+def split_text(canvas, text, width):
+    return simpleSplit(text, canvas._fontname, canvas._fontsize, width)
+
+
+def draw_header(canvas, doc, title):
+    title_size = 10
+
+    canvas.setFillColor(DARK_GREY)
+    canvas.rect(
+        0,
+        PAGE_HEIGHT - doc.topMargin,
+        PAGE_WIDTH,
+        doc.topMargin,
+        stroke=False,
+        fill=True,
+    )
+    # Set canvas font to correctly calculate the splitting
+
+    canvas.setFont("MontserratBold", title_size)
+    canvas.setFillColor(white)
+
+    text_width = PAGE_WIDTH - doc.leftMargin - doc.rightMargin - 2 * FRAME_PADDING
+    split_title = split_text(canvas, title, text_width)
+
+    pos = (
+        (PAGE_HEIGHT - doc.topMargin) +  # bottom of top margin
+        1.5 * len(split_title) * title_size +  # text
+        title_size / 2  # bottom padding
+    )
+
+    for line in split_title:
+        pos -= title_size
+        canvas.drawString(
+            doc.leftMargin + FRAME_PADDING,
+            pos,
+            line,
+        )
+        pos -= title_size / 2
+
+
+def draw_title_block(canvas, doc, title, meta):
+    title_size = 30
+    meta_size = 10
+
+    text_width = PAGE_WIDTH - doc.leftMargin - doc.rightMargin - 2 * FRAME_PADDING
+
+    # Set canvas font to correctly calculate the splitting
+    canvas.setFont("MontserratBold", title_size)
+    canvas.setFillColor(white)
+    split_title = split_text(canvas, title, text_width)
+
+    total_height = (
+        doc.topMargin +
+        len(split_title) * (title_size + title_size / 2) +  # title + spacing
+        meta_size * 4  # 1 for text 3 for spacing
+    )
+
+    canvas.setFillColor(DARK_GREY)
+    canvas.rect(
+        0,
+        PAGE_HEIGHT - total_height,
+        PAGE_WIDTH,
+        total_height,
+        stroke=False,
+        fill=True,
+    )
+
+    canvas.setFont("MontserratBold", title_size)
+    canvas.setFillColor(white)
+    pos = PAGE_HEIGHT - doc.topMargin
+
+    for line in split_title:
+        pos -= title_size
+        canvas.drawString(
+            doc.leftMargin + FRAME_PADDING,
+            pos,
+            line,
+        )
+        pos -= title_size / 2
+
+    canvas.setFont("MontserratBold", meta_size)
+    canvas.setFillColor(white)
+    meta_text = '  |  '.join(str(text) for text in meta)
+
+    pos -= meta_size * 2
+    canvas.drawString(
+        doc.leftMargin + FRAME_PADDING,
+        pos,
+        meta_text,
+    )
+
+    return Spacer(1, total_height - doc.topMargin)
+
+
+def handle_block(block):
+    paragraphs = []
+
+    for tag in block:
+        if isinstance(tag, NavigableString):
+            text = tag.strip()
+            if text:
+                paragraphs.append(Paragraph(text, styles['Normal']))
+        elif tag.name in {'ul', 'ol'}:
+            style = styles['Normal']
+            if tag.name == 'ul':
+                bullet = 'bullet'
+            elif tag.name == 'ol':
+                bullet = '1'
+
+            paragraphs.append(
+                ListFlowable(
+                    [
+                        ListItem(Paragraph(bullet_item.get_text(), style))
+                        for bullet_item in tag.find_all('li')
+                    ],
+                    bulletType=bullet,
+                )
+            )
+        elif tag.name in {'table'}:
+            paragraphs.append(
+                Table(
+                    [
+                        [
+                            Paragraph(cell.get_text(), styles['Normal'])
+                            for cell in row.find_all({'td', 'th'})
+                        ]
+                        for row in tag.find_all('tr')
+                    ],
+                    colWidths='*',
+                    style=TableStyle([
+                        ('VALIGN', (0, 0), (-1, -1), 'TOP'),
+                        ('LINEABOVE', (0, 0), (-1, -1), 1, DARK_GREY),
+                    ]),
+                )
+            )
+        else:
+            if tag.name in {'p'}:
+                style = styles['Normal']
+            elif tag.name == 'h2':
+                style = styles['Heading2']
+            elif tag.name == 'h3':
+                style = styles['Heading3']
+            elif tag.name == 'h4':
+                style = styles['Heading4']
+            elif tag.name == 'h5':
+                style = styles['Heading5']
+
+            text = tag.get_text()
+            if text:
+                paragraphs.append(Paragraph(text, style))
+    return paragraphs
+
+
+def draw_content(content):
+    paragraphs = []
+
+    for section in BeautifulSoup(content, "html5lib").find_all('section'):
+        question_text = section.select_one('.question').get_text()
+        question = Paragraph(question_text, styles['Question'])
+
+        # Keep the question and the first block of the answer together
+        # this keeps 1 line answers tidy and ensures that bigger responses break
+        # sooner instead of waiting to fill an entire page. There may still be issues
+        first_answer, *rest = handle_block(section.select_one('.answer'))
+        paragraphs.extend([
+            KeepTogether([
+                question,
+                first_answer,
+            ]),
+            *rest
+        ])
+    return paragraphs
diff --git a/opentech/settings/base.py b/opentech/settings/base.py
index 3a19783090da2fddc9072c8b705a2893538033e4..ac6161eba693c7f379199440c5ae504e6ee0b6a0 100644
--- a/opentech/settings/base.py
+++ b/opentech/settings/base.py
@@ -413,7 +413,7 @@ WAGTAILADMIN_RICH_TEXT_EDITORS = {
         'OPTIONS': {
             'features': [
                 'bold', 'italic',
-                'h2', 'h3', 'h4', 'h5',
+                'h1', 'h2', 'h3', 'h4', 'h5',
                 'ol', 'ul',
                 'link'
             ]
diff --git a/opentech/static_src/src/sass/apply/components/_rich-text.scss b/opentech/static_src/src/sass/apply/components/_rich-text.scss
index 256965df61c030bd864de3f4d120b78ae97e2624..8279fcd11051308274b4dc1ae11777b5dde4dc88 100644
--- a/opentech/static_src/src/sass/apply/components/_rich-text.scss
+++ b/opentech/static_src/src/sass/apply/components/_rich-text.scss
@@ -4,7 +4,7 @@
 
     &--answers {
         > section {
-            margin: 0 0 1rem;
+            margin: 0 0 2rem;
 
             p:first-of-type {
                 margin-top: 0;
@@ -14,10 +14,28 @@
                 margin: 0;
             }
         }
+    }
 
-        h4 {
-            margin: 0;
-        }
+    h1 {
+        font-size: 20px;
+        font-family: $font--primary;
+    }
+
+    h2 {
+        font-size: 18px;
+        font-family: $font--primary;
+    }
+
+    h3,
+    h4:not(.question),
+    h5,
+    h6 {
+        font-size: 16px;
+        font-family: $font--primary;
+    }
+
+    .question {
+        margin: 0;
     }
 
     &--hidden {
diff --git a/opentech/static_src/src/sass/apply/components/_simplified.scss b/opentech/static_src/src/sass/apply/components/_simplified.scss
index 15b31cb1ef1b5e3b4d4c9b3bec8bb53fb9bdfc29..0eda6fa9de19d4999811b0fbd9a404d750e57bb6 100644
--- a/opentech/static_src/src/sass/apply/components/_simplified.scss
+++ b/opentech/static_src/src/sass/apply/components/_simplified.scss
@@ -97,7 +97,7 @@
         word-break: break-word;
 
         > section {
-            margin: 0 0 1rem;
+            margin: 0 0 2rem;
 
             p:first-of-type {
                 margin-top: 0;
@@ -108,8 +108,27 @@
             }
         }
 
-        h4 {
+        h1 {
+            font-size: 20px;
+            font-family: $font--primary;
+        }
+
+        h2 {
+            font-size: 18px;
+            font-family: $font--primary;
+        }
+
+        h3,
+        h4:not(.question),
+        h5,
+        h6 {
+            font-size: 16px;
+            font-family: $font--primary;
+        }
+
+        .question {
             margin: 0;
         }
+
     }
 }
diff --git a/requirements.txt b/requirements.txt
index 99fba24cc43a2b94ab2423117503ce9d14a1642a..baece5f064b879ca37e4a8e0d9736c353d3f7567 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -34,6 +34,7 @@ mistune==0.8.4
 more-itertools==7.2.0
 Pillow==5.4.1
 psycopg2==2.7.3.1
+reportlab==3.5.31
 social_auth_app_django==3.1.0
 tomd==0.1.3
 wagtail==2.5.1