From 6bfd3ea7c593ca72d015a90ff3328bcbc1d016d1 Mon Sep 17 00:00:00 2001
From: mdipierro
Date: Thu, 23 Aug 2012 08:31:04 -0500
Subject: [PATCH 01/53] new pyfpdf, thanks Mariano
---
VERSION | 2 +-
gluon/contrib/pyfpdf/README | 1 -
gluon/contrib/pyfpdf/__init__.py | 19 +-
gluon/contrib/pyfpdf/designer.py | 736 --------------------
gluon/contrib/pyfpdf/fonts.py | 156 +++++
gluon/contrib/pyfpdf/fpdf.py | 1098 ++++++++++++++++++------------
gluon/contrib/pyfpdf/html.py | 127 +---
gluon/contrib/pyfpdf/php.py | 49 ++
gluon/contrib/pyfpdf/template.py | 107 +--
gluon/contrib/pyfpdf/ttfonts.py | 1033 ++++++++++++++++++++++++++++
10 files changed, 1997 insertions(+), 1331 deletions(-)
delete mode 100644 gluon/contrib/pyfpdf/README
delete mode 100644 gluon/contrib/pyfpdf/designer.py
create mode 100644 gluon/contrib/pyfpdf/fonts.py
create mode 100644 gluon/contrib/pyfpdf/php.py
mode change 100755 => 100644 gluon/contrib/pyfpdf/template.py
create mode 100644 gluon/contrib/pyfpdf/ttfonts.py
diff --git a/VERSION b/VERSION
index 88809e6c..775477a3 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-Version 2.00.0 (2012-08-22 19:47:08) dev
+Version 2.00.0 (2012-08-23 08:31:01) dev
diff --git a/gluon/contrib/pyfpdf/README b/gluon/contrib/pyfpdf/README
deleted file mode 100644
index 88916d70..00000000
--- a/gluon/contrib/pyfpdf/README
+++ /dev/null
@@ -1 +0,0 @@
-Read more about this http://code.google.com/p/pyfpdf
diff --git a/gluon/contrib/pyfpdf/__init__.py b/gluon/contrib/pyfpdf/__init__.py
index 7c4a2ca3..3ba17860 100644
--- a/gluon/contrib/pyfpdf/__init__.py
+++ b/gluon/contrib/pyfpdf/__init__.py
@@ -1,5 +1,16 @@
-from fpdf import FPDF
-from html import HTMLMixin
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+"FPDF for python"
+
+__license__ = "LGPL 3.0"
+__version__ = "1.7"
+
+from fpdf import *
+try:
+ from html import HTMLMixin
+except ImportError:
+ import warnings
+ warnings.warn("web2py gluon package not installed, required for html2pdf")
+
from template import Template
-
-
diff --git a/gluon/contrib/pyfpdf/designer.py b/gluon/contrib/pyfpdf/designer.py
deleted file mode 100644
index 3807bd6d..00000000
--- a/gluon/contrib/pyfpdf/designer.py
+++ /dev/null
@@ -1,736 +0,0 @@
-#!/usr/bin/python
-# -*- coding: latin-1 -*-
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by the
-# Free Software Foundation; either version 3, or (at your option) any later
-# version.
-#
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTIBILITY
-# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
-# for more details.
-
-"Visual Template designer for PyFPDF (using wxPython OGL library)"
-
-__author__ = "Mariano Reingart "
-__copyright__ = "Copyright (C) 2011 Mariano Reingart"
-__license__ = "GPL 3.0"
-__version__ = "1.01a"
-
-# Based on:
-# * pySjetch.py wxPython sample application
-# * OGL.py and other wxPython demo modules
-
-
-import os, sys
-import wx
-import wx.lib.ogl as ogl
-from wx.lib.wordwrap import wordwrap
-
-DEBUG = True
-
-
-class CustomDialog(wx.Dialog):
- "A dinamyc dialog to ask user about arbitrary fields"
-
- def __init__(
- self, parent, ID, title, size=wx.DefaultSize, pos=wx.DefaultPosition,
- style=wx.DEFAULT_DIALOG_STYLE, fields=None, data=None,
- ):
-
- wx.Dialog.__init__ (self, parent, ID, title, pos, size, style)
-
- sizer = wx.BoxSizer(wx.VERTICAL)
-
- self.textctrls = {}
- for field in fields:
- box = wx.BoxSizer(wx.HORIZONTAL)
- label = wx.StaticText(self, -1, field)
- label.SetHelpText("This is the help text for the label")
- box.Add(label, 1, wx.ALIGN_CENTRE|wx.ALL, 5)
- text = wx.TextCtrl(self, -1, "", size=(80,-1))
- text.SetHelpText("Here's some help text for field #1")
- if field in data:
- text.SetValue(repr(data[field]))
- box.Add(text, 1, wx.ALIGN_CENTRE|wx.ALL, 1)
- sizer.Add(box, 0, wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.ALL, 1)
- self.textctrls[field] = text
-
- line = wx.StaticLine(self, -1, size=(20,-1), style=wx.LI_HORIZONTAL)
- sizer.Add(line, 0, wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.RIGHT|wx.TOP, 5)
-
- btnsizer = wx.StdDialogButtonSizer()
-
- btn = wx.Button(self, wx.ID_OK)
- btn.SetHelpText("The OK button completes the dialog")
- btn.SetDefault()
- btnsizer.AddButton(btn)
-
- btn = wx.Button(self, wx.ID_CANCEL)
- btn.SetHelpText("The Cancel button cancels the dialog. (Cool, huh?)")
- btnsizer.AddButton(btn)
- btnsizer.Realize()
-
- sizer.Add(btnsizer, 0, wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5)
-
- self.SetSizer(sizer)
- sizer.Fit(self)
-
- @classmethod
- def do_input(Class, parent, title, fields, data):
- dlg = Class(parent, -1, title, size=(350, 200),
- style=wx.DEFAULT_DIALOG_STYLE, # & ~wx.CLOSE_BOX,
- fields=fields, data=data
- )
- dlg.CenterOnScreen()
- while 1:
- val = dlg.ShowModal()
- if val == wx.ID_OK:
- values = {}
- for field in fields:
- try:
- values[field] = eval(dlg.textctrls[field].GetValue())
- except Exception, e:
- msg = wx.MessageDialog(parent, unicode(e),
- "Error in field %s" % field,
- wx.OK | wx.ICON_INFORMATION
- )
- msg.ShowModal()
- msg.Destroy()
- break
- else:
- return dict([(field, values[field]) for field in fields])
- else:
- return None
-
-
-class MyEvtHandler(ogl.ShapeEvtHandler):
- "Custom Event Handler for Shapes"
- def __init__(self, callback):
- ogl.ShapeEvtHandler.__init__(self)
- self.callback = callback
-
- def OnLeftClick(self, x, y, keys=0, attachment=0):
- shape = self.GetShape()
- canvas = shape.GetCanvas()
- dc = wx.ClientDC(canvas)
- canvas.PrepareDC(dc)
-
- if shape.Selected() and keys & ogl.KEY_SHIFT:
- shape.Select(False, dc)
- #canvas.Redraw(dc)
- canvas.Refresh(False)
- else:
- redraw = False
- shapeList = canvas.GetDiagram().GetShapeList()
- toUnselect = []
-
- for s in shapeList:
- if s.Selected() and not keys & ogl.KEY_SHIFT:
- # If we unselect it now then some of the objects in
- # shapeList will become invalid (the control points are
- # shapes too!) and bad things will happen...
- toUnselect.append(s)
-
- shape.Select(True, dc)
-
- if toUnselect:
- for s in toUnselect:
- s.Select(False, dc)
- ##canvas.Redraw(dc)
- canvas.Refresh(False)
-
- self.callback()
-
- def OnEndDragLeft(self, x, y, keys=0, attachment=0):
- shape = self.GetShape()
- ogl.ShapeEvtHandler.OnEndDragLeft(self, x, y, keys, attachment)
-
- if not shape.Selected():
- self.OnLeftClick(x, y, keys, attachment)
-
- self.callback()
-
- def OnSizingEndDragLeft(self, pt, x, y, keys, attch):
- ogl.ShapeEvtHandler.OnSizingEndDragLeft(self, pt, x, y, keys, attch)
- self.callback()
-
- def OnMovePost(self, dc, x, y, oldX, oldY, display):
- shape = self.GetShape()
- ogl.ShapeEvtHandler.OnMovePost(self, dc, x, y, oldX, oldY, display)
- self.callback()
- if "wxMac" in wx.PlatformInfo:
- shape.GetCanvas().Refresh(False)
-
- def OnLeftDoubleClick(self, x, y, keys = 0, attachment = 0):
- self.callback("LeftDoubleClick")
-
- def OnRightClick(self, *dontcare):
- self.callback("RightClick")
-
-
-class Element(object):
- "Visual class that represent a placeholder in the template"
-
- fields = ['name', 'type',
- 'x1', 'y1', 'x2', 'y2',
- 'font', 'size',
- 'bold', 'italic', 'underline',
- 'foreground', 'background',
- 'align', 'text', 'priority',]
-
- def __init__(self, canvas=None, frame=None, zoom=5.0, static=False, **kwargs):
- self.kwargs = kwargs
- self.zoom = zoom
- self.frame = frame
- self.canvas = canvas
- self.static = static
-
- name = kwargs['name']
- kwargs['type']
- type = kwargs['type']
-
- x, y, w, h = self.set_coordinates(kwargs['x1'], kwargs['y1'], kwargs['x2'], kwargs['y2'])
-
- text = kwargs['text']
-
- shape = self.shape = ogl.RectangleShape(w, h)
-
- if not static:
- shape.SetDraggable(True, True)
-
- shape.SetX(x)
- shape.SetY(y)
- #if pen: shape.SetPen(pen)
- #if brush: shape.SetBrush(brush)
- shape.SetBrush(wx.TRANSPARENT_BRUSH)
-
- if type not in ('L', 'B', 'BC'):
- if not static:
- pen = wx.LIGHT_GREY_PEN
- else:
- pen = wx.RED_PEN
- shape.SetPen(pen)
-
- self.text = kwargs['text']
-
- evthandler = MyEvtHandler(self.evt_callback)
- evthandler.SetShape(shape)
- evthandler.SetPreviousHandler(shape.GetEventHandler())
- shape.SetEventHandler(evthandler)
- shape.SetCentreResize(False)
- shape.SetMaintainAspectRatio(False)
-
- canvas.AddShape( shape )
-
- @classmethod
- def new(Class, parent):
- data = dict(name='some_name', type='T',
- x1=5.0, y1=5.0, x2=100.0, y2=10.0,
- font="Arial", size=12,
- bold=False, italic=False, underline=False,
- foreground= 0x000000, background=0xFFFFFF,
- align="L", text="", priority=0)
- data = CustomDialog.do_input(parent, 'New element', Class.fields, data)
- if data:
- return Class(canvas=parent.canvas, frame=parent, **data)
-
- def edit(self):
- "Edit current element (show a dialog box with all fields)"
- data = self.kwargs.copy()
- x1, y1, x2, y2 = self.get_coordinates()
- data.update(dict(name=self.name,
- text=self.text,
- x1=x1, y1=y1, x2=x2, y2=y2,
- ))
- data = CustomDialog.do_input(self.frame, 'Edit element', self.fields, data)
- if data:
- self.kwargs.update(data)
- self.name = data['name']
- self.text = data['text']
- x,y, w, h = self.set_coordinates(data['x1'], data['y1'], data['x2'], data['y2'])
- self.shape.SetX(x)
- self.shape.SetY(y)
- self.shape.SetWidth(w)
- self.shape.SetHeight(h)
- self.canvas.Refresh(False)
- self.canvas.GetDiagram().ShowAll(1)
-
- def edit_text(self):
- "Allow text edition (i.e. for doubleclick)"
- dlg = wx.TextEntryDialog(
- self.frame, 'Text for %s' % self.name,
- 'Edit Text', '')
- if self.text:
- dlg.SetValue(self.text)
- if dlg.ShowModal() == wx.ID_OK:
- self.text = dlg.GetValue().encode("latin1")
- dlg.Destroy()
-
- def copy(self):
- "Return an identical duplicate"
- kwargs = self.as_dict()
- element = Element(canvas=self.canvas, frame=self.frame, zoom=self.zoom, static=self.static, **kwargs)
- return element
-
- def remove(self):
- "Erases visual shape from OGL canvas (element must be deleted manually)"
- self.canvas.RemoveShape(self.shape)
-
- def move(self, dx, dy):
- "Change pdf coordinates (converting to wx internal values)"
- x1, y1, x2, y2 = self.get_coordinates()
- x1 += dx
- x2 += dx
- y1 += dy
- y2 += dy
- x, y, w, h = self.set_coordinates(x1, y1, x2, y2)
- self.shape.SetX(x)
- self.shape.SetY(y)
-
- def evt_callback(self, evt_type=None):
- "Event dispatcher"
- if evt_type=="LeftDoubleClick":
- self.edit_text()
- if evt_type=='RightClick':
- self.edit()
-
- # update the status bar
- x1, y1, x2, y2 = self.get_coordinates()
- self.frame.SetStatusText("%s (%0.2f, %0.2f) - (%0.2f, %0.2f)" %
- (self.name, x1, y1, x2, y2))
-
- def get_coordinates(self):
- "Convert from wx to pdf coordinates"
- x, y = self.shape.GetX(), self.shape.GetY()
- w, h = self.shape.GetBoundingBoxMax()
- w -= 1
- h -= 1
- x1 = x/self.zoom - w/self.zoom/2.0
- x2 = x/self.zoom + w/self.zoom/2.0
- y1 = y/self.zoom - h/self.zoom/2.0
- y2 = y/self.zoom + h/self.zoom/2.0
- return x1, y1, x2, y2
-
- def set_coordinates(self, x1, y1, x2, y2):
- "Convert from pdf to wx coordinates"
- x1 = x1 * self.zoom
- x2 = x2 * self.zoom
- y1 = y1 * self.zoom
- y2 = y2 * self.zoom
-
- # shapes seems to be centred, pdf coord not
- w = max(x1, x2) - min(x1, x2) + 1
- h = max(y1, y2) - min(y1, y2) + 1
- x = (min(x1, x2) + w/2.0)
- y = (min(y1, y2) + h/2.0)
- return x, y, w, h
-
- def text(self, txt=None):
- if txt is not None:
- if not isinstance(txt,str):
- txt = str(txt)
- self.kwargs['text'] = txt
- self.shape.ClearText()
- for line in txt.split('\n'):
- self.shape.AddText(unicode(line, "latin1"))
- self.canvas.Refresh(False)
- return self.kwargs['text']
- text = property(text, text)
-
- def set_x(self, x):
- self.shape.SetX(x)
- self.canvas.Refresh(False)
- self.evt_callback()
- def set_y(self, y):
- self.shape.SetY(y)
- self.canvas.Refresh(False)
- self.evt_callback()
- def get_x(self):
- return self.shape.GetX()
- def get_y(self):
- return self.shape.GetY()
-
- x = property(get_x, set_x)
- y = property(get_y, set_y)
-
- def selected(self, sel=None):
- if sel is not None:
- print "Setting Select(%s)" % sel
- self.shape.Select(sel)
- return self.shape.Selected()
- selected = property(selected, selected)
-
- def name(self, name=None):
- if name is not None:
- self.kwargs['name'] = name
- return self.kwargs['name']
- name = property(name, name)
-
- def __contains__(self, k):
- "Implement in keyword for searchs"
- return k in self.name.lower() or self.text and k in self.text.lower()
-
- def as_dict(self):
- "Return a dictionary representation, used by pyfpdf"
- d = self.kwargs
- x1, y1, x2, y2 = self.get_coordinates()
- d.update({
- 'x1': x1, 'y1': y1, 'x2': x2, 'y2': y2,
- 'text': self.text})
- return d
-
-
-class AppFrame(wx.Frame):
- "OGL Designer main window"
- title = "PyFPDF Template Designer (wx OGL)"
-
- def __init__(self):
- wx.Frame.__init__( self,
- None, -1, self.title,
- size=(640,480),
- style=wx.DEFAULT_FRAME_STYLE )
- sys.excepthook = self.except_hook
- self.filename = ""
- # Create a toolbar:
- tsize = (16,16)
- self.toolbar = self.CreateToolBar(wx.TB_HORIZONTAL | wx.NO_BORDER | wx.TB_FLAT)
-
- artBmp = wx.ArtProvider.GetBitmap
- self.toolbar.AddSimpleTool(
- wx.ID_NEW, artBmp(wx.ART_NEW, wx.ART_TOOLBAR, tsize), "New")
- self.toolbar.AddSimpleTool(
- wx.ID_OPEN, artBmp(wx.ART_FILE_OPEN, wx.ART_TOOLBAR, tsize), "Open")
- self.toolbar.AddSimpleTool(
- wx.ID_SAVE, artBmp(wx.ART_FILE_SAVE, wx.ART_TOOLBAR, tsize), "Save")
- self.toolbar.AddSimpleTool(
- wx.ID_SAVEAS, artBmp(wx.ART_FILE_SAVE_AS, wx.ART_TOOLBAR, tsize),
- "Save As...")
- #-------
- self.toolbar.AddSeparator()
- self.toolbar.AddSimpleTool(
- wx.ID_UNDO, artBmp(wx.ART_UNDO, wx.ART_TOOLBAR, tsize), "Undo")
- self.toolbar.AddSimpleTool(
- wx.ID_REDO, artBmp(wx.ART_REDO, wx.ART_TOOLBAR, tsize), "Redo")
- self.toolbar.AddSeparator()
- #-------
- self.toolbar.AddSimpleTool(
- wx.ID_CUT, artBmp(wx.ART_CUT, wx.ART_TOOLBAR, tsize), "Remove")
- self.toolbar.AddSimpleTool(
- wx.ID_COPY, artBmp(wx.ART_COPY, wx.ART_TOOLBAR, tsize), "Duplicate")
- self.toolbar.AddSimpleTool(
- wx.ID_PASTE, artBmp(wx.ART_PASTE, wx.ART_TOOLBAR, tsize), "Insert")
- self.toolbar.AddSeparator()
- self.toolbar.AddSimpleTool(
- wx.ID_FIND, artBmp(wx.ART_FIND, wx.ART_TOOLBAR, tsize), "Find")
- self.toolbar.AddSeparator()
- self.toolbar.AddSimpleTool(
- wx.ID_PRINT, artBmp(wx.ART_PRINT, wx.ART_TOOLBAR, tsize), "Print")
- self.toolbar.AddSimpleTool(
- wx.ID_ABOUT, artBmp(wx.ART_HELP, wx.ART_TOOLBAR, tsize), "About")
-
- self.toolbar.Realize()
-
- self.toolbar.EnableTool(wx.ID_SAVEAS, False)
- self.toolbar.EnableTool(wx.ID_UNDO, False)
- self.toolbar.EnableTool(wx.ID_REDO, False)
-
- menu_handlers = [
- (wx.ID_NEW, self.do_new),
- (wx.ID_OPEN, self.do_open),
- (wx.ID_SAVE, self.do_save),
- (wx.ID_PRINT, self.do_print),
- (wx.ID_FIND, self.do_find),
- (wx.ID_CUT, self.do_cut),
- (wx.ID_COPY, self.do_copy),
- (wx.ID_PASTE, self.do_paste),
- (wx.ID_ABOUT, self.do_about),
- ]
- for menu_id, handler in menu_handlers:
- self.Bind(wx.EVT_MENU, handler, id = menu_id)
-
- sizer = wx.BoxSizer(wx.VERTICAL)
- # put stuff into sizer
-
- self.CreateStatusBar()
-
- canvas = self.canvas = ogl.ShapeCanvas( self )
- maxWidth = 1500
- maxHeight = 2000
- canvas.SetScrollbars(20, 20, maxWidth/20, maxHeight/20)
- sizer.Add( canvas, 1, wx.GROW )
-
- canvas.SetBackgroundColour("WHITE") #
-
- diagram = self.diagram = ogl.Diagram()
- canvas.SetDiagram( diagram )
- diagram.SetCanvas( canvas )
- diagram.SetSnapToGrid( False )
-
- # apply sizer
- self.SetSizer(sizer)
- self.SetAutoLayout(1)
- self.Show(1)
-
- self.Bind(wx.EVT_CHAR_HOOK, self.on_key_event)
- self.elements = []
-
- def on_key_event(self, event):
- """ Respond to a keypress event.
-
- We make the arrow keys move the selected object(s) by one pixel in
- the given direction.
- """
- step = 1
- if event.ControlDown():
- step = 20
-
- if event.GetKeyCode() == wx.WXK_UP:
- self.move_elements(0, -step)
- elif event.GetKeyCode() == wx.WXK_DOWN:
- self.move_elements(0, step)
- elif event.GetKeyCode() == wx.WXK_LEFT:
- self.move_elements(-step, 0)
- elif event.GetKeyCode() == wx.WXK_RIGHT:
- self.move_elements(step, 0)
- elif event.GetKeyCode() == wx.WXK_DELETE:
- self.do_cut()
- else:
- event.Skip()
-
- def do_new(self, evt=None):
- for element in self.elements:
- element.remove()
- self.elements = []
- # draw paper size guides
- for k, (w, h) in [('legal', (216, 356)), ('A4', (210, 297)), ('letter', (216, 279))]:
- self.create_elements(
- k, 'R', 0, 0, w, h,
- size=70, foreground=0x808080, priority=-100,
- canvas=self.canvas, frame=self, static=True)
- self.diagram.ShowAll( 1 )
-
- def do_open(self, evt):
- dlg = wx.FileDialog(
- self, message="Choose a file",
- defaultDir=os.getcwd(),
- defaultFile="invoice.csv",
- wildcard="CSV Files (*.csv)|*.csv",
- style=wx.OPEN
- )
-
- if dlg.ShowModal() == wx.ID_OK:
- # This returns a Python list of files that were selected.
- self.filename = dlg.GetPaths()[0]
-
- dlg.Destroy()
- self.SetTitle(self.filename + " - " + self.title)
-
- self.do_new()
- tmp = []
- f = open(self.filename)
- try:
- filedata = f.readlines()
- finally:
- f.close()
- for lno, linea in enumerate(filedata):
- if DEBUG: print "processing line", lno, linea
- args = []
- for i,v in enumerate(linea.split(";")):
- if not v.startswith("'"):
- v = v.replace(",",".")
- else:
- v = v#.decode('latin1')
- if v.strip()=='':
- v = None
- else:
- v = eval(v.strip())
- args.append(v)
- tmp.append(args)
-
- # sort by z-order (priority)
- for args in sorted(tmp, key=lambda t: t[-1]):
- if DEBUG: print args
- self.create_elements(*args)
- self.diagram.ShowAll( 1 ) #
-
- return True
-
- def do_save(self, evt, filename=None):
- try:
- from time import gmtime, strftime
- ts = strftime("%Y%m%d%H%M%S", gmtime())
- os.rename(self.filename, self.filename + ts + ".bak")
- except Exception, e:
- if DEBUG: print e
- pass
-
- def csv_repr(v, decimal_sep="."):
- if isinstance(v, float):
- return ("%0.2f" % v).replace(".", decimal_sep)
- else:
- return repr(v)
-
- f = open(self.filename, "w")
- try:
- for element in sorted(self.elements, key=lambda e:e.name):
- if element.static:
- continue
- d = element.as_dict()
- l = [d['name'], d['type'],
- d['x1'], d['y1'], d['x2'], d['y2'],
- d['font'], d['size'],
- d['bold'], d['italic'], d['underline'],
- d['foreground'], d['background'],
- d['align'], d['text'], d['priority'],
- ]
- f.write(";".join([csv_repr(v) for v in l]))
- f.write("\n")
- finally:
- f.close()
-
- def do_print(self, evt):
- # genero el renderizador con propiedades del PDF
- from template import Template
- t = Template(elements=[e.as_dict() for e in self.elements if not e.static])
- t.add_page()
- if not t['logo'] or not os.path.exists(t['logo']):
- # put a default logo so it doesn't trow an exception
- logo = os.path.join(os.path.dirname(__file__), 'tutorial','logo.png')
- t.set('logo', logo)
- try:
- t.render(self.filename +".pdf")
- except:
- if DEBUG and False:
- import pdb;
- pdb.pm()
- else:
- raise
- if sys.platform=="linux2":
- os.system("evince ""%s""" % self.filename +".pdf")
- else:
- os.startfile(self.filename +".pdf")
-
- def do_find(self, evt):
- # busco nombre o texto
- dlg = wx.TextEntryDialog(
- self, 'Enter text to search for',
- 'Find Text', '')
- if dlg.ShowModal() == wx.ID_OK:
- txt = dlg.GetValue().encode("latin1").lower()
- for element in self.elements:
- if txt in element:
- element.selected = True
- print "Found:", element.name
- self.canvas.Refresh(False)
- dlg.Destroy()
-
- def do_cut(self, evt=None):
- "Delete selected elements"
- new_elements = []
- for element in self.elements:
- if element.selected:
- print "Erasing:", element.name
- element.selected = False
- self.canvas.Refresh(False)
- element.remove()
- else:
- new_elements.append(element)
- self.elements = new_elements
- self.canvas.Refresh(False)
- self.diagram.ShowAll( 1 )
-
- def do_copy(self, evt):
- "Duplicate selected elements"
- fields = ['qty', 'dx', 'dy']
- data = {'qty': 1, 'dx': 0.0, 'dy': 5.0}
- data = CustomDialog.do_input(self, 'Copy elements', fields, data)
- if data:
- new_elements = []
- for i in range(1, data['qty']+1):
- for element in self.elements:
- if element.selected:
- print "Copying:", element.name
- new_element = element.copy()
- name = new_element.name
- if len(name)>2 and name[-2:].isdigit():
- new_element.name = name[:-2] + "%02d" % (int(name[-2:])+i)
- else:
- new_element.name = new_element.name + "_copy"
- new_element.selected = False
- new_element.move(data['dx']*i, data['dy']*i)
- new_elements.append(new_element)
- self.elements.extend(new_elements)
- self.canvas.Refresh(False)
- self.diagram.ShowAll( 1 )
-
- def do_paste(self, evt):
- "Insert new elements"
- element = Element.new(self)
- if element:
- self.canvas.Refresh(False)
- self.elements.append(element)
- self.diagram.ShowAll( 1 )
-
- def create_elements(self, name, type, x1, y1, x2, y2,
- font="Arial", size=12,
- bold=False, italic=False, underline=False,
- foreground= 0x000000, background=0xFFFFFF,
- align="L", text="", priority=0, canvas=None, frame=None, static=False,
- **kwargs):
- element = Element(name=name, type=type, x1=x1, y1=y1, x2=x2, y2=y2,
- font=font, size=size,
- bold=bold, italic=italic, underline=underline,
- foreground= foreground, background=background,
- align=align, text=text, priority=priority,
- canvas=canvas or self.canvas, frame=frame or self,
- static=static)
- self.elements.append(element)
-
- def move_elements(self, x, y):
- for element in self.elements:
- if element.selected:
- print "moving", element.name, x, y
- element.x = element.x + x
- element.y = element.y + y
-
- def do_about(self, evt):
- info = wx.AboutDialogInfo()
- info.Name = self.title
- info.Version = __version__
- info.Copyright = __copyright__
- info.Description = (
- "Visual Template designer for PyFPDF (using wxPython OGL library)\n"
- "Input files are CSV format describing the layout, separated by ;\n"
- "Use toolbar buttons to open, save, print (preview) your template, "
- "and there are buttons to find, add, remove or duplicate elements.\n"
- "Over an element, a double left click opens edit text dialog, "
- "and a right click opens edit properties dialog. \n"
- "Multiple element can be selected with shift left click. \n"
- "Use arrow keys or drag-and-drop to move elements.\n"
- "For further information see project webpage:"
- )
- info.WebSite = ("http://code.google.com/p/pyfpdf/wiki/Templates",
- "pyfpdf Google Code Project")
- info.Developers = [ __author__, ]
-
- info.License = wordwrap(__license__, 500, wx.ClientDC(self))
-
- # Then we call wx.AboutBox giving it that info object
- wx.AboutBox(info)
-
- def except_hook(self, type, value, trace):
- import traceback
- exc = traceback.format_exception(type, value, trace)
- for e in exc: wx.LogError(e)
- wx.LogError('Unhandled Error: %s: %s'%(str(type), str(value)))
-
-
-app = wx.PySimpleApp()
-ogl.OGLInitialize()
-frame = AppFrame()
-app.MainLoop()
-app.Destroy()
-
-
-
diff --git a/gluon/contrib/pyfpdf/fonts.py b/gluon/contrib/pyfpdf/fonts.py
new file mode 100644
index 00000000..433fbf61
--- /dev/null
+++ b/gluon/contrib/pyfpdf/fonts.py
@@ -0,0 +1,156 @@
+#!/usr/bin/env python
+# -*- coding: latin-1 -*-
+
+# Fonts:
+
+fpdf_charwidths = {}
+
+fpdf_charwidths['courier']={}
+
+for i in xrange(0,256):
+ fpdf_charwidths['courier'][chr(i)]=600
+ fpdf_charwidths['courierB']=fpdf_charwidths['courier']
+ fpdf_charwidths['courierI']=fpdf_charwidths['courier']
+ fpdf_charwidths['courierBI']=fpdf_charwidths['courier']
+
+fpdf_charwidths['helvetica']={
+ '\x00':278,'\x01':278,'\x02':278,'\x03':278,'\x04':278,'\x05':278,'\x06':278,'\x07':278,'\x08':278,'\t':278,'\n':278,'\x0b':278,'\x0c':278,'\r':278,'\x0e':278,'\x0f':278,'\x10':278,'\x11':278,'\x12':278,'\x13':278,'\x14':278,'\x15':278,
+ '\x16':278,'\x17':278,'\x18':278,'\x19':278,'\x1a':278,'\x1b':278,'\x1c':278,'\x1d':278,'\x1e':278,'\x1f':278,' ':278,'!':278,'"':355,'#':556,'$':556,'%':889,'&':667,'\'':191,'(':333,')':333,'*':389,'+':584,
+ ',':278,'-':333,'.':278,'/':278,'0':556,'1':556,'2':556,'3':556,'4':556,'5':556,'6':556,'7':556,'8':556,'9':556,':':278,';':278,'<':584,'=':584,'>':584,'?':556,'@':1015,'A':667,
+ 'B':667,'C':722,'D':722,'E':667,'F':611,'G':778,'H':722,'I':278,'J':500,'K':667,'L':556,'M':833,'N':722,'O':778,'P':667,'Q':778,'R':722,'S':667,'T':611,'U':722,'V':667,'W':944,
+ 'X':667,'Y':667,'Z':611,'[':278,'\\':278,']':278,'^':469,'_':556,'`':333,'a':556,'b':556,'c':500,'d':556,'e':556,'f':278,'g':556,'h':556,'i':222,'j':222,'k':500,'l':222,'m':833,
+ 'n':556,'o':556,'p':556,'q':556,'r':333,'s':500,'t':278,'u':556,'v':500,'w':722,'x':500,'y':500,'z':500,'{':334,'|':260,'}':334,'~':584,'\x7f':350,'\x80':556,'\x81':350,'\x82':222,'\x83':556,
+ '\x84':333,'\x85':1000,'\x86':556,'\x87':556,'\x88':333,'\x89':1000,'\x8a':667,'\x8b':333,'\x8c':1000,'\x8d':350,'\x8e':611,'\x8f':350,'\x90':350,'\x91':222,'\x92':222,'\x93':333,'\x94':333,'\x95':350,'\x96':556,'\x97':1000,'\x98':333,'\x99':1000,
+ '\x9a':500,'\x9b':333,'\x9c':944,'\x9d':350,'\x9e':500,'\x9f':667,'\xa0':278,'\xa1':333,'\xa2':556,'\xa3':556,'\xa4':556,'\xa5':556,'\xa6':260,'\xa7':556,'\xa8':333,'\xa9':737,'\xaa':370,'\xab':556,'\xac':584,'\xad':333,'\xae':737,'\xaf':333,
+ '\xb0':400,'\xb1':584,'\xb2':333,'\xb3':333,'\xb4':333,'\xb5':556,'\xb6':537,'\xb7':278,'\xb8':333,'\xb9':333,'\xba':365,'\xbb':556,'\xbc':834,'\xbd':834,'\xbe':834,'\xbf':611,'\xc0':667,'\xc1':667,'\xc2':667,'\xc3':667,'\xc4':667,'\xc5':667,
+ '\xc6':1000,'\xc7':722,'\xc8':667,'\xc9':667,'\xca':667,'\xcb':667,'\xcc':278,'\xcd':278,'\xce':278,'\xcf':278,'\xd0':722,'\xd1':722,'\xd2':778,'\xd3':778,'\xd4':778,'\xd5':778,'\xd6':778,'\xd7':584,'\xd8':778,'\xd9':722,'\xda':722,'\xdb':722,
+ '\xdc':722,'\xdd':667,'\xde':667,'\xdf':611,'\xe0':556,'\xe1':556,'\xe2':556,'\xe3':556,'\xe4':556,'\xe5':556,'\xe6':889,'\xe7':500,'\xe8':556,'\xe9':556,'\xea':556,'\xeb':556,'\xec':278,'\xed':278,'\xee':278,'\xef':278,'\xf0':556,'\xf1':556,
+ '\xf2':556,'\xf3':556,'\xf4':556,'\xf5':556,'\xf6':556,'\xf7':584,'\xf8':611,'\xf9':556,'\xfa':556,'\xfb':556,'\xfc':556,'\xfd':500,'\xfe':556,'\xff':500}
+
+fpdf_charwidths['helveticaB']={
+ '\x00':278,'\x01':278,'\x02':278,'\x03':278,'\x04':278,'\x05':278,'\x06':278,'\x07':278,'\x08':278,'\t':278,'\n':278,'\x0b':278,'\x0c':278,'\r':278,'\x0e':278,'\x0f':278,'\x10':278,'\x11':278,'\x12':278,'\x13':278,'\x14':278,'\x15':278,
+ '\x16':278,'\x17':278,'\x18':278,'\x19':278,'\x1a':278,'\x1b':278,'\x1c':278,'\x1d':278,'\x1e':278,'\x1f':278,' ':278,'!':333,'"':474,'#':556,'$':556,'%':889,'&':722,'\'':238,'(':333,')':333,'*':389,'+':584,
+ ',':278,'-':333,'.':278,'/':278,'0':556,'1':556,'2':556,'3':556,'4':556,'5':556,'6':556,'7':556,'8':556,'9':556,':':333,';':333,'<':584,'=':584,'>':584,'?':611,'@':975,'A':722,
+ 'B':722,'C':722,'D':722,'E':667,'F':611,'G':778,'H':722,'I':278,'J':556,'K':722,'L':611,'M':833,'N':722,'O':778,'P':667,'Q':778,'R':722,'S':667,'T':611,'U':722,'V':667,'W':944,
+ 'X':667,'Y':667,'Z':611,'[':333,'\\':278,']':333,'^':584,'_':556,'`':333,'a':556,'b':611,'c':556,'d':611,'e':556,'f':333,'g':611,'h':611,'i':278,'j':278,'k':556,'l':278,'m':889,
+ 'n':611,'o':611,'p':611,'q':611,'r':389,'s':556,'t':333,'u':611,'v':556,'w':778,'x':556,'y':556,'z':500,'{':389,'|':280,'}':389,'~':584,'\x7f':350,'\x80':556,'\x81':350,'\x82':278,'\x83':556,
+ '\x84':500,'\x85':1000,'\x86':556,'\x87':556,'\x88':333,'\x89':1000,'\x8a':667,'\x8b':333,'\x8c':1000,'\x8d':350,'\x8e':611,'\x8f':350,'\x90':350,'\x91':278,'\x92':278,'\x93':500,'\x94':500,'\x95':350,'\x96':556,'\x97':1000,'\x98':333,'\x99':1000,
+ '\x9a':556,'\x9b':333,'\x9c':944,'\x9d':350,'\x9e':500,'\x9f':667,'\xa0':278,'\xa1':333,'\xa2':556,'\xa3':556,'\xa4':556,'\xa5':556,'\xa6':280,'\xa7':556,'\xa8':333,'\xa9':737,'\xaa':370,'\xab':556,'\xac':584,'\xad':333,'\xae':737,'\xaf':333,
+ '\xb0':400,'\xb1':584,'\xb2':333,'\xb3':333,'\xb4':333,'\xb5':611,'\xb6':556,'\xb7':278,'\xb8':333,'\xb9':333,'\xba':365,'\xbb':556,'\xbc':834,'\xbd':834,'\xbe':834,'\xbf':611,'\xc0':722,'\xc1':722,'\xc2':722,'\xc3':722,'\xc4':722,'\xc5':722,
+ '\xc6':1000,'\xc7':722,'\xc8':667,'\xc9':667,'\xca':667,'\xcb':667,'\xcc':278,'\xcd':278,'\xce':278,'\xcf':278,'\xd0':722,'\xd1':722,'\xd2':778,'\xd3':778,'\xd4':778,'\xd5':778,'\xd6':778,'\xd7':584,'\xd8':778,'\xd9':722,'\xda':722,'\xdb':722,
+ '\xdc':722,'\xdd':667,'\xde':667,'\xdf':611,'\xe0':556,'\xe1':556,'\xe2':556,'\xe3':556,'\xe4':556,'\xe5':556,'\xe6':889,'\xe7':556,'\xe8':556,'\xe9':556,'\xea':556,'\xeb':556,'\xec':278,'\xed':278,'\xee':278,'\xef':278,'\xf0':611,'\xf1':611,
+ '\xf2':611,'\xf3':611,'\xf4':611,'\xf5':611,'\xf6':611,'\xf7':584,'\xf8':611,'\xf9':611,'\xfa':611,'\xfb':611,'\xfc':611,'\xfd':556,'\xfe':611,'\xff':556
+}
+
+fpdf_charwidths['helveticaBI']={
+ '\x00':278,'\x01':278,'\x02':278,'\x03':278,'\x04':278,'\x05':278,'\x06':278,'\x07':278,'\x08':278,'\t':278,'\n':278,'\x0b':278,'\x0c':278,'\r':278,'\x0e':278,'\x0f':278,'\x10':278,'\x11':278,'\x12':278,'\x13':278,'\x14':278,'\x15':278,
+ '\x16':278,'\x17':278,'\x18':278,'\x19':278,'\x1a':278,'\x1b':278,'\x1c':278,'\x1d':278,'\x1e':278,'\x1f':278,' ':278,'!':333,'"':474,'#':556,'$':556,'%':889,'&':722,'\'':238,'(':333,')':333,'*':389,'+':584,
+ ',':278,'-':333,'.':278,'/':278,'0':556,'1':556,'2':556,'3':556,'4':556,'5':556,'6':556,'7':556,'8':556,'9':556,':':333,';':333,'<':584,'=':584,'>':584,'?':611,'@':975,'A':722,
+ 'B':722,'C':722,'D':722,'E':667,'F':611,'G':778,'H':722,'I':278,'J':556,'K':722,'L':611,'M':833,'N':722,'O':778,'P':667,'Q':778,'R':722,'S':667,'T':611,'U':722,'V':667,'W':944,
+ 'X':667,'Y':667,'Z':611,'[':333,'\\':278,']':333,'^':584,'_':556,'`':333,'a':556,'b':611,'c':556,'d':611,'e':556,'f':333,'g':611,'h':611,'i':278,'j':278,'k':556,'l':278,'m':889,
+ 'n':611,'o':611,'p':611,'q':611,'r':389,'s':556,'t':333,'u':611,'v':556,'w':778,'x':556,'y':556,'z':500,'{':389,'|':280,'}':389,'~':584,'\x7f':350,'\x80':556,'\x81':350,'\x82':278,'\x83':556,
+ '\x84':500,'\x85':1000,'\x86':556,'\x87':556,'\x88':333,'\x89':1000,'\x8a':667,'\x8b':333,'\x8c':1000,'\x8d':350,'\x8e':611,'\x8f':350,'\x90':350,'\x91':278,'\x92':278,'\x93':500,'\x94':500,'\x95':350,'\x96':556,'\x97':1000,'\x98':333,'\x99':1000,
+ '\x9a':556,'\x9b':333,'\x9c':944,'\x9d':350,'\x9e':500,'\x9f':667,'\xa0':278,'\xa1':333,'\xa2':556,'\xa3':556,'\xa4':556,'\xa5':556,'\xa6':280,'\xa7':556,'\xa8':333,'\xa9':737,'\xaa':370,'\xab':556,'\xac':584,'\xad':333,'\xae':737,'\xaf':333,
+ '\xb0':400,'\xb1':584,'\xb2':333,'\xb3':333,'\xb4':333,'\xb5':611,'\xb6':556,'\xb7':278,'\xb8':333,'\xb9':333,'\xba':365,'\xbb':556,'\xbc':834,'\xbd':834,'\xbe':834,'\xbf':611,'\xc0':722,'\xc1':722,'\xc2':722,'\xc3':722,'\xc4':722,'\xc5':722,
+ '\xc6':1000,'\xc7':722,'\xc8':667,'\xc9':667,'\xca':667,'\xcb':667,'\xcc':278,'\xcd':278,'\xce':278,'\xcf':278,'\xd0':722,'\xd1':722,'\xd2':778,'\xd3':778,'\xd4':778,'\xd5':778,'\xd6':778,'\xd7':584,'\xd8':778,'\xd9':722,'\xda':722,'\xdb':722,
+ '\xdc':722,'\xdd':667,'\xde':667,'\xdf':611,'\xe0':556,'\xe1':556,'\xe2':556,'\xe3':556,'\xe4':556,'\xe5':556,'\xe6':889,'\xe7':556,'\xe8':556,'\xe9':556,'\xea':556,'\xeb':556,'\xec':278,'\xed':278,'\xee':278,'\xef':278,'\xf0':611,'\xf1':611,
+ '\xf2':611,'\xf3':611,'\xf4':611,'\xf5':611,'\xf6':611,'\xf7':584,'\xf8':611,'\xf9':611,'\xfa':611,'\xfb':611,'\xfc':611,'\xfd':556,'\xfe':611,'\xff':556}
+
+fpdf_charwidths['helveticaI']={
+ '\x00':278,'\x01':278,'\x02':278,'\x03':278,'\x04':278,'\x05':278,'\x06':278,'\x07':278,'\x08':278,'\t':278,'\n':278,'\x0b':278,'\x0c':278,'\r':278,'\x0e':278,'\x0f':278,'\x10':278,'\x11':278,'\x12':278,'\x13':278,'\x14':278,'\x15':278,
+ '\x16':278,'\x17':278,'\x18':278,'\x19':278,'\x1a':278,'\x1b':278,'\x1c':278,'\x1d':278,'\x1e':278,'\x1f':278,' ':278,'!':278,'"':355,'#':556,'$':556,'%':889,'&':667,'\'':191,'(':333,')':333,'*':389,'+':584,
+ ',':278,'-':333,'.':278,'/':278,'0':556,'1':556,'2':556,'3':556,'4':556,'5':556,'6':556,'7':556,'8':556,'9':556,':':278,';':278,'<':584,'=':584,'>':584,'?':556,'@':1015,'A':667,
+ 'B':667,'C':722,'D':722,'E':667,'F':611,'G':778,'H':722,'I':278,'J':500,'K':667,'L':556,'M':833,'N':722,'O':778,'P':667,'Q':778,'R':722,'S':667,'T':611,'U':722,'V':667,'W':944,
+ 'X':667,'Y':667,'Z':611,'[':278,'\\':278,']':278,'^':469,'_':556,'`':333,'a':556,'b':556,'c':500,'d':556,'e':556,'f':278,'g':556,'h':556,'i':222,'j':222,'k':500,'l':222,'m':833,
+ 'n':556,'o':556,'p':556,'q':556,'r':333,'s':500,'t':278,'u':556,'v':500,'w':722,'x':500,'y':500,'z':500,'{':334,'|':260,'}':334,'~':584,'\x7f':350,'\x80':556,'\x81':350,'\x82':222,'\x83':556,
+ '\x84':333,'\x85':1000,'\x86':556,'\x87':556,'\x88':333,'\x89':1000,'\x8a':667,'\x8b':333,'\x8c':1000,'\x8d':350,'\x8e':611,'\x8f':350,'\x90':350,'\x91':222,'\x92':222,'\x93':333,'\x94':333,'\x95':350,'\x96':556,'\x97':1000,'\x98':333,'\x99':1000,
+ '\x9a':500,'\x9b':333,'\x9c':944,'\x9d':350,'\x9e':500,'\x9f':667,'\xa0':278,'\xa1':333,'\xa2':556,'\xa3':556,'\xa4':556,'\xa5':556,'\xa6':260,'\xa7':556,'\xa8':333,'\xa9':737,'\xaa':370,'\xab':556,'\xac':584,'\xad':333,'\xae':737,'\xaf':333,
+ '\xb0':400,'\xb1':584,'\xb2':333,'\xb3':333,'\xb4':333,'\xb5':556,'\xb6':537,'\xb7':278,'\xb8':333,'\xb9':333,'\xba':365,'\xbb':556,'\xbc':834,'\xbd':834,'\xbe':834,'\xbf':611,'\xc0':667,'\xc1':667,'\xc2':667,'\xc3':667,'\xc4':667,'\xc5':667,
+ '\xc6':1000,'\xc7':722,'\xc8':667,'\xc9':667,'\xca':667,'\xcb':667,'\xcc':278,'\xcd':278,'\xce':278,'\xcf':278,'\xd0':722,'\xd1':722,'\xd2':778,'\xd3':778,'\xd4':778,'\xd5':778,'\xd6':778,'\xd7':584,'\xd8':778,'\xd9':722,'\xda':722,'\xdb':722,
+ '\xdc':722,'\xdd':667,'\xde':667,'\xdf':611,'\xe0':556,'\xe1':556,'\xe2':556,'\xe3':556,'\xe4':556,'\xe5':556,'\xe6':889,'\xe7':500,'\xe8':556,'\xe9':556,'\xea':556,'\xeb':556,'\xec':278,'\xed':278,'\xee':278,'\xef':278,'\xf0':556,'\xf1':556,
+ '\xf2':556,'\xf3':556,'\xf4':556,'\xf5':556,'\xf6':556,'\xf7':584,'\xf8':611,'\xf9':556,'\xfa':556,'\xfb':556,'\xfc':556,'\xfd':500,'\xfe':556,'\xff':500}
+
+fpdf_charwidths['symbol']={
+ '\x00':250,'\x01':250,'\x02':250,'\x03':250,'\x04':250,'\x05':250,'\x06':250,'\x07':250,'\x08':250,'\t':250,'\n':250,'\x0b':250,'\x0c':250,'\r':250,'\x0e':250,'\x0f':250,'\x10':250,'\x11':250,'\x12':250,'\x13':250,'\x14':250,'\x15':250,
+ '\x16':250,'\x17':250,'\x18':250,'\x19':250,'\x1a':250,'\x1b':250,'\x1c':250,'\x1d':250,'\x1e':250,'\x1f':250,' ':250,'!':333,'"':713,'#':500,'$':549,'%':833,'&':778,'\'':439,'(':333,')':333,'*':500,'+':549,
+ ',':250,'-':549,'.':250,'/':278,'0':500,'1':500,'2':500,'3':500,'4':500,'5':500,'6':500,'7':500,'8':500,'9':500,':':278,';':278,'<':549,'=':549,'>':549,'?':444,'@':549,'A':722,
+ 'B':667,'C':722,'D':612,'E':611,'F':763,'G':603,'H':722,'I':333,'J':631,'K':722,'L':686,'M':889,'N':722,'O':722,'P':768,'Q':741,'R':556,'S':592,'T':611,'U':690,'V':439,'W':768,
+ 'X':645,'Y':795,'Z':611,'[':333,'\\':863,']':333,'^':658,'_':500,'`':500,'a':631,'b':549,'c':549,'d':494,'e':439,'f':521,'g':411,'h':603,'i':329,'j':603,'k':549,'l':549,'m':576,
+ 'n':521,'o':549,'p':549,'q':521,'r':549,'s':603,'t':439,'u':576,'v':713,'w':686,'x':493,'y':686,'z':494,'{':480,'|':200,'}':480,'~':549,'\x7f':0,'\x80':0,'\x81':0,'\x82':0,'\x83':0,
+ '\x84':0,'\x85':0,'\x86':0,'\x87':0,'\x88':0,'\x89':0,'\x8a':0,'\x8b':0,'\x8c':0,'\x8d':0,'\x8e':0,'\x8f':0,'\x90':0,'\x91':0,'\x92':0,'\x93':0,'\x94':0,'\x95':0,'\x96':0,'\x97':0,'\x98':0,'\x99':0,
+ '\x9a':0,'\x9b':0,'\x9c':0,'\x9d':0,'\x9e':0,'\x9f':0,'\xa0':750,'\xa1':620,'\xa2':247,'\xa3':549,'\xa4':167,'\xa5':713,'\xa6':500,'\xa7':753,'\xa8':753,'\xa9':753,'\xaa':753,'\xab':1042,'\xac':987,'\xad':603,'\xae':987,'\xaf':603,
+ '\xb0':400,'\xb1':549,'\xb2':411,'\xb3':549,'\xb4':549,'\xb5':713,'\xb6':494,'\xb7':460,'\xb8':549,'\xb9':549,'\xba':549,'\xbb':549,'\xbc':1000,'\xbd':603,'\xbe':1000,'\xbf':658,'\xc0':823,'\xc1':686,'\xc2':795,'\xc3':987,'\xc4':768,'\xc5':768,
+ '\xc6':823,'\xc7':768,'\xc8':768,'\xc9':713,'\xca':713,'\xcb':713,'\xcc':713,'\xcd':713,'\xce':713,'\xcf':713,'\xd0':768,'\xd1':713,'\xd2':790,'\xd3':790,'\xd4':890,'\xd5':823,'\xd6':549,'\xd7':250,'\xd8':713,'\xd9':603,'\xda':603,'\xdb':1042,
+ '\xdc':987,'\xdd':603,'\xde':987,'\xdf':603,'\xe0':494,'\xe1':329,'\xe2':790,'\xe3':790,'\xe4':786,'\xe5':713,'\xe6':384,'\xe7':384,'\xe8':384,'\xe9':384,'\xea':384,'\xeb':384,'\xec':494,'\xed':494,'\xee':494,'\xef':494,'\xf0':0,'\xf1':329,
+ '\xf2':274,'\xf3':686,'\xf4':686,'\xf5':686,'\xf6':384,'\xf7':384,'\xf8':384,'\xf9':384,'\xfa':384,'\xfb':384,'\xfc':494,'\xfd':494,'\xfe':494,'\xff':0}
+
+fpdf_charwidths['times']={
+ '\x00':250,'\x01':250,'\x02':250,'\x03':250,'\x04':250,'\x05':250,'\x06':250,'\x07':250,'\x08':250,'\t':250,'\n':250,'\x0b':250,'\x0c':250,'\r':250,'\x0e':250,'\x0f':250,'\x10':250,'\x11':250,'\x12':250,'\x13':250,'\x14':250,'\x15':250,
+ '\x16':250,'\x17':250,'\x18':250,'\x19':250,'\x1a':250,'\x1b':250,'\x1c':250,'\x1d':250,'\x1e':250,'\x1f':250,' ':250,'!':333,'"':408,'#':500,'$':500,'%':833,'&':778,'\'':180,'(':333,')':333,'*':500,'+':564,
+ ',':250,'-':333,'.':250,'/':278,'0':500,'1':500,'2':500,'3':500,'4':500,'5':500,'6':500,'7':500,'8':500,'9':500,':':278,';':278,'<':564,'=':564,'>':564,'?':444,'@':921,'A':722,
+ 'B':667,'C':667,'D':722,'E':611,'F':556,'G':722,'H':722,'I':333,'J':389,'K':722,'L':611,'M':889,'N':722,'O':722,'P':556,'Q':722,'R':667,'S':556,'T':611,'U':722,'V':722,'W':944,
+ 'X':722,'Y':722,'Z':611,'[':333,'\\':278,']':333,'^':469,'_':500,'`':333,'a':444,'b':500,'c':444,'d':500,'e':444,'f':333,'g':500,'h':500,'i':278,'j':278,'k':500,'l':278,'m':778,
+ 'n':500,'o':500,'p':500,'q':500,'r':333,'s':389,'t':278,'u':500,'v':500,'w':722,'x':500,'y':500,'z':444,'{':480,'|':200,'}':480,'~':541,'\x7f':350,'\x80':500,'\x81':350,'\x82':333,'\x83':500,
+ '\x84':444,'\x85':1000,'\x86':500,'\x87':500,'\x88':333,'\x89':1000,'\x8a':556,'\x8b':333,'\x8c':889,'\x8d':350,'\x8e':611,'\x8f':350,'\x90':350,'\x91':333,'\x92':333,'\x93':444,'\x94':444,'\x95':350,'\x96':500,'\x97':1000,'\x98':333,'\x99':980,
+ '\x9a':389,'\x9b':333,'\x9c':722,'\x9d':350,'\x9e':444,'\x9f':722,'\xa0':250,'\xa1':333,'\xa2':500,'\xa3':500,'\xa4':500,'\xa5':500,'\xa6':200,'\xa7':500,'\xa8':333,'\xa9':760,'\xaa':276,'\xab':500,'\xac':564,'\xad':333,'\xae':760,'\xaf':333,
+ '\xb0':400,'\xb1':564,'\xb2':300,'\xb3':300,'\xb4':333,'\xb5':500,'\xb6':453,'\xb7':250,'\xb8':333,'\xb9':300,'\xba':310,'\xbb':500,'\xbc':750,'\xbd':750,'\xbe':750,'\xbf':444,'\xc0':722,'\xc1':722,'\xc2':722,'\xc3':722,'\xc4':722,'\xc5':722,
+ '\xc6':889,'\xc7':667,'\xc8':611,'\xc9':611,'\xca':611,'\xcb':611,'\xcc':333,'\xcd':333,'\xce':333,'\xcf':333,'\xd0':722,'\xd1':722,'\xd2':722,'\xd3':722,'\xd4':722,'\xd5':722,'\xd6':722,'\xd7':564,'\xd8':722,'\xd9':722,'\xda':722,'\xdb':722,
+ '\xdc':722,'\xdd':722,'\xde':556,'\xdf':500,'\xe0':444,'\xe1':444,'\xe2':444,'\xe3':444,'\xe4':444,'\xe5':444,'\xe6':667,'\xe7':444,'\xe8':444,'\xe9':444,'\xea':444,'\xeb':444,'\xec':278,'\xed':278,'\xee':278,'\xef':278,'\xf0':500,'\xf1':500,
+ '\xf2':500,'\xf3':500,'\xf4':500,'\xf5':500,'\xf6':500,'\xf7':564,'\xf8':500,'\xf9':500,'\xfa':500,'\xfb':500,'\xfc':500,'\xfd':500,'\xfe':500,'\xff':500}
+
+fpdf_charwidths['timesB']={
+ '\x00':250,'\x01':250,'\x02':250,'\x03':250,'\x04':250,'\x05':250,'\x06':250,'\x07':250,'\x08':250,'\t':250,'\n':250,'\x0b':250,'\x0c':250,'\r':250,'\x0e':250,'\x0f':250,'\x10':250,'\x11':250,'\x12':250,'\x13':250,'\x14':250,'\x15':250,
+ '\x16':250,'\x17':250,'\x18':250,'\x19':250,'\x1a':250,'\x1b':250,'\x1c':250,'\x1d':250,'\x1e':250,'\x1f':250,' ':250,'!':333,'"':555,'#':500,'$':500,'%':1000,'&':833,'\'':278,'(':333,')':333,'*':500,'+':570,
+ ',':250,'-':333,'.':250,'/':278,'0':500,'1':500,'2':500,'3':500,'4':500,'5':500,'6':500,'7':500,'8':500,'9':500,':':333,';':333,'<':570,'=':570,'>':570,'?':500,'@':930,'A':722,
+ 'B':667,'C':722,'D':722,'E':667,'F':611,'G':778,'H':778,'I':389,'J':500,'K':778,'L':667,'M':944,'N':722,'O':778,'P':611,'Q':778,'R':722,'S':556,'T':667,'U':722,'V':722,'W':1000,
+ 'X':722,'Y':722,'Z':667,'[':333,'\\':278,']':333,'^':581,'_':500,'`':333,'a':500,'b':556,'c':444,'d':556,'e':444,'f':333,'g':500,'h':556,'i':278,'j':333,'k':556,'l':278,'m':833,
+ 'n':556,'o':500,'p':556,'q':556,'r':444,'s':389,'t':333,'u':556,'v':500,'w':722,'x':500,'y':500,'z':444,'{':394,'|':220,'}':394,'~':520,'\x7f':350,'\x80':500,'\x81':350,'\x82':333,'\x83':500,
+ '\x84':500,'\x85':1000,'\x86':500,'\x87':500,'\x88':333,'\x89':1000,'\x8a':556,'\x8b':333,'\x8c':1000,'\x8d':350,'\x8e':667,'\x8f':350,'\x90':350,'\x91':333,'\x92':333,'\x93':500,'\x94':500,'\x95':350,'\x96':500,'\x97':1000,'\x98':333,'\x99':1000,
+ '\x9a':389,'\x9b':333,'\x9c':722,'\x9d':350,'\x9e':444,'\x9f':722,'\xa0':250,'\xa1':333,'\xa2':500,'\xa3':500,'\xa4':500,'\xa5':500,'\xa6':220,'\xa7':500,'\xa8':333,'\xa9':747,'\xaa':300,'\xab':500,'\xac':570,'\xad':333,'\xae':747,'\xaf':333,
+ '\xb0':400,'\xb1':570,'\xb2':300,'\xb3':300,'\xb4':333,'\xb5':556,'\xb6':540,'\xb7':250,'\xb8':333,'\xb9':300,'\xba':330,'\xbb':500,'\xbc':750,'\xbd':750,'\xbe':750,'\xbf':500,'\xc0':722,'\xc1':722,'\xc2':722,'\xc3':722,'\xc4':722,'\xc5':722,
+ '\xc6':1000,'\xc7':722,'\xc8':667,'\xc9':667,'\xca':667,'\xcb':667,'\xcc':389,'\xcd':389,'\xce':389,'\xcf':389,'\xd0':722,'\xd1':722,'\xd2':778,'\xd3':778,'\xd4':778,'\xd5':778,'\xd6':778,'\xd7':570,'\xd8':778,'\xd9':722,'\xda':722,'\xdb':722,
+ '\xdc':722,'\xdd':722,'\xde':611,'\xdf':556,'\xe0':500,'\xe1':500,'\xe2':500,'\xe3':500,'\xe4':500,'\xe5':500,'\xe6':722,'\xe7':444,'\xe8':444,'\xe9':444,'\xea':444,'\xeb':444,'\xec':278,'\xed':278,'\xee':278,'\xef':278,'\xf0':500,'\xf1':556,
+ '\xf2':500,'\xf3':500,'\xf4':500,'\xf5':500,'\xf6':500,'\xf7':570,'\xf8':500,'\xf9':556,'\xfa':556,'\xfb':556,'\xfc':556,'\xfd':500,'\xfe':556,'\xff':500}
+
+fpdf_charwidths['timesBI']={
+ '\x00':250,'\x01':250,'\x02':250,'\x03':250,'\x04':250,'\x05':250,'\x06':250,'\x07':250,'\x08':250,'\t':250,'\n':250,'\x0b':250,'\x0c':250,'\r':250,'\x0e':250,'\x0f':250,'\x10':250,'\x11':250,'\x12':250,'\x13':250,'\x14':250,'\x15':250,
+ '\x16':250,'\x17':250,'\x18':250,'\x19':250,'\x1a':250,'\x1b':250,'\x1c':250,'\x1d':250,'\x1e':250,'\x1f':250,' ':250,'!':389,'"':555,'#':500,'$':500,'%':833,'&':778,'\'':278,'(':333,')':333,'*':500,'+':570,
+ ',':250,'-':333,'.':250,'/':278,'0':500,'1':500,'2':500,'3':500,'4':500,'5':500,'6':500,'7':500,'8':500,'9':500,':':333,';':333,'<':570,'=':570,'>':570,'?':500,'@':832,'A':667,
+ 'B':667,'C':667,'D':722,'E':667,'F':667,'G':722,'H':778,'I':389,'J':500,'K':667,'L':611,'M':889,'N':722,'O':722,'P':611,'Q':722,'R':667,'S':556,'T':611,'U':722,'V':667,'W':889,
+ 'X':667,'Y':611,'Z':611,'[':333,'\\':278,']':333,'^':570,'_':500,'`':333,'a':500,'b':500,'c':444,'d':500,'e':444,'f':333,'g':500,'h':556,'i':278,'j':278,'k':500,'l':278,'m':778,
+ 'n':556,'o':500,'p':500,'q':500,'r':389,'s':389,'t':278,'u':556,'v':444,'w':667,'x':500,'y':444,'z':389,'{':348,'|':220,'}':348,'~':570,'\x7f':350,'\x80':500,'\x81':350,'\x82':333,'\x83':500,
+ '\x84':500,'\x85':1000,'\x86':500,'\x87':500,'\x88':333,'\x89':1000,'\x8a':556,'\x8b':333,'\x8c':944,'\x8d':350,'\x8e':611,'\x8f':350,'\x90':350,'\x91':333,'\x92':333,'\x93':500,'\x94':500,'\x95':350,'\x96':500,'\x97':1000,'\x98':333,'\x99':1000,
+ '\x9a':389,'\x9b':333,'\x9c':722,'\x9d':350,'\x9e':389,'\x9f':611,'\xa0':250,'\xa1':389,'\xa2':500,'\xa3':500,'\xa4':500,'\xa5':500,'\xa6':220,'\xa7':500,'\xa8':333,'\xa9':747,'\xaa':266,'\xab':500,'\xac':606,'\xad':333,'\xae':747,'\xaf':333,
+ '\xb0':400,'\xb1':570,'\xb2':300,'\xb3':300,'\xb4':333,'\xb5':576,'\xb6':500,'\xb7':250,'\xb8':333,'\xb9':300,'\xba':300,'\xbb':500,'\xbc':750,'\xbd':750,'\xbe':750,'\xbf':500,'\xc0':667,'\xc1':667,'\xc2':667,'\xc3':667,'\xc4':667,'\xc5':667,
+ '\xc6':944,'\xc7':667,'\xc8':667,'\xc9':667,'\xca':667,'\xcb':667,'\xcc':389,'\xcd':389,'\xce':389,'\xcf':389,'\xd0':722,'\xd1':722,'\xd2':722,'\xd3':722,'\xd4':722,'\xd5':722,'\xd6':722,'\xd7':570,'\xd8':722,'\xd9':722,'\xda':722,'\xdb':722,
+ '\xdc':722,'\xdd':611,'\xde':611,'\xdf':500,'\xe0':500,'\xe1':500,'\xe2':500,'\xe3':500,'\xe4':500,'\xe5':500,'\xe6':722,'\xe7':444,'\xe8':444,'\xe9':444,'\xea':444,'\xeb':444,'\xec':278,'\xed':278,'\xee':278,'\xef':278,'\xf0':500,'\xf1':556,
+ '\xf2':500,'\xf3':500,'\xf4':500,'\xf5':500,'\xf6':500,'\xf7':570,'\xf8':500,'\xf9':556,'\xfa':556,'\xfb':556,'\xfc':556,'\xfd':444,'\xfe':500,'\xff':444}
+
+fpdf_charwidths['timesI']={
+ '\x00':250,'\x01':250,'\x02':250,'\x03':250,'\x04':250,'\x05':250,'\x06':250,'\x07':250,'\x08':250,'\t':250,'\n':250,'\x0b':250,'\x0c':250,'\r':250,'\x0e':250,'\x0f':250,'\x10':250,'\x11':250,'\x12':250,'\x13':250,'\x14':250,'\x15':250,
+ '\x16':250,'\x17':250,'\x18':250,'\x19':250,'\x1a':250,'\x1b':250,'\x1c':250,'\x1d':250,'\x1e':250,'\x1f':250,' ':250,'!':333,'"':420,'#':500,'$':500,'%':833,'&':778,'\'':214,'(':333,')':333,'*':500,'+':675,
+ ',':250,'-':333,'.':250,'/':278,'0':500,'1':500,'2':500,'3':500,'4':500,'5':500,'6':500,'7':500,'8':500,'9':500,':':333,';':333,'<':675,'=':675,'>':675,'?':500,'@':920,'A':611,
+ 'B':611,'C':667,'D':722,'E':611,'F':611,'G':722,'H':722,'I':333,'J':444,'K':667,'L':556,'M':833,'N':667,'O':722,'P':611,'Q':722,'R':611,'S':500,'T':556,'U':722,'V':611,'W':833,
+ 'X':611,'Y':556,'Z':556,'[':389,'\\':278,']':389,'^':422,'_':500,'`':333,'a':500,'b':500,'c':444,'d':500,'e':444,'f':278,'g':500,'h':500,'i':278,'j':278,'k':444,'l':278,'m':722,
+ 'n':500,'o':500,'p':500,'q':500,'r':389,'s':389,'t':278,'u':500,'v':444,'w':667,'x':444,'y':444,'z':389,'{':400,'|':275,'}':400,'~':541,'\x7f':350,'\x80':500,'\x81':350,'\x82':333,'\x83':500,
+ '\x84':556,'\x85':889,'\x86':500,'\x87':500,'\x88':333,'\x89':1000,'\x8a':500,'\x8b':333,'\x8c':944,'\x8d':350,'\x8e':556,'\x8f':350,'\x90':350,'\x91':333,'\x92':333,'\x93':556,'\x94':556,'\x95':350,'\x96':500,'\x97':889,'\x98':333,'\x99':980,
+ '\x9a':389,'\x9b':333,'\x9c':667,'\x9d':350,'\x9e':389,'\x9f':556,'\xa0':250,'\xa1':389,'\xa2':500,'\xa3':500,'\xa4':500,'\xa5':500,'\xa6':275,'\xa7':500,'\xa8':333,'\xa9':760,'\xaa':276,'\xab':500,'\xac':675,'\xad':333,'\xae':760,'\xaf':333,
+ '\xb0':400,'\xb1':675,'\xb2':300,'\xb3':300,'\xb4':333,'\xb5':500,'\xb6':523,'\xb7':250,'\xb8':333,'\xb9':300,'\xba':310,'\xbb':500,'\xbc':750,'\xbd':750,'\xbe':750,'\xbf':500,'\xc0':611,'\xc1':611,'\xc2':611,'\xc3':611,'\xc4':611,'\xc5':611,
+ '\xc6':889,'\xc7':667,'\xc8':611,'\xc9':611,'\xca':611,'\xcb':611,'\xcc':333,'\xcd':333,'\xce':333,'\xcf':333,'\xd0':722,'\xd1':667,'\xd2':722,'\xd3':722,'\xd4':722,'\xd5':722,'\xd6':722,'\xd7':675,'\xd8':722,'\xd9':722,'\xda':722,'\xdb':722,
+ '\xdc':722,'\xdd':556,'\xde':611,'\xdf':500,'\xe0':500,'\xe1':500,'\xe2':500,'\xe3':500,'\xe4':500,'\xe5':500,'\xe6':667,'\xe7':444,'\xe8':444,'\xe9':444,'\xea':444,'\xeb':444,'\xec':278,'\xed':278,'\xee':278,'\xef':278,'\xf0':500,'\xf1':500,
+ '\xf2':500,'\xf3':500,'\xf4':500,'\xf5':500,'\xf6':500,'\xf7':675,'\xf8':500,'\xf9':500,'\xfa':500,'\xfb':500,'\xfc':500,'\xfd':444,'\xfe':500,'\xff':444}
+
+fpdf_charwidths['zapfdingbats']={
+ '\x00':0,'\x01':0,'\x02':0,'\x03':0,'\x04':0,'\x05':0,'\x06':0,'\x07':0,'\x08':0,'\t':0,'\n':0,'\x0b':0,'\x0c':0,'\r':0,'\x0e':0,'\x0f':0,'\x10':0,'\x11':0,'\x12':0,'\x13':0,'\x14':0,'\x15':0,
+ '\x16':0,'\x17':0,'\x18':0,'\x19':0,'\x1a':0,'\x1b':0,'\x1c':0,'\x1d':0,'\x1e':0,'\x1f':0,' ':278,'!':974,'"':961,'#':974,'$':980,'%':719,'&':789,'\'':790,'(':791,')':690,'*':960,'+':939,
+ ',':549,'-':855,'.':911,'/':933,'0':911,'1':945,'2':974,'3':755,'4':846,'5':762,'6':761,'7':571,'8':677,'9':763,':':760,';':759,'<':754,'=':494,'>':552,'?':537,'@':577,'A':692,
+ 'B':786,'C':788,'D':788,'E':790,'F':793,'G':794,'H':816,'I':823,'J':789,'K':841,'L':823,'M':833,'N':816,'O':831,'P':923,'Q':744,'R':723,'S':749,'T':790,'U':792,'V':695,'W':776,
+ 'X':768,'Y':792,'Z':759,'[':707,'\\':708,']':682,'^':701,'_':826,'`':815,'a':789,'b':789,'c':707,'d':687,'e':696,'f':689,'g':786,'h':787,'i':713,'j':791,'k':785,'l':791,'m':873,
+ 'n':761,'o':762,'p':762,'q':759,'r':759,'s':892,'t':892,'u':788,'v':784,'w':438,'x':138,'y':277,'z':415,'{':392,'|':392,'}':668,'~':668,'\x7f':0,'\x80':390,'\x81':390,'\x82':317,'\x83':317,
+ '\x84':276,'\x85':276,'\x86':509,'\x87':509,'\x88':410,'\x89':410,'\x8a':234,'\x8b':234,'\x8c':334,'\x8d':334,'\x8e':0,'\x8f':0,'\x90':0,'\x91':0,'\x92':0,'\x93':0,'\x94':0,'\x95':0,'\x96':0,'\x97':0,'\x98':0,'\x99':0,
+ '\x9a':0,'\x9b':0,'\x9c':0,'\x9d':0,'\x9e':0,'\x9f':0,'\xa0':0,'\xa1':732,'\xa2':544,'\xa3':544,'\xa4':910,'\xa5':667,'\xa6':760,'\xa7':760,'\xa8':776,'\xa9':595,'\xaa':694,'\xab':626,'\xac':788,'\xad':788,'\xae':788,'\xaf':788,
+ '\xb0':788,'\xb1':788,'\xb2':788,'\xb3':788,'\xb4':788,'\xb5':788,'\xb6':788,'\xb7':788,'\xb8':788,'\xb9':788,'\xba':788,'\xbb':788,'\xbc':788,'\xbd':788,'\xbe':788,'\xbf':788,'\xc0':788,'\xc1':788,'\xc2':788,'\xc3':788,'\xc4':788,'\xc5':788,
+ '\xc6':788,'\xc7':788,'\xc8':788,'\xc9':788,'\xca':788,'\xcb':788,'\xcc':788,'\xcd':788,'\xce':788,'\xcf':788,'\xd0':788,'\xd1':788,'\xd2':788,'\xd3':788,'\xd4':894,'\xd5':838,'\xd6':1016,'\xd7':458,'\xd8':748,'\xd9':924,'\xda':748,'\xdb':918,
+ '\xdc':927,'\xdd':928,'\xde':928,'\xdf':834,'\xe0':873,'\xe1':828,'\xe2':924,'\xe3':924,'\xe4':917,'\xe5':930,'\xe6':931,'\xe7':463,'\xe8':883,'\xe9':836,'\xea':836,'\xeb':867,'\xec':867,'\xed':696,'\xee':696,'\xef':874,'\xf0':0,'\xf1':874,
+ '\xf2':760,'\xf3':946,'\xf4':771,'\xf5':865,'\xf6':771,'\xf7':888,'\xf8':967,'\xf9':888,'\xfa':831,'\xfb':873,'\xfc':927,'\xfd':970,'\xfe':918,'\xff':0}
+
diff --git a/gluon/contrib/pyfpdf/fpdf.py b/gluon/contrib/pyfpdf/fpdf.py
index fec42f03..dd8d2a7f 100644
--- a/gluon/contrib/pyfpdf/fpdf.py
+++ b/gluon/contrib/pyfpdf/fpdf.py
@@ -1,20 +1,27 @@
#!/usr/bin/env python
# -*- coding: latin-1 -*-
-# ******************************************************************************
-# * Software: FPDF for python *
-# * Version: 1.54c *
-# * Date: 2010-09-10 *
-# * License: LGPL v3.0 *
-# * *
-# * Original Author (PHP): Olivier PLATHEY 2004-12-31 *
-# * Ported to Python 2.4 by Max (maxpat78@yahoo.it) on 2006-05 *
-# * Maintainer: Mariano Reingart (reingart@gmail.com) et al since 2008 (est.) *
-# * NOTE: 'I' and 'D' destinations are disabled, and simply print to STDOUT *
-# *****************************************************************************/
+# ****************************************************************************
+# * Software: FPDF for python *
+# * Version: 1.7.1 *
+# * Date: 2010-09-10 *
+# * Last update: 2012-08-16 *
+# * License: LGPL v3.0 *
+# * *
+# * Original Author (PHP): Olivier PLATHEY 2004-12-31 *
+# * Ported to Python 2.4 by Max (maxpat78@yahoo.it) on 2006-05 *
+# * Maintainer: Mariano Reingart (reingart@gmail.com) et al since 2008 est. *
+# * NOTE: 'I' and 'D' destinations are disabled, and simply print to STDOUT *
+# ****************************************************************************
from datetime import datetime
import math
-import os, sys, zlib, struct
+import errno
+import os, sys, zlib, struct, re, tempfile, struct
+
+try:
+ import cPickle as pickle
+except ImportError:
+ import pickle
try:
# Check if PIL is available, necessary for JPEG support.
@@ -22,115 +29,62 @@ try:
except ImportError:
Image = None
-def substr(s, start, length=-1):
- if length < 0:
- length=len(s)-start
- return s[start:start+length]
-def sprintf(fmt, *args): return fmt % args
+from ttfonts import TTFontFile
+from fonts import fpdf_charwidths
+from php import substr, sprintf, print_r, UTF8ToUTF16BE, UTF8StringToArray
+
# Global variables
-FPDF_VERSION='1.54b'
-FPDF_FONT_DIR=os.path.join(os.path.dirname(__file__),'font')
-fpdf_charwidths = {}
+FPDF_VERSION = '1.7.1'
+FPDF_FONT_DIR = os.path.join(os.path.dirname(__file__),'font')
+SYSTEM_TTFONTS = None
-class FPDF:
-#Private properties
-#~ page; #current page number
-#~ n; #current object number
-#~ offsets; #array of object offsets
-#~ buffer; #buffer holding in-memory PDF
-#~ pages; #array containing pages
-#~ state; #current document state
-#~ compress; #compression flag
-#~ def_orientation; #default orientation
-#~ cur_orientation; #current orientation
-#~ orientation_changes; #array indicating orientation changes
-#~ k; #scale factor (number of points in user unit)
-#~ fw_pt,fh_pt; #dimensions of page format in points
-#~ fw,fh; #dimensions of page format in user unit
-#~ w_pt,h_pt; #current dimensions of page in points
-#~ w,h; #current dimensions of page in user unit
-#~ l_margin; #left margin
-#~ t_margin; #top margin
-#~ r_margin; #right margin
-#~ b_margin; #page break margin
-#~ c_margin; #cell margin
-#~ x,y; #current position in user unit for cell positioning
-#~ lasth; #height of last cell printed
-#~ line_width; #line width in user unit
-#~ core_fonts; #array of standard font names
-#~ fonts; #array of used fonts
-#~ font_files; #array of font files
-#~ diffs; #array of encoding differences
-#~ images; #array of used images
-#~ page_links; #array of links in pages
-#~ links; #array of internal links
-#~ font_family; #current font family
-#~ font_style; #current font style
-#~ underline; #underlining flag
-#~ current_font; #current font info
-#~ font_size_pt; #current font size in points
-#~ font_size; #current font size in user unit
-#~ draw_color; #commands for drawing color
-#~ fill_color; #commands for filling color
-#~ text_color; #commands for text color
-#~ color_flag; #indicates whether fill and text colors are different
-#~ ws; #word spacing
-#~ auto_page_break; #automatic page breaking
-#~ page_break_trigger; #threshold used to trigger page breaks
-#~ in_footer; #flag set when processing footer
-#~ zoom_mode; #zoom display mode
-#~ layout_mode; #layout display mode
-#~ title; #title
-#~ subject; #subject
-#~ author; #author
-#~ keywords; #keywords
-#~ creator; #creator
-#~ alias_nb_pages; #alias for total number of pages
-#~ pdf_version; #PDF version number
+PY3K = sys.version_info >= (3, 0)
-# ******************************************************************************
-# * *
-# * Public methods *
-# * *
-# *******************************************************************************/
+def set_global(var, val):
+ globals()[var] = val
+
+
+class FPDF(object):
+ "PDF Generation class"
+
def __init__(self, orientation='P',unit='mm',format='A4'):
- #Some checks
+ # Some checks
self._dochecks()
- #Initialization of properties
- self.offsets={}
- self.page=0
- self.n=2
- self.buffer=''
- self.pages={}
- self.orientation_changes={}
- self.state=0
- self.fonts={}
- self.font_files={}
- self.diffs={}
- self.images={}
- self.page_links={}
- self.links={}
- self.in_footer=0
+ # Initialization of properties
+ self.offsets={} # array of object offsets
+ self.page=0 # current page number
+ self.n=2 # current object number
+ self.buffer='' # buffer holding in-memory PDF
+ self.pages={} # array containing pages
+ self.orientation_changes={} # array indicating orientation changes
+ self.state=0 # current document state
+ self.fonts={} # array of used fonts
+ self.font_files={} # array of font files
+ self.diffs={} # array of encoding differences
+ self.images={} # array of used images
+ self.page_links={} # array of links in pages
+ self.links={} # array of internal links
+ self.in_footer=0 # flag set when processing footer
self.lastw=0
- self.lasth=0
- self.font_family=''
- self.font_style=''
- self.font_size_pt=12
- self.underline=0
+ self.lasth=0 # height of last cell printed
+ self.font_family='' # current font family
+ self.font_style='' # current font style
+ self.font_size_pt=12 # current font size in points
+ self.underline=0 # underlining flag
self.draw_color='0 G'
self.fill_color='0 g'
self.text_color='0 g'
- self.color_flag=0
- self.ws=0
+ self.color_flag=0 # indicates whether fill and text colors are different
+ self.ws=0 # word spacing
self.angle=0
- #Standard fonts
+ # Standard fonts
self.core_fonts={'courier':'Courier','courierB':'Courier-Bold','courierI':'Courier-Oblique','courierBI':'Courier-BoldOblique',
'helvetica':'Helvetica','helveticaB':'Helvetica-Bold','helveticaI':'Helvetica-Oblique','helveticaBI':'Helvetica-BoldOblique',
'times':'Times-Roman','timesB':'Times-Bold','timesI':'Times-Italic','timesBI':'Times-BoldItalic',
'symbol':'Symbol','zapfdingbats':'ZapfDingbats'}
- #Scale factor
+ # Scale factor
if(unit=='pt'):
self.k=1
elif(unit=='mm'):
@@ -141,7 +95,7 @@ class FPDF:
self.k=72
else:
self.error('Incorrect unit: '+unit)
- #Page format
+ # Page format
if(isinstance(format,basestring)):
format=format.lower()
if(format=='a3'):
@@ -163,7 +117,7 @@ class FPDF:
self.fh_pt=format[1]*self.k
self.fw=self.fw_pt/self.k
self.fh=self.fh_pt/self.k
- #Page orientation
+ # Page orientation
orientation=orientation.lower()
if(orientation=='p' or orientation=='portrait'):
self.def_orientation='P'
@@ -178,20 +132,20 @@ class FPDF:
self.cur_orientation=self.def_orientation
self.w=self.w_pt/self.k
self.h=self.h_pt/self.k
- #Page margins (1 cm)
+ # Page margins (1 cm)
margin=28.35/self.k
self.set_margins(margin,margin)
- #Interior cell margin (1 mm)
+ # Interior cell margin (1 mm)
self.c_margin=margin/10.0
- #line width (0.2 mm)
+ # line width (0.2 mm)
self.line_width=.567/self.k
- #Automatic page break
+ # Automatic page break
self.set_auto_page_break(1,2*margin)
- #Full width display mode
+ # Full width display mode
self.set_display_mode('fullwidth')
- #Enable compression
+ # Enable compression
self.set_compression(1)
- #Set default PDF version number
+ # Set default PDF version number
self.pdf_version='1.3'
def set_margins(self, left,top,right=-1):
@@ -389,8 +343,20 @@ class FPDF:
cw=self.current_font['cw']
w=0
l=len(s)
- for i in xrange(0, l):
- w += cw.get(s[i],0)
+ if self.unifontsubset:
+ for char in s:
+ char = ord(char)
+ if len(cw) > char:
+ w += cw[char] # ord(cw[2*char])<<8 + ord(cw[2*char+1])
+ #elif (char>0 and char<128 and isset($cw[chr($char)])) { $w += $cw[chr($char)]; }
+ elif (self.current_font['desc']['MissingWidth']) :
+ w += self.current_font['desc']['MissingWidth']
+ #elif (isset($this->CurrentFont['MissingWidth'])) { $w += $this->CurrentFont['MissingWidth']; }
+ else:
+ w += 500
+ else:
+ for i in xrange(0, l):
+ w += cw.get(s[i],0)
return w*self.font_size/1000.0
def set_line_width(self, width):
@@ -413,42 +379,119 @@ class FPDF:
op='S'
self._out(sprintf('%.2f %.2f %.2f %.2f re %s',x*self.k,(self.h-y)*self.k,w*self.k,-h*self.k,op))
- def add_font(self, family,style='',fname=''):
+ def add_font(self, family, style='', fname='', uni=False):
"Add a TrueType or Type1 font"
- family=family.lower()
- if(fname==''):
- fname=family.replace(' ','')+style.lower()+'.font'
- fname=os.path.join(FPDF_FONT_DIR,fname)
- if(family=='arial'):
- family='helvetica'
- style=style.upper()
- if(style=='IB'):
- style='BI'
- fontkey=family+style
+ family = family.lower()
+ if (fname == ''):
+ fname = family.replace(' ','') + style.lower() + '.pkl'
+ if (family == 'arial'):
+ family = 'helvetica'
+ style = style.upper()
+ if (style == 'IB'):
+ style = 'BI'
+ fontkey = family+style
if fontkey in self.fonts:
- self.error('Font already added: '+family+' '+style)
- execfile(fname, globals(), globals())
- if 'name' not in globals():
- self.error('Could not include font definition file')
- i=len(self.fonts)+1
- self.fonts[fontkey]={'i':i,'type':type,'name':name,'desc':desc,'up':up,'ut':ut,'cw':cw,'enc':enc,'file':filename}
- if(diff):
- #Search existing encodings
- d=0
- nb=len(self.diffs)
- for i in xrange(1,nb+1):
- if(self.diffs[i]==diff):
- d=i
- break
- if(d==0):
- d=nb+1
- self.diffs[d]=diff
- self.fonts[fontkey]['diff']=d
- if(filename):
- if(type=='TrueType'):
- self.font_files[filename]={'length1':originalsize}
+ # Font already added!
+ return
+ if (uni):
+ global SYSTEM_TTFONTS
+ if os.path.exists(fname):
+ ttffilename = fname
+ elif (FPDF_FONT_DIR and
+ os.path.exists(os.path.join(FPDF_FONT_DIR, fname))):
+ ttffilename = os.path.join(FPDF_FONT_DIR, fname)
+ elif (SYSTEM_TTFONTS and
+ os.path.exists(os.path.join(SYSTEM_TTFONTS, fname))):
+ ttffilename = os.path.join(SYSTEM_TTFONTS, fname)
else:
- self.font_files[filename]={'length1':size1,'length2':size2}
+ raise RuntimeError("TTF Font file not found: %s" % fname)
+ unifilename = os.path.splitext(ttffilename)[0] + '.pkl'
+ name = ''
+ if os.path.exists(unifilename):
+ fh = open(unifilename)
+ try:
+ font_dict = pickle.load(fh)
+ finally:
+ fh.close()
+ else:
+ ttf = TTFontFile()
+ ttf.getMetrics(ttffilename)
+ desc = {
+ 'Ascent': int(round(ttf.ascent, 0)),
+ 'Descent': int(round(ttf.descent, 0)),
+ 'CapHeight': int(round(ttf.capHeight, 0)),
+ 'Flags': ttf.flags,
+ 'FontBBox': "[%s %s %s %s]" % (
+ int(round(ttf.bbox[0], 0)),
+ int(round(ttf.bbox[1], 0)),
+ int(round(ttf.bbox[2], 0)),
+ int(round(ttf.bbox[3], 0))),
+ 'ItalicAngle': int(ttf.italicAngle),
+ 'StemV': int(round(ttf.stemV, 0)),
+ 'MissingWidth': int(round(ttf.defaultWidth, 0)),
+ }
+ # Generate metrics .pkl file
+ font_dict = {
+ 'name': re.sub('[ ()]', '', ttf.fullName),
+ 'type': 'TTF',
+ 'desc': desc,
+ 'up': round(ttf.underlinePosition),
+ 'ut': round(ttf.underlineThickness),
+ 'ttffile': ttffilename,
+ 'fontkey': fontkey,
+ 'originalsize': os.stat(ttffilename).st_size,
+ 'cw': ttf.charWidths,
+ }
+ try:
+ fh = open(unifilename, "w")
+ pickle.dump(font_dict, fh)
+ fh.close()
+ except IOError as e:
+ if not e.errno == errno.EACCES:
+ raise # Not a permission error.
+ del ttf
+ if hasattr(self,'str_alias_nb_pages'):
+ sbarr = range(0,57) # include numbers in the subset!
+ else:
+ sbarr = range(0,32)
+ self.fonts[fontkey] = {
+ 'i': len(self.fonts)+1, 'type': font_dict['type'],
+ 'name': font_dict['name'], 'desc': font_dict['desc'],
+ 'up': font_dict['up'], 'ut': font_dict['ut'],
+ 'cw': font_dict['cw'],
+ 'ttffile': font_dict['ttffile'], 'fontkey': fontkey,
+ 'subset': sbarr, 'unifilename': unifilename,
+ }
+ self.font_files[fontkey] = {'length1': font_dict['originalsize'],
+ 'type': "TTF", 'ttffile': ttffilename}
+ self.font_files[fname] = {'type': "TTF"}
+ else:
+ fontfile = open(fname)
+ try:
+ font_dict = pickle.load(fontfile)
+ finally:
+ fontfile.close()
+ self.fonts[fontkey] = {'i': len(self.fonts)+1}
+ self.fonts[fontkey].update(font_dict)
+ if (diff):
+ #Search existing encodings
+ d = 0
+ nb = len(self.diffs)
+ for i in xrange(1, nb+1):
+ if(self.diffs[i] == diff):
+ d = i
+ break
+ if (d == 0):
+ d = nb + 1
+ self.diffs[d] = diff
+ self.fonts[fontkey]['diff'] = d
+ filename = font_dict.get('filename')
+ if (filename):
+ if (type == 'TrueType'):
+ self.font_files[filename]={'length1': originalsize}
+ else:
+ self.font_files[filename]={'length1': size1,
+ 'length2': size2}
def set_font(self, family,style='',size=0):
"Select a font; size given in points"
@@ -495,6 +538,7 @@ class FPDF:
self.font_size_pt=size
self.font_size=size/self.k
self.current_font=self.fonts[fontkey]
+ self.unifontsubset = (self.fonts[fontkey]['type'] == 'TTF')
if(self.page>0):
self._out(sprintf('BT /F%d %.2f Tf ET',self.current_font['i'],self.font_size_pt))
@@ -527,9 +571,16 @@ class FPDF:
self.page_links[self.page] = []
self.page_links[self.page] += [(x*self.k,self.h_pt-y*self.k,w*self.k,h*self.k,link),]
- def text(self, x,y,txt):
+ def text(self, x, y, txt=''):
"Output a string"
- s=sprintf('BT %.2f %.2f Td (%s) Tj ET',x*self.k,(self.h-y)*self.k,self._escape(txt))
+ txt = self.normalize_text(txt)
+ if (self.unifontsubset):
+ txt2 = self._escape(UTF8ToUTF16BE(txt, False))
+ for uni in UTF8StringToArray(txt):
+ self.current_font['subset'].append(uni)
+ else:
+ txt2 = self._escape(txt)
+ s=sprintf('BT %.2f %.2f Td (%s) Tj ET',x*self.k,(self.h-y)*self.k, txt2)
if(self.underline and txt!=''):
s+=' '+self._dounderline(x,y,txt)
if(self.color_flag):
@@ -559,6 +610,7 @@ class FPDF:
def cell(self, w,h=0,txt='',border=0,ln=0,align='',fill=0,link=''):
"Output a cell"
+ txt = self.normalize_text(txt)
k=self.k
if(self.y+h>self.page_break_trigger and not self.in_footer and self.accept_page_break()):
#Automatic page break
@@ -604,8 +656,33 @@ class FPDF:
dx=self.c_margin
if(self.color_flag):
s+='q '+self.text_color+' '
- txt2=txt.replace('\\','\\\\').replace(')','\\)').replace('(','\\(')
- s+=sprintf('BT %.2f %.2f Td (%s) Tj ET',(self.x+dx)*k,(self.h-(self.y+.5*h+.3*self.font_size))*k,txt2)
+
+ # If multibyte, Tw has no effect - do word spacing using an adjustment before each space
+ if (self.ws and self.unifontsubset):
+ for uni in UTF8StringToArray(txt):
+ self.current_font['subset'].append(uni)
+ space = self._escape(UTF8ToUTF16BE(' ', False))
+ s += sprintf('BT 0 Tw %.2F %.2F Td [',(self.x + dx) * k,(self.h - (self.y + 0.5*h+ 0.3 * self.font_size)) * k)
+ t = txt.split(' ')
+ numt = len(t)
+ for i in range(numt):
+ tx = t[i]
+ tx = '(' + self._escape(UTF8ToUTF16BE(tx, False)) + ')'
+ s += sprintf('%s ', tx);
+ if ((i+1)0):
self.ws=0
- self._out('0 Tw')
+ if not split_only:
+ self._out('0 Tw')
if not split_only:
self.cell(w,h,substr(s,j,i-j),b,2,align,fill)
else:
@@ -681,7 +760,10 @@ class FPDF:
sep=i
ls=l
ns+=1
- l+=cw.get(c,0)
+ if self.unifontsubset:
+ l += self.get_string_width(c) / self.font_size*1000.0
+ else:
+ l += cw.get(c,0)
if(l>wmax):
#Automatic line break
if(sep==-1):
@@ -689,7 +771,8 @@ class FPDF:
i+=1
if(self.ws>0):
self.ws=0
- self._out('0 Tw')
+ if not split_only:
+ self._out('0 Tw')
if not split_only:
self.cell(w,h,substr(s,j,i-j),b,2,align,fill)
else:
@@ -700,7 +783,8 @@ class FPDF:
self.ws=(wmax-ls)/1000.0*self.font_size/(ns-1)
else:
self.ws=0
- self._out(sprintf('%.3f Tw',self.ws*self.k))
+ if not split_only:
+ self._out(sprintf('%.3f Tw',self.ws*self.k))
if not split_only:
self.cell(w,h,substr(s,j,sep-j),b,2,align,fill)
else:
@@ -718,18 +802,20 @@ class FPDF:
#Last chunk
if(self.ws>0):
self.ws=0
- self._out('0 Tw')
+ if not split_only:
+ self._out('0 Tw')
if(border and 'B' in border):
b+='B'
if not split_only:
self.cell(w,h,substr(s,j,i-j),b,2,align,fill)
+ self.x=self.l_margin
else:
ret.append(substr(s,j,i-j))
- self.x=self.l_margin
return ret
- def write(self, h,txt,link=''):
+ def write(self, h, txt='', link=''):
"Output text in flowing mode"
+ txt = self.normalize_text(txt)
cw=self.current_font['cw']
w=self.w-self.r_margin-self.x
wmax=(w-2*self.c_margin)*1000.0/self.font_size
@@ -758,7 +844,10 @@ class FPDF:
continue
if(c==' '):
sep=i
- l+=cw.get(c,0)
+ if self.unifontsubset:
+ l += self.get_string_width(c) / self.font_size*1000.0
+ else:
+ l += cw.get(c,0)
if(l>wmax):
#Automatic line break
if(sep==-1):
@@ -791,7 +880,7 @@ class FPDF:
if(i!=j):
self.cell(l/1000.0*self.font_size,h,substr(s,j),0,0,'',0,link)
- def image(self, name,x,y,w=0,h=0,type='',link=''):
+ def image(self, name, x=None, y=None, w=0,h=0,type='',link=''):
"Put an image on the page"
if not name in self.images:
#First use of image, get info
@@ -810,7 +899,7 @@ class FPDF:
mtd='_parse'+type
if not hasattr(self,mtd):
self.error('Unsupported image type: '+type)
- info=self.mtd(name)
+ info=getattr(self, mtd)(name)
info['i']=len(self.images)+1
self.images[name]=info
else:
@@ -820,10 +909,21 @@ class FPDF:
#Put image at 72 dpi
w=info['w']/self.k
h=info['h']/self.k
- if(w==0):
+ elif(w==0):
w=h*info['w']/info['h']
- if(h==0):
+ elif(h==0):
h=w*info['h']/info['w']
+ # Flowing mode
+ if y is None:
+ if (self.y + h > self.page_break_trigger and not self.in_footer and self.accept_page_break()):
+ #Automatic page break
+ x = self.x
+ self.add_page(self.cur_orientation)
+ self.x = x
+ y = self.y
+ self.y += h
+ if x is None:
+ x = self.x
self._out(sprintf('q %.2f 0 0 %.2f %.2f %.2f cm /I%d Do Q',w*self.k,h*self.k,x*self.k,(self.h-(y+h))*self.k,info['i']))
if(link):
self.link(x,y,w,h,link)
@@ -869,12 +969,6 @@ class FPDF:
#Finish document if necessary
if(self.state<3):
self.close()
- #Normalize parameters
- # if(type(dest)==type(bool())):
- # if dest:
- # dest='D'
- # else:
- # dest='F'
dest=dest.upper()
if(dest==''):
if(name==''):
@@ -888,10 +982,14 @@ class FPDF:
print self.buffer
elif dest=='F':
#Save to local file
- f=file(name,'wb')
+ f=open(name,'wb')
if(not f):
self.error('Unable to create output file: '+name)
- f.write(self.buffer)
+ if PY3K:
+ # TODO: proper unicode support
+ f.write(self.buffer.encode("latin1"))
+ else:
+ f.write(self.buffer)
f.close()
elif dest=='S':
#Return as a string
@@ -900,11 +998,17 @@ class FPDF:
self.error('Incorrect output destination: '+dest)
return ''
-# ******************************************************************************
-# * *
-# * Protected methods *
-# * *
-# *******************************************************************************/
+ def normalize_text(self, txt):
+ "Check that text input is in the correct format/encoding"
+ # - for TTF unicode fonts: unicode object (utf8 encoding)
+ # - for built-in fonts: string instances (latin 1 encoding)
+ if self.unifontsubset and isinstance(txt, str):
+ txt = txt.decode('utf8')
+ elif not self.unifontsubset and isinstance(txt, unicode) and not PY3K:
+ txt = txt.encode('latin1')
+ return txt
+
+
def _dochecks(self):
#Check for locale-related bug
# if(1.1==1):
@@ -920,7 +1024,12 @@ class FPDF:
def _putpages(self):
nb=self.page
if hasattr(self,'str_alias_nb_pages'):
- #Replace number of pages
+ # Replace number of pages in fonts using subsets (unicode)
+ alias = UTF8ToUTF16BE(self.str_alias_nb_pages, False);
+ r = UTF8ToUTF16BE(str(nb), False)
+ for n in xrange(1, nb+1):
+ self.pages[n] = self.pages[n].replace(alias, r)
+ # Now repeat for no pages in non-subset fonts
for n in xrange(1,nb+1):
self.pages[n]=self.pages[n].replace(self.str_alias_nb_pages,str(nb))
if(self.def_orientation=='P'):
@@ -957,6 +1066,8 @@ class FPDF:
h=h_pt
annots+=sprintf('/Dest [%d 0 R /XYZ 0 %.2f null]>>',1+2*l[0],h-l[1]*self.k)
self._out(annots+']')
+ if(self.pdf_version>'1.3'):
+ self._out('/Group <>')
self._out('/Contents '+str(self.n+1)+' 0 R>>')
self._out('endobj')
#Page content
@@ -989,33 +1100,34 @@ class FPDF:
self._out('<>')
self._out('endobj')
for name,info in self.font_files.iteritems():
- #Font file embedding
- self._newobj()
- self.font_files[name]['n']=self.n
- font=''
- f=file(self._getfontpath()+name,'rb',1)
- if(not f):
- self.error('Font file not found')
- font=f.read()
- f.close()
- compressed=(substr(name,-2)=='.z')
- if(not compressed and 'length2' in info):
- header=(ord(font[0])==128)
- if(header):
- #Strip first binary header
- font=substr(font,6)
- if(header and ord(font[info['length1']])==128):
- #Strip second binary header
- font=substr(font,0,info['length1'])+substr(font,info['length1']+6)
- self._out('<>')
- self._putstream(font)
- self._out('endobj')
+ if 'type' in info and info['type'] != 'TTF':
+ #Font file embedding
+ self._newobj()
+ self.font_files[name]['n']=self.n
+ font=''
+ f=open(self._getfontpath()+name,'rb',1)
+ if(not f):
+ self.error('Font file not found')
+ font=f.read()
+ f.close()
+ compressed=(substr(name,-2)=='.z')
+ if(not compressed and 'length2' in info):
+ header=(ord(font[0])==128)
+ if(header):
+ #Strip first binary header
+ font=substr(font,6)
+ if(header and ord(font[info['length1']])==128):
+ #Strip second binary header
+ font=substr(font,0,info['length1'])+substr(font,info['length1']+6)
+ self._out('<>')
+ self._putstream(font)
+ self._out('endobj')
for k,font in self.fonts.iteritems():
#Font objects
self.fonts[k]['n']=self.n+1
@@ -1059,8 +1171,8 @@ class FPDF:
#Descriptor
self._newobj()
s='<>')
self._out('endobj')
+ elif (type == 'TTF'):
+ self.fonts[k]['n'] = self.n + 1
+ ttf = TTFontFile()
+ fontname = 'MPDFAA' + '+' + font['name']
+ subset = font['subset']
+ del subset[0]
+ ttfontstream = ttf.makeSubset(font['ttffile'], subset)
+ ttfontsize = len(ttfontstream)
+ fontstream = zlib.compress(ttfontstream)
+ codeToGlyph = ttf.codeToGlyph
+ ##del codeToGlyph[0]
+ # Type0 Font
+ # A composite font - a font composed of other fonts, organized hierarchically
+ self._newobj()
+ self._out('<>')
+ self._out('endobj')
+
+ # CIDFontType2
+ # A CIDFont whose glyph descriptions are based on TrueType font technology
+ self._newobj()
+ self._out('<>')
+ self._out('endobj')
+
+ # ToUnicode
+ self._newobj()
+ toUni = "/CIDInit /ProcSet findresource begin\n" \
+ "12 dict begin\n" \
+ "begincmap\n" \
+ "/CIDSystemInfo\n" \
+ "<> def\n" \
+ "/CMapName /Adobe-Identity-UCS def\n" \
+ "/CMapType 2 def\n" \
+ "1 begincodespacerange\n" \
+ "<0000> \n" \
+ "endcodespacerange\n" \
+ "1 beginbfrange\n" \
+ "<0000> <0000>\n" \
+ "endbfrange\n" \
+ "endcmap\n" \
+ "CMapName currentdict /CMap defineresource pop\n" \
+ "end\n" \
+ "end"
+ self._out('<>')
+ self._putstream(toUni)
+ self._out('endobj')
+
+ # CIDSystemInfo dictionary
+ self._newobj()
+ self._out('<>')
+ self._out('endobj')
+
+ # Font descriptor
+ self._newobj()
+ self._out('<>')
+ self._out('endobj')
+
+ # Embed CIDToGIDMap
+ # A specification of the mapping from CIDs to glyph indices
+ cidtogidmap = '';
+ cidtogidmap = ["\x00"] * 256*256*2
+ for cc, glyph in codeToGlyph.items():
+ cidtogidmap[cc*2] = chr(glyph >> 8)
+ cidtogidmap[cc*2 + 1] = chr(glyph & 0xFF)
+ cidtogidmap = zlib.compress(''.join(cidtogidmap));
+ self._newobj()
+ self._out('<>')
+ self._putstream(cidtogidmap)
+ self._out('endobj')
+
+ #Font file
+ self._newobj()
+ self._out('<>')
+ self._putstream(fontstream)
+ self._out('endobj')
+ del ttf
else:
#Allow for additional types
mtd='_put'+type.lower()
@@ -1076,13 +1298,121 @@ class FPDF:
self.error('Unsupported font type: '+type)
self.mtd(font)
+ def _putTTfontwidths(self, font, maxUni):
+ cw127fname = os.path.splitext(font['unifilename'])[0] + '.cw127.pkl'
+ if (os.path.exists(cw127fname)):
+ fh = open(cw127fname);
+ try:
+ font_dict = pickle.load(fh)
+ finally:
+ fh.close()
+ rangeid = font_dict['rangeid']
+ range_ = font_dict['range']
+ prevcid = font_dict['prevcid']
+ prevwidth = font_dict['prevwidth']
+ interval = font_dict['interval']
+ range_interval = font_dict['range_interval']
+ startcid = 128
+ else:
+ rangeid = 0
+ range_ = {}
+ range_interval = {}
+ prevcid = -2
+ prevwidth = -1
+ interval = False
+ startcid = 1
+ cwlen = maxUni + 1
+
+ # for each character
+ for cid in range(startcid, cwlen):
+ if (cid==128 and not os.path.exists(cw127fname)):
+ try:
+ fh = open(cw127fname, "wb")
+ font_dict = {}
+ font_dict['rangeid'] = rangeid
+ font_dict['prevcid'] = prevcid
+ font_dict['prevwidth'] = prevwidth
+ font_dict['interval'] = interval
+ font_dict['range_interval'] = range_interval
+ font_dict['range'] = range_
+ pickle.dump(font_dict, fh)
+ fh.close()
+ except IOError as e:
+ if not e.errno == errno.EACCES:
+ raise # Not a permission error.
+ if (font['cw'][cid] == 0):
+ continue
+ width = font['cw'][cid]
+ if (width == 65535): width = 0
+ if (cid > 255 and (cid not in font['subset']) or not cid): #
+ continue
+ if ('dw' not in font or (font['dw'] and width != font['dw'])):
+ if (cid == (prevcid + 1)):
+ if (width == prevwidth):
+ if (width == range_[rangeid][0]):
+ range_.setdefault(rangeid, []).append(width)
+ else:
+ range_[rangeid].pop()
+ # new range
+ rangeid = prevcid
+ range_[rangeid] = [prevwidth, width]
+ interval = True
+ range_interval[rangeid] = True
+ else:
+ if (interval):
+ # new range
+ rangeid = cid
+ range_[rangeid] = [width]
+ else:
+ range_[rangeid].append(width)
+ interval = False
+ else:
+ rangeid = cid
+ range_[rangeid] = [width]
+ interval = False
+ prevcid = cid
+ prevwidth = width
+ prevk = -1
+ nextk = -1
+ prevint = False
+ for k, ws in sorted(range_.items()):
+ cws = len(ws)
+ if (k == nextk and not prevint and (not k in range_interval or cws < 3)):
+ if (k in range_interval):
+ del range_interval[k]
+ range_[prevk] = range_[prevk] + range_[k]
+ del range_[k]
+ else:
+ prevk = k
+ nextk = k + cws
+ if (k in range_interval):
+ prevint = (cws > 3)
+ del range_interval[k]
+ nextk -= 1
+ else:
+ prevint = False
+ w = []
+ for k, ws in sorted(range_.items()):
+ if (len(set(ws)) == 1):
+ w.append(' %s %s %s' % (k, k + len(ws) - 1, ws[0]))
+ else:
+ w.append(' %s [ %s ]\n' % (k, ' '.join([str(int(h)) for h in ws]))) ##
+ self._out('/W [%s]' % ''.join(w))
+
def _putimages(self):
filter=''
if self.compress:
filter='/Filter /FlateDecode '
for filename,info in self.images.iteritems():
+ self._putimage(info)
+ del info['data']
+ if 'smask' in info:
+ del info['smask']
+
+ def _putimage(self, info):
+ if 'data' in info:
self._newobj()
- self.images[filename]['n']=self.n
+ info['n']=self.n
self._out('<>')
+ if('trns' in info and isinstance(info['trns'], list)):
trns=''
for i in xrange(0,len(info['trns'])):
trns+=str(info['trns'][i])+' '+str(info['trns'][i])+' '
self._out('/Mask ['+trns+']')
+ if('smask' in info):
+ self._out('/SMask ' + str(self.n+1) + ' 0 R');
self._out('/Length '+str(len(info['data']))+'>>')
self._putstream(info['data'])
- self.images[filename]['data'] = None
self._out('endobj')
+ # Soft mask
+ if('smask' in info):
+ dp = '/Predictor 15 /Colors 1 /BitsPerComponent 8 /Columns ' + str(info['w'])
+ smask = {'w': info['w'], 'h': info['h'], 'cs': 'DeviceGray', 'bpc': 8, 'f': info['f'], 'dp': dp, 'data': info['smask']}
+ self._putimage(smask)
#Palette
if(info['cs']=='Indexed'):
self._newobj()
+ filter = self.compress and '/Filter /FlateDecode ' or ''
if self.compress:
pal=zlib.compress(info['pal'])
else:
@@ -1288,6 +1625,25 @@ class FPDF:
f.close()
return {'w':a[0],'h':a[1],'cs':colspace,'bpc':bpc,'f':'DCTDecode','data':data}
+ def _parsegif(self, filename):
+ # Extract info from a GIF file (via PNG conversion)
+ if Image is None:
+ self.error('PIL is required for GIF support')
+ try:
+ im = Image.open(filename)
+ except Exception, e:
+ self.error('Missing or incorrect image file: %s. error: %s' % (filename, str(e)))
+ else:
+ # Use temporary file
+ f = tempfile.NamedTemporaryFile(delete=False, suffix=".png")
+ tmp = f.name
+ transparency = im.info['transparency']
+ f.close()
+ im.save(tmp, transparency=transparency)
+ info = self._parsepng(tmp)
+ os.unlink(tmp)
+ return info
+
def _parsepng(self, name):
#Extract info from a PNG file
if name.startswith("http://") or name.startswith("https://"):
@@ -1310,14 +1666,14 @@ class FPDF:
if(bpc>8):
self.error('16-bit depth not supported: '+name)
ct=ord(f.read(1))
- if(ct==0):
+ if(ct==0 or ct==4):
colspace='DeviceGray'
- elif(ct==2):
+ elif(ct==2 or ct==6):
colspace='DeviceRGB'
elif(ct==3):
colspace='Indexed'
else:
- self.error('Alpha channel not supported: '+name)
+ self.error('Unknown color type: '+name)
if(ord(f.read(1))!=0):
self.error('Unknown compression method: '+name)
if(ord(f.read(1))!=0):
@@ -1325,12 +1681,12 @@ class FPDF:
if(ord(f.read(1))!=0):
self.error('Interlacing not supported: '+name)
f.read(4)
- parms='/DecodeParms <>'
+ dp+='1'
+ dp+=' /BitsPerComponent '+str(bpc)+' /Columns '+str(w)+''
#Scan chunks looking for palette, transparency and image data
pal=''
trns=''
@@ -1366,7 +1722,39 @@ class FPDF:
if(colspace=='Indexed' and not pal):
self.error('Missing palette in '+name)
f.close()
- return {'w':w,'h':h,'cs':colspace,'bpc':bpc,'f':'FlateDecode','parms':parms,'pal':pal,'trns':trns,'data':data}
+ info = {'w':w,'h':h,'cs':colspace,'bpc':bpc,'f':'FlateDecode','dp':dp,'pal':pal,'trns':trns,}
+ if(ct>=4):
+ # Extract alpha channel
+ data = zlib.decompress(data)
+ color = '';
+ alpha = '';
+ if(ct==4):
+ # Gray image
+ length = 2*w
+ for i in range(h):
+ pos = (1+length)*i
+ color += data[pos]
+ alpha += data[pos]
+ line = substr(data, pos+1, length)
+ color += re.sub('(.).',lambda m: m.group(1),line, flags=re.DOTALL)
+ alpha += re.sub('.(.)',lambda m: m.group(1),line, flags=re.DOTALL)
+ else:
+ # RGB image
+ length = 4*w
+ for i in range(h):
+ pos = (1+length)*i
+ color += data[pos]
+ alpha += data[pos]
+ line = substr(data, pos+1, length)
+ color += re.sub('(.{3}).',lambda m: m.group(1),line, flags=re.DOTALL)
+ alpha += re.sub('.{3}(.)',lambda m: m.group(1),line, flags=re.DOTALL)
+ del data
+ data = zlib.compress(color)
+ info['smask'] = zlib.compress(alpha)
+ if (self.pdf_version < '1.4'):
+ self.pdf_version = '1.4'
+ info['data'] = data
+ return info
def _freadint(self, f):
#Read a 4-byte integer from file
@@ -1397,44 +1785,34 @@ class FPDF:
def interleaved2of5(self, txt, x, y, w=1.0, h=10.0):
"Barcode I2of5 (numeric), adds a 0 if odd lenght"
- narrow = w / 3.0
+ narrow = w / 3.0
wide = w
-
+
# wide/narrow codes for the digits
- bar_char={}
- bar_char['0'] = 'nnwwn'
- bar_char['1'] = 'wnnnw'
- bar_char['2'] = 'nwnnw'
- bar_char['3'] = 'wwnnn'
- bar_char['4'] = 'nnwnw'
- bar_char['5'] = 'wnwnn'
- bar_char['6'] = 'nwwnn'
- bar_char['7'] = 'nnnww'
- bar_char['8'] = 'wnnwn'
- bar_char['9'] = 'nwnwn'
- bar_char['A'] = 'nn'
- bar_char['Z'] = 'wn'
-
+ bar_char={'0': 'nnwwn', '1': 'wnnnw', '2': 'nwnnw', '3': 'wwnnn',
+ '4': 'nnwnw', '5': 'wnwnn', '6': 'nwwnn', '7': 'nnnww',
+ '8': 'wnnwn', '9': 'nwnwn', 'A': 'nn', 'Z': 'wn'}
+
self.set_fill_color(0)
code = txt
# add leading zero if code-length is odd
if len(code) % 2 != 0:
code = '0' + code
-
+
# add start and stop codes
code = 'AA' + code.lower() + 'ZA'
for i in xrange(0, len(code), 2):
# choose next pair of digits
- char_bar = code[i];
- char_space = code[i+1];
+ char_bar = code[i]
+ char_space = code[i+1]
# check whether it is a valid digit
if not char_bar in bar_char.keys():
- raise RuntimeError ('Caractér "%s" inválido para el código de barras I25: ' % char_bar)
+ raise RuntimeError ('Char "%s" invalid for I25: ' % char_bar)
if not char_space in bar_char.keys():
- raise RuntimeError ('Caractér "%s" inválido para el código de barras I25: ' % char_space)
+ raise RuntimeError ('Char "%s" invalid for I25: ' % char_space)
- # create a wide/narrow-sequence (first digit=bars, second digit=spaces)
+ # create a wide/narrow-seq (first digit=bars, second digit=spaces)
seq = ''
for s in xrange(0, len(bar_char[char_bar])):
seq += bar_char[char_bar][s] + bar_char[char_space][s]
@@ -1446,7 +1824,7 @@ class FPDF:
else:
line_width = wide
- # draw every second value, because the second digit of the pair is represented by the spaces
+ # draw every second value, the other is represented by space
if bar % 2 == 0:
self.rect(x, y, line_width, h, 'F')
@@ -1456,68 +1834,38 @@ class FPDF:
def code39(self, txt, x, y, w=1.5, h=5.0):
"Barcode 3of9"
wide = w
- narrow = w /3.0
+ narrow = w / 3.0
gap = narrow
- bar_char={}
- bar_char['0'] = 'nnnwwnwnn'
- bar_char['1'] = 'wnnwnnnnw'
- bar_char['2'] = 'nnwwnnnnw'
- bar_char['3'] = 'wnwwnnnnn'
- bar_char['4'] = 'nnnwwnnnw'
- bar_char['5'] = 'wnnwwnnnn'
- bar_char['6'] = 'nnwwwnnnn'
- bar_char['7'] = 'nnnwnnwnw'
- bar_char['8'] = 'wnnwnnwnn'
- bar_char['9'] = 'nnwwnnwnn'
- bar_char['A'] = 'wnnnnwnnw'
- bar_char['B'] = 'nnwnnwnnw'
- bar_char['C'] = 'wnwnnwnnn'
- bar_char['D'] = 'nnnnwwnnw'
- bar_char['E'] = 'wnnnwwnnn'
- bar_char['F'] = 'nnwnwwnnn'
- bar_char['G'] = 'nnnnnwwnw'
- bar_char['H'] = 'wnnnnwwnn'
- bar_char['I'] = 'nnwnnwwnn'
- bar_char['J'] = 'nnnnwwwnn'
- bar_char['K'] = 'wnnnnnnww'
- bar_char['L'] = 'nnwnnnnww'
- bar_char['M'] = 'wnwnnnnwn'
- bar_char['N'] = 'nnnnwnnww'
- bar_char['O'] = 'wnnnwnnwn'
- bar_char['P'] = 'nnwnwnnwn'
- bar_char['Q'] = 'nnnnnnwww'
- bar_char['R'] = 'wnnnnnwwn'
- bar_char['S'] = 'nnwnnnwwn'
- bar_char['T'] = 'nnnnwnwwn'
- bar_char['U'] = 'wwnnnnnnw'
- bar_char['V'] = 'nwwnnnnnw'
- bar_char['W'] = 'wwwnnnnnn'
- bar_char['X'] = 'nwnnwnnnw'
- bar_char['Y'] = 'wwnnwnnnn'
- bar_char['Z'] = 'nwwnwnnnn'
- bar_char['-'] = 'nwnnnnwnw'
- bar_char['.'] = 'wwnnnnwnn'
- bar_char[' '] = 'nwwnnnwnn'
- bar_char['*'] = 'nwnnwnwnn'
- bar_char['$'] = 'nwnwnwnnn'
- bar_char['/'] = 'nwnwnnnwn'
- bar_char['+'] = 'nwnnnwnwn'
- bar_char['%'] = 'nnnwnwnwn'
+ bar_char={'0': 'nnnwwnwnn', '1': 'wnnwnnnnw', '2': 'nnwwnnnnw',
+ '3': 'wnwwnnnnn', '4': 'nnnwwnnnw', '5': 'wnnwwnnnn',
+ '6': 'nnwwwnnnn', '7': 'nnnwnnwnw', '8': 'wnnwnnwnn',
+ '9': 'nnwwnnwnn', 'A': 'wnnnnwnnw', 'B': 'nnwnnwnnw',
+ 'C': 'wnwnnwnnn', 'D': 'nnnnwwnnw', 'E': 'wnnnwwnnn',
+ 'F': 'nnwnwwnnn', 'G': 'nnnnnwwnw', 'H': 'wnnnnwwnn',
+ 'I': 'nnwnnwwnn', 'J': 'nnnnwwwnn', 'K': 'wnnnnnnww',
+ 'L': 'nnwnnnnww', 'M': 'wnwnnnnwn', 'N': 'nnnnwnnww',
+ 'O': 'wnnnwnnwn', 'P': 'nnwnwnnwn', 'Q': 'nnnnnnwww',
+ 'R': 'wnnnnnwwn', 'S': 'nnwnnnwwn', 'T': 'nnnnwnwwn',
+ 'U': 'wwnnnnnnw', 'V': 'nwwnnnnnw', 'W': 'wwwnnnnnn',
+ 'X': 'nwnnwnnnw', 'Y': 'wwnnwnnnn', 'Z': 'nwwnwnnnn',
+ '-': 'nwnnnnwnw', '.': 'wwnnnnwnn', ' ': 'nwwnnnwnn',
+ '*': 'nwnnwnwnn', '$': 'nwnwnwnnn', '/': 'nwnwnnnwn',
+ '+': 'nwnnnwnwn', '%': 'nnnwnwnwn'}
self.set_fill_color(0)
- code = txt
-
+ code = txt
+
code = code.upper()
for i in xrange (0, len(code), 2):
- char_bar = code[i];
+ char_bar = code[i]
if not char_bar in bar_char.keys():
- raise RuntimeError ('Caracter "%s" inválido para el código de barras' % char_bar)
+ raise RuntimeError ('Char "%s" invalid for Code39' % char_bar)
seq= ''
for s in xrange(0, len(bar_char[char_bar])):
- seq += bar_char[char_bar][s]
+ seq += bar_char[char_bar][s]
for bar in xrange(0, len(seq)):
if seq[bar] == 'n':
@@ -1526,162 +1874,8 @@ class FPDF:
line_width = wide
if bar % 2 == 0:
- self.rect(x,y,line_width,h,'F')
+ self.rect(x, y, line_width, h, 'F')
x += line_width
x += gap
-#End of class
-
-# Fonts:
-
-fpdf_charwidths['courier']={}
-
-for i in xrange(0,256):
- fpdf_charwidths['courier'][chr(i)]=600
- fpdf_charwidths['courierB']=fpdf_charwidths['courier']
- fpdf_charwidths['courierI']=fpdf_charwidths['courier']
- fpdf_charwidths['courierBI']=fpdf_charwidths['courier']
-
-fpdf_charwidths['helvetica']={
- '\x00':278,'\x01':278,'\x02':278,'\x03':278,'\x04':278,'\x05':278,'\x06':278,'\x07':278,'\x08':278,'\t':278,'\n':278,'\x0b':278,'\x0c':278,'\r':278,'\x0e':278,'\x0f':278,'\x10':278,'\x11':278,'\x12':278,'\x13':278,'\x14':278,'\x15':278,
- '\x16':278,'\x17':278,'\x18':278,'\x19':278,'\x1a':278,'\x1b':278,'\x1c':278,'\x1d':278,'\x1e':278,'\x1f':278,' ':278,'!':278,'"':355,'#':556,'$':556,'%':889,'&':667,'\'':191,'(':333,')':333,'*':389,'+':584,
- ',':278,'-':333,'.':278,'/':278,'0':556,'1':556,'2':556,'3':556,'4':556,'5':556,'6':556,'7':556,'8':556,'9':556,':':278,';':278,'<':584,'=':584,'>':584,'?':556,'@':1015,'A':667,
- 'B':667,'C':722,'D':722,'E':667,'F':611,'G':778,'H':722,'I':278,'J':500,'K':667,'L':556,'M':833,'N':722,'O':778,'P':667,'Q':778,'R':722,'S':667,'T':611,'U':722,'V':667,'W':944,
- 'X':667,'Y':667,'Z':611,'[':278,'\\':278,']':278,'^':469,'_':556,'`':333,'a':556,'b':556,'c':500,'d':556,'e':556,'f':278,'g':556,'h':556,'i':222,'j':222,'k':500,'l':222,'m':833,
- 'n':556,'o':556,'p':556,'q':556,'r':333,'s':500,'t':278,'u':556,'v':500,'w':722,'x':500,'y':500,'z':500,'{':334,'|':260,'}':334,'~':584,'\x7f':350,'\x80':556,'\x81':350,'\x82':222,'\x83':556,
- '\x84':333,'\x85':1000,'\x86':556,'\x87':556,'\x88':333,'\x89':1000,'\x8a':667,'\x8b':333,'\x8c':1000,'\x8d':350,'\x8e':611,'\x8f':350,'\x90':350,'\x91':222,'\x92':222,'\x93':333,'\x94':333,'\x95':350,'\x96':556,'\x97':1000,'\x98':333,'\x99':1000,
- '\x9a':500,'\x9b':333,'\x9c':944,'\x9d':350,'\x9e':500,'\x9f':667,'\xa0':278,'\xa1':333,'\xa2':556,'\xa3':556,'\xa4':556,'\xa5':556,'\xa6':260,'\xa7':556,'\xa8':333,'\xa9':737,'\xaa':370,'\xab':556,'\xac':584,'\xad':333,'\xae':737,'\xaf':333,
- '\xb0':400,'\xb1':584,'\xb2':333,'\xb3':333,'\xb4':333,'\xb5':556,'\xb6':537,'\xb7':278,'\xb8':333,'\xb9':333,'\xba':365,'\xbb':556,'\xbc':834,'\xbd':834,'\xbe':834,'\xbf':611,'\xc0':667,'\xc1':667,'\xc2':667,'\xc3':667,'\xc4':667,'\xc5':667,
- '\xc6':1000,'\xc7':722,'\xc8':667,'\xc9':667,'\xca':667,'\xcb':667,'\xcc':278,'\xcd':278,'\xce':278,'\xcf':278,'\xd0':722,'\xd1':722,'\xd2':778,'\xd3':778,'\xd4':778,'\xd5':778,'\xd6':778,'\xd7':584,'\xd8':778,'\xd9':722,'\xda':722,'\xdb':722,
- '\xdc':722,'\xdd':667,'\xde':667,'\xdf':611,'\xe0':556,'\xe1':556,'\xe2':556,'\xe3':556,'\xe4':556,'\xe5':556,'\xe6':889,'\xe7':500,'\xe8':556,'\xe9':556,'\xea':556,'\xeb':556,'\xec':278,'\xed':278,'\xee':278,'\xef':278,'\xf0':556,'\xf1':556,
- '\xf2':556,'\xf3':556,'\xf4':556,'\xf5':556,'\xf6':556,'\xf7':584,'\xf8':611,'\xf9':556,'\xfa':556,'\xfb':556,'\xfc':556,'\xfd':500,'\xfe':556,'\xff':500}
-
-fpdf_charwidths['helveticaB']={
- '\x00':278,'\x01':278,'\x02':278,'\x03':278,'\x04':278,'\x05':278,'\x06':278,'\x07':278,'\x08':278,'\t':278,'\n':278,'\x0b':278,'\x0c':278,'\r':278,'\x0e':278,'\x0f':278,'\x10':278,'\x11':278,'\x12':278,'\x13':278,'\x14':278,'\x15':278,
- '\x16':278,'\x17':278,'\x18':278,'\x19':278,'\x1a':278,'\x1b':278,'\x1c':278,'\x1d':278,'\x1e':278,'\x1f':278,' ':278,'!':333,'"':474,'#':556,'$':556,'%':889,'&':722,'\'':238,'(':333,')':333,'*':389,'+':584,
- ',':278,'-':333,'.':278,'/':278,'0':556,'1':556,'2':556,'3':556,'4':556,'5':556,'6':556,'7':556,'8':556,'9':556,':':333,';':333,'<':584,'=':584,'>':584,'?':611,'@':975,'A':722,
- 'B':722,'C':722,'D':722,'E':667,'F':611,'G':778,'H':722,'I':278,'J':556,'K':722,'L':611,'M':833,'N':722,'O':778,'P':667,'Q':778,'R':722,'S':667,'T':611,'U':722,'V':667,'W':944,
- 'X':667,'Y':667,'Z':611,'[':333,'\\':278,']':333,'^':584,'_':556,'`':333,'a':556,'b':611,'c':556,'d':611,'e':556,'f':333,'g':611,'h':611,'i':278,'j':278,'k':556,'l':278,'m':889,
- 'n':611,'o':611,'p':611,'q':611,'r':389,'s':556,'t':333,'u':611,'v':556,'w':778,'x':556,'y':556,'z':500,'{':389,'|':280,'}':389,'~':584,'\x7f':350,'\x80':556,'\x81':350,'\x82':278,'\x83':556,
- '\x84':500,'\x85':1000,'\x86':556,'\x87':556,'\x88':333,'\x89':1000,'\x8a':667,'\x8b':333,'\x8c':1000,'\x8d':350,'\x8e':611,'\x8f':350,'\x90':350,'\x91':278,'\x92':278,'\x93':500,'\x94':500,'\x95':350,'\x96':556,'\x97':1000,'\x98':333,'\x99':1000,
- '\x9a':556,'\x9b':333,'\x9c':944,'\x9d':350,'\x9e':500,'\x9f':667,'\xa0':278,'\xa1':333,'\xa2':556,'\xa3':556,'\xa4':556,'\xa5':556,'\xa6':280,'\xa7':556,'\xa8':333,'\xa9':737,'\xaa':370,'\xab':556,'\xac':584,'\xad':333,'\xae':737,'\xaf':333,
- '\xb0':400,'\xb1':584,'\xb2':333,'\xb3':333,'\xb4':333,'\xb5':611,'\xb6':556,'\xb7':278,'\xb8':333,'\xb9':333,'\xba':365,'\xbb':556,'\xbc':834,'\xbd':834,'\xbe':834,'\xbf':611,'\xc0':722,'\xc1':722,'\xc2':722,'\xc3':722,'\xc4':722,'\xc5':722,
- '\xc6':1000,'\xc7':722,'\xc8':667,'\xc9':667,'\xca':667,'\xcb':667,'\xcc':278,'\xcd':278,'\xce':278,'\xcf':278,'\xd0':722,'\xd1':722,'\xd2':778,'\xd3':778,'\xd4':778,'\xd5':778,'\xd6':778,'\xd7':584,'\xd8':778,'\xd9':722,'\xda':722,'\xdb':722,
- '\xdc':722,'\xdd':667,'\xde':667,'\xdf':611,'\xe0':556,'\xe1':556,'\xe2':556,'\xe3':556,'\xe4':556,'\xe5':556,'\xe6':889,'\xe7':556,'\xe8':556,'\xe9':556,'\xea':556,'\xeb':556,'\xec':278,'\xed':278,'\xee':278,'\xef':278,'\xf0':611,'\xf1':611,
- '\xf2':611,'\xf3':611,'\xf4':611,'\xf5':611,'\xf6':611,'\xf7':584,'\xf8':611,'\xf9':611,'\xfa':611,'\xfb':611,'\xfc':611,'\xfd':556,'\xfe':611,'\xff':556
-}
-
-fpdf_charwidths['helveticaBI']={
- '\x00':278,'\x01':278,'\x02':278,'\x03':278,'\x04':278,'\x05':278,'\x06':278,'\x07':278,'\x08':278,'\t':278,'\n':278,'\x0b':278,'\x0c':278,'\r':278,'\x0e':278,'\x0f':278,'\x10':278,'\x11':278,'\x12':278,'\x13':278,'\x14':278,'\x15':278,
- '\x16':278,'\x17':278,'\x18':278,'\x19':278,'\x1a':278,'\x1b':278,'\x1c':278,'\x1d':278,'\x1e':278,'\x1f':278,' ':278,'!':333,'"':474,'#':556,'$':556,'%':889,'&':722,'\'':238,'(':333,')':333,'*':389,'+':584,
- ',':278,'-':333,'.':278,'/':278,'0':556,'1':556,'2':556,'3':556,'4':556,'5':556,'6':556,'7':556,'8':556,'9':556,':':333,';':333,'<':584,'=':584,'>':584,'?':611,'@':975,'A':722,
- 'B':722,'C':722,'D':722,'E':667,'F':611,'G':778,'H':722,'I':278,'J':556,'K':722,'L':611,'M':833,'N':722,'O':778,'P':667,'Q':778,'R':722,'S':667,'T':611,'U':722,'V':667,'W':944,
- 'X':667,'Y':667,'Z':611,'[':333,'\\':278,']':333,'^':584,'_':556,'`':333,'a':556,'b':611,'c':556,'d':611,'e':556,'f':333,'g':611,'h':611,'i':278,'j':278,'k':556,'l':278,'m':889,
- 'n':611,'o':611,'p':611,'q':611,'r':389,'s':556,'t':333,'u':611,'v':556,'w':778,'x':556,'y':556,'z':500,'{':389,'|':280,'}':389,'~':584,'\x7f':350,'\x80':556,'\x81':350,'\x82':278,'\x83':556,
- '\x84':500,'\x85':1000,'\x86':556,'\x87':556,'\x88':333,'\x89':1000,'\x8a':667,'\x8b':333,'\x8c':1000,'\x8d':350,'\x8e':611,'\x8f':350,'\x90':350,'\x91':278,'\x92':278,'\x93':500,'\x94':500,'\x95':350,'\x96':556,'\x97':1000,'\x98':333,'\x99':1000,
- '\x9a':556,'\x9b':333,'\x9c':944,'\x9d':350,'\x9e':500,'\x9f':667,'\xa0':278,'\xa1':333,'\xa2':556,'\xa3':556,'\xa4':556,'\xa5':556,'\xa6':280,'\xa7':556,'\xa8':333,'\xa9':737,'\xaa':370,'\xab':556,'\xac':584,'\xad':333,'\xae':737,'\xaf':333,
- '\xb0':400,'\xb1':584,'\xb2':333,'\xb3':333,'\xb4':333,'\xb5':611,'\xb6':556,'\xb7':278,'\xb8':333,'\xb9':333,'\xba':365,'\xbb':556,'\xbc':834,'\xbd':834,'\xbe':834,'\xbf':611,'\xc0':722,'\xc1':722,'\xc2':722,'\xc3':722,'\xc4':722,'\xc5':722,
- '\xc6':1000,'\xc7':722,'\xc8':667,'\xc9':667,'\xca':667,'\xcb':667,'\xcc':278,'\xcd':278,'\xce':278,'\xcf':278,'\xd0':722,'\xd1':722,'\xd2':778,'\xd3':778,'\xd4':778,'\xd5':778,'\xd6':778,'\xd7':584,'\xd8':778,'\xd9':722,'\xda':722,'\xdb':722,
- '\xdc':722,'\xdd':667,'\xde':667,'\xdf':611,'\xe0':556,'\xe1':556,'\xe2':556,'\xe3':556,'\xe4':556,'\xe5':556,'\xe6':889,'\xe7':556,'\xe8':556,'\xe9':556,'\xea':556,'\xeb':556,'\xec':278,'\xed':278,'\xee':278,'\xef':278,'\xf0':611,'\xf1':611,
- '\xf2':611,'\xf3':611,'\xf4':611,'\xf5':611,'\xf6':611,'\xf7':584,'\xf8':611,'\xf9':611,'\xfa':611,'\xfb':611,'\xfc':611,'\xfd':556,'\xfe':611,'\xff':556}
-
-fpdf_charwidths['helveticaI']={
- '\x00':278,'\x01':278,'\x02':278,'\x03':278,'\x04':278,'\x05':278,'\x06':278,'\x07':278,'\x08':278,'\t':278,'\n':278,'\x0b':278,'\x0c':278,'\r':278,'\x0e':278,'\x0f':278,'\x10':278,'\x11':278,'\x12':278,'\x13':278,'\x14':278,'\x15':278,
- '\x16':278,'\x17':278,'\x18':278,'\x19':278,'\x1a':278,'\x1b':278,'\x1c':278,'\x1d':278,'\x1e':278,'\x1f':278,' ':278,'!':278,'"':355,'#':556,'$':556,'%':889,'&':667,'\'':191,'(':333,')':333,'*':389,'+':584,
- ',':278,'-':333,'.':278,'/':278,'0':556,'1':556,'2':556,'3':556,'4':556,'5':556,'6':556,'7':556,'8':556,'9':556,':':278,';':278,'<':584,'=':584,'>':584,'?':556,'@':1015,'A':667,
- 'B':667,'C':722,'D':722,'E':667,'F':611,'G':778,'H':722,'I':278,'J':500,'K':667,'L':556,'M':833,'N':722,'O':778,'P':667,'Q':778,'R':722,'S':667,'T':611,'U':722,'V':667,'W':944,
- 'X':667,'Y':667,'Z':611,'[':278,'\\':278,']':278,'^':469,'_':556,'`':333,'a':556,'b':556,'c':500,'d':556,'e':556,'f':278,'g':556,'h':556,'i':222,'j':222,'k':500,'l':222,'m':833,
- 'n':556,'o':556,'p':556,'q':556,'r':333,'s':500,'t':278,'u':556,'v':500,'w':722,'x':500,'y':500,'z':500,'{':334,'|':260,'}':334,'~':584,'\x7f':350,'\x80':556,'\x81':350,'\x82':222,'\x83':556,
- '\x84':333,'\x85':1000,'\x86':556,'\x87':556,'\x88':333,'\x89':1000,'\x8a':667,'\x8b':333,'\x8c':1000,'\x8d':350,'\x8e':611,'\x8f':350,'\x90':350,'\x91':222,'\x92':222,'\x93':333,'\x94':333,'\x95':350,'\x96':556,'\x97':1000,'\x98':333,'\x99':1000,
- '\x9a':500,'\x9b':333,'\x9c':944,'\x9d':350,'\x9e':500,'\x9f':667,'\xa0':278,'\xa1':333,'\xa2':556,'\xa3':556,'\xa4':556,'\xa5':556,'\xa6':260,'\xa7':556,'\xa8':333,'\xa9':737,'\xaa':370,'\xab':556,'\xac':584,'\xad':333,'\xae':737,'\xaf':333,
- '\xb0':400,'\xb1':584,'\xb2':333,'\xb3':333,'\xb4':333,'\xb5':556,'\xb6':537,'\xb7':278,'\xb8':333,'\xb9':333,'\xba':365,'\xbb':556,'\xbc':834,'\xbd':834,'\xbe':834,'\xbf':611,'\xc0':667,'\xc1':667,'\xc2':667,'\xc3':667,'\xc4':667,'\xc5':667,
- '\xc6':1000,'\xc7':722,'\xc8':667,'\xc9':667,'\xca':667,'\xcb':667,'\xcc':278,'\xcd':278,'\xce':278,'\xcf':278,'\xd0':722,'\xd1':722,'\xd2':778,'\xd3':778,'\xd4':778,'\xd5':778,'\xd6':778,'\xd7':584,'\xd8':778,'\xd9':722,'\xda':722,'\xdb':722,
- '\xdc':722,'\xdd':667,'\xde':667,'\xdf':611,'\xe0':556,'\xe1':556,'\xe2':556,'\xe3':556,'\xe4':556,'\xe5':556,'\xe6':889,'\xe7':500,'\xe8':556,'\xe9':556,'\xea':556,'\xeb':556,'\xec':278,'\xed':278,'\xee':278,'\xef':278,'\xf0':556,'\xf1':556,
- '\xf2':556,'\xf3':556,'\xf4':556,'\xf5':556,'\xf6':556,'\xf7':584,'\xf8':611,'\xf9':556,'\xfa':556,'\xfb':556,'\xfc':556,'\xfd':500,'\xfe':556,'\xff':500}
-
-fpdf_charwidths['symbol']={
- '\x00':250,'\x01':250,'\x02':250,'\x03':250,'\x04':250,'\x05':250,'\x06':250,'\x07':250,'\x08':250,'\t':250,'\n':250,'\x0b':250,'\x0c':250,'\r':250,'\x0e':250,'\x0f':250,'\x10':250,'\x11':250,'\x12':250,'\x13':250,'\x14':250,'\x15':250,
- '\x16':250,'\x17':250,'\x18':250,'\x19':250,'\x1a':250,'\x1b':250,'\x1c':250,'\x1d':250,'\x1e':250,'\x1f':250,' ':250,'!':333,'"':713,'#':500,'$':549,'%':833,'&':778,'\'':439,'(':333,')':333,'*':500,'+':549,
- ',':250,'-':549,'.':250,'/':278,'0':500,'1':500,'2':500,'3':500,'4':500,'5':500,'6':500,'7':500,'8':500,'9':500,':':278,';':278,'<':549,'=':549,'>':549,'?':444,'@':549,'A':722,
- 'B':667,'C':722,'D':612,'E':611,'F':763,'G':603,'H':722,'I':333,'J':631,'K':722,'L':686,'M':889,'N':722,'O':722,'P':768,'Q':741,'R':556,'S':592,'T':611,'U':690,'V':439,'W':768,
- 'X':645,'Y':795,'Z':611,'[':333,'\\':863,']':333,'^':658,'_':500,'`':500,'a':631,'b':549,'c':549,'d':494,'e':439,'f':521,'g':411,'h':603,'i':329,'j':603,'k':549,'l':549,'m':576,
- 'n':521,'o':549,'p':549,'q':521,'r':549,'s':603,'t':439,'u':576,'v':713,'w':686,'x':493,'y':686,'z':494,'{':480,'|':200,'}':480,'~':549,'\x7f':0,'\x80':0,'\x81':0,'\x82':0,'\x83':0,
- '\x84':0,'\x85':0,'\x86':0,'\x87':0,'\x88':0,'\x89':0,'\x8a':0,'\x8b':0,'\x8c':0,'\x8d':0,'\x8e':0,'\x8f':0,'\x90':0,'\x91':0,'\x92':0,'\x93':0,'\x94':0,'\x95':0,'\x96':0,'\x97':0,'\x98':0,'\x99':0,
- '\x9a':0,'\x9b':0,'\x9c':0,'\x9d':0,'\x9e':0,'\x9f':0,'\xa0':750,'\xa1':620,'\xa2':247,'\xa3':549,'\xa4':167,'\xa5':713,'\xa6':500,'\xa7':753,'\xa8':753,'\xa9':753,'\xaa':753,'\xab':1042,'\xac':987,'\xad':603,'\xae':987,'\xaf':603,
- '\xb0':400,'\xb1':549,'\xb2':411,'\xb3':549,'\xb4':549,'\xb5':713,'\xb6':494,'\xb7':460,'\xb8':549,'\xb9':549,'\xba':549,'\xbb':549,'\xbc':1000,'\xbd':603,'\xbe':1000,'\xbf':658,'\xc0':823,'\xc1':686,'\xc2':795,'\xc3':987,'\xc4':768,'\xc5':768,
- '\xc6':823,'\xc7':768,'\xc8':768,'\xc9':713,'\xca':713,'\xcb':713,'\xcc':713,'\xcd':713,'\xce':713,'\xcf':713,'\xd0':768,'\xd1':713,'\xd2':790,'\xd3':790,'\xd4':890,'\xd5':823,'\xd6':549,'\xd7':250,'\xd8':713,'\xd9':603,'\xda':603,'\xdb':1042,
- '\xdc':987,'\xdd':603,'\xde':987,'\xdf':603,'\xe0':494,'\xe1':329,'\xe2':790,'\xe3':790,'\xe4':786,'\xe5':713,'\xe6':384,'\xe7':384,'\xe8':384,'\xe9':384,'\xea':384,'\xeb':384,'\xec':494,'\xed':494,'\xee':494,'\xef':494,'\xf0':0,'\xf1':329,
- '\xf2':274,'\xf3':686,'\xf4':686,'\xf5':686,'\xf6':384,'\xf7':384,'\xf8':384,'\xf9':384,'\xfa':384,'\xfb':384,'\xfc':494,'\xfd':494,'\xfe':494,'\xff':0}
-
-fpdf_charwidths['times']={
- '\x00':250,'\x01':250,'\x02':250,'\x03':250,'\x04':250,'\x05':250,'\x06':250,'\x07':250,'\x08':250,'\t':250,'\n':250,'\x0b':250,'\x0c':250,'\r':250,'\x0e':250,'\x0f':250,'\x10':250,'\x11':250,'\x12':250,'\x13':250,'\x14':250,'\x15':250,
- '\x16':250,'\x17':250,'\x18':250,'\x19':250,'\x1a':250,'\x1b':250,'\x1c':250,'\x1d':250,'\x1e':250,'\x1f':250,' ':250,'!':333,'"':408,'#':500,'$':500,'%':833,'&':778,'\'':180,'(':333,')':333,'*':500,'+':564,
- ',':250,'-':333,'.':250,'/':278,'0':500,'1':500,'2':500,'3':500,'4':500,'5':500,'6':500,'7':500,'8':500,'9':500,':':278,';':278,'<':564,'=':564,'>':564,'?':444,'@':921,'A':722,
- 'B':667,'C':667,'D':722,'E':611,'F':556,'G':722,'H':722,'I':333,'J':389,'K':722,'L':611,'M':889,'N':722,'O':722,'P':556,'Q':722,'R':667,'S':556,'T':611,'U':722,'V':722,'W':944,
- 'X':722,'Y':722,'Z':611,'[':333,'\\':278,']':333,'^':469,'_':500,'`':333,'a':444,'b':500,'c':444,'d':500,'e':444,'f':333,'g':500,'h':500,'i':278,'j':278,'k':500,'l':278,'m':778,
- 'n':500,'o':500,'p':500,'q':500,'r':333,'s':389,'t':278,'u':500,'v':500,'w':722,'x':500,'y':500,'z':444,'{':480,'|':200,'}':480,'~':541,'\x7f':350,'\x80':500,'\x81':350,'\x82':333,'\x83':500,
- '\x84':444,'\x85':1000,'\x86':500,'\x87':500,'\x88':333,'\x89':1000,'\x8a':556,'\x8b':333,'\x8c':889,'\x8d':350,'\x8e':611,'\x8f':350,'\x90':350,'\x91':333,'\x92':333,'\x93':444,'\x94':444,'\x95':350,'\x96':500,'\x97':1000,'\x98':333,'\x99':980,
- '\x9a':389,'\x9b':333,'\x9c':722,'\x9d':350,'\x9e':444,'\x9f':722,'\xa0':250,'\xa1':333,'\xa2':500,'\xa3':500,'\xa4':500,'\xa5':500,'\xa6':200,'\xa7':500,'\xa8':333,'\xa9':760,'\xaa':276,'\xab':500,'\xac':564,'\xad':333,'\xae':760,'\xaf':333,
- '\xb0':400,'\xb1':564,'\xb2':300,'\xb3':300,'\xb4':333,'\xb5':500,'\xb6':453,'\xb7':250,'\xb8':333,'\xb9':300,'\xba':310,'\xbb':500,'\xbc':750,'\xbd':750,'\xbe':750,'\xbf':444,'\xc0':722,'\xc1':722,'\xc2':722,'\xc3':722,'\xc4':722,'\xc5':722,
- '\xc6':889,'\xc7':667,'\xc8':611,'\xc9':611,'\xca':611,'\xcb':611,'\xcc':333,'\xcd':333,'\xce':333,'\xcf':333,'\xd0':722,'\xd1':722,'\xd2':722,'\xd3':722,'\xd4':722,'\xd5':722,'\xd6':722,'\xd7':564,'\xd8':722,'\xd9':722,'\xda':722,'\xdb':722,
- '\xdc':722,'\xdd':722,'\xde':556,'\xdf':500,'\xe0':444,'\xe1':444,'\xe2':444,'\xe3':444,'\xe4':444,'\xe5':444,'\xe6':667,'\xe7':444,'\xe8':444,'\xe9':444,'\xea':444,'\xeb':444,'\xec':278,'\xed':278,'\xee':278,'\xef':278,'\xf0':500,'\xf1':500,
- '\xf2':500,'\xf3':500,'\xf4':500,'\xf5':500,'\xf6':500,'\xf7':564,'\xf8':500,'\xf9':500,'\xfa':500,'\xfb':500,'\xfc':500,'\xfd':500,'\xfe':500,'\xff':500}
-
-fpdf_charwidths['timesB']={
- '\x00':250,'\x01':250,'\x02':250,'\x03':250,'\x04':250,'\x05':250,'\x06':250,'\x07':250,'\x08':250,'\t':250,'\n':250,'\x0b':250,'\x0c':250,'\r':250,'\x0e':250,'\x0f':250,'\x10':250,'\x11':250,'\x12':250,'\x13':250,'\x14':250,'\x15':250,
- '\x16':250,'\x17':250,'\x18':250,'\x19':250,'\x1a':250,'\x1b':250,'\x1c':250,'\x1d':250,'\x1e':250,'\x1f':250,' ':250,'!':333,'"':555,'#':500,'$':500,'%':1000,'&':833,'\'':278,'(':333,')':333,'*':500,'+':570,
- ',':250,'-':333,'.':250,'/':278,'0':500,'1':500,'2':500,'3':500,'4':500,'5':500,'6':500,'7':500,'8':500,'9':500,':':333,';':333,'<':570,'=':570,'>':570,'?':500,'@':930,'A':722,
- 'B':667,'C':722,'D':722,'E':667,'F':611,'G':778,'H':778,'I':389,'J':500,'K':778,'L':667,'M':944,'N':722,'O':778,'P':611,'Q':778,'R':722,'S':556,'T':667,'U':722,'V':722,'W':1000,
- 'X':722,'Y':722,'Z':667,'[':333,'\\':278,']':333,'^':581,'_':500,'`':333,'a':500,'b':556,'c':444,'d':556,'e':444,'f':333,'g':500,'h':556,'i':278,'j':333,'k':556,'l':278,'m':833,
- 'n':556,'o':500,'p':556,'q':556,'r':444,'s':389,'t':333,'u':556,'v':500,'w':722,'x':500,'y':500,'z':444,'{':394,'|':220,'}':394,'~':520,'\x7f':350,'\x80':500,'\x81':350,'\x82':333,'\x83':500,
- '\x84':500,'\x85':1000,'\x86':500,'\x87':500,'\x88':333,'\x89':1000,'\x8a':556,'\x8b':333,'\x8c':1000,'\x8d':350,'\x8e':667,'\x8f':350,'\x90':350,'\x91':333,'\x92':333,'\x93':500,'\x94':500,'\x95':350,'\x96':500,'\x97':1000,'\x98':333,'\x99':1000,
- '\x9a':389,'\x9b':333,'\x9c':722,'\x9d':350,'\x9e':444,'\x9f':722,'\xa0':250,'\xa1':333,'\xa2':500,'\xa3':500,'\xa4':500,'\xa5':500,'\xa6':220,'\xa7':500,'\xa8':333,'\xa9':747,'\xaa':300,'\xab':500,'\xac':570,'\xad':333,'\xae':747,'\xaf':333,
- '\xb0':400,'\xb1':570,'\xb2':300,'\xb3':300,'\xb4':333,'\xb5':556,'\xb6':540,'\xb7':250,'\xb8':333,'\xb9':300,'\xba':330,'\xbb':500,'\xbc':750,'\xbd':750,'\xbe':750,'\xbf':500,'\xc0':722,'\xc1':722,'\xc2':722,'\xc3':722,'\xc4':722,'\xc5':722,
- '\xc6':1000,'\xc7':722,'\xc8':667,'\xc9':667,'\xca':667,'\xcb':667,'\xcc':389,'\xcd':389,'\xce':389,'\xcf':389,'\xd0':722,'\xd1':722,'\xd2':778,'\xd3':778,'\xd4':778,'\xd5':778,'\xd6':778,'\xd7':570,'\xd8':778,'\xd9':722,'\xda':722,'\xdb':722,
- '\xdc':722,'\xdd':722,'\xde':611,'\xdf':556,'\xe0':500,'\xe1':500,'\xe2':500,'\xe3':500,'\xe4':500,'\xe5':500,'\xe6':722,'\xe7':444,'\xe8':444,'\xe9':444,'\xea':444,'\xeb':444,'\xec':278,'\xed':278,'\xee':278,'\xef':278,'\xf0':500,'\xf1':556,
- '\xf2':500,'\xf3':500,'\xf4':500,'\xf5':500,'\xf6':500,'\xf7':570,'\xf8':500,'\xf9':556,'\xfa':556,'\xfb':556,'\xfc':556,'\xfd':500,'\xfe':556,'\xff':500}
-
-fpdf_charwidths['timesBI']={
- '\x00':250,'\x01':250,'\x02':250,'\x03':250,'\x04':250,'\x05':250,'\x06':250,'\x07':250,'\x08':250,'\t':250,'\n':250,'\x0b':250,'\x0c':250,'\r':250,'\x0e':250,'\x0f':250,'\x10':250,'\x11':250,'\x12':250,'\x13':250,'\x14':250,'\x15':250,
- '\x16':250,'\x17':250,'\x18':250,'\x19':250,'\x1a':250,'\x1b':250,'\x1c':250,'\x1d':250,'\x1e':250,'\x1f':250,' ':250,'!':389,'"':555,'#':500,'$':500,'%':833,'&':778,'\'':278,'(':333,')':333,'*':500,'+':570,
- ',':250,'-':333,'.':250,'/':278,'0':500,'1':500,'2':500,'3':500,'4':500,'5':500,'6':500,'7':500,'8':500,'9':500,':':333,';':333,'<':570,'=':570,'>':570,'?':500,'@':832,'A':667,
- 'B':667,'C':667,'D':722,'E':667,'F':667,'G':722,'H':778,'I':389,'J':500,'K':667,'L':611,'M':889,'N':722,'O':722,'P':611,'Q':722,'R':667,'S':556,'T':611,'U':722,'V':667,'W':889,
- 'X':667,'Y':611,'Z':611,'[':333,'\\':278,']':333,'^':570,'_':500,'`':333,'a':500,'b':500,'c':444,'d':500,'e':444,'f':333,'g':500,'h':556,'i':278,'j':278,'k':500,'l':278,'m':778,
- 'n':556,'o':500,'p':500,'q':500,'r':389,'s':389,'t':278,'u':556,'v':444,'w':667,'x':500,'y':444,'z':389,'{':348,'|':220,'}':348,'~':570,'\x7f':350,'\x80':500,'\x81':350,'\x82':333,'\x83':500,
- '\x84':500,'\x85':1000,'\x86':500,'\x87':500,'\x88':333,'\x89':1000,'\x8a':556,'\x8b':333,'\x8c':944,'\x8d':350,'\x8e':611,'\x8f':350,'\x90':350,'\x91':333,'\x92':333,'\x93':500,'\x94':500,'\x95':350,'\x96':500,'\x97':1000,'\x98':333,'\x99':1000,
- '\x9a':389,'\x9b':333,'\x9c':722,'\x9d':350,'\x9e':389,'\x9f':611,'\xa0':250,'\xa1':389,'\xa2':500,'\xa3':500,'\xa4':500,'\xa5':500,'\xa6':220,'\xa7':500,'\xa8':333,'\xa9':747,'\xaa':266,'\xab':500,'\xac':606,'\xad':333,'\xae':747,'\xaf':333,
- '\xb0':400,'\xb1':570,'\xb2':300,'\xb3':300,'\xb4':333,'\xb5':576,'\xb6':500,'\xb7':250,'\xb8':333,'\xb9':300,'\xba':300,'\xbb':500,'\xbc':750,'\xbd':750,'\xbe':750,'\xbf':500,'\xc0':667,'\xc1':667,'\xc2':667,'\xc3':667,'\xc4':667,'\xc5':667,
- '\xc6':944,'\xc7':667,'\xc8':667,'\xc9':667,'\xca':667,'\xcb':667,'\xcc':389,'\xcd':389,'\xce':389,'\xcf':389,'\xd0':722,'\xd1':722,'\xd2':722,'\xd3':722,'\xd4':722,'\xd5':722,'\xd6':722,'\xd7':570,'\xd8':722,'\xd9':722,'\xda':722,'\xdb':722,
- '\xdc':722,'\xdd':611,'\xde':611,'\xdf':500,'\xe0':500,'\xe1':500,'\xe2':500,'\xe3':500,'\xe4':500,'\xe5':500,'\xe6':722,'\xe7':444,'\xe8':444,'\xe9':444,'\xea':444,'\xeb':444,'\xec':278,'\xed':278,'\xee':278,'\xef':278,'\xf0':500,'\xf1':556,
- '\xf2':500,'\xf3':500,'\xf4':500,'\xf5':500,'\xf6':500,'\xf7':570,'\xf8':500,'\xf9':556,'\xfa':556,'\xfb':556,'\xfc':556,'\xfd':444,'\xfe':500,'\xff':444}
-
-fpdf_charwidths['timesI']={
- '\x00':250,'\x01':250,'\x02':250,'\x03':250,'\x04':250,'\x05':250,'\x06':250,'\x07':250,'\x08':250,'\t':250,'\n':250,'\x0b':250,'\x0c':250,'\r':250,'\x0e':250,'\x0f':250,'\x10':250,'\x11':250,'\x12':250,'\x13':250,'\x14':250,'\x15':250,
- '\x16':250,'\x17':250,'\x18':250,'\x19':250,'\x1a':250,'\x1b':250,'\x1c':250,'\x1d':250,'\x1e':250,'\x1f':250,' ':250,'!':333,'"':420,'#':500,'$':500,'%':833,'&':778,'\'':214,'(':333,')':333,'*':500,'+':675,
- ',':250,'-':333,'.':250,'/':278,'0':500,'1':500,'2':500,'3':500,'4':500,'5':500,'6':500,'7':500,'8':500,'9':500,':':333,';':333,'<':675,'=':675,'>':675,'?':500,'@':920,'A':611,
- 'B':611,'C':667,'D':722,'E':611,'F':611,'G':722,'H':722,'I':333,'J':444,'K':667,'L':556,'M':833,'N':667,'O':722,'P':611,'Q':722,'R':611,'S':500,'T':556,'U':722,'V':611,'W':833,
- 'X':611,'Y':556,'Z':556,'[':389,'\\':278,']':389,'^':422,'_':500,'`':333,'a':500,'b':500,'c':444,'d':500,'e':444,'f':278,'g':500,'h':500,'i':278,'j':278,'k':444,'l':278,'m':722,
- 'n':500,'o':500,'p':500,'q':500,'r':389,'s':389,'t':278,'u':500,'v':444,'w':667,'x':444,'y':444,'z':389,'{':400,'|':275,'}':400,'~':541,'\x7f':350,'\x80':500,'\x81':350,'\x82':333,'\x83':500,
- '\x84':556,'\x85':889,'\x86':500,'\x87':500,'\x88':333,'\x89':1000,'\x8a':500,'\x8b':333,'\x8c':944,'\x8d':350,'\x8e':556,'\x8f':350,'\x90':350,'\x91':333,'\x92':333,'\x93':556,'\x94':556,'\x95':350,'\x96':500,'\x97':889,'\x98':333,'\x99':980,
- '\x9a':389,'\x9b':333,'\x9c':667,'\x9d':350,'\x9e':389,'\x9f':556,'\xa0':250,'\xa1':389,'\xa2':500,'\xa3':500,'\xa4':500,'\xa5':500,'\xa6':275,'\xa7':500,'\xa8':333,'\xa9':760,'\xaa':276,'\xab':500,'\xac':675,'\xad':333,'\xae':760,'\xaf':333,
- '\xb0':400,'\xb1':675,'\xb2':300,'\xb3':300,'\xb4':333,'\xb5':500,'\xb6':523,'\xb7':250,'\xb8':333,'\xb9':300,'\xba':310,'\xbb':500,'\xbc':750,'\xbd':750,'\xbe':750,'\xbf':500,'\xc0':611,'\xc1':611,'\xc2':611,'\xc3':611,'\xc4':611,'\xc5':611,
- '\xc6':889,'\xc7':667,'\xc8':611,'\xc9':611,'\xca':611,'\xcb':611,'\xcc':333,'\xcd':333,'\xce':333,'\xcf':333,'\xd0':722,'\xd1':667,'\xd2':722,'\xd3':722,'\xd4':722,'\xd5':722,'\xd6':722,'\xd7':675,'\xd8':722,'\xd9':722,'\xda':722,'\xdb':722,
- '\xdc':722,'\xdd':556,'\xde':611,'\xdf':500,'\xe0':500,'\xe1':500,'\xe2':500,'\xe3':500,'\xe4':500,'\xe5':500,'\xe6':667,'\xe7':444,'\xe8':444,'\xe9':444,'\xea':444,'\xeb':444,'\xec':278,'\xed':278,'\xee':278,'\xef':278,'\xf0':500,'\xf1':500,
- '\xf2':500,'\xf3':500,'\xf4':500,'\xf5':500,'\xf6':500,'\xf7':675,'\xf8':500,'\xf9':500,'\xfa':500,'\xfb':500,'\xfc':500,'\xfd':444,'\xfe':500,'\xff':444}
-
-fpdf_charwidths['zapfdingbats']={
- '\x00':0,'\x01':0,'\x02':0,'\x03':0,'\x04':0,'\x05':0,'\x06':0,'\x07':0,'\x08':0,'\t':0,'\n':0,'\x0b':0,'\x0c':0,'\r':0,'\x0e':0,'\x0f':0,'\x10':0,'\x11':0,'\x12':0,'\x13':0,'\x14':0,'\x15':0,
- '\x16':0,'\x17':0,'\x18':0,'\x19':0,'\x1a':0,'\x1b':0,'\x1c':0,'\x1d':0,'\x1e':0,'\x1f':0,' ':278,'!':974,'"':961,'#':974,'$':980,'%':719,'&':789,'\'':790,'(':791,')':690,'*':960,'+':939,
- ',':549,'-':855,'.':911,'/':933,'0':911,'1':945,'2':974,'3':755,'4':846,'5':762,'6':761,'7':571,'8':677,'9':763,':':760,';':759,'<':754,'=':494,'>':552,'?':537,'@':577,'A':692,
- 'B':786,'C':788,'D':788,'E':790,'F':793,'G':794,'H':816,'I':823,'J':789,'K':841,'L':823,'M':833,'N':816,'O':831,'P':923,'Q':744,'R':723,'S':749,'T':790,'U':792,'V':695,'W':776,
- 'X':768,'Y':792,'Z':759,'[':707,'\\':708,']':682,'^':701,'_':826,'`':815,'a':789,'b':789,'c':707,'d':687,'e':696,'f':689,'g':786,'h':787,'i':713,'j':791,'k':785,'l':791,'m':873,
- 'n':761,'o':762,'p':762,'q':759,'r':759,'s':892,'t':892,'u':788,'v':784,'w':438,'x':138,'y':277,'z':415,'{':392,'|':392,'}':668,'~':668,'\x7f':0,'\x80':390,'\x81':390,'\x82':317,'\x83':317,
- '\x84':276,'\x85':276,'\x86':509,'\x87':509,'\x88':410,'\x89':410,'\x8a':234,'\x8b':234,'\x8c':334,'\x8d':334,'\x8e':0,'\x8f':0,'\x90':0,'\x91':0,'\x92':0,'\x93':0,'\x94':0,'\x95':0,'\x96':0,'\x97':0,'\x98':0,'\x99':0,
- '\x9a':0,'\x9b':0,'\x9c':0,'\x9d':0,'\x9e':0,'\x9f':0,'\xa0':0,'\xa1':732,'\xa2':544,'\xa3':544,'\xa4':910,'\xa5':667,'\xa6':760,'\xa7':760,'\xa8':776,'\xa9':595,'\xaa':694,'\xab':626,'\xac':788,'\xad':788,'\xae':788,'\xaf':788,
- '\xb0':788,'\xb1':788,'\xb2':788,'\xb3':788,'\xb4':788,'\xb5':788,'\xb6':788,'\xb7':788,'\xb8':788,'\xb9':788,'\xba':788,'\xbb':788,'\xbc':788,'\xbd':788,'\xbe':788,'\xbf':788,'\xc0':788,'\xc1':788,'\xc2':788,'\xc3':788,'\xc4':788,'\xc5':788,
- '\xc6':788,'\xc7':788,'\xc8':788,'\xc9':788,'\xca':788,'\xcb':788,'\xcc':788,'\xcd':788,'\xce':788,'\xcf':788,'\xd0':788,'\xd1':788,'\xd2':788,'\xd3':788,'\xd4':894,'\xd5':838,'\xd6':1016,'\xd7':458,'\xd8':748,'\xd9':924,'\xda':748,'\xdb':918,
- '\xdc':927,'\xdd':928,'\xde':928,'\xdf':834,'\xe0':873,'\xe1':828,'\xe2':924,'\xe3':924,'\xe4':917,'\xe5':930,'\xe6':931,'\xe7':463,'\xe8':883,'\xe9':836,'\xea':836,'\xeb':867,'\xec':867,'\xed':696,'\xee':696,'\xef':874,'\xf0':0,'\xf1':874,
- '\xf2':760,'\xf3':946,'\xf4':771,'\xf5':865,'\xf6':771,'\xf7':888,'\xf8':967,'\xf9':888,'\xfa':831,'\xfb':873,'\xfc':927,'\xfd':970,'\xfe':918,'\xff':0}
-
-
diff --git a/gluon/contrib/pyfpdf/html.py b/gluon/contrib/pyfpdf/html.py
index 71e5ef57..bc1c53b7 100644
--- a/gluon/contrib/pyfpdf/html.py
+++ b/gluon/contrib/pyfpdf/html.py
@@ -26,22 +26,23 @@ def hex2dec(color = "#000000"):
class HTML2FPDF(HTMLParser):
"Render basic HTML to FPDF"
- def __init__(self, pdf, image_map, **kwargs):
+ def __init__(self, pdf):
HTMLParser.__init__(self)
- self.image_map = image_map
self.style = {}
self.pre = False
self.href = ''
self.align = ''
self.page_links = {}
self.font_list = ("times","courier", "helvetica")
+ self.font = None
+ self.font_stack = []
self.pdf = pdf
self.r = self.g = self.b = 0
self.indent = 0
self.bullet = []
- self.font_face="times" # initialize font
- self.color=0 # initialize font color
- self.set_font(kwargs.get("font","times"), kwargs.get("fontsize",12))
+ self.set_font("times", 12)
+ self.font_face = "times" # initialize font
+ self.color = 0 #initialize font color
self.table = None # table attributes
self.table_col_width = None # column (header) widths
self.table_col_index = None # current column index
@@ -53,7 +54,7 @@ class HTML2FPDF(HTMLParser):
self.thead = None
self.tfoot = None
self.theader_out = self.tfooter_out = False
-
+
def width2mm(self, length):
if length[-1]=='%':
total = self.pdf.w - self.pdf.r_margin - self.pdf.l_margin
@@ -66,7 +67,10 @@ class HTML2FPDF(HTMLParser):
def handle_data(self, txt):
if self.td is not None: # drawing a table?
if 'width' not in self.td and 'colspan' not in self.td:
- l = [self.table_col_width[self.table_col_index]]
+ try:
+ l = [self.table_col_width[self.table_col_index]]
+ except IndexError:
+ raise RuntimeError("Table column/cell width not specified, unable to continue")
elif 'colspan' in self.td:
i = self.table_col_index
colspan = int(self.td['colspan'])
@@ -83,7 +87,7 @@ class HTML2FPDF(HTMLParser):
else:
self.set_style('B',True)
border = border or 'B'
- align = self.td.get('align', 'C')[0].upper()
+ align = 'C'
bgcolor = hex2dec(self.td.get('bgcolor', self.tr.get('bgcolor', '')))
# parsing table header/footer (drawn later):
if self.thead is not None:
@@ -97,13 +101,13 @@ class HTML2FPDF(HTMLParser):
self.pdf.add_page()
self.theader_out = self.tfooter_out = False
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.box_shadow(w, h, bgcolor)
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
+ # ignore anything else than td inside a table
pass
elif self.align:
if DEBUG: print "cell", txt, "*"
@@ -138,7 +142,7 @@ class HTML2FPDF(HTMLParser):
self.pdf.set_x(self.table_offset)
#self.pdf.set_x(x)
self.theader_out = True
-
+
def output_table_footer(self):
if self.tfooter:
x = self.pdf.x
@@ -152,7 +156,7 @@ class HTML2FPDF(HTMLParser):
if int(self.table.get('border', 0)):
self.output_table_sep()
self.tfooter_out = True
-
+
def output_table_sep(self):
self.pdf.set_x(self.table_offset)
x1 = self.pdf.x
@@ -173,7 +177,7 @@ class HTML2FPDF(HTMLParser):
if tag=='p':
self.pdf.ln(5)
if attrs:
- self.align=attrs['align'].lower()
+ if attrs: self.align = attrs.get('align')
if tag in ('h1', 'h2', 'h3', 'h4', 'h5', 'h6'):
k = (2, 1.5, 1.17, 1, 0.83, 0.67)[int(tag[1])]
self.pdf.ln(5*k)
@@ -208,6 +212,8 @@ class HTML2FPDF(HTMLParser):
self.pdf.write(self.h,'%s%s ' % (' '*5*self.indent, bullet))
self.set_text_color()
if tag=='font':
+ # 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'])
self.set_text_color(*color)
@@ -234,6 +240,7 @@ class HTML2FPDF(HTMLParser):
self.tfooter = []
self.thead = None
self.tfoot = None
+ self.table_h = 0
self.pdf.ln()
if tag=='tr':
self.tr = dict([(k.lower(), v) for k,v in attrs.items()])
@@ -244,7 +251,7 @@ class HTML2FPDF(HTMLParser):
if tag=='th':
self.td = dict([(k.lower(), v) for k,v in attrs.items()])
self.th = True
- if self.td['width']:
+ if 'width' in self.td:
self.table_col_width.append(self.td['width'])
if tag=='thead':
self.thead = {}
@@ -258,8 +265,7 @@ class HTML2FPDF(HTMLParser):
h = px2mm(attrs.get('height',0))
if self.align and self.align[0].upper() == 'C':
x = (self.pdf.w-x)/2.0 - w/2.0
- self.pdf.image(self.image_map(attrs['src']),
- x, y, w, h, link=self.href)
+ self.pdf.image(attrs['src'], x, y, w, h, link=self.href)
self.pdf.set_x(x+w)
self.pdf.set_y(y+h)
if tag=='b' or tag=='i' or tag=='u':
@@ -324,12 +330,13 @@ class HTML2FPDF(HTMLParser):
self.td = None
self.th = False
if tag=='font':
- if self.color:
+ # recover last font state
+ face, size, color = self.font_stack.pop()
+ if face:
self.pdf.set_text_color(0,0,0)
self.color = None
- if self.font_face:
- self.set_font('Times',12)
-
+ self.set_font(face, size)
+ self.font = None
if tag=='center':
self.align = None
@@ -345,7 +352,7 @@ class HTML2FPDF(HTMLParser):
self.set_style('u', False)
self.set_style('b', False)
self.set_style('i', False)
- self.set_text_color()
+ self.set_text_color()
def set_style(self, tag=None, enable=None):
#Modify style and select corresponding font
@@ -367,7 +374,7 @@ class HTML2FPDF(HTMLParser):
self.r = r
self.g = g
self.b = b
-
+
def put_link(self, url, txt):
#Put a hyperlink
self.set_text_color(0,0,255)
@@ -381,79 +388,9 @@ class HTML2FPDF(HTMLParser):
self.pdf.line(self.pdf.get_x(),self.pdf.get_y(),self.pdf.get_x()+187,self.pdf.get_y())
self.pdf.ln(3)
-class HTMLMixin():
- def write_html(self, text, image_map=lambda x:x, **kwargs):
+class HTMLMixin(object):
+ def write_html(self, text):
"Parse HTML and convert it to PDF"
- h2p = HTML2FPDF(self,image_map=image_map,**kwargs)
+ h2p = HTML2FPDF(self)
h2p.feed(text)
-if __name__=='__main__':
- html="""
-
html2fpdf
-
Basic usage
-
You can now easily print text mixing different
-styles : bold, italic, underlined, or
-all at once! You can also insert links
-on text, such as www.fpdf.org,
-or on an image: click on the logo.
-
-
-
-
Sample List
-
option 1
-
option 2
-
option 3
-
-
-
Header 1
header 2
-
-
cell 1
cell 2
-
cell 2
cell 3
-
-
-
-
-
-
Header 1
header 2
-
footer 1
footer 2
-
-
cell 1
cell 2
-
-
cell 1
cell 2
-
-
-
cell spanned
-
-""" + """
-
cell 3
cell 4
-
-
cell 5
cell 6
-
""" * 200 + """
-
-
-"""
-
- class MyFPDF(FPDF, HTMLMixin):
- def header(self):
- self.image('tutorial/logo_pb.png',10,8,33)
- self.set_font('Arial','B',15)
- self.cell(80)
- self.cell(30,10,'Title',1,0,'C')
- self.ln(20)
-
- def footer(self):
- self.set_y(-15)
- self.set_font('Arial','I',8)
- txt = 'Page %s of %s' % (self.page_no(), self.alias_nb_pages())
- self.cell(0,10,txt,0,0,'C')
-
- pdf=MyFPDF()
- #First page
- pdf.add_page()
- pdf.write_html(html)
- pdf.output('html.pdf','F')
-
- import os
- os.system("evince html.pdf")
-
-
diff --git a/gluon/contrib/pyfpdf/php.py b/gluon/contrib/pyfpdf/php.py
new file mode 100644
index 00000000..80a4622d
--- /dev/null
+++ b/gluon/contrib/pyfpdf/php.py
@@ -0,0 +1,49 @@
+#!/usr/bin/env python
+# -*- coding: latin-1 -*-
+
+# fpdf php helpers:
+
+def substr(s, start, length=-1):
+ if length < 0:
+ length=len(s)-start
+ return s[start:start+length]
+
+def sprintf(fmt, *args): return fmt % args
+
+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),
+
+def UTF8ToUTF16BE(instr, setbom=True):
+ "Converts UTF-8 strings to UTF16-BE."
+ outstr = ""
+ if (setbom):
+ outstr += "\xFE\xFF";
+ if not isinstance(instr, unicode):
+ instr = instr.decode('UTF-8')
+ outstr += instr.encode('UTF-16BE')
+ return outstr
+
+def UTF8StringToArray(instr):
+ "Converts UTF-8 strings to codepoints array"
+ return [ord(c) for c in instr]
+
+# ttfints php helpers:
+
+def die(msg):
+ raise RuntimeError(msg)
+
+def str_repeat(s, count):
+ return s * count
+
+def str_pad(s, pad_length=0, pad_char= " ", pad_type= +1 ):
+ if pad_type<0: # pad left
+ return s.rjust(pad_length, pad_char)
+ elif pad_type>0: # pad right
+ return s.ljust(pad_length, pad_char)
+ else: # pad both
+ return s.center(pad_length, pad_char)
+
+strlen = count = lambda s: len(s)
\ No newline at end of file
diff --git a/gluon/contrib/pyfpdf/template.py b/gluon/contrib/pyfpdf/template.py
old mode 100755
new mode 100644
index 8b158b36..2d38c9f9
--- a/gluon/contrib/pyfpdf/template.py
+++ b/gluon/contrib/pyfpdf/template.py
@@ -16,8 +16,9 @@ class Template:
def __init__(self, infile=None, elements=None, format='A4', orientation='portrait',
title='', author='', subject='', creator='', keywords=''):
if elements:
- self.elements = dict([(v['name'].lower(),v) for v in elements])
- self.handlers = {'T': self.text, 'L': self.line, 'I': self.image,
+ self.elements = elements
+ self.keys = [v['name'].lower() for v in self.elements]
+ self.handlers = {'T': self.text, 'L': self.line, 'I': self.image,
'B': self.rect, 'BC': self.barcode, }
self.pg_no = 0
self.texts = {}
@@ -32,34 +33,33 @@ class Template:
"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')
- self.elements = {}
- f = open(infile, 'rb')
- try:
- 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[kargs['name'].lower()] = kargs
- finally:
- f.close()
+ '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.keys = [v['name'].lower() for v in self.elements]
def add_page(self):
self.pg_no += 1
self.texts[self.pg_no] = {}
-
+
def __setitem__(self, name, value):
- if name.lower() in self.elements:
+ if self.has_key(name):
if isinstance(value,unicode):
value = value.encode("latin1","ignore")
+ elif value is None:
+ value = ""
else:
value = str(value)
self.texts[self.pg_no][name.lower()] = value
@@ -67,14 +67,27 @@ class Template:
# setitem shortcut (may be further extended)
set = __setitem__
+ def has_key(self, name):
+ return name.lower() in self.keys
+
def __getitem__(self, name):
- if name.lower() in self.elements:
- return self.texts[self.pg_no].get(name.lower(), self.elements[name.lower()]['text'])
+ if self.has_key(name):
+ key = name.lower()
+ if key in self.texts:
+ # text for this page:
+ return self.texts[self.pg_no][key]
+ else:
+ # find first element for default text:
+ elements = [element for element in self.elements
+ if element['name'].lower() == key]
+ if elements:
+ return elements[0]['text']
def split_multicell(self, text, element_name):
"Divide (\n) a string using a given element width"
pdf = self.pdf
- element = self.elements[element_name.lower()]
+ element = [element for element in self.elements
+ if element['name'].lower() == element_name.lower()][0]
style = ""
if element['bold']: style += "B"
if element['italic']: style += "I"
@@ -88,7 +101,7 @@ class Template:
return pdf.multi_cell(w=element['x2']-element['x1'],
h=element['y2']-element['y1'],
txt=text,align=align,split_only=True)
-
+
def render(self, outfile, dest="F"):
pdf = self.pdf
for pg in range(1, self.pg_no+1):
@@ -96,7 +109,7 @@ class Template:
pdf.set_font('Arial','B',16)
pdf.set_auto_page_break(False,margin=0)
- for element in sorted(self.elements.values(),key=lambda x: x['priority']):
+ for element in sorted(self.elements,key=lambda x: x['priority']):
#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'])
@@ -105,12 +118,12 @@ class Template:
self.handlers[element['type'].upper()](pdf, **element)
if 'rotate' in element:
pdf.rotate(0)
-
+
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="",
- foreground=0, backgroud=65535,
+
+ def text(self, pdf, x1=0, y1=0, x2=0, y2=0, text='', font="arial", size=10,
+ bold=False, italic=False, underline=False, align="",
+ foreground=0, backgroud=65535, multiline=None,
*args, **kwargs):
if text:
if pdf.text_color!=rgb(foreground):
@@ -134,7 +147,19 @@ class Template:
##m_k = 72 / 2.54
##h = (size/m_k)
pdf.set_xy(x1,y1)
- pdf.cell(w=x2-x1,h=y2-y1,txt=text,border=0,ln=0,align=align)
+ if multiline is None:
+ # multiline==None: write without wrapping/trimming (default)
+ pdf.cell(w=x2-x1,h=y2-y1,txt=text,border=0,ln=0,align=align)
+ elif multiline:
+ # multiline==True: automatic word - warp
+ pdf.multi_cell(w=x2-x1,h=y2-y1,txt=text,border=0,align=align)
+ else:
+ # 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
+ pdf.cell(w=x2-x1,h=y2-y1,txt=text,border=0,ln=0,align=align)
+
#pdf.Text(x=x1,y=y1,txt=text)
def line(self, pdf, x1=0, y1=0, x2=0, y2=0, size=0, foreground=0, *args, **kwargs):
@@ -176,7 +201,7 @@ if __name__ == "__main__":
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):
@@ -185,7 +210,7 @@ if __name__ == "__main__":
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,
+ qty=qty, price=price,
amount=qty*price,
ds="%s: %s" % (i,ds)))
@@ -228,7 +253,7 @@ if __name__ == "__main__":
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_header2"] = "http://www.example.com"
f["company_footer1"] = "Tax Code ..."
f["company_footer2"] = "Tax/VAT ID ..."
f['number'] = '0001-00001234'
@@ -236,9 +261,9 @@ if __name__ == "__main__":
f['due_date'] = '2099-09-10'
f['customer_name'] = "Sample Client"
f['customer_address'] = "Siempreviva 1234"
-
+
# print line item...
- li = 0
+ li = 0
k = 0
total = Decimal("0.00")
for it in li_items:
@@ -268,11 +293,9 @@ if __name__ == "__main__":
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")
-
-
diff --git a/gluon/contrib/pyfpdf/ttfonts.py b/gluon/contrib/pyfpdf/ttfonts.py
new file mode 100644
index 00000000..cdb280a3
--- /dev/null
+++ b/gluon/contrib/pyfpdf/ttfonts.py
@@ -0,0 +1,1033 @@
+#******************************************************************************
+# TTFontFile class
+#
+# This class is based on The ReportLab Open Source PDF library
+# written in Python - http://www.reportlab.com/software/opensource/
+# together with ideas from the OpenOffice source code and others.
+#
+# Version: 1.04
+# Date: 2011-09-18
+# Author: Ian Back
+# License: LGPL
+# Copyright (c) Ian Back, 2010
+# Ported to Python 2.7 by Mariano Reingart (reingart@gmail.com) on 2012
+# This header must be retained in any redistribution or
+# modification of the file.
+#
+#******************************************************************************
+
+from struct import pack, unpack, unpack_from
+import re
+import warnings
+from php import die, substr, str_repeat, str_pad, strlen, count
+
+
+# Define the value used in the "head" table of a created TTF file
+# 0x74727565 "true" for Mac
+# 0x00010000 for Windows
+# Either seems to work for a font embedded in a PDF file
+# when read by Adobe Reader on a Windows PC(!)
+_TTF_MAC_HEADER = False
+
+
+# TrueType Font Glyph operators
+GF_WORDS = (1 << 0)
+GF_SCALE = (1 << 3)
+GF_MORE = (1 << 5)
+GF_XYSCALE = (1 << 6)
+GF_TWOBYTWO = (1 << 7)
+
+
+def sub32(x, y):
+ xlo = x[1]
+ xhi = x[0]
+ ylo = y[1]
+ yhi = y[0]
+ if (ylo > xlo):
+ xlo += 1 << 16
+ yhi += 1
+ reslo = xlo-ylo
+ if (yhi > xhi):
+ xhi += 1 << 16
+ reshi = xhi-yhi
+ reshi = reshi & 0xFFFF
+ return (reshi, reslo)
+
+def calcChecksum(data):
+ if (strlen(data) % 4):
+ data += str_repeat("\0", (4-(len(data) % 4)))
+ hi=0x0000
+ lo=0x0000
+ for i in range(0, len(data), 4):
+ hi += (ord(data[i])<<8) + ord(data[i+1])
+ lo += (ord(data[i+2])<<8) + ord(data[i+3])
+ hi += lo >> 16
+ lo = lo & 0xFFFF
+ hi = hi & 0xFFFF
+ return (hi, lo)
+
+
+class TTFontFile:
+
+ def __init__(self):
+ self.maxStrLenRead = 200000 # Maximum size of glyf table to read in as string (otherwise reads each glyph from file)
+
+ 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()
+
+ def readTableDirectory(self, ):
+ self.numTables = self.read_ushort()
+ self.searchRange = self.read_ushort()
+ self.entrySelector = self.read_ushort()
+ self.rangeShift = self.read_ushort()
+ self.tables = {}
+ for i in range(self.numTables):
+ record = {}
+ record['tag'] = self.read_tag()
+ record['checksum'] = (self.read_ushort(),self.read_ushort())
+ record['offset'] = self.read_ulong()
+ record['length'] = self.read_ulong()
+ self.tables[record['tag']] = record
+
+ def get_table_pos(self, tag):
+ offset = self.tables[tag]['offset']
+ length = self.tables[tag]['length']
+ return (offset, length)
+
+ def seek(self, pos):
+ self._pos = pos
+ self.fh.seek(self._pos)
+
+ def skip(self, delta):
+ self._pos = self._pos + delta
+ self.fh.seek(self._pos)
+
+ def seek_table(self, tag, offset_in_table = 0):
+ tpos = self.get_table_pos(tag)
+ self._pos = tpos[0] + offset_in_table
+ self.fh.seek(self._pos)
+ return self._pos
+
+ def read_tag(self):
+ self._pos += 4
+ return self.fh.read(4)
+
+ def read_short(self):
+ self._pos += 2
+ s = self.fh.read(2)
+ a = (ord(s[0])<<8) + ord(s[1])
+ if (a & (1 << 15) ):
+ a = (a - (1 << 16))
+ return a
+
+ def unpack_short(self, s):
+ a = (ord(s[0])<<8) + ord(s[1])
+ if (a & (1 << 15) ):
+ a = (a - (1 << 16))
+ return a
+
+ def read_ushort(self):
+ self._pos += 2
+ s = self.fh.read(2)
+ return (ord(s[0])<<8) + ord(s[1])
+
+ def read_ulong(self):
+ self._pos += 4
+ s = self.fh.read(4)
+ # if large uInt32 as an integer, PHP converts it to -ve
+ return (ord(s[0])*16777216) + (ord(s[1])<<16) + (ord(s[2])<<8) + ord(s[3]) # 16777216 = 1<<24
+
+ def get_ushort(self, pos):
+ self.fh.seek(pos)
+ s = self.fh.read(2)
+ return (ord(s[0])<<8) + ord(s[1])
+
+ def get_ulong(self, pos):
+ self.fh.seek(pos)
+ s = self.fh.read(4)
+ # iF large uInt32 as an integer, PHP converts it to -ve
+ return (ord(s[0])*16777216) + (ord(s[1])<<16) + (ord(s[2])<<8) + ord(s[3]) # 16777216 = 1<<24
+
+ def pack_short(self, val):
+ if (val<0):
+ val = abs(val)
+ val = ~val
+ val += 1
+ return pack(">H",val)
+
+ def splice(self, stream, offset, value):
+ return substr(stream,0,offset) + value + substr(stream,offset+strlen(value))
+
+ def _set_ushort(self, stream, offset, value):
+ up = pack(">H", value)
+ return self.splice(stream, offset, up)
+
+ def _set_short(self, stream, offset, val):
+ if (val<0):
+ val = abs(val)
+ val = ~val
+ val += 1
+ up = pack(">H",val)
+ return self.splice(stream, offset, up)
+
+ def get_chunk(self, pos, length):
+ self.fh.seek(pos)
+ if (length <1): return ''
+ return (self.fh.read(length))
+
+ def get_table(self, tag):
+ (pos, length) = self.get_table_pos(tag)
+ if (length == 0):
+ die('Truetype font (' + self.filename + '): error reading table: ' + tag)
+ self.fh.seek(pos)
+ return (self.fh.read(length))
+
+ def add(self, tag, data):
+ if (tag == 'head') :
+ data = self.splice(data, 8, "\0\0\0\0")
+ self.otables[tag] = data
+
+############################################/
+############################################/
+
+############################################/
+
+ def extractInfo(self):
+ #################/
+ # name - Naming table
+ #################/
+ self.sFamilyClass = 0
+ self.sFamilySubClass = 0
+
+ name_offset = self.seek_table("name")
+ format = self.read_ushort()
+ if (format != 0):
+ die("Unknown name table format " + format)
+ numRecords = self.read_ushort()
+ string_data_offset = name_offset + self.read_ushort()
+ names = {1:'',2:'',3:'',4:'',6:''}
+ K = names.keys()
+ nameCount = len(names)
+ for i in range(numRecords):
+ platformId = self.read_ushort()
+ encodingId = self.read_ushort()
+ languageId = self.read_ushort()
+ nameId = self.read_ushort()
+ length = self.read_ushort()
+ offset = self.read_ushort()
+ if (nameId not in K): continue
+ N = ''
+ if (platformId == 3 and encodingId == 1 and languageId == 0x409): # Microsoft, Unicode, US English, PS Name
+ opos = self._pos
+ self.seek(string_data_offset + offset)
+ if (length % 2 != 0):
+ die("PostScript name is UTF-16BE string of odd length")
+ length /= 2
+ N = ''
+ while (length > 0):
+ char = self.read_ushort()
+ N += (chr(char))
+ length -= 1
+ self._pos = opos
+ self.seek(opos)
+
+ 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)
+ self._pos = opos
+ self.seek(opos)
+
+ if (N and names[nameId]==''):
+ names[nameId] = N
+ nameCount -= 1
+ if (nameCount==0): break
+
+
+ if (names[6]):
+ psName = names[6]
+ elif (names[4]):
+ psName = re.sub(' ','-',names[4])
+ elif (names[1]):
+ psName = re.sub(' ','-',names[1])
+ else:
+ psName = ''
+ if (not psName):
+ die("Could not find PostScript font name")
+ self.name = psName
+ if (names[1]):
+ self.familyName = names[1]
+ else:
+ self.familyName = psName
+ if (names[2]):
+ self.styleName = names[2]
+ else:
+ self.styleName = 'Regular'
+ if (names[4]):
+ self.fullName = names[4]
+ else:
+ self.fullName = psName
+ if (names[3]):
+ self.uniqueFontID = names[3]
+ else:
+ self.uniqueFontID = psName
+ if (names[6]):
+ self.fullName = names[6]
+
+ #################/
+ # head - Font header table
+ #################/
+ self.seek_table("head")
+ self.skip(18)
+ self.unitsPerEm = unitsPerEm = self.read_ushort()
+ scale = 1000 / float(unitsPerEm)
+ self.skip(16)
+ xMin = self.read_short()
+ yMin = self.read_short()
+ xMax = self.read_short()
+ yMax = self.read_short()
+ self.bbox = [(xMin*scale), (yMin*scale), (xMax*scale), (yMax*scale)]
+ self.skip(3*2)
+ indexToLocFormat = self.read_ushort()
+ glyphDataFormat = self.read_ushort()
+ if (glyphDataFormat != 0):
+ die('Unknown glyph data format ' + glyphDataFormat)
+
+ #################/
+ # hhea metrics table
+ #################/
+ # ttf2t1 seems to use this value rather than the one in OS/2 - so put in for compatibility
+ if ("hhea" in self.tables):
+ self.seek_table("hhea")
+ self.skip(4)
+ hheaAscender = self.read_short()
+ hheaDescender = self.read_short()
+ self.ascent = (hheaAscender *scale)
+ self.descent = (hheaDescender *scale)
+
+
+ #################/
+ # OS/2 - OS/2 and Windows metrics table
+ #################/
+ if ("OS/2" in self.tables):
+ self.seek_table("OS/2")
+ version = self.read_ushort()
+ self.skip(2)
+ usWeightClass = self.read_ushort()
+ self.skip(2)
+ fsType = self.read_ushort()
+ if (fsType == 0x0002 or (fsType & 0x0300) != 0):
+ die('ERROR - Font file ' + self.filename + ' cannot be embedded due to copyright restrictions.')
+ self.restrictedUse = True
+
+ self.skip(20)
+ sF = self.read_short()
+ self.sFamilyClass = (sF >> 8)
+ self.sFamilySubClass = (sF & 0xFF)
+ self._pos += 10 #PANOSE = 10 byte length
+ panose = self.fh.read(10)
+ self.skip(26)
+ sTypoAscender = self.read_short()
+ sTypoDescender = self.read_short()
+ if (not self.ascent):
+ self.ascent = (sTypoAscender*scale)
+ if (not self.descent):
+ self.descent = (sTypoDescender*scale)
+ if (version > 1):
+ self.skip(16)
+ sCapHeight = self.read_short()
+ self.capHeight = (sCapHeight*scale)
+ else:
+ self.capHeight = self.ascent
+
+ else:
+ usWeightClass = 500
+ if (not self.ascent): self.ascent = (yMax*scale)
+ if (not self.descent): self.descent = (yMin*scale)
+ self.capHeight = self.ascent
+
+ self.stemV = 50 + int(pow((usWeightClass / 65.0),2))
+
+ #################/
+ # post - PostScript table
+ #################/
+ self.seek_table("post")
+ self.skip(4)
+ self.italicAngle = self.read_short() + self.read_ushort() / 65536.0
+ self.underlinePosition = self.read_short() * scale
+ self.underlineThickness = self.read_short() * scale
+ isFixedPitch = self.read_ulong()
+
+ self.flags = 4
+
+ if (self.italicAngle!= 0):
+ self.flags = self.flags | 64
+ if (usWeightClass >= 600):
+ self.flags = self.flags | 262144
+ if (isFixedPitch):
+ self.flags = self.flags | 1
+
+ #################/
+ # hhea - Horizontal header table
+ #################/
+ self.seek_table("hhea")
+ self.skip(32)
+ metricDataFormat = self.read_ushort()
+ if (metricDataFormat != 0):
+ die('Unknown horizontal metric data format '.metricDataFormat)
+ numberOfHMetrics = self.read_ushort()
+ if (numberOfHMetrics == 0):
+ die('Number of horizontal metrics is 0')
+
+ #################/
+ # 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
+ 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 == 1) or platformID == 0): # Microsoft, Unicode
+ format = self.get_ushort(cmap_offset + offset)
+ if (format == 4):
+ if (not unicode_cmap_offset):
+ unicode_cmap_offset = cmap_offset + offset
+ break
+ self.seek(save_pos )
+
+ if (not unicode_cmap_offset):
+ die('Font (' + self.filename + ') does not have cmap for Unicode (platform 3, encoding 1, format 4, or platform 0, any encoding, format 4)')
+
+ glyphToChar = {}
+ charToGlyph = {}
+ self.getCMAP4(unicode_cmap_offset, glyphToChar, charToGlyph )
+
+ #################/
+ # hmtx - Horizontal metrics table
+ #################/
+ self.getHMTX(numberOfHMetrics, numGlyphs, glyphToChar, scale)
+
+
+############################################/
+############################################/
+
+ 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()
+
+ #################/
+ # 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()
+
+ #################/
+ # 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
+ 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 == 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):
+ die('Font (' + self.filename + ') does not have cmap for Unicode (platform 3, encoding 1, format 4, or platform 0, any encoding, format 4)')
+
+ glyphToChar = {}
+ charToGlyph = {}
+ self.getCMAP4(unicode_cmap_offset, glyphToChar, charToGlyph )
+
+ self.charToGlyph = charToGlyph
+
+ #################/
+ # hmtx - Horizontal metrics table
+ #################/
+ scale = 1 # not used
+ self.getHMTX(numberOfHMetrics, numGlyphs, glyphToChar, scale)
+
+ #################/
+ # 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.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)
+
+ 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))
+
+ # 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)
+
+ # 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 ):
+ 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]
+
+ 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:
+ 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 = ''
+ 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
+
+ offsets.append(pos)
+ try:
+ glyphPos = self.glyphPos[originalGlyphIdx]
+ glyphLen = self.glyphPos[originalGlyphIdx + 1] - glyphPos
+ except IndexError:
+ warnings.warn("missing glyph %s" % (originalGlyphIdx))
+ glyphLen = 0
+
+ if (glyfLength < self.maxStrLenRead):
+ data = substr(glyphData,glyphPos,glyphLen)
+ else:
+ if (glyphLen > 0):
+ data = self.get_chunk(glyfOffset+glyphPos,glyphLen)
+ else:
+ data = ''
+
+ 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
+
+ offsets.append(pos)
+ self.add('glyf', glyf)
+
+ # hmtx - Horizontal Metrics
+ self.add('hmtx', hmtxstr)
+
+ # 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
+ stm = self.endTTFile('')
+ return stm
+
+
+ #########################################
+ # Recursively get composite glyph data
+ def getGlyphData(self, originalGlyphIdx, nonlocals):
+ # &maxdepth, &depth, &points, &contours
+ nonlocals['depth'] += 1
+ nonlocals['maxdepth'] = max(nonlocals['maxdepth'], nonlocals['depth'])
+ if (len(self.glyphdata[originalGlyphIdx]['compGlyphs'])):
+ for glyphIdx in self.glyphdata[originalGlyphIdx]['compGlyphs']:
+ self.getGlyphData(glyphIdx, nonlocals)
+
+ elif ((self.glyphdata[originalGlyphIdx]['nContours'] > 0) and nonlocals['depth'] > 0): # simple
+ contours += self.glyphdata[originalGlyphIdx]['nContours']
+ points += self.glyphdata[originalGlyphIdx]['nPoints']
+
+ nonlocals['depth'] -= 1
+
+
+ #########################################
+ # Recursively get composite glyphs
+ def getGlyphs(self, originalGlyphIdx, nonlocals):
+ # &start, &glyphSet, &subsetglyphs)
+
+ try:
+ glyphPos = self.glyphPos[originalGlyphIdx]
+ glyphLen = self.glyphPos[originalGlyphIdx + 1] - glyphPos
+ except IndexError:
+ warnings.warn("missing glyph %s" % (originalGlyphIdx))
+ return
+
+ if (not glyphLen):
+ return
+
+ self.seek(nonlocals['start'] + glyphPos)
+ numberOfContours = self.read_short()
+ if (numberOfContours < 0):
+ self.skip(8)
+ flags = GF_MORE
+ while (flags & GF_MORE):
+ flags = self.read_ushort()
+ glyphIdx = self.read_ushort()
+ if (glyphIdx not in nonlocals['glyphSet']):
+ nonlocals['glyphSet'][glyphIdx] = len(nonlocals['subsetglyphs']) # old glyphID to new glyphID
+ nonlocals['subsetglyphs'].append((glyphIdx, 1))
+
+ savepos = self.fh.tell()
+ self.getGlyphs(glyphIdx, nonlocals)
+ self.seek(savepos)
+ if (flags & GF_WORDS):
+ self.skip(4)
+ else:
+ self.skip(2)
+ if (flags & GF_SCALE):
+ self.skip(2)
+ elif (flags & GF_XYSCALE):
+ self.skip(4)
+ elif (flags & GF_TWOBYTWO):
+ self.skip(8)
+
+ #########################################
+
+ def getHMTX(self, numberOfHMetrics, numGlyphs, glyphToChar, scale):
+ start = self.seek_table("hmtx")
+ aw = 0
+ self.charWidths = [0] * 256*256*2
+ nCharWidths = 0
+ if ((numberOfHMetrics*4) < self.maxStrLenRead):
+ data = self.get_chunk(start,(numberOfHMetrics*4))
+ arr = unpack(">" + "H" * (len(data)/2), data)
+ else:
+ self.seek(start)
+ for glyph in range(numberOfHMetrics):
+ if ((numberOfHMetrics*4) < self.maxStrLenRead):
+ aw = arr[(glyph*2)] # PHP starts arrays from index 0!? +1
+ else:
+ aw = self.read_ushort()
+ lsb = self.read_ushort()
+
+ if (glyph in glyphToChar or glyph == 0):
+ if (aw >= (1 << 15) ):
+ aw = 0 # 1.03 Some (arabic) fonts have -ve values for width
+ # although should be unsigned value - comes out as e.g. 65108 (intended -50)
+ if (glyph == 0):
+ self.defaultWidth = scale*aw
+ continue
+
+ for char in glyphToChar[glyph]:
+ if (char != 0 and char != 65535):
+ w = int(round(scale*aw))
+ if (w == 0): w = 65535
+ if (char < 196608):
+ self.charWidths[char] = w
+ nCharWidths += 1
+
+
+ data = self.get_chunk((start+numberOfHMetrics*4),(numGlyphs*2))
+ arr = unpack(">" + "H" * (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))
+ if (w == 0): w = 65535
+ if (char < 196608):
+ self.charWidths[char] = w
+ nCharWidths += 1
+
+
+ # NB 65535 is a set width of 0
+ # First bytes define number of chars in font
+ self.charWidths[0] = nCharWidths
+
+
+ def getHMetric(self, numberOfHMetrics, gid):
+ start = self.seek_table("hmtx")
+ if (gid < numberOfHMetrics):
+ self.seek(start+(gid*4))
+ hm = self.fh.read(4)
+ else:
+ self.seek(start+((numberOfHMetrics-1)*4))
+ hm = self.fh.read(2)
+ self.seek(start+(numberOfHMetrics*2)+(gid*2))
+ hm += self.fh.read(2)
+ return hm
+
+
+ def getLOCA(self, indexToLocFormat, numGlyphs):
+ start = self.seek_table('loca')
+ self.glyphPos = []
+ if (indexToLocFormat == 0):
+ data = self.get_chunk(start,(numGlyphs*2)+2)
+ arr = unpack(">" + "H" * (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)
+ for n in range(numGlyphs):
+ self.glyphPos.append((arr[n])) # n+1 !?
+ else:
+ die('Unknown location table format ' + indexToLocFormat)
+
+ # CMAP Format 4
+ def getCMAP4(self, unicode_cmap_offset, glyphToChar, charToGlyph ):
+ self.maxUniChar = 0
+ self.seek(unicode_cmap_offset + 2)
+ length = self.read_ushort()
+ limit = unicode_cmap_offset + length
+ self.skip(2)
+
+ segCount = self.read_ushort() / 2
+ self.skip(6)
+ endCount = []
+ for i in range(segCount):
+ endCount.append(self.read_ushort())
+ self.skip(2)
+ startCount = []
+ for i in range(segCount):
+ startCount.append(self.read_ushort())
+ idDelta = []
+ for i in range(segCount):
+ idDelta.append(self.read_short()) # ???? was unsigned short
+ idRangeOffset_start = self._pos
+ idRangeOffset = []
+ for i in range(segCount):
+ idRangeOffset.append(self.read_ushort())
+
+ for n in range(segCount):
+ endpoint = (endCount[n] + 1)
+ for unichar in range(startCount[n], endpoint, 1):
+ if (idRangeOffset[n] == 0):
+ glyph = (unichar + idDelta[n]) & 0xFFFF
+ else:
+ offset = (unichar - startCount[n]) * 2 + idRangeOffset[n]
+ offset = idRangeOffset_start + 2 * n + offset
+ if (offset >= limit):
+ glyph = 0
+ else:
+ glyph = self.get_ushort(offset)
+ if (glyph != 0):
+ glyph = (glyph + idDelta[n]) & 0xFFFF
+
+ charToGlyph[unichar] = glyph
+ if (unichar < 196608):
+ self.maxUniChar = max(unichar,self.maxUniChar)
+ glyphToChar.setdefault(glyph, []).append(unichar)
+
+
+ # Put the TTF file together
+ def endTTFile(self, stm):
+ stm = ''
+ numTables = count(self.otables)
+ searchRange = 1
+ entrySelector = 0
+ while (searchRange * 2 <= numTables):
+ searchRange = searchRange * 2
+ entrySelector = entrySelector + 1
+
+ searchRange = searchRange * 16
+ rangeShift = numTables * 16 - searchRange
+
+ # Header
+ if (_TTF_MAC_HEADER):
+ stm += (pack(">LHHHH", 0x74727565, numTables, searchRange, entrySelector, rangeShift)) # Mac
+ else:
+ stm += (pack(">LHHHH", 0x00010000 , numTables, searchRange, entrySelector, rangeShift)) # Windows
+
+
+ # Table directory
+ tables = self.otables
+
+ offset = 12 + numTables * 16
+ sorted_tables = sorted(tables.items())
+ for tag, data in sorted_tables:
+ if (tag == 'head'):
+ head_start = offset
+ stm += tag
+ checksum = calcChecksum(data)
+ stm += pack(">HH", checksum[0],checksum[1])
+ stm += pack(">LL", offset, strlen(data))
+ paddedLength = (strlen(data)+3)&~3
+ offset = offset + paddedLength
+
+ # Table data
+ for tag, data in sorted_tables:
+ data += "\0\0\0"
+ stm += substr(data,0,(strlen(data)&~3))
+
+ checksum = calcChecksum(stm)
+ checksum = sub32((0xB1B0,0xAFBA), checksum)
+ chk = pack(">HH", checksum[0],checksum[1])
+ 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()
+
\ No newline at end of file
From d03bf8af8854a6b4d387d27cbc9ee9299a5bda75 Mon Sep 17 00:00:00 2001
From: mdipierro
Date: Thu, 23 Aug 2012 08:35:41 -0500
Subject: [PATCH 02/53] moved pyfpdf to fpdf, thanks Mariano
---
VERSION | 2 +-
gluon/contrib/{pyfpdf => fpdf}/__init__.py | 0
gluon/contrib/{pyfpdf => fpdf}/fonts.py | 0
gluon/contrib/{pyfpdf => fpdf}/fpdf.py | 0
gluon/contrib/{pyfpdf => fpdf}/html.py | 0
gluon/contrib/{pyfpdf => fpdf}/php.py | 0
gluon/contrib/{pyfpdf => fpdf}/template.py | 0
gluon/contrib/{pyfpdf => fpdf}/ttfonts.py | 0
gluon/contrib/pyfpdf.py | 11 +++++++++++
9 files changed, 12 insertions(+), 1 deletion(-)
rename gluon/contrib/{pyfpdf => fpdf}/__init__.py (100%)
rename gluon/contrib/{pyfpdf => fpdf}/fonts.py (100%)
rename gluon/contrib/{pyfpdf => fpdf}/fpdf.py (100%)
rename gluon/contrib/{pyfpdf => fpdf}/html.py (100%)
rename gluon/contrib/{pyfpdf => fpdf}/php.py (100%)
rename gluon/contrib/{pyfpdf => fpdf}/template.py (100%)
rename gluon/contrib/{pyfpdf => fpdf}/ttfonts.py (100%)
create mode 100644 gluon/contrib/pyfpdf.py
diff --git a/VERSION b/VERSION
index 775477a3..234bace8 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-Version 2.00.0 (2012-08-23 08:31:01) dev
+Version 2.00.0 (2012-08-23 08:35:38) dev
diff --git a/gluon/contrib/pyfpdf/__init__.py b/gluon/contrib/fpdf/__init__.py
similarity index 100%
rename from gluon/contrib/pyfpdf/__init__.py
rename to gluon/contrib/fpdf/__init__.py
diff --git a/gluon/contrib/pyfpdf/fonts.py b/gluon/contrib/fpdf/fonts.py
similarity index 100%
rename from gluon/contrib/pyfpdf/fonts.py
rename to gluon/contrib/fpdf/fonts.py
diff --git a/gluon/contrib/pyfpdf/fpdf.py b/gluon/contrib/fpdf/fpdf.py
similarity index 100%
rename from gluon/contrib/pyfpdf/fpdf.py
rename to gluon/contrib/fpdf/fpdf.py
diff --git a/gluon/contrib/pyfpdf/html.py b/gluon/contrib/fpdf/html.py
similarity index 100%
rename from gluon/contrib/pyfpdf/html.py
rename to gluon/contrib/fpdf/html.py
diff --git a/gluon/contrib/pyfpdf/php.py b/gluon/contrib/fpdf/php.py
similarity index 100%
rename from gluon/contrib/pyfpdf/php.py
rename to gluon/contrib/fpdf/php.py
diff --git a/gluon/contrib/pyfpdf/template.py b/gluon/contrib/fpdf/template.py
similarity index 100%
rename from gluon/contrib/pyfpdf/template.py
rename to gluon/contrib/fpdf/template.py
diff --git a/gluon/contrib/pyfpdf/ttfonts.py b/gluon/contrib/fpdf/ttfonts.py
similarity index 100%
rename from gluon/contrib/pyfpdf/ttfonts.py
rename to gluon/contrib/fpdf/ttfonts.py
diff --git a/gluon/contrib/pyfpdf.py b/gluon/contrib/pyfpdf.py
new file mode 100644
index 00000000..26fc2940
--- /dev/null
+++ b/gluon/contrib/pyfpdf.py
@@ -0,0 +1,11 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+"FPDF for python (a.k.a. pyfpdf)"
+# 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 *
+
+# import warnings
+# warnings.warn("pyfpdf package name is deprecated, please use fpdf instead")
From 36c191588381249d366e1884cb1387eea212e043 Mon Sep 17 00:00:00 2001
From: mdipierro
Date: Thu, 23 Aug 2012 08:36:50 -0500
Subject: [PATCH 03/53] removed unwanted trailing spaces from fpdf
---
VERSION | 2 +-
gluon/contrib/fpdf/__init__.py | 2 +
gluon/contrib/fpdf/fonts.py | 5 +-
gluon/contrib/fpdf/fpdf.py | 59 +++---
gluon/contrib/fpdf/html.py | 19 +-
gluon/contrib/fpdf/php.py | 12 +-
gluon/contrib/fpdf/template.py | 31 +--
gluon/contrib/fpdf/ttfonts.py | 354 ++++++++++++++++-----------------
8 files changed, 245 insertions(+), 239 deletions(-)
diff --git a/VERSION b/VERSION
index 234bace8..03475297 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-Version 2.00.0 (2012-08-23 08:35:38) dev
+Version 2.00.0 (2012-08-23 08:36:47) dev
diff --git a/gluon/contrib/fpdf/__init__.py b/gluon/contrib/fpdf/__init__.py
index 3ba17860..e0ffc017 100644
--- a/gluon/contrib/fpdf/__init__.py
+++ b/gluon/contrib/fpdf/__init__.py
@@ -14,3 +14,5 @@ except ImportError:
warnings.warn("web2py gluon package not installed, required for html2pdf")
from template import Template
+
+
diff --git a/gluon/contrib/fpdf/fonts.py b/gluon/contrib/fpdf/fonts.py
index 433fbf61..f584a1e8 100644
--- a/gluon/contrib/fpdf/fonts.py
+++ b/gluon/contrib/fpdf/fonts.py
@@ -83,7 +83,7 @@ fpdf_charwidths['symbol']={
'\xc6':823,'\xc7':768,'\xc8':768,'\xc9':713,'\xca':713,'\xcb':713,'\xcc':713,'\xcd':713,'\xce':713,'\xcf':713,'\xd0':768,'\xd1':713,'\xd2':790,'\xd3':790,'\xd4':890,'\xd5':823,'\xd6':549,'\xd7':250,'\xd8':713,'\xd9':603,'\xda':603,'\xdb':1042,
'\xdc':987,'\xdd':603,'\xde':987,'\xdf':603,'\xe0':494,'\xe1':329,'\xe2':790,'\xe3':790,'\xe4':786,'\xe5':713,'\xe6':384,'\xe7':384,'\xe8':384,'\xe9':384,'\xea':384,'\xeb':384,'\xec':494,'\xed':494,'\xee':494,'\xef':494,'\xf0':0,'\xf1':329,
'\xf2':274,'\xf3':686,'\xf4':686,'\xf5':686,'\xf6':384,'\xf7':384,'\xf8':384,'\xf9':384,'\xfa':384,'\xfb':384,'\xfc':494,'\xfd':494,'\xfe':494,'\xff':0}
-
+
fpdf_charwidths['times']={
'\x00':250,'\x01':250,'\x02':250,'\x03':250,'\x04':250,'\x05':250,'\x06':250,'\x07':250,'\x08':250,'\t':250,'\n':250,'\x0b':250,'\x0c':250,'\r':250,'\x0e':250,'\x0f':250,'\x10':250,'\x11':250,'\x12':250,'\x13':250,'\x14':250,'\x15':250,
'\x16':250,'\x17':250,'\x18':250,'\x19':250,'\x1a':250,'\x1b':250,'\x1c':250,'\x1d':250,'\x1e':250,'\x1f':250,' ':250,'!':333,'"':408,'#':500,'$':500,'%':833,'&':778,'\'':180,'(':333,')':333,'*':500,'+':564,
@@ -111,7 +111,7 @@ fpdf_charwidths['timesB']={
'\xc6':1000,'\xc7':722,'\xc8':667,'\xc9':667,'\xca':667,'\xcb':667,'\xcc':389,'\xcd':389,'\xce':389,'\xcf':389,'\xd0':722,'\xd1':722,'\xd2':778,'\xd3':778,'\xd4':778,'\xd5':778,'\xd6':778,'\xd7':570,'\xd8':778,'\xd9':722,'\xda':722,'\xdb':722,
'\xdc':722,'\xdd':722,'\xde':611,'\xdf':556,'\xe0':500,'\xe1':500,'\xe2':500,'\xe3':500,'\xe4':500,'\xe5':500,'\xe6':722,'\xe7':444,'\xe8':444,'\xe9':444,'\xea':444,'\xeb':444,'\xec':278,'\xed':278,'\xee':278,'\xef':278,'\xf0':500,'\xf1':556,
'\xf2':500,'\xf3':500,'\xf4':500,'\xf5':500,'\xf6':500,'\xf7':570,'\xf8':500,'\xf9':556,'\xfa':556,'\xfb':556,'\xfc':556,'\xfd':500,'\xfe':556,'\xff':500}
-
+
fpdf_charwidths['timesBI']={
'\x00':250,'\x01':250,'\x02':250,'\x03':250,'\x04':250,'\x05':250,'\x06':250,'\x07':250,'\x08':250,'\t':250,'\n':250,'\x0b':250,'\x0c':250,'\r':250,'\x0e':250,'\x0f':250,'\x10':250,'\x11':250,'\x12':250,'\x13':250,'\x14':250,'\x15':250,
'\x16':250,'\x17':250,'\x18':250,'\x19':250,'\x1a':250,'\x1b':250,'\x1c':250,'\x1d':250,'\x1e':250,'\x1f':250,' ':250,'!':389,'"':555,'#':500,'$':500,'%':833,'&':778,'\'':278,'(':333,')':333,'*':500,'+':570,
@@ -154,3 +154,4 @@ fpdf_charwidths['zapfdingbats']={
'\xdc':927,'\xdd':928,'\xde':928,'\xdf':834,'\xe0':873,'\xe1':828,'\xe2':924,'\xe3':924,'\xe4':917,'\xe5':930,'\xe6':931,'\xe7':463,'\xe8':883,'\xe9':836,'\xea':836,'\xeb':867,'\xec':867,'\xed':696,'\xee':696,'\xef':874,'\xf0':0,'\xf1':874,
'\xf2':760,'\xf3':946,'\xf4':771,'\xf5':865,'\xf6':771,'\xf7':888,'\xf8':967,'\xf9':888,'\xfa':831,'\xfb':873,'\xfc':927,'\xfd':970,'\xfe':918,'\xff':0}
+
diff --git a/gluon/contrib/fpdf/fpdf.py b/gluon/contrib/fpdf/fpdf.py
index dd8d2a7f..4f9a3eb5 100644
--- a/gluon/contrib/fpdf/fpdf.py
+++ b/gluon/contrib/fpdf/fpdf.py
@@ -48,7 +48,7 @@ def set_global(var, val):
class FPDF(object):
"PDF Generation class"
-
+
def __init__(self, orientation='P',unit='mm',format='A4'):
# Some checks
self._dochecks()
@@ -352,7 +352,7 @@ class FPDF(object):
elif (self.current_font['desc']['MissingWidth']) :
w += self.current_font['desc']['MissingWidth']
#elif (isset($this->CurrentFont['MissingWidth'])) { $w += $this->CurrentFont['MissingWidth']; }
- else:
+ else:
w += 500
else:
for i in xrange(0, l):
@@ -397,10 +397,10 @@ class FPDF(object):
global SYSTEM_TTFONTS
if os.path.exists(fname):
ttffilename = fname
- elif (FPDF_FONT_DIR and
+ elif (FPDF_FONT_DIR and
os.path.exists(os.path.join(FPDF_FONT_DIR, fname))):
ttffilename = os.path.join(FPDF_FONT_DIR, fname)
- elif (SYSTEM_TTFONTS and
+ elif (SYSTEM_TTFONTS and
os.path.exists(os.path.join(SYSTEM_TTFONTS, fname))):
ttffilename = os.path.join(SYSTEM_TTFONTS, fname)
else:
@@ -455,11 +455,11 @@ class FPDF(object):
else:
sbarr = range(0,32)
self.fonts[fontkey] = {
- 'i': len(self.fonts)+1, 'type': font_dict['type'],
- 'name': font_dict['name'], 'desc': font_dict['desc'],
- 'up': font_dict['up'], 'ut': font_dict['ut'],
- 'cw': font_dict['cw'],
- 'ttffile': font_dict['ttffile'], 'fontkey': fontkey,
+ 'i': len(self.fonts)+1, 'type': font_dict['type'],
+ 'name': font_dict['name'], 'desc': font_dict['desc'],
+ 'up': font_dict['up'], 'ut': font_dict['ut'],
+ 'cw': font_dict['cw'],
+ 'ttffile': font_dict['ttffile'], 'fontkey': fontkey,
'subset': sbarr, 'unifilename': unifilename,
}
self.font_files[fontkey] = {'length1': font_dict['originalsize'],
@@ -490,7 +490,7 @@ class FPDF(object):
if (type == 'TrueType'):
self.font_files[filename]={'length1': originalsize}
else:
- self.font_files[filename]={'length1': size1,
+ self.font_files[filename]={'length1': size1,
'length2': size2}
def set_font(self, family,style='',size=0):
@@ -656,7 +656,7 @@ class FPDF(object):
dx=self.c_margin
if(self.color_flag):
s+='q '+self.text_color+' '
-
+
# If multibyte, Tw has no effect - do word spacing using an adjustment before each space
if (self.ws and self.unifontsubset):
for uni in UTF8StringToArray(txt):
@@ -680,9 +680,9 @@ class FPDF(object):
for uni in UTF8StringToArray(txt):
self.current_font['subset'].append(uni)
else:
- txt2 = txt.replace('\\','\\\\').replace(')','\\)').replace('(','\\(')
+ txt2 = txt.replace('\\','\\\\').replace(')','\\)').replace('(','\\(')
s += sprintf('BT %.2f %.2f Td (%s) Tj ET',(self.x+dx)*k,(self.h-(self.y+.5*h+.3*self.font_size))*k,txt2)
-
+
if(self.underline):
s+=' '+self._dounderline(self.x+dx,self.y+.5*h+.3*self.font_size,txt)
if(self.color_flag):
@@ -760,7 +760,7 @@ class FPDF(object):
sep=i
ls=l
ns+=1
- if self.unifontsubset:
+ if self.unifontsubset:
l += self.get_string_width(c) / self.font_size*1000.0
else:
l += cw.get(c,0)
@@ -844,7 +844,7 @@ class FPDF(object):
continue
if(c==' '):
sep=i
- if self.unifontsubset:
+ if self.unifontsubset:
l += self.get_string_width(c) / self.font_size*1000.0
else:
l += cw.get(c,0)
@@ -1198,7 +1198,7 @@ class FPDF(object):
self._out('<>')
@@ -1260,7 +1260,7 @@ class FPDF(object):
for kd in ('Ascent', 'Descent', 'CapHeight', 'Flags', 'FontBBox', 'ItalicAngle', 'StemV', 'MissingWidth'):
v = font['desc'][kd]
if (kd == 'Flags'):
- v = v | 4;
+ v = v | 4;
v = v & ~32; # SYMBOLIC font flag
self._out(' /%s %s' % (kd, v))
self._out('/FontFile2 ' + str(self.n + 2) + ' 0 R')
@@ -1282,7 +1282,7 @@ class FPDF(object):
self._putstream(cidtogidmap)
self._out('endobj')
- #Font file
+ #Font file
self._newobj()
self._out('< 255 and (cid not in font['subset']) or not cid): #
+ if (cid > 255 and (cid not in font['subset']) or not cid): #
continue
if ('dw' not in font or (font['dw'] and width != font['dw'])):
if (cid == (prevcid + 1)):
@@ -1396,7 +1396,7 @@ class FPDF(object):
if (len(set(ws)) == 1):
w.append(' %s %s %s' % (k, k + len(ws) - 1, ws[0]))
else:
- w.append(' %s [ %s ]\n' % (k, ' '.join([str(int(h)) for h in ws]))) ##
+ w.append(' %s [ %s ]\n' % (k, ' '.join([str(int(h)) for h in ws]))) ##
self._out('/W [%s]' % ''.join(w))
def _putimages(self):
@@ -1408,7 +1408,7 @@ class FPDF(object):
del info['data']
if 'smask' in info:
del info['smask']
-
+
def _putimage(self, info):
if 'data' in info:
self._newobj()
@@ -1785,20 +1785,20 @@ class FPDF(object):
def interleaved2of5(self, txt, x, y, w=1.0, h=10.0):
"Barcode I2of5 (numeric), adds a 0 if odd lenght"
- narrow = w / 3.0
+ narrow = w / 3.0
wide = w
-
+
# wide/narrow codes for the digits
bar_char={'0': 'nnwwn', '1': 'wnnnw', '2': 'nwnnw', '3': 'wwnnn',
'4': 'nnwnw', '5': 'wnwnn', '6': 'nwwnn', '7': 'nnnww',
'8': 'wnnwn', '9': 'nwnwn', 'A': 'nn', 'Z': 'wn'}
-
+
self.set_fill_color(0)
code = txt
# add leading zero if code-length is odd
if len(code) % 2 != 0:
code = '0' + code
-
+
# add start and stop codes
code = 'AA' + code.lower() + 'ZA'
@@ -1837,7 +1837,7 @@ class FPDF(object):
narrow = w / 3.0
gap = narrow
- bar_char={'0': 'nnnwwnwnn', '1': 'wnnwnnnnw', '2': 'nnwwnnnnw',
+ bar_char={'0': 'nnnwwnwnn', '1': 'wnnwnnnnw', '2': 'nnwwnnnnw',
'3': 'wnwwnnnnn', '4': 'nnnwwnnnw', '5': 'wnnwwnnnn',
'6': 'nnwwwnnnn', '7': 'nnnwnnwnw', '8': 'wnnwnnwnn',
'9': 'nnwwnnwnn', 'A': 'wnnnnwnnw', 'B': 'nnwnnwnnw',
@@ -1854,8 +1854,8 @@ class FPDF(object):
'+': 'nwnnnwnwn', '%': 'nnnwnwnwn'}
self.set_fill_color(0)
- code = txt
-
+ code = txt
+
code = code.upper()
for i in xrange (0, len(code), 2):
char_bar = code[i]
@@ -1865,7 +1865,7 @@ class FPDF(object):
seq= ''
for s in xrange(0, len(bar_char[char_bar])):
- seq += bar_char[char_bar][s]
+ seq += bar_char[char_bar][s]
for bar in xrange(0, len(seq)):
if seq[bar] == 'n':
@@ -1879,3 +1879,4 @@ class FPDF(object):
x += gap
+
diff --git a/gluon/contrib/fpdf/html.py b/gluon/contrib/fpdf/html.py
index bc1c53b7..95b7e8b3 100644
--- a/gluon/contrib/fpdf/html.py
+++ b/gluon/contrib/fpdf/html.py
@@ -35,7 +35,7 @@ class HTML2FPDF(HTMLParser):
self.page_links = {}
self.font_list = ("times","courier", "helvetica")
self.font = None
- self.font_stack = []
+ self.font_stack = []
self.pdf = pdf
self.r = self.g = self.b = 0
self.indent = 0
@@ -54,7 +54,7 @@ class HTML2FPDF(HTMLParser):
self.thead = None
self.tfoot = None
self.theader_out = self.tfooter_out = False
-
+
def width2mm(self, length):
if length[-1]=='%':
total = self.pdf.w - self.pdf.r_margin - self.pdf.l_margin
@@ -101,13 +101,13 @@ class HTML2FPDF(HTMLParser):
self.pdf.add_page()
self.theader_out = self.tfooter_out = False
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.box_shadow(w, h, bgcolor)
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
+ # ignore anything else than td inside a table
pass
elif self.align:
if DEBUG: print "cell", txt, "*"
@@ -142,7 +142,7 @@ class HTML2FPDF(HTMLParser):
self.pdf.set_x(self.table_offset)
#self.pdf.set_x(x)
self.theader_out = True
-
+
def output_table_footer(self):
if self.tfooter:
x = self.pdf.x
@@ -156,7 +156,7 @@ class HTML2FPDF(HTMLParser):
if int(self.table.get('border', 0)):
self.output_table_sep()
self.tfooter_out = True
-
+
def output_table_sep(self):
self.pdf.set_x(self.table_offset)
x1 = self.pdf.x
@@ -335,7 +335,7 @@ class HTML2FPDF(HTMLParser):
if face:
self.pdf.set_text_color(0,0,0)
self.color = None
- self.set_font(face, size)
+ self.set_font(face, size)
self.font = None
if tag=='center':
self.align = None
@@ -352,7 +352,7 @@ class HTML2FPDF(HTMLParser):
self.set_style('u', False)
self.set_style('b', False)
self.set_style('i', False)
- self.set_text_color()
+ self.set_text_color()
def set_style(self, tag=None, enable=None):
#Modify style and select corresponding font
@@ -374,7 +374,7 @@ class HTML2FPDF(HTMLParser):
self.r = r
self.g = g
self.b = b
-
+
def put_link(self, url, txt):
#Put a hyperlink
self.set_text_color(0,0,255)
@@ -394,3 +394,4 @@ class HTMLMixin(object):
h2p = HTML2FPDF(self)
h2p.feed(text)
+
diff --git a/gluon/contrib/fpdf/php.py b/gluon/contrib/fpdf/php.py
index 80a4622d..569bf9e0 100644
--- a/gluon/contrib/fpdf/php.py
+++ b/gluon/contrib/fpdf/php.py
@@ -15,12 +15,12 @@ def print_r(array):
array = dict([(k, k) for k in array])
for k, v in array.items():
print "[%s] => %s" % (k, v),
-
+
def UTF8ToUTF16BE(instr, setbom=True):
"Converts UTF-8 strings to UTF16-BE."
outstr = ""
if (setbom):
- outstr += "\xFE\xFF";
+ outstr += "\xFE\xFF";
if not isinstance(instr, unicode):
instr = instr.decode('UTF-8')
outstr += instr.encode('UTF-16BE')
@@ -30,14 +30,14 @@ def UTF8StringToArray(instr):
"Converts UTF-8 strings to codepoints array"
return [ord(c) for c in instr]
-# ttfints php helpers:
+# ttfints php helpers:
def die(msg):
raise RuntimeError(msg)
-
+
def str_repeat(s, count):
return s * count
-
+
def str_pad(s, pad_length=0, pad_char= " ", pad_type= +1 ):
if pad_type<0: # pad left
return s.rjust(pad_length, pad_char)
@@ -46,4 +46,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)
\ No newline at end of file
+strlen = count = lambda s: len(s)
diff --git a/gluon/contrib/fpdf/template.py b/gluon/contrib/fpdf/template.py
index 2d38c9f9..651a1b83 100644
--- a/gluon/contrib/fpdf/template.py
+++ b/gluon/contrib/fpdf/template.py
@@ -18,7 +18,7 @@ class Template:
if elements:
self.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, }
self.pg_no = 0
self.texts = {}
@@ -38,7 +38,7 @@ class Template:
for row in csv.reader(open(infile, 'rb'), delimiter=delimiter):
kargs = {}
for i,v in enumerate(row):
- if not v.startswith("'") and decimal_sep!=".":
+ if not v.startswith("'") and decimal_sep!=".":
v = v.replace(decimal_sep,".")
else:
v = v
@@ -53,7 +53,7 @@ class Template:
def add_page(self):
self.pg_no += 1
self.texts[self.pg_no] = {}
-
+
def __setitem__(self, name, value):
if self.has_key(name):
if isinstance(value,unicode):
@@ -69,7 +69,7 @@ class Template:
def has_key(self, name):
return name.lower() in self.keys
-
+
def __getitem__(self, name):
if self.has_key(name):
key = name.lower()
@@ -101,7 +101,7 @@ class Template:
return pdf.multi_cell(w=element['x2']-element['x1'],
h=element['y2']-element['y1'],
txt=text,align=align,split_only=True)
-
+
def render(self, outfile, dest="F"):
pdf = self.pdf
for pg in range(1, self.pg_no+1):
@@ -118,11 +118,11 @@ class Template:
self.handlers[element['type'].upper()](pdf, **element)
if 'rotate' in element:
pdf.rotate(0)
-
+
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="",
+
+ def text(self, pdf, x1=0, y1=0, x2=0, y2=0, text='', font="arial", size=10,
+ bold=False, italic=False, underline=False, align="",
foreground=0, backgroud=65535, multiline=None,
*args, **kwargs):
if text:
@@ -201,7 +201,7 @@ if __name__ == "__main__":
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):
@@ -210,7 +210,7 @@ if __name__ == "__main__":
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,
+ qty=qty, price=price,
amount=qty*price,
ds="%s: %s" % (i,ds)))
@@ -253,7 +253,7 @@ if __name__ == "__main__":
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_header2"] = "http://www.example.com"
f["company_footer1"] = "Tax Code ..."
f["company_footer2"] = "Tax/VAT ID ..."
f['number'] = '0001-00001234'
@@ -261,9 +261,9 @@ if __name__ == "__main__":
f['due_date'] = '2099-09-10'
f['customer_name'] = "Sample Client"
f['customer_address'] = "Siempreviva 1234"
-
+
# print line item...
- li = 0
+ li = 0
k = 0
total = Decimal("0.00")
for it in li_items:
@@ -293,9 +293,10 @@ if __name__ == "__main__":
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")
+
diff --git a/gluon/contrib/fpdf/ttfonts.py b/gluon/contrib/fpdf/ttfonts.py
index cdb280a3..ddab0798 100644
--- a/gluon/contrib/fpdf/ttfonts.py
+++ b/gluon/contrib/fpdf/ttfonts.py
@@ -1,19 +1,19 @@
#******************************************************************************
-# TTFontFile class
-#
-# This class is based on The ReportLab Open Source PDF library
-# written in Python - http://www.reportlab.com/software/opensource/
-# together with ideas from the OpenOffice source code and others.
-#
-# Version: 1.04
-# Date: 2011-09-18
-# Author: Ian Back
-# License: LGPL
-# Copyright (c) Ian Back, 2010
-# Ported to Python 2.7 by Mariano Reingart (reingart@gmail.com) on 2012
-# This header must be retained in any redistribution or
-# modification of the file.
-#
+# TTFontFile class
+#
+# This class is based on The ReportLab Open Source PDF library
+# written in Python - http://www.reportlab.com/software/opensource/
+# together with ideas from the OpenOffice source code and others.
+#
+# Version: 1.04
+# Date: 2011-09-18
+# Author: Ian Back
+# License: LGPL
+# Copyright (c) Ian Back, 2010
+# Ported to Python 2.7 by Mariano Reingart (reingart@gmail.com) on 2012
+# This header must be retained in any redistribution or
+# modification of the file.
+#
#******************************************************************************
from struct import pack, unpack, unpack_from
@@ -43,22 +43,22 @@ def sub32(x, y):
xhi = x[0]
ylo = y[1]
yhi = y[0]
- if (ylo > xlo):
- xlo += 1 << 16
- yhi += 1
+ if (ylo > xlo):
+ xlo += 1 << 16
+ yhi += 1
reslo = xlo-ylo
- if (yhi > xhi):
- xhi += 1 << 16
+ if (yhi > xhi):
+ xhi += 1 << 16
reshi = xhi-yhi
reshi = reshi & 0xFFFF
return (reshi, reslo)
-def calcChecksum(data):
+def calcChecksum(data):
if (strlen(data) % 4):
data += str_repeat("\0", (4-(len(data) % 4)))
hi=0x0000
lo=0x0000
- for i in range(0, len(data), 4):
+ for i in range(0, len(data), 4):
hi += (ord(data[i])<<8) + ord(data[i+1])
lo += (ord(data[i+2])<<8) + ord(data[i+3])
hi += lo >> 16
@@ -94,34 +94,34 @@ class TTFontFile:
self.readTableDirectory()
self.extractInfo()
self.fh.close()
-
+
def readTableDirectory(self, ):
self.numTables = self.read_ushort()
self.searchRange = self.read_ushort()
self.entrySelector = self.read_ushort()
self.rangeShift = self.read_ushort()
- self.tables = {}
+ self.tables = {}
for i in range(self.numTables):
record = {}
record['tag'] = self.read_tag()
record['checksum'] = (self.read_ushort(),self.read_ushort())
record['offset'] = self.read_ulong()
record['length'] = self.read_ulong()
- self.tables[record['tag']] = record
+ self.tables[record['tag']] = record
def get_table_pos(self, tag):
offset = self.tables[tag]['offset']
length = self.tables[tag]['length']
return (offset, length)
-
- def seek(self, pos):
+
+ def seek(self, pos):
self._pos = pos
self.fh.seek(self._pos)
-
- def skip(self, delta):
+
+ def skip(self, delta):
self._pos = self._pos + delta
self.fh.seek(self._pos)
-
+
def seek_table(self, tag, offset_in_table = 0):
tpos = self.get_table_pos(tag)
self._pos = tpos[0] + offset_in_table
@@ -132,32 +132,32 @@ class TTFontFile:
self._pos += 4
return self.fh.read(4)
- def read_short(self):
+ def read_short(self):
self._pos += 2
s = self.fh.read(2)
a = (ord(s[0])<<8) + ord(s[1])
if (a & (1 << 15) ):
- a = (a - (1 << 16))
+ a = (a - (1 << 16))
return a
-
+
def unpack_short(self, s):
a = (ord(s[0])<<8) + ord(s[1])
if (a & (1 << 15) ):
- a = (a - (1 << 16))
+ a = (a - (1 << 16))
return a
-
+
def read_ushort(self):
self._pos += 2
s = self.fh.read(2)
return (ord(s[0])<<8) + ord(s[1])
- def read_ulong(self):
+ def read_ulong(self):
self._pos += 4
s = self.fh.read(4)
# if large uInt32 as an integer, PHP converts it to -ve
return (ord(s[0])*16777216) + (ord(s[1])<<16) + (ord(s[2])<<8) + ord(s[3]) # 16777216 = 1<<24
- def get_ushort(self, pos):
+ def get_ushort(self, pos):
self.fh.seek(pos)
s = self.fh.read(2)
return (ord(s[0])<<8) + ord(s[1])
@@ -166,45 +166,45 @@ class TTFontFile:
self.fh.seek(pos)
s = self.fh.read(4)
# iF large uInt32 as an integer, PHP converts it to -ve
- return (ord(s[0])*16777216) + (ord(s[1])<<16) + (ord(s[2])<<8) + ord(s[3]) # 16777216 = 1<<24
+ return (ord(s[0])*16777216) + (ord(s[1])<<16) + (ord(s[2])<<8) + ord(s[3]) # 16777216 = 1<<24
def pack_short(self, val):
if (val<0):
val = abs(val)
val = ~val
val += 1
- return pack(">H",val)
-
+ return pack(">H",val)
+
def splice(self, stream, offset, value):
return substr(stream,0,offset) + value + substr(stream,offset+strlen(value))
-
+
def _set_ushort(self, stream, offset, value):
up = pack(">H", value)
- return self.splice(stream, offset, up)
+ return self.splice(stream, offset, up)
def _set_short(self, stream, offset, val):
if (val<0):
val = abs(val)
val = ~val
val += 1
- up = pack(">H",val)
+ up = pack(">H",val)
return self.splice(stream, offset, up)
- def get_chunk(self, pos, length):
+ def get_chunk(self, pos, length):
self.fh.seek(pos)
- if (length <1): return ''
+ if (length <1): return ''
return (self.fh.read(length))
def get_table(self, tag):
(pos, length) = self.get_table_pos(tag)
if (length == 0):
- die('Truetype font (' + self.filename + '): error reading table: ' + tag)
+ die('Truetype font (' + self.filename + '): error reading table: ' + tag)
self.fh.seek(pos)
return (self.fh.read(length))
def add(self, tag, data):
if (tag == 'head') :
- data = self.splice(data, 8, "\0\0\0\0")
+ data = self.splice(data, 8, "\0\0\0\0")
self.otables[tag] = data
############################################/
@@ -212,7 +212,7 @@ class TTFontFile:
############################################/
- def extractInfo(self):
+ def extractInfo(self):
#################/
# name - Naming table
#################/
@@ -228,7 +228,7 @@ class TTFontFile:
names = {1:'',2:'',3:'',4:'',6:''}
K = names.keys()
nameCount = len(names)
- for i in range(numRecords):
+ for i in range(numRecords):
platformId = self.read_ushort()
encodingId = self.read_ushort()
languageId = self.read_ushort()
@@ -250,19 +250,19 @@ class TTFontFile:
length -= 1
self._pos = opos
self.seek(opos)
-
+
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)
self._pos = opos
self.seek(opos)
-
+
if (N and names[nameId]==''):
names[nameId] = N
nameCount -= 1
if (nameCount==0): break
-
-
+
+
if (names[6]):
psName = names[6]
elif (names[4]):
@@ -275,29 +275,29 @@ class TTFontFile:
die("Could not find PostScript font name")
self.name = psName
if (names[1]):
- self.familyName = names[1]
- else:
- self.familyName = psName
+ self.familyName = names[1]
+ else:
+ self.familyName = psName
if (names[2]):
self.styleName = names[2]
else:
- self.styleName = 'Regular'
+ self.styleName = 'Regular'
if (names[4]):
self.fullName = names[4]
else:
- self.fullName = psName
+ self.fullName = psName
if (names[3]):
self.uniqueFontID = names[3]
else:
- self.uniqueFontID = psName
+ self.uniqueFontID = psName
if (names[6]):
- self.fullName = names[6]
+ self.fullName = names[6]
#################/
# head - Font header table
#################/
self.seek_table("head")
- self.skip(18)
+ self.skip(18)
self.unitsPerEm = unitsPerEm = self.read_ushort()
scale = 1000 / float(unitsPerEm)
self.skip(16)
@@ -323,22 +323,22 @@ class TTFontFile:
hheaDescender = self.read_short()
self.ascent = (hheaAscender *scale)
self.descent = (hheaDescender *scale)
-
+
#################/
# OS/2 - OS/2 and Windows metrics table
#################/
- if ("OS/2" in self.tables):
+ if ("OS/2" in self.tables):
self.seek_table("OS/2")
version = self.read_ushort()
self.skip(2)
usWeightClass = self.read_ushort()
self.skip(2)
fsType = self.read_ushort()
- if (fsType == 0x0002 or (fsType & 0x0300) != 0):
+ if (fsType == 0x0002 or (fsType & 0x0300) != 0):
die('ERROR - Font file ' + self.filename + ' cannot be embedded due to copyright restrictions.')
self.restrictedUse = True
-
+
self.skip(20)
sF = self.read_short()
self.sFamilyClass = (sF >> 8)
@@ -348,30 +348,30 @@ class TTFontFile:
self.skip(26)
sTypoAscender = self.read_short()
sTypoDescender = self.read_short()
- if (not self.ascent):
+ if (not self.ascent):
self.ascent = (sTypoAscender*scale)
- if (not self.descent):
+ if (not self.descent):
self.descent = (sTypoDescender*scale)
if (version > 1):
self.skip(16)
sCapHeight = self.read_short()
self.capHeight = (sCapHeight*scale)
else:
- self.capHeight = self.ascent
-
+ self.capHeight = self.ascent
+
else:
usWeightClass = 500
if (not self.ascent): self.ascent = (yMax*scale)
if (not self.descent): self.descent = (yMin*scale)
self.capHeight = self.ascent
-
+
self.stemV = 50 + int(pow((usWeightClass / 65.0),2))
#################/
# post - PostScript table
#################/
self.seek_table("post")
- self.skip(4)
+ self.skip(4)
self.italicAngle = self.read_short() + self.read_ushort() / 65536.0
self.underlinePosition = self.read_short() * scale
self.underlineThickness = self.read_short() * scale
@@ -390,7 +390,7 @@ class TTFontFile:
# hhea - Horizontal header table
#################/
self.seek_table("hhea")
- self.skip(32)
+ self.skip(32)
metricDataFormat = self.read_ushort()
if (metricDataFormat != 0):
die('Unknown horizontal metric data format '.metricDataFormat)
@@ -402,7 +402,7 @@ class TTFontFile:
# maxp - Maximum profile table
#################/
self.seek_table("maxp")
- self.skip(4)
+ self.skip(4)
numGlyphs = self.read_ushort()
#################/
@@ -424,7 +424,7 @@ class TTFontFile:
unicode_cmap_offset = cmap_offset + offset
break
self.seek(save_pos )
-
+
if (not unicode_cmap_offset):
die('Font (' + self.filename + ') does not have cmap for Unicode (platform 3, encoding 1, format 4, or platform 0, any encoding, format 4)')
@@ -460,7 +460,7 @@ class TTFontFile:
# head - Font header table
#################/
self.seek_table("head")
- self.skip(50)
+ self.skip(50)
indexToLocFormat = self.read_ushort()
glyphDataFormat = self.read_ushort()
@@ -468,7 +468,7 @@ class TTFontFile:
# hhea - Horizontal header table
#################/
self.seek_table("hhea")
- self.skip(32)
+ self.skip(32)
metricDataFormat = self.read_ushort()
orignHmetrics = numberOfHMetrics = self.read_ushort()
@@ -496,9 +496,9 @@ class TTFontFile:
if (format == 4):
unicode_cmap_offset = cmap_offset + offset
break
-
+
self.seek(save_pos )
-
+
if (not unicode_cmap_offset):
die('Font (' + self.filename + ') does not have cmap for Unicode (platform 3, encoding 1, format 4, or platform 0, any encoding, format 4)')
@@ -521,7 +521,7 @@ class TTFontFile:
subsetglyphs = [(0, 0)] # special "sorted dict"!
subsetCharToGlyph = {}
- for code in subset:
+ 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
@@ -540,12 +540,12 @@ class TTFontFile:
codeToGlyph = {}
for uni, originalGlyphIdx in sorted(subsetCharToGlyph.items()):
- codeToGlyph[uni] = glyphSet[originalGlyphIdx]
-
+ codeToGlyph[uni] = glyphSet[originalGlyphIdx]
+
self.codeToGlyph = codeToGlyph
-
- for originalGlyphIdx, uni in subsetglyphs:
- nonlocals = {'start': start, 'glyphSet': glyphSet,
+
+ for originalGlyphIdx, uni in subsetglyphs:
+ nonlocals = {'start': start, 'glyphSet': glyphSet,
'subsetglyphs': subsetglyphs}
self.getGlyphs(originalGlyphIdx, nonlocals)
@@ -553,12 +553,12 @@ class TTFontFile:
#tables copied from the original
tags = ['name']
- for tag in tags:
- self.add(tag, self.get_table(tag))
+ 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))
+ if (tag in self.tables):
+ self.add(tag, self.get_table(tag))
# post - PostScript
opost = self.get_table('post')
@@ -592,7 +592,7 @@ class TTFontFile:
while (searchRange * 2 <= segCount ):
searchRange = searchRange * 2
entrySelector = entrySelector + 1
-
+
searchRange = searchRange * 2
rangeShift = segCount * 2 - searchRange
length = 16 + (8*segCount ) + (numGlyphs+1)
@@ -606,46 +606,46 @@ class TTFontFile:
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
# startCode(s)
- for start, subrange in range_:
+ 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(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_:
+ # 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_:
+ 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)
+ cmapstr += pack(">H", cm)
else:
try:
- cmapstr += pack(">h", cm)
+ cmapstr += pack(">h", cm)
except:
warnings.warn("cmap value too big/small: %s" % cm)
- cmapstr += pack(">H", -cm)
+ cmapstr += pack(">H", -cm)
self.add('cmap', cmapstr)
# glyf - Glyph data
@@ -674,9 +674,9 @@ class TTFontFile:
maxComponentDepth = 0 # levels of recursion, set to 0 if font has only simple glyphs
self.glyphdata = {}
- for originalGlyphIdx, uni in subsetglyphs:
+ for originalGlyphIdx, uni in subsetglyphs:
# hmtx - Horizontal Metrics
- hm = self.getHMetric(orignHmetrics, originalGlyphIdx)
+ hm = self.getHMetric(orignHmetrics, originalGlyphIdx)
hmtxstr += hm
offsets.append(pos)
@@ -694,7 +694,7 @@ class TTFontFile:
data = self.get_chunk(glyfOffset+glyphPos,glyphLen)
else:
data = ''
-
+
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
@@ -714,22 +714,22 @@ class TTFontFile:
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_WORDS):
+ pos_in_glyph += 4
+ else:
+ pos_in_glyph += 2
if (flags & GF_SCALE):
- pos_in_glyph += 2
+ pos_in_glyph += 2
elif (flags & GF_XYSCALE):
- pos_in_glyph += 4
+ pos_in_glyph += 4
elif (flags & GF_TWOBYTWO):
- pos_in_glyph += 8
-
+ pos_in_glyph += 8
+
maxComponentElements = max(maxComponentElements, nComponentElements)
-
+
glyf += data
pos += glyphLen
- if (pos % 4 != 0):
+ if (pos % 4 != 0):
padding = 4 - (pos % 4)
glyf += str_repeat("\0",padding)
pos += padding
@@ -742,15 +742,15 @@ class TTFontFile:
# loca - Index to location
locastr = ''
- if (((pos + 1) >> 1) > 0xFFFF):
+ if (((pos + 1) >> 1) > 0xFFFF):
indexToLocFormat = 1 # long format
for offset in offsets:
- locastr += pack(">L",offset)
+ locastr += pack(">L",offset)
else:
indexToLocFormat = 0 # short format
- for offset in offsets:
- locastr += pack(">H",(offset/2))
-
+ for offset in offsets:
+ locastr += pack(">H",(offset/2))
+
self.add('loca', locastr)
# head - Font header
@@ -776,8 +776,8 @@ class TTFontFile:
# Put the TTF file together
stm = self.endTTFile('')
- return stm
-
+ return stm
+
#########################################
# Recursively get composite glyph data
@@ -786,21 +786,21 @@ class TTFontFile:
nonlocals['depth'] += 1
nonlocals['maxdepth'] = max(nonlocals['maxdepth'], nonlocals['depth'])
if (len(self.glyphdata[originalGlyphIdx]['compGlyphs'])):
- for glyphIdx in self.glyphdata[originalGlyphIdx]['compGlyphs']:
- self.getGlyphData(glyphIdx, nonlocals)
-
+ for glyphIdx in self.glyphdata[originalGlyphIdx]['compGlyphs']:
+ self.getGlyphData(glyphIdx, nonlocals)
+
elif ((self.glyphdata[originalGlyphIdx]['nContours'] > 0) and nonlocals['depth'] > 0): # simple
contours += self.glyphdata[originalGlyphIdx]['nContours']
points += self.glyphdata[originalGlyphIdx]['nPoints']
-
+
nonlocals['depth'] -= 1
#########################################
# Recursively get composite glyphs
def getGlyphs(self, originalGlyphIdx, nonlocals):
- # &start, &glyphSet, &subsetglyphs)
-
+ # &start, &glyphSet, &subsetglyphs)
+
try:
glyphPos = self.glyphPos[originalGlyphIdx]
glyphLen = self.glyphPos[originalGlyphIdx + 1] - glyphPos
@@ -808,21 +808,21 @@ class TTFontFile:
warnings.warn("missing glyph %s" % (originalGlyphIdx))
return
- if (not glyphLen):
+ if (not glyphLen):
return
-
+
self.seek(nonlocals['start'] + glyphPos)
numberOfContours = self.read_short()
if (numberOfContours < 0):
self.skip(8)
flags = GF_MORE
- while (flags & GF_MORE):
+ while (flags & GF_MORE):
flags = self.read_ushort()
glyphIdx = self.read_ushort()
if (glyphIdx not in nonlocals['glyphSet']):
nonlocals['glyphSet'][glyphIdx] = len(nonlocals['subsetglyphs']) # old glyphID to new glyphID
nonlocals['subsetglyphs'].append((glyphIdx, 1))
-
+
savepos = self.fh.tell()
self.getGlyphs(glyphIdx, nonlocals)
self.seek(savepos)
@@ -844,56 +844,56 @@ class TTFontFile:
aw = 0
self.charWidths = [0] * 256*256*2
nCharWidths = 0
- if ((numberOfHMetrics*4) < self.maxStrLenRead):
+ if ((numberOfHMetrics*4) < self.maxStrLenRead):
data = self.get_chunk(start,(numberOfHMetrics*4))
arr = unpack(">" + "H" * (len(data)/2), data)
else:
- self.seek(start)
- for glyph in range(numberOfHMetrics):
+ self.seek(start)
+ for glyph in range(numberOfHMetrics):
if ((numberOfHMetrics*4) < self.maxStrLenRead):
aw = arr[(glyph*2)] # PHP starts arrays from index 0!? +1
else:
aw = self.read_ushort()
lsb = self.read_ushort()
-
+
if (glyph in glyphToChar or glyph == 0):
if (aw >= (1 << 15) ):
aw = 0 # 1.03 Some (arabic) fonts have -ve values for width
# although should be unsigned value - comes out as e.g. 65108 (intended -50)
- if (glyph == 0):
+ if (glyph == 0):
self.defaultWidth = scale*aw
continue
-
- for char in glyphToChar[glyph]:
- if (char != 0 and char != 65535):
+
+ for char in glyphToChar[glyph]:
+ if (char != 0 and char != 65535):
w = int(round(scale*aw))
- if (w == 0): w = 65535
- if (char < 196608):
- self.charWidths[char] = w
+ if (w == 0): w = 65535
+ if (char < 196608):
+ self.charWidths[char] = w
nCharWidths += 1
-
-
+
+
data = self.get_chunk((start+numberOfHMetrics*4),(numGlyphs*2))
arr = unpack(">" + "H" * (len(data)/2), data)
diff = numGlyphs-numberOfHMetrics
- for pos in range(diff):
+ for pos in range(diff):
glyph = pos + numberOfHMetrics
- if (glyph in glyphToChar):
- for char in glyphToChar[glyph]:
- if (char != 0 and char != 65535):
+ if (glyph in glyphToChar):
+ for char in glyphToChar[glyph]:
+ if (char != 0 and char != 65535):
w = int(round(scale*aw))
- if (w == 0): w = 65535
+ if (w == 0): w = 65535
if (char < 196608):
self.charWidths[char] = w
- nCharWidths += 1
-
-
+ nCharWidths += 1
+
+
# NB 65535 is a set width of 0
# First bytes define number of chars in font
- self.charWidths[0] = nCharWidths
-
+ self.charWidths[0] = nCharWidths
- def getHMetric(self, numberOfHMetrics, gid):
+
+ def getHMetric(self, numberOfHMetrics, gid):
start = self.seek_table("hmtx")
if (gid < numberOfHMetrics):
self.seek(start+(gid*4))
@@ -904,15 +904,15 @@ class TTFontFile:
self.seek(start+(numberOfHMetrics*2)+(gid*2))
hm += self.fh.read(2)
return hm
-
- def getLOCA(self, indexToLocFormat, numGlyphs):
+
+ def getLOCA(self, indexToLocFormat, numGlyphs):
start = self.seek_table('loca')
self.glyphPos = []
if (indexToLocFormat == 0):
data = self.get_chunk(start,(numGlyphs*2)+2)
arr = unpack(">" + "H" * (len(data)/2), data)
- for n in range(numGlyphs):
+ for n in range(numGlyphs):
self.glyphPos.append((arr[n] * 2)) # n+1 !?
elif (indexToLocFormat == 1):
data = self.get_chunk(start,(numGlyphs*4)+4)
@@ -938,18 +938,18 @@ class TTFontFile:
self.skip(2)
startCount = []
for i in range(segCount):
- startCount.append(self.read_ushort())
+ startCount.append(self.read_ushort())
idDelta = []
for i in range(segCount):
idDelta.append(self.read_short()) # ???? was unsigned short
idRangeOffset_start = self._pos
idRangeOffset = []
for i in range(segCount):
- idRangeOffset.append(self.read_ushort())
+ idRangeOffset.append(self.read_ushort())
- for n in range(segCount):
+ for n in range(segCount):
endpoint = (endCount[n] + 1)
- for unichar in range(startCount[n], endpoint, 1):
+ for unichar in range(startCount[n], endpoint, 1):
if (idRangeOffset[n] == 0):
glyph = (unichar + idDelta[n]) & 0xFFFF
else:
@@ -961,33 +961,33 @@ class TTFontFile:
glyph = self.get_ushort(offset)
if (glyph != 0):
glyph = (glyph + idDelta[n]) & 0xFFFF
-
+
charToGlyph[unichar] = glyph
if (unichar < 196608):
- self.maxUniChar = max(unichar,self.maxUniChar)
+ self.maxUniChar = max(unichar,self.maxUniChar)
glyphToChar.setdefault(glyph, []).append(unichar)
# Put the TTF file together
- def endTTFile(self, stm):
+ def endTTFile(self, stm):
stm = ''
numTables = count(self.otables)
searchRange = 1
entrySelector = 0
- while (searchRange * 2 <= numTables):
+ while (searchRange * 2 <= numTables):
searchRange = searchRange * 2
entrySelector = entrySelector + 1
-
+
searchRange = searchRange * 16
rangeShift = numTables * 16 - searchRange
# Header
- if (_TTF_MAC_HEADER):
+ if (_TTF_MAC_HEADER):
stm += (pack(">LHHHH", 0x74727565, numTables, searchRange, entrySelector, rangeShift)) # Mac
else:
stm += (pack(">LHHHH", 0x00010000 , numTables, searchRange, entrySelector, rangeShift)) # Windows
-
+
# Table directory
tables = self.otables
@@ -995,7 +995,7 @@ class TTFontFile:
sorted_tables = sorted(tables.items())
for tag, data in sorted_tables:
if (tag == 'head'):
- head_start = offset
+ head_start = offset
stm += tag
checksum = calcChecksum(data)
stm += pack(">HH", checksum[0],checksum[1])
@@ -1004,7 +1004,7 @@ class TTFontFile:
offset = offset + paddedLength
# Table data
- for tag, data in sorted_tables:
+ for tag, data in sorted_tables:
data += "\0\0\0"
stm += substr(data,0,(strlen(data)&~3))
@@ -1012,8 +1012,8 @@ class TTFontFile:
checksum = sub32((0xB1B0,0xAFBA), checksum)
chk = pack(">HH", checksum[0],checksum[1])
stm = self.splice(stm,(head_start + 8),chk)
- return stm
-
+ return stm
+
if __name__ == '__main__':
ttf = TTFontFile()
ttffile = 'DejaVuSansCondensed.ttf';
@@ -1030,4 +1030,4 @@ if __name__ == '__main__':
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()
-
\ No newline at end of file
+
From aad7aac1c7185de30cc3a0067519798f353342a8 Mon Sep 17 00:00:00 2001
From: mdipierro
Date: Thu, 23 Aug 2012 08:37:55 -0500
Subject: [PATCH 04/53] fixed changelog typos, thanks Niphlod
---
CHANGELOG | 40 +++++++++++++++++++++++++++-------------
VERSION | 2 +-
2 files changed, 28 insertions(+), 14 deletions(-)
diff --git a/CHANGELOG b/CHANGELOG
index b28b148d..1e93192a 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -12,15 +12,15 @@
- db(...).select(db.mytable.myfield.count(distinct=True))
- db(db.a)._update(name=db(db.b.a==db.a.id).nested_select(db.b.id))
- db.mytable.myfield.filter_in, filter_out
-- db.mytable._enable_record_versioning(db) adds verining to this table
+- db.mytable._enable_record_versioning(db) adds versioning to this table
- teradata adapter, thanks Andrew Willimott
- experimental Sybase Adapter
- added db.table.field.avg()
-- Suport for Google App Engine projections
+- Support for Google App Engine projections
### Auth improvements
-- auth.enable_record_versioning(db) adds full versining to all tables
+- auth.enable_record_versioning(db) adds full versioning to all tables
- @auth.requires_login(otherwise=URL(...))
- auth supports salt and compatible with third party data, thanks Dave Stoll
- CRYPT now defaults to pbkdf2(1000,20,sha1)
@@ -33,14 +33,14 @@
- FORM.confirm('Are you sure?',{'Back':URL(...)})
- SQLFORM.smartdictform(dict)
- form.add_button(value,link)
-- SQLFORM.grid(groupby=...')
+- SQLFORM.grid(groupby='...')
- fixed security issue with SQLFORM.grid and SQLFORM.smartgrid
- more export options in SQLFORM.grid and SQLFORM.smartgrid (html, xml, csv, ...)
### Admin improvements
- new admin pages: manage_students, bulk_regsiter, and progress reports
-- increased secure admin against CSRF
+- increased security in admin against CSRF
- experimental Git integration
- experimental OpenShift deployment
- multi-language pluralization engine
@@ -49,14 +49,28 @@
- Romanian translation for welcome, thanks ionel
- support for mercurial 2.6, thanks Vlad
-### Scheduler Inprovements
+### Scheduler Improvements (thanks to niphlod, ykessler, dhx, toomim)
- web2py.py -K myapp -X starts the myapp scheduler alongside the webserver
+- tasks are marked EXPIRED (if stop_time passed)
+- functions with no result don't end up in scheduler_run
- more options: web2py.py -E -b -L
-- scheduler can now handle 10k tasks with 20 concurrent workers and no known issues (thanks to niphlod, ykessler, dhx, toomim)
-- tasks can be found in the environment (no need to define the tasks parameter)
-- max_empty_runs kills the workers automatically if no new tasks are found in queue (nice for "spikes" of processing power), discard_results to completely discard the results (if you don't need the output of the task), utc_time enables datetime calculations with UTC time, task_name is no longer required (filled automatically with function_name if found empty), uuid makes easy to coordinate scheduler_task maintenance (filled automatically if not provided), stop_time has no default (previously was today+1), retry_failed to requeue automatically failed tasks, sync_output refreshes automatically the output (nice to report percentages)
-- tasks can be DISABLED (put to sleep and do nothing if not sending the heartbeat every 30 seconds), TERMINATE (complete the current task and then die), KILL (kill ASAP), EXPIRED (if stop_time passed)
+- scheduler can now handle 10k tasks with 20 concurrent workers and with no issues
+- new params:
+ tasks can be found in the environment (no need to define the tasks parameter)
+ max_empty_runs kills the workers automatically if no new tasks are found in queue (nice for "spikes" of processing power)
+ discard_results to completely discard the results (if you don't need the output of the task)
+ utc_time enables datetime calculations with UTC time
+- scheduler_task changes:
+ task_name is no longer required (filled automatically with function_name if found empty)
+ uuid makes easy to coordinate scheduler_task maintenance (filled automatically if not provided)
+ stop_time has no default (previously was today+1)
+ retry_failed to requeue automatically failed tasks
+ sync_output refreshes automatically the output (nice to report percentages)
+- workers can be:
+ DISABLED (put to sleep and do nothing if not sending the heartbeat every 30 seconds)
+ TERMINATE (complete the current task and then die)
+ KILL (kill ASAP)
### Other Improvements
@@ -67,12 +81,12 @@
- routes in can redirect outside with routes_in=[('/path','303->http://..')]
- better memcache support
- improved spreadsheet, thanks Alan
-- new interantionalization engine, thanks Vladyslav
+- new internationalization engine, thanks Vladyslav
- pluralization engine, thanks Vladyslav
-- new makrmin with supports for nested lists, , , autolinks, thanks Vladyslav
+- new markmin with support for nested lists, , , autolinks, thanks Vladyslav
- new syntax: {{=BR()*5}}
- gluon.cache.lazy_cache decorator allows caching functions in modules
-- .coffee and .less support in response.fields, thanks Sam Sheftel
+- .coffee and .less support in response.files, thanks Sam Sheftel
- ldap certificate support
- pg8000 postgresql driver support (experimental)
- @cache('%(name)s%(args)s%(vars)s',5) and cache.autokey
diff --git a/VERSION b/VERSION
index 03475297..580b29d0 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-Version 2.00.0 (2012-08-23 08:36:47) dev
+Version 2.00.0 (2012-08-23 08:37:52) dev
From cf51c40e8603b9d1432660e0cb0a500d1a7e0ca9 Mon Sep 17 00:00:00 2001
From: mdipierro
Date: Thu, 23 Aug 2012 08:42:47 -0500
Subject: [PATCH 05/53] restoring Settings backward compatibility, thanks
Dominic
---
VERSION | 2 +-
gluon/storage.py | 8 ++++----
2 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/VERSION b/VERSION
index 580b29d0..5cdb6dba 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-Version 2.00.0 (2012-08-23 08:37:52) dev
+Version 2.00.0 (2012-08-23 08:42:44) dev
diff --git a/gluon/storage.py b/gluon/storage.py
index b1d36a8c..fa956703 100644
--- a/gluon/storage.py
+++ b/gluon/storage.py
@@ -35,7 +35,7 @@ class Storage(dict):
2
>>> del o.a
- >>> print o.a
+ >>> print o.a
None
"""
def __getattr__(self, key):
@@ -100,7 +100,7 @@ class Storage(dict):
def getlast(self,key,default=None):
"""
- Returns the last or only single value when
+ Returns the last or only single value when
given a request.vars-style key.
If the value is a list, the last item will be returned;
@@ -155,9 +155,9 @@ def save_storage(storage, filename):
class Settings(Storage):
def __setattr__(self, key, value):
- if key != 'lock_keys' and 'lock_keys' in self and not key in self:
+ if key != 'lock_keys' and self['lock_keys'] and key not in self:
raise SyntaxError, 'setting key \'%s\' does not exist' % key
- if key != 'lock_values' and 'lock_values' in self:
+ if key != 'lock_values' and self['lock_values']:
raise SyntaxError, 'setting value cannot be changed: %s' % key
self[key] = value
From 089c0b33db5c3f5cb6c05dff7576a5778f5ad0d8 Mon Sep 17 00:00:00 2001
From: mdipierro
Date: Thu, 23 Aug 2012 08:54:34 -0500
Subject: [PATCH 06/53] added DIV.add_class, DIV.remove_class, thanks Dulakian,
fixed issue 952
---
VERSION | 2 +-
gluon/html.py | 13 +++++++++++++
2 files changed, 14 insertions(+), 1 deletion(-)
diff --git a/VERSION b/VERSION
index 5cdb6dba..faceb93c 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-Version 2.00.0 (2012-08-23 08:42:44) dev
+Version 2.00.0 (2012-08-23 08:54:31) dev
diff --git a/gluon/html.py b/gluon/html.py
index 7aa16ec6..e88d8604 100644
--- a/gluon/html.py
+++ b/gluon/html.py
@@ -484,6 +484,19 @@ class XmlComponent(object):
components += [other]
return CAT(*components)
+ def add_class(self, name):
+ """ add a class to _class attribute """
+ classes = set(self['_class'].split())|set(name.split())
+ self['_class'] = ' '.join(classes) if classes else None
+ return self
+
+ def remove_class(self, name):
+ """ remove a class from _class attribute """
+ classes = set(self['_class'].split())-set(name.split())
+ self['_class'] = ' '.join(classes) if classes else None
+ return self
+
+
class XML(XmlComponent):
"""
use it to wrap a string that contains XML/HTML so that it will not be
From bd12fd4c7bb7159d1109011cea422e26da1ada33 Mon Sep 17 00:00:00 2001
From: mdipierro
Date: Thu, 23 Aug 2012 09:00:56 -0500
Subject: [PATCH 07/53] added Killer Web Development link, thanks Anthony
---
VERSION | 2 +-
.../private/content/en/default/documentation/more.markmin | 1 +
2 files changed, 2 insertions(+), 1 deletion(-)
diff --git a/VERSION b/VERSION
index faceb93c..a6884d6d 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-Version 2.00.0 (2012-08-23 08:54:31) dev
+Version 2.00.0 (2012-08-23 09:00:53) dev
diff --git a/applications/examples/private/content/en/default/documentation/more.markmin b/applications/examples/private/content/en/default/documentation/more.markmin
index 854e076d..fe803573 100644
--- a/applications/examples/private/content/en/default/documentation/more.markmin
+++ b/applications/examples/private/content/en/default/documentation/more.markmin
@@ -7,6 +7,7 @@
- [[User Voice http://web2py.uservoice.com/ popup]]
#### Learning and Demos
+- [[Killer Web Development Tutorial http://killer-web-development.com/]]
- [[Admin Demo http://www.web2py.com/demo_admin popup]] (web-based IDE)
- [[Welcome App Demo http://www.web2py.com/welcome]] (scaffolding application)
- [[Videos http://www.web2py.com/examples/default/videos/]]
From 09dc27faed2cc7678e3261196497bc98cfb46c58 Mon Sep 17 00:00:00 2001
From: mdipierro
Date: Thu, 23 Aug 2012 09:20:25 -0500
Subject: [PATCH 08/53] db.define_table('person',Field('name'),on_define=lambda
table: [table.name.set_attributes(writable=False)]), thanks Jonathan
---
VERSION | 2 +-
gluon/dal.py | 7 ++++++-
2 files changed, 7 insertions(+), 2 deletions(-)
diff --git a/VERSION b/VERSION
index a6884d6d..0b20db7d 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-Version 2.00.0 (2012-08-23 09:00:53) dev
+Version 2.00.0 (2012-08-23 09:20:22) dev
diff --git a/gluon/dal.py b/gluon/dal.py
index 8d313931..30be93cc 100644
--- a/gluon/dal.py
+++ b/gluon/dal.py
@@ -174,7 +174,7 @@ import platform
CALLABLETYPES = (types.LambdaType, types.FunctionType,
types.BuiltinFunctionType,
types.MethodType, types.BuiltinMethodType)
-TABLE_ARGS = ('migrate','primarykey','fake_migrate','format','singular','plural','trigger_name','sequence_name','common_filter','polymodel','table_class')
+TABLE_ARGS = ('migrate','primarykey','fake_migrate','format','singular','plural','trigger_name','sequence_name','common_filter','polymodel','table_class','on_define')
###################################################################################
# following checks allow the use of dal without web2py, as a standalone module
@@ -6977,6 +6977,8 @@ def index():
sql_locker.release()
else:
table._dbt = None
+ on_define = args.get('on_define',None)
+ if on_define: on_define(table)
return table
def __iter__(self):
@@ -8149,6 +8151,9 @@ class Field(Expression):
self.label = label if label!=None else fieldname.replace('_',' ').title()
self.requires = requires if requires!=None else []
+ def set_attributes(self,*args,**attributes):
+ self.__dict__.update(*args,**attributes)
+
def clone(self,point_self_references_to=False,**args):
field = copy.copy(self)
if point_self_references_to and \
From 87a7d2e4239e9e99656c021ecf33d09c624f22c5 Mon Sep 17 00:00:00 2001
From: mdipierro
Date: Thu, 23 Aug 2012 10:08:00 -0500
Subject: [PATCH 09/53] fixed problem with on_define
---
CHANGELOG | 2 +
VERSION | 2 +-
gluon/tools.py | 107 ++++++++++++++++++++++++++++++-------------------
3 files changed, 68 insertions(+), 43 deletions(-)
diff --git a/CHANGELOG b/CHANGELOG
index 1e93192a..41b5f636 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -2,6 +2,7 @@
### DAL Improvements
+- Support for DAL(lazy_tables=True) and db.define_table(on_define=lambda table:), thanks Jonathan
- MongoDB support in DAL (experimental)
- geodal and spatialite, thanks Denes and Fran (experimental)
- db.mytable._before_insert, _after_insert, _before_update, _after_update, _before_delete. _after_delete (list of callbacks)
@@ -17,6 +18,7 @@
- experimental Sybase Adapter
- added db.table.field.avg()
- Support for Google App Engine projections
+- Field(... 'upload', default=path) now accepts a path to a local file as default value, if user does not upload a file. Relative path looks inside current application folder, thanks Marin
### Auth improvements
diff --git a/VERSION b/VERSION
index 0b20db7d..4888417d 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-Version 2.00.0 (2012-08-23 09:20:22) dev
+Version 2.00.0 (2012-08-23 10:07:57) dev
diff --git a/gluon/tools.py b/gluon/tools.py
index e67d5e4c..d2c8a46c 100644
--- a/gluon/tools.py
+++ b/gluon/tools.py
@@ -1460,7 +1460,7 @@ class Auth(object):
if not settings.email_case_sensitive:
table.email.requires.insert(1,IS_LOWER())
table.registration_key.default = ''
- settings.table_user = db[settings.table_user_name]
+ reference_table_user = 'reference %s' % settings.table_user_name
if not settings.table_group_name in db.tables:
extra_fields = settings.extra_fields.get(
settings.table_group_name,[])+signature_list
@@ -1475,37 +1475,41 @@ class Auth(object):
migrate=self.__get_migrate(
settings.table_group_name, migrate),
fake_migrate=fake_migrate,
- format = '%(role)s (%(id)s)'))
- table.role.requires = IS_NOT_IN_DB(db, '%s.role'
- % settings.table_group_name)
- settings.table_group = db[settings.table_group_name]
+ format = '%(role)s (%(id)s)',
+ on_define = lambda table:
+ table.role.set_attributes(
+ requires = IS_NOT_IN_DB(
+ db, '%s.role'% settings.table_group_name))))
+ reference_table_group = 'reference %s' % settings.table_group_name
if not settings.table_membership_name in db.tables:
extra_fields = settings.extra_fields.get(
settings.table_membership_name,[])+signature_list
table = db.define_table(
settings.table_membership_name,
- Field('user_id', settings.table_user,
+ Field('user_id', reference_table_user,
label=self.messages.label_user_id),
- Field('group_id', settings.table_group,
+ Field('group_id', reference_table_group,
label=self.messages.label_group_id),
*extra_fields,
**dict(
migrate=self.__get_migrate(
settings.table_membership_name, migrate),
- fake_migrate=fake_migrate))
- table.user_id.requires = IS_IN_DB(db, '%s.id' %
- settings.table_user_name,
- settings.table_user._format)
- table.group_id.requires = IS_IN_DB(db, '%s.id' %
- settings.table_group_name,
- '%(role)s (%(id)s)')
- settings.table_membership = db[settings.table_membership_name]
+ fake_migrate=fake_migrate,
+ on_define = lambda table: (
+ table.user_id.set_attributes(
+ requires = IS_IN_DB(
+ db, '%s.id' % settings.table_user_name,
+ settings.table_user._format)).
+ table.group_id.set_attributes(
+ requires = IS_IN_DB(
+ db, '%s.id' % settings.table_group_name,
+ '%(role)s (%(id)s)')))))
if not settings.table_permission_name in db.tables:
extra_fields = settings.extra_fields.get(
settings.table_permission_name,[])+signature_list
table = db.define_table(
settings.table_permission_name,
- Field('group_id', settings.table_group,
+ Field('group_id', reference_table_group,
label=self.messages.label_group_id),
Field('name', default='default', length=512,
label=self.messages.label_name),
@@ -1517,14 +1521,17 @@ class Auth(object):
**dict(
migrate=self.__get_migrate(
settings.table_permission_name, migrate),
- fake_migrate=fake_migrate))
- table.group_id.requires = IS_IN_DB(db, '%s.id' %
- settings.table_group_name,
- '%(role)s (%(id)s)')
- table.name.requires = IS_NOT_EMPTY(error_message=self.messages.is_empty)
- #table.table_name.requires = IS_EMPTY_OR(IS_IN_SET(self.db.tables))
- table.record_id.requires = IS_INT_IN_RANGE(0, 10 ** 9)
- settings.table_permission = db[settings.table_permission_name]
+ fake_migrate=fake_migrate,
+ on_define = lambda table: (
+ table.group_id.set_attributes(
+ requires = IS_IN_DB(
+ db, '%s.id' % settings.table_group_name,
+ '%(role)s (%(id)s)')),
+ table.name.set_attributes(
+ requires = IS_NOT_EMPTY(
+ error_message=self.messages.is_empty)),
+ table.record_id.set_attributes(
+ requires = IS_INT_IN_RANGE(0, 10 ** 9)))))
if not settings.table_event_name in db.tables:
table = db.define_table(
settings.table_event_name,
@@ -1534,7 +1541,7 @@ class Auth(object):
Field('client_ip',
default=current.request.client,
label=self.messages.label_client_ip),
- Field('user_id', settings.table_user, default=None,
+ Field('user_id', reference_table_user, default=None,
label=self.messages.label_user_id),
Field('origin', default='auth', length=512,
label=self.messages.label_origin),
@@ -1544,19 +1551,24 @@ class Auth(object):
**dict(
migrate=self.__get_migrate(
settings.table_event_name, migrate),
- fake_migrate=fake_migrate))
- table.user_id.requires = IS_IN_DB(db, '%s.id' %
- settings.table_user_name,
- settings.table_user._format)
- table.origin.requires = IS_NOT_EMPTY(error_message=self.messages.is_empty)
- table.description.requires = IS_NOT_EMPTY(error_message=self.messages.is_empty)
- settings.table_event = db[settings.table_event_name]
+ fake_migrate=fake_migrate,
+ on_define = lambda table: (
+ table.user_id.set_attributes(
+ requires = IS_IN_DB(
+ db, '%s.id' % settings.table_user_name,
+ settings.table_user._format)),
+ table.origin.set_attributes(
+ requires = IS_NOT_EMPTY(
+ error_message=self.messages.is_empty)),
+ table.description.set_attributes(
+ requires = IS_NOT_EMPTY(
+ error_message=self.messages.is_empty)))))
now = current.request.now
if settings.cas_domains:
if not settings.table_cas_name in db.tables:
table = db.define_table(
settings.table_cas_name,
- Field('user_id', settings.table_user, default=None,
+ Field('user_id', reference_table_user, default=None,
label=self.messages.label_user_id),
Field('created_on','datetime',default=now),
Field('service',requires=IS_URL()),
@@ -1566,18 +1578,29 @@ class Auth(object):
**dict(
migrate=self.__get_migrate(
settings.table_cas_name, migrate),
- fake_migrate=fake_migrate))
- table.user_id.requires = IS_IN_DB(db, '%s.id' % \
- settings.table_user_name,
- settings.table_user._format)
- settings.table_cas = db[settings.table_cas_name]
- db._lazy_tables = lazy_tables
- if settings.cas_provider:
+ fake_migrate=fake_migrate,
+ on_define = lambda table: (
+ table.user_id.set_attributes(
+ requires = IS_IN_DB(
+ db, '%s.id' % settings.table_user_name,
+ settings.table_user._format)))))
+
+ if not db._lazy_tables:
+ settings.table_user = db[settings.table_user_name]
+ settings.table_group = db[settings.table_group_name]
+ settings.table_membership = db[settings.table_membership_name]
+ settings.table_permission = db[settings.table_permission_name]
+ settings.table_event = db[settings.table_event_name]
+ if settings.cas_domains:
+ settings.table_cas = db[settings.table_cas_name]
+
+ if settings.cas_provider: ### THIS IS NOT LAZY
settings.actions_disabled = \
- ['profile','register','change_password','request_reset_password']
+ ['profile','register','change_password',
+ 'request_reset_password']
from gluon.contrib.login_methods.cas_auth import CasAuth
maps = settings.cas_maps
- if not maps:
+ if not maps:
maps = dict((name,lambda v,n=name:v.get(n,None)) for name in \
settings.table_user.fields if name!='id' \
and settings.table_user[name].readable)
From f6e89cc80487113b874088688b690fa06cd29a5e Mon Sep 17 00:00:00 2001
From: mdipierro
Date: Thu, 23 Aug 2012 10:09:49 -0500
Subject: [PATCH 10/53] reverted accidental tools change
---
VERSION | 2 +-
gluon/tools.py | 107 +++++++++++++++++++------------------------------
2 files changed, 43 insertions(+), 66 deletions(-)
diff --git a/VERSION b/VERSION
index 4888417d..a6d46fcb 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-Version 2.00.0 (2012-08-23 10:07:57) dev
+Version 2.00.0 (2012-08-23 10:09:46) dev
diff --git a/gluon/tools.py b/gluon/tools.py
index d2c8a46c..e67d5e4c 100644
--- a/gluon/tools.py
+++ b/gluon/tools.py
@@ -1460,7 +1460,7 @@ class Auth(object):
if not settings.email_case_sensitive:
table.email.requires.insert(1,IS_LOWER())
table.registration_key.default = ''
- reference_table_user = 'reference %s' % settings.table_user_name
+ settings.table_user = db[settings.table_user_name]
if not settings.table_group_name in db.tables:
extra_fields = settings.extra_fields.get(
settings.table_group_name,[])+signature_list
@@ -1475,41 +1475,37 @@ class Auth(object):
migrate=self.__get_migrate(
settings.table_group_name, migrate),
fake_migrate=fake_migrate,
- format = '%(role)s (%(id)s)',
- on_define = lambda table:
- table.role.set_attributes(
- requires = IS_NOT_IN_DB(
- db, '%s.role'% settings.table_group_name))))
- reference_table_group = 'reference %s' % settings.table_group_name
+ format = '%(role)s (%(id)s)'))
+ table.role.requires = IS_NOT_IN_DB(db, '%s.role'
+ % settings.table_group_name)
+ settings.table_group = db[settings.table_group_name]
if not settings.table_membership_name in db.tables:
extra_fields = settings.extra_fields.get(
settings.table_membership_name,[])+signature_list
table = db.define_table(
settings.table_membership_name,
- Field('user_id', reference_table_user,
+ Field('user_id', settings.table_user,
label=self.messages.label_user_id),
- Field('group_id', reference_table_group,
+ Field('group_id', settings.table_group,
label=self.messages.label_group_id),
*extra_fields,
**dict(
migrate=self.__get_migrate(
settings.table_membership_name, migrate),
- fake_migrate=fake_migrate,
- on_define = lambda table: (
- table.user_id.set_attributes(
- requires = IS_IN_DB(
- db, '%s.id' % settings.table_user_name,
- settings.table_user._format)).
- table.group_id.set_attributes(
- requires = IS_IN_DB(
- db, '%s.id' % settings.table_group_name,
- '%(role)s (%(id)s)')))))
+ fake_migrate=fake_migrate))
+ table.user_id.requires = IS_IN_DB(db, '%s.id' %
+ settings.table_user_name,
+ settings.table_user._format)
+ table.group_id.requires = IS_IN_DB(db, '%s.id' %
+ settings.table_group_name,
+ '%(role)s (%(id)s)')
+ settings.table_membership = db[settings.table_membership_name]
if not settings.table_permission_name in db.tables:
extra_fields = settings.extra_fields.get(
settings.table_permission_name,[])+signature_list
table = db.define_table(
settings.table_permission_name,
- Field('group_id', reference_table_group,
+ Field('group_id', settings.table_group,
label=self.messages.label_group_id),
Field('name', default='default', length=512,
label=self.messages.label_name),
@@ -1521,17 +1517,14 @@ class Auth(object):
**dict(
migrate=self.__get_migrate(
settings.table_permission_name, migrate),
- fake_migrate=fake_migrate,
- on_define = lambda table: (
- table.group_id.set_attributes(
- requires = IS_IN_DB(
- db, '%s.id' % settings.table_group_name,
- '%(role)s (%(id)s)')),
- table.name.set_attributes(
- requires = IS_NOT_EMPTY(
- error_message=self.messages.is_empty)),
- table.record_id.set_attributes(
- requires = IS_INT_IN_RANGE(0, 10 ** 9)))))
+ fake_migrate=fake_migrate))
+ table.group_id.requires = IS_IN_DB(db, '%s.id' %
+ settings.table_group_name,
+ '%(role)s (%(id)s)')
+ table.name.requires = IS_NOT_EMPTY(error_message=self.messages.is_empty)
+ #table.table_name.requires = IS_EMPTY_OR(IS_IN_SET(self.db.tables))
+ table.record_id.requires = IS_INT_IN_RANGE(0, 10 ** 9)
+ settings.table_permission = db[settings.table_permission_name]
if not settings.table_event_name in db.tables:
table = db.define_table(
settings.table_event_name,
@@ -1541,7 +1534,7 @@ class Auth(object):
Field('client_ip',
default=current.request.client,
label=self.messages.label_client_ip),
- Field('user_id', reference_table_user, default=None,
+ Field('user_id', settings.table_user, default=None,
label=self.messages.label_user_id),
Field('origin', default='auth', length=512,
label=self.messages.label_origin),
@@ -1551,24 +1544,19 @@ class Auth(object):
**dict(
migrate=self.__get_migrate(
settings.table_event_name, migrate),
- fake_migrate=fake_migrate,
- on_define = lambda table: (
- table.user_id.set_attributes(
- requires = IS_IN_DB(
- db, '%s.id' % settings.table_user_name,
- settings.table_user._format)),
- table.origin.set_attributes(
- requires = IS_NOT_EMPTY(
- error_message=self.messages.is_empty)),
- table.description.set_attributes(
- requires = IS_NOT_EMPTY(
- error_message=self.messages.is_empty)))))
+ fake_migrate=fake_migrate))
+ table.user_id.requires = IS_IN_DB(db, '%s.id' %
+ settings.table_user_name,
+ settings.table_user._format)
+ table.origin.requires = IS_NOT_EMPTY(error_message=self.messages.is_empty)
+ table.description.requires = IS_NOT_EMPTY(error_message=self.messages.is_empty)
+ settings.table_event = db[settings.table_event_name]
now = current.request.now
if settings.cas_domains:
if not settings.table_cas_name in db.tables:
table = db.define_table(
settings.table_cas_name,
- Field('user_id', reference_table_user, default=None,
+ Field('user_id', settings.table_user, default=None,
label=self.messages.label_user_id),
Field('created_on','datetime',default=now),
Field('service',requires=IS_URL()),
@@ -1578,29 +1566,18 @@ class Auth(object):
**dict(
migrate=self.__get_migrate(
settings.table_cas_name, migrate),
- fake_migrate=fake_migrate,
- on_define = lambda table: (
- table.user_id.set_attributes(
- requires = IS_IN_DB(
- db, '%s.id' % settings.table_user_name,
- settings.table_user._format)))))
-
- if not db._lazy_tables:
- settings.table_user = db[settings.table_user_name]
- settings.table_group = db[settings.table_group_name]
- settings.table_membership = db[settings.table_membership_name]
- settings.table_permission = db[settings.table_permission_name]
- settings.table_event = db[settings.table_event_name]
- if settings.cas_domains:
- settings.table_cas = db[settings.table_cas_name]
-
- if settings.cas_provider: ### THIS IS NOT LAZY
+ fake_migrate=fake_migrate))
+ table.user_id.requires = IS_IN_DB(db, '%s.id' % \
+ settings.table_user_name,
+ settings.table_user._format)
+ settings.table_cas = db[settings.table_cas_name]
+ db._lazy_tables = lazy_tables
+ if settings.cas_provider:
settings.actions_disabled = \
- ['profile','register','change_password',
- 'request_reset_password']
+ ['profile','register','change_password','request_reset_password']
from gluon.contrib.login_methods.cas_auth import CasAuth
maps = settings.cas_maps
- if not maps:
+ if not maps:
maps = dict((name,lambda v,n=name:v.get(n,None)) for name in \
settings.table_user.fields if name!='id' \
and settings.table_user[name].readable)
From 417495e164e54fa38a44732fd536f2fdbac34ec9 Mon Sep 17 00:00:00 2001
From: mdipierro
Date: Thu, 23 Aug 2012 10:48:36 -0500
Subject: [PATCH 11/53] lazy auth?
---
VERSION | 2 +-
gluon/tools.py | 251 +++++++++++++++++++++++++------------------------
2 files changed, 130 insertions(+), 123 deletions(-)
diff --git a/VERSION b/VERSION
index a6d46fcb..ff874f51 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-Version 2.00.0 (2012-08-23 10:09:46) dev
+Version 2.00.0 (2012-08-23 10:48:33) dev
diff --git a/gluon/tools.py b/gluon/tools.py
index e67d5e4c..c5fdab89 100644
--- a/gluon/tools.py
+++ b/gluon/tools.py
@@ -997,7 +997,7 @@ class Auth(object):
settings.table_event_name = 'auth_event'
settings.table_cas_name = 'auth_cas'
- # ## if none, they will be created
+ # ## if none, they will be created, unless DAL(lazy_tables=True)!!!
settings.table_user = None
settings.table_group = None
@@ -1167,6 +1167,17 @@ class Auth(object):
user_id = property(_get_user_id, doc="user.id or None")
+ def table_user(self):
+ return self.db[self.settings.table_user_name]
+ def table_group(self):
+ return self.db[self.settings.table_group_name]
+ def table_memberhsip(self):
+ return self.db[self.settings.table_membership_name]
+ def table_event(self):
+ return self.db[self.settings.table_event_name]
+ def table_cas(self):
+ return self.db[self.settings.table_cas_name]
+
def _HTTP(self, *a, **b):
"""
only used in lambda: self._HTTP(404)
@@ -1261,7 +1272,7 @@ class Auth(object):
if not 'register' in self.settings.actions_disabled:
bar.insert(-1, s2)
bar.insert(-1, register)
- if 'username' in self.settings.table_user.fields() and \
+ if 'username' in self.user_username and \
not 'retrieve_username' in self.settings.actions_disabled:
bar.insert(-1, s2)
bar.insert(-1, retrieve_username)
@@ -1373,6 +1384,7 @@ class Auth(object):
db = self.db
settings = self.settings
+ self.use_username = username
if not self.signature:
self.define_signature()
if signature==True:
@@ -1384,23 +1396,40 @@ class Auth(object):
else:
signature_list = signature
lazy_tables, db._lazy_tables = db._lazy_tables, False
+ is_not_empty = IS_NOT_EMPTY(error_message=self.messages.is_empty)
+ is_crypted = CRYPT(key=settings.hmac_key,
+ min_length=settings.password_min_length)
+ is_unique_email = [
+ IS_EMAIL(error_message=self.messages.invalid_email),
+ IS_NOT_IN_DB(db, 'email.id')]
+ if not settings.email_case_sensitive:
+ is_unique_email.insert(1,IS_LOWER())
if not settings.table_user_name in db.tables:
passfield = settings.password_field
extra_fields = settings.extra_fields.get(
settings.table_user_name,[])+signature_list
- if username or settings.cas_provider:
+ if username or settings.cas_provider:
+ is_unique_username = \
+ [IS_MATCH('[\w\.\-]+'), IS_NOT_IN_DB(db, table.username)]
+ if not settings.username_case_sensitive:
+ is_unique_username.insert(1,IS_LOWER())
table = db.define_table(
settings.table_user_name,
Field('first_name', length=128, default='',
- label=self.messages.label_first_name),
+ label=self.messages.label_first_name,
+ requires = is_not_empty),
Field('last_name', length=128, default='',
- label=self.messages.label_last_name),
+ label=self.messages.label_last_name,
+ requires = is_not_empty),
Field('email', length=512, default='',
- label=self.messages.label_email),
+ label=self.messages.label_email,
+ requires = is_unique_email),
Field('username', length=128, default='',
- label=self.messages.label_username),
+ label=self.messages.label_username,
+ requires=is_unique_username),
Field(passfield, 'password', length=512,
- readable=False, label=self.messages.label_password),
+ readable=False, label=self.messages.label_password,
+ requires = [is_crypted]),
Field('registration_key', length=512,
writable=False, readable=False, default='',
label=self.messages.label_registration_key),
@@ -1416,22 +1445,21 @@ class Auth(object):
migrate),
fake_migrate=fake_migrate,
format='%(username)s'))
- table.username.requires = \
- [IS_MATCH('[\w\.\-]+'),
- IS_NOT_IN_DB(db, table.username)]
- if not settings.username_case_sensitive:
- table.username.requires.insert(1,IS_LOWER())
else:
table = db.define_table(
settings.table_user_name,
Field('first_name', length=128, default='',
- label=self.messages.label_first_name),
+ label=self.messages.label_first_name,
+ requires = is_not_empty),
Field('last_name', length=128, default='',
- label=self.messages.label_last_name),
+ label=self.messages.label_last_name,
+ requires = is_not_empty),
Field('email', length=512, default='',
- label=self.messages.label_email),
+ label=self.messages.label_email,
+ requires = is_unique_email),
Field(passfield, 'password', length=512,
- readable=False, label=self.messages.label_password),
+ readable=False, label=self.messages.label_password,
+ requires = [is_crypted]),
Field('registration_key', length=512,
writable=False, readable=False, default='',
label=self.messages.label_registration_key),
@@ -1447,27 +1475,16 @@ class Auth(object):
migrate),
fake_migrate=fake_migrate,
format='%(first_name)s %(last_name)s (%(id)s)'))
- table.first_name.requires = \
- IS_NOT_EMPTY(error_message=self.messages.is_empty)
- table.last_name.requires = \
- IS_NOT_EMPTY(error_message=self.messages.is_empty)
- table[passfield].requires = [
- CRYPT(key=settings.hmac_key,
- min_length=settings.password_min_length)]
- table.email.requires = \
- [IS_EMAIL(error_message=self.messages.invalid_email),
- IS_NOT_IN_DB(db, table.email)]
- if not settings.email_case_sensitive:
- table.email.requires.insert(1,IS_LOWER())
- table.registration_key.default = ''
- settings.table_user = db[settings.table_user_name]
+ reference_table_user = 'reference %s' % settings.table_user_name
if not settings.table_group_name in db.tables:
extra_fields = settings.extra_fields.get(
settings.table_group_name,[])+signature_list
table = db.define_table(
settings.table_group_name,
Field('role', length=512, default='',
- label=self.messages.label_role),
+ label=self.messages.label_role,
+ requires = IS_NOT_IN_DB(
+ db, '%s.role'% settings.table_group_name)),
Field('description', 'text',
label=self.messages.label_description),
*extra_fields,
@@ -1476,55 +1493,41 @@ class Auth(object):
settings.table_group_name, migrate),
fake_migrate=fake_migrate,
format = '%(role)s (%(id)s)'))
- table.role.requires = IS_NOT_IN_DB(db, '%s.role'
- % settings.table_group_name)
- settings.table_group = db[settings.table_group_name]
+ reference_table_group = 'reference %s' % settings.table_group_name
if not settings.table_membership_name in db.tables:
extra_fields = settings.extra_fields.get(
settings.table_membership_name,[])+signature_list
table = db.define_table(
settings.table_membership_name,
- Field('user_id', settings.table_user,
+ Field('user_id', reference_table_user,
label=self.messages.label_user_id),
- Field('group_id', settings.table_group,
+ Field('group_id', reference_table_group,
label=self.messages.label_group_id),
*extra_fields,
**dict(
migrate=self.__get_migrate(
settings.table_membership_name, migrate),
fake_migrate=fake_migrate))
- table.user_id.requires = IS_IN_DB(db, '%s.id' %
- settings.table_user_name,
- settings.table_user._format)
- table.group_id.requires = IS_IN_DB(db, '%s.id' %
- settings.table_group_name,
- '%(role)s (%(id)s)')
- settings.table_membership = db[settings.table_membership_name]
if not settings.table_permission_name in db.tables:
extra_fields = settings.extra_fields.get(
settings.table_permission_name,[])+signature_list
table = db.define_table(
settings.table_permission_name,
- Field('group_id', settings.table_group,
- label=self.messages.label_group_id),
+ Field('group_id', reference_table_group,
+ label=self.messages.label_group_id),
Field('name', default='default', length=512,
- label=self.messages.label_name),
+ label=self.messages.label_name,
+ requires=is_not_empty),
Field('table_name', length=512,
- label=self.messages.label_table_name),
+ label=self.messages.label_table_name),
Field('record_id', 'integer',default=0,
- label=self.messages.label_record_id),
+ label=self.messages.label_record_id,
+ requires = IS_INT_IN_RANGE(0, 10 ** 9)),
*extra_fields,
**dict(
migrate=self.__get_migrate(
settings.table_permission_name, migrate),
fake_migrate=fake_migrate))
- table.group_id.requires = IS_IN_DB(db, '%s.id' %
- settings.table_group_name,
- '%(role)s (%(id)s)')
- table.name.requires = IS_NOT_EMPTY(error_message=self.messages.is_empty)
- #table.table_name.requires = IS_EMPTY_OR(IS_IN_SET(self.db.tables))
- table.record_id.requires = IS_INT_IN_RANGE(0, 10 ** 9)
- settings.table_permission = db[settings.table_permission_name]
if not settings.table_event_name in db.tables:
table = db.define_table(
settings.table_event_name,
@@ -1534,29 +1537,25 @@ class Auth(object):
Field('client_ip',
default=current.request.client,
label=self.messages.label_client_ip),
- Field('user_id', settings.table_user, default=None,
- label=self.messages.label_user_id),
+ Field('user_id', reference_table_user, default=None,
+ label=self.messages.label_user_id),
Field('origin', default='auth', length=512,
- label=self.messages.label_origin),
+ label=self.messages.label_origin,
+ requires=is_not_empty),
Field('description', 'text', default='',
- label=self.messages.label_description),
+ label=self.messages.label_description,
+ requires=is_not_empty),
*settings.extra_fields.get(settings.table_event_name,[]),
**dict(
migrate=self.__get_migrate(
settings.table_event_name, migrate),
fake_migrate=fake_migrate))
- table.user_id.requires = IS_IN_DB(db, '%s.id' %
- settings.table_user_name,
- settings.table_user._format)
- table.origin.requires = IS_NOT_EMPTY(error_message=self.messages.is_empty)
- table.description.requires = IS_NOT_EMPTY(error_message=self.messages.is_empty)
- settings.table_event = db[settings.table_event_name]
now = current.request.now
if settings.cas_domains:
if not settings.table_cas_name in db.tables:
table = db.define_table(
settings.table_cas_name,
- Field('user_id', settings.table_user, default=None,
+ Field('user_id', reference_table_user, default=None,
label=self.messages.label_user_id),
Field('created_on','datetime',default=now),
Field('service',requires=IS_URL()),
@@ -1567,20 +1566,26 @@ class Auth(object):
migrate=self.__get_migrate(
settings.table_cas_name, migrate),
fake_migrate=fake_migrate))
- table.user_id.requires = IS_IN_DB(db, '%s.id' % \
- settings.table_user_name,
- settings.table_user._format)
- settings.table_cas = db[settings.table_cas_name]
- db._lazy_tables = lazy_tables
- if settings.cas_provider:
+ if not db._lazy_tables:
+ settings.table_user = db[settings.table_user_name]
+ settings.table_group = db[settings.table_group_name]
+ settings.table_membership = db[settings.table_membership_name]
+ settings.table_permission = db[settings.table_permission_name]
+ settings.table_event = db[settings.table_event_name]
+ if settings.cas_domains:
+ settings.table_cas = db[settings.table_cas_name]
+
+ if settings.cas_provider: ### THIS IS NOT LAZY
settings.actions_disabled = \
- ['profile','register','change_password','request_reset_password']
+ ['profile','register','change_password',
+ 'request_reset_password']
from gluon.contrib.login_methods.cas_auth import CasAuth
maps = settings.cas_maps
- if not maps:
+ if not maps:
+ table_user = self.table_user()
maps = dict((name,lambda v,n=name:v.get(n,None)) for name in \
- settings.table_user.fields if name!='id' \
- and settings.table_user[name].readable)
+ table_user.fields if name!='id' \
+ and table_user[name].readable)
maps['registration_id'] = \
lambda v,p=settings.cas_provider:'%s/%s' % (p,v['user'])
actions = [settings.cas_actions['login'],
@@ -1606,8 +1611,9 @@ class Auth(object):
else:
user_id = None # user unknown
vars = vars or {}
- self.settings.table_event.insert(description=description % vars,
- origin=origin, user_id=user_id)
+ self.table_event().insert(
+ description=description % vars,
+ origin=origin, user_id=user_id)
def get_or_create_user(self, keys, update_fields=['email']):
"""
@@ -1615,7 +1621,7 @@ class Auth(object):
If the user exists already then password is updated.
If the user doesn't yet exist, then they are created.
"""
- table_user = self.settings.table_user
+ table_user = self.table_user()
user = None
checks = []
# make a guess about who this user is
@@ -1673,7 +1679,7 @@ class Auth(object):
request = current.request
session = current.session
- table_user = self.settings.table_user
+ table_user = self.table_user()
if self.settings.login_userfield:
userfield = self.settings.login_userfield
elif 'username' in table_user.fields:
@@ -1711,7 +1717,7 @@ class Auth(object):
request = current.request
response = current.response
session = current.session
- db, table = self.db, self.settings.table_cas
+ db, table = self.db, self.table_cas()
session._cas_service = request.vars.service or session._cas_service
if not request.env.http_host in self.settings.cas_domains or \
not session._cas_service:
@@ -1747,7 +1753,7 @@ class Auth(object):
def cas_validate(self, version=2, proxy=False):
request = current.request
- db, table = self.db, self.settings.table_cas
+ db, table = self.db, self.table_cas()
current.response.headers['Content-Type']='text'
ticket = request.vars.ticket
renew = True if request.vars.has_key('renew') else False
@@ -1763,7 +1769,7 @@ class Auth(object):
# If ticket is a service Ticket and RENEW flag respected
if ticket[0:3] == 'ST-' and \
not ((row.renew and renew) ^ renew):
- user = self.settings.table_user(row.user_id)
+ user = self.table_user()(row.user_id)
row.delete_record()
success = True
def build_response(body):
@@ -1779,7 +1785,7 @@ class Auth(object):
TAG['cas:authenticationSuccess'](
TAG['cas:user'](username),
*[TAG['cas:'+field.name](user[field.name]) \
- for field in self.settings.table_user \
+ for field in self.table_user() \
if field.readable]))
else:
if version == 1:
@@ -1808,7 +1814,7 @@ class Auth(object):
"""
- table_user = self.settings.table_user
+ table_user = self.table_user()
if self.settings.login_userfield:
username = self.settings.login_userfield
elif 'username' in table_user.fields:
@@ -2043,7 +2049,7 @@ class Auth(object):
"""
- table_user = self.settings.table_user
+ table_user = self.table_user()
request = current.request
response = current.response
session = current.session
@@ -2058,7 +2064,7 @@ class Auth(object):
if log is DEFAULT:
log = self.messages.register_log
- table_user = self.settings.table_user
+ table_user = self.table_user()
if 'username' in table_user.fields:
username = 'username'
else:
@@ -2182,7 +2188,7 @@ class Auth(object):
"""
key = getarg(-1)
- table_user = self.settings.table_user
+ table_user = self.table_user()
user = self.db(table_user.registration_key == key).select().first()
if not user:
redirect(self.settings.login_url)
@@ -2221,7 +2227,7 @@ class Auth(object):
"""
- table_user = self.settings.table_user
+ table_user = self.table_user()
if not 'username' in table_user.fields:
raise HTTP(404)
request = current.request
@@ -2306,7 +2312,7 @@ class Auth(object):
"""
- table_user = self.settings.table_user
+ table_user = self.table_user()
request = current.request
response = current.response
session = current.session
@@ -2383,7 +2389,7 @@ class Auth(object):
"""
- table_user = self.settings.table_user
+ table_user = self.table_user()
request = current.request
# response = current.response
session = current.session
@@ -2403,7 +2409,7 @@ class Auth(object):
form = SQLFORM.factory(
Field('new_password', 'password',
label=self.messages.new_password,
- requires=self.settings.table_user[passfield].requires),
+ requires=self.table_user()[passfield].requires),
Field('new_password2', 'password',
label=self.messages.verify_password,
requires=[IS_EXPR('value==%s' % repr(request.vars.new_password),
@@ -2435,7 +2441,7 @@ class Auth(object):
[, onvalidation=DEFAULT [, onaccept=DEFAULT [, log=DEFAULT]]]])
"""
- table_user = self.settings.table_user
+ table_user = self.table_user()
request = current.request
response = current.response
session = current.session
@@ -2533,7 +2539,7 @@ class Auth(object):
if not self.is_logged_in():
redirect(self.settings.login_url)
db = self.db
- table_user = self.settings.table_user
+ table_user = self.table_user()
usern = self.settings.table_user_name
s = db(table_user.id == self.user.id)
@@ -2599,11 +2605,11 @@ class Auth(object):
"""
- table_user = self.settings.table_user
+ table_user = self.table_user()
if not self.is_logged_in():
redirect(self.settings.login_url)
passfield = self.settings.password_field
- self.settings.table_user[passfield].writable = False
+ table_user[passfield].writable = False
request = current.request
session = current.session
if next is DEFAULT:
@@ -2654,6 +2660,7 @@ class Auth(object):
request = current.request
session = current.session
auth = session.auth
+ table_user = self.table_user()
if not self.is_logged_in():
raise HTTP(401, "Not Authorized")
current_id = auth.user.id
@@ -2665,12 +2672,12 @@ class Auth(object):
self.settings.table_user_name,
user_id):
raise HTTP(403, "Forbidden")
- user = self.settings.table_user(user_id)
+ user = table_user(user_id)
if not user:
raise HTTP(401, "Not Authorized")
auth.impersonator = cPickle.dumps(session)
auth.user.update(
- self.settings.table_user._filter_fields(user, True))
+ table_user._filter_fields(user, True))
self.user = auth.user
if self.settings.login_onaccept:
form = Storage(dict(vars=self.user))
@@ -2691,10 +2698,11 @@ class Auth(object):
user_groups = self.user_groups = {}
if current.session.auth:
current.session.auth.user_groups = self.user_groups
- memberships = self.db(self.settings.table_membership.user_id
- == self.user.id).select()
+ table_group = self.table_group()
+ table_membership = self.table_membership()
+ memberships = self.db(table_membership.user_id==self.user.id).select()
for membership in memberships:
- group = self.settings.table_group(membership.group_id)
+ group = table_group(membership.group_id)
if group:
user_groups[membership.group_id] = group.role
@@ -2705,12 +2713,12 @@ class Auth(object):
if not self.is_logged_in():
redirect(self.settings.login_url)
- memberships = self.db(self.settings.table_membership.user_id
- == self.user.id).select()
+ table_membership = self.table_membership()
+ memberships = self.db(table_membership.user_id==self.user.id).select()
table = TABLE()
for membership in memberships:
- groups = self.db(self.settings.table_group.id
- == membership.group_id).select()
+ table_group = self.db[self.settings.table_group_name]
+ groups = self.db(table_group.id==membership.group_id).select()
if groups:
group = groups[0]
table.append(TR(H3(group.role, '(%s)' % group.id)))
@@ -2814,7 +2822,7 @@ class Auth(object):
creates a group associated to a role
"""
- group_id = self.settings.table_group.insert(
+ group_id = self.table_group().insert(
role=role, description=description)
self.log_event(self.messages.add_group_log,
dict(group_id=group_id, role=role))
@@ -2824,10 +2832,9 @@ class Auth(object):
"""
deletes a group
"""
-
- self.db(self.settings.table_group.id == group_id).delete()
- self.db(self.settings.table_membership.group_id == group_id).delete()
- self.db(self.settings.table_permission.group_id == group_id).delete()
+ self.db(self.table_group().id == group_id).delete()
+ self.db(self.table_membership().group_id == group_id).delete()
+ self.db(self.table_permission().group_id == group_id).delete()
self.update_groups()
self.log_event(self.messages.del_group_log,dict(group_id=group_id))
@@ -2835,7 +2842,7 @@ class Auth(object):
"""
returns the group_id of the group specified by the role
"""
- rows = self.db(self.settings.table_group.role == role).select()
+ rows = self.db(self.table_group().role == role).select()
if not rows:
return None
return rows[0].id
@@ -2849,7 +2856,7 @@ class Auth(object):
def user_group_role(self, user_id=None):
if user_id:
- user = self.settings.table_user[user_id]
+ user = self.table_user()[user_id]
else:
user = self.user
return self.settings.create_user_groups % user
@@ -2867,7 +2874,7 @@ class Auth(object):
group_id = self.id_group(group_id) # interpret group_id as a role
if not user_id and self.user:
user_id = self.user.id
- membership = self.settings.table_membership
+ membership = self.table_membership()
if self.db((membership.user_id == user_id)
& (membership.group_id == group_id)).select():
r = True
@@ -2890,7 +2897,7 @@ class Auth(object):
group_id = self.id_group(group_id) # interpret group_id as a role
if not user_id and self.user:
user_id = self.user.id
- membership = self.settings.table_membership
+ membership = self.table_membership()
record = membership(user_id = user_id,group_id = group_id)
if record:
return record.id
@@ -2910,7 +2917,7 @@ class Auth(object):
group_id = group_id or self.id_group(role)
if not user_id and self.user:
user_id = self.user.id
- membership = self.settings.table_membership
+ membership = self.table_membership()
self.log_event(self.messages.del_membership_log,
dict(user_id=user_id,group_id=group_id))
ret = self.db(membership.user_id
@@ -2941,7 +2948,7 @@ class Auth(object):
if not user_id and not group_id and self.user:
user_id = self.user.id
if user_id:
- membership = self.settings.table_membership
+ membership = self.table_membership()
rows = self.db(membership.user_id
== user_id).select(membership.group_id)
groups = set([row.group_id for row in rows])
@@ -2949,7 +2956,7 @@ class Auth(object):
return False
else:
groups = set([group_id])
- permission = self.settings.table_permission
+ permission = self.table_permission()
rows = self.db(permission.name == name)(permission.table_name
== str(table_name))(permission.record_id
== record_id).select(permission.group_id)
@@ -2982,7 +2989,7 @@ class Auth(object):
gives group_id 'name' access to 'table_name' and 'record_id'
"""
- permission = self.settings.table_permission
+ permission = self.table_permission()
if group_id == 0:
group_id = self.user_group()
record = self.db(permission.group_id==group_id)(permission.name==name)\
@@ -3011,7 +3018,7 @@ class Auth(object):
revokes group_id 'name' access to 'table_name' and 'record_id'
"""
- permission = self.settings.table_permission
+ permission = self.table_permission()
self.log_event(self.messages.del_permission_log,
dict(group_id=group_id, name=name,
table_name=table_name, record_id=record_id))
@@ -3039,8 +3046,8 @@ class Auth(object):
self.has_permission(name, table, 0, user_id):
return table.id > 0
db = self.db
- membership = self.settings.table_membership
- permission = self.settings.table_permission
+ membership = self.table_membership()
+ permission = self.table_permission()
query = table.id.belongs(
db(membership.user_id == user_id)\
(membership.group_id == permission.group_id)\
From b8dbb8a5dd567e30d02804cf765eaea68ba563e2 Mon Sep 17 00:00:00 2001
From: mdipierro
Date: Thu, 23 Aug 2012 10:56:40 -0500
Subject: [PATCH 12/53] fixed auth with lazy auth
---
VERSION | 2 +-
gluon/tools.py | 6 +++---
2 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/VERSION b/VERSION
index ff874f51..c687f9a9 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-Version 2.00.0 (2012-08-23 10:48:33) dev
+Version 2.00.0 (2012-08-23 10:56:37) dev
diff --git a/gluon/tools.py b/gluon/tools.py
index c5fdab89..10736178 100644
--- a/gluon/tools.py
+++ b/gluon/tools.py
@@ -1171,7 +1171,7 @@ class Auth(object):
return self.db[self.settings.table_user_name]
def table_group(self):
return self.db[self.settings.table_group_name]
- def table_memberhsip(self):
+ def table_membership(self):
return self.db[self.settings.table_membership_name]
def table_event(self):
return self.db[self.settings.table_event_name]
@@ -1272,7 +1272,7 @@ class Auth(object):
if not 'register' in self.settings.actions_disabled:
bar.insert(-1, s2)
bar.insert(-1, register)
- if 'username' in self.user_username and \
+ if self.use_username and \
not 'retrieve_username' in self.settings.actions_disabled:
bar.insert(-1, s2)
bar.insert(-1, retrieve_username)
@@ -1401,7 +1401,7 @@ class Auth(object):
min_length=settings.password_min_length)
is_unique_email = [
IS_EMAIL(error_message=self.messages.invalid_email),
- IS_NOT_IN_DB(db, 'email.id')]
+ IS_NOT_IN_DB(db, '%s.email' % settings.table_user_name)]
if not settings.email_case_sensitive:
is_unique_email.insert(1,IS_LOWER())
if not settings.table_user_name in db.tables:
From dcb4a92bc4f60302050420ec66210ef40fa7160f Mon Sep 17 00:00:00 2001
From: mdipierro
Date: Thu, 23 Aug 2012 11:11:41 -0500
Subject: [PATCH 13/53] R-2.0.1-rc2
---
VERSION | 2 +-
gluon/storage.py | 58 ++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 59 insertions(+), 1 deletion(-)
diff --git a/VERSION b/VERSION
index c687f9a9..e2ad51cb 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-Version 2.00.0 (2012-08-23 10:56:37) dev
+Version 2.00.0 (2012-08-23 11:11:38) dev
diff --git a/gluon/storage.py b/gluon/storage.py
index fa956703..1b6da5c1 100644
--- a/gluon/storage.py
+++ b/gluon/storage.py
@@ -170,6 +170,64 @@ class Messages(Settings):
return str(self.T(value))
return value
+class FastStorage(dict):
+ """
+ Eventually this should replace class Storage but causes memory leak
+ because of http://bugs.python.org/issue1469629
+
+ >>> s = FastStorage()
+ >>> s.a = 1
+ >>> s.a
+ 1
+ >>> s['a']
+ 1
+ >>> s.b
+ >>> s['b']
+ >>> s['b']=2
+ >>> s['b']
+ 2
+ >>> s.b
+ 2
+ >>> isinstance(s,dict)
+ True
+ >>> dict(s)
+ {'a': 1, 'b': 2}
+ >>> dict(FastStorage(s))
+ {'a': 1, 'b': 2}
+ >>> import pickle
+ >>> s = pickle.loads(pickle.dumps(s))
+ >>> dict(s)
+ {'a': 1, 'b': 2}
+ >>> del s.b
+ >>> del s.a
+ >>> s.a
+ >>> s.b
+ >>> s['a']
+ >>> s['b']
+ """
+ def __init__(self, *args, **kwargs):
+ dict.__init__(self, *args, **kwargs)
+ self.__dict__ = self
+ def __getattr__(self,key):
+ return getattr(self,key) if key in self else None
+ def __getitem__(self,key):
+ return dict.get(self,key,None)
+ def copy(self):
+ self.__dict__ = {}
+ s = FastStorage(self)
+ self.__dict__ = self
+ return s
+ def __repr__(self):
+ return '' % dict.__repr__(self)
+ def __getstate__(self):
+ return dict(self)
+ def __setstate__(self, sdict):
+ dict.__init__(self, sdict)
+ self.__dict__=self
+ def update(self, *args, **kwargs):
+ dict.__init__(self, *args, **kwargs)
+ self.__dict__=self
+
class List(list):
"""
Like a regular python list but a[i] if i is out of bounds return None
From 807045e8b30da740748e096812b51e1c34e6bd83 Mon Sep 17 00:00:00 2001
From: mdipierro
Date: Thu, 23 Aug 2012 12:44:31 -0500
Subject: [PATCH 14/53] edited makefile
---
Makefile | 8 ++++----
VERSION | 2 +-
2 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/Makefile b/Makefile
index 472d89b5..864d37ec 100644
--- a/Makefile
+++ b/Makefile
@@ -1,3 +1,7 @@
+all:
+ echo "The Makefile is used to build the distribution."
+ echo "In order to run web2py you do not need to make anything."
+ echo "just run web2py.py"
clean:
rm -f httpserver.log
rm -f parameters*.py
@@ -13,10 +17,6 @@ clean:
find ./applications/examples/ -name '.*' -exec rm -f {} \;
find ./applications/welcome/ -name '.*' -exec rm -f {} \;
find ./ -name '*.pyc' -exec rm -f {} \;
-all:
- echo "The Makefile is used to build the distribution."
- echo "In order to run web2py you do not need to make anything."
- echo "just run web2py.py"
epydoc:
### build epydoc
rm -f -r applications/examples/static/epydoc/
diff --git a/VERSION b/VERSION
index e2ad51cb..26abe865 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-Version 2.00.0 (2012-08-23 11:11:38) dev
+Version 2.00.0 (2012-08-23 12:44:28) dev
From 00f9292e0667065cb13d931ee843265f9541439a Mon Sep 17 00:00:00 2001
From: mdipierro
Date: Thu, 23 Aug 2012 13:19:19 -0500
Subject: [PATCH 15/53] smarter index.html for wiki
---
VERSION | 2 +-
applications/welcome/views/default/index.html | 5 +++--
2 files changed, 4 insertions(+), 3 deletions(-)
diff --git a/VERSION b/VERSION
index 26abe865..fd7fc765 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-Version 2.00.0 (2012-08-23 12:44:28) dev
+Version 2.00.0 (2012-08-23 13:19:16) dev
diff --git a/applications/welcome/views/default/index.html b/applications/welcome/views/default/index.html
index 50e64114..6e9ab1ff 100644
--- a/applications/welcome/views/default/index.html
+++ b/applications/welcome/views/default/index.html
@@ -1,4 +1,4 @@
-{{left_sidebar_enabled,right_sidebar_enabled=False,True}}
+{{left_sidebar_enabled,right_sidebar_enabled=False,('message' in globals())}}
{{extend 'layout.html'}}
{{if 'message' in globals():}}
@@ -17,6 +17,8 @@
_href=URL('admin','default','peek',args=(request.application,'views',request.controller,'index.html')))))}}
{{=T('You can modify this application and adapt it to your needs')}}