initial commit
This commit is contained in:
@@ -0,0 +1 @@
|
||||
Read more about this http://code.google.com/p/pyfpdf
|
||||
@@ -0,0 +1,5 @@
|
||||
from fpdf import FPDF
|
||||
from html import HTMLMixin
|
||||
from template import Template
|
||||
|
||||
|
||||
@@ -0,0 +1,736 @@
|
||||
#!/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 <reingart@gmail.com>"
|
||||
__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()
|
||||
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,459 @@
|
||||
# -*- coding: latin-1 -*-
|
||||
|
||||
"HTML Renderer for FPDF.py"
|
||||
|
||||
__author__ = "Mariano Reingart <reingart@gmail.com>"
|
||||
__copyright__ = "Copyright (C) 2010 Mariano Reingart"
|
||||
__license__ = "LGPL 3.0"
|
||||
|
||||
# Inspired by tuto5.py and several examples from fpdf.org, html2fpdf, etc.
|
||||
|
||||
from fpdf import FPDF
|
||||
from HTMLParser import HTMLParser
|
||||
|
||||
DEBUG = False
|
||||
|
||||
def px2mm(px):
|
||||
return int(px)*25.4/72.0
|
||||
|
||||
def hex2dec(color = "#000000"):
|
||||
if color:
|
||||
r = int(color[1:3], 16)
|
||||
g = int(color[3:5], 16)
|
||||
b = int(color[5:7], 16)
|
||||
return r, g, b
|
||||
|
||||
class HTML2FPDF(HTMLParser):
|
||||
"Render basic HTML to FPDF"
|
||||
|
||||
def __init__(self, pdf, image_map, **kwargs):
|
||||
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.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.table = None # table attributes
|
||||
self.table_col_width = None # column (header) widths
|
||||
self.table_col_index = None # current column index
|
||||
self.td = None # cell attributes
|
||||
self.th = False # header enabled
|
||||
self.tr = None
|
||||
self.theader = None # table header cells
|
||||
self.tfooter = None # table footer cells
|
||||
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
|
||||
if self.table['width'][-1]=='%':
|
||||
total *= int(self.table['width'][:-1])/100.0
|
||||
return int(length[:-1]) * total / 101.0
|
||||
else:
|
||||
return int(length) / 6.0
|
||||
|
||||
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]]
|
||||
elif 'colspan' in self.td:
|
||||
i = self.table_col_index
|
||||
colspan = int(self.td['colspan'])
|
||||
l = self.table_col_width[i:i+colspan]
|
||||
else:
|
||||
l = [self.td.get('width','240')]
|
||||
w = sum([self.width2mm(lenght) for lenght in l])
|
||||
h = int(self.td.get('height', 0)) / 4 or self.h*1.30
|
||||
self.table_h = h
|
||||
border = int(self.table.get('border', 0))
|
||||
if not self.th:
|
||||
align = self.td.get('align', 'L')[0].upper()
|
||||
border = border and 'LR'
|
||||
else:
|
||||
self.set_style('B',True)
|
||||
border = border or 'B'
|
||||
align = 'C'
|
||||
bgcolor = hex2dec(self.td.get('bgcolor', self.tr.get('bgcolor', '')))
|
||||
# parsing table header/footer (drawn later):
|
||||
if self.thead is not None:
|
||||
self.theader.append(((w,h,txt,border,0,align), bgcolor))
|
||||
if self.tfoot is not None:
|
||||
self.tfooter.append(((w,h,txt,border,0,align), bgcolor))
|
||||
# check if reached end of page, add table footer and header:
|
||||
height = h + (self.tfooter and self.tfooter[0][0][1] or 0)
|
||||
if self.pdf.y+height>self.pdf.page_break_trigger and not self.th:
|
||||
self.output_table_footer()
|
||||
self.pdf.add_page()
|
||||
self.theader_out = self.tfooter_out = False
|
||||
if self.tfoot is None and self.thead is None:
|
||||
if not self.theader_out:
|
||||
self.output_table_header()
|
||||
self.box_shadow(w, h, bgcolor)
|
||||
if DEBUG: print "td cell", self.pdf.x, w, txt, "*"
|
||||
self.pdf.cell(w,h,txt,border,0,align)
|
||||
elif self.table is not None:
|
||||
# ignore anything else than td inside a table
|
||||
pass
|
||||
elif self.align:
|
||||
if DEBUG: print "cell", txt, "*"
|
||||
self.pdf.cell(0,self.h,txt,0,1,self.align[0].upper(), self.href)
|
||||
else:
|
||||
txt = txt.replace("\n"," ")
|
||||
if self.href:
|
||||
self.put_link(self.href,txt)
|
||||
else:
|
||||
if DEBUG: print "write", txt, "*"
|
||||
self.pdf.write(self.h,txt)
|
||||
|
||||
def box_shadow(self, w, h, bgcolor):
|
||||
if DEBUG: print "box_shadow", w, h, bgcolor
|
||||
if bgcolor:
|
||||
fill_color = self.pdf.fill_color
|
||||
self.pdf.set_fill_color(*bgcolor)
|
||||
self.pdf.rect(self.pdf.x, self.pdf.y, w, h, 'F')
|
||||
self.pdf.fill_color = fill_color
|
||||
|
||||
def output_table_header(self):
|
||||
if self.theader:
|
||||
b = self.b
|
||||
x = self.pdf.x
|
||||
self.pdf.set_x(self.table_offset)
|
||||
self.set_style('B',True)
|
||||
for cell, bgcolor in self.theader:
|
||||
self.box_shadow(cell[0], cell[1], bgcolor)
|
||||
self.pdf.cell(*cell)
|
||||
self.set_style('B',b)
|
||||
self.pdf.ln(self.theader[0][0][1])
|
||||
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
|
||||
self.pdf.set_x(self.table_offset)
|
||||
#TODO: self.output_table_sep()
|
||||
for cell, bgcolor in self.tfooter:
|
||||
self.box_shadow(cell[0], cell[1], bgcolor)
|
||||
self.pdf.cell(*cell)
|
||||
self.pdf.ln(self.tfooter[0][0][1])
|
||||
self.pdf.set_x(x)
|
||||
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
|
||||
y1 = self.pdf.y
|
||||
w = sum([self.width2mm(lenght) for lenght in self.table_col_width])
|
||||
self.pdf.line(x1,y1,x1+w,y1)
|
||||
|
||||
|
||||
def handle_starttag(self, tag, attrs):
|
||||
attrs = dict(attrs)
|
||||
if DEBUG: print "STARTTAG", tag, attrs
|
||||
if tag=='b' or tag=='i' or tag=='u':
|
||||
self.set_style(tag,1)
|
||||
if tag=='a':
|
||||
self.href=attrs['href']
|
||||
if tag=='br':
|
||||
self.pdf.ln(5)
|
||||
if tag=='p':
|
||||
self.pdf.ln(5)
|
||||
if attrs:
|
||||
self.align=attrs['align'].lower()
|
||||
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)
|
||||
self.pdf.set_text_color(150,0,0)
|
||||
self.pdf.set_font_size(12 * k)
|
||||
if attrs: self.align = attrs.get('align')
|
||||
if tag=='hr':
|
||||
self.put_line()
|
||||
if tag=='pre':
|
||||
self.pdf.set_font('Courier','',11)
|
||||
self.pdf.set_font_size(11)
|
||||
self.set_style('B',False)
|
||||
self.set_style('I',False)
|
||||
self.pre = True
|
||||
if tag=='blockquote':
|
||||
self.set_text_color(100,0,45)
|
||||
self.pdf.ln(3)
|
||||
if tag=='ul':
|
||||
self.indent+=1
|
||||
self.bullet.append('\x95')
|
||||
if tag=='ol':
|
||||
self.indent+=1
|
||||
self.bullet.append(0)
|
||||
if tag=='li':
|
||||
self.pdf.ln(self.h+2)
|
||||
self.pdf.set_text_color(190,0,0)
|
||||
bullet = self.bullet[self.indent-1]
|
||||
if not isinstance(bullet, basestring):
|
||||
bullet += 1
|
||||
self.bullet[self.indent-1] = bullet
|
||||
bullet = "%s. " % bullet
|
||||
self.pdf.write(self.h,'%s%s ' % (' '*5*self.indent, bullet))
|
||||
self.set_text_color()
|
||||
if tag=='font':
|
||||
if 'color' in attrs:
|
||||
self.color = hex2dec(attrs['color'])
|
||||
self.set_text_color(*color)
|
||||
self.color = color
|
||||
if 'face' in attrs and attrs['face'].lower() in self.font_list:
|
||||
face = attrs.get('face').lower()
|
||||
self.pdf.set_font(face)
|
||||
self.font_face = face
|
||||
if 'size' in attrs:
|
||||
size = int(attrs.get('size'))
|
||||
self.pdf.set_font(self.font_face, size=int(size))
|
||||
self.font_size = size
|
||||
if tag=='table':
|
||||
self.table = dict([(k.lower(), v) for k,v in attrs.items()])
|
||||
if not 'width' in self.table:
|
||||
self.table['width'] = '100%'
|
||||
if self.table['width'][-1]=='%':
|
||||
w = self.pdf.w - self.pdf.r_margin - self.pdf.l_margin
|
||||
w *= int(self.table['width'][:-1])/100.0
|
||||
self.table_offset = (self.pdf.w-w)/2.0
|
||||
self.table_col_width = []
|
||||
self.theader_out = self.tfooter_out = False
|
||||
self.theader = []
|
||||
self.tfooter = []
|
||||
self.thead = None
|
||||
self.tfoot = None
|
||||
self.pdf.ln()
|
||||
if tag=='tr':
|
||||
self.tr = dict([(k.lower(), v) for k,v in attrs.items()])
|
||||
self.table_col_index = 0
|
||||
self.pdf.set_x(self.table_offset)
|
||||
if tag=='td':
|
||||
self.td = dict([(k.lower(), v) for k,v in attrs.items()])
|
||||
if tag=='th':
|
||||
self.td = dict([(k.lower(), v) for k,v in attrs.items()])
|
||||
self.th = True
|
||||
if self.td['width']:
|
||||
self.table_col_width.append(self.td['width'])
|
||||
if tag=='thead':
|
||||
self.thead = {}
|
||||
if tag=='tfoot':
|
||||
self.tfoot = {}
|
||||
if tag=='img':
|
||||
if 'src' in attrs:
|
||||
x = self.pdf.get_x()
|
||||
y = self.pdf.get_y()
|
||||
w = px2mm(attrs.get('width', 0))
|
||||
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.set_x(x+w)
|
||||
self.pdf.set_y(y+h)
|
||||
if tag=='b' or tag=='i' or tag=='u':
|
||||
self.set_style(tag, True)
|
||||
if tag=='center':
|
||||
self.align = 'Center'
|
||||
|
||||
def handle_endtag(self, tag):
|
||||
#Closing tag
|
||||
if DEBUG: print "ENDTAG", tag
|
||||
if tag=='h1' or tag=='h2' or tag=='h3' or tag=='h4':
|
||||
self.pdf.ln(6)
|
||||
self.set_font()
|
||||
self.set_style()
|
||||
self.align = None
|
||||
if tag=='pre':
|
||||
self.pdf.set_font(self.font or 'Times','',12)
|
||||
self.pdf.set_font_size(12)
|
||||
self.pre=False
|
||||
if tag=='blockquote':
|
||||
self.set_text_color(0,0,0)
|
||||
self.pdf.ln(3)
|
||||
if tag=='strong':
|
||||
tag='b'
|
||||
if tag=='em':
|
||||
tag='i'
|
||||
if tag=='b' or tag=='i' or tag=='u':
|
||||
self.set_style(tag, False)
|
||||
if tag=='a':
|
||||
self.href=''
|
||||
if tag=='p':
|
||||
self.align=''
|
||||
if tag in ('ul', 'ol'):
|
||||
self.indent-=1
|
||||
self.bullet.pop()
|
||||
if tag=='table':
|
||||
if not self.tfooter_out:
|
||||
self.output_table_footer()
|
||||
self.table = None
|
||||
self.th = False
|
||||
self.theader = None
|
||||
self.tfooter = None
|
||||
self.pdf.ln()
|
||||
if tag=='thead':
|
||||
self.thead = None
|
||||
if tag=='tfoot':
|
||||
self.tfoot = None
|
||||
if tag=='tbody':
|
||||
# draw a line separator between table bodies
|
||||
self.pdf.set_x(self.table_offset)
|
||||
self.output_table_sep()
|
||||
if tag=='tr':
|
||||
h = self.table_h
|
||||
if self.tfoot is None:
|
||||
self.pdf.ln(h)
|
||||
self.tr = None
|
||||
if tag=='td' or tag=='th':
|
||||
if self.th:
|
||||
if DEBUG: print "revert style"
|
||||
self.set_style('B', False) # revert style
|
||||
self.table_col_index += int(self.td.get('colspan','1'))
|
||||
self.td = None
|
||||
self.th = False
|
||||
if tag=='font':
|
||||
if self.color:
|
||||
self.pdf.set_text_color(0,0,0)
|
||||
self.color = None
|
||||
if self.font_face:
|
||||
self.set_font('Times',12)
|
||||
|
||||
if tag=='center':
|
||||
self.align = None
|
||||
|
||||
def set_font(self, face=None, size=None):
|
||||
if face:
|
||||
self.font_face = face
|
||||
if size:
|
||||
self.font_size = size
|
||||
self.h = size / 72.0*25.4
|
||||
if DEBUG: print "H", self.h
|
||||
self.pdf.set_font(self.font_face or 'times','',12)
|
||||
self.pdf.set_font_size(self.font_size or 12)
|
||||
self.set_style('u', False)
|
||||
self.set_style('b', False)
|
||||
self.set_style('i', False)
|
||||
self.set_text_color()
|
||||
|
||||
def set_style(self, tag=None, enable=None):
|
||||
#Modify style and select corresponding font
|
||||
if tag:
|
||||
t = self.style.get(tag.lower())
|
||||
self.style[tag.lower()] = enable
|
||||
style=''
|
||||
for s in ('b','i','u'):
|
||||
if self.style.get(s):
|
||||
style+=s
|
||||
if DEBUG: print "SET_FONT_STYLE", style
|
||||
self.pdf.set_font('',style)
|
||||
|
||||
def set_text_color(self, r=None, g=0, b=0):
|
||||
if r is None:
|
||||
self.pdf.set_text_color(self.r,self.g,self.b)
|
||||
else:
|
||||
self.pdf.set_text_color(r, g, b)
|
||||
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)
|
||||
self.set_style('u', True)
|
||||
self.pdf.write(5,txt,url)
|
||||
self.set_style('u', False)
|
||||
self.set_text_color(0)
|
||||
|
||||
def put_line(self):
|
||||
self.pdf.ln(2)
|
||||
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):
|
||||
"Parse HTML and convert it to PDF"
|
||||
h2p = HTML2FPDF(self,image_map=image_map,**kwargs)
|
||||
h2p.feed(text)
|
||||
|
||||
if __name__=='__main__':
|
||||
html="""
|
||||
<H1 align="center">html2fpdf</H1>
|
||||
<h2>Basic usage</h2>
|
||||
<p>You can now easily print text mixing different
|
||||
styles : <B>bold</B>, <I>italic</I>, <U>underlined</U>, or
|
||||
<B><I><U>all at once</U></I></B>!<BR>You can also insert links
|
||||
on text, such as <A HREF="http://www.fpdf.org">www.fpdf.org</A>,
|
||||
or on an image: click on the logo.<br>
|
||||
<center>
|
||||
<A HREF="http://www.fpdf.org"><img src="tutorial/logo.png" width="104" height="71"></A>
|
||||
</center>
|
||||
<h3>Sample List</h3>
|
||||
<ul><li>option 1</li>
|
||||
<ol><li>option 2</li></ol>
|
||||
<li>option 3</li></ul>
|
||||
|
||||
<table border="0" align="center" width="50%">
|
||||
<thead><tr><th width="30%">Header 1</th><th width="70%">header 2</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td>cell 1</td><td>cell 2</td></tr>
|
||||
<tr><td>cell 2</td><td>cell 3</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
|
||||
<table border="1">
|
||||
<thead><tr bgcolor="#A0A0A0"><th width="30%">Header 1</th><th width="70%">header 2</th></tr></thead>
|
||||
<tfoot><tr bgcolor="#E0E0E0"><td>footer 1</td><td>footer 2</td></tr></tfoot>
|
||||
<tbody>
|
||||
<tr><td>cell 1</td><td>cell 2</td></tr>
|
||||
<tr>
|
||||
<td width="30%">cell 1</td><td width="70%" bgcolor="#D0D0FF" align='right'>cell 2</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<tbody><tr><td colspan="2">cell spanned</td></tr></tbody>
|
||||
<tbody>
|
||||
""" + """<tr bgcolor="#F0F0F0">
|
||||
<td>cell 3</td><td>cell 4</td>
|
||||
</tr><tr bgcolor="#FFFFFF">
|
||||
<td>cell 5</td><td>cell 6</td>
|
||||
</tr>""" * 200 + """
|
||||
</tbody>
|
||||
</table>
|
||||
"""
|
||||
|
||||
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")
|
||||
|
||||
|
||||
Executable
+278
@@ -0,0 +1,278 @@
|
||||
# -*- coding: iso-8859-1 -*-
|
||||
|
||||
"PDF Template Helper for FPDF.py"
|
||||
|
||||
__author__ = "Mariano Reingart <reingart@gmail.com>"
|
||||
__copyright__ = "Copyright (C) 2010 Mariano Reingart"
|
||||
__license__ = "LGPL 3.0"
|
||||
|
||||
import sys,os,csv
|
||||
from fpdf import FPDF
|
||||
|
||||
def rgb(col):
|
||||
return (col // 65536), (col // 256 % 256), (col% 256)
|
||||
|
||||
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,
|
||||
'B': self.rect, 'BC': self.barcode, }
|
||||
self.pg_no = 0
|
||||
self.texts = {}
|
||||
pdf = self.pdf = FPDF(format=format,orientation=orientation, unit="mm")
|
||||
pdf.set_title(title)
|
||||
pdf.set_author(author)
|
||||
pdf.set_creator(creator)
|
||||
pdf.set_subject(subject)
|
||||
pdf.set_keywords(keywords)
|
||||
|
||||
def parse_csv(self, infile, delimiter=",", decimal_sep="."):
|
||||
"Parse template format csv file and create elements dict"
|
||||
keys = ('name','type','x1','y1','x2','y2','font','size',
|
||||
'bold','italic','underline','foreground','background',
|
||||
'align','text','priority')
|
||||
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()
|
||||
|
||||
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 isinstance(value,unicode):
|
||||
value = value.encode("latin1","ignore")
|
||||
else:
|
||||
value = str(value)
|
||||
self.texts[self.pg_no][name.lower()] = value
|
||||
|
||||
# setitem shortcut (may be further extended)
|
||||
set = __setitem__
|
||||
|
||||
def __getitem__(self, name):
|
||||
if name.lower() in self.elements:
|
||||
return self.texts[self.pg_no].get(name.lower(), self.elements[name.lower()]['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()]
|
||||
style = ""
|
||||
if element['bold']: style += "B"
|
||||
if element['italic']: style += "I"
|
||||
if element['underline']: style += "U"
|
||||
pdf.set_font(element['font'],style,element['size'])
|
||||
align = {'L':'L','R':'R','I':'L','D':'R','C':'C','':''}.get(element['align']) # D/I in spanish
|
||||
if isinstance(text, unicode):
|
||||
text = text.encode("latin1","ignore")
|
||||
else:
|
||||
text = str(text)
|
||||
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):
|
||||
pdf.add_page()
|
||||
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']):
|
||||
#print "dib",element['type'], element['name'], element['x1'], element['y1'], element['x2'], element['y2']
|
||||
element = element.copy()
|
||||
element['text'] = self.texts[pg].get(element['name'].lower(), element['text'])
|
||||
if 'rotate' in element:
|
||||
pdf.rotate(element['rotate'], element['x1'], element['y1'])
|
||||
self.handlers[element['type'].upper()](pdf, **element)
|
||||
if 'rotate' in element:
|
||||
pdf.rotate(0)
|
||||
|
||||
return pdf.output(outfile, dest)
|
||||
|
||||
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,
|
||||
*args, **kwargs):
|
||||
if text:
|
||||
if pdf.text_color!=rgb(foreground):
|
||||
pdf.set_text_color(*rgb(foreground))
|
||||
if pdf.fill_color!=rgb(backgroud):
|
||||
pdf.set_fill_color(*rgb(backgroud))
|
||||
|
||||
font = font.strip().lower()
|
||||
if font == 'arial black':
|
||||
font = 'arial'
|
||||
style = ""
|
||||
for tag in 'B', 'I', 'U':
|
||||
if (text.startswith("<%s>" % tag) and text.endswith("</%s>" %tag)):
|
||||
text = text[3:-4]
|
||||
style += tag
|
||||
if bold: style += "B"
|
||||
if italic: style += "I"
|
||||
if underline: style += "U"
|
||||
align = {'L':'L','R':'R','I':'L','D':'R','C':'C','':''}.get(align) # D/I in spanish
|
||||
pdf.set_font(font,style,size)
|
||||
##m_k = 72 / 2.54
|
||||
##h = (size/m_k)
|
||||
pdf.set_xy(x1,y1)
|
||||
pdf.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):
|
||||
if pdf.draw_color!=rgb(foreground):
|
||||
#print "SetDrawColor", hex(foreground)
|
||||
pdf.set_draw_color(*rgb(foreground))
|
||||
#print "SetLineWidth", size
|
||||
pdf.set_line_width(size)
|
||||
pdf.line(x1, y1, x2, y2)
|
||||
|
||||
def rect(self, pdf, x1=0, y1=0, x2=0, y2=0, size=0, foreground=0, backgroud=65535, *args, **kwargs):
|
||||
if pdf.draw_color!=rgb(foreground):
|
||||
pdf.set_draw_color(*rgb(foreground))
|
||||
if pdf.fill_color!=rgb(backgroud):
|
||||
pdf.set_fill_color(*rgb(backgroud))
|
||||
pdf.set_line_width(size)
|
||||
pdf.rect(x1, y1, x2-x1, y2-y1)
|
||||
|
||||
def image(self, pdf, x1=0, y1=0, x2=0, y2=0, text='', *args,**kwargs):
|
||||
pdf.image(text,x1,y1,w=x2-x1,h=y2-y1,type='',link='')
|
||||
|
||||
def barcode(self, pdf, x1=0, y1=0, x2=0, y2=0, text='', font="arial", size=1,
|
||||
foreground=0, *args, **kwargs):
|
||||
if pdf.draw_color!=rgb(foreground):
|
||||
pdf.set_draw_color(*rgb(foreground))
|
||||
font = font.lower().strip()
|
||||
if font == 'interleaved 2of5 nt':
|
||||
pdf.interleaved2of5(text,x1,y1,w=size,h=y2-y1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
# generate sample invoice (according Argentina's regulations)
|
||||
|
||||
import random
|
||||
from decimal import Decimal
|
||||
|
||||
f = Template(format="A4",
|
||||
title="Sample Invoice", author="Sample Company",
|
||||
subject="Sample Customer", keywords="Electronic TAX Invoice")
|
||||
f.parse_csv(infile="invoice.csv", delimiter=";", decimal_sep=",")
|
||||
|
||||
detail = "Lorem ipsum dolor sit amet, consectetur. " * 30
|
||||
items = []
|
||||
for i in range(1, 30):
|
||||
ds = "Sample product %s" % i
|
||||
qty = random.randint(1,10)
|
||||
price = round(random.random()*100,3)
|
||||
code = "%s%s%02d" % (chr(random.randint(65,90)), chr(random.randint(65,90)),i)
|
||||
items.append(dict(code=code, unit='u',
|
||||
qty=qty, price=price,
|
||||
amount=qty*price,
|
||||
ds="%s: %s" % (i,ds)))
|
||||
|
||||
# divide and count lines
|
||||
lines = 0
|
||||
li_items = []
|
||||
for it in items:
|
||||
qty = it['qty']
|
||||
code = it['code']
|
||||
unit = it['unit']
|
||||
for ds in f.split_multicell(it['ds'], 'item_description01'):
|
||||
# add item description line (without price nor amount)
|
||||
li_items.append(dict(code=code, ds=ds, qty=qty, unit=unit, price=None, amount=None))
|
||||
# clean qty and code (show only at first)
|
||||
unit = qty = code = None
|
||||
# set last item line price and amount
|
||||
li_items[-1].update(amount = it['amount'],
|
||||
price = it['price'])
|
||||
|
||||
obs="\n<U>Detail:</U>\n\n" + detail
|
||||
for ds in f.split_multicell(obs, 'item_description01'):
|
||||
li_items.append(dict(code=code, ds=ds, qty=qty, unit=unit, price=None, amount=None))
|
||||
|
||||
# calculate pages:
|
||||
lines = len(li_items)
|
||||
max_lines_per_page = 24
|
||||
pages = lines / (max_lines_per_page - 1)
|
||||
if lines % (max_lines_per_page - 1): pages = pages + 1
|
||||
|
||||
# completo campos y hojas
|
||||
for page in range(1, pages+1):
|
||||
f.add_page()
|
||||
f['page'] = 'Page %s of %s' % (page, pages)
|
||||
if pages>1 and page<pages:
|
||||
s = 'Continues on page %s' % (page+1)
|
||||
else:
|
||||
s = ''
|
||||
f['item_description%02d' % (max_lines_per_page+1)] = s
|
||||
|
||||
f["company_name"] = "Sample Company"
|
||||
f["company_logo"] = "tutorial/logo.png"
|
||||
f["company_header1"] = "Some Address - somewhere -"
|
||||
f["company_header2"] = "http://www.example.com"
|
||||
f["company_footer1"] = "Tax Code ..."
|
||||
f["company_footer2"] = "Tax/VAT ID ..."
|
||||
f['number'] = '0001-00001234'
|
||||
f['issue_date'] = '2010-09-10'
|
||||
f['due_date'] = '2099-09-10'
|
||||
f['customer_name'] = "Sample Client"
|
||||
f['customer_address'] = "Siempreviva 1234"
|
||||
|
||||
# print line item...
|
||||
li = 0
|
||||
k = 0
|
||||
total = Decimal("0.00")
|
||||
for it in li_items:
|
||||
k = k + 1
|
||||
if k > page * (max_lines_per_page - 1):
|
||||
break
|
||||
if it['amount']:
|
||||
total += Decimal("%.6f" % it['amount'])
|
||||
if k > (page - 1) * (max_lines_per_page - 1):
|
||||
li += 1
|
||||
if it['qty'] is not None:
|
||||
f['item_quantity%02d' % li] = it['qty']
|
||||
if it['code'] is not None:
|
||||
f['item_code%02d' % li] = it['code']
|
||||
if it['unit'] is not None:
|
||||
f['item_unit%02d' % li] = it['unit']
|
||||
f['item_description%02d' % li] = it['ds']
|
||||
if it['price'] is not None:
|
||||
f['item_price%02d' % li] = "%0.3f" % it['price']
|
||||
if it['amount'] is not None:
|
||||
f['item_amount%02d' % li] = "%0.2f" % it['amount']
|
||||
|
||||
if pages == page:
|
||||
f['net'] = "%0.2f" % (total/Decimal("1.21"))
|
||||
f['vat'] = "%0.2f" % (total*(1-1/Decimal("1.21")))
|
||||
f['total_label'] = 'Total:'
|
||||
else:
|
||||
f['total_label'] = 'SubTotal:'
|
||||
f['total'] = "%0.2f" % total
|
||||
|
||||
f.render("./invoice.pdf")
|
||||
if sys.platform.startswith("linux"):
|
||||
os.system("evince ./invoice.pdf")
|
||||
else:
|
||||
os.system("./invoice.pdf")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user