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
  • -
    1. option 2
    -
  • option 3
- - - - - - - -
Header 1header 2
cell 1cell 2
cell 2cell 3
- - - - - - - - - - - - - -""" + """ - - - -""" * 200 + """ - -
Header 1header 2
footer 1footer 2
cell 1cell 2
cell 1cell 2
cell spanned
cell 3cell 4
cell 5cell 6
-""" - - 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