Updated fpdf to the last version (py3 compatible), fix contrib/appconfig
This commit is contained in:
@@ -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).
|
||||
|
||||
"""
|
||||
import thread
|
||||
import os
|
||||
from ConfigParser import SafeConfigParser
|
||||
from gluon import current
|
||||
import json
|
||||
from gluon._compat import thread, configparser
|
||||
from gluon.globals import current
|
||||
|
||||
locker = thread.allocate_lock()
|
||||
|
||||
@@ -121,7 +120,7 @@ class AppConfigLoader(object):
|
||||
self.read_config()
|
||||
|
||||
def read_config_ini(self):
|
||||
config = SafeConfigParser()
|
||||
config = configparser.SafeConfigParser()
|
||||
config.read(self.file)
|
||||
settings = {}
|
||||
for section in config.sections():
|
||||
|
||||
@@ -4,13 +4,13 @@
|
||||
"FPDF for python"
|
||||
|
||||
__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:
|
||||
from html import HTMLMixin
|
||||
from .html import HTMLMixin
|
||||
except ImportError:
|
||||
import warnings
|
||||
warnings.warn("web2py gluon package not installed, required for html2pdf")
|
||||
|
||||
from template import Template
|
||||
from .template import Template
|
||||
|
||||
@@ -7,7 +7,7 @@ fpdf_charwidths = {}
|
||||
|
||||
fpdf_charwidths['courier']={}
|
||||
|
||||
for i in xrange(0,256):
|
||||
for i in range(0,256):
|
||||
fpdf_charwidths['courier'][chr(i)]=600
|
||||
fpdf_charwidths['courierB']=fpdf_charwidths['courier']
|
||||
fpdf_charwidths['courierI']=fpdf_charwidths['courier']
|
||||
|
||||
+537
-398
File diff suppressed because it is too large
Load Diff
+21
-17
@@ -8,8 +8,8 @@ __license__ = "LGPL 3.0"
|
||||
|
||||
# Inspired by tuto5.py and several examples from fpdf.org, html2fpdf, etc.
|
||||
|
||||
from fpdf import FPDF
|
||||
from HTMLParser import HTMLParser
|
||||
from .fpdf import FPDF
|
||||
from .py3k import PY3K, basestring, unicode, HTMLParser
|
||||
|
||||
DEBUG = False
|
||||
|
||||
@@ -78,8 +78,8 @@ class HTML2FPDF(HTMLParser):
|
||||
l = self.table_col_width[i:i+colspan]
|
||||
else:
|
||||
l = [self.td.get('width','240')]
|
||||
w = sum([self.width2mm(lenght) for lenght in l])
|
||||
h = int(self.td.get('height', 0)) / 4 or self.h*1.30
|
||||
w = sum([self.width2mm(length) for length in l])
|
||||
h = int(self.td.get('height', 0)) // 4 or self.h*1.30
|
||||
self.table_h = h
|
||||
border = int(self.table.get('border', 0))
|
||||
if not self.th:
|
||||
@@ -99,30 +99,30 @@ class HTML2FPDF(HTMLParser):
|
||||
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:
|
||||
self.output_table_footer()
|
||||
self.pdf.add_page()
|
||||
self.pdf.add_page(same = True)
|
||||
self.theader_out = self.tfooter_out = False
|
||||
if self.tfoot is None and self.thead is None:
|
||||
if not self.theader_out:
|
||||
self.output_table_header()
|
||||
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)
|
||||
elif self.table is not None:
|
||||
# ignore anything else than td inside a table
|
||||
pass
|
||||
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)
|
||||
else:
|
||||
txt = txt.replace("\n"," ")
|
||||
if self.href:
|
||||
self.put_link(self.href,txt)
|
||||
else:
|
||||
if DEBUG: print "write", txt, "*"
|
||||
if DEBUG: print("write", txt, "*")
|
||||
self.pdf.write(self.h,txt)
|
||||
|
||||
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:
|
||||
fill_color = self.pdf.fill_color
|
||||
self.pdf.set_fill_color(*bgcolor)
|
||||
@@ -168,7 +168,7 @@ class HTML2FPDF(HTMLParser):
|
||||
|
||||
def handle_starttag(self, tag, 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':
|
||||
self.set_style(tag,1)
|
||||
if tag=='a':
|
||||
@@ -216,13 +216,16 @@ class HTML2FPDF(HTMLParser):
|
||||
# save previous font state:
|
||||
self.font_stack.append((self.font_face, self.font_size, self.color))
|
||||
if 'color' in attrs:
|
||||
self.color = hex2dec(attrs['color'])
|
||||
color = hex2dec(attrs['color'])
|
||||
self.set_text_color(*color)
|
||||
self.color = color
|
||||
if 'face' in attrs:
|
||||
face = attrs.get('face').lower()
|
||||
self.pdf.set_font(face)
|
||||
self.font_face = face
|
||||
try:
|
||||
self.pdf.set_font(face)
|
||||
self.font_face = face
|
||||
except RuntimeError:
|
||||
pass # font not found, ignore
|
||||
if 'size' in attrs:
|
||||
size = int(attrs.get('size'))
|
||||
self.pdf.set_font(self.font_face, size=int(size))
|
||||
@@ -277,7 +280,7 @@ class HTML2FPDF(HTMLParser):
|
||||
|
||||
def handle_endtag(self, 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':
|
||||
self.pdf.ln(6)
|
||||
self.set_font()
|
||||
@@ -326,7 +329,7 @@ class HTML2FPDF(HTMLParser):
|
||||
self.tr = None
|
||||
if tag=='td' or tag=='th':
|
||||
if self.th:
|
||||
if DEBUG: print "revert style"
|
||||
if DEBUG: print("revert style")
|
||||
self.set_style('B', False) # revert style
|
||||
self.table_col_index += int(self.td.get('colspan','1'))
|
||||
self.td = None
|
||||
@@ -348,7 +351,7 @@ class HTML2FPDF(HTMLParser):
|
||||
if size:
|
||||
self.font_size = size
|
||||
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_size(self.font_size or 12)
|
||||
self.set_style('u', False)
|
||||
@@ -365,7 +368,7 @@ class HTML2FPDF(HTMLParser):
|
||||
for s in ('b','i','u'):
|
||||
if self.style.get(s):
|
||||
style+=s
|
||||
if DEBUG: print "SET_FONT_STYLE", style
|
||||
if DEBUG: print("SET_FONT_STYLE", style)
|
||||
self.pdf.set_font('',style)
|
||||
|
||||
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):
|
||||
"Parse HTML and convert it to PDF"
|
||||
h2p = HTML2FPDF(self, image_map)
|
||||
text = h2p.unescape(text) # To deal with HTML entities
|
||||
h2p.feed(text)
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: latin-1 -*-
|
||||
|
||||
from .py3k import PY3K, basestring, unicode
|
||||
|
||||
# fpdf php helpers:
|
||||
|
||||
def substr(s, start, length=-1):
|
||||
@@ -14,16 +16,19 @@ def print_r(array):
|
||||
if not isinstance(array, dict):
|
||||
array = dict([(k, k) for k in array])
|
||||
for k, v in array.items():
|
||||
print "[%s] => %s" % (k, v),
|
||||
print("[%s] => %s " % (k, v))
|
||||
|
||||
def UTF8ToUTF16BE(instr, setbom=True):
|
||||
"Converts UTF-8 strings to UTF16-BE."
|
||||
outstr = ""
|
||||
outstr = "".encode()
|
||||
if (setbom):
|
||||
outstr += "\xFE\xFF";
|
||||
outstr += "\xFE\xFF".encode("latin1")
|
||||
if not isinstance(instr, unicode):
|
||||
instr = instr.decode('UTF-8')
|
||||
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
|
||||
|
||||
def UTF8StringToArray(instr):
|
||||
@@ -46,4 +51,4 @@ def str_pad(s, pad_length=0, pad_char= " ", pad_type= +1 ):
|
||||
else: # pad both
|
||||
return s.center(pad_length, pad_char)
|
||||
|
||||
strlen = count = lambda s: len(s)
|
||||
strlen = count = lambda s: len(s)
|
||||
|
||||
@@ -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]
|
||||
|
||||
|
||||
+66
-138
@@ -2,12 +2,15 @@
|
||||
|
||||
"PDF Template Helper for FPDF.py"
|
||||
|
||||
from __future__ import with_statement
|
||||
|
||||
__author__ = "Mariano Reingart <reingart@gmail.com>"
|
||||
__copyright__ = "Copyright (C) 2010 Mariano Reingart"
|
||||
__license__ = "LGPL 3.0"
|
||||
|
||||
import sys,os,csv
|
||||
from fpdf import FPDF
|
||||
from .fpdf import FPDF
|
||||
from .py3k import PY3K, basestring, unicode
|
||||
|
||||
def rgb(col):
|
||||
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',
|
||||
title='', author='', subject='', creator='', keywords=''):
|
||||
if elements:
|
||||
self.elements = elements
|
||||
self.keys = [v['name'].lower() for v in self.elements]
|
||||
self.load_elements(elements)
|
||||
self.handlers = {'T': self.text, 'L': self.line, 'I': self.image,
|
||||
'B': self.rect, 'BC': self.barcode, }
|
||||
self.pg_no = 0
|
||||
'B': self.rect, 'BC': self.barcode, 'W': self.write, }
|
||||
self.texts = {}
|
||||
pdf = self.pdf = FPDF(format=format,orientation=orientation, unit="mm")
|
||||
pdf.set_title(title)
|
||||
@@ -29,25 +30,37 @@ class Template:
|
||||
pdf.set_subject(subject)
|
||||
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="."):
|
||||
"Parse template format csv file and create elements dict"
|
||||
keys = ('name','type','x1','y1','x2','y2','font','size',
|
||||
'bold','italic','underline','foreground','background',
|
||||
'align','text','priority', 'multiline')
|
||||
self.elements = []
|
||||
for row in csv.reader(open(infile, 'rb'), delimiter=delimiter):
|
||||
kargs = {}
|
||||
for i,v in enumerate(row):
|
||||
if not v.startswith("'") and decimal_sep!=".":
|
||||
v = v.replace(decimal_sep,".")
|
||||
else:
|
||||
v = v
|
||||
if v=='':
|
||||
v = None
|
||||
else:
|
||||
v = eval(v.strip())
|
||||
kargs[keys[i]] = v
|
||||
self.elements.append(kargs)
|
||||
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 = {}
|
||||
for i,v in enumerate(row):
|
||||
if not v.startswith("'") and decimal_sep!=".":
|
||||
v = v.replace(decimal_sep,".")
|
||||
else:
|
||||
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]
|
||||
|
||||
def add_page(self):
|
||||
@@ -55,8 +68,8 @@ class Template:
|
||||
self.texts[self.pg_no] = {}
|
||||
|
||||
def __setitem__(self, name, value):
|
||||
if self.has_key(name):
|
||||
if isinstance(value,unicode):
|
||||
if name.lower() in self.keys:
|
||||
if not PY3K and isinstance(value, unicode):
|
||||
value = value.encode("latin1","ignore")
|
||||
elif value is None:
|
||||
value = ""
|
||||
@@ -71,7 +84,7 @@ class Template:
|
||||
return name.lower() in self.keys
|
||||
|
||||
def __getitem__(self, name):
|
||||
if self.has_key(name):
|
||||
if name in self.keys:
|
||||
key = name.lower()
|
||||
if key in self.texts:
|
||||
# text for this page:
|
||||
@@ -94,7 +107,7 @@ class Template:
|
||||
if element['underline']: style += "U"
|
||||
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
|
||||
if isinstance(text, unicode):
|
||||
if isinstance(text, unicode) and not PY3K:
|
||||
text = text.encode("latin1","ignore")
|
||||
else:
|
||||
text = str(text)
|
||||
@@ -110,16 +123,17 @@ class Template:
|
||||
pdf.set_auto_page_break(False,margin=0)
|
||||
|
||||
for element in sorted(self.elements,key=lambda x: x['priority']):
|
||||
# make a copy of the element:
|
||||
element = dict(element)
|
||||
#print "dib",element['type'], element['name'], element['x1'], element['y1'], element['x2'], element['y2']
|
||||
element = element.copy()
|
||||
element['text'] = self.texts[pg].get(element['name'].lower(), element['text'])
|
||||
if 'rotate' in element:
|
||||
pdf.rotate(element['rotate'], element['x1'], element['y1'])
|
||||
self.handlers[element['type'].upper()](pdf, **element)
|
||||
if 'rotate' in element:
|
||||
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,
|
||||
bold=False, italic=False, underline=False, align="",
|
||||
@@ -157,7 +171,7 @@ class Template:
|
||||
# multiline==False: trim to fit exactly the space defined
|
||||
text = pdf.multi_cell(w=x2-x1, h=y2-y1,
|
||||
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.Text(x=x1,y=y1,txt=text)
|
||||
@@ -179,7 +193,8 @@ class Template:
|
||||
pdf.rect(x1, y1, x2-x1, y2-y1)
|
||||
|
||||
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,
|
||||
foreground=0, *args, **kwargs):
|
||||
@@ -189,113 +204,26 @@ class Template:
|
||||
if font == 'interleaved 2of5 nt':
|
||||
pdf.interleaved2of5(text,x1,y1,w=size,h=y2-y1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
# generate sample invoice (according Argentina's regulations)
|
||||
|
||||
import random
|
||||
from decimal import Decimal
|
||||
|
||||
f = Template(format="A4",
|
||||
title="Sample Invoice", author="Sample Company",
|
||||
subject="Sample Customer", keywords="Electronic TAX Invoice")
|
||||
f.parse_csv(infile="invoice.csv", delimiter=";", decimal_sep=",")
|
||||
|
||||
detail = "Lorem ipsum dolor sit amet, consectetur. " * 30
|
||||
items = []
|
||||
for i in range(1, 30):
|
||||
ds = "Sample product %s" % i
|
||||
qty = random.randint(1,10)
|
||||
price = round(random.random()*100,3)
|
||||
code = "%s%s%02d" % (chr(random.randint(65,90)), chr(random.randint(65,90)),i)
|
||||
items.append(dict(code=code, unit='u',
|
||||
qty=qty, price=price,
|
||||
amount=qty*price,
|
||||
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")
|
||||
# Added by Derek Schwalenberg Schwalenberg1013@gmail.com to allow (url) links in templates (using write method) 2014-02-22
|
||||
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',
|
||||
foreground=0, *args, **kwargs):
|
||||
if pdf.text_color!=rgb(foreground):
|
||||
pdf.set_text_color(*rgb(foreground))
|
||||
font = font.strip().lower()
|
||||
if font == 'arial black':
|
||||
font = 'arial'
|
||||
style = ""
|
||||
for tag in 'B', 'I', 'U':
|
||||
if (text.startswith("<%s>" % tag) and text.endswith("</%s>" %tag)):
|
||||
text = text[3:-4]
|
||||
style += tag
|
||||
if bold: style += "B"
|
||||
if italic: style += "I"
|
||||
if underline: style += "U"
|
||||
align = {'L':'L','R':'R','I':'L','D':'R','C':'C','':''}.get(align) # D/I in spanish
|
||||
pdf.set_font(font,style,size)
|
||||
##m_k = 72 / 2.54
|
||||
##h = (size/m_k)
|
||||
pdf.set_xy(x1,y1)
|
||||
pdf.write(5,text,link)
|
||||
|
||||
+358
-366
@@ -16,10 +16,13 @@
|
||||
#
|
||||
#******************************************************************************
|
||||
|
||||
from __future__ import with_statement
|
||||
|
||||
from struct import pack, unpack, unpack_from
|
||||
import re
|
||||
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
|
||||
@@ -55,7 +58,7 @@ def sub32(x, y):
|
||||
|
||||
def calcChecksum(data):
|
||||
if (strlen(data) % 4):
|
||||
data += str_repeat("\0", (4-(len(data) % 4)))
|
||||
data += str_repeat(b("\0"), (4-(len(data) % 4)))
|
||||
hi=0x0000
|
||||
lo=0x0000
|
||||
for i in range(0, len(data), 4):
|
||||
@@ -74,26 +77,25 @@ class TTFontFile:
|
||||
|
||||
def getMetrics(self, file):
|
||||
self.filename = file
|
||||
self.fh = open(file,'rb')
|
||||
self._pos = 0
|
||||
self.charWidths = []
|
||||
self.glyphPos = {}
|
||||
self.charToGlyph = {}
|
||||
self.tables = {}
|
||||
self.otables = {}
|
||||
self.ascent = 0
|
||||
self.descent = 0
|
||||
self.TTCFonts = {}
|
||||
self.version = version = self.read_ulong()
|
||||
if (version==0x4F54544F):
|
||||
die("Postscript outlines are not supported")
|
||||
if (version==0x74746366):
|
||||
die("ERROR - TrueType Fonts Collections not supported")
|
||||
if (version not in (0x00010000,0x74727565)):
|
||||
die("Not a TrueType font: version=" + version)
|
||||
self.readTableDirectory()
|
||||
self.extractInfo()
|
||||
self.fh.close()
|
||||
with open(file,'rb') as self.fh:
|
||||
self._pos = 0
|
||||
self.charWidths = []
|
||||
self.glyphPos = {}
|
||||
self.charToGlyph = {}
|
||||
self.tables = {}
|
||||
self.otables = {}
|
||||
self.ascent = 0
|
||||
self.descent = 0
|
||||
self.TTCFonts = {}
|
||||
self.version = version = self.read_ulong()
|
||||
if (version==0x4F54544F):
|
||||
die("Postscript outlines are not supported")
|
||||
if (version==0x74746366):
|
||||
die("ERROR - TrueType Fonts Collections not supported")
|
||||
if (version not in (0x00010000,0x74727565)):
|
||||
die("Not a TrueType font: version=" + str(version))
|
||||
self.readTableDirectory()
|
||||
self.extractInfo()
|
||||
|
||||
def readTableDirectory(self, ):
|
||||
self.numTables = self.read_ushort()
|
||||
@@ -130,7 +132,7 @@ class TTFontFile:
|
||||
|
||||
def read_tag(self):
|
||||
self._pos += 4
|
||||
return self.fh.read(4)
|
||||
return self.fh.read(4).decode("latin1")
|
||||
|
||||
def read_short(self):
|
||||
self._pos += 2
|
||||
@@ -204,7 +206,7 @@ class TTFontFile:
|
||||
|
||||
def add(self, tag, data):
|
||||
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
|
||||
|
||||
############################################/
|
||||
@@ -226,7 +228,7 @@ class TTFontFile:
|
||||
numRecords = self.read_ushort()
|
||||
string_data_offset = name_offset + self.read_ushort()
|
||||
names = {1:'',2:'',3:'',4:'',6:''}
|
||||
K = names.keys()
|
||||
K = list(names.keys())
|
||||
nameCount = len(names)
|
||||
for i in range(numRecords):
|
||||
platformId = self.read_ushort()
|
||||
@@ -242,7 +244,7 @@ class TTFontFile:
|
||||
self.seek(string_data_offset + offset)
|
||||
if (length % 2 != 0):
|
||||
die("PostScript name is UTF-16BE string of odd length")
|
||||
length /= 2
|
||||
length //= 2
|
||||
N = ''
|
||||
while (length > 0):
|
||||
char = self.read_ushort()
|
||||
@@ -253,7 +255,7 @@ class TTFontFile:
|
||||
|
||||
elif (platformId == 1 and encodingId == 0 and languageId == 0): # Macintosh, Roman, English, PS Name
|
||||
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.seek(opos)
|
||||
|
||||
@@ -455,346 +457,344 @@ class TTFontFile:
|
||||
|
||||
def makeSubset(self, file, subset):
|
||||
self.filename = file
|
||||
self.fh = open(file ,'rb')
|
||||
self._pos = 0
|
||||
self.charWidths = []
|
||||
self.glyphPos = {}
|
||||
self.charToGlyph = {}
|
||||
self.tables = {}
|
||||
self.otables = {}
|
||||
self.ascent = 0
|
||||
self.descent = 0
|
||||
self.skip(4)
|
||||
self.maxUni = 0
|
||||
self.readTableDirectory()
|
||||
with open(file ,'rb') as self.fh:
|
||||
self._pos = 0
|
||||
self.charWidths = []
|
||||
self.glyphPos = {}
|
||||
self.charToGlyph = {}
|
||||
self.tables = {}
|
||||
self.otables = {}
|
||||
self.ascent = 0
|
||||
self.descent = 0
|
||||
self.skip(4)
|
||||
self.maxUni = 0
|
||||
self.readTableDirectory()
|
||||
|
||||
#################/
|
||||
# head - Font header table
|
||||
#################/
|
||||
self.seek_table("head")
|
||||
self.skip(50)
|
||||
indexToLocFormat = self.read_ushort()
|
||||
glyphDataFormat = self.read_ushort()
|
||||
#################/
|
||||
# head - Font header table
|
||||
#################/
|
||||
self.seek_table("head")
|
||||
self.skip(50)
|
||||
indexToLocFormat = self.read_ushort()
|
||||
glyphDataFormat = self.read_ushort()
|
||||
|
||||
#################/
|
||||
# hhea - Horizontal header table
|
||||
#################/
|
||||
self.seek_table("hhea")
|
||||
self.skip(32)
|
||||
metricDataFormat = self.read_ushort()
|
||||
orignHmetrics = numberOfHMetrics = self.read_ushort()
|
||||
#################/
|
||||
# hhea - Horizontal header table
|
||||
#################/
|
||||
self.seek_table("hhea")
|
||||
self.skip(32)
|
||||
metricDataFormat = self.read_ushort()
|
||||
orignHmetrics = numberOfHMetrics = self.read_ushort()
|
||||
|
||||
#################/
|
||||
# maxp - Maximum profile table
|
||||
#################/
|
||||
self.seek_table("maxp")
|
||||
self.skip(4)
|
||||
numGlyphs = self.read_ushort()
|
||||
#################/
|
||||
# maxp - Maximum profile table
|
||||
#################/
|
||||
self.seek_table("maxp")
|
||||
self.skip(4)
|
||||
numGlyphs = self.read_ushort()
|
||||
|
||||
#################/
|
||||
# cmap - Character to glyph index mapping table
|
||||
#################/
|
||||
cmap_offset = self.seek_table("cmap")
|
||||
self.skip(2)
|
||||
cmapTableCount = self.read_ushort()
|
||||
unicode_cmap_offset = 0
|
||||
unicode_cmap_offset12 = 0
|
||||
for i in range(cmapTableCount):
|
||||
platformID = self.read_ushort()
|
||||
encodingID = self.read_ushort()
|
||||
offset = self.read_ulong()
|
||||
save_pos = self._pos
|
||||
if platformID == 3 and encodingID == 10: # Microsoft, UCS-4
|
||||
format = self.get_ushort(cmap_offset + offset)
|
||||
if (format == 12):
|
||||
if not unicode_cmap_offset12:
|
||||
unicode_cmap_offset12 = cmap_offset + offset
|
||||
break
|
||||
if ((platformID == 3 and encodingID == 1) or platformID == 0): # Microsoft, Unicode
|
||||
format = self.get_ushort(cmap_offset + offset)
|
||||
if (format == 4):
|
||||
unicode_cmap_offset = cmap_offset + offset
|
||||
break
|
||||
#################/
|
||||
# cmap - Character to glyph index mapping table
|
||||
#################/
|
||||
cmap_offset = self.seek_table("cmap")
|
||||
self.skip(2)
|
||||
cmapTableCount = self.read_ushort()
|
||||
unicode_cmap_offset = 0
|
||||
unicode_cmap_offset12 = 0
|
||||
for i in range(cmapTableCount):
|
||||
platformID = self.read_ushort()
|
||||
encodingID = self.read_ushort()
|
||||
offset = self.read_ulong()
|
||||
save_pos = self._pos
|
||||
if platformID == 3 and encodingID == 10: # Microsoft, UCS-4
|
||||
format = self.get_ushort(cmap_offset + offset)
|
||||
if (format == 12):
|
||||
if not unicode_cmap_offset12:
|
||||
unicode_cmap_offset12 = cmap_offset + offset
|
||||
break
|
||||
if ((platformID == 3 and encodingID == 1) or platformID == 0): # Microsoft, Unicode
|
||||
format = self.get_ushort(cmap_offset + offset)
|
||||
if (format == 4):
|
||||
unicode_cmap_offset = cmap_offset + offset
|
||||
break
|
||||
|
||||
self.seek(save_pos )
|
||||
|
||||
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)')
|
||||
self.seek(save_pos )
|
||||
|
||||
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)')
|
||||
|
||||
glyphToChar = {}
|
||||
charToGlyph = {}
|
||||
if unicode_cmap_offset12:
|
||||
self.getCMAP12(unicode_cmap_offset12, glyphToChar, charToGlyph)
|
||||
else:
|
||||
self.getCMAP4(unicode_cmap_offset, glyphToChar, charToGlyph)
|
||||
glyphToChar = {}
|
||||
charToGlyph = {}
|
||||
if unicode_cmap_offset12:
|
||||
self.getCMAP12(unicode_cmap_offset12, glyphToChar, charToGlyph)
|
||||
else:
|
||||
self.getCMAP4(unicode_cmap_offset, glyphToChar, charToGlyph)
|
||||
|
||||
self.charToGlyph = charToGlyph
|
||||
self.charToGlyph = charToGlyph
|
||||
|
||||
#################/
|
||||
# hmtx - Horizontal metrics table
|
||||
#################/
|
||||
scale = 1 # not used
|
||||
self.getHMTX(numberOfHMetrics, numGlyphs, glyphToChar, scale)
|
||||
#################/
|
||||
# hmtx - Horizontal metrics table
|
||||
#################/
|
||||
scale = 1 # not used
|
||||
self.getHMTX(numberOfHMetrics, numGlyphs, glyphToChar, scale)
|
||||
|
||||
#################/
|
||||
# loca - Index to location
|
||||
#################/
|
||||
self.getLOCA(indexToLocFormat, numGlyphs)
|
||||
#################/
|
||||
# loca - Index to location
|
||||
#################/
|
||||
self.getLOCA(indexToLocFormat, numGlyphs)
|
||||
|
||||
subsetglyphs = [(0, 0)] # special "sorted dict"!
|
||||
subsetCharToGlyph = {}
|
||||
for code in subset:
|
||||
if (code in self.charToGlyph):
|
||||
if (self.charToGlyph[code], code) not in subsetglyphs:
|
||||
subsetglyphs.append((self.charToGlyph[code], code)) # Old Glyph ID => Unicode
|
||||
subsetCharToGlyph[code] = self.charToGlyph[code] # Unicode to old GlyphID
|
||||
self.maxUni = max(self.maxUni, code)
|
||||
(start,dummy) = self.get_table_pos('glyf')
|
||||
subsetglyphs = [(0, 0)] # special "sorted dict"!
|
||||
subsetCharToGlyph = {}
|
||||
for code in subset:
|
||||
if (code in self.charToGlyph):
|
||||
if (self.charToGlyph[code], code) not in subsetglyphs:
|
||||
subsetglyphs.append((self.charToGlyph[code], code)) # Old Glyph ID => Unicode
|
||||
subsetCharToGlyph[code] = self.charToGlyph[code] # Unicode to old GlyphID
|
||||
self.maxUni = max(self.maxUni, code)
|
||||
(start,dummy) = self.get_table_pos('glyf')
|
||||
|
||||
subsetglyphs.sort()
|
||||
glyphSet = {}
|
||||
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.
|
||||
for originalGlyphIdx, uni in subsetglyphs:
|
||||
fsLastCharIndex = max(fsLastCharIndex , uni)
|
||||
glyphSet[originalGlyphIdx] = n # old glyphID to new glyphID
|
||||
n += 1
|
||||
subsetglyphs.sort()
|
||||
glyphSet = {}
|
||||
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.
|
||||
for originalGlyphIdx, uni in subsetglyphs:
|
||||
fsLastCharIndex = max(fsLastCharIndex , uni)
|
||||
glyphSet[originalGlyphIdx] = n # old glyphID to new glyphID
|
||||
n += 1
|
||||
|
||||
codeToGlyph = {}
|
||||
for uni, originalGlyphIdx in sorted(subsetCharToGlyph.items()):
|
||||
codeToGlyph[uni] = glyphSet[originalGlyphIdx]
|
||||
|
||||
self.codeToGlyph = codeToGlyph
|
||||
|
||||
for originalGlyphIdx, uni in subsetglyphs:
|
||||
nonlocals = {'start': start, 'glyphSet': glyphSet,
|
||||
'subsetglyphs': subsetglyphs}
|
||||
self.getGlyphs(originalGlyphIdx, nonlocals)
|
||||
codeToGlyph = {}
|
||||
for uni, originalGlyphIdx in sorted(subsetCharToGlyph.items()):
|
||||
codeToGlyph[uni] = glyphSet[originalGlyphIdx]
|
||||
|
||||
self.codeToGlyph = codeToGlyph
|
||||
|
||||
for originalGlyphIdx, uni in subsetglyphs:
|
||||
nonlocals = {'start': start, 'glyphSet': glyphSet,
|
||||
'subsetglyphs': subsetglyphs}
|
||||
self.getGlyphs(originalGlyphIdx, nonlocals)
|
||||
|
||||
numGlyphs = numberOfHMetrics = len(subsetglyphs)
|
||||
numGlyphs = numberOfHMetrics = len(subsetglyphs)
|
||||
|
||||
#tables copied from the original
|
||||
tags = ['name']
|
||||
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))
|
||||
#tables copied from the original
|
||||
tags = ['name']
|
||||
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))
|
||||
|
||||
# post - PostScript
|
||||
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"
|
||||
self.add('post', post)
|
||||
# post - PostScript
|
||||
opost = self.get_table('post')
|
||||
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)
|
||||
|
||||
# Sort CID2GID map into segments of contiguous codes
|
||||
if 0 in codeToGlyph:
|
||||
del codeToGlyph[0]
|
||||
#unset(codeToGlyph[65535])
|
||||
rangeid = 0
|
||||
range_ = {}
|
||||
prevcid = -2
|
||||
prevglidx = -1
|
||||
# for each character
|
||||
for cid, glidx in sorted(codeToGlyph.items()):
|
||||
if (cid == (prevcid + 1) and glidx == (prevglidx + 1)):
|
||||
range_[rangeid].append(glidx)
|
||||
else:
|
||||
# new range
|
||||
rangeid = cid
|
||||
range_[rangeid] = []
|
||||
range_[rangeid].append(glidx)
|
||||
prevcid = cid
|
||||
prevglidx = glidx
|
||||
# Sort CID2GID map into segments of contiguous codes
|
||||
if 0 in codeToGlyph:
|
||||
del codeToGlyph[0]
|
||||
#unset(codeToGlyph[65535])
|
||||
rangeid = 0
|
||||
range_ = {}
|
||||
prevcid = -2
|
||||
prevglidx = -1
|
||||
# for each character
|
||||
for cid, glidx in sorted(codeToGlyph.items()):
|
||||
if (cid == (prevcid + 1) and glidx == (prevglidx + 1)):
|
||||
range_[rangeid].append(glidx)
|
||||
else:
|
||||
# new range
|
||||
rangeid = cid
|
||||
range_[rangeid] = []
|
||||
range_[rangeid].append(glidx)
|
||||
prevcid = cid
|
||||
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 ):
|
||||
# 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
|
||||
|
||||
searchRange = searchRange * 2
|
||||
entrySelector = entrySelector + 1
|
||||
|
||||
searchRange = searchRange * 2
|
||||
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]
|
||||
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)
|
||||
|
||||
cmap.append(0xFFFF) # endCode of last Segment
|
||||
cmap.append(0) # reservedPad
|
||||
range_ = sorted(range_.items())
|
||||
|
||||
# endCode(s)
|
||||
for start, subrange in range_:
|
||||
endCode = start + (len(subrange)-1)
|
||||
cmap.append(endCode) # endCode(s)
|
||||
|
||||
cmap.append(0xFFFF) # endCode of last Segment
|
||||
cmap.append(0) # reservedPad
|
||||
|
||||
# startCode(s)
|
||||
for start, subrange in range_:
|
||||
cmap.append(start) # startCode(s)
|
||||
|
||||
cmap.append(0xFFFF) # startCode of last Segment
|
||||
# idDelta(s)
|
||||
for start, subrange in range_:
|
||||
idDelta = -(start-subrange[0])
|
||||
n += count(subrange)
|
||||
cmap.append(idDelta) # idDelta(s)
|
||||
|
||||
cmap.append(1) # idDelta of last Segment
|
||||
# idRangeOffset(s)
|
||||
for subrange in range_:
|
||||
cmap.append(0) # idRangeOffset[segCount] Offset in bytes to glyph indexArray, or 0
|
||||
|
||||
cmap.append(0) # idRangeOffset of last Segment
|
||||
for subrange, glidx in range_:
|
||||
cmap.extend(glidx)
|
||||
|
||||
cmap.append(0) # Mapping for last character
|
||||
cmapstr = ''
|
||||
for cm in cmap:
|
||||
if cm >= 0:
|
||||
cmapstr += pack(">H", cm)
|
||||
else:
|
||||
# startCode(s)
|
||||
for start, subrange in range_:
|
||||
cmap.append(start) # startCode(s)
|
||||
|
||||
cmap.append(0xFFFF) # startCode of last Segment
|
||||
# idDelta(s)
|
||||
for start, subrange in range_:
|
||||
idDelta = -(start-subrange[0])
|
||||
n += count(subrange)
|
||||
cmap.append(idDelta) # idDelta(s)
|
||||
|
||||
cmap.append(1) # idDelta of last Segment
|
||||
# idRangeOffset(s)
|
||||
for subrange in range_:
|
||||
cmap.append(0) # idRangeOffset[segCount] Offset in bytes to glyph indexArray, or 0
|
||||
|
||||
cmap.append(0) # idRangeOffset of last Segment
|
||||
for subrange, glidx in range_:
|
||||
cmap.extend(glidx)
|
||||
|
||||
cmap.append(0) # Mapping for last character
|
||||
cmapstr = b('')
|
||||
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)
|
||||
|
||||
# glyf - Glyph data
|
||||
(glyfOffset,glyfLength) = self.get_table_pos('glyf')
|
||||
if (glyfLength < self.maxStrLenRead):
|
||||
glyphData = self.get_table('glyf')
|
||||
|
||||
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:
|
||||
cmapstr += pack(">h", cm)
|
||||
except:
|
||||
warnings.warn("cmap value too big/small: %s" % cm)
|
||||
cmapstr += pack(">H", -cm)
|
||||
self.add('cmap', cmapstr)
|
||||
glyphPos = self.glyphPos[originalGlyphIdx]
|
||||
glyphLen = self.glyphPos[originalGlyphIdx + 1] - glyphPos
|
||||
except IndexError:
|
||||
warnings.warn("missing glyph %s" % (originalGlyphIdx))
|
||||
glyphLen = 0
|
||||
|
||||
# glyf - Glyph data
|
||||
(glyfOffset,glyfLength) = self.get_table_pos('glyf')
|
||||
if (glyfLength < self.maxStrLenRead):
|
||||
glyphData = self.get_table('glyf')
|
||||
|
||||
offsets = []
|
||||
glyf = ''
|
||||
pos = 0
|
||||
|
||||
hmtxstr = ''
|
||||
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
|
||||
if (glyfLength < self.maxStrLenRead):
|
||||
data = substr(glyphData,glyphPos,glyphLen)
|
||||
else:
|
||||
if (glyphLen > 0):
|
||||
data = self.get_chunk(glyfOffset+glyphPos,glyphLen)
|
||||
else:
|
||||
data = b('')
|
||||
|
||||
if (glyphLen > 0):
|
||||
up = unpack(">H", substr(data,0,2))[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
|
||||
|
||||
maxComponentElements = max(maxComponentElements, nComponentElements)
|
||||
|
||||
glyf += data
|
||||
pos += glyphLen
|
||||
if (pos % 4 != 0):
|
||||
padding = 4 - (pos % 4)
|
||||
glyf += str_repeat(b("\0"),padding)
|
||||
pos += padding
|
||||
|
||||
offsets.append(pos)
|
||||
try:
|
||||
glyphPos = self.glyphPos[originalGlyphIdx]
|
||||
glyphLen = self.glyphPos[originalGlyphIdx + 1] - glyphPos
|
||||
except IndexError:
|
||||
warnings.warn("missing glyph %s" % (originalGlyphIdx))
|
||||
glyphLen = 0
|
||||
self.add('glyf', glyf)
|
||||
|
||||
if (glyfLength < self.maxStrLenRead):
|
||||
data = substr(glyphData,glyphPos,glyphLen)
|
||||
# hmtx - Horizontal Metrics
|
||||
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:
|
||||
if (glyphLen > 0):
|
||||
data = self.get_chunk(glyfOffset+glyphPos,glyphLen)
|
||||
else:
|
||||
data = ''
|
||||
indexToLocFormat = 0 # short format
|
||||
for offset in offsets:
|
||||
locastr += pack(">H",offset//2)
|
||||
|
||||
if (glyphLen > 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
|
||||
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)
|
||||
|
||||
glyf += data
|
||||
pos += glyphLen
|
||||
if (pos % 4 != 0):
|
||||
padding = 4 - (pos % 4)
|
||||
glyf += str_repeat("\0",padding)
|
||||
pos += padding
|
||||
self.add('loca', locastr)
|
||||
|
||||
offsets.append(pos)
|
||||
self.add('glyf', glyf)
|
||||
# head - Font header
|
||||
head = self.get_table('head')
|
||||
head = self._set_ushort(head, 50, indexToLocFormat)
|
||||
self.add('head', head)
|
||||
|
||||
# hmtx - Horizontal Metrics
|
||||
self.add('hmtx', hmtxstr)
|
||||
# hhea - Horizontal Header
|
||||
hhea = self.get_table('hhea')
|
||||
hhea = self._set_ushort(hhea, 34, numberOfHMetrics)
|
||||
self.add('hhea', hhea)
|
||||
|
||||
# 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)
|
||||
# maxp - Maximum Profile
|
||||
maxp = self.get_table('maxp')
|
||||
maxp = self._set_ushort(maxp, 4, numGlyphs)
|
||||
self.add('maxp', maxp)
|
||||
|
||||
# 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()
|
||||
# OS/2 - OS/2
|
||||
os2 = self.get_table('OS/2')
|
||||
self.add('OS/2', os2 )
|
||||
|
||||
# Put the TTF file together
|
||||
stm = self.endTTFile('')
|
||||
@@ -864,11 +864,16 @@ class TTFontFile:
|
||||
def getHMTX(self, numberOfHMetrics, numGlyphs, glyphToChar, scale):
|
||||
start = self.seek_table("hmtx")
|
||||
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
|
||||
if ((numberOfHMetrics*4) < self.maxStrLenRead):
|
||||
data = self.get_chunk(start,(numberOfHMetrics*4))
|
||||
arr = unpack(">" + "H" * (len(data)/2), data)
|
||||
arr = unpack(">%dH" % (len(data)//2), data)
|
||||
else:
|
||||
self.seek(start)
|
||||
for glyph in range(numberOfHMetrics):
|
||||
@@ -886,26 +891,30 @@ class TTFontFile:
|
||||
self.defaultWidth = scale*aw
|
||||
continue
|
||||
|
||||
for char in glyphToChar[glyph]:
|
||||
for char in glyphToChar[glyph]:
|
||||
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 (char < 196608):
|
||||
if (char < 196608):
|
||||
if char >= len(self.charWidths):
|
||||
resize_cw(char, self.defaultWidth)
|
||||
self.charWidths[char] = w
|
||||
nCharWidths += 1
|
||||
|
||||
|
||||
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
|
||||
for pos in range(diff):
|
||||
glyph = pos + numberOfHMetrics
|
||||
if (glyph in glyphToChar):
|
||||
for char in glyphToChar[glyph]:
|
||||
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 (char < 196608):
|
||||
if char >= len(self.charWidths):
|
||||
resize_cw(char, self.defaultWidth)
|
||||
self.charWidths[char] = w
|
||||
nCharWidths += 1
|
||||
|
||||
@@ -933,12 +942,12 @@ class TTFontFile:
|
||||
self.glyphPos = []
|
||||
if (indexToLocFormat == 0):
|
||||
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):
|
||||
self.glyphPos.append((arr[n] * 2)) # n+1 !?
|
||||
elif (indexToLocFormat == 1):
|
||||
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):
|
||||
self.glyphPos.append((arr[n])) # n+1 !?
|
||||
else:
|
||||
@@ -952,7 +961,7 @@ class TTFontFile:
|
||||
limit = unicode_cmap_offset + length
|
||||
self.skip(2)
|
||||
|
||||
segCount = self.read_ushort() / 2
|
||||
segCount = self.read_ushort() // 2
|
||||
self.skip(6)
|
||||
endCount = []
|
||||
for i in range(segCount):
|
||||
@@ -1020,7 +1029,7 @@ class TTFontFile:
|
||||
|
||||
# Put the TTF file together
|
||||
def endTTFile(self, stm):
|
||||
stm = ''
|
||||
stm = b('')
|
||||
numTables = count(self.otables)
|
||||
searchRange = 1
|
||||
entrySelector = 0
|
||||
@@ -1046,7 +1055,7 @@ class TTFontFile:
|
||||
for tag, data in sorted_tables:
|
||||
if (tag == 'head'):
|
||||
head_start = offset
|
||||
stm += tag
|
||||
stm += tag.encode("latin1")
|
||||
checksum = calcChecksum(data)
|
||||
stm += pack(">HH", checksum[0],checksum[1])
|
||||
stm += pack(">LL", offset, strlen(data))
|
||||
@@ -1055,7 +1064,7 @@ class TTFontFile:
|
||||
|
||||
# Table data
|
||||
for tag, data in sorted_tables:
|
||||
data += "\0\0\0"
|
||||
data += b("\0\0\0")
|
||||
stm += substr(data,0,(strlen(data)&~3))
|
||||
|
||||
checksum = calcChecksum(stm)
|
||||
@@ -1064,20 +1073,3 @@ class TTFontFile:
|
||||
stm = self.splice(stm,(head_start + 8),chk)
|
||||
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()
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
# Read more about this http://code.google.com/p/pyfpdf
|
||||
# Please note that new package name is fpdf (to avoid some naming conflicts)
|
||||
# import fpdf into pyfpdf for backward compatibility (prior web2py 2.0):
|
||||
from fpdf import *
|
||||
from gluon.contrib.fpdf import *
|
||||
|
||||
# import warnings
|
||||
# warnings.warn("pyfpdf package name is deprecated, please use fpdf instead")
|
||||
|
||||
@@ -10,6 +10,7 @@ from .test_dal import *
|
||||
from .test_cache import *
|
||||
from .test_template import *
|
||||
from .test_html import *
|
||||
from .test_contribs import *
|
||||
|
||||
if sys.version[:3] == '2.7':
|
||||
from .test_compileapp import *
|
||||
@@ -24,5 +25,4 @@ if sys.version[:3] == '2.7':
|
||||
from .test_appadmin import *
|
||||
from .test_scheduler import *
|
||||
from .test_web import *
|
||||
from .test_contribs import *
|
||||
from .test_old_doctests import *
|
||||
|
||||
@@ -8,7 +8,7 @@ import os
|
||||
from .fix_path import fix_sys_path
|
||||
|
||||
fix_sys_path(__file__)
|
||||
|
||||
from gluon._compat import to_bytes
|
||||
from gluon.storage import Storage
|
||||
from gluon.contrib import fpdf as fpdf
|
||||
from gluon.contrib import pyfpdf as pyfpdf
|
||||
@@ -42,8 +42,8 @@ class TestContribs(unittest.TestCase):
|
||||
pdf.write(5, 'hello world')
|
||||
pdf_out = pdf.output('', 'S')
|
||||
|
||||
self.assertTrue(fpdf.FPDF_VERSION in pdf_out, 'version string')
|
||||
self.assertTrue('hello world' in pdf_out, 'sample message')
|
||||
self.assertTrue(to_bytes(fpdf.FPDF_VERSION) in pdf_out, 'version string')
|
||||
self.assertTrue(to_bytes('hello world') in pdf_out, 'sample message')
|
||||
|
||||
def test_appconfig(self):
|
||||
"""
|
||||
|
||||
Reference in New Issue
Block a user