Skip to content

Commit

Permalink
Use custom fonts in generated PDF reports
Browse files Browse the repository at this point in the history
  • Loading branch information
iamjazzar committed Jul 28, 2018
1 parent 6cd863e commit 7245697
Show file tree
Hide file tree
Showing 3 changed files with 281 additions and 16 deletions.
61 changes: 48 additions & 13 deletions eoc_journal/eoc_journal.py
Expand Up @@ -4,14 +4,16 @@

from collections import OrderedDict
from io import BytesIO
from urlparse import urljoin

import webob
from django.conf import settings

from lxml import html
from lxml.html.clean import clean_html

from reportlab.lib import pagesizes
from reportlab.lib.colors import Color
from reportlab.lib.styles import getSampleStyleSheet
from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer

from problem_builder.models import Answer
Expand All @@ -24,6 +26,7 @@

from .api_client import ApiClient, calculate_engagement_score
from .course_blocks_api import CourseBlocksApiClient
from .pdf_generator import get_style_sheet
from .utils import _, normalize_id

try:
Expand Down Expand Up @@ -63,8 +66,8 @@ class EOCJournalXBlock(StudioEditableXBlockMixin, XBlock):

display_name = String(
display_name=_("Title (display name)"),
help=_("Title to display. Leave blank to use the course title."),
default=None,
help=_("Title to display"),
default=_("Course Journal"),
scope=Scope.content,
)

Expand All @@ -87,6 +90,13 @@ class EOCJournalXBlock(StudioEditableXBlockMixin, XBlock):
list_values_provider=provide_pb_answer_list,
)

pdf_title = String(
display_name=_("PDF Title"),
help=_("Title to of the PDF report. Leave blank to use the course title."),
default=None,
scope=Scope.content,
)

pdf_report_link_heading = String(
display_name=_("PDF Report Link heading"),
help=_("The heading text to display above the link for downloading the PDF Report."),
Expand Down Expand Up @@ -122,15 +132,27 @@ class EOCJournalXBlock(StudioEditableXBlockMixin, XBlock):
scope=Scope.content,
)

custom_font = String(
display_name=_("Default Font"),
help=_("Studio static URL to a custom font file to be used for PDF report. "
"Example: \"/static/myfont.ttf\". Leave empty to use default fonts. "
"You can upload custom TTF font files from the Content - Files "
"& Uploads page."),
default=None,
scope=Scope.settings,
)

editable_fields = (
'display_name',
'key_takeaways_pdf',
'selected_pb_answer_blocks',
'pdf_title',
'pdf_report_link_heading',
'pdf_report_link_text',
'display_metrics_section',
'display_key_takeaways_section',
'display_answers',
'custom_font',
)

def student_view(self, context=None):
Expand Down Expand Up @@ -177,16 +199,15 @@ def serve_pdf(self, request, _suffix):
"""
Builds and serves a PDF document containing user's freeform answers.
"""
styles = getSampleStyleSheet()
font_path = self._expand_static_url(self.custom_font) if self.custom_font else None
styles = get_style_sheet(font_url=self._make_url_absolute(font_path))
pdf_buffer = BytesIO()
course_name = self._get_course_name()

title = _('{course_name} Report'.format(course_name=course_name))
display_name = self.display_name or course_name

document = SimpleDocTemplate(pdf_buffer, pagesize=pagesizes.letter, title=title)
report_header_name = self.pdf_title or course_name
document = SimpleDocTemplate(pdf_buffer, pagesize=pagesizes.letter, title=report_header_name)
story = [
Paragraph(display_name, styles["Title"]),
Paragraph(report_header_name, styles["Title"]),
]

answer_sections = self.list_user_pb_answers_by_section()
Expand Down Expand Up @@ -316,17 +337,31 @@ def _get_course_name(self):
try:
course_key = self.scope_ids.usage_id.course_key
except AttributeError:
return "" # We are not in an edX runtime
return '' # We are not in an edX runtime

try:
course_root_key = course_key.make_usage_key('course', 'course')
return self.runtime.get_block(course_root_key).display_name
except Exception: # ItemNotFoundError most likely, but we can't import that exception in non-edX environments
# ItemNotFoundError most likely, but we can't import that exception in non-edX environments
except Exception: # pylint: disable=W0703
# We may be on old mongo:
try:
course_root_key = course_key.make_usage_key('course', course_key.run)
return self.runtime.get_block(course_root_key).display_name
except Exception:
return ""
except Exception: # pylint: disable=W0703
return ''

@staticmethod
def _make_url_absolute(url):
"""
This method will turn make relative urls absolute. It's helpfull in
some cases where some functions treat a varible as a path and url in
the same time
"""
lms_base = settings.ENV_TOKENS.get('LMS_BASE')
scheme = 'https' if settings.HTTPS == 'on' else 'http'
lms_base = '{}://{}'.format(scheme, lms_base)
return urljoin(lms_base, url)

def get_progress_metrics(self):
"""
Expand Down
175 changes: 175 additions & 0 deletions eoc_journal/pdf_generator.py
@@ -0,0 +1,175 @@
"""
Utils around Reportlab customizations
"""

from reportlab.lib.enums import TA_CENTER
from reportlab.lib.fonts import tt2ps
from reportlab.lib.styles import (
getSampleStyleSheet,
StyleSheet1,
ParagraphStyle,
)

import reportlab
import reportlab.rl_config
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont


def get_style_sheet(font_url=None):
"""Returns a custom stylesheet object"""
if not font_url:
return getSampleStyleSheet()

stylesheet = StyleSheet1()
font_name = 'customFont'
font = TTFont(font_name, font_url)

reportlab.rl_config.warnOnMissingFontGlyphs = 0
pdfmetrics.registerFont(font)

font_name_bold = tt2ps(font_name, 1, 0)
font_name_italic = tt2ps(font_name, 0, 1)
font_name_bold_italic = tt2ps(font_name, 1, 1)

stylesheet.add(ParagraphStyle(
name='Normal',
fontName=font_name,
fontSize=10,
leading=12
))

stylesheet.add(ParagraphStyle(
name='BodyText',
parent=stylesheet['Normal'],
spaceBefore=6
))
stylesheet.add(ParagraphStyle(
name='Italic',
parent=stylesheet['BodyText'],
fontName=font_name_italic
))

stylesheet.add(
ParagraphStyle(
name='Heading1',
parent=stylesheet['Normal'],
fontName=font_name_bold,
fontSize=18,
leading=22,
spaceAfter=6
),
alias='h1'
)

stylesheet.add(
ParagraphStyle(
name='Title',
parent=stylesheet['Normal'],
fontName=font_name_bold,
fontSize=22,
leading=22,
alignment=TA_CENTER,
spaceAfter=6
),
alias='title'
)

stylesheet.add(
ParagraphStyle(
name='Heading2',
parent=stylesheet['Normal'],
fontName=font_name_bold,
fontSize=14,
leading=18,
spaceBefore=12,
spaceAfter=6
),
alias='h2'
)

stylesheet.add(
ParagraphStyle(
name='Heading3',
parent=stylesheet['Normal'],
fontName=font_name_bold_italic,
fontSize=12,
leading=14,
spaceBefore=12,
spaceAfter=6
),
alias='h3'
)

stylesheet.add(ParagraphStyle(
name='Heading4',
parent=stylesheet['Normal'],
fontName=font_name_bold_italic,
fontSize=10,
leading=12,
spaceBefore=10,
spaceAfter=4
),
alias='h4'
)

stylesheet.add(ParagraphStyle(
name='Heading5',
parent=stylesheet['Normal'],
fontName=font_name_bold,
fontSize=9,
leading=10.8,
spaceBefore=8,
spaceAfter=4
),
alias='h5'
)

stylesheet.add(
ParagraphStyle(
name='Heading6',
parent=stylesheet['Normal'],
fontName=font_name_bold,
fontSize=7,
leading=8.4,
spaceBefore=6,
spaceAfter=2
),
alias='h6'
)

stylesheet.add(
ParagraphStyle(
name='Bullet',
parent=stylesheet['Normal'],
firstLineIndent=0,
spaceBefore=3
),
alias='bu'
)

stylesheet.add(
ParagraphStyle(
name='Definition',
parent=stylesheet['Normal'],
firstLineIndent=0,
leftIndent=36,
bulletIndent=0,
spaceBefore=6,
bulletFontName=font_name_bold_italic
),
alias='df'
)

stylesheet.add(
ParagraphStyle(
name='Code',
parent=stylesheet['Normal'],
fontName='Courier',
fontSize=8,
leading=8.8,
firstLineIndent=0,
leftIndent=36
))

return stylesheet

0 comments on commit 7245697

Please sign in to comment.