Skip to content

Commit

Permalink
Support alternative PDF Fonts (especially Unifont)
Browse files Browse the repository at this point in the history
- based on work by Anurag
  • Loading branch information
flavour committed Feb 13, 2015
1 parent de4d42e commit 13b930b
Show file tree
Hide file tree
Showing 7 changed files with 158 additions and 18 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
/sessions/*
/uploads/*
/static/cache/*
/static/fonts/unifont.ttf
/static/scripts/tools/compiler.jar
/tests/unit/*.pdf
.DS_Store
Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
nursix-1.1.0-devel-2009-g4254634 (2015-02-13 09:02:17)
de4d42e (2015-02-13 12:19:29)
6 changes: 4 additions & 2 deletions models/zzz_1st_run.py
Original file line number Diff line number Diff line change
Expand Up @@ -205,11 +205,13 @@
# Create the bulk Importer object
bi = s3base.S3BulkImporter()

s3.import_role = bi.import_role
s3.import_user = bi.import_user
# Register handlers
s3.import_font = bi.import_font
s3.import_image = bi.import_image
s3.import_remote_csv = bi.import_remote_csv
s3.import_role = bi.import_role
s3.import_script = bi.import_script
s3.import_user = bi.import_user

# Relax strict email-matching rule for import updates of person records
email_required = settings.get_pr_import_update_requires_email()
Expand Down
67 changes: 57 additions & 10 deletions modules/s3/s3codecs/pdf.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@
from reportlab.lib.colors import Color
from reportlab.lib.pagesizes import A4, LETTER, landscape, portrait
from reportlab.platypus.flowables import Flowable
from reportlab.pdfbase.ttfonts import TTFont
reportLabImported = True
except ImportError:
reportLabImported = False
Expand All @@ -90,6 +91,39 @@
PDF_WIDTH = 0
PDF_HEIGHT = 1

def set_fonts(self):
"""
DRY Helper function for all classes which use PDF to set the appropriate Fonts
"""

font_set = current.deployment_settings.get_pdf_export_font()
if font_set:
try:
font_name = font_set[0]
font_name_bold = font_set[1]
folder = current.request.folder
# Requires the font-files at /static/fonts/<font_name>.ttf
pdfmetrics.registerFont(TTFont(font_name, os.path.join(folder,
"static",
"fonts",
"%s.ttf" % font_name)))
pdfmetrics.registerFont(TTFont(font_name_bold, os.path.join(folder,
"static",
"fonts",
"%s.ttf" % font_name_bold)))
except:
current.log.error("%s Font not found: Please install it to see the correct fonts in PDF exports" % font_set[0])
# Use the default "Helvetica" and "Helvetica-Bold"
self.font_name = "Helvetica"
self.font_name_bold = "Helvetica-Bold"
else:
self.font_name = font_name
self.font_name_bold = font_name_bold
else:
# Use the default "Helvetica" and "Helvetica-Bold"
self.font_name = "Helvetica"
self.font_name_bold = "Helvetica-Bold"

# =============================================================================
class S3RL_PDF(S3Codec):
"""
Expand All @@ -108,6 +142,9 @@ def __init__(self):
RL_ERROR = "Python needs the ReportLab module installed for PDF export"
)

# Fonts
set_fonts(self)

# -------------------------------------------------------------------------
def encode(self, resource, **attr):
"""
Expand Down Expand Up @@ -240,7 +277,7 @@ def encode(self, resource, **attr):

styleSheet = getSampleStyleSheet()
style = styleSheet["Normal"]
style.fontName = "Helvetica"
style.fontName = self.font_name
style.fontSize = 9
if not body_flowable:
body_flowable = [Paragraph("", style)]
Expand Down Expand Up @@ -628,6 +665,9 @@ def __init__(self,
else:
self.paper_size = A4

# Fonts
set_fonts(self)

self.pdf = document
# @todo: Change the code to use raw_data directly rather than this
# conversion to an ordered list of values
Expand Down Expand Up @@ -1069,11 +1109,13 @@ def tableStyle(self, startRow, rowCnt, endCol, colour_required=False):
(should work but need to test with a split table)
"""

style = [("FONTNAME", (0, 0), (-1, -1), "Helvetica"),
font_name_bold = self.font_name_bold

style = [("FONTNAME", (0, 0), (-1, -1), self.font_name),
("FONTSIZE", (0, 0), (-1, -1), self.fontsize),
("VALIGN", (0, 0), (-1, -1), "TOP"),
("LINEBELOW", (0, 0), (endCol, 0), 1, Color(0, 0, 0)),
("FONTNAME", (0, 0), (endCol, 0), "Helvetica-Bold"),
("FONTNAME", (0, 0), (endCol, 0), font_name_bold),
]
sappend = style.append
if colour_required:
Expand All @@ -1091,7 +1133,7 @@ def tableStyle(self, startRow, rowCnt, endCol, colour_required=False):
if colour_required:
sappend(("BACKGROUND", (0, i), (endCol, i),
self.headerColour))
sappend(("FONTNAME", (0, i), (endCol, i), "Helvetica-Bold"))
sappend(("FONTNAME", (0, i), (endCol, i), font_name_bold))
sappend(("SPAN", (0, i), (endCol, i)))
sappend(("LEFTPADDING", (0, i), (endCol, i), 6 * level))
elif i > 0:
Expand All @@ -1118,18 +1160,21 @@ def __init__(self,
and converts it to pdf
"""

# Fonts
set_fonts(self)

self.exclude_class_list = exclude_class_list
self.pageWidth = pageWidth
self.fontsize = 10
styleSheet = getSampleStyleSheet()
self.plainstyle = styleSheet["Normal"]
self.plainstyle.fontName = "Helvetica"
self.plainstyle.fontName = self.font_name
self.plainstyle.fontSize = 9
self.boldstyle = deepcopy(styleSheet["Normal"])
self.boldstyle.fontName = "Helvetica-Bold"
self.boldstyle.fontName = self.font_name_bold
self.boldstyle.fontSize = 10
self.titlestyle = deepcopy(styleSheet["Normal"])
self.titlestyle.fontName = "Helvetica-Bold"
self.titlestyle.fontName = self.font_name_bold
self.titlestyle.fontSize = 16
self.normalstyle = self.plainstyle
# To add more pdf styles define the style above (just like the titlestyle)
Expand Down Expand Up @@ -1254,7 +1299,8 @@ def parse_img(html, uploadfolder=None):
src = os.path.join(current.request.folder, src)
else:
src = src.rsplit(sep, 1)
src = os.path.join(current.request.folder, "uploads%s"%sep, src[1])
src = os.path.join(current.request.folder,
"uploads%s" % sep, src[1])
if os.path.exists(src):
I = Image(src)

Expand Down Expand Up @@ -1308,7 +1354,7 @@ def parse_table(self, html):

style = [("FONTSIZE", (0, 0), (-1, -1), self.fontsize),
("VALIGN", (0, 0), (-1, -1), "TOP"),
("FONTNAME", (0, 0), (-1, -1), "Helvetica"),
("FONTNAME", (0, 0), (-1, -1), self.font_name),
("GRID", (0, 0), (-1, -1), 0.5, colors.grey),
]
content = []
Expand Down Expand Up @@ -1350,6 +1396,7 @@ def parse_tr (self, html, style, rowCnt):
colCnt = 0
exclude_tag = self.exclude_tag
select_tag = self.select_tag
font_name_bold = self.font_name_bold
for component in html.components:
if isinstance(component, (TH, TD)):
if exclude_tag(component):
Expand All @@ -1364,7 +1411,7 @@ def parse_tr (self, html, style, rowCnt):
rappend(result)
if isinstance(component, TH):
sappend(("BACKGROUND", (colCnt, rowCnt), (colCnt, rowCnt), colors.lightgrey))
sappend(("FONTNAME", (colCnt, rowCnt), (colCnt, rowCnt), "Helvetica-Bold"))
sappend(("FONTNAME", (colCnt, rowCnt), (colCnt, rowCnt), font_name_bold))
if colspan > 1:
for i in xrange(1, colspan):
rappend("")
Expand Down
74 changes: 69 additions & 5 deletions modules/s3/s3import.py
Original file line number Diff line number Diff line change
Expand Up @@ -3863,7 +3863,9 @@ def execute_special_task(self, task):

# -------------------------------------------------------------------------
def import_role(self, filename):
""" Import Roles from CSV """
"""
Import Roles from CSV
"""

# Check if the source file is accessible
try:
Expand Down Expand Up @@ -3954,7 +3956,9 @@ def parseACL(_acl):

# -------------------------------------------------------------------------
def import_user(self, filename):
""" Import Users from CSV """
"""
Import Users from CSV
"""

current.response.s3.import_prep = current.auth.s3_import_prep
user_task = [1,
Expand Down Expand Up @@ -4072,6 +4076,68 @@ def import_image(self,
for (key, error) in form.errors.items():
current.log.error("error importing logo %s: %s %s" % (image, key, error))

# -------------------------------------------------------------------------
def import_font(self, url):
"""
Install a Font
"""

if url == "unifont":
UNIFONT = True
url = "http://unifoundry.com/pub/unifont-7.0.06/font-builds/unifont-7.0.06.ttf"
# Rename to make version upgrades be transparent
filename = "unifont.ttf"
extension = "ttf"
else:
UNIFONT = False

filename = url.split("/")[-1]
filename, extension = filename.rsplit(".", 1)

if extension not in ("ttf", "gz", "zip"):
current.log.warning("Unsupported font extension: %s" % extension)
return

filename = "%s.ttf" % filename

fontPath = os.path.join(current.request.folder, "static", "fonts")
if os.path.exists(os.path.join(fontPath, filename)):
current.log.warning("Using cached copy of %s" % filename)
return

# Download as we have no cached copy

# Copy the current working directory to revert back to later
cwd = os.getcwd()

# Set the current working directory
os.chdir(fontPath)
try:
_file = fetch(url)
except urllib2.URLError, exception:
current.log.error(exception)
# Revert back to the working directory as before.
os.chdir(cwd)
return

if extension == "gz":
import tarfile
tf = tarfile.open(fileobj=StringIO(_file))
tf.extractall()

elif extension == "zip":
import zipfile
zf = zipfile.ZipFile(StringIO(_file))
zf.extractall()

else:
f = open(filename, "wb")
f.write(_file)
f.close()

# Revert back to the working directory as before.
os.chdir(cwd)

# -------------------------------------------------------------------------
def import_remote_csv(self, url, prefix, resource, stylesheet):
""" Import CSV files from remote servers """
Expand Down Expand Up @@ -4114,13 +4180,11 @@ def import_remote_csv(self, url, prefix, resource, stylesheet):
os.chdir(cwd)
return

fp = StringIO(_file)

if extension == "zip":
# Need to unzip
import zipfile
try:
myfile = zipfile.ZipFile(fp)
myfile = zipfile.ZipFile(StringIO(_file))
except zipfile.BadZipfile, exception:
# e.g. trying to download through a captive portal
current.log.error(exception)
Expand Down
24 changes: 24 additions & 0 deletions modules/s3cfg.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,25 @@ class S3Config(Storage):
"zh-tw": "%Y/%m/%d",
}

# PDF fonts for each language
# fontset format -> [normal-version, bold-version]
# defaults to ["Helvetica", "Helvetica-Bold"] if not-specified here
# Requires installation of appropriate font - e.g. using import_font in tasks.cfg
# Unifont can be downloaded from http://unifoundry.com/pub/unifont-7.0.06/font-builds/unifont-7.0.06.ttf
fonts = {"ar": ["unifont", "unifont"],
"km": ["unifont", "unifont"],
"ko": ["unifont", "unifont"],
"mn": ["unifont", "unifont"],
"ne": ["unifont", "unifont"],
"prs": ["unifont", "unifont"],
"ps": ["unifont", "unifont"],
#"th": ["unifont", "unifont"],
"tr": ["unifont", "unifont"],
"vi": ["unifont", "unifont"],
"zh-cn": ["unifont", "unifont"],
"zh-tw": ["unifont", "unifont"],
}

def __init__(self):
self.asset = Storage()
self.auth = Storage()
Expand Down Expand Up @@ -1392,9 +1411,14 @@ def get_L10n_pootle_password(self):
# PDF settings
def get_paper_size(self):
return self.base.get("paper_size", "A4")

def get_pdf_logo(self):
return self.ui.get("pdf_logo", None)

def get_pdf_export_font(self):
language = current.session.s3.language
return self.fonts.get(language, None)

# Optical Character Recognition (OCR)
def get_pdf_excluded_fields(self, resourcename):
excluded_fields_dict = {
Expand Down
2 changes: 2 additions & 0 deletions modules/templates/IFRC/tasks.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
##########################################################################
# Roles
*,import_role,auth_roles.csv
# Fonts
*,import_font,,unifont
# -----------------------------------------------------------------------------
# Org
org,sector,org_sector.csv,sector.xsl
Expand Down

0 comments on commit 13b930b

Please sign in to comment.