Updated fpdf to the last version (py3 compatible), fix contrib/appconfig

This commit is contained in:
ilvalle
2016-06-04 09:23:31 +02:00
parent 2f7d76769c
commit dcd24cf88c
12 changed files with 1087 additions and 937 deletions
+3 -4
View File
@@ -27,11 +27,10 @@ Once the value has been fetched (and casted) it won't change until the process
is restarted (or reload=True is passed). is restarted (or reload=True is passed).
""" """
import thread
import os import os
from ConfigParser import SafeConfigParser
from gluon import current
import json import json
from gluon._compat import thread, configparser
from gluon.globals import current
locker = thread.allocate_lock() locker = thread.allocate_lock()
@@ -121,7 +120,7 @@ class AppConfigLoader(object):
self.read_config() self.read_config()
def read_config_ini(self): def read_config_ini(self):
config = SafeConfigParser() config = configparser.SafeConfigParser()
config.read(self.file) config.read(self.file)
settings = {} settings = {}
for section in config.sections(): for section in config.sections():
+4 -4
View File
@@ -4,13 +4,13 @@
"FPDF for python" "FPDF for python"
__license__ = "LGPL 3.0" __license__ = "LGPL 3.0"
__version__ = "1.7" __version__ = "1.7.2"
from fpdf import * from .fpdf import FPDF, FPDF_FONT_DIR, FPDF_VERSION, SYSTEM_TTFONTS, set_global, FPDF_CACHE_MODE, FPDF_CACHE_DIR
try: try:
from html import HTMLMixin from .html import HTMLMixin
except ImportError: except ImportError:
import warnings import warnings
warnings.warn("web2py gluon package not installed, required for html2pdf") warnings.warn("web2py gluon package not installed, required for html2pdf")
from template import Template from .template import Template
+1 -1
View File
@@ -7,7 +7,7 @@ fpdf_charwidths = {}
fpdf_charwidths['courier']={} fpdf_charwidths['courier']={}
for i in xrange(0,256): for i in range(0,256):
fpdf_charwidths['courier'][chr(i)]=600 fpdf_charwidths['courier'][chr(i)]=600
fpdf_charwidths['courierB']=fpdf_charwidths['courier'] fpdf_charwidths['courierB']=fpdf_charwidths['courier']
fpdf_charwidths['courierI']=fpdf_charwidths['courier'] fpdf_charwidths['courierI']=fpdf_charwidths['courier']
+536 -397
View File
File diff suppressed because it is too large Load Diff
+21 -17
View File
@@ -8,8 +8,8 @@ __license__ = "LGPL 3.0"
# Inspired by tuto5.py and several examples from fpdf.org, html2fpdf, etc. # Inspired by tuto5.py and several examples from fpdf.org, html2fpdf, etc.
from fpdf import FPDF from .fpdf import FPDF
from HTMLParser import HTMLParser from .py3k import PY3K, basestring, unicode, HTMLParser
DEBUG = False DEBUG = False
@@ -78,8 +78,8 @@ class HTML2FPDF(HTMLParser):
l = self.table_col_width[i:i+colspan] l = self.table_col_width[i:i+colspan]
else: else:
l = [self.td.get('width','240')] l = [self.td.get('width','240')]
w = sum([self.width2mm(lenght) for lenght in l]) w = sum([self.width2mm(length) for length in l])
h = int(self.td.get('height', 0)) / 4 or self.h*1.30 h = int(self.td.get('height', 0)) // 4 or self.h*1.30
self.table_h = h self.table_h = h
border = int(self.table.get('border', 0)) border = int(self.table.get('border', 0))
if not self.th: if not self.th:
@@ -99,30 +99,30 @@ class HTML2FPDF(HTMLParser):
height = h + (self.tfooter and self.tfooter[0][0][1] or 0) height = h + (self.tfooter and self.tfooter[0][0][1] or 0)
if self.pdf.y+height>self.pdf.page_break_trigger and not self.th: if self.pdf.y+height>self.pdf.page_break_trigger and not self.th:
self.output_table_footer() self.output_table_footer()
self.pdf.add_page() self.pdf.add_page(same = True)
self.theader_out = self.tfooter_out = False self.theader_out = self.tfooter_out = False
if self.tfoot is None and self.thead is None: if self.tfoot is None and self.thead is None:
if not self.theader_out: if not self.theader_out:
self.output_table_header() self.output_table_header()
self.box_shadow(w, h, bgcolor) self.box_shadow(w, h, bgcolor)
if DEBUG: print "td cell", self.pdf.x, w, txt, "*" if DEBUG: print("td cell", self.pdf.x, w, txt, "*")
self.pdf.cell(w,h,txt,border,0,align) self.pdf.cell(w,h,txt,border,0,align)
elif self.table is not None: elif self.table is not None:
# ignore anything else than td inside a table # ignore anything else than td inside a table
pass pass
elif self.align: elif self.align:
if DEBUG: print "cell", txt, "*" if DEBUG: print("cell", txt, "*")
self.pdf.cell(0,self.h,txt,0,1,self.align[0].upper(), self.href) self.pdf.cell(0,self.h,txt,0,1,self.align[0].upper(), self.href)
else: else:
txt = txt.replace("\n"," ") txt = txt.replace("\n"," ")
if self.href: if self.href:
self.put_link(self.href,txt) self.put_link(self.href,txt)
else: else:
if DEBUG: print "write", txt, "*" if DEBUG: print("write", txt, "*")
self.pdf.write(self.h,txt) self.pdf.write(self.h,txt)
def box_shadow(self, w, h, bgcolor): def box_shadow(self, w, h, bgcolor):
if DEBUG: print "box_shadow", w, h, bgcolor if DEBUG: print("box_shadow", w, h, bgcolor)
if bgcolor: if bgcolor:
fill_color = self.pdf.fill_color fill_color = self.pdf.fill_color
self.pdf.set_fill_color(*bgcolor) self.pdf.set_fill_color(*bgcolor)
@@ -168,7 +168,7 @@ class HTML2FPDF(HTMLParser):
def handle_starttag(self, tag, attrs): def handle_starttag(self, tag, attrs):
attrs = dict(attrs) attrs = dict(attrs)
if DEBUG: print "STARTTAG", tag, attrs if DEBUG: print("STARTTAG", tag, attrs)
if tag=='b' or tag=='i' or tag=='u': if tag=='b' or tag=='i' or tag=='u':
self.set_style(tag,1) self.set_style(tag,1)
if tag=='a': if tag=='a':
@@ -216,13 +216,16 @@ class HTML2FPDF(HTMLParser):
# save previous font state: # save previous font state:
self.font_stack.append((self.font_face, self.font_size, self.color)) self.font_stack.append((self.font_face, self.font_size, self.color))
if 'color' in attrs: if 'color' in attrs:
self.color = hex2dec(attrs['color']) color = hex2dec(attrs['color'])
self.set_text_color(*color) self.set_text_color(*color)
self.color = color self.color = color
if 'face' in attrs: if 'face' in attrs:
face = attrs.get('face').lower() face = attrs.get('face').lower()
self.pdf.set_font(face) try:
self.font_face = face self.pdf.set_font(face)
self.font_face = face
except RuntimeError:
pass # font not found, ignore
if 'size' in attrs: if 'size' in attrs:
size = int(attrs.get('size')) size = int(attrs.get('size'))
self.pdf.set_font(self.font_face, size=int(size)) self.pdf.set_font(self.font_face, size=int(size))
@@ -277,7 +280,7 @@ class HTML2FPDF(HTMLParser):
def handle_endtag(self, tag): def handle_endtag(self, tag):
#Closing tag #Closing tag
if DEBUG: print "ENDTAG", tag if DEBUG: print("ENDTAG", tag)
if tag=='h1' or tag=='h2' or tag=='h3' or tag=='h4': if tag=='h1' or tag=='h2' or tag=='h3' or tag=='h4':
self.pdf.ln(6) self.pdf.ln(6)
self.set_font() self.set_font()
@@ -326,7 +329,7 @@ class HTML2FPDF(HTMLParser):
self.tr = None self.tr = None
if tag=='td' or tag=='th': if tag=='td' or tag=='th':
if self.th: if self.th:
if DEBUG: print "revert style" if DEBUG: print("revert style")
self.set_style('B', False) # revert style self.set_style('B', False) # revert style
self.table_col_index += int(self.td.get('colspan','1')) self.table_col_index += int(self.td.get('colspan','1'))
self.td = None self.td = None
@@ -348,7 +351,7 @@ class HTML2FPDF(HTMLParser):
if size: if size:
self.font_size = size self.font_size = size
self.h = size / 72.0*25.4 self.h = size / 72.0*25.4
if DEBUG: print "H", self.h if DEBUG: print("H", self.h)
self.pdf.set_font(self.font_face or 'times','',12) self.pdf.set_font(self.font_face or 'times','',12)
self.pdf.set_font_size(self.font_size or 12) self.pdf.set_font_size(self.font_size or 12)
self.set_style('u', False) self.set_style('u', False)
@@ -365,7 +368,7 @@ class HTML2FPDF(HTMLParser):
for s in ('b','i','u'): for s in ('b','i','u'):
if self.style.get(s): if self.style.get(s):
style+=s style+=s
if DEBUG: print "SET_FONT_STYLE", style if DEBUG: print("SET_FONT_STYLE", style)
self.pdf.set_font('',style) self.pdf.set_font('',style)
def set_text_color(self, r=None, g=0, b=0): def set_text_color(self, r=None, g=0, b=0):
@@ -394,5 +397,6 @@ class HTMLMixin(object):
def write_html(self, text, image_map=None): def write_html(self, text, image_map=None):
"Parse HTML and convert it to PDF" "Parse HTML and convert it to PDF"
h2p = HTML2FPDF(self, image_map) h2p = HTML2FPDF(self, image_map)
text = h2p.unescape(text) # To deal with HTML entities
h2p.feed(text) h2p.feed(text)
+8 -3
View File
@@ -1,6 +1,8 @@
#!/usr/bin/env python #!/usr/bin/env python
# -*- coding: latin-1 -*- # -*- coding: latin-1 -*-
from .py3k import PY3K, basestring, unicode
# fpdf php helpers: # fpdf php helpers:
def substr(s, start, length=-1): def substr(s, start, length=-1):
@@ -14,16 +16,19 @@ def print_r(array):
if not isinstance(array, dict): if not isinstance(array, dict):
array = dict([(k, k) for k in array]) array = dict([(k, k) for k in array])
for k, v in array.items(): for k, v in array.items():
print "[%s] => %s" % (k, v), print("[%s] => %s " % (k, v))
def UTF8ToUTF16BE(instr, setbom=True): def UTF8ToUTF16BE(instr, setbom=True):
"Converts UTF-8 strings to UTF16-BE." "Converts UTF-8 strings to UTF16-BE."
outstr = "" outstr = "".encode()
if (setbom): if (setbom):
outstr += "\xFE\xFF"; outstr += "\xFE\xFF".encode("latin1")
if not isinstance(instr, unicode): if not isinstance(instr, unicode):
instr = instr.decode('UTF-8') instr = instr.decode('UTF-8')
outstr += instr.encode('UTF-16BE') outstr += instr.encode('UTF-16BE')
# convert bytes back to fake unicode string until PEP461-like is implemented
if PY3K:
outstr = outstr.decode("latin1")
return outstr return outstr
def UTF8StringToArray(instr): def UTF8StringToArray(instr):
+83
View File
@@ -0,0 +1,83 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"Special module to handle differences between Python 2 and 3 versions"
import sys
PY3K = sys.version_info >= (3, 0)
try:
import cPickle as pickle
except ImportError:
import pickle
try:
from urllib import urlopen
except ImportError:
from urllib.request import urlopen
try:
from io import BytesIO
except ImportError:
try:
from cStringIO import StringIO as BytesIO
except ImportError:
from StringIO import StringIO as BytesIO
try:
from hashlib import md5
except ImportError:
try:
from md5 import md5
except ImportError:
md5 = None
def hashpath(fn):
h = md5()
if PY3K:
h.update(fn.encode("UTF-8"))
else:
h.update(fn)
return h.hexdigest()
# Check if PIL is available (tries importing both pypi version and corrected or manually installed versions).
# Necessary for JPEG and GIF support.
# TODO: Pillow support
try:
from PIL import Image
except ImportError:
try:
import Image
except ImportError:
Image = None
try:
from HTMLParser import HTMLParser
except ImportError:
from html.parser import HTMLParser
if PY3K:
basestring = str
unicode = str
ord = lambda x: x
else:
basestring = basestring
unicode = unicode
ord = ord
# shortcut to bytes conversion (b prefix)
def b(s):
if isinstance(s, basestring):
return s.encode("latin1")
elif isinstance(s, int):
if PY3K:
return bytes([s]) # http://bugs.python.org/issue4588
else:
return chr(s)
def exception():
"Return the current the exception instance currently being handled"
# this is needed to support Python 2.5 that lacks "as" syntax
return sys.exc_info()[1]
+65 -137
View File
@@ -2,12 +2,15 @@
"PDF Template Helper for FPDF.py" "PDF Template Helper for FPDF.py"
from __future__ import with_statement
__author__ = "Mariano Reingart <reingart@gmail.com>" __author__ = "Mariano Reingart <reingart@gmail.com>"
__copyright__ = "Copyright (C) 2010 Mariano Reingart" __copyright__ = "Copyright (C) 2010 Mariano Reingart"
__license__ = "LGPL 3.0" __license__ = "LGPL 3.0"
import sys,os,csv import sys,os,csv
from fpdf import FPDF from .fpdf import FPDF
from .py3k import PY3K, basestring, unicode
def rgb(col): def rgb(col):
return (col // 65536), (col // 256 % 256), (col% 256) return (col // 65536), (col // 256 % 256), (col% 256)
@@ -16,11 +19,9 @@ class Template:
def __init__(self, infile=None, elements=None, format='A4', orientation='portrait', def __init__(self, infile=None, elements=None, format='A4', orientation='portrait',
title='', author='', subject='', creator='', keywords=''): title='', author='', subject='', creator='', keywords=''):
if elements: if elements:
self.elements = elements self.load_elements(elements)
self.keys = [v['name'].lower() for v in self.elements]
self.handlers = {'T': self.text, 'L': self.line, 'I': self.image, self.handlers = {'T': self.text, 'L': self.line, 'I': self.image,
'B': self.rect, 'BC': self.barcode, } 'B': self.rect, 'BC': self.barcode, 'W': self.write, }
self.pg_no = 0
self.texts = {} self.texts = {}
pdf = self.pdf = FPDF(format=format,orientation=orientation, unit="mm") pdf = self.pdf = FPDF(format=format,orientation=orientation, unit="mm")
pdf.set_title(title) pdf.set_title(title)
@@ -29,25 +30,37 @@ class Template:
pdf.set_subject(subject) pdf.set_subject(subject)
pdf.set_keywords(keywords) pdf.set_keywords(keywords)
def load_elements(self, elements):
"Initialize the internal element structures"
self.pg_no = 0
self.elements = elements
self.keys = [v['name'].lower() for v in self.elements]
def parse_csv(self, infile, delimiter=",", decimal_sep="."): def parse_csv(self, infile, delimiter=",", decimal_sep="."):
"Parse template format csv file and create elements dict" "Parse template format csv file and create elements dict"
keys = ('name','type','x1','y1','x2','y2','font','size', keys = ('name','type','x1','y1','x2','y2','font','size',
'bold','italic','underline','foreground','background', 'bold','italic','underline','foreground','background',
'align','text','priority', 'multiline') 'align','text','priority', 'multiline')
self.elements = [] self.elements = []
for row in csv.reader(open(infile, 'rb'), delimiter=delimiter): self.pg_no = 0
kargs = {} if not PY3K:
for i,v in enumerate(row): f = open(infile, 'rb')
if not v.startswith("'") and decimal_sep!=".": else:
v = v.replace(decimal_sep,".") f = open(infile)
else: with f:
v = v for row in csv.reader(f, delimiter=delimiter):
if v=='': kargs = {}
v = None for i,v in enumerate(row):
else: if not v.startswith("'") and decimal_sep!=".":
v = eval(v.strip()) v = v.replace(decimal_sep,".")
kargs[keys[i]] = v else:
self.elements.append(kargs) v = v
if v=='':
v = None
else:
v = eval(v.strip())
kargs[keys[i]] = v
self.elements.append(kargs)
self.keys = [v['name'].lower() for v in self.elements] self.keys = [v['name'].lower() for v in self.elements]
def add_page(self): def add_page(self):
@@ -55,8 +68,8 @@ class Template:
self.texts[self.pg_no] = {} self.texts[self.pg_no] = {}
def __setitem__(self, name, value): def __setitem__(self, name, value):
if self.has_key(name): if name.lower() in self.keys:
if isinstance(value,unicode): if not PY3K and isinstance(value, unicode):
value = value.encode("latin1","ignore") value = value.encode("latin1","ignore")
elif value is None: elif value is None:
value = "" value = ""
@@ -71,7 +84,7 @@ class Template:
return name.lower() in self.keys return name.lower() in self.keys
def __getitem__(self, name): def __getitem__(self, name):
if self.has_key(name): if name in self.keys:
key = name.lower() key = name.lower()
if key in self.texts: if key in self.texts:
# text for this page: # text for this page:
@@ -94,7 +107,7 @@ class Template:
if element['underline']: style += "U" if element['underline']: style += "U"
pdf.set_font(element['font'],style,element['size']) pdf.set_font(element['font'],style,element['size'])
align = {'L':'L','R':'R','I':'L','D':'R','C':'C','':''}.get(element['align']) # D/I in spanish align = {'L':'L','R':'R','I':'L','D':'R','C':'C','':''}.get(element['align']) # D/I in spanish
if isinstance(text, unicode): if isinstance(text, unicode) and not PY3K:
text = text.encode("latin1","ignore") text = text.encode("latin1","ignore")
else: else:
text = str(text) text = str(text)
@@ -110,8 +123,8 @@ class Template:
pdf.set_auto_page_break(False,margin=0) pdf.set_auto_page_break(False,margin=0)
for element in sorted(self.elements,key=lambda x: x['priority']): for element in sorted(self.elements,key=lambda x: x['priority']):
# make a copy of the element: #print "dib",element['type'], element['name'], element['x1'], element['y1'], element['x2'], element['y2']
element = dict(element) element = element.copy()
element['text'] = self.texts[pg].get(element['name'].lower(), element['text']) element['text'] = self.texts[pg].get(element['name'].lower(), element['text'])
if 'rotate' in element: if 'rotate' in element:
pdf.rotate(element['rotate'], element['x1'], element['y1']) pdf.rotate(element['rotate'], element['x1'], element['y1'])
@@ -119,7 +132,8 @@ class Template:
if 'rotate' in element: if 'rotate' in element:
pdf.rotate(0) pdf.rotate(0)
return pdf.output(outfile, dest) if dest:
return pdf.output(outfile, dest)
def text(self, pdf, x1=0, y1=0, x2=0, y2=0, text='', font="arial", size=10, def text(self, pdf, x1=0, y1=0, x2=0, y2=0, text='', font="arial", size=10,
bold=False, italic=False, underline=False, align="", bold=False, italic=False, underline=False, align="",
@@ -157,7 +171,7 @@ class Template:
# multiline==False: trim to fit exactly the space defined # multiline==False: trim to fit exactly the space defined
text = pdf.multi_cell(w=x2-x1, h=y2-y1, text = pdf.multi_cell(w=x2-x1, h=y2-y1,
txt=text, align=align, split_only=True)[0] txt=text, align=align, split_only=True)[0]
print "trimming: *%s*" % text print("trimming: *%s*" % text)
pdf.cell(w=x2-x1,h=y2-y1,txt=text,border=0,ln=0,align=align) pdf.cell(w=x2-x1,h=y2-y1,txt=text,border=0,ln=0,align=align)
#pdf.Text(x=x1,y=y1,txt=text) #pdf.Text(x=x1,y=y1,txt=text)
@@ -179,7 +193,8 @@ class Template:
pdf.rect(x1, y1, x2-x1, y2-y1) pdf.rect(x1, y1, x2-x1, y2-y1)
def image(self, pdf, x1=0, y1=0, x2=0, y2=0, text='', *args,**kwargs): def image(self, pdf, x1=0, y1=0, x2=0, y2=0, text='', *args,**kwargs):
pdf.image(text,x1,y1,w=x2-x1,h=y2-y1,type='',link='') if text:
pdf.image(text,x1,y1,w=x2-x1,h=y2-y1,type='',link='')
def barcode(self, pdf, x1=0, y1=0, x2=0, y2=0, text='', font="arial", size=1, def barcode(self, pdf, x1=0, y1=0, x2=0, y2=0, text='', font="arial", size=1,
foreground=0, *args, **kwargs): foreground=0, *args, **kwargs):
@@ -189,113 +204,26 @@ class Template:
if font == 'interleaved 2of5 nt': if font == 'interleaved 2of5 nt':
pdf.interleaved2of5(text,x1,y1,w=size,h=y2-y1) pdf.interleaved2of5(text,x1,y1,w=size,h=y2-y1)
# Added by Derek Schwalenberg Schwalenberg1013@gmail.com to allow (url) links in templates (using write method) 2014-02-22
if __name__ == "__main__": def write(self, pdf, x1=0, y1=0, x2=0, y2=0, text='', font="arial", size=1,
bold=False, italic=False, underline=False, align="", link='http://example.com',
# generate sample invoice (according Argentina's regulations) foreground=0, *args, **kwargs):
if pdf.text_color!=rgb(foreground):
import random pdf.set_text_color(*rgb(foreground))
from decimal import Decimal font = font.strip().lower()
if font == 'arial black':
f = Template(format="A4", font = 'arial'
title="Sample Invoice", author="Sample Company", style = ""
subject="Sample Customer", keywords="Electronic TAX Invoice") for tag in 'B', 'I', 'U':
f.parse_csv(infile="invoice.csv", delimiter=";", decimal_sep=",") if (text.startswith("<%s>" % tag) and text.endswith("</%s>" %tag)):
text = text[3:-4]
detail = "Lorem ipsum dolor sit amet, consectetur. " * 30 style += tag
items = [] if bold: style += "B"
for i in range(1, 30): if italic: style += "I"
ds = "Sample product %s" % i if underline: style += "U"
qty = random.randint(1,10) align = {'L':'L','R':'R','I':'L','D':'R','C':'C','':''}.get(align) # D/I in spanish
price = round(random.random()*100,3) pdf.set_font(font,style,size)
code = "%s%s%02d" % (chr(random.randint(65,90)), chr(random.randint(65,90)),i) ##m_k = 72 / 2.54
items.append(dict(code=code, unit='u', ##h = (size/m_k)
qty=qty, price=price, pdf.set_xy(x1,y1)
amount=qty*price, pdf.write(5,text,link)
ds="%s: %s" % (i,ds)))
# divide and count lines
lines = 0
li_items = []
for it in items:
qty = it['qty']
code = it['code']
unit = it['unit']
for ds in f.split_multicell(it['ds'], 'item_description01'):
# add item description line (without price nor amount)
li_items.append(dict(code=code, ds=ds, qty=qty, unit=unit, price=None, amount=None))
# clean qty and code (show only at first)
unit = qty = code = None
# set last item line price and amount
li_items[-1].update(amount = it['amount'],
price = it['price'])
obs="\n<U>Detail:</U>\n\n" + detail
for ds in f.split_multicell(obs, 'item_description01'):
li_items.append(dict(code=code, ds=ds, qty=qty, unit=unit, price=None, amount=None))
# calculate pages:
lines = len(li_items)
max_lines_per_page = 24
pages = lines / (max_lines_per_page - 1)
if lines % (max_lines_per_page - 1): pages = pages + 1
# completo campos y hojas
for page in range(1, pages+1):
f.add_page()
f['page'] = 'Page %s of %s' % (page, pages)
if pages>1 and page<pages:
s = 'Continues on page %s' % (page+1)
else:
s = ''
f['item_description%02d' % (max_lines_per_page+1)] = s
f["company_name"] = "Sample Company"
f["company_logo"] = "tutorial/logo.png"
f["company_header1"] = "Some Address - somewhere -"
f["company_header2"] = "http://www.example.com"
f["company_footer1"] = "Tax Code ..."
f["company_footer2"] = "Tax/VAT ID ..."
f['number'] = '0001-00001234'
f['issue_date'] = '2010-09-10'
f['due_date'] = '2099-09-10'
f['customer_name'] = "Sample Client"
f['customer_address'] = "Siempreviva 1234"
# print line item...
li = 0
k = 0
total = Decimal("0.00")
for it in li_items:
k = k + 1
if k > page * (max_lines_per_page - 1):
break
if it['amount']:
total += Decimal("%.6f" % it['amount'])
if k > (page - 1) * (max_lines_per_page - 1):
li += 1
if it['qty'] is not None:
f['item_quantity%02d' % li] = it['qty']
if it['code'] is not None:
f['item_code%02d' % li] = it['code']
if it['unit'] is not None:
f['item_unit%02d' % li] = it['unit']
f['item_description%02d' % li] = it['ds']
if it['price'] is not None:
f['item_price%02d' % li] = "%0.3f" % it['price']
if it['amount'] is not None:
f['item_amount%02d' % li] = "%0.2f" % it['amount']
if pages == page:
f['net'] = "%0.2f" % (total/Decimal("1.21"))
f['vat'] = "%0.2f" % (total*(1-1/Decimal("1.21")))
f['total_label'] = 'Total:'
else:
f['total_label'] = 'SubTotal:'
f['total'] = "%0.2f" % total
f.render("./invoice.pdf")
if sys.platform.startswith("linux"):
os.system("evince ./invoice.pdf")
else:
os.system("./invoice.pdf")
+342 -350
View File
@@ -16,10 +16,13 @@
# #
#****************************************************************************** #******************************************************************************
from __future__ import with_statement
from struct import pack, unpack, unpack_from from struct import pack, unpack, unpack_from
import re import re
import warnings import warnings
from php import die, substr, str_repeat, str_pad, strlen, count from .php import die, substr, str_repeat, str_pad, strlen, count
from .py3k import b, ord
# Define the value used in the "head" table of a created TTF file # Define the value used in the "head" table of a created TTF file
@@ -55,7 +58,7 @@ def sub32(x, y):
def calcChecksum(data): def calcChecksum(data):
if (strlen(data) % 4): if (strlen(data) % 4):
data += str_repeat("\0", (4-(len(data) % 4))) data += str_repeat(b("\0"), (4-(len(data) % 4)))
hi=0x0000 hi=0x0000
lo=0x0000 lo=0x0000
for i in range(0, len(data), 4): for i in range(0, len(data), 4):
@@ -74,26 +77,25 @@ class TTFontFile:
def getMetrics(self, file): def getMetrics(self, file):
self.filename = file self.filename = file
self.fh = open(file,'rb') with open(file,'rb') as self.fh:
self._pos = 0 self._pos = 0
self.charWidths = [] self.charWidths = []
self.glyphPos = {} self.glyphPos = {}
self.charToGlyph = {} self.charToGlyph = {}
self.tables = {} self.tables = {}
self.otables = {} self.otables = {}
self.ascent = 0 self.ascent = 0
self.descent = 0 self.descent = 0
self.TTCFonts = {} self.TTCFonts = {}
self.version = version = self.read_ulong() self.version = version = self.read_ulong()
if (version==0x4F54544F): if (version==0x4F54544F):
die("Postscript outlines are not supported") die("Postscript outlines are not supported")
if (version==0x74746366): if (version==0x74746366):
die("ERROR - TrueType Fonts Collections not supported") die("ERROR - TrueType Fonts Collections not supported")
if (version not in (0x00010000,0x74727565)): if (version not in (0x00010000,0x74727565)):
die("Not a TrueType font: version=" + version) die("Not a TrueType font: version=" + str(version))
self.readTableDirectory() self.readTableDirectory()
self.extractInfo() self.extractInfo()
self.fh.close()
def readTableDirectory(self, ): def readTableDirectory(self, ):
self.numTables = self.read_ushort() self.numTables = self.read_ushort()
@@ -130,7 +132,7 @@ class TTFontFile:
def read_tag(self): def read_tag(self):
self._pos += 4 self._pos += 4
return self.fh.read(4) return self.fh.read(4).decode("latin1")
def read_short(self): def read_short(self):
self._pos += 2 self._pos += 2
@@ -204,7 +206,7 @@ class TTFontFile:
def add(self, tag, data): def add(self, tag, data):
if (tag == 'head') : if (tag == 'head') :
data = self.splice(data, 8, "\0\0\0\0") data = self.splice(data, 8, b("\0\0\0\0"))
self.otables[tag] = data self.otables[tag] = data
############################################/ ############################################/
@@ -226,7 +228,7 @@ class TTFontFile:
numRecords = self.read_ushort() numRecords = self.read_ushort()
string_data_offset = name_offset + self.read_ushort() string_data_offset = name_offset + self.read_ushort()
names = {1:'',2:'',3:'',4:'',6:''} names = {1:'',2:'',3:'',4:'',6:''}
K = names.keys() K = list(names.keys())
nameCount = len(names) nameCount = len(names)
for i in range(numRecords): for i in range(numRecords):
platformId = self.read_ushort() platformId = self.read_ushort()
@@ -242,7 +244,7 @@ class TTFontFile:
self.seek(string_data_offset + offset) self.seek(string_data_offset + offset)
if (length % 2 != 0): if (length % 2 != 0):
die("PostScript name is UTF-16BE string of odd length") die("PostScript name is UTF-16BE string of odd length")
length /= 2 length //= 2
N = '' N = ''
while (length > 0): while (length > 0):
char = self.read_ushort() char = self.read_ushort()
@@ -253,7 +255,7 @@ class TTFontFile:
elif (platformId == 1 and encodingId == 0 and languageId == 0): # Macintosh, Roman, English, PS Name elif (platformId == 1 and encodingId == 0 and languageId == 0): # Macintosh, Roman, English, PS Name
opos = self._pos opos = self._pos
N = self.get_chunk(string_data_offset + offset, length) N = self.get_chunk(string_data_offset + offset, length).decode("latin1")
self._pos = opos self._pos = opos
self.seek(opos) self.seek(opos)
@@ -455,346 +457,344 @@ class TTFontFile:
def makeSubset(self, file, subset): def makeSubset(self, file, subset):
self.filename = file self.filename = file
self.fh = open(file ,'rb') with open(file ,'rb') as self.fh:
self._pos = 0 self._pos = 0
self.charWidths = [] self.charWidths = []
self.glyphPos = {} self.glyphPos = {}
self.charToGlyph = {} self.charToGlyph = {}
self.tables = {} self.tables = {}
self.otables = {} self.otables = {}
self.ascent = 0 self.ascent = 0
self.descent = 0 self.descent = 0
self.skip(4) self.skip(4)
self.maxUni = 0 self.maxUni = 0
self.readTableDirectory() self.readTableDirectory()
#################/ #################/
# head - Font header table # head - Font header table
#################/ #################/
self.seek_table("head") self.seek_table("head")
self.skip(50) self.skip(50)
indexToLocFormat = self.read_ushort() indexToLocFormat = self.read_ushort()
glyphDataFormat = self.read_ushort() glyphDataFormat = self.read_ushort()
#################/ #################/
# hhea - Horizontal header table # hhea - Horizontal header table
#################/ #################/
self.seek_table("hhea") self.seek_table("hhea")
self.skip(32) self.skip(32)
metricDataFormat = self.read_ushort() metricDataFormat = self.read_ushort()
orignHmetrics = numberOfHMetrics = self.read_ushort() orignHmetrics = numberOfHMetrics = self.read_ushort()
#################/ #################/
# maxp - Maximum profile table # maxp - Maximum profile table
#################/ #################/
self.seek_table("maxp") self.seek_table("maxp")
self.skip(4) self.skip(4)
numGlyphs = self.read_ushort() numGlyphs = self.read_ushort()
#################/ #################/
# cmap - Character to glyph index mapping table # cmap - Character to glyph index mapping table
#################/ #################/
cmap_offset = self.seek_table("cmap") cmap_offset = self.seek_table("cmap")
self.skip(2) self.skip(2)
cmapTableCount = self.read_ushort() cmapTableCount = self.read_ushort()
unicode_cmap_offset = 0 unicode_cmap_offset = 0
unicode_cmap_offset12 = 0 unicode_cmap_offset12 = 0
for i in range(cmapTableCount): for i in range(cmapTableCount):
platformID = self.read_ushort() platformID = self.read_ushort()
encodingID = self.read_ushort() encodingID = self.read_ushort()
offset = self.read_ulong() offset = self.read_ulong()
save_pos = self._pos save_pos = self._pos
if platformID == 3 and encodingID == 10: # Microsoft, UCS-4 if platformID == 3 and encodingID == 10: # Microsoft, UCS-4
format = self.get_ushort(cmap_offset + offset) format = self.get_ushort(cmap_offset + offset)
if (format == 12): if (format == 12):
if not unicode_cmap_offset12: if not unicode_cmap_offset12:
unicode_cmap_offset12 = cmap_offset + offset unicode_cmap_offset12 = cmap_offset + offset
break break
if ((platformID == 3 and encodingID == 1) or platformID == 0): # Microsoft, Unicode if ((platformID == 3 and encodingID == 1) or platformID == 0): # Microsoft, Unicode
format = self.get_ushort(cmap_offset + offset) format = self.get_ushort(cmap_offset + offset)
if (format == 4): if (format == 4):
unicode_cmap_offset = cmap_offset + offset unicode_cmap_offset = cmap_offset + offset
break break
self.seek(save_pos ) self.seek(save_pos )
if not unicode_cmap_offset and not unicode_cmap_offset12: if not unicode_cmap_offset and not unicode_cmap_offset12:
die('Font (' + self.filename + ') does not have cmap for Unicode (platform 3, encoding 1, format 4, or platform 3, encoding 10, format 12, or platform 0, any encoding, format 4)') die('Font (' + self.filename + ') does not have cmap for Unicode (platform 3, encoding 1, format 4, or platform 3, encoding 10, format 12, or platform 0, any encoding, format 4)')
glyphToChar = {} glyphToChar = {}
charToGlyph = {} charToGlyph = {}
if unicode_cmap_offset12: if unicode_cmap_offset12:
self.getCMAP12(unicode_cmap_offset12, glyphToChar, charToGlyph) self.getCMAP12(unicode_cmap_offset12, glyphToChar, charToGlyph)
else: else:
self.getCMAP4(unicode_cmap_offset, glyphToChar, charToGlyph) self.getCMAP4(unicode_cmap_offset, glyphToChar, charToGlyph)
self.charToGlyph = charToGlyph self.charToGlyph = charToGlyph
#################/ #################/
# hmtx - Horizontal metrics table # hmtx - Horizontal metrics table
#################/ #################/
scale = 1 # not used scale = 1 # not used
self.getHMTX(numberOfHMetrics, numGlyphs, glyphToChar, scale) self.getHMTX(numberOfHMetrics, numGlyphs, glyphToChar, scale)
#################/ #################/
# loca - Index to location # loca - Index to location
#################/ #################/
self.getLOCA(indexToLocFormat, numGlyphs) self.getLOCA(indexToLocFormat, numGlyphs)
subsetglyphs = [(0, 0)] # special "sorted dict"! subsetglyphs = [(0, 0)] # special "sorted dict"!
subsetCharToGlyph = {} subsetCharToGlyph = {}
for code in subset: for code in subset:
if (code in self.charToGlyph): if (code in self.charToGlyph):
if (self.charToGlyph[code], code) not in subsetglyphs: if (self.charToGlyph[code], code) not in subsetglyphs:
subsetglyphs.append((self.charToGlyph[code], code)) # Old Glyph ID => Unicode subsetglyphs.append((self.charToGlyph[code], code)) # Old Glyph ID => Unicode
subsetCharToGlyph[code] = self.charToGlyph[code] # Unicode to old GlyphID subsetCharToGlyph[code] = self.charToGlyph[code] # Unicode to old GlyphID
self.maxUni = max(self.maxUni, code) self.maxUni = max(self.maxUni, code)
(start,dummy) = self.get_table_pos('glyf') (start,dummy) = self.get_table_pos('glyf')
subsetglyphs.sort() subsetglyphs.sort()
glyphSet = {} glyphSet = {}
n = 0 n = 0
fsLastCharIndex = 0 # maximum Unicode index (character code) in this font, according to the cmap subtable for platform ID 3 and platform- specific encoding ID 0 or 1. fsLastCharIndex = 0 # maximum Unicode index (character code) in this font, according to the cmap subtable for platform ID 3 and platform- specific encoding ID 0 or 1.
for originalGlyphIdx, uni in subsetglyphs: for originalGlyphIdx, uni in subsetglyphs:
fsLastCharIndex = max(fsLastCharIndex , uni) fsLastCharIndex = max(fsLastCharIndex , uni)
glyphSet[originalGlyphIdx] = n # old glyphID to new glyphID glyphSet[originalGlyphIdx] = n # old glyphID to new glyphID
n += 1 n += 1
codeToGlyph = {} codeToGlyph = {}
for uni, originalGlyphIdx in sorted(subsetCharToGlyph.items()): for uni, originalGlyphIdx in sorted(subsetCharToGlyph.items()):
codeToGlyph[uni] = glyphSet[originalGlyphIdx] codeToGlyph[uni] = glyphSet[originalGlyphIdx]
self.codeToGlyph = codeToGlyph self.codeToGlyph = codeToGlyph
for originalGlyphIdx, uni in subsetglyphs: for originalGlyphIdx, uni in subsetglyphs:
nonlocals = {'start': start, 'glyphSet': glyphSet, nonlocals = {'start': start, 'glyphSet': glyphSet,
'subsetglyphs': subsetglyphs} 'subsetglyphs': subsetglyphs}
self.getGlyphs(originalGlyphIdx, nonlocals) self.getGlyphs(originalGlyphIdx, nonlocals)
numGlyphs = numberOfHMetrics = len(subsetglyphs) numGlyphs = numberOfHMetrics = len(subsetglyphs)
#tables copied from the original #tables copied from the original
tags = ['name'] tags = ['name']
for tag in tags: for tag in tags:
self.add(tag, self.get_table(tag))
tags = ['cvt ', 'fpgm', 'prep', 'gasp']
for tag in tags:
if (tag in self.tables):
self.add(tag, self.get_table(tag)) self.add(tag, self.get_table(tag))
tags = ['cvt ', 'fpgm', 'prep', 'gasp']
for tag in tags:
if (tag in self.tables):
self.add(tag, self.get_table(tag))
# post - PostScript # post - PostScript
opost = self.get_table('post') opost = self.get_table('post')
post = "\x00\x03\x00\x00" + substr(opost,4,12) + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" post = b("\x00\x03\x00\x00") + substr(opost,4,12) + b("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00")
self.add('post', post) self.add('post', post)
# Sort CID2GID map into segments of contiguous codes # Sort CID2GID map into segments of contiguous codes
if 0 in codeToGlyph: if 0 in codeToGlyph:
del codeToGlyph[0] del codeToGlyph[0]
#unset(codeToGlyph[65535]) #unset(codeToGlyph[65535])
rangeid = 0 rangeid = 0
range_ = {} range_ = {}
prevcid = -2 prevcid = -2
prevglidx = -1 prevglidx = -1
# for each character # for each character
for cid, glidx in sorted(codeToGlyph.items()): for cid, glidx in sorted(codeToGlyph.items()):
if (cid == (prevcid + 1) and glidx == (prevglidx + 1)): if (cid == (prevcid + 1) and glidx == (prevglidx + 1)):
range_[rangeid].append(glidx) range_[rangeid].append(glidx)
else: else:
# new range # new range
rangeid = cid rangeid = cid
range_[rangeid] = [] range_[rangeid] = []
range_[rangeid].append(glidx) range_[rangeid].append(glidx)
prevcid = cid prevcid = cid
prevglidx = glidx prevglidx = glidx
# cmap - Character to glyph mapping - Format 4 (MS / )
segCount = len(range_) + 1 # + 1 Last segment has missing character 0xFFFF
searchRange = 1
entrySelector = 0
while (searchRange * 2 <= segCount ):
searchRange = searchRange * 2
entrySelector = entrySelector + 1
# cmap - Character to glyph mapping - Format 4 (MS / )
segCount = len(range_) + 1 # + 1 Last segment has missing character 0xFFFF
searchRange = 1
entrySelector = 0
while (searchRange * 2 <= segCount ):
searchRange = searchRange * 2 searchRange = searchRange * 2
entrySelector = entrySelector + 1 rangeShift = segCount * 2 - searchRange
length = 16 + (8*segCount ) + (numGlyphs+1)
cmap = [0, 1, # Index : version, number of encoding subtables
3, 1, # Encoding Subtable : platform (MS=3), encoding (Unicode)
0, 12, # Encoding Subtable : offset (hi,lo)
4, length, 0, # Format 4 Mapping subtable: format, length, language
segCount*2,
searchRange,
entrySelector,
rangeShift]
searchRange = searchRange * 2 range_ = sorted(range_.items())
rangeShift = segCount * 2 - searchRange
length = 16 + (8*segCount ) + (numGlyphs+1)
cmap = [0, 1, # Index : version, number of encoding subtables
3, 1, # Encoding Subtable : platform (MS=3), encoding (Unicode)
0, 12, # Encoding Subtable : offset (hi,lo)
4, length, 0, # Format 4 Mapping subtable: format, length, language
segCount*2,
searchRange,
entrySelector,
rangeShift]
range_ = sorted(range_.items()) # endCode(s)
for start, subrange in range_:
endCode = start + (len(subrange)-1)
cmap.append(endCode) # endCode(s)
# endCode(s) cmap.append(0xFFFF) # endCode of last Segment
for start, subrange in range_: cmap.append(0) # reservedPad
endCode = start + (len(subrange)-1)
cmap.append(endCode) # endCode(s)
cmap.append(0xFFFF) # endCode of last Segment # startCode(s)
cmap.append(0) # reservedPad for start, subrange in range_:
cmap.append(start) # startCode(s)
# startCode(s) cmap.append(0xFFFF) # startCode of last Segment
for start, subrange in range_: # idDelta(s)
cmap.append(start) # startCode(s) for start, subrange in range_:
idDelta = -(start-subrange[0])
n += count(subrange)
cmap.append(idDelta) # idDelta(s)
cmap.append(0xFFFF) # startCode of last Segment cmap.append(1) # idDelta of last Segment
# idDelta(s) # idRangeOffset(s)
for start, subrange in range_: for subrange in range_:
idDelta = -(start-subrange[0]) cmap.append(0) # idRangeOffset[segCount] Offset in bytes to glyph indexArray, or 0
n += count(subrange)
cmap.append(idDelta) # idDelta(s)
cmap.append(1) # idDelta of last Segment cmap.append(0) # idRangeOffset of last Segment
# idRangeOffset(s) for subrange, glidx in range_:
for subrange in range_: cmap.extend(glidx)
cmap.append(0) # idRangeOffset[segCount] Offset in bytes to glyph indexArray, or 0
cmap.append(0) # idRangeOffset of last Segment cmap.append(0) # Mapping for last character
for subrange, glidx in range_: cmapstr = b('')
cmap.extend(glidx) for cm in cmap:
if cm >= 0:
cmapstr += pack(">H", cm)
else:
try:
cmapstr += pack(">h", cm)
except:
warnings.warn("cmap value too big/small: %s" % cm)
cmapstr += pack(">H", -cm)
self.add('cmap', cmapstr)
cmap.append(0) # Mapping for last character # glyf - Glyph data
cmapstr = '' (glyfOffset,glyfLength) = self.get_table_pos('glyf')
for cm in cmap: if (glyfLength < self.maxStrLenRead):
if cm >= 0: glyphData = self.get_table('glyf')
cmapstr += pack(">H", cm)
else: offsets = []
glyf = b('')
pos = 0
hmtxstr = b('')
xMinT = 0
yMinT = 0
xMaxT = 0
yMaxT = 0
advanceWidthMax = 0
minLeftSideBearing = 0
minRightSideBearing = 0
xMaxExtent = 0
maxPoints = 0 # points in non-compound glyph
maxContours = 0 # contours in non-compound glyph
maxComponentPoints = 0 # points in compound glyph
maxComponentContours = 0 # contours in compound glyph
maxComponentElements = 0 # number of glyphs referenced at top level
maxComponentDepth = 0 # levels of recursion, set to 0 if font has only simple glyphs
self.glyphdata = {}
for originalGlyphIdx, uni in subsetglyphs:
# hmtx - Horizontal Metrics
hm = self.getHMetric(orignHmetrics, originalGlyphIdx)
hmtxstr += hm
offsets.append(pos)
try: try:
cmapstr += pack(">h", cm) glyphPos = self.glyphPos[originalGlyphIdx]
except: glyphLen = self.glyphPos[originalGlyphIdx + 1] - glyphPos
warnings.warn("cmap value too big/small: %s" % cm) except IndexError:
cmapstr += pack(">H", -cm) warnings.warn("missing glyph %s" % (originalGlyphIdx))
self.add('cmap', cmapstr) glyphLen = 0
# glyf - Glyph data if (glyfLength < self.maxStrLenRead):
(glyfOffset,glyfLength) = self.get_table_pos('glyf') data = substr(glyphData,glyphPos,glyphLen)
if (glyfLength < self.maxStrLenRead): else:
glyphData = self.get_table('glyf') if (glyphLen > 0):
data = self.get_chunk(glyfOffset+glyphPos,glyphLen)
else:
data = b('')
offsets = [] if (glyphLen > 0):
glyf = '' up = unpack(">H", substr(data,0,2))[0]
pos = 0 if (glyphLen > 2 and (up & (1 << 15)) ): # If number of contours <= -1 i.e. composite glyph
pos_in_glyph = 10
flags = GF_MORE
nComponentElements = 0
while (flags & GF_MORE):
nComponentElements += 1 # number of glyphs referenced at top level
up = unpack(">H", substr(data,pos_in_glyph,2))
flags = up[0]
up = unpack(">H", substr(data,pos_in_glyph+2,2))
glyphIdx = up[0]
self.glyphdata.setdefault(originalGlyphIdx, {}).setdefault('compGlyphs', []).append(glyphIdx)
try:
data = self._set_ushort(data, pos_in_glyph + 2, glyphSet[glyphIdx])
except KeyError:
data = 0
warnings.warn("missing glyph data %s" % glyphIdx)
pos_in_glyph += 4
if (flags & GF_WORDS):
pos_in_glyph += 4
else:
pos_in_glyph += 2
if (flags & GF_SCALE):
pos_in_glyph += 2
elif (flags & GF_XYSCALE):
pos_in_glyph += 4
elif (flags & GF_TWOBYTWO):
pos_in_glyph += 8
hmtxstr = '' maxComponentElements = max(maxComponentElements, nComponentElements)
xMinT = 0
yMinT = 0
xMaxT = 0
yMaxT = 0
advanceWidthMax = 0
minLeftSideBearing = 0
minRightSideBearing = 0
xMaxExtent = 0
maxPoints = 0 # points in non-compound glyph
maxContours = 0 # contours in non-compound glyph
maxComponentPoints = 0 # points in compound glyph
maxComponentContours = 0 # contours in compound glyph
maxComponentElements = 0 # number of glyphs referenced at top level
maxComponentDepth = 0 # levels of recursion, set to 0 if font has only simple glyphs
self.glyphdata = {}
for originalGlyphIdx, uni in subsetglyphs: glyf += data
# hmtx - Horizontal Metrics pos += glyphLen
hm = self.getHMetric(orignHmetrics, originalGlyphIdx) if (pos % 4 != 0):
hmtxstr += hm padding = 4 - (pos % 4)
glyf += str_repeat(b("\0"),padding)
pos += padding
offsets.append(pos) offsets.append(pos)
try: self.add('glyf', glyf)
glyphPos = self.glyphPos[originalGlyphIdx]
glyphLen = self.glyphPos[originalGlyphIdx + 1] - glyphPos
except IndexError:
warnings.warn("missing glyph %s" % (originalGlyphIdx))
glyphLen = 0
if (glyfLength < self.maxStrLenRead): # hmtx - Horizontal Metrics
data = substr(glyphData,glyphPos,glyphLen) self.add('hmtx', hmtxstr)
# loca - Index to location
locastr = b('')
if (((pos + 1) >> 1) > 0xFFFF):
indexToLocFormat = 1 # long format
for offset in offsets:
locastr += pack(">L",offset)
else: else:
if (glyphLen > 0): indexToLocFormat = 0 # short format
data = self.get_chunk(glyfOffset+glyphPos,glyphLen) for offset in offsets:
else: locastr += pack(">H",offset//2)
data = ''
if (glyphLen > 0): self.add('loca', locastr)
up = unpack(">H", substr(data,0,2))[0]
if (glyphLen > 2 and (up & (1 << 15)) ): # If number of contours <= -1 i.e. composiste glyph
pos_in_glyph = 10
flags = GF_MORE
nComponentElements = 0
while (flags & GF_MORE):
nComponentElements += 1 # number of glyphs referenced at top level
up = unpack(">H", substr(data,pos_in_glyph,2))
flags = up[0]
up = unpack(">H", substr(data,pos_in_glyph+2,2))
glyphIdx = up[0]
self.glyphdata.setdefault(originalGlyphIdx, {}).setdefault('compGlyphs', []).append(glyphIdx)
try:
data = self._set_ushort(data, pos_in_glyph + 2, glyphSet[glyphIdx])
except KeyError:
data = 0
warnings.warn("missing glyph data %s" % glyphIdx)
pos_in_glyph += 4
if (flags & GF_WORDS):
pos_in_glyph += 4
else:
pos_in_glyph += 2
if (flags & GF_SCALE):
pos_in_glyph += 2
elif (flags & GF_XYSCALE):
pos_in_glyph += 4
elif (flags & GF_TWOBYTWO):
pos_in_glyph += 8
maxComponentElements = max(maxComponentElements, nComponentElements) # head - Font header
head = self.get_table('head')
head = self._set_ushort(head, 50, indexToLocFormat)
self.add('head', head)
glyf += data # hhea - Horizontal Header
pos += glyphLen hhea = self.get_table('hhea')
if (pos % 4 != 0): hhea = self._set_ushort(hhea, 34, numberOfHMetrics)
padding = 4 - (pos % 4) self.add('hhea', hhea)
glyf += str_repeat("\0",padding)
pos += padding
offsets.append(pos) # maxp - Maximum Profile
self.add('glyf', glyf) maxp = self.get_table('maxp')
maxp = self._set_ushort(maxp, 4, numGlyphs)
self.add('maxp', maxp)
# hmtx - Horizontal Metrics # OS/2 - OS/2
self.add('hmtx', hmtxstr) os2 = self.get_table('OS/2')
self.add('OS/2', os2 )
# loca - Index to location
locastr = ''
if (((pos + 1) >> 1) > 0xFFFF):
indexToLocFormat = 1 # long format
for offset in offsets:
locastr += pack(">L",offset)
else:
indexToLocFormat = 0 # short format
for offset in offsets:
locastr += pack(">H",(offset/2))
self.add('loca', locastr)
# head - Font header
head = self.get_table('head')
head = self._set_ushort(head, 50, indexToLocFormat)
self.add('head', head)
# hhea - Horizontal Header
hhea = self.get_table('hhea')
hhea = self._set_ushort(hhea, 34, numberOfHMetrics)
self.add('hhea', hhea)
# maxp - Maximum Profile
maxp = self.get_table('maxp')
maxp = self._set_ushort(maxp, 4, numGlyphs)
self.add('maxp', maxp)
# OS/2 - OS/2
os2 = self.get_table('OS/2')
self.add('OS/2', os2 )
self.fh.close()
# Put the TTF file together # Put the TTF file together
stm = self.endTTFile('') stm = self.endTTFile('')
@@ -864,11 +864,16 @@ class TTFontFile:
def getHMTX(self, numberOfHMetrics, numGlyphs, glyphToChar, scale): def getHMTX(self, numberOfHMetrics, numGlyphs, glyphToChar, scale):
start = self.seek_table("hmtx") start = self.seek_table("hmtx")
aw = 0 aw = 0
self.charWidths = [0] * 256*256*2 self.charWidths = []
def resize_cw(size, default):
size = (((size + 1) // 1024) + 1) * 1024
delta = size - len(self.charWidths)
if delta > 0:
self.charWidths += [default] * delta
nCharWidths = 0 nCharWidths = 0
if ((numberOfHMetrics*4) < self.maxStrLenRead): if ((numberOfHMetrics*4) < self.maxStrLenRead):
data = self.get_chunk(start,(numberOfHMetrics*4)) data = self.get_chunk(start,(numberOfHMetrics*4))
arr = unpack(">" + "H" * (len(data)/2), data) arr = unpack(">%dH" % (len(data)//2), data)
else: else:
self.seek(start) self.seek(start)
for glyph in range(numberOfHMetrics): for glyph in range(numberOfHMetrics):
@@ -888,24 +893,28 @@ class TTFontFile:
for char in glyphToChar[glyph]: for char in glyphToChar[glyph]:
if (char != 0 and char != 65535): if (char != 0 and char != 65535):
w = int(round(scale*aw)) w = int(round(scale*aw+0.001)) # ROUND_HALF_UP in PY3K (like php)
if (w == 0): w = 65535 if (w == 0): w = 65535
if (char < 196608): if (char < 196608):
if char >= len(self.charWidths):
resize_cw(char, self.defaultWidth)
self.charWidths[char] = w self.charWidths[char] = w
nCharWidths += 1 nCharWidths += 1
data = self.get_chunk((start+numberOfHMetrics*4),(numGlyphs*2)) data = self.get_chunk((start+numberOfHMetrics*4),(numGlyphs*2))
arr = unpack(">" + "H" * (len(data)/2), data) arr = unpack(">%dH" % (len(data)//2), data)
diff = numGlyphs-numberOfHMetrics diff = numGlyphs-numberOfHMetrics
for pos in range(diff): for pos in range(diff):
glyph = pos + numberOfHMetrics glyph = pos + numberOfHMetrics
if (glyph in glyphToChar): if (glyph in glyphToChar):
for char in glyphToChar[glyph]: for char in glyphToChar[glyph]:
if (char != 0 and char != 65535): if (char != 0 and char != 65535):
w = int(round(scale*aw)) w = int(round(scale*aw+0.001)) # ROUND_HALF_UP in PY3K (like php)
if (w == 0): w = 65535 if (w == 0): w = 65535
if (char < 196608): if (char < 196608):
if char >= len(self.charWidths):
resize_cw(char, self.defaultWidth)
self.charWidths[char] = w self.charWidths[char] = w
nCharWidths += 1 nCharWidths += 1
@@ -933,12 +942,12 @@ class TTFontFile:
self.glyphPos = [] self.glyphPos = []
if (indexToLocFormat == 0): if (indexToLocFormat == 0):
data = self.get_chunk(start,(numGlyphs*2)+2) data = self.get_chunk(start,(numGlyphs*2)+2)
arr = unpack(">" + "H" * (len(data)/2), data) arr = unpack(">%dH" % (len(data)//2), data)
for n in range(numGlyphs): for n in range(numGlyphs):
self.glyphPos.append((arr[n] * 2)) # n+1 !? self.glyphPos.append((arr[n] * 2)) # n+1 !?
elif (indexToLocFormat == 1): elif (indexToLocFormat == 1):
data = self.get_chunk(start,(numGlyphs*4)+4) data = self.get_chunk(start,(numGlyphs*4)+4)
arr = unpack(">" + "L" * (len(data)/4), data) arr = unpack(">%dL" % (len(data)//4), data)
for n in range(numGlyphs): for n in range(numGlyphs):
self.glyphPos.append((arr[n])) # n+1 !? self.glyphPos.append((arr[n])) # n+1 !?
else: else:
@@ -952,7 +961,7 @@ class TTFontFile:
limit = unicode_cmap_offset + length limit = unicode_cmap_offset + length
self.skip(2) self.skip(2)
segCount = self.read_ushort() / 2 segCount = self.read_ushort() // 2
self.skip(6) self.skip(6)
endCount = [] endCount = []
for i in range(segCount): for i in range(segCount):
@@ -1020,7 +1029,7 @@ class TTFontFile:
# Put the TTF file together # Put the TTF file together
def endTTFile(self, stm): def endTTFile(self, stm):
stm = '' stm = b('')
numTables = count(self.otables) numTables = count(self.otables)
searchRange = 1 searchRange = 1
entrySelector = 0 entrySelector = 0
@@ -1046,7 +1055,7 @@ class TTFontFile:
for tag, data in sorted_tables: for tag, data in sorted_tables:
if (tag == 'head'): if (tag == 'head'):
head_start = offset head_start = offset
stm += tag stm += tag.encode("latin1")
checksum = calcChecksum(data) checksum = calcChecksum(data)
stm += pack(">HH", checksum[0],checksum[1]) stm += pack(">HH", checksum[0],checksum[1])
stm += pack(">LL", offset, strlen(data)) stm += pack(">LL", offset, strlen(data))
@@ -1055,7 +1064,7 @@ class TTFontFile:
# Table data # Table data
for tag, data in sorted_tables: for tag, data in sorted_tables:
data += "\0\0\0" data += b("\0\0\0")
stm += substr(data,0,(strlen(data)&~3)) stm += substr(data,0,(strlen(data)&~3))
checksum = calcChecksum(stm) checksum = calcChecksum(stm)
@@ -1064,20 +1073,3 @@ class TTFontFile:
stm = self.splice(stm,(head_start + 8),chk) stm = self.splice(stm,(head_start + 8),chk)
return stm return stm
if __name__ == '__main__':
ttf = TTFontFile()
ttffile = 'DejaVuSansCondensed.ttf';
ttf.getMetrics(ttffile)
# test basic metrics:
assert round(ttf.descent, 0) == -236
assert round(ttf.capHeight, 0) == 928
assert ttf.flags == 4
assert [round(i, 0) for i in ttf.bbox] == [-918, -415, 1513, 1167]
assert ttf.italicAngle == 0
assert ttf.stemV == 87
assert round(ttf.defaultWidth, 0) == 540
assert round(ttf.underlinePosition, 0) == -63
assert round(ttf.underlineThickness, 0) == 44
# test char widths 8(against binary file generated by tfpdf.php):
assert ''.join(ttf.charWidths) == open("dejavusanscondensed.cw.dat").read()
+1 -1
View File
@@ -5,7 +5,7 @@
# Read more about this http://code.google.com/p/pyfpdf # Read more about this http://code.google.com/p/pyfpdf
# Please note that new package name is fpdf (to avoid some naming conflicts) # Please note that new package name is fpdf (to avoid some naming conflicts)
# import fpdf into pyfpdf for backward compatibility (prior web2py 2.0): # import fpdf into pyfpdf for backward compatibility (prior web2py 2.0):
from fpdf import * from gluon.contrib.fpdf import *
# import warnings # import warnings
# warnings.warn("pyfpdf package name is deprecated, please use fpdf instead") # warnings.warn("pyfpdf package name is deprecated, please use fpdf instead")
+1 -1
View File
@@ -10,6 +10,7 @@ from .test_dal import *
from .test_cache import * from .test_cache import *
from .test_template import * from .test_template import *
from .test_html import * from .test_html import *
from .test_contribs import *
if sys.version[:3] == '2.7': if sys.version[:3] == '2.7':
from .test_compileapp import * from .test_compileapp import *
@@ -24,5 +25,4 @@ if sys.version[:3] == '2.7':
from .test_appadmin import * from .test_appadmin import *
from .test_scheduler import * from .test_scheduler import *
from .test_web import * from .test_web import *
from .test_contribs import *
from .test_old_doctests import * from .test_old_doctests import *
+3 -3
View File
@@ -8,7 +8,7 @@ import os
from .fix_path import fix_sys_path from .fix_path import fix_sys_path
fix_sys_path(__file__) fix_sys_path(__file__)
from gluon._compat import to_bytes
from gluon.storage import Storage from gluon.storage import Storage
from gluon.contrib import fpdf as fpdf from gluon.contrib import fpdf as fpdf
from gluon.contrib import pyfpdf as pyfpdf from gluon.contrib import pyfpdf as pyfpdf
@@ -42,8 +42,8 @@ class TestContribs(unittest.TestCase):
pdf.write(5, 'hello world') pdf.write(5, 'hello world')
pdf_out = pdf.output('', 'S') pdf_out = pdf.output('', 'S')
self.assertTrue(fpdf.FPDF_VERSION in pdf_out, 'version string') self.assertTrue(to_bytes(fpdf.FPDF_VERSION) in pdf_out, 'version string')
self.assertTrue('hello world' in pdf_out, 'sample message') self.assertTrue(to_bytes('hello world') in pdf_out, 'sample message')
def test_appconfig(self): def test_appconfig(self):
""" """