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']
+503 -364
View File
File diff suppressed because it is too large Load Diff
+19 -15
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()
try:
self.pdf.set_font(face) self.pdf.set_font(face)
self.font_face = 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]
+51 -123
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,13 +30,25 @@ 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
if not PY3K:
f = open(infile, 'rb')
else:
f = open(infile)
with f:
for row in csv.reader(f, delimiter=delimiter):
kargs = {} kargs = {}
for i,v in enumerate(row): for i,v in enumerate(row):
if not v.startswith("'") and decimal_sep!=".": if not v.startswith("'") and decimal_sep!=".":
@@ -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,6 +132,7 @@ class Template:
if 'rotate' in element: if 'rotate' in element:
pdf.rotate(0) pdf.rotate(0)
if dest:
return pdf.output(outfile, 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,
@@ -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,6 +193,7 @@ 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):
if text:
pdf.image(text,x1,y1,w=x2-x1,h=y2-y1,type='',link='') 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,
@@ -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")
+42 -50
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,7 +77,7 @@ 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 = {}
@@ -90,10 +93,9 @@ class TTFontFile:
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,7 +457,7 @@ 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 = {}
@@ -584,7 +586,7 @@ class TTFontFile:
# 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
@@ -658,7 +660,7 @@ class TTFontFile:
cmap.extend(glidx) cmap.extend(glidx)
cmap.append(0) # Mapping for last character cmap.append(0) # Mapping for last character
cmapstr = '' cmapstr = b('')
for cm in cmap: for cm in cmap:
if cm >= 0: if cm >= 0:
cmapstr += pack(">H", cm) cmapstr += pack(">H", cm)
@@ -676,10 +678,10 @@ class TTFontFile:
glyphData = self.get_table('glyf') glyphData = self.get_table('glyf')
offsets = [] offsets = []
glyf = '' glyf = b('')
pos = 0 pos = 0
hmtxstr = '' hmtxstr = b('')
xMinT = 0 xMinT = 0
yMinT = 0 yMinT = 0
xMaxT = 0 xMaxT = 0
@@ -715,11 +717,11 @@ class TTFontFile:
if (glyphLen > 0): if (glyphLen > 0):
data = self.get_chunk(glyfOffset+glyphPos,glyphLen) data = self.get_chunk(glyfOffset+glyphPos,glyphLen)
else: else:
data = '' data = b('')
if (glyphLen > 0): if (glyphLen > 0):
up = unpack(">H", substr(data,0,2))[0] up = unpack(">H", substr(data,0,2))[0]
if (glyphLen > 2 and (up & (1 << 15)) ): # If number of contours <= -1 i.e. composiste glyph if (glyphLen > 2 and (up & (1 << 15)) ): # If number of contours <= -1 i.e. composite glyph
pos_in_glyph = 10 pos_in_glyph = 10
flags = GF_MORE flags = GF_MORE
nComponentElements = 0 nComponentElements = 0
@@ -753,7 +755,7 @@ class TTFontFile:
pos += glyphLen pos += glyphLen
if (pos % 4 != 0): if (pos % 4 != 0):
padding = 4 - (pos % 4) padding = 4 - (pos % 4)
glyf += str_repeat("\0",padding) glyf += str_repeat(b("\0"),padding)
pos += padding pos += padding
offsets.append(pos) offsets.append(pos)
@@ -763,7 +765,7 @@ class TTFontFile:
self.add('hmtx', hmtxstr) self.add('hmtx', hmtxstr)
# loca - Index to location # loca - Index to location
locastr = '' locastr = b('')
if (((pos + 1) >> 1) > 0xFFFF): if (((pos + 1) >> 1) > 0xFFFF):
indexToLocFormat = 1 # long format indexToLocFormat = 1 # long format
for offset in offsets: for offset in offsets:
@@ -771,7 +773,7 @@ class TTFontFile:
else: else:
indexToLocFormat = 0 # short format indexToLocFormat = 0 # short format
for offset in offsets: for offset in offsets:
locastr += pack(">H",(offset/2)) locastr += pack(">H",offset//2)
self.add('loca', locastr) self.add('loca', locastr)
@@ -794,8 +796,6 @@ class TTFontFile:
os2 = self.get_table('OS/2') os2 = self.get_table('OS/2')
self.add('OS/2', os2 ) self.add('OS/2', os2 )
self.fh.close()
# Put the TTF file together # Put the TTF file together
stm = self.endTTFile('') stm = self.endTTFile('')
return stm return stm
@@ -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):
""" """