Removed reportlab
bzr revid: pinky-6fa6b5bb1cfc5dd54307cd22a31f527d28d61e05
|
@ -1,6 +0,0 @@
|
||||||
global-include *.dtd *.txt *.xml *.yml
|
|
||||||
global-include *.c *.h *.in *.mashed
|
|
||||||
global-include *.gif *.png *.jpg .a85
|
|
||||||
global-include *.AFM *.PFB *.ttf
|
|
||||||
global-include README
|
|
||||||
include changes
|
|
|
@ -1,35 +0,0 @@
|
||||||
#copyright ReportLab Inc. 2000-2001
|
|
||||||
#see license.txt for license details
|
|
||||||
#history http://cvs.sourceforge.net/cgi-bin/cvsweb.cgi/reportlab/README?cvsroot=reportlab
|
|
||||||
#$Header: /tmp/reportlab/reportlab/README,v 1.9 2002/03/05 17:21:44 rgbecker Exp $
|
|
||||||
|
|
||||||
This is the ReportLab PDF library.
|
|
||||||
|
|
||||||
Licensing
|
|
||||||
=========
|
|
||||||
BSD license. See license.txt for details
|
|
||||||
|
|
||||||
Installation
|
|
||||||
============
|
|
||||||
Either unpack reportlab.zip or reportlab.tgz to some directory say
|
|
||||||
d:\ReportLab. If you can, ensure that the line terminator style is
|
|
||||||
correct for your OS (man zip programs have a text mode option eg -a).
|
|
||||||
|
|
||||||
Create a .pth file, say reportlab.pth in your Python
|
|
||||||
home directory. It should have one line:
|
|
||||||
d:/ReportLab.
|
|
||||||
|
|
||||||
Alternatively unpack the archive into a directory which is already on your
|
|
||||||
python path.
|
|
||||||
|
|
||||||
Documentation
|
|
||||||
=============
|
|
||||||
Full documentation is included in PDF format in the docs directory.
|
|
||||||
If you are working with a CVS version, run the script genAll.py
|
|
||||||
in that directory to generate the documentation.
|
|
||||||
|
|
||||||
Acknowledgements and Thanks
|
|
||||||
===========================
|
|
||||||
lib/normalDate.py originally by Jeff Bauer
|
|
||||||
(please let us know of other acknowledgements - we just
|
|
||||||
started this section - 13/2/2002)
|
|
|
@ -1,33 +0,0 @@
|
||||||
#Copyright ReportLab Europe Ltd. 2000-2004
|
|
||||||
#see license.txt for license details
|
|
||||||
#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/__init__.py
|
|
||||||
__version__=''' $Id: __init__.py 2877 2006-05-18 15:11:23Z andy $ '''
|
|
||||||
__doc__="""The Reportlab PDF generation library."""
|
|
||||||
Version = "2.0"
|
|
||||||
|
|
||||||
import sys
|
|
||||||
|
|
||||||
if sys.version_info[0:2] < (2, 3):
|
|
||||||
warning = """The trunk of reportlab requires Python 2.3 or higher.
|
|
||||||
Any older applications should either use released versions beginning
|
|
||||||
with 1.x (e.g. 1.21), or snapshots or checkouts from our 'version1'
|
|
||||||
branch.
|
|
||||||
"""
|
|
||||||
raise ImportError("reportlab needs Python 2.3 or higher", warning)
|
|
||||||
|
|
||||||
def getStory(context):
|
|
||||||
if context.target == 'UserGuide':
|
|
||||||
# parse some local file
|
|
||||||
import os
|
|
||||||
myDir = os.path.split(__file__)[0]
|
|
||||||
import yaml
|
|
||||||
return yaml.parseFile(myDir + os.sep + 'mydocs.yaml')
|
|
||||||
else:
|
|
||||||
# this signals that it should revert to default processing
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def getMonitor():
|
|
||||||
import reportlab.monitor
|
|
||||||
mon = reportlab.monitor.ReportLabToolkitMonitor()
|
|
||||||
return mon
|
|
|
@ -1,105 +0,0 @@
|
||||||
#Copyright ReportLab Europe Ltd. 2000-2004
|
|
||||||
#see license.txt for license details
|
|
||||||
#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/demos/colors/colortest.py
|
|
||||||
import reportlab.pdfgen.canvas
|
|
||||||
from reportlab.lib import colors
|
|
||||||
from reportlab.lib.units import inch
|
|
||||||
|
|
||||||
|
|
||||||
def run():
|
|
||||||
c = reportlab.pdfgen.canvas.Canvas('colortest.pdf')
|
|
||||||
|
|
||||||
#do a test of CMYK interspersed with RGB
|
|
||||||
|
|
||||||
#first do RGB values
|
|
||||||
framePage(c, 'Color Demo - RGB Space and CMYK spaces interspersed' )
|
|
||||||
|
|
||||||
y = 700
|
|
||||||
|
|
||||||
c.setFillColorRGB(0,0,0)
|
|
||||||
c.drawString(100, y, 'cyan')
|
|
||||||
c.setFillColorCMYK(1,0,0,0)
|
|
||||||
c.rect(200, y, 300, 30, fill=1)
|
|
||||||
y = y - 40
|
|
||||||
|
|
||||||
c.setFillColorRGB(0,0,0)
|
|
||||||
c.drawString(100, y, 'red')
|
|
||||||
c.setFillColorRGB(1,0,0)
|
|
||||||
c.rect(200, y, 300, 30, fill=1)
|
|
||||||
y = y - 40
|
|
||||||
|
|
||||||
c.setFillColorRGB(0,0,0)
|
|
||||||
c.drawString(100, y, 'magenta')
|
|
||||||
c.setFillColorCMYK(0,1,0,0)
|
|
||||||
c.rect(200, y, 300, 30, fill=1)
|
|
||||||
y = y - 40
|
|
||||||
|
|
||||||
c.setFillColorRGB(0,0,0)
|
|
||||||
c.drawString(100, y, 'green')
|
|
||||||
c.setFillColorRGB(0,1,0)
|
|
||||||
c.rect(200, y, 300, 30, fill=1)
|
|
||||||
y = y - 40
|
|
||||||
|
|
||||||
c.setFillColorRGB(0,0,0)
|
|
||||||
c.drawString(100, y, 'yellow')
|
|
||||||
c.setFillColorCMYK(0,0,1,0)
|
|
||||||
c.rect(200, y, 300, 30, fill=1)
|
|
||||||
y = y - 40
|
|
||||||
|
|
||||||
c.setFillColorRGB(0,0,0)
|
|
||||||
c.drawString(100, y, 'blue')
|
|
||||||
c.setFillColorRGB(0,0,1)
|
|
||||||
c.rect(200, y, 300, 30, fill=1)
|
|
||||||
y = y - 40
|
|
||||||
|
|
||||||
c.setFillColorRGB(0,0,0)
|
|
||||||
c.drawString(100, y, 'black')
|
|
||||||
c.setFillColorCMYK(0,0,0,1)
|
|
||||||
c.rect(200, y, 300, 30, fill=1)
|
|
||||||
y = y - 40
|
|
||||||
|
|
||||||
|
|
||||||
c.showPage()
|
|
||||||
|
|
||||||
#do all named colors
|
|
||||||
framePage(c, 'Color Demo - RGB Space - page %d' % c.getPageNumber())
|
|
||||||
|
|
||||||
all_colors = reportlab.lib.colors.getAllNamedColors().items()
|
|
||||||
all_colors.sort() # alpha order by name
|
|
||||||
c.setFont('Times-Roman', 12)
|
|
||||||
c.drawString(72,730, 'This shows all the named colors in the HTML standard.')
|
|
||||||
y = 700
|
|
||||||
for (name, color) in all_colors:
|
|
||||||
c.setFillColor(colors.black)
|
|
||||||
c.drawString(100, y, name)
|
|
||||||
c.setFillColor(color)
|
|
||||||
c.rect(200, y-10, 300, 30, fill=1)
|
|
||||||
y = y - 40
|
|
||||||
if y < 100:
|
|
||||||
c.showPage()
|
|
||||||
framePage(c, 'Color Demo - RGB Space - page %d' % c.getPageNumber())
|
|
||||||
y = 700
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
c.save()
|
|
||||||
|
|
||||||
def framePage(canvas, title):
|
|
||||||
canvas.setFont('Times-BoldItalic',20)
|
|
||||||
canvas.drawString(inch, 10.5 * inch, title)
|
|
||||||
|
|
||||||
canvas.setFont('Times-Roman',10)
|
|
||||||
canvas.drawCentredString(4.135 * inch, 0.75 * inch,
|
|
||||||
'Page %d' % canvas.getPageNumber())
|
|
||||||
|
|
||||||
#draw a border
|
|
||||||
canvas.setStrokeColorRGB(1,0,0)
|
|
||||||
canvas.setLineWidth(5)
|
|
||||||
canvas.line(0.8 * inch, inch, 0.8 * inch, 10.75 * inch)
|
|
||||||
#reset carefully afterwards
|
|
||||||
canvas.setLineWidth(1)
|
|
||||||
canvas.setStrokeColorRGB(0,0,0)
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
run()
|
|
|
@ -1,3 +0,0 @@
|
||||||
This is Aaron Watters' first script;
|
|
||||||
it renders his paper for IPC8 into
|
|
||||||
PDF. A fascinating read, as well.
|
|
|
@ -1,903 +0,0 @@
|
||||||
#Copyright ReportLab Europe Ltd. 2000-2004
|
|
||||||
#see license.txt for license details
|
|
||||||
#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/demos/gadflypaper/gfe.py
|
|
||||||
__version__=''' $Id: gfe.py 2385 2004-06-17 15:26:05Z rgbecker $ '''
|
|
||||||
__doc__=''
|
|
||||||
|
|
||||||
#REPORTLAB_TEST_SCRIPT
|
|
||||||
import sys
|
|
||||||
from reportlab.platypus import *
|
|
||||||
from reportlab.lib.styles import getSampleStyleSheet
|
|
||||||
from reportlab.rl_config import defaultPageSize
|
|
||||||
PAGE_HEIGHT=defaultPageSize[1]
|
|
||||||
|
|
||||||
styles = getSampleStyleSheet()
|
|
||||||
|
|
||||||
Title = "Integrating Diverse Data Sources with Gadfly 2"
|
|
||||||
|
|
||||||
Author = "Aaron Watters"
|
|
||||||
|
|
||||||
URL = "http://www.chordate.com/"
|
|
||||||
|
|
||||||
email = "arw@ifu.net"
|
|
||||||
|
|
||||||
Abstract = """This paper describes the primative methods underlying the implementation
|
|
||||||
of SQL query evaluation in Gadfly 2, a database management system implemented
|
|
||||||
in Python [Van Rossum]. The major design goals behind
|
|
||||||
the architecture described here are to simplify the implementation
|
|
||||||
and to permit flexible and efficient extensions to the gadfly
|
|
||||||
engine. Using this architecture and its interfaces programmers
|
|
||||||
can add functionality to the engine such as alternative disk based
|
|
||||||
indexed table implementations, dynamic interfaces to remote data
|
|
||||||
bases or or other data sources, and user defined computations."""
|
|
||||||
|
|
||||||
from reportlab.lib.units import inch
|
|
||||||
|
|
||||||
pageinfo = "%s / %s / %s" % (Author, email, Title)
|
|
||||||
|
|
||||||
def myFirstPage(canvas, doc):
|
|
||||||
canvas.saveState()
|
|
||||||
#canvas.setStrokeColorRGB(1,0,0)
|
|
||||||
#canvas.setLineWidth(5)
|
|
||||||
#canvas.line(66,72,66,PAGE_HEIGHT-72)
|
|
||||||
canvas.setFont('Times-Bold',16)
|
|
||||||
canvas.drawString(108, PAGE_HEIGHT-108, Title)
|
|
||||||
canvas.setFont('Times-Roman',9)
|
|
||||||
canvas.drawString(inch, 0.75 * inch, "First Page / %s" % pageinfo)
|
|
||||||
canvas.restoreState()
|
|
||||||
|
|
||||||
def myLaterPages(canvas, doc):
|
|
||||||
#canvas.drawImage("snkanim.gif", 36, 36)
|
|
||||||
canvas.saveState()
|
|
||||||
#canvas.setStrokeColorRGB(1,0,0)
|
|
||||||
#canvas.setLineWidth(5)
|
|
||||||
#canvas.line(66,72,66,PAGE_HEIGHT-72)
|
|
||||||
canvas.setFont('Times-Roman',9)
|
|
||||||
canvas.drawString(inch, 0.75 * inch, "Page %d %s" % (doc.page, pageinfo))
|
|
||||||
canvas.restoreState()
|
|
||||||
|
|
||||||
def go():
|
|
||||||
Elements.insert(0,Spacer(0,inch))
|
|
||||||
doc = SimpleDocTemplate('gfe.pdf')
|
|
||||||
doc.build(Elements,onFirstPage=myFirstPage, onLaterPages=myLaterPages)
|
|
||||||
|
|
||||||
Elements = []
|
|
||||||
|
|
||||||
HeaderStyle = styles["Heading1"] # XXXX
|
|
||||||
|
|
||||||
def header(txt, style=HeaderStyle, klass=Paragraph, sep=0.3):
|
|
||||||
s = Spacer(0.2*inch, sep*inch)
|
|
||||||
Elements.append(s)
|
|
||||||
para = klass(txt, style)
|
|
||||||
Elements.append(para)
|
|
||||||
|
|
||||||
ParaStyle = styles["Normal"]
|
|
||||||
|
|
||||||
def p(txt):
|
|
||||||
return header(txt, style=ParaStyle, sep=0.1)
|
|
||||||
|
|
||||||
#pre = p # XXX
|
|
||||||
|
|
||||||
PreStyle = styles["Code"]
|
|
||||||
|
|
||||||
def pre(txt):
|
|
||||||
s = Spacer(0.1*inch, 0.1*inch)
|
|
||||||
Elements.append(s)
|
|
||||||
p = Preformatted(txt, PreStyle)
|
|
||||||
Elements.append(p)
|
|
||||||
|
|
||||||
#header(Title, sep=0.1. style=ParaStyle)
|
|
||||||
header(Author, sep=0.1, style=ParaStyle)
|
|
||||||
header(URL, sep=0.1, style=ParaStyle)
|
|
||||||
header(email, sep=0.1, style=ParaStyle)
|
|
||||||
header("ABSTRACT")
|
|
||||||
p(Abstract)
|
|
||||||
|
|
||||||
header("Backgrounder")
|
|
||||||
|
|
||||||
p("""\
|
|
||||||
The term "database" usually refers to a persistent
|
|
||||||
collection of data. Data is persistent if it continues
|
|
||||||
to exist whether or not it is associated with a running
|
|
||||||
process on the computer, or even if the computer is
|
|
||||||
shut down and restarted at some future time. Database
|
|
||||||
management systems provide support for constructing databases,
|
|
||||||
maintaining databases, and extracting information from databases.""")
|
|
||||||
p("""\
|
|
||||||
Relational databases manipulate and store persistent
|
|
||||||
table structures called relations, such as the following
|
|
||||||
three tables""")
|
|
||||||
|
|
||||||
pre("""\
|
|
||||||
-- drinkers who frequent bars (this is a comment)
|
|
||||||
select * from frequents
|
|
||||||
|
|
||||||
DRINKER | PERWEEK | BAR
|
|
||||||
============================
|
|
||||||
adam | 1 | lolas
|
|
||||||
woody | 5 | cheers
|
|
||||||
sam | 5 | cheers
|
|
||||||
norm | 3 | cheers
|
|
||||||
wilt | 2 | joes
|
|
||||||
norm | 1 | joes
|
|
||||||
lola | 6 | lolas
|
|
||||||
norm | 2 | lolas
|
|
||||||
woody | 1 | lolas
|
|
||||||
pierre | 0 | frankies
|
|
||||||
)
|
|
||||||
""")
|
|
||||||
pre("""\
|
|
||||||
-- drinkers who like beers
|
|
||||||
select * from likes
|
|
||||||
|
|
||||||
DRINKER | PERDAY | BEER
|
|
||||||
===============================
|
|
||||||
adam | 2 | bud
|
|
||||||
wilt | 1 | rollingrock
|
|
||||||
sam | 2 | bud
|
|
||||||
norm | 3 | rollingrock
|
|
||||||
norm | 2 | bud
|
|
||||||
nan | 1 | sierranevada
|
|
||||||
woody | 2 | pabst
|
|
||||||
lola | 5 | mickies
|
|
||||||
|
|
||||||
""")
|
|
||||||
pre("""\
|
|
||||||
-- beers served from bars
|
|
||||||
select * from serves
|
|
||||||
|
|
||||||
BAR | QUANTITY | BEER
|
|
||||||
=================================
|
|
||||||
cheers | 500 | bud
|
|
||||||
cheers | 255 | samadams
|
|
||||||
joes | 217 | bud
|
|
||||||
joes | 13 | samadams
|
|
||||||
joes | 2222 | mickies
|
|
||||||
lolas | 1515 | mickies
|
|
||||||
lolas | 333 | pabst
|
|
||||||
winkos | 432 | rollingrock
|
|
||||||
frankies | 5 | snafu
|
|
||||||
""")
|
|
||||||
p("""
|
|
||||||
The relational model for database structures makes
|
|
||||||
the simplifying assumption that all data in a database
|
|
||||||
can be represented in simple table structures
|
|
||||||
such as these. Although this assumption seems extreme
|
|
||||||
it provides a good foundation for defining solid and
|
|
||||||
well defined database management systems and some
|
|
||||||
of the most successful software companies in the
|
|
||||||
world, such as Oracle, Sybase, IBM, and Microsoft,
|
|
||||||
have marketed database management systems based on
|
|
||||||
the relational model quite successfully.
|
|
||||||
""")
|
|
||||||
p("""
|
|
||||||
SQL stands for Structured Query Language.
|
|
||||||
The SQL language defines industry standard
|
|
||||||
mechanisms for creating, querying, and modified
|
|
||||||
relational tables. Several years ago SQL was one
|
|
||||||
of many Relational Database Management System
|
|
||||||
(RDBMS) query languages in use, and many would
|
|
||||||
argue not the best on. Now, largely due
|
|
||||||
to standardization efforts and the
|
|
||||||
backing of IBM, SQL is THE standard way to talk
|
|
||||||
to database systems.
|
|
||||||
""")
|
|
||||||
p("""
|
|
||||||
There are many advantages SQL offers over other
|
|
||||||
database query languages and alternative paradigms
|
|
||||||
at this time (please see [O'Neill] or [Korth and Silberschatz]
|
|
||||||
for more extensive discussions and comparisons between the
|
|
||||||
SQL/relational approach and others.)
|
|
||||||
""")
|
|
||||||
p("""
|
|
||||||
The chief advantage over all contenders at this time
|
|
||||||
is that SQL and the relational model are now widely
|
|
||||||
used as interfaces and back end data stores to many
|
|
||||||
different products with different performance characteristics,
|
|
||||||
user interfaces, and other qualities: Oracle, Sybase,
|
|
||||||
Ingres, SQL Server, Access, Outlook,
|
|
||||||
Excel, IBM DB2, Paradox, MySQL, MSQL, POSTgres, and many
|
|
||||||
others. For this reason a program designed to use
|
|
||||||
an SQL database as its data storage mechanism can
|
|
||||||
easily be ported from one SQL data manager to another,
|
|
||||||
possibly on different platforms. In fact the same
|
|
||||||
program can seamlessly use several backends and/or
|
|
||||||
import/export data between different data base platforms
|
|
||||||
with trivial ease.
|
|
||||||
No other paradigm offers such flexibility at the moment.
|
|
||||||
""")
|
|
||||||
p("""
|
|
||||||
Another advantage which is not as immediately
|
|
||||||
obvious is that the relational model and the SQL
|
|
||||||
query language are easily understood by semi-technical
|
|
||||||
and non-technical professionals, such as business
|
|
||||||
people and accountants. Human resources managers
|
|
||||||
who would be terrified by an object model diagram
|
|
||||||
or a snippet of code that resembles a conventional
|
|
||||||
programming language will frequently feel quite at
|
|
||||||
ease with a relational model which resembles the
|
|
||||||
sort of tabular data they deal with on paper in
|
|
||||||
reports and forms on a daily basis. With a little training the
|
|
||||||
same HR managers may be able to translate the request
|
|
||||||
"Who are the drinkers who like bud and frequent cheers?"
|
|
||||||
into the SQL query
|
|
||||||
""")
|
|
||||||
pre("""
|
|
||||||
select drinker
|
|
||||||
from frequents
|
|
||||||
where bar='cheers'
|
|
||||||
and drinker in (
|
|
||||||
select drinker
|
|
||||||
from likes
|
|
||||||
where beer='bud')
|
|
||||||
""")
|
|
||||||
p("""
|
|
||||||
(or at least they have some hope of understanding
|
|
||||||
the query once it is written by a technical person
|
|
||||||
or generated by a GUI interface tool). Thus the use
|
|
||||||
of SQL and the relational model enables communication
|
|
||||||
between different communities which must understand
|
|
||||||
and interact with stored information. In contrast
|
|
||||||
many other approaches cannot be understood easily
|
|
||||||
by people without extensive programming experience.
|
|
||||||
""")
|
|
||||||
p("""
|
|
||||||
Furthermore the declarative nature of SQL
|
|
||||||
lends itself to automatic query optimization,
|
|
||||||
and engines such as Gadfly can automatically translate a user query
|
|
||||||
into an optimized query plan which takes
|
|
||||||
advantage of available indices and other data characteristics.
|
|
||||||
In contrast more navigational techniques require the application
|
|
||||||
program itself to optimize the accesses to the database and
|
|
||||||
explicitly make use of indices.
|
|
||||||
""")
|
|
||||||
|
|
||||||
# HACK
|
|
||||||
Elements.append(PageBreak())
|
|
||||||
|
|
||||||
p("""
|
|
||||||
While it must be admitted that there are application
|
|
||||||
domains such as computer aided engineering design where
|
|
||||||
the relational model is unnatural, it is also important
|
|
||||||
to recognize that for many application domains (such
|
|
||||||
as scheduling, accounting, inventory, finance, personal
|
|
||||||
information management, electronic mail) the relational
|
|
||||||
model is a very natural fit and the SQL query language
|
|
||||||
make most accesses to the underlying data (even sophisticated
|
|
||||||
ones) straightforward. """)
|
|
||||||
|
|
||||||
p("""For an example of a moderately
|
|
||||||
sophisticated query using the tables given above,
|
|
||||||
the following query lists the drinkers who frequent lolas bar
|
|
||||||
and like at least two beers not served by lolas
|
|
||||||
""")
|
|
||||||
|
|
||||||
if 0:
|
|
||||||
go()
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
pre("""
|
|
||||||
select f.drinker
|
|
||||||
from frequents f, likes l
|
|
||||||
where f.drinker=l.drinker and f.bar='lolas'
|
|
||||||
and l.beer not in
|
|
||||||
(select beer from serves where bar='lolas')
|
|
||||||
group by f.drinker
|
|
||||||
having count(distinct beer)>=2
|
|
||||||
""")
|
|
||||||
p("""
|
|
||||||
yielding the result
|
|
||||||
""")
|
|
||||||
pre("""
|
|
||||||
DRINKER
|
|
||||||
=======
|
|
||||||
norm
|
|
||||||
""")
|
|
||||||
p("""
|
|
||||||
Experience shows that queries of this sort are actually
|
|
||||||
quite common in many applications, and are often much more
|
|
||||||
difficult to formulate using some navigational database
|
|
||||||
organizations, such as some "object oriented" database
|
|
||||||
paradigms.
|
|
||||||
""")
|
|
||||||
p("""
|
|
||||||
Certainly,
|
|
||||||
SQL does not provide all you need to interact with
|
|
||||||
databases -- in order to do "real work" with SQL you
|
|
||||||
need to use SQL and at least one other language
|
|
||||||
(such as C, Pascal, C++, Perl, Python, TCL, Visual Basic
|
|
||||||
or others) to do work (such as readable formatting a report
|
|
||||||
from raw data) that SQL was not designed to do.
|
|
||||||
""")
|
|
||||||
|
|
||||||
header("Why Gadfly 1?")
|
|
||||||
|
|
||||||
p("""Gadfly 1.0 is an SQL based relational database implementation
|
|
||||||
implemented entirely in the Python programming language, with
|
|
||||||
optional fast data structure accellerators implemented in the
|
|
||||||
C programming language. Gadfly is relatively small, highly portable,
|
|
||||||
very easy to use (especially for programmers with previous experience
|
|
||||||
with SQL databases such as MS Access or Oracle), and reasonably
|
|
||||||
fast (especially when the kjbuckets C accellerators are used).
|
|
||||||
For moderate sized problems Gadfly offers a fairly complete
|
|
||||||
set of features such as transaction semantics, failure recovery,
|
|
||||||
and a TCP/IP based client/server mode (Please see [Gadfly] for
|
|
||||||
detailed discussion).""")
|
|
||||||
|
|
||||||
|
|
||||||
header("Why Gadfly 2?")
|
|
||||||
|
|
||||||
p("""Gadfly 1.0 also has significant limitations. An active Gadfly
|
|
||||||
1.0 database keeps all data in (virtual) memory, and hence a Gadfly
|
|
||||||
1.0 database is limited in size to available virtual memory. Important
|
|
||||||
features such as date/time/interval operations, regular expression
|
|
||||||
matching and other standard SQL features are not implemented in
|
|
||||||
Gadfly 1.0. The optimizer and the query evaluator perform optimizations
|
|
||||||
using properties of the equality predicate but do not optimize
|
|
||||||
using properties of inequalities such as BETWEEN or less-than.
|
|
||||||
It is possible to add "extension views" to a Gadfly
|
|
||||||
1.0 database, but the mechanism is somewhat clumsy and indices
|
|
||||||
over extension views are not well supported. The features of Gadfly
|
|
||||||
2.0 discussed here attempt to address these deficiencies by providing
|
|
||||||
a uniform extension model that permits addition of alternate table,
|
|
||||||
function, and predicate implementations.""")
|
|
||||||
|
|
||||||
p("""Other deficiencies, such as missing constructs like "ALTER
|
|
||||||
TABLE" and the lack of outer joins and NULL values are not
|
|
||||||
addressed here, although they may be addressed in Gadfly 2.0 or
|
|
||||||
a later release. This paper also does not intend to explain
|
|
||||||
the complete operations of the internals; it is intended to provide
|
|
||||||
at least enough information to understand the basic mechanisms
|
|
||||||
for extending gadfly.""")
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
p("""Some concepts and definitions provided next help with the description
|
|
||||||
of the gadfly interfaces. [Note: due to the terseness of this
|
|
||||||
format the ensuing is not a highly formal presentation, but attempts
|
|
||||||
to approach precision where precision is important.]""")
|
|
||||||
|
|
||||||
header("The semilattice of substitutions")
|
|
||||||
|
|
||||||
p("""Underlying the gadfly implementation are the basic concepts
|
|
||||||
associated with substitutions. A substitution is a mapping
|
|
||||||
of attribute names to values (implemented in gadfly using kjbuckets.kjDict
|
|
||||||
objects). Here an attribute refers to some sort of "descriptive
|
|
||||||
variable", such as NAME and a value is an assignment for that variable,
|
|
||||||
like "Dave Ascher". In Gadfly a table is implemented as a sequence
|
|
||||||
of substitutions, and substitutions are used in many other ways as well.
|
|
||||||
""")
|
|
||||||
p("""
|
|
||||||
For example consider the substitutions""")
|
|
||||||
|
|
||||||
pre("""
|
|
||||||
A = [DRINKER=>'sam']
|
|
||||||
B = [DRINKER=>'sam', BAR=>'cheers']
|
|
||||||
C = [DRINKER=>'woody', BEER=>'bud']
|
|
||||||
D = [DRINKER=>'sam', BEER=>'mickies']
|
|
||||||
E = [DRINKER=>'sam', BAR=>'cheers', BEER=>'mickies']
|
|
||||||
F = [DRINKER=>'sam', BEER=>'mickies']
|
|
||||||
G = [BEER=>'bud', BAR=>'lolas']
|
|
||||||
H = [] # the empty substitution
|
|
||||||
I = [BAR=>'cheers', CAPACITY=>300]""")
|
|
||||||
|
|
||||||
p("""A trivial but important observation is that since substitutions
|
|
||||||
are mappings, no attribute can assume more than one value in a
|
|
||||||
substitution. In the operations described below whenever an operator
|
|
||||||
"tries" to assign more than one value to an attribute
|
|
||||||
the operator yields an "overdefined" or "inconsistent"
|
|
||||||
result.""")
|
|
||||||
|
|
||||||
header("Information Semi-order:")
|
|
||||||
|
|
||||||
p("""Substitution B is said to be
|
|
||||||
more informative than A because B agrees with all assignments
|
|
||||||
in A (in addition to providing more information as well). Similarly
|
|
||||||
we say that E is more informative than A, B, D, F. and H but E
|
|
||||||
is not more informative than the others since, for example G disagrees
|
|
||||||
with E on the value assigned to the BEER attribute and I provides
|
|
||||||
additional CAPACITY information not provided in E.""")
|
|
||||||
|
|
||||||
header("Joins and Inconsistency:")
|
|
||||||
|
|
||||||
p("""A join of two substitutions
|
|
||||||
X and Y is the least informative substitution Z such that Z is
|
|
||||||
more informative (or equally informative) than both X and Y. For
|
|
||||||
example B is the join of B with A, E is the join of B with D and""")
|
|
||||||
|
|
||||||
pre("""
|
|
||||||
E join I =
|
|
||||||
[DRINKER=>'sam', BAR=>'cheers', BEER=>'mickies', CAPACITY=>300]""")
|
|
||||||
|
|
||||||
p("""For any two substitutions either (1) they disagree on the value
|
|
||||||
assigned to some attribute and have no join or (2) they agree
|
|
||||||
on all common attributes (if there are any) and their join is
|
|
||||||
the union of all (name, value) assignments in both substitutions.
|
|
||||||
Written in terms of kjbucket.kjDict operations two kjDicts X and
|
|
||||||
Y have a join Z = (X+Y) if and only if Z.Clean() is not None.
|
|
||||||
Two substitutions that have no join are said to be inconsistent.
|
|
||||||
For example I and G are inconsistent since they disagree on
|
|
||||||
the value assigned to the BAR attribute and therefore have no
|
|
||||||
join. The algebra of substitutions with joins technically defines
|
|
||||||
an abstract algebraic structure called a semilattice.""")
|
|
||||||
|
|
||||||
header("Name space remapping")
|
|
||||||
|
|
||||||
p("""Another primitive operation over substitutions is the remap
|
|
||||||
operation S2 = S.remap(R) where S is a substitution and R is a
|
|
||||||
graph of attribute names and S2 is a substitution. This operation
|
|
||||||
is defined to produce the substitution S2 such that""")
|
|
||||||
|
|
||||||
pre("""
|
|
||||||
Name=>Value in S2 if and only if
|
|
||||||
Name1=>Value in S and Name<=Name1 in R
|
|
||||||
""")
|
|
||||||
|
|
||||||
p("""or if there is no such substitution S2 the remap value is said
|
|
||||||
to be overdefined.""")
|
|
||||||
|
|
||||||
p("""For example the remap operation may be used to eliminate attributes
|
|
||||||
from a substitution. For example""")
|
|
||||||
|
|
||||||
pre("""
|
|
||||||
E.remap([DRINKER<=DRINKER, BAR<=BAR])
|
|
||||||
= [DRINKER=>'sam', BAR=>'cheers']
|
|
||||||
""")
|
|
||||||
|
|
||||||
p("""Illustrating that remapping using the [DRINKER<=DRINKER,
|
|
||||||
BAR<=BAR] graph eliminates all attributes except DRINKER and
|
|
||||||
BAR, such as BEER. More generally remap can be used in this way
|
|
||||||
to implement the classical relational projection operation. (See [Korth and Silberschatz]
|
|
||||||
for a detailed discussion of the projection operator and other relational
|
|
||||||
algebra operators such as selection, rename, difference and joins.)""")
|
|
||||||
|
|
||||||
p("""The remap operation can also be used to implement "selection
|
|
||||||
on attribute equality". For example if we are interested
|
|
||||||
in the employee names of employees who are their own bosses we
|
|
||||||
can use the remapping graph""")
|
|
||||||
|
|
||||||
pre("""
|
|
||||||
R1 = [NAME<=NAME, NAME<=BOSS]
|
|
||||||
""")
|
|
||||||
|
|
||||||
p("""and reject substitutions where remapping using R1 is overdefined.
|
|
||||||
For example""")
|
|
||||||
|
|
||||||
pre("""
|
|
||||||
S1 = [NAME=>'joe', BOSS=>'joe']
|
|
||||||
S1.remap(R1) = [NAME=>'joe']
|
|
||||||
S2 = [NAME=>'fred', BOSS=>'joe']
|
|
||||||
S2.remap(R1) is overdefined.
|
|
||||||
""")
|
|
||||||
|
|
||||||
p("""The last remap is overdefined because the NAME attribute cannot
|
|
||||||
assume both the values 'fred' and 'joe' in a substitution.""")
|
|
||||||
|
|
||||||
p("""Furthermore, of course, the remap operation can be used to
|
|
||||||
"rename attributes" or "copy attribute values"
|
|
||||||
in substitutions. Note below that the missing attribute CAPACITY
|
|
||||||
in B is effectively ignored in the remapping operation.""")
|
|
||||||
|
|
||||||
pre("""
|
|
||||||
B.remap([D<=DRINKER, B<=BAR, B2<=BAR, C<=CAPACITY])
|
|
||||||
= [D=>'sam', B=>'cheers', B2=>'cheers']
|
|
||||||
""")
|
|
||||||
|
|
||||||
p("""More interestingly, a single remap operation can be used to
|
|
||||||
perform a combination of renaming, projection, value copying,
|
|
||||||
and attribute equality selection as one operation. In kjbuckets the remapper
|
|
||||||
graph is implemented using a kjbuckets.kjGraph and the remap operation
|
|
||||||
is an intrinsic method of kjbuckets.kjDict objects.""")
|
|
||||||
|
|
||||||
header("Generalized Table Joins and the Evaluator Mainloop""")
|
|
||||||
|
|
||||||
p("""Strictly speaking the Gadfly 2.0 query evaluator only uses
|
|
||||||
the join and remap operations as its "basic assembly language"
|
|
||||||
-- all other computations, including inequality comparisons and
|
|
||||||
arithmetic, are implemented externally to the evaluator as "generalized
|
|
||||||
table joins." """)
|
|
||||||
|
|
||||||
p("""A table is a sequence of substitutions (which in keeping with
|
|
||||||
SQL semantics may contain redundant entries). The join between
|
|
||||||
two tables T1 and T2 is the sequence of all possible defined joins
|
|
||||||
between pairs of elements from the two tables. Procedurally we
|
|
||||||
might compute the join as""")
|
|
||||||
|
|
||||||
pre("""
|
|
||||||
T1JoinT2 = empty
|
|
||||||
for t1 in T1:
|
|
||||||
for t2 in T2:
|
|
||||||
if t1 join t2 is defined:
|
|
||||||
add t1 join t2 to T1joinT2""")
|
|
||||||
|
|
||||||
p("""In general circumstances this intuitive implementation is a
|
|
||||||
very inefficient way to compute the join, and Gadfly almost always
|
|
||||||
uses other methods, particularly since, as described below, a
|
|
||||||
"generalized table" can have an "infinite"
|
|
||||||
number of entries.""")
|
|
||||||
|
|
||||||
p("""For an example of a table join consider the EMPLOYEES table
|
|
||||||
containing""")
|
|
||||||
|
|
||||||
pre("""
|
|
||||||
[NAME=>'john', JOB=>'executive']
|
|
||||||
[NAME=>'sue', JOB=>'programmer']
|
|
||||||
[NAME=>'eric', JOB=>'peon']
|
|
||||||
[NAME=>'bill', JOB=>'peon']
|
|
||||||
""")
|
|
||||||
|
|
||||||
p("""and the ACTIVITIES table containing""")
|
|
||||||
|
|
||||||
pre("""
|
|
||||||
[JOB=>'peon', DOES=>'windows']
|
|
||||||
[JOB=>'peon', DOES=>'floors']
|
|
||||||
[JOB=>'programmer', DOES=>'coding']
|
|
||||||
[JOB=>'secretary', DOES=>'phone']""")
|
|
||||||
|
|
||||||
p("""then the join between EMPLOYEES and ACTIVITIES must containining""")
|
|
||||||
|
|
||||||
pre("""
|
|
||||||
[NAME=>'sue', JOB=>'programmer', DOES=>'coding']
|
|
||||||
[NAME=>'eric', JOB=>'peon', DOES=>'windows']
|
|
||||||
[NAME=>'bill', JOB=>'peon', DOES=>'windows']
|
|
||||||
[NAME=>'eric', JOB=>'peon', DOES=>'floors']
|
|
||||||
[NAME=>'bill', JOB=>'peon', DOES=>'floors']""")
|
|
||||||
|
|
||||||
p("""A compiled gadfly subquery ultimately appears to the evaluator
|
|
||||||
as a sequence of generalized tables that must be joined (in combination
|
|
||||||
with certain remapping operations that are beyond the scope of
|
|
||||||
this discussion). The Gadfly mainloop proceeds following the very
|
|
||||||
loose pseudocode:""")
|
|
||||||
|
|
||||||
pre("""
|
|
||||||
Subs = [ [] ] # the unary sequence containing "true"
|
|
||||||
While some table hasn't been chosen yet:
|
|
||||||
Choose an unchosen table with the least cost join estimate.
|
|
||||||
Subs = Subs joined with the chosen table
|
|
||||||
return Subs""")
|
|
||||||
|
|
||||||
p("""[Note that it is a property of the join operation that the
|
|
||||||
order in which the joins are carried out will not affect the result,
|
|
||||||
so the greedy strategy of evaluating the "cheapest join next"
|
|
||||||
will not effect the result. Also note that the treatment of logical
|
|
||||||
OR and NOT as well as EXIST, IN, UNION, and aggregation and so
|
|
||||||
forth are not discussed here, even though they do fit into this
|
|
||||||
approach.]""")
|
|
||||||
|
|
||||||
p("""The actual implementation is a bit more complex than this,
|
|
||||||
but the above outline may provide some useful intuition. The "cost
|
|
||||||
estimation" step and the implementation of the join operation
|
|
||||||
itself are left up to the generalized table object implementation.
|
|
||||||
A table implementation has the ability to give an "infinite"
|
|
||||||
cost estimate, which essentially means "don't join me in
|
|
||||||
yet under any circumstances." """)
|
|
||||||
|
|
||||||
header("Implementing Functions")
|
|
||||||
|
|
||||||
p("""As mentioned above operations such as arithmetic are implemented
|
|
||||||
using generalized tables. For example the arithmetic Add operation
|
|
||||||
is implemented in Gadfly internally as an "infinite generalized
|
|
||||||
table" containing all possible substitutions""")
|
|
||||||
|
|
||||||
pre("""
|
|
||||||
ARG0=>a, ARG1=>b, RESULT=>a+b]
|
|
||||||
""")
|
|
||||||
|
|
||||||
p("""Where a and b are all possible values which can be summed.
|
|
||||||
Clearly, it is not possible to enumerate this table, but given
|
|
||||||
a sequence of substitutions with defined values for ARG0 and ARG1
|
|
||||||
such as""")
|
|
||||||
|
|
||||||
pre("""
|
|
||||||
[ARG0=>1, ARG1=-4]
|
|
||||||
[ARG0=>2.6, ARG1=50]
|
|
||||||
[ARG0=>99, ARG1=1]
|
|
||||||
""")
|
|
||||||
|
|
||||||
p("""it is possible to implement a "join operation" against
|
|
||||||
this sequence that performs the same augmentation as a join with
|
|
||||||
the infinite table defined above:""")
|
|
||||||
|
|
||||||
pre("""
|
|
||||||
[ARG0=>1, ARG1=-4, RESULT=-3]
|
|
||||||
[ARG0=>2.6, ARG1=50, RESULT=52.6]
|
|
||||||
[ARG0=>99, ARG1=1, RESULT=100]
|
|
||||||
""")
|
|
||||||
|
|
||||||
p("""Furthermore by giving an "infinite estimate" for
|
|
||||||
all attempts to evaluate the join where ARG0 and ARG1 are not
|
|
||||||
available the generalized table implementation for the addition
|
|
||||||
operation can refuse to compute an "infinite join." """)
|
|
||||||
|
|
||||||
p("""More generally all functions f(a,b,c,d) are represented in
|
|
||||||
gadfly as generalized tables containing all possible relevant
|
|
||||||
entries""")
|
|
||||||
|
|
||||||
pre("""
|
|
||||||
[ARG0=>a, ARG1=>b, ARG2=>c, ARG3=>d, RESULT=>f(a,b,c,d)]""")
|
|
||||||
|
|
||||||
p("""and the join estimation function refuses all attempts to perform
|
|
||||||
a join unless all the arguments are provided by the input substitution
|
|
||||||
sequence.""")
|
|
||||||
|
|
||||||
header("Implementing Predicates")
|
|
||||||
|
|
||||||
p("""Similarly to functions, predicates such as less-than and BETWEEN
|
|
||||||
and LIKE are implemented using the generalized table mechanism.
|
|
||||||
For example the "x BETWEEN y AND z" predicate is implemented
|
|
||||||
as a generalized table "containing" all possible""")
|
|
||||||
|
|
||||||
pre("""
|
|
||||||
[ARG0=>a, ARG1=>b, ARG2=>c]""")
|
|
||||||
|
|
||||||
p("""where b<a<c. Furthermore joins with this table are not
|
|
||||||
permitted unless all three arguments are available in the sequence
|
|
||||||
of input substitutions.""")
|
|
||||||
|
|
||||||
header("Some Gadfly extension interfaces")
|
|
||||||
|
|
||||||
p("""A gadfly database engine may be extended with user defined
|
|
||||||
functions, predicates, and alternative table and index implementations.
|
|
||||||
This section snapshots several Gadfly 2.0 interfaces, currently under
|
|
||||||
development and likely to change before the package is released.""")
|
|
||||||
|
|
||||||
p("""The basic interface for adding functions and predicates (logical tests)
|
|
||||||
to a gadfly engine are relatively straightforward. For example to add the
|
|
||||||
ability to match a regular expression within a gadfly query use the
|
|
||||||
following implementation.""")
|
|
||||||
|
|
||||||
pre("""
|
|
||||||
from re import match
|
|
||||||
|
|
||||||
def addrematch(gadflyinstance):
|
|
||||||
gadflyinstance.add_predicate("rematch", match)
|
|
||||||
""")
|
|
||||||
p("""
|
|
||||||
Then upon connecting to the database execute
|
|
||||||
""")
|
|
||||||
pre("""
|
|
||||||
g = gadfly(...)
|
|
||||||
...
|
|
||||||
addrematch(g)
|
|
||||||
""")
|
|
||||||
p("""
|
|
||||||
In this case the "semijoin operation" associated with the new predicate
|
|
||||||
"rematch" is automatically generated, and after the add_predicate
|
|
||||||
binding operation the gadfly instance supports queries such as""")
|
|
||||||
pre("""
|
|
||||||
select drinker, beer
|
|
||||||
from likes
|
|
||||||
where rematch('b*', beer) and drinker not in
|
|
||||||
(select drinker from frequents where rematch('c*', bar))
|
|
||||||
""")
|
|
||||||
p("""
|
|
||||||
By embedding the "rematch" operation within the query the SQL
|
|
||||||
engine can do "more work" for the programmer and reduce or eliminate the
|
|
||||||
need to process the query result externally to the engine.
|
|
||||||
""")
|
|
||||||
p("""
|
|
||||||
In a similar manner functions may be added to a gadfly instance,""")
|
|
||||||
pre("""
|
|
||||||
def modulo(x,y):
|
|
||||||
return x % y
|
|
||||||
|
|
||||||
def addmodulo(gadflyinstance):
|
|
||||||
gadflyinstance.add_function("modulo", modulo)
|
|
||||||
|
|
||||||
...
|
|
||||||
g = gadfly(...)
|
|
||||||
...
|
|
||||||
addmodulo(g)
|
|
||||||
""")
|
|
||||||
p("""
|
|
||||||
Then after the binding the modulo function can be used whereever
|
|
||||||
an SQL expression can occur.
|
|
||||||
""")
|
|
||||||
p("""
|
|
||||||
Adding alternative table implementations to a Gadfly instance
|
|
||||||
is more interesting and more difficult. An "extension table" implementation
|
|
||||||
must conform to the following interface:""")
|
|
||||||
|
|
||||||
pre("""
|
|
||||||
# get the kjbuckets.kjSet set of attribute names for this table
|
|
||||||
names = table.attributes()
|
|
||||||
|
|
||||||
# estimate the difficulty of evaluating a join given known attributes
|
|
||||||
# return None for "impossible" or n>=0 otherwise with larger values
|
|
||||||
# indicating greater difficulty or expense
|
|
||||||
estimate = table.estimate(known_attributes)
|
|
||||||
|
|
||||||
# return the join of the rows of the table with
|
|
||||||
# the list of kjbuckets.kjDict mappings as a list of mappings.
|
|
||||||
resultmappings = table.join(listofmappings)
|
|
||||||
""")
|
|
||||||
p("""
|
|
||||||
In this case add the table to a gadfly instance using""")
|
|
||||||
pre("""
|
|
||||||
gadflyinstance.add_table("table_name", table)
|
|
||||||
""")
|
|
||||||
p("""
|
|
||||||
For example to add a table which automatically queries filenames
|
|
||||||
in the filesystems of the host computer a gadfly instance could
|
|
||||||
be augmented with a GLOB table implemented using the standard
|
|
||||||
library function glob.glob as follows:""")
|
|
||||||
pre("""
|
|
||||||
import kjbuckets
|
|
||||||
|
|
||||||
class GlobTable:
|
|
||||||
def __init__(self): pass
|
|
||||||
|
|
||||||
def attributes(self):
|
|
||||||
return kjbuckets.kjSet("PATTERN", "NAME")
|
|
||||||
|
|
||||||
def estimate(self, known_attributes):
|
|
||||||
if known_attributes.member("PATTERN"):
|
|
||||||
return 66 # join not too difficult
|
|
||||||
else:
|
|
||||||
return None # join is impossible (must have PATTERN)
|
|
||||||
|
|
||||||
def join(self, listofmappings):
|
|
||||||
from glob import glob
|
|
||||||
result = []
|
|
||||||
for m in listofmappings:
|
|
||||||
pattern = m["PATTERN"]
|
|
||||||
for name in glob(pattern):
|
|
||||||
newmapping = kjbuckets.kjDict(m)
|
|
||||||
newmapping["NAME"] = name
|
|
||||||
if newmapping.Clean():
|
|
||||||
result.append(newmapping)
|
|
||||||
return result
|
|
||||||
|
|
||||||
...
|
|
||||||
gadfly_instance.add_table("GLOB", GlobTable())
|
|
||||||
""")
|
|
||||||
p("""
|
|
||||||
Then one could formulate queries such as "list the files in directories
|
|
||||||
associated with packages installed by guido"
|
|
||||||
""")
|
|
||||||
pre("""
|
|
||||||
select g.name as filename
|
|
||||||
from packages p, glob g
|
|
||||||
where p.installer = 'guido' and g.pattern=p.root_directory
|
|
||||||
""")
|
|
||||||
p("""
|
|
||||||
Note that conceptually the GLOB table is an infinite table including
|
|
||||||
all filenames on the current computer in the "NAME" column, paired with
|
|
||||||
a potentially infinite number of patterns.
|
|
||||||
""")
|
|
||||||
p("""
|
|
||||||
More interesting examples would allow queries to remotely access
|
|
||||||
data served by an HTTP server, or from any other resource.
|
|
||||||
""")
|
|
||||||
p("""
|
|
||||||
Furthermore an extension table can be augmented with update methods
|
|
||||||
""")
|
|
||||||
pre("""
|
|
||||||
table.insert_rows(listofmappings)
|
|
||||||
table.update_rows(oldlist, newlist)
|
|
||||||
table.delete_rows(oldlist)
|
|
||||||
""")
|
|
||||||
p("""
|
|
||||||
Note: at present the implementation does not enforce recovery or
|
|
||||||
transaction semantics for updates to extension tables, although this
|
|
||||||
may change in the final release.
|
|
||||||
""")
|
|
||||||
p("""
|
|
||||||
The table implementation is free to provide its own implementations of
|
|
||||||
indices which take advantage of data provided by the join argument.
|
|
||||||
""")
|
|
||||||
|
|
||||||
header("Efficiency Notes")
|
|
||||||
|
|
||||||
p("""The following thought experiment attempts to explain why the
|
|
||||||
Gadfly implementation is surprisingly fast considering that it
|
|
||||||
is almost entirely implemented in Python (an interpreted programming
|
|
||||||
language which is not especially fast when compared to alternatives).
|
|
||||||
Although Gadfly is quite complex, at an abstract level the process
|
|
||||||
of query evaluation boils down to a series of embedded loops.
|
|
||||||
Consider the following nested loops:""")
|
|
||||||
|
|
||||||
pre("""
|
|
||||||
iterate 1000:
|
|
||||||
f(...) # fixed cost of outer loop
|
|
||||||
iterate 10:
|
|
||||||
g(...) # fixed cost of middle loop
|
|
||||||
iterate 10:
|
|
||||||
# the real work (string parse, matrix mul, query eval...)
|
|
||||||
h(...)""")
|
|
||||||
|
|
||||||
p("""In my experience many computations follow this pattern where
|
|
||||||
f, g, are complex, dynamic, special purpose and h is simple, general
|
|
||||||
purpose, static. Some example computations that follow this pattern
|
|
||||||
include: file massaging (perl), matrix manipulation (python, tcl),
|
|
||||||
database/cgi page generation, and vector graphics/imaging.""")
|
|
||||||
|
|
||||||
p("""Suppose implementing f, g, h in python is easy but result in
|
|
||||||
execution times10 times slower than a much harder implementation
|
|
||||||
in C, choosing arbitrary and debatable numbers assume each function
|
|
||||||
call consumes 1 tick in C, 5 ticks in java, 10 ticks in python
|
|
||||||
for a straightforward implementation of each function f, g, and
|
|
||||||
h. Under these conditions we get the following cost analysis,
|
|
||||||
eliminating some uninteresting combinations, of implementing the
|
|
||||||
function f, g, and h in combinations of Python, C and java:""")
|
|
||||||
|
|
||||||
pre("""
|
|
||||||
COST | FLANG | GLANG | HLANG
|
|
||||||
==================================
|
|
||||||
111000 | C | C | C
|
|
||||||
115000 | java | C | C
|
|
||||||
120000 | python | C | C
|
|
||||||
155000 | java | java | C
|
|
||||||
210000 | python | python | C
|
|
||||||
555000 | java | java | java
|
|
||||||
560000 | python | java | java
|
|
||||||
610000 | python | python | java
|
|
||||||
1110000 | python | python | python
|
|
||||||
""")
|
|
||||||
|
|
||||||
p("""Note that moving only the innermost loop to C (python/python/C)
|
|
||||||
speeds up the calculation by half an order of magnitude compared
|
|
||||||
to the python-only implementation and brings the speed to within
|
|
||||||
a factor of 2 of an implementation done entirely in C.""")
|
|
||||||
|
|
||||||
p("""Although this artificial and contrived thought experiment is
|
|
||||||
far from conclusive, we may be tempted to draw the conclusion
|
|
||||||
that generally programmers should focus first on obtaining a working
|
|
||||||
implementation (because as John Ousterhout is reported to have
|
|
||||||
said "the biggest performance improvement is the transition
|
|
||||||
from non-working to working") using the methodology that
|
|
||||||
is most likely to obtain a working solution the quickest (Python). Only then if the performance
|
|
||||||
is inadequate should the programmer focus on optimizing
|
|
||||||
the inner most loops, perhaps moving them to a very efficient
|
|
||||||
implementation (C). Optimizing the outer loops will buy little
|
|
||||||
improvement, and should be done later, if ever.""")
|
|
||||||
|
|
||||||
p("""This was precisely the strategy behind the gadfly implementations,
|
|
||||||
where most of the inner loops are implemented in the kjbuckets
|
|
||||||
C extension module and the higher level logic is all in Python.
|
|
||||||
This also explains why gadfly appears to be "slower"
|
|
||||||
for simple queries over small data sets, but seems to be relatively
|
|
||||||
"faster" for more complex queries over larger data sets,
|
|
||||||
since larger queries and data sets take better advantage of the
|
|
||||||
optimized inner loops.""")
|
|
||||||
|
|
||||||
header("A Gadfly variant for OLAP?")
|
|
||||||
|
|
||||||
p("""In private correspondence Andy Robinson points out that the
|
|
||||||
basic logical design underlying Gadfly could be adapted to provide
|
|
||||||
Online Analytical Processing (OLAP) and other forms of data warehousing
|
|
||||||
and data mining. Since SQL is not particularly well suited for
|
|
||||||
the kinds of requests common in these domains the higher level
|
|
||||||
interfaces would require modification, but the underlying logic
|
|
||||||
of substitutions and name mappings seems to be appropriate.""")
|
|
||||||
|
|
||||||
header("Conclusion")
|
|
||||||
|
|
||||||
p("""The revamped query engine design in Gadfly 2 supports
|
|
||||||
a flexible and general extension methodology that permits programmers
|
|
||||||
to extend the gadfly engine to include additional computations
|
|
||||||
and access to remote data sources. Among other possibilities this
|
|
||||||
will permit the gadfly engine to make use of disk based indexed
|
|
||||||
tables and to dynamically retrieve information from remote data
|
|
||||||
sources (such as an Excel spreadsheet or an Oracle database).
|
|
||||||
These features will make gadfly a very useful tool for data manipulation
|
|
||||||
and integration.""")
|
|
||||||
|
|
||||||
header("References")
|
|
||||||
|
|
||||||
p("""[Van Rossum] Van Rossum, Python Reference Manual, Tutorial, and Library Manuals,
|
|
||||||
please look to http://www.python.org
|
|
||||||
for the latest versions, downloads and links to printed versions.""")
|
|
||||||
|
|
||||||
p("""[O'Neill] O'Neill, P., Data Base Principles, Programming, Performance,
|
|
||||||
Morgan Kaufmann Publishers, San Francisco, 1994.""")
|
|
||||||
|
|
||||||
p("""[Korth and Silberschatz] Korth, H. and Silberschatz, A. and Sudarshan, S.
|
|
||||||
Data Base System Concepts, McGraw-Hill Series in Computer Science, Boston,
|
|
||||||
1997""")
|
|
||||||
|
|
||||||
p("""[Gadfly]Gadfly: SQL Relational Database in Python,
|
|
||||||
http://www.chordate.com/kwParsing/gadfly.html""")
|
|
||||||
|
|
||||||
go()
|
|
|
@ -1,56 +0,0 @@
|
||||||
This contains a number of benchmarks and demos
|
|
||||||
based on Homer's Odyssey (which is widely available
|
|
||||||
in plain, line-oriented text format). There are a large
|
|
||||||
selection of online books at:
|
|
||||||
http://classics.mit.edu/
|
|
||||||
|
|
||||||
|
|
||||||
Our distribution ships with just the first chapter
|
|
||||||
in odyssey.txt. For a more meaningful speed test,
|
|
||||||
download the full copy from
|
|
||||||
http://www.reportlab.com/ftp/odyssey.full.zip
|
|
||||||
or
|
|
||||||
ftp://ftp.reportlab.com/odyssey.full.zip
|
|
||||||
and unzip to extract odyssey.full.txt (608kb).
|
|
||||||
|
|
||||||
Benchmark speed depends quite critically
|
|
||||||
on the presence of our accelerator module,
|
|
||||||
_rl_accel, which is a C (or Java) extension.
|
|
||||||
Serious users ought to compile or download this!
|
|
||||||
|
|
||||||
The times quoted are from one machine (Andy Robinson's
|
|
||||||
home PC, approx 1.2Ghz 128Mb Ram, Win2k in Sep 2003)
|
|
||||||
in order to give a rough idea of what features cost
|
|
||||||
what performance.
|
|
||||||
|
|
||||||
|
|
||||||
The tests are as follows:
|
|
||||||
|
|
||||||
(1) odyssey.py (produces odyssey.pdf)
|
|
||||||
This demo takes a large volume of text and prints it
|
|
||||||
in the simplest way possible. It is a demo of the
|
|
||||||
basic technique of looping down a page manually and
|
|
||||||
breaking at the bottom. On my 1.2 Ghz machine this takes
|
|
||||||
1.91 seconds (124 pages per second)
|
|
||||||
|
|
||||||
(2) fodyssey.py (produces fodyssey.pdf)
|
|
||||||
This is a 'flowing document' we parse the file and
|
|
||||||
throw away line breaks to make proper paragraphs.
|
|
||||||
The Platypus framework renders these. This necessitates
|
|
||||||
measuring the width of every word in every paragraph
|
|
||||||
for wrapping purposes.
|
|
||||||
|
|
||||||
This takes 3.27 seconds on the same machine. Paragraph
|
|
||||||
wrapping basically doubles the work. The text is more
|
|
||||||
compact with about 50% more words per page. Very roughly,
|
|
||||||
we can wrap 40 pages of ten-point text per second and save
|
|
||||||
to PDF.
|
|
||||||
|
|
||||||
(3) dodyssey.py (produced dodyssey.pdf)
|
|
||||||
This is a slightly fancier version which uses different
|
|
||||||
page templates (one column for first page in a chapter,
|
|
||||||
two column for body poages). The additional layout logic
|
|
||||||
adds about 15%, going up to 3.8 seconds. This is probably
|
|
||||||
a realistic benchmark for a simple long text document
|
|
||||||
with a single pass. Documents doing cross-references
|
|
||||||
and a table of contents might need twice as long.
|
|
|
@ -1,254 +0,0 @@
|
||||||
#Copyright ReportLab Europe Ltd. 2000-2004
|
|
||||||
#see license.txt for license details
|
|
||||||
#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/demos/odyssey/dodyssey.py
|
|
||||||
__version__=''' $Id: dodyssey.py 2856 2006-05-11 09:48:13Z rgbecker $ '''
|
|
||||||
__doc__=''
|
|
||||||
|
|
||||||
#REPORTLAB_TEST_SCRIPT
|
|
||||||
import sys, copy, string, os
|
|
||||||
from reportlab.platypus import *
|
|
||||||
_NEW_PARA=os.environ.get('NEW_PARA','0')[0] in ('y','Y','1')
|
|
||||||
_REDCAP=int(os.environ.get('REDCAP','0'))
|
|
||||||
_CALLBACK=os.environ.get('CALLBACK','0')[0] in ('y','Y','1')
|
|
||||||
if _NEW_PARA:
|
|
||||||
def Paragraph(s,style):
|
|
||||||
from rlextra.radxml.para import Paragraph as PPPP
|
|
||||||
return PPPP(s,style)
|
|
||||||
|
|
||||||
from reportlab.lib.units import inch
|
|
||||||
from reportlab.lib.styles import getSampleStyleSheet
|
|
||||||
from reportlab.lib.enums import TA_LEFT, TA_RIGHT, TA_CENTER, TA_JUSTIFY
|
|
||||||
|
|
||||||
import reportlab.rl_config
|
|
||||||
reportlab.rl_config.invariant = 1
|
|
||||||
|
|
||||||
styles = getSampleStyleSheet()
|
|
||||||
|
|
||||||
Title = "The Odyssey"
|
|
||||||
Author = "Homer"
|
|
||||||
|
|
||||||
def myTitlePage(canvas, doc):
|
|
||||||
canvas.saveState()
|
|
||||||
canvas.restoreState()
|
|
||||||
|
|
||||||
def myLaterPages(canvas, doc):
|
|
||||||
canvas.saveState()
|
|
||||||
canvas.setFont('Times-Roman',9)
|
|
||||||
canvas.drawString(inch, 0.75 * inch, "Page %d" % doc.page)
|
|
||||||
canvas.restoreState()
|
|
||||||
|
|
||||||
def go():
|
|
||||||
def myCanvasMaker(fn,**kw):
|
|
||||||
from reportlab.pdfgen.canvas import Canvas
|
|
||||||
canv = apply(Canvas,(fn,),kw)
|
|
||||||
# attach our callback to the canvas
|
|
||||||
canv.myOnDrawCB = myOnDrawCB
|
|
||||||
return canv
|
|
||||||
|
|
||||||
doc = BaseDocTemplate('dodyssey.pdf',showBoundary=0)
|
|
||||||
|
|
||||||
#normal frame as for SimpleFlowDocument
|
|
||||||
frameT = Frame(doc.leftMargin, doc.bottomMargin, doc.width, doc.height, id='normal')
|
|
||||||
|
|
||||||
#Two Columns
|
|
||||||
frame1 = Frame(doc.leftMargin, doc.bottomMargin, doc.width/2-6, doc.height, id='col1')
|
|
||||||
frame2 = Frame(doc.leftMargin+doc.width/2+6, doc.bottomMargin, doc.width/2-6,
|
|
||||||
doc.height, id='col2')
|
|
||||||
doc.addPageTemplates([PageTemplate(id='First',frames=frameT, onPage=myTitlePage),
|
|
||||||
PageTemplate(id='OneCol',frames=frameT, onPage=myLaterPages),
|
|
||||||
PageTemplate(id='TwoCol',frames=[frame1,frame2], onPage=myLaterPages),
|
|
||||||
])
|
|
||||||
doc.build(Elements,canvasmaker=myCanvasMaker)
|
|
||||||
|
|
||||||
Elements = []
|
|
||||||
|
|
||||||
ChapterStyle = copy.deepcopy(styles["Heading1"])
|
|
||||||
ChapterStyle.alignment = TA_CENTER
|
|
||||||
ChapterStyle.fontsize = 14
|
|
||||||
InitialStyle = copy.deepcopy(ChapterStyle)
|
|
||||||
InitialStyle.fontsize = 16
|
|
||||||
InitialStyle.leading = 20
|
|
||||||
PreStyle = styles["Code"]
|
|
||||||
|
|
||||||
def newPage():
|
|
||||||
Elements.append(PageBreak())
|
|
||||||
|
|
||||||
chNum = 0
|
|
||||||
def myOnDrawCB(canv,kind,label):
|
|
||||||
print 'myOnDrawCB(%s)'%kind, 'Page number=', canv.getPageNumber(), 'label value=', label
|
|
||||||
|
|
||||||
def chapter(txt, style=ChapterStyle):
|
|
||||||
global chNum
|
|
||||||
Elements.append(NextPageTemplate('OneCol'))
|
|
||||||
newPage()
|
|
||||||
chNum = chNum + 1
|
|
||||||
if _NEW_PARA or not _CALLBACK:
|
|
||||||
Elements.append(Paragraph(('chap %d'%chNum)+txt, style))
|
|
||||||
else:
|
|
||||||
Elements.append(Paragraph(('foo<onDraw name="myOnDrawCB" label="chap %d"/> '%chNum)+txt, style))
|
|
||||||
Elements.append(Spacer(0.2*inch, 0.3*inch))
|
|
||||||
if useTwoCol:
|
|
||||||
Elements.append(NextPageTemplate('TwoCol'))
|
|
||||||
|
|
||||||
def fTitle(txt,style=InitialStyle):
|
|
||||||
Elements.append(Paragraph(txt, style))
|
|
||||||
|
|
||||||
ParaStyle = copy.deepcopy(styles["Normal"])
|
|
||||||
ParaStyle.spaceBefore = 0.1*inch
|
|
||||||
if 'right' in sys.argv:
|
|
||||||
ParaStyle.alignment = TA_RIGHT
|
|
||||||
elif 'left' in sys.argv:
|
|
||||||
ParaStyle.alignment = TA_LEFT
|
|
||||||
elif 'justify' in sys.argv:
|
|
||||||
ParaStyle.alignment = TA_JUSTIFY
|
|
||||||
elif 'center' in sys.argv or 'centre' in sys.argv:
|
|
||||||
ParaStyle.alignment = TA_CENTER
|
|
||||||
else:
|
|
||||||
ParaStyle.alignment = TA_JUSTIFY
|
|
||||||
|
|
||||||
useTwoCol = 'notwocol' not in sys.argv
|
|
||||||
|
|
||||||
def spacer(inches):
|
|
||||||
Elements.append(Spacer(0.1*inch, inches*inch))
|
|
||||||
|
|
||||||
def p(txt, style=ParaStyle):
|
|
||||||
if _REDCAP:
|
|
||||||
fs, fe = '<font color="red" size="+2">', '</font>'
|
|
||||||
n = len(txt)
|
|
||||||
for i in xrange(n):
|
|
||||||
if 'a'<=txt[i]<='z' or 'A'<=txt[i]<='Z':
|
|
||||||
txt = (txt[:i]+(fs+txt[i]+fe))+txt[i+1:]
|
|
||||||
break
|
|
||||||
if _REDCAP>=2 and n>20:
|
|
||||||
j = i+len(fs)+len(fe)+1+int((n-1)/2)
|
|
||||||
while not ('a'<=txt[j]<='z' or 'A'<=txt[j]<='Z'): j += 1
|
|
||||||
txt = (txt[:j]+('<b><i><font size="+2" color="blue">'+txt[j]+'</font></i></b>'))+txt[j+1:]
|
|
||||||
|
|
||||||
if _REDCAP==3 and n>20:
|
|
||||||
n = len(txt)
|
|
||||||
fs = '<font color="green" size="+1">'
|
|
||||||
for i in xrange(n-1,-1,-1):
|
|
||||||
if 'a'<=txt[i]<='z' or 'A'<=txt[i]<='Z':
|
|
||||||
txt = txt[:i]+((fs+txt[i]+fe)+txt[i+1:])
|
|
||||||
break
|
|
||||||
|
|
||||||
Elements.append(Paragraph(txt, style))
|
|
||||||
|
|
||||||
firstPre = 1
|
|
||||||
def pre(txt, style=PreStyle):
|
|
||||||
global firstPre
|
|
||||||
if firstPre:
|
|
||||||
Elements.append(NextPageTemplate('OneCol'))
|
|
||||||
newPage()
|
|
||||||
firstPre = 0
|
|
||||||
|
|
||||||
spacer(0.1)
|
|
||||||
p = Preformatted(txt, style)
|
|
||||||
Elements.append(p)
|
|
||||||
|
|
||||||
def parseOdyssey(fn):
|
|
||||||
from time import time
|
|
||||||
E = []
|
|
||||||
t0=time()
|
|
||||||
L = open(fn,'r').readlines()
|
|
||||||
t1 = time()
|
|
||||||
print "open(%s,'r').readlines() took %.4f seconds" %(fn,t1-t0)
|
|
||||||
for i in xrange(len(L)):
|
|
||||||
if L[i][-1]=='\012':
|
|
||||||
L[i] = L[i][:-1]
|
|
||||||
t2 = time()
|
|
||||||
print "Removing all linefeeds took %.4f seconds" %(t2-t1)
|
|
||||||
L.append('')
|
|
||||||
L.append('-----')
|
|
||||||
|
|
||||||
def findNext(L, i):
|
|
||||||
while 1:
|
|
||||||
if string.strip(L[i])=='':
|
|
||||||
del L[i]
|
|
||||||
kind = 1
|
|
||||||
if i<len(L):
|
|
||||||
while string.strip(L[i])=='':
|
|
||||||
del L[i]
|
|
||||||
|
|
||||||
if i<len(L):
|
|
||||||
kind = L[i][-1]=='-' and L[i][0]=='-'
|
|
||||||
if kind:
|
|
||||||
del L[i]
|
|
||||||
if i<len(L):
|
|
||||||
while string.strip(L[i])=='':
|
|
||||||
del L[i]
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
i = i + 1
|
|
||||||
|
|
||||||
return i, kind
|
|
||||||
|
|
||||||
f = s = 0
|
|
||||||
while 1:
|
|
||||||
f, k = findNext(L,0)
|
|
||||||
if k: break
|
|
||||||
|
|
||||||
E.append([spacer,2])
|
|
||||||
E.append([fTitle,'<font color="red">%s</font>' % Title, InitialStyle])
|
|
||||||
E.append([fTitle,'<font size="-4">by</font> <font color="green">%s</font>' % Author, InitialStyle])
|
|
||||||
|
|
||||||
while 1:
|
|
||||||
if f>=len(L): break
|
|
||||||
|
|
||||||
if string.upper(L[f][0:5])=='BOOK ':
|
|
||||||
E.append([chapter,L[f]])
|
|
||||||
f=f+1
|
|
||||||
while string.strip(L[f])=='': del L[f]
|
|
||||||
style = ParaStyle
|
|
||||||
func = p
|
|
||||||
else:
|
|
||||||
style = PreStyle
|
|
||||||
func = pre
|
|
||||||
|
|
||||||
while 1:
|
|
||||||
s=f
|
|
||||||
f, k=findNext(L,s)
|
|
||||||
sep= (func is pre) and '\012' or ' '
|
|
||||||
E.append([func,string.join(L[s:f],sep),style])
|
|
||||||
if k: break
|
|
||||||
t3 = time()
|
|
||||||
print "Parsing into memory took %.4f seconds" %(t3-t2)
|
|
||||||
del L
|
|
||||||
t4 = time()
|
|
||||||
print "Deleting list of lines took %.4f seconds" %(t4-t3)
|
|
||||||
for i in xrange(len(E)):
|
|
||||||
apply(E[i][0],E[i][1:])
|
|
||||||
t5 = time()
|
|
||||||
print "Moving into platypus took %.4f seconds" %(t5-t4)
|
|
||||||
del E
|
|
||||||
t6 = time()
|
|
||||||
print "Deleting list of actions took %.4f seconds" %(t6-t5)
|
|
||||||
go()
|
|
||||||
t7 = time()
|
|
||||||
print "saving to PDF took %.4f seconds" %(t7-t6)
|
|
||||||
print "Total run took %.4f seconds"%(t7-t0)
|
|
||||||
|
|
||||||
import md5
|
|
||||||
print 'file digest: %s' % md5.md5(open('dodyssey.pdf','rb').read()).hexdigest()
|
|
||||||
|
|
||||||
def run():
|
|
||||||
for fn in ('odyssey.full.txt','odyssey.txt'):
|
|
||||||
if os.path.isfile(fn):
|
|
||||||
parseOdyssey(fn)
|
|
||||||
break
|
|
||||||
|
|
||||||
def doProf(profname,func,*args,**kwd):
|
|
||||||
import hotshot, hotshot.stats
|
|
||||||
prof = hotshot.Profile(profname)
|
|
||||||
prof.runcall(func)
|
|
||||||
prof.close()
|
|
||||||
stats = hotshot.stats.load(profname)
|
|
||||||
stats.strip_dirs()
|
|
||||||
stats.sort_stats('time', 'calls')
|
|
||||||
stats.print_stats(20)
|
|
||||||
|
|
||||||
if __name__=='__main__':
|
|
||||||
if '--prof' in sys.argv:
|
|
||||||
doProf('dodyssey.prof',run)
|
|
||||||
else:
|
|
||||||
run()
|
|
|
@ -1,165 +0,0 @@
|
||||||
#Copyright ReportLab Europe Ltd. 2000-2004
|
|
||||||
#see license.txt for license details
|
|
||||||
#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/demos/odyssey/fodyssey.py
|
|
||||||
__version__=''' $Id: fodyssey.py 2385 2004-06-17 15:26:05Z rgbecker $ '''
|
|
||||||
__doc__=''
|
|
||||||
|
|
||||||
#REPORTLAB_TEST_SCRIPT
|
|
||||||
import sys, copy, string, os
|
|
||||||
from reportlab.platypus import *
|
|
||||||
from reportlab.lib.units import inch
|
|
||||||
from reportlab.lib.styles import getSampleStyleSheet
|
|
||||||
from reportlab.lib.enums import TA_LEFT, TA_RIGHT, TA_CENTER, TA_JUSTIFY
|
|
||||||
|
|
||||||
styles = getSampleStyleSheet()
|
|
||||||
|
|
||||||
Title = "The Odyssey"
|
|
||||||
Author = "Homer"
|
|
||||||
|
|
||||||
def myFirstPage(canvas, doc):
|
|
||||||
canvas.saveState()
|
|
||||||
canvas.restoreState()
|
|
||||||
|
|
||||||
def myLaterPages(canvas, doc):
|
|
||||||
canvas.saveState()
|
|
||||||
canvas.setFont('Times-Roman',9)
|
|
||||||
canvas.drawString(inch, 0.75 * inch, "Page %d" % doc.page)
|
|
||||||
canvas.restoreState()
|
|
||||||
|
|
||||||
def go():
|
|
||||||
doc = SimpleDocTemplate('fodyssey.pdf',showBoundary='showboundary' in sys.argv)
|
|
||||||
doc.allowSplitting = not 'nosplitting' in sys.argv
|
|
||||||
doc.build(Elements,myFirstPage,myLaterPages)
|
|
||||||
|
|
||||||
Elements = []
|
|
||||||
|
|
||||||
ChapterStyle = copy.copy(styles["Heading1"])
|
|
||||||
ChapterStyle.alignment = TA_CENTER
|
|
||||||
ChapterStyle.fontsize = 16
|
|
||||||
InitialStyle = copy.deepcopy(ChapterStyle)
|
|
||||||
InitialStyle.fontsize = 16
|
|
||||||
InitialStyle.leading = 20
|
|
||||||
PreStyle = styles["Code"]
|
|
||||||
|
|
||||||
def newPage():
|
|
||||||
Elements.append(PageBreak())
|
|
||||||
|
|
||||||
def chapter(txt, style=ChapterStyle):
|
|
||||||
newPage()
|
|
||||||
Elements.append(Paragraph(txt, style))
|
|
||||||
Elements.append(Spacer(0.2*inch, 0.3*inch))
|
|
||||||
|
|
||||||
def fTitle(txt,style=InitialStyle):
|
|
||||||
Elements.append(Paragraph(txt, style))
|
|
||||||
|
|
||||||
ParaStyle = copy.deepcopy(styles["Normal"])
|
|
||||||
ParaStyle.spaceBefore = 0.1*inch
|
|
||||||
if 'right' in sys.argv:
|
|
||||||
ParaStyle.alignment = TA_RIGHT
|
|
||||||
elif 'left' in sys.argv:
|
|
||||||
ParaStyle.alignment = TA_LEFT
|
|
||||||
elif 'justify' in sys.argv:
|
|
||||||
ParaStyle.alignment = TA_JUSTIFY
|
|
||||||
elif 'center' in sys.argv or 'centre' in sys.argv:
|
|
||||||
ParaStyle.alignment = TA_CENTER
|
|
||||||
else:
|
|
||||||
ParaStyle.alignment = TA_JUSTIFY
|
|
||||||
|
|
||||||
def spacer(inches):
|
|
||||||
Elements.append(Spacer(0.1*inch, inches*inch))
|
|
||||||
|
|
||||||
def p(txt, style=ParaStyle):
|
|
||||||
Elements.append(Paragraph(txt, style))
|
|
||||||
|
|
||||||
def pre(txt, style=PreStyle):
|
|
||||||
spacer(0.1)
|
|
||||||
p = Preformatted(txt, style)
|
|
||||||
Elements.append(p)
|
|
||||||
|
|
||||||
def parseOdyssey(fn):
|
|
||||||
from time import time
|
|
||||||
E = []
|
|
||||||
t0=time()
|
|
||||||
L = open(fn,'r').readlines()
|
|
||||||
t1 = time()
|
|
||||||
print "open(%s,'r').readlines() took %.4f seconds" %(fn,t1-t0)
|
|
||||||
for i in xrange(len(L)):
|
|
||||||
if L[i][-1]=='\012':
|
|
||||||
L[i] = L[i][:-1]
|
|
||||||
t2 = time()
|
|
||||||
print "Removing all linefeeds took %.4f seconds" %(t2-t1)
|
|
||||||
L.append('')
|
|
||||||
L.append('-----')
|
|
||||||
|
|
||||||
def findNext(L, i):
|
|
||||||
while 1:
|
|
||||||
if string.strip(L[i])=='':
|
|
||||||
del L[i]
|
|
||||||
kind = 1
|
|
||||||
if i<len(L):
|
|
||||||
while string.strip(L[i])=='':
|
|
||||||
del L[i]
|
|
||||||
|
|
||||||
if i<len(L):
|
|
||||||
kind = L[i][-1]=='-' and L[i][0]=='-'
|
|
||||||
if kind:
|
|
||||||
del L[i]
|
|
||||||
if i<len(L):
|
|
||||||
while string.strip(L[i])=='':
|
|
||||||
del L[i]
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
i = i + 1
|
|
||||||
|
|
||||||
return i, kind
|
|
||||||
|
|
||||||
f = s = 0
|
|
||||||
while 1:
|
|
||||||
f, k = findNext(L,0)
|
|
||||||
if k: break
|
|
||||||
|
|
||||||
E.append([spacer,2])
|
|
||||||
E.append([fTitle,'<font color=red>%s</font>' % Title, InitialStyle])
|
|
||||||
E.append([fTitle,'<font size=-4>by</font> <font color=green>%s</font>' % Author, InitialStyle])
|
|
||||||
|
|
||||||
while 1:
|
|
||||||
if f>=len(L): break
|
|
||||||
|
|
||||||
if string.upper(L[f][0:5])=='BOOK ':
|
|
||||||
E.append([chapter,L[f]])
|
|
||||||
f=f+1
|
|
||||||
while string.strip(L[f])=='': del L[f]
|
|
||||||
style = ParaStyle
|
|
||||||
func = p
|
|
||||||
else:
|
|
||||||
style = PreStyle
|
|
||||||
func = pre
|
|
||||||
|
|
||||||
while 1:
|
|
||||||
s=f
|
|
||||||
f, k=findNext(L,s)
|
|
||||||
sep= (func is pre) and '\012' or ' '
|
|
||||||
E.append([func,string.join(L[s:f],sep),style])
|
|
||||||
if k: break
|
|
||||||
t3 = time()
|
|
||||||
print "Parsing into memory took %.4f seconds" %(t3-t2)
|
|
||||||
del L
|
|
||||||
t4 = time()
|
|
||||||
print "Deleting list of lines took %.4f seconds" %(t4-t3)
|
|
||||||
for i in xrange(len(E)):
|
|
||||||
apply(E[i][0],E[i][1:])
|
|
||||||
t5 = time()
|
|
||||||
print "Moving into platypus took %.4f seconds" %(t5-t4)
|
|
||||||
del E
|
|
||||||
t6 = time()
|
|
||||||
print "Deleting list of actions took %.4f seconds" %(t6-t5)
|
|
||||||
go()
|
|
||||||
t7 = time()
|
|
||||||
print "saving to PDF took %.4f seconds" %(t7-t6)
|
|
||||||
print "Total run took %.4f seconds"%(t7-t0)
|
|
||||||
|
|
||||||
for fn in ('odyssey.full.txt','odyssey.txt'):
|
|
||||||
if os.path.isfile(fn):
|
|
||||||
break
|
|
||||||
if __name__=='__main__':
|
|
||||||
parseOdyssey(fn)
|
|
|
@ -1,151 +0,0 @@
|
||||||
#Copyright ReportLab Europe Ltd. 2000-2004
|
|
||||||
#see license.txt for license details
|
|
||||||
#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/demos/odyssey/odyssey.py
|
|
||||||
__version__=''' $Id: odyssey.py 2385 2004-06-17 15:26:05Z rgbecker $ '''
|
|
||||||
___doc__=''
|
|
||||||
#odyssey.py
|
|
||||||
#
|
|
||||||
#Demo/benchmark of PDFgen rendering Homer's Odyssey.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#results on my humble P266 with 64MB:
|
|
||||||
# Without page compression:
|
|
||||||
# 239 pages in 3.76 seconds = 77 pages per second
|
|
||||||
|
|
||||||
# With textOut rather than textLine, i.e. computing width
|
|
||||||
# of every word as we would for wrapping:
|
|
||||||
# 239 pages in 10.83 seconds = 22 pages per second
|
|
||||||
|
|
||||||
# With page compression and textLine():
|
|
||||||
# 239 pages in 39.39 seconds = 6 pages per second
|
|
||||||
|
|
||||||
from reportlab.pdfgen import canvas
|
|
||||||
import time, os, sys
|
|
||||||
|
|
||||||
#find out what platform we are on and whether accelerator is
|
|
||||||
#present, in order to print this as part of benchmark info.
|
|
||||||
try:
|
|
||||||
import _rl_accel
|
|
||||||
ACCEL = 1
|
|
||||||
except ImportError:
|
|
||||||
ACCEL = 0
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
from reportlab.lib.units import inch, cm
|
|
||||||
from reportlab.lib.pagesizes import A4
|
|
||||||
|
|
||||||
#precalculate some basics
|
|
||||||
top_margin = A4[1] - inch
|
|
||||||
bottom_margin = inch
|
|
||||||
left_margin = inch
|
|
||||||
right_margin = A4[0] - inch
|
|
||||||
frame_width = right_margin - left_margin
|
|
||||||
|
|
||||||
|
|
||||||
def drawPageFrame(canv):
|
|
||||||
canv.line(left_margin, top_margin, right_margin, top_margin)
|
|
||||||
canv.setFont('Times-Italic',12)
|
|
||||||
canv.drawString(left_margin, top_margin + 2, "Homer's Odyssey")
|
|
||||||
canv.line(left_margin, top_margin, right_margin, top_margin)
|
|
||||||
|
|
||||||
|
|
||||||
canv.line(left_margin, bottom_margin, right_margin, bottom_margin)
|
|
||||||
canv.drawCentredString(0.5*A4[0], 0.5 * inch,
|
|
||||||
"Page %d" % canv.getPageNumber())
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def run(verbose=1):
|
|
||||||
if sys.platform[0:4] == 'java':
|
|
||||||
impl = 'Jython'
|
|
||||||
else:
|
|
||||||
impl = 'Python'
|
|
||||||
verStr = '%d.%d' % (sys.version_info[0:2])
|
|
||||||
if ACCEL:
|
|
||||||
accelStr = 'with _rl_accel'
|
|
||||||
else:
|
|
||||||
accelStr = 'without _rl_accel'
|
|
||||||
print 'Benchmark of %s %s %s' % (impl, verStr, accelStr)
|
|
||||||
|
|
||||||
started = time.time()
|
|
||||||
canv = canvas.Canvas('odyssey.pdf', invariant=1)
|
|
||||||
canv.setPageCompression(1)
|
|
||||||
drawPageFrame(canv)
|
|
||||||
|
|
||||||
#do some title page stuff
|
|
||||||
canv.setFont("Times-Bold", 36)
|
|
||||||
canv.drawCentredString(0.5 * A4[0], 7 * inch, "Homer's Odyssey")
|
|
||||||
|
|
||||||
canv.setFont("Times-Bold", 18)
|
|
||||||
canv.drawCentredString(0.5 * A4[0], 5 * inch, "Translated by Samuel Burton")
|
|
||||||
|
|
||||||
canv.setFont("Times-Bold", 12)
|
|
||||||
tx = canv.beginText(left_margin, 3 * inch)
|
|
||||||
tx.textLine("This is a demo-cum-benchmark for PDFgen. It renders the complete text of Homer's Odyssey")
|
|
||||||
tx.textLine("from a text file. On my humble P266, it does 77 pages per secondwhile creating a 238 page")
|
|
||||||
tx.textLine("document. If it is asked to computer text metrics, measuring the width of each word as ")
|
|
||||||
tx.textLine("one would for paragraph wrapping, it still manages 22 pages per second.")
|
|
||||||
tx.textLine("")
|
|
||||||
tx.textLine("Andy Robinson, Robinson Analytics Ltd.")
|
|
||||||
canv.drawText(tx)
|
|
||||||
|
|
||||||
canv.showPage()
|
|
||||||
#on with the text...
|
|
||||||
drawPageFrame(canv)
|
|
||||||
|
|
||||||
canv.setFont('Times-Roman', 12)
|
|
||||||
tx = canv.beginText(left_margin, top_margin - 0.5*inch)
|
|
||||||
|
|
||||||
for fn in ('odyssey.full.txt','odyssey.txt'):
|
|
||||||
if os.path.isfile(fn):
|
|
||||||
break
|
|
||||||
|
|
||||||
data = open(fn,'r').readlines()
|
|
||||||
for line in data:
|
|
||||||
#this just does it the fast way...
|
|
||||||
tx.textLine(line)
|
|
||||||
#this forces it to do text metrics, which would be the slow
|
|
||||||
#part if we were wrappng paragraphs.
|
|
||||||
#canv.textOut(line)
|
|
||||||
#canv.textLine('')
|
|
||||||
|
|
||||||
#page breaking
|
|
||||||
y = tx.getY() #get y coordinate
|
|
||||||
if y < bottom_margin + 0.5*inch:
|
|
||||||
canv.drawText(tx)
|
|
||||||
canv.showPage()
|
|
||||||
drawPageFrame(canv)
|
|
||||||
canv.setFont('Times-Roman', 12)
|
|
||||||
tx = canv.beginText(left_margin, top_margin - 0.5*inch)
|
|
||||||
|
|
||||||
#page
|
|
||||||
pg = canv.getPageNumber()
|
|
||||||
if verbose and pg % 10 == 0:
|
|
||||||
print 'formatted page %d' % canv.getPageNumber()
|
|
||||||
|
|
||||||
if tx:
|
|
||||||
canv.drawText(tx)
|
|
||||||
canv.showPage()
|
|
||||||
drawPageFrame(canv)
|
|
||||||
|
|
||||||
if verbose:
|
|
||||||
print 'about to write to disk...'
|
|
||||||
|
|
||||||
canv.save()
|
|
||||||
|
|
||||||
finished = time.time()
|
|
||||||
elapsed = finished - started
|
|
||||||
pages = canv.getPageNumber()-1
|
|
||||||
speed = pages / elapsed
|
|
||||||
fileSize = os.stat('odyssey.pdf')[6] / 1024
|
|
||||||
print '%d pages in %0.2f seconds = %0.2f pages per second, file size %d kb' % (
|
|
||||||
pages, elapsed, speed, fileSize)
|
|
||||||
import md5
|
|
||||||
print 'file digest: %s' % md5.md5(open('odyssey.pdf','rb').read()).hexdigest()
|
|
||||||
|
|
||||||
if __name__=='__main__':
|
|
||||||
quiet = ('-q' in sys.argv)
|
|
||||||
run(verbose = not quiet)
|
|
|
@ -1,207 +0,0 @@
|
||||||
Provided by The Internet Classics Archive.
|
|
||||||
See bottom for copyright. Available online at
|
|
||||||
http://classics.mit.edu//Homer/odyssey.html
|
|
||||||
|
|
||||||
The Odyssey
|
|
||||||
By Homer
|
|
||||||
|
|
||||||
|
|
||||||
Translated by Samuel Butler
|
|
||||||
|
|
||||||
----------------------------------------------------------------------
|
|
||||||
|
|
||||||
BOOK I
|
|
||||||
<bullet indent="-18"><font name="courier" size="13" color="blue">I</font></bullet><font color="green"><b><i>Tell</i></b></font> me, O muse, of that ingenious hero who travelled far and wide
|
|
||||||
a b c &| & | <b>A</b>' <b>A</b> ' after he had sacked the famous town of <font color="red" size="12"><b>Troy</b></font>. Many cities did he visit,
|
|
||||||
and many were the nations with whose manners and customs he was acquainted;
|
|
||||||
moreover he suffered much by sea while trying to save his own life
|
|
||||||
and bring his men safely home; but do what he might he could not
|
|
||||||
save<super><font color="red">1</font></super>
|
|
||||||
his men, for they perished through their own sheer folly in eating
|
|
||||||
the cattle of the Sun-god Hyperion; so the god prevented them from
|
|
||||||
ever reaching home. Tell me, too, about all these things, O daughter
|
|
||||||
of Jove, from whatsoever source you may know them.
|
|
||||||
|
|
||||||
So now all who escaped death in battle or by shipwreck had got safely
|
|
||||||
home except Ulysses, and he, though he was longing to return to his
|
|
||||||
wife and country, was detained by the goddess Calypso, who had got
|
|
||||||
him into a large cave and wanted to marry him. But as years went by,
|
|
||||||
there came a time when the gods settled that he should go back to
|
|
||||||
Ithaca; even then, however, when he was among his own people, his
|
|
||||||
troubles were not yet over; nevertheless all the gods had now begun
|
|
||||||
to pity him except Neptune, who still persecuted him without ceasing
|
|
||||||
and would not let him get home.
|
|
||||||
|
|
||||||
<font color="green">Now Neptune had gone off to the Ethiopians, who are at the world's
|
|
||||||
end, and lie in two halves, the one looking West and the other East.
|
|
||||||
He had gone there to accept a hecatomb of sheep and oxen, and was
|
|
||||||
enjoying himself at his festival; but the other gods met in the house
|
|
||||||
of Olympian Jove, and the sire of gods and men spoke first. At that
|
|
||||||
moment he was thinking of Aegisthus, who had been killed by Agamemnon's
|
|
||||||
son Orestes; so he said to the other gods:</font>
|
|
||||||
|
|
||||||
"See now, how men lay blame upon us gods for what is after all nothing
|
|
||||||
but their own folly. Look at Aegisthus; he must needs make love to
|
|
||||||
Agamemnon's wife unrighteously and then kill Agamemnon, though he
|
|
||||||
knew it would be the death of him; for I sent Mercury to warn him
|
|
||||||
not to do either of these things, inasmuch as Orestes would be sure
|
|
||||||
to take his revenge when he grew up and wanted to return home. Mercury
|
|
||||||
told him this in all good will but he would not listen, and now he
|
|
||||||
has paid for everything in full."
|
|
||||||
|
|
||||||
Then Minerva said, "Father, son of Saturn, King of kings, it served
|
|
||||||
Aegisthus right, and so it would any one else who does as he did;
|
|
||||||
but Aegisthus is neither here nor there; it is for Ulysses that my
|
|
||||||
heart bleeds, when I think of his sufferings in that lonely sea-girt
|
|
||||||
island, far away, poor man, from all his friends. It is an island
|
|
||||||
covered with forest, in the very middle of the sea, and a goddess
|
|
||||||
lives there, daughter of the magician Atlas, who looks after the bottom
|
|
||||||
of the ocean, and carries the great columns that keep heaven and earth
|
|
||||||
asunder. This daughter of Atlas has got hold of poor unhappy Ulysses,
|
|
||||||
and keeps trying by every kind of blandishment to make him forget
|
|
||||||
his home, so that he is tired of life, and thinks of nothing but how
|
|
||||||
he may once more see the smoke of his own chimneys. You, sir, take
|
|
||||||
no heed of this, and yet when Ulysses was before Troy did he not propitiate
|
|
||||||
you with many a burnt sacrifice? Why then should you keep on being
|
|
||||||
so angry with him?"
|
|
||||||
|
|
||||||
And Jove said, "My child, what are you talking about? How can I forget
|
|
||||||
Ulysses than whom there is no more capable man on earth, nor more
|
|
||||||
liberal in his offerings to the immortal gods that live in heaven?
|
|
||||||
Bear in mind, however, that Neptune is still furious with Ulysses
|
|
||||||
for having blinded an eye of Polyphemus king of the Cyclopes. Polyphemus
|
|
||||||
is son to Neptune by the nymph Thoosa, daughter to the sea-king Phorcys;
|
|
||||||
therefore though he will not kill Ulysses outright, he torments him
|
|
||||||
by preventing him from getting home. Still, let us lay our heads together
|
|
||||||
and see how we can help him to return; Neptune will then be pacified,
|
|
||||||
for if we are all of a mind he can hardly stand out against us."
|
|
||||||
|
|
||||||
And Minerva said, "Father, son of Saturn, King of kings, if, then,
|
|
||||||
the gods now mean that Ulysses should get home, we should first send
|
|
||||||
Mercury to the Ogygian island to tell Calypso that we have made up
|
|
||||||
our minds and that he is to return. In the meantime I will go to Ithaca,
|
|
||||||
to put heart into Ulysses' son Telemachus; I will embolden him to
|
|
||||||
call the Achaeans in assembly, and speak out to the suitors of his
|
|
||||||
mother Penelope, who persist in eating up any number of his sheep
|
|
||||||
and oxen; I will also conduct him to Sparta and to Pylos, to see if
|
|
||||||
he can hear anything about the return of his dear father- for this
|
|
||||||
will make people speak well of him."
|
|
||||||
|
|
||||||
Ellipsis ellipsis ellipsis ellipsis ellipsis ellipsis ellipsis ellipsis.
|
|
||||||
Ellipsis ellipsis ellipsis ellipsis ellipsis ellipsis ellipsis ellipsis.
|
|
||||||
Ellipsis ellipsis ellipsis ellipsis ellipsis ellipsis ellipsis ellipsis.
|
|
||||||
Ellipsis ellipsis ellipsis ellipsis ellipsis ellipsis ellipsis ellipsis.
|
|
||||||
Ellipsis ellipsis ellipsis ellipsis ellipsis ellipsis ellipsis ellipsis.
|
|
||||||
Ellipsis ellipsis ellipsis ellipsis ellipsis ellipsis ellipsis ellipsis.
|
|
||||||
Ellipsis ellipsis ellipsis ellipsis ellipsis ellipsis ellipsis ellipsis.
|
|
||||||
Ellipsis ellipsis ellipsis ellipsis ellipsis ellipsis ellipsis ellipsis.
|
|
||||||
Ellipsis ellipsis ellipsis ellipsis ellipsis ellipsis ellipsis ellipsis.
|
|
||||||
|
|
||||||
"Men of Ithaca, it is all your own fault that things have turned out
|
|
||||||
as they have; you would not listen to me, nor yet to Mentor, when
|
|
||||||
we bade you check the folly of your sons who were doing much wrong
|
|
||||||
in the wantonness of their hearts- wasting the substance and dishonouring
|
|
||||||
the wife of a chieftain who they thought would not return. Now, however,
|
|
||||||
let it be as I say, and do as I tell you. Do not go out against Ulysses,
|
|
||||||
or you may find that you have been drawing down evil on your own heads."
|
|
||||||
|
|
||||||
This was what he said, and more than half raised a loud shout, and
|
|
||||||
at once left the assembly. But the rest stayed where they were, for
|
|
||||||
the speech of Halitherses displeased them, and they sided with Eupeithes;
|
|
||||||
they therefore hurried off for their armour, and when they had armed
|
|
||||||
themselves, they met together in front of the city, and Eupeithes
|
|
||||||
led them on in their folly. He thought he was going to avenge the
|
|
||||||
murder of his son, whereas in truth he was never to return, but was
|
|
||||||
himself to perish in his attempt.
|
|
||||||
|
|
||||||
Then Minerva said to Jove, "Father, son of Saturn, king of kings,
|
|
||||||
answer me this question- What do you propose to do? Will you set them
|
|
||||||
fighting still further, or will you make peace between them?"
|
|
||||||
|
|
||||||
And Jove answered, "My child, why should you ask me? Was it not by
|
|
||||||
your own arrangement that Ulysses came home and took his revenge upon
|
|
||||||
the suitors? Do whatever you like, but I will tell you what I think
|
|
||||||
will be most reasonable arrangement. Now that Ulysses is revenged,
|
|
||||||
let them swear to a solemn covenant, in virtue of which he shall continue
|
|
||||||
to rule, while we cause the others to forgive and forget the massacre
|
|
||||||
of their sons and brothers. Let them then all become friends as heretofore,
|
|
||||||
and let peace and plenty reign."
|
|
||||||
|
|
||||||
This was what Minerva was already eager to bring about, so down she
|
|
||||||
darted from off the topmost summits of Olympus.
|
|
||||||
|
|
||||||
Now when Laertes and the others had done dinner, Ulysses began by
|
|
||||||
saying, "Some of you go out and see if they are not getting close
|
|
||||||
up to us." So one of Dolius's sons went as he was bid. Standing on
|
|
||||||
the threshold he could see them all quite near, and said to Ulysses,
|
|
||||||
"Here they are, let us put on our armour at once."
|
|
||||||
|
|
||||||
They put on their armour as fast as they could- that is to say Ulysses,
|
|
||||||
his three men, and the six sons of Dolius. Laertes also and Dolius
|
|
||||||
did the same- warriors by necessity in spite of their grey hair. When
|
|
||||||
they had all put on their armour, they opened the gate and sallied
|
|
||||||
forth, Ulysses leading the way.
|
|
||||||
|
|
||||||
Then Jove's daughter Minerva came up to them, having assumed the form
|
|
||||||
and voice of Mentor. Ulysses was glad when he saw her, and said to
|
|
||||||
his son Telemachus, "Telemachus, now that are about to fight in an
|
|
||||||
engagement, which will show every man's mettle, be sure not to disgrace
|
|
||||||
your ancestors, who were eminent for their strength and courage all
|
|
||||||
the world over."
|
|
||||||
|
|
||||||
"You say truly, my dear father," answered Telemachus, "and you shall
|
|
||||||
see, if you will, that I am in no mind to disgrace your family."
|
|
||||||
|
|
||||||
Laertes was delighted when he heard this. "Good heavens, he exclaimed,
|
|
||||||
"what a day I am enjoying: I do indeed rejoice at it. My son and grandson
|
|
||||||
are vying with one another in the matter of valour."
|
|
||||||
|
|
||||||
On this Minerva came close up to him and said, "Son of Arceisius-
|
|
||||||
best friend I have in the world- pray to the blue-eyed damsel, and
|
|
||||||
to Jove her father; then poise your spear and hurl it."
|
|
||||||
|
|
||||||
As she spoke she infused fresh vigour into him, and when he had prayed
|
|
||||||
to her he poised his spear and hurled it. He hit Eupeithes' helmet,
|
|
||||||
and the spear went right through it, for the helmet stayed it not,
|
|
||||||
and his armour rang rattling round him as he fell heavily to the ground.
|
|
||||||
Meantime Ulysses and his son fell the front line of the foe and smote
|
|
||||||
them with their swords and spears; indeed, they would have killed
|
|
||||||
every one of them, and prevented them from ever getting home again,
|
|
||||||
only Minerva raised her voice aloud, and made every one pause. "Men
|
|
||||||
of Ithaca," she cried, cease this dreadful war, and settle the matter
|
|
||||||
at once without further bloodshed."
|
|
||||||
|
|
||||||
On this pale fear seized every one; they were so frightened that their
|
|
||||||
arms dropped from their hands and fell upon the ground at the sound
|
|
||||||
of the goddess's voice, and they fled back to the city for their lives.
|
|
||||||
But Ulysses gave a great cry, and gathering himself together swooped
|
|
||||||
down like a soaring eagle. Then the son of Saturn sent a thunderbolt
|
|
||||||
of fire that fell just in front of Minerva, so she said to Ulysses,
|
|
||||||
"Ulysses, noble son of Laertes, stop this warful strife, or Jove will
|
|
||||||
be angry with you."
|
|
||||||
|
|
||||||
Thus spoke Minerva, and Ulysses obeyed her gladly. Then Minerva assumed
|
|
||||||
the form and voice of Mentor, and presently made a covenant of peace
|
|
||||||
between the two contending parties.
|
|
||||||
|
|
||||||
THE END
|
|
||||||
|
|
||||||
----------------------------------------------------------------------
|
|
||||||
|
|
||||||
Copyright statement:
|
|
||||||
The Internet Classics Archive by Daniel C. Stevenson, Web Atomics.
|
|
||||||
World Wide Web presentation is copyright (C) 1994-1998, Daniel
|
|
||||||
C. Stevenson, Web Atomics.
|
|
||||||
All rights reserved under international and pan-American copyright
|
|
||||||
conventions, including the right of reproduction in whole or in part
|
|
||||||
in any form. Direct permission requests to classics@classics.mit.edu.
|
|
||||||
Translation of "The Deeds of the Divine Augustus" by Augustus is
|
|
||||||
copyright (C) Thomas Bushnell, BSG.
|
|
||||||
|
|
||||||
|
|
||||||
To really test that reportlab can produce pages quickly download the
|
|
||||||
complete version of the test from http://classics.mit.edu//Homer/odyssey.html
|
|
||||||
and copy it to this directory as odyssey.full.txt.
|
|
||||||
|
|
||||||
A zipped version of the full text is available for download at
|
|
||||||
ftp://ftp.reportlab.com/odyssey.full.zip
|
|
|
@ -1,73 +0,0 @@
|
||||||
# rlzope : an external Zope method to show people how to use
|
|
||||||
# the ReportLab toolkit from within Zope.
|
|
||||||
#
|
|
||||||
# this method searches an image named 'logo' in the
|
|
||||||
# ZODB then prints it at the top of a simple PDF
|
|
||||||
# document made with ReportLab
|
|
||||||
#
|
|
||||||
# the resulting PDF document is returned to the
|
|
||||||
# user's web browser and, if possible, it is
|
|
||||||
# simultaneously saved into the ZODB.
|
|
||||||
#
|
|
||||||
# this method illustrates how to use both the platypus
|
|
||||||
# and canvas frameworks.
|
|
||||||
#
|
|
||||||
# License : The ReportLab Toolkit's license (similar to BSD)
|
|
||||||
#
|
|
||||||
# Author : Jerome Alet - alet@unice.fr
|
|
||||||
#
|
|
||||||
|
|
||||||
Installation instructions :
|
|
||||||
===========================
|
|
||||||
|
|
||||||
0 - If not installed then install Zope.
|
|
||||||
|
|
||||||
1 - Install reportlab in the Zope/lib/python/Shared directory by unpacking
|
|
||||||
the tarball and putting a reportlabs.pth file in site-packages for the Zope
|
|
||||||
used with Python. The path value in the reportlabs.pth file must be
|
|
||||||
relative. For a typical Zope installation, the path is "../../python/Shared".
|
|
||||||
Remember to restart Zope so the new path is instantiated.
|
|
||||||
|
|
||||||
2 - Install PIL in the Zope/lib/python/Shared directory. You need to
|
|
||||||
ensure that the _imaging.so or .pyd is also installed appropriately.
|
|
||||||
It should be compatible with the python running the zope site.
|
|
||||||
|
|
||||||
3 - Copy rlzope.py to your Zope installation's "Extensions"
|
|
||||||
subdirectory, e.g. /var/lib/zope/Extensions/ under Debian GNU/Linux.
|
|
||||||
|
|
||||||
4 - From within Zope's management interface, add an External Method with
|
|
||||||
these parameters :
|
|
||||||
|
|
||||||
Id : rlzope
|
|
||||||
Title : rlzope
|
|
||||||
Module Name : rlzope
|
|
||||||
Function Name : rlzope
|
|
||||||
|
|
||||||
5 - From within Zope's management interface, add an image called "logo"
|
|
||||||
in the same Folder than rlzope, or somewhere above in the Folder
|
|
||||||
hierarchy. For example you can use ReportLab's logo which you
|
|
||||||
can find in reportlab/docs/images/replogo.gif
|
|
||||||
|
|
||||||
6 - Point your web browser to rlzope, e.g. on my laptop under
|
|
||||||
Debian GNU/Linux :
|
|
||||||
|
|
||||||
http://localhost:9673/rlzope
|
|
||||||
|
|
||||||
This will send a simple PDF document named 'dummy.pdf' to your
|
|
||||||
web browser, and if possible save it as a File object in the
|
|
||||||
Zope Object DataBase, with this name. Note, however, that if
|
|
||||||
an object with the same name already exists then it won't
|
|
||||||
be replaced for security reasons.
|
|
||||||
|
|
||||||
You can optionally add a parameter called 'name' with
|
|
||||||
a filename as the value, to specify another filename,
|
|
||||||
e.g. :
|
|
||||||
logo
|
|
||||||
http://localhost:9673/rlzope?name=sample.pdf
|
|
||||||
|
|
||||||
7 - Adapt it to your own needs.
|
|
||||||
|
|
||||||
8 - Enjoy !
|
|
||||||
|
|
||||||
Send comments or bug reports at : alet@unice.fr
|
|
||||||
|
|
|
@ -1,169 +0,0 @@
|
||||||
#
|
|
||||||
# Using the ReportLab toolkit from within Zope
|
|
||||||
#
|
|
||||||
# WARNING : The MyPDFDoc class deals with ReportLab's platypus framework,
|
|
||||||
# while the MyPageTemplate class directly deals with ReportLab's
|
|
||||||
# canvas, this way you know how to do with both...
|
|
||||||
#
|
|
||||||
# License : the ReportLab Toolkit's one
|
|
||||||
# see : http://www.reportlab.com
|
|
||||||
#
|
|
||||||
# Author : Jerome Alet - alet@unice.fr
|
|
||||||
#
|
|
||||||
#
|
|
||||||
|
|
||||||
import string, cStringIO
|
|
||||||
try :
|
|
||||||
from Shared.reportlab.platypus.paragraph import Paragraph
|
|
||||||
from Shared.reportlab.platypus.doctemplate import *
|
|
||||||
from Shared.reportlab.lib.units import inch
|
|
||||||
from Shared.reportlab.lib import styles
|
|
||||||
from Shared.reportlab.lib.utils import ImageReader
|
|
||||||
except ImportError :
|
|
||||||
from reportlab.platypus.paragraph import Paragraph
|
|
||||||
from reportlab.platypus.doctemplate import *
|
|
||||||
from reportlab.lib.units import inch
|
|
||||||
from reportlab.lib import styles
|
|
||||||
from reportlab.lib.utils import ImageReader
|
|
||||||
|
|
||||||
class MyPDFDoc :
|
|
||||||
class MyPageTemplate(PageTemplate) :
|
|
||||||
"""Our own page template."""
|
|
||||||
def __init__(self, parent) :
|
|
||||||
"""Initialise our page template."""
|
|
||||||
#
|
|
||||||
# we must save a pointer to our parent somewhere
|
|
||||||
self.parent = parent
|
|
||||||
|
|
||||||
# Our doc is made of a single frame
|
|
||||||
content = Frame(0.75 * inch, 0.5 * inch, parent.document.pagesize[0] - 1.25 * inch, parent.document.pagesize[1] - (1.5 * inch))
|
|
||||||
PageTemplate.__init__(self, "MyTemplate", [content])
|
|
||||||
|
|
||||||
# get all the images we need now, in case we've got
|
|
||||||
# several pages this will save some CPU
|
|
||||||
self.logo = self.getImageFromZODB("logo")
|
|
||||||
|
|
||||||
def getImageFromZODB(self, name) :
|
|
||||||
"""Retrieves an Image from the ZODB, converts it to PIL,
|
|
||||||
and makes it 0.75 inch high.
|
|
||||||
"""
|
|
||||||
try :
|
|
||||||
# try to get it from ZODB
|
|
||||||
logo = getattr(self.parent.context, name)
|
|
||||||
except AttributeError :
|
|
||||||
# not found !
|
|
||||||
return None
|
|
||||||
|
|
||||||
# Convert it to PIL
|
|
||||||
image = ImageReader(cStringIO.StringIO(str(logo.data)))
|
|
||||||
(width, height) = image.getSize()
|
|
||||||
|
|
||||||
# scale it to be 0.75 inch high
|
|
||||||
multi = ((height + 0.0) / (0.75 * inch))
|
|
||||||
width = int(width / multi)
|
|
||||||
height = int(height / multi)
|
|
||||||
|
|
||||||
return ((width, height), image)
|
|
||||||
|
|
||||||
def beforeDrawPage(self, canvas, doc) :
|
|
||||||
"""Draws a logo and an contribution message on each page."""
|
|
||||||
canvas.saveState()
|
|
||||||
if self.logo is not None :
|
|
||||||
# draws the logo if it exists
|
|
||||||
((width, height), image) = self.logo
|
|
||||||
canvas.drawImage(image, inch, doc.pagesize[1] - inch, width, height)
|
|
||||||
canvas.setFont('Times-Roman', 10)
|
|
||||||
canvas.drawCentredString(inch + (doc.pagesize[0] - (1.5 * inch)) / 2, 0.25 * inch, "Contributed by Jerome Alet - alet@unice.fr")
|
|
||||||
canvas.restoreState()
|
|
||||||
|
|
||||||
def __init__(self, context, filename) :
|
|
||||||
# save some datas
|
|
||||||
self.context = context
|
|
||||||
self.built = 0
|
|
||||||
self.objects = []
|
|
||||||
|
|
||||||
# we will build an in-memory document
|
|
||||||
# instead of creating an on-disk file.
|
|
||||||
self.report = cStringIO.StringIO()
|
|
||||||
|
|
||||||
# initialise a PDF document using ReportLab's platypus
|
|
||||||
self.document = BaseDocTemplate(self.report)
|
|
||||||
|
|
||||||
# add our page template
|
|
||||||
# (we could add more than one, but I prefer to keep it simple)
|
|
||||||
self.document.addPageTemplates(self.MyPageTemplate(self))
|
|
||||||
|
|
||||||
# get the default style sheets
|
|
||||||
self.StyleSheet = styles.getSampleStyleSheet()
|
|
||||||
|
|
||||||
# then build a simple doc with ReportLab's platypus
|
|
||||||
sometext = "A sample script to show how to use ReportLab from within Zope"
|
|
||||||
url = self.escapexml(context.absolute_url())
|
|
||||||
urlfilename = self.escapexml(context.absolute_url() + '/%s' % filename)
|
|
||||||
self.append(Paragraph("Using ReportLab from within Zope", self.StyleSheet["Heading3"]))
|
|
||||||
self.append(Spacer(0, 10))
|
|
||||||
self.append(Paragraph("You launched it from : %s" % url, self.StyleSheet['Normal']))
|
|
||||||
self.append(Spacer(0, 40))
|
|
||||||
self.append(Paragraph("If possible, this report will be automatically saved as : %s" % urlfilename, self.StyleSheet['Normal']))
|
|
||||||
|
|
||||||
# generation du document PDF
|
|
||||||
self.document.build(self.objects)
|
|
||||||
self.built = 1
|
|
||||||
|
|
||||||
def __str__(self) :
|
|
||||||
"""Returns the PDF document as a string of text, or None if it's not ready yet."""
|
|
||||||
if self.built :
|
|
||||||
return self.report.getvalue()
|
|
||||||
else :
|
|
||||||
return None
|
|
||||||
|
|
||||||
def append(self, object) :
|
|
||||||
"""Appends an object to our platypus "story" (using ReportLab's terminology)."""
|
|
||||||
self.objects.append(object)
|
|
||||||
|
|
||||||
def escapexml(self, s) :
|
|
||||||
"""Escape some xml entities."""
|
|
||||||
s = string.strip(s)
|
|
||||||
s = string.replace(s, "&", "&")
|
|
||||||
s = string.replace(s, "<", "<")
|
|
||||||
return string.replace(s, ">", ">")
|
|
||||||
|
|
||||||
def rlzope(self) :
|
|
||||||
"""A sample external method to show people how to use ReportLab from within Zope."""
|
|
||||||
try:
|
|
||||||
#
|
|
||||||
# which file/object name to use ?
|
|
||||||
# append ?name=xxxxx to rlzope's url to
|
|
||||||
# choose another name
|
|
||||||
filename = self.REQUEST.get("name", "dummy.pdf")
|
|
||||||
if filename[-4:] != '.pdf' :
|
|
||||||
filename = filename + '.pdf'
|
|
||||||
|
|
||||||
# tell the browser we send some PDF document
|
|
||||||
# with the requested filename
|
|
||||||
|
|
||||||
# get the document's content itself as a string of text
|
|
||||||
content = str(MyPDFDoc(self, filename))
|
|
||||||
|
|
||||||
# we will return it to the browser, but before that we also want to
|
|
||||||
# save it into the ZODB into the current folder
|
|
||||||
try :
|
|
||||||
self.manage_addFile(id = filename, file = content, title = "A sample PDF document produced with ReportLab", precondition = '', content_type = "application/pdf")
|
|
||||||
except :
|
|
||||||
# it seems an object with this name already exists in the ZODB:
|
|
||||||
# it's more secure to not replace it, since we could possibly
|
|
||||||
# destroy an important PDF document of this name.
|
|
||||||
pass
|
|
||||||
self.REQUEST.RESPONSE.setHeader('Content-Type', 'application/pdf')
|
|
||||||
self.REQUEST.RESPONSE.setHeader('Content-Disposition', 'attachment; filename=%s' % filename)
|
|
||||||
except:
|
|
||||||
import traceback, sys, cgi
|
|
||||||
content = sys.stdout = sys.stderr = cStringIO.StringIO()
|
|
||||||
self.REQUEST.RESPONSE.setHeader('Content-Type', 'text/html')
|
|
||||||
traceback.print_exc()
|
|
||||||
sys.stdout = sys.__stdout__
|
|
||||||
sys.stderr = sys.__stderr__
|
|
||||||
content = '<html><head></head><body><pre>%s</pre></body></html>' % cgi.escape(content.getvalue())
|
|
||||||
|
|
||||||
# then we also return the PDF content to the browser
|
|
||||||
return content
|
|
|
@ -1,7 +0,0 @@
|
||||||
This lists out the standard 14 fonts
|
|
||||||
in a very plain and simple fashion.
|
|
||||||
|
|
||||||
Notably, the output is huge - it makes
|
|
||||||
two separate text objects for each glyph.
|
|
||||||
Smarter programming would make tighter
|
|
||||||
PDF, but more lines of Python!
|
|
|
@ -1,74 +0,0 @@
|
||||||
#Copyright ReportLab Europe Ltd. 2000-2004
|
|
||||||
#see license.txt for license details
|
|
||||||
#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/demos/stdfonts/stdfonts.py
|
|
||||||
__version__=''' $Id: stdfonts.py 2830 2006-04-05 15:18:32Z rgbecker $ '''
|
|
||||||
__doc__="""
|
|
||||||
This generates tables showing the 14 standard fonts in both
|
|
||||||
WinAnsi and MacRoman encodings, and their character codes.
|
|
||||||
Supply an argument of 'hex' or 'oct' to get code charts
|
|
||||||
in those encodings; octal is what you need for \\n escape
|
|
||||||
sequences in Python literals.
|
|
||||||
|
|
||||||
usage: standardfonts.py [dec|hex|oct]
|
|
||||||
"""
|
|
||||||
import sys
|
|
||||||
from reportlab.pdfbase import pdfmetrics
|
|
||||||
from reportlab.pdfgen import canvas
|
|
||||||
import string
|
|
||||||
|
|
||||||
label_formats = {'dec':('%d=', 'Decimal'),
|
|
||||||
'oct':('%o=','Octal'),
|
|
||||||
'hex':('0x%x=', 'Hexadecimal')}
|
|
||||||
|
|
||||||
def run(mode):
|
|
||||||
|
|
||||||
label_formatter, caption = label_formats[mode]
|
|
||||||
|
|
||||||
for enc in ['MacRoman', 'WinAnsi']:
|
|
||||||
canv = canvas.Canvas(
|
|
||||||
'StandardFonts_%s.pdf' % enc,
|
|
||||||
)
|
|
||||||
canv.setPageCompression(0)
|
|
||||||
|
|
||||||
for faceName in pdfmetrics.standardFonts:
|
|
||||||
if faceName in ['Symbol', 'ZapfDingbats']:
|
|
||||||
encLabel = faceName+'Encoding'
|
|
||||||
else:
|
|
||||||
encLabel = enc + 'Encoding'
|
|
||||||
|
|
||||||
fontName = faceName + '-' + encLabel
|
|
||||||
pdfmetrics.registerFont(pdfmetrics.Font(fontName,
|
|
||||||
faceName,
|
|
||||||
encLabel)
|
|
||||||
)
|
|
||||||
|
|
||||||
canv.setFont('Times-Bold', 18)
|
|
||||||
canv.drawString(80, 744, fontName)
|
|
||||||
canv.setFont('Times-BoldItalic', 12)
|
|
||||||
canv.drawRightString(515, 744, 'Labels in ' + caption)
|
|
||||||
|
|
||||||
|
|
||||||
#for dingbats, we need to use another font for the numbers.
|
|
||||||
#do two parallel text objects.
|
|
||||||
for byt in range(32, 256):
|
|
||||||
col, row = divmod(byt - 32, 32)
|
|
||||||
x = 72 + (66*col)
|
|
||||||
y = 720 - (18*row)
|
|
||||||
canv.setFont('Helvetica', 14)
|
|
||||||
canv.drawString(x, y, label_formatter % byt)
|
|
||||||
canv.setFont(fontName, 14)
|
|
||||||
canv.drawString(x+44, y, chr(byt).decode(encLabel,'ignore').encode('utf8'))
|
|
||||||
canv.showPage()
|
|
||||||
canv.save()
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
if len(sys.argv)==2:
|
|
||||||
mode = string.lower(sys.argv[1])
|
|
||||||
if mode not in ['dec','oct','hex']:
|
|
||||||
print __doc__
|
|
||||||
|
|
||||||
elif len(sys.argv) == 1:
|
|
||||||
mode = 'dec'
|
|
||||||
run(mode)
|
|
||||||
else:
|
|
||||||
print __doc__
|
|
|
@ -1,14 +0,0 @@
|
||||||
#!/bin/env python
|
|
||||||
#Copyright ReportLab Europe Ltd. 2000-2004
|
|
||||||
#see license.txt for license details
|
|
||||||
#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/demos/tests/testdemos.py
|
|
||||||
__version__=''' $Id: testdemos.py 2385 2004-06-17 15:26:05Z rgbecker $ '''
|
|
||||||
__doc__='Test all demos'
|
|
||||||
_globals=globals().copy()
|
|
||||||
import os, sys
|
|
||||||
from reportlab import pdfgen
|
|
||||||
|
|
||||||
for p in ('pythonpoint/pythonpoint.py','stdfonts/stdfonts.py','odyssey/odyssey.py', 'gadflypaper/gfe.py'):
|
|
||||||
fn = os.path.normcase(os.path.normpath(os.path.join(os.path.dirname(pdfgen.__file__),'..','demos',p)))
|
|
||||||
os.chdir(os.path.dirname(fn))
|
|
||||||
execfile(fn,_globals.copy())
|
|
|
@ -1,7 +0,0 @@
|
||||||
Thid directory holds documentation. For end users,
|
|
||||||
it should contain a number of PDF manuals. For
|
|
||||||
people working with the source, this directory will
|
|
||||||
be the destination for any manuals built.
|
|
||||||
|
|
||||||
If you don't see the pdf manual you expected or you wich to
|
|
||||||
ensure an up to date copy run the script tools/genAll.py!
|
|
|
@ -1,37 +0,0 @@
|
||||||
#!/bin/env python
|
|
||||||
import os, sys
|
|
||||||
def _genAll(d=None,verbose=1):
|
|
||||||
if not d: d = '.'
|
|
||||||
if not os.path.isabs(d):
|
|
||||||
d = os.path.normpath(os.path.join(os.getcwd(),d))
|
|
||||||
L = ['reference/genreference.py',
|
|
||||||
'userguide/genuserguide.py',
|
|
||||||
'graphguide/gengraphguide.py',
|
|
||||||
'../tools/docco/graphdocpy.py',
|
|
||||||
]
|
|
||||||
if os.path.isdir('../rl_addons'):
|
|
||||||
L = L + ['../rl_addons/pyRXP/docs/PyRXP_Documentation.rml']
|
|
||||||
elif os.path.isdir('../../rl_addons'):
|
|
||||||
L = L + ['../../rl_addons/pyRXP/docs/PyRXP_Documentation.rml']
|
|
||||||
for p in L:
|
|
||||||
os.chdir(d)
|
|
||||||
os.chdir(os.path.dirname(p))
|
|
||||||
if p[-4:]=='.rml':
|
|
||||||
try:
|
|
||||||
from rlextra.rml2pdf.rml2pdf import main
|
|
||||||
main(exe=0,fn=[os.path.basename(p)], quiet=not verbose, outDir=d)
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
cmd = '%s %s %s' % (sys.executable,os.path.basename(p), not verbose and '-s' or '')
|
|
||||||
if verbose: print cmd
|
|
||||||
os.system(cmd)
|
|
||||||
|
|
||||||
"""Runs the manual-building scripts"""
|
|
||||||
if __name__=='__main__':
|
|
||||||
#need a quiet mode for the test suite
|
|
||||||
if '-s' in sys.argv: # 'silent
|
|
||||||
verbose = 0
|
|
||||||
else:
|
|
||||||
verbose = 1
|
|
||||||
_genAll(os.path.dirname(sys.argv[0]),verbose)
|
|
|
@ -1,105 +0,0 @@
|
||||||
#Copyright ReportLab Europe Ltd. 2000-2004
|
|
||||||
#see license.txt for license details
|
|
||||||
#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/docs/graphguide/ch1_intro.py
|
|
||||||
from reportlab.tools.docco.rl_doc_utils import *
|
|
||||||
import reportlab
|
|
||||||
|
|
||||||
title("Graphics Guide")
|
|
||||||
centred('ReportLab Version ' + reportlab.Version)
|
|
||||||
|
|
||||||
nextTemplate("Normal")
|
|
||||||
|
|
||||||
########################################################################
|
|
||||||
#
|
|
||||||
# Chapter 1
|
|
||||||
#
|
|
||||||
########################################################################
|
|
||||||
|
|
||||||
|
|
||||||
heading1("Introduction")
|
|
||||||
|
|
||||||
|
|
||||||
heading2("About this document")
|
|
||||||
disc("""
|
|
||||||
This document is intended to be a helpful and reasonably full
|
|
||||||
introduction to the use of the ReportLab Graphics sub-package.
|
|
||||||
Starting with simple drawings and shapes, we will take you through the
|
|
||||||
slightly more complex reusable widgets all the way through to our
|
|
||||||
powerful and flexible chart library. You will see examples of using
|
|
||||||
reportlab/graphics to make bar charts, line charts, line plots, pie
|
|
||||||
charts... and a smiley face.
|
|
||||||
""")
|
|
||||||
|
|
||||||
disc("""
|
|
||||||
We presume that you have already installed both the Python programming
|
|
||||||
language and the core ReportLab library. If you have not done either
|
|
||||||
of these, look in the ReportLab User Guide where chapter one
|
|
||||||
talks you through all the required steps.
|
|
||||||
""")
|
|
||||||
|
|
||||||
disc("""
|
|
||||||
We recommend that you read some or all of the User Guide and have at
|
|
||||||
least a basic understanding of how the ReportLab library works before
|
|
||||||
you start getting to grips with ReportLab Graphics.
|
|
||||||
""")
|
|
||||||
|
|
||||||
disc("")
|
|
||||||
todo("""
|
|
||||||
Be warned! This document is in a <em>very</em> preliminary form. We need
|
|
||||||
your help to make sure it is complete and helpful. Please send any
|
|
||||||
feedback to our user mailing list, reportlab-users@reportlab.com.
|
|
||||||
""")
|
|
||||||
|
|
||||||
heading2("What is ReportLab?")
|
|
||||||
disc("""ReportLab is a software library that lets you directly
|
|
||||||
create documents in Adobe's Portable Document Format (PDF) using
|
|
||||||
the Python programming language. """)
|
|
||||||
|
|
||||||
disc("""The ReportLab library directly creates PDF based on
|
|
||||||
your graphics commands. There are no intervening steps. Your applications
|
|
||||||
can generate reports extremely fast - sometimes orders
|
|
||||||
of magnitude faster than traditional report-writing
|
|
||||||
tools.""")
|
|
||||||
|
|
||||||
heading2("What is ReportLab Graphics?")
|
|
||||||
disc("""
|
|
||||||
ReportLab Graphics is one of the sub-packages to the ReportLab
|
|
||||||
library. It started off as a stand-alone set of programs, but is now a
|
|
||||||
fully integrated part of the ReportLab toolkit that allows you to use
|
|
||||||
its powerful charting and graphics features to improve your PDF forms
|
|
||||||
and reports.
|
|
||||||
""")
|
|
||||||
|
|
||||||
heading2("Getting Involved")
|
|
||||||
disc("""ReportLab is an Open Source project. Although we are
|
|
||||||
a commercial company we provide the core PDF generation
|
|
||||||
sources freely, even for commercial purposes, and we make no income directly
|
|
||||||
from these modules. We also welcome help from the community
|
|
||||||
as much as any other Open Source project. There are many
|
|
||||||
ways in which you can help:""")
|
|
||||||
|
|
||||||
bullet("""General feedback on the core API. Does it work for you?
|
|
||||||
Are there any rough edges? Does anything feel clunky and awkward?""")
|
|
||||||
|
|
||||||
bullet("""New objects to put in reports, or useful utilities for the library.
|
|
||||||
We have an open standard for report objects, so if you have written a nice
|
|
||||||
chart or table class, why not contribute it?""")
|
|
||||||
|
|
||||||
bullet("""Demonstrations and Case Studies: If you have produced some nice
|
|
||||||
output, send it to us (with or without scripts). If ReportLab solved a
|
|
||||||
problem for you at work, write a little 'case study' and send it in.
|
|
||||||
And if your web site uses our tools to make reports, let us link to it.
|
|
||||||
We will be happy to display your work (and credit it with your name
|
|
||||||
and company) on our site!""")
|
|
||||||
|
|
||||||
bullet("""Working on the core code: we have a long list of things
|
|
||||||
to refine or to implement. If you are missing some features or
|
|
||||||
just want to help out, let us know!""")
|
|
||||||
|
|
||||||
disc("""The first step for anyone wanting to learn more or
|
|
||||||
get involved is to join the mailing list. To Subscribe visit
|
|
||||||
$http://two.pairlist.net/mailman/listinfo/reportlab-users$.
|
|
||||||
From there you can also browse through the group's archives
|
|
||||||
and contributions. The mailing list is
|
|
||||||
the place to report bugs and get support. """)
|
|
||||||
|
|
|
@ -1,376 +0,0 @@
|
||||||
#Copyright ReportLab Europe Ltd. 2000-2004
|
|
||||||
#see license.txt for license details
|
|
||||||
#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/docs/graphguide/ch2_concepts.py
|
|
||||||
from reportlab.tools.docco.rl_doc_utils import *
|
|
||||||
|
|
||||||
heading1("General Concepts")
|
|
||||||
|
|
||||||
disc("""
|
|
||||||
In this chapter we will present some of the more fundamental principles of
|
|
||||||
the graphics library, which will show-up later in various places.
|
|
||||||
""")
|
|
||||||
|
|
||||||
|
|
||||||
heading2("Drawings and Renderers")
|
|
||||||
|
|
||||||
disc("""
|
|
||||||
A <i>Drawing</i> is a platform-independent description of a collection of
|
|
||||||
shapes.
|
|
||||||
It is not directly associated with PDF, Postscript or any other output
|
|
||||||
format.
|
|
||||||
Fortunately, most vector graphics systems have followed the Postscript
|
|
||||||
model and it is possible to describe shapes unambiguously.
|
|
||||||
""")
|
|
||||||
|
|
||||||
disc("""
|
|
||||||
A drawing contains a number of primitive <i>Shapes</i>.
|
|
||||||
Normal shapes are those widely known as rectangles, circles, lines,
|
|
||||||
etc.
|
|
||||||
One special (logic) shape is a <i>Group</i>, which can hold other
|
|
||||||
shapes and apply a transformation to them.
|
|
||||||
Groups represent composites of shapes and allow to treat the
|
|
||||||
composite as if it were a single shape.
|
|
||||||
Just about anything can be built up from a small number of basic
|
|
||||||
shapes.
|
|
||||||
""")
|
|
||||||
|
|
||||||
disc("""
|
|
||||||
The package provides several <i>Renderers</i> which know how to draw a
|
|
||||||
drawing into different formats.
|
|
||||||
These include PDF (of course), Postscript, and bitmap output.
|
|
||||||
The bitmap renderer uses Raph Levien's <i>libart</i> rasterizer
|
|
||||||
and Fredrik Lundh's <i>Python Imaging Library</i> (PIL).
|
|
||||||
Very recently, an experimental SVG renderer was also added.
|
|
||||||
It makes use of Python's standard library XML modules, so you don't
|
|
||||||
need to install the XML-SIG's additional package named PyXML.
|
|
||||||
If you have the right extensions installed, you can generate drawings
|
|
||||||
in bitmap form for the web as well as vector form for PDF documents,
|
|
||||||
and get "identical output".
|
|
||||||
""")
|
|
||||||
|
|
||||||
disc("""
|
|
||||||
The PDF renderer has special "privileges" - a Drawing object is also
|
|
||||||
a <i>Flowable</i> and, hence, can be placed directly in the story
|
|
||||||
of any Platypus document, or drawn directly on a <i>Canvas</i> with
|
|
||||||
one line of code.
|
|
||||||
In addition, the PDF renderer has a utility function to make
|
|
||||||
a one-page PDF document quickly.
|
|
||||||
""")
|
|
||||||
|
|
||||||
disc("""
|
|
||||||
The SVG renderer is special as it is still pretty experimental.
|
|
||||||
The SVG code it generates is not really optimised in any way and
|
|
||||||
maps only the features available in ReportLab Graphics (RLG) to
|
|
||||||
SVG. This means there is no support for SVG animation, interactivity,
|
|
||||||
scripting or more sophisticated clipping, masking or graduation
|
|
||||||
shapes.
|
|
||||||
So, be careful, and please report any bugs you find!
|
|
||||||
""")
|
|
||||||
|
|
||||||
disc("""
|
|
||||||
We expect to add both input and output filters for many vector
|
|
||||||
graphics formats in future.
|
|
||||||
SVG was the most prominent first one to start with for which there
|
|
||||||
is now an output filter in the graphics package.
|
|
||||||
An SVG input filter will probably become available in Summer 2002
|
|
||||||
as an additional module.
|
|
||||||
GUIs will be able to obtain screen images from the bitmap output
|
|
||||||
filter working with PIL, so a chart could appear in a Tkinter
|
|
||||||
GUI window.
|
|
||||||
""")
|
|
||||||
|
|
||||||
|
|
||||||
heading2("Coordinate System")
|
|
||||||
|
|
||||||
disc("""
|
|
||||||
The Y-direction in our X-Y coordinate system points from the
|
|
||||||
bottom <i>up</i>.
|
|
||||||
This is consistent with PDF, Postscript and mathematical notation.
|
|
||||||
It also appears to be more natural for people, especially when
|
|
||||||
working with charts.
|
|
||||||
Note that in other graphics models (such as SVG) the Y-coordinate
|
|
||||||
points <i>down</i>.
|
|
||||||
For the SVG renderer this is actually no problem as it will take
|
|
||||||
your drawings and flip things as needed, so your SVG output looks
|
|
||||||
just as expected.
|
|
||||||
""")
|
|
||||||
|
|
||||||
disc("""
|
|
||||||
The X-coordinate points, as usual, from left to right.
|
|
||||||
So far there doesn't seem to be any model advocating the opposite
|
|
||||||
direction - at least not yet (with interesting exceptions, as it
|
|
||||||
seems, for Arabs looking at time series charts...).
|
|
||||||
""")
|
|
||||||
|
|
||||||
|
|
||||||
heading2("Getting Started")
|
|
||||||
|
|
||||||
disc("""
|
|
||||||
Let's create a simple drawing containing the string "Hello World",
|
|
||||||
displayed on top of a coloured rectangle.
|
|
||||||
After creating it we will save the drawing to a standalone PDF file.
|
|
||||||
""")
|
|
||||||
|
|
||||||
eg("""
|
|
||||||
from reportlab.lib import colors
|
|
||||||
from reportlab.graphics.shapes import *
|
|
||||||
|
|
||||||
d = Drawing(400, 200)
|
|
||||||
d.add(Rect(50, 50, 300, 100, fillColor=colors.yellow))
|
|
||||||
d.add(String(150,100, 'Hello World',
|
|
||||||
fontSize=18, fillColor=colors.red))
|
|
||||||
|
|
||||||
from reportlab.graphics import renderPDF
|
|
||||||
renderPDF.drawToFile(d, 'example1.pdf', 'My First Drawing')
|
|
||||||
""")
|
|
||||||
|
|
||||||
disc("This will produce a PDF file containing the following graphic:")
|
|
||||||
|
|
||||||
from reportlab.graphics.shapes import *
|
|
||||||
from reportlab.graphics import testshapes
|
|
||||||
t = testshapes.getDrawing01()
|
|
||||||
draw(t, "'Hello World'")
|
|
||||||
|
|
||||||
disc("""
|
|
||||||
Each renderer is allowed to do whatever is appropriate for its format,
|
|
||||||
and may have whatever API is needed.
|
|
||||||
If it refers to a file format, it usually has a $drawToFile$ function,
|
|
||||||
and that's all you need to know about the renderer.
|
|
||||||
Let's save the same drawing in Encapsulated Postscript format:
|
|
||||||
""")
|
|
||||||
|
|
||||||
##eg("""
|
|
||||||
## from reportlab.graphics import renderPS
|
|
||||||
## renderPS.drawToFile(D, 'example1.eps', 'My First Drawing')
|
|
||||||
##""")
|
|
||||||
eg("""
|
|
||||||
from reportlab.graphics import renderPS
|
|
||||||
renderPS.drawToFile(d, 'example1.eps')
|
|
||||||
""")
|
|
||||||
|
|
||||||
disc("""
|
|
||||||
This will produce an EPS file with the identical drawing, which
|
|
||||||
may be imported into publishing tools such as Quark Express.
|
|
||||||
If we wanted to generate the same drawing as a bitmap file for
|
|
||||||
a website, say, all we need to do is write code like this:
|
|
||||||
""")
|
|
||||||
|
|
||||||
eg("""
|
|
||||||
from reportlab.graphics import renderPM
|
|
||||||
renderPM.saveToFile(d, 'example1.png', 'PNG')
|
|
||||||
""")
|
|
||||||
|
|
||||||
disc("""
|
|
||||||
Many other bitmap formats, like GIF, JPG, TIFF, BMP and PPN are
|
|
||||||
genuinely available, making it unlikely you'll need to add external
|
|
||||||
postprocessing steps to convert to the final format you need.
|
|
||||||
""")
|
|
||||||
|
|
||||||
disc("""
|
|
||||||
To produce an SVG file containing the identical drawing, which
|
|
||||||
may be imported into graphical editing tools such as Illustrator
|
|
||||||
all we need to do is write code like this:
|
|
||||||
""")
|
|
||||||
|
|
||||||
eg("""
|
|
||||||
from reportlab.graphics import renderSVG
|
|
||||||
renderSVG.drawToFile(d, 'example1.svg')
|
|
||||||
""")
|
|
||||||
|
|
||||||
|
|
||||||
heading2("Attribute Verification")
|
|
||||||
|
|
||||||
disc("""
|
|
||||||
Python is very dynamic and lets us execute statements at run time that
|
|
||||||
can easily be the source for unexpected behaviour.
|
|
||||||
One subtle 'error' is when assigning to an attribute that the framework
|
|
||||||
doesn't know about because the used attribute's name contains a typo.
|
|
||||||
Python lets you get away with it (adding a new attribute to an object,
|
|
||||||
say), but the graphics framework will not detect this 'typo' without
|
|
||||||
taking special counter-measures.
|
|
||||||
""")
|
|
||||||
|
|
||||||
disc("""
|
|
||||||
There are two verification techniques to avoid this situation.
|
|
||||||
The default is for every object to check every assignment at run
|
|
||||||
time, such that you can only assign to 'legal' attributes.
|
|
||||||
This is what happens by default.
|
|
||||||
As this imposes a small performance penalty, this behaviour can
|
|
||||||
be turned off when you need it to be.
|
|
||||||
""")
|
|
||||||
|
|
||||||
eg("""
|
|
||||||
>>> r = Rect(10,10,200,100, fillColor=colors.red)
|
|
||||||
>>>
|
|
||||||
>>> r.fullColor = colors.green # note the typo
|
|
||||||
>>> r.x = 'not a number' # illegal argument type
|
|
||||||
>>> del r.width # that should confuse it
|
|
||||||
""")
|
|
||||||
|
|
||||||
disc("""
|
|
||||||
These statements would be caught by the compiler in a statically
|
|
||||||
typed language, but Python lets you get away with it.
|
|
||||||
The first error could leave you staring at the picture trying to
|
|
||||||
figure out why the colors were wrong.
|
|
||||||
The second error would probably become clear only later, when
|
|
||||||
some back-end tries to draw the rectangle.
|
|
||||||
The third, though less likely, results in an invalid object that
|
|
||||||
would not know how to draw itself.
|
|
||||||
""")
|
|
||||||
|
|
||||||
eg("""
|
|
||||||
>>> r = shapes.Rect(10,10,200,80)
|
|
||||||
>>> r.fullColor = colors.green
|
|
||||||
Traceback (most recent call last):
|
|
||||||
File "<interactive input>", line 1, in ?
|
|
||||||
File "C:\code\users\andy\graphics\shapes.py", line 254, in __setattr__
|
|
||||||
validateSetattr(self,attr,value) #from reportlab.lib.attrmap
|
|
||||||
File "C:\code\users\andy\lib\attrmap.py", line 74, in validateSetattr
|
|
||||||
raise AttributeError, "Illegal attribute '%s' in class %s" % (name, obj.__class__.__name__)
|
|
||||||
AttributeError: Illegal attribute 'fullColor' in class Rect
|
|
||||||
>>>
|
|
||||||
""")
|
|
||||||
|
|
||||||
disc("""
|
|
||||||
This imposes a performance penalty, so this behaviour can be turned
|
|
||||||
off when you need it to be.
|
|
||||||
To do this, you should use the following lines of code before you
|
|
||||||
first import reportlab.graphics.shapes:
|
|
||||||
""")
|
|
||||||
|
|
||||||
eg("""
|
|
||||||
>>> import reportlab.rl_config
|
|
||||||
>>> reportlab.rl_config.shapeChecking = 0
|
|
||||||
>>> from reportlab.graphics import shapes
|
|
||||||
>>>
|
|
||||||
""")
|
|
||||||
|
|
||||||
disc("""
|
|
||||||
Once you turn off $shapeChecking$, the classes are actually built
|
|
||||||
without the verification hook; code should get faster, then.
|
|
||||||
Currently the penalty seems to be about 25% on batches of charts,
|
|
||||||
so it is hardly worth disabling.
|
|
||||||
However, if we move the renderers to C in future (which is eminently
|
|
||||||
possible), the remaining 75% would shrink to almost nothing and
|
|
||||||
the saving from verification would be significant.
|
|
||||||
""")
|
|
||||||
|
|
||||||
disc("""
|
|
||||||
Each object, including the drawing itself, has a $verify()$ method.
|
|
||||||
This either succeeds, or raises an exception.
|
|
||||||
If you turn off automatic verification, then you should explicitly
|
|
||||||
call $verify()$ in testing when developing the code, or perhaps
|
|
||||||
once in a batch process.
|
|
||||||
""")
|
|
||||||
|
|
||||||
|
|
||||||
heading2("Property Editing")
|
|
||||||
|
|
||||||
disc("""
|
|
||||||
A cornerstone of the reportlab/graphics which we will cover below is
|
|
||||||
that you can automatically document widgets.
|
|
||||||
This means getting hold of all of their editable properties,
|
|
||||||
including those of their subcomponents.
|
|
||||||
""")
|
|
||||||
|
|
||||||
disc("""
|
|
||||||
Another goal is to be able to create GUIs and config files for
|
|
||||||
drawings.
|
|
||||||
A generic GUI can be built to show all editable properties
|
|
||||||
of a drawing, and let you modify them and see the results.
|
|
||||||
The Visual Basic or Delphi development environment are good
|
|
||||||
examples of this kind of thing.
|
|
||||||
In a batch charting application, a file could list all the
|
|
||||||
properties of all the components in a chart, and be merged
|
|
||||||
with a database query to make a batch of charts.
|
|
||||||
""")
|
|
||||||
|
|
||||||
disc("""
|
|
||||||
To support these applications we have two interfaces, $getProperties$
|
|
||||||
and $setProperties$, as well as a convenience method $dumpProperties$.
|
|
||||||
The first returns a dictionary of the editable properties of an
|
|
||||||
object; the second sets them en masse.
|
|
||||||
If an object has publicly exposed 'children' then one can recursively
|
|
||||||
set and get their properties too.
|
|
||||||
This will make much more sense when we look at <i>Widgets</i> later on,
|
|
||||||
but we need to put the support into the base of the framework.
|
|
||||||
""")
|
|
||||||
|
|
||||||
eg("""
|
|
||||||
>>> r = shapes.Rect(0,0,200,100)
|
|
||||||
>>> import pprint
|
|
||||||
>>> pprint.pprint(r.getProperties())
|
|
||||||
{'fillColor': Color(0.00,0.00,0.00),
|
|
||||||
'height': 100,
|
|
||||||
'rx': 0,
|
|
||||||
'ry': 0,
|
|
||||||
'strokeColor': Color(0.00,0.00,0.00),
|
|
||||||
'strokeDashArray': None,
|
|
||||||
'strokeLineCap': 0,
|
|
||||||
'strokeLineJoin': 0,
|
|
||||||
'strokeMiterLimit': 0,
|
|
||||||
'strokeWidth': 1,
|
|
||||||
'width': 200,
|
|
||||||
'x': 0,
|
|
||||||
'y': 0}
|
|
||||||
>>> r.setProperties({'x':20, 'y':30, 'strokeColor': colors.red})
|
|
||||||
>>> r.dumpProperties()
|
|
||||||
fillColor = Color(0.00,0.00,0.00)
|
|
||||||
height = 100
|
|
||||||
rx = 0
|
|
||||||
ry = 0
|
|
||||||
strokeColor = Color(1.00,0.00,0.00)
|
|
||||||
strokeDashArray = None
|
|
||||||
strokeLineCap = 0
|
|
||||||
strokeLineJoin = 0
|
|
||||||
strokeMiterLimit = 0
|
|
||||||
strokeWidth = 1
|
|
||||||
width = 200
|
|
||||||
x = 20
|
|
||||||
y = 30
|
|
||||||
>>> """)
|
|
||||||
|
|
||||||
disc("""
|
|
||||||
<i>Note: $pprint$ is the standard Python library module that allows
|
|
||||||
you to 'pretty print' output over multiple lines rather than having
|
|
||||||
one very long line.</i>
|
|
||||||
""")
|
|
||||||
|
|
||||||
disc("""
|
|
||||||
These three methods don't seem to do much here, but as we will see
|
|
||||||
they make our widgets framework much more powerful when dealing with
|
|
||||||
non-primitive objects.
|
|
||||||
""")
|
|
||||||
|
|
||||||
|
|
||||||
heading2("Naming Children")
|
|
||||||
|
|
||||||
disc("""
|
|
||||||
You can add objects to the $Drawing$ and $Group$ objects.
|
|
||||||
These normally go into a list of contents.
|
|
||||||
However, you may also give objects a name when adding them.
|
|
||||||
This allows you to refer to and possibly change any element
|
|
||||||
of a drawing after constructing it.
|
|
||||||
""")
|
|
||||||
|
|
||||||
eg("""
|
|
||||||
>>> d = shapes.Drawing(400, 200)
|
|
||||||
>>> s = shapes.String(10, 10, 'Hello World')
|
|
||||||
>>> d.add(s, 'caption')
|
|
||||||
>>> s.caption.text
|
|
||||||
'Hello World'
|
|
||||||
>>>
|
|
||||||
""")
|
|
||||||
|
|
||||||
disc("""
|
|
||||||
Note that you can use the same shape instance in several contexts
|
|
||||||
in a drawing; if you choose to use the same $Circle$ object in many
|
|
||||||
locations (e.g. a scatter plot) and use different names to access
|
|
||||||
it, it will still be a shared object and the changes will be
|
|
||||||
global.
|
|
||||||
""")
|
|
||||||
|
|
||||||
disc("""
|
|
||||||
This provides one paradigm for creating and modifying interactive
|
|
||||||
drawings.
|
|
||||||
""")
|
|
|
@ -1,416 +0,0 @@
|
||||||
#Copyright ReportLab Europe Ltd. 2000-2004
|
|
||||||
#see license.txt for license details
|
|
||||||
#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/docs/graphguide/ch3_shapes.py
|
|
||||||
|
|
||||||
from reportlab.tools.docco.rl_doc_utils import *
|
|
||||||
from reportlab.graphics.shapes import *
|
|
||||||
|
|
||||||
heading1("Shapes")
|
|
||||||
|
|
||||||
disc("""
|
|
||||||
This chapter describes the concept of shapes and their importance
|
|
||||||
as building blocks for all output generated by the graphics library.
|
|
||||||
Some properties of existing shapes and their relationship to
|
|
||||||
diagrams are presented and the notion of having different renderers
|
|
||||||
for different output formats is briefly introduced.
|
|
||||||
""")
|
|
||||||
|
|
||||||
heading2("Available Shapes")
|
|
||||||
|
|
||||||
disc("""
|
|
||||||
Drawings are made up of Shapes.
|
|
||||||
Absolutely anything can be built up by combining the same set of
|
|
||||||
primitive shapes.
|
|
||||||
The module $shapes.py$ supplies a number of primitive shapes and
|
|
||||||
constructs which can be added to a drawing.
|
|
||||||
They are:
|
|
||||||
""")
|
|
||||||
|
|
||||||
bullet("Rect")
|
|
||||||
bullet("Circle")
|
|
||||||
bullet("Ellipse")
|
|
||||||
bullet("Wedge (a pie slice)")
|
|
||||||
bullet("Polygon")
|
|
||||||
bullet("Line")
|
|
||||||
bullet("PolyLine")
|
|
||||||
bullet("String")
|
|
||||||
bullet("Group")
|
|
||||||
bullet("Path (<i>not implemented yet, but will be added in the future</i>)")
|
|
||||||
|
|
||||||
disc("""
|
|
||||||
The following drawing, taken from our test suite, shows most of the
|
|
||||||
basic shapes (except for groups).
|
|
||||||
Those with a filled green surface are also called <i>solid shapes</i>
|
|
||||||
(these are $Rect$, $Circle$, $Ellipse$, $Wedge$ and $Polygon$).
|
|
||||||
""")
|
|
||||||
|
|
||||||
from reportlab.graphics import testshapes
|
|
||||||
|
|
||||||
t = testshapes.getDrawing06()
|
|
||||||
draw(t, "Basic shapes")
|
|
||||||
|
|
||||||
|
|
||||||
heading2("Shape Properties")
|
|
||||||
|
|
||||||
disc("""
|
|
||||||
Shapes have two kinds of properties - some to define their geometry
|
|
||||||
and some to define their style.
|
|
||||||
Let's create a red rectangle with 3-point thick green borders:
|
|
||||||
""")
|
|
||||||
|
|
||||||
eg("""
|
|
||||||
>>> from reportlab.graphics.shapes import Rect
|
|
||||||
>>> from reportlab.lib.colors import red, green
|
|
||||||
>>> r = Rect(5, 5, 200, 100)
|
|
||||||
>>> r.fillColor = red
|
|
||||||
>>> r.strokeColor = green
|
|
||||||
>>> r.strokeWidth = 3
|
|
||||||
>>>
|
|
||||||
""")
|
|
||||||
|
|
||||||
from reportlab.graphics.shapes import Rect
|
|
||||||
from reportlab.lib.colors import red, green
|
|
||||||
d = Drawing(220, 120)
|
|
||||||
r = Rect(5, 5, 200, 100)
|
|
||||||
r.fillColor = red
|
|
||||||
r.strokeColor = green
|
|
||||||
r.strokeWidth = 3
|
|
||||||
d.add(r)
|
|
||||||
draw(d, "red rectangle with green border")
|
|
||||||
|
|
||||||
disc("""
|
|
||||||
<i>Note: In future examples we will omit the import statements.</i>
|
|
||||||
""")
|
|
||||||
|
|
||||||
disc("""
|
|
||||||
All shapes have a number of properties which can be set.
|
|
||||||
At an interactive prompt, we can use their <i>dumpProperties()</i>
|
|
||||||
method to list these.
|
|
||||||
Here's what you can use to configure a Rect:
|
|
||||||
""")
|
|
||||||
|
|
||||||
eg("""
|
|
||||||
>>> r.dumpProperties()
|
|
||||||
fillColor = Color(1.00,0.00,0.00)
|
|
||||||
height = 100
|
|
||||||
rx = 0
|
|
||||||
ry = 0
|
|
||||||
strokeColor = Color(0.00,0.50,0.00)
|
|
||||||
strokeDashArray = None
|
|
||||||
strokeLineCap = 0
|
|
||||||
strokeLineJoin = 0
|
|
||||||
strokeMiterLimit = 0
|
|
||||||
strokeWidth = 3
|
|
||||||
width = 200
|
|
||||||
x = 5
|
|
||||||
y = 5
|
|
||||||
>>>
|
|
||||||
""")
|
|
||||||
|
|
||||||
disc("""
|
|
||||||
Shapes generally have <i>style properties</i> and <i>geometry
|
|
||||||
properties</i>.
|
|
||||||
$x$, $y$, $width$ and $height$ are part of the geometry and must
|
|
||||||
be provided when creating the rectangle, since it does not make
|
|
||||||
much sense without those properties.
|
|
||||||
The others are optional and come with sensible defaults.
|
|
||||||
""")
|
|
||||||
|
|
||||||
disc("""
|
|
||||||
You may set other properties on subsequent lines, or by passing them
|
|
||||||
as optional arguments to the constructor.
|
|
||||||
We could also have created our rectangle this way:
|
|
||||||
""")
|
|
||||||
|
|
||||||
eg("""
|
|
||||||
>>> r = Rect(5, 5, 200, 100,
|
|
||||||
fillColor=red,
|
|
||||||
strokeColor=green,
|
|
||||||
strokeWidth=3)
|
|
||||||
""")
|
|
||||||
|
|
||||||
disc("""
|
|
||||||
Let's run through the style properties. $fillColor$ is obvious.
|
|
||||||
$stroke$ is publishing terminology for the edge of a shape;
|
|
||||||
the stroke has a color, width, possibly a dash pattern, and
|
|
||||||
some (rarely used) features for what happens when a line turns
|
|
||||||
a corner.
|
|
||||||
$rx$ and $ry$ are optional geometric properties and are used to
|
|
||||||
define the corner radius for a rounded rectangle.
|
|
||||||
""")
|
|
||||||
|
|
||||||
disc("All the other solid shapes share the same style properties.")
|
|
||||||
|
|
||||||
|
|
||||||
heading2("Lines")
|
|
||||||
|
|
||||||
disc("""
|
|
||||||
We provide single straight lines, PolyLines and curves.
|
|
||||||
Lines have all the $stroke*$ properties, but no $fillColor$.
|
|
||||||
Here are a few Line and PolyLine examples and the corresponding
|
|
||||||
graphics output:
|
|
||||||
""")
|
|
||||||
|
|
||||||
eg("""
|
|
||||||
Line(50,50, 300,100,
|
|
||||||
strokeColor=colors.blue, strokeWidth=5)
|
|
||||||
Line(50,100, 300,50,
|
|
||||||
strokeColor=colors.red,
|
|
||||||
strokeWidth=10,
|
|
||||||
strokeDashArray=[10, 20])
|
|
||||||
PolyLine([120,110, 130,150, 140,110, 150,150, 160,110,
|
|
||||||
170,150, 180,110, 190,150, 200,110],
|
|
||||||
strokeWidth=2,
|
|
||||||
strokeColor=colors.purple)
|
|
||||||
""")
|
|
||||||
|
|
||||||
d = Drawing(400, 200)
|
|
||||||
d.add(Line(50,50, 300,100,strokeColor=colors.blue, strokeWidth=5))
|
|
||||||
d.add(Line(50,100, 300,50,
|
|
||||||
strokeColor=colors.red,
|
|
||||||
strokeWidth=10,
|
|
||||||
strokeDashArray=[10, 20]))
|
|
||||||
d.add(PolyLine([120,110, 130,150, 140,110, 150,150, 160,110,
|
|
||||||
170,150, 180,110, 190,150, 200,110],
|
|
||||||
strokeWidth=2,
|
|
||||||
strokeColor=colors.purple))
|
|
||||||
draw(d, "Line and PolyLine examples")
|
|
||||||
|
|
||||||
|
|
||||||
heading2("Strings")
|
|
||||||
|
|
||||||
disc("""
|
|
||||||
The ReportLab Graphics package is not designed for fancy text
|
|
||||||
layout, but it can place strings at desired locations and with
|
|
||||||
left/right/center alignment.
|
|
||||||
Let's specify a $String$ object and look at its properties:
|
|
||||||
""")
|
|
||||||
|
|
||||||
eg("""
|
|
||||||
>>> s = String(10, 50, 'Hello World')
|
|
||||||
>>> s.dumpProperties()
|
|
||||||
fillColor = Color(0.00,0.00,0.00)
|
|
||||||
fontName = Times-Roman
|
|
||||||
fontSize = 10
|
|
||||||
text = Hello World
|
|
||||||
textAnchor = start
|
|
||||||
x = 10
|
|
||||||
y = 50
|
|
||||||
>>>
|
|
||||||
""")
|
|
||||||
|
|
||||||
disc("""
|
|
||||||
Strings have a textAnchor property, which may have one of the
|
|
||||||
values 'start', 'middle', 'end'.
|
|
||||||
If this is set to 'start', x and y relate to the start of the
|
|
||||||
string, and so on.
|
|
||||||
This provides an easy way to align text.
|
|
||||||
""")
|
|
||||||
|
|
||||||
disc("""
|
|
||||||
Strings use a common font standard: the Type 1 Postscript fonts
|
|
||||||
present in Acrobat Reader.
|
|
||||||
We can thus use the basic 14 fonts in ReportLab and get accurate
|
|
||||||
metrics for them.
|
|
||||||
We have recently also added support for extra Type 1 fonts
|
|
||||||
and the renderers all know how to render Type 1 fonts.
|
|
||||||
""")
|
|
||||||
|
|
||||||
##Until now we have worked with bitmap renderers which have to use
|
|
||||||
##TrueType fonts and which make some substitutions; this could lead
|
|
||||||
##to differences in text wrapping or even the number of labels on
|
|
||||||
##a chart between renderers.
|
|
||||||
|
|
||||||
disc("""
|
|
||||||
Here is a more fancy example using the code snippet below.
|
|
||||||
Please consult the ReportLab User Guide to see how non-standard
|
|
||||||
like 'LettErrorRobot-Chrome' fonts are being registered!
|
|
||||||
""")
|
|
||||||
|
|
||||||
eg("""
|
|
||||||
d = Drawing(400, 200)
|
|
||||||
for size in range(12, 36, 4):
|
|
||||||
d.add(String(10+size*2, 10+size*2, 'Hello World',
|
|
||||||
fontName='Times-Roman',
|
|
||||||
fontSize=size))
|
|
||||||
|
|
||||||
d.add(String(130, 120, 'Hello World',
|
|
||||||
fontName='Courier',
|
|
||||||
fontSize=36))
|
|
||||||
|
|
||||||
d.add(String(150, 160, 'Hello World',
|
|
||||||
fontName='LettErrorRobot-Chrome',
|
|
||||||
fontSize=36))
|
|
||||||
""")
|
|
||||||
|
|
||||||
from reportlab.pdfbase import pdfmetrics
|
|
||||||
from reportlab import rl_config
|
|
||||||
rl_config.warnOnMissingFontGlyphs = 0
|
|
||||||
afmFile, pfbFile = getJustFontPaths()
|
|
||||||
T1face = pdfmetrics.EmbeddedType1Face(afmFile, pfbFile)
|
|
||||||
T1faceName = 'LettErrorRobot-Chrome'
|
|
||||||
pdfmetrics.registerTypeFace(T1face)
|
|
||||||
T1font = pdfmetrics.Font(T1faceName, T1faceName, 'WinAnsiEncoding')
|
|
||||||
pdfmetrics.registerFont(T1font)
|
|
||||||
|
|
||||||
d = Drawing(400, 200)
|
|
||||||
for size in range(12, 36, 4):
|
|
||||||
d.add(String(10+size*2, 10+size*2, 'Hello World',
|
|
||||||
fontName='Times-Roman',
|
|
||||||
fontSize=size))
|
|
||||||
|
|
||||||
d.add(String(130, 120, 'Hello World',
|
|
||||||
fontName='Courier',
|
|
||||||
fontSize=36))
|
|
||||||
|
|
||||||
d.add(String(150, 160, 'Hello World',
|
|
||||||
fontName='LettErrorRobot-Chrome',
|
|
||||||
fontSize=36))
|
|
||||||
|
|
||||||
draw(d, 'fancy font example')
|
|
||||||
|
|
||||||
|
|
||||||
heading2("""Paths""")
|
|
||||||
|
|
||||||
disc("""
|
|
||||||
Postscript paths are a widely understood concept in graphics.
|
|
||||||
They are not implemented in $reportlab/graphics$ as yet, but they
|
|
||||||
will be, soon.
|
|
||||||
""")
|
|
||||||
|
|
||||||
# NB This commented out section is for 'future compatibility' - paths haven't
|
|
||||||
# been implemented yet, but when they are we can uncomment this back in.
|
|
||||||
|
|
||||||
##disc("""Postscript paths are a widely understood concept in graphics. A Path
|
|
||||||
## is a way of defining a region in space. You put an imaginary pen down,
|
|
||||||
## draw straight and curved segments, and even pick the pen up and move
|
|
||||||
## it. At the end of this you have described a region, which may consist
|
|
||||||
## of several distinct close shapes or unclosed lines. At the end, this
|
|
||||||
## 'path' is 'stroked and filled' according to its properties. A Path has
|
|
||||||
## the same style properties as a solid shape. It can be used to create
|
|
||||||
## any irregular shape.""")
|
|
||||||
##
|
|
||||||
##disc("""In Postscript-based imaging models such as PDF, Postscript and SVG,
|
|
||||||
## everything is done with paths. All the specific shapes covered above
|
|
||||||
## are instances of paths; even text strings (which are shapes in which
|
|
||||||
## each character is an outline to be filled). Here we begin creating a
|
|
||||||
## path with a straight line and a bezier curve:""")
|
|
||||||
##
|
|
||||||
##eg("""
|
|
||||||
##>>> P = Path(0,0, strokeWidth=3, strokeColor=red)
|
|
||||||
##>>> P.lineTo(0, 50)
|
|
||||||
##>>> P.curveTo(10,50,80,80,100,30)
|
|
||||||
##>>>
|
|
||||||
##""")
|
|
||||||
|
|
||||||
##disc("""As well as being the only way to draw complex shapes, paths offer some
|
|
||||||
## performance advantages in renderers which support them. If you want to
|
|
||||||
## create a scatter plot with 5000 blue circles of different sizes, you
|
|
||||||
## can create 5000 circles, or one path object. With the latter, you only
|
|
||||||
## need to set the color and line width once. PINGO just remembers the
|
|
||||||
## drawing sequence, and writes it out into the file. In renderers which
|
|
||||||
## do not support paths, the renderer will still have to decompose it
|
|
||||||
## into 5000 circles so you won't save anything.""")
|
|
||||||
##
|
|
||||||
##disc("""<b>Note that our current path implementation is an approximation; it
|
|
||||||
## should be finished off accurately for PDF and PS.</b>""")
|
|
||||||
|
|
||||||
|
|
||||||
heading2("Groups")
|
|
||||||
|
|
||||||
disc("""
|
|
||||||
Finally, we have Group objects.
|
|
||||||
A group has a list of contents, which are other nodes.
|
|
||||||
It can also apply a transformation - its contents can be rotated,
|
|
||||||
scaled or shifted.
|
|
||||||
If you know the math, you can set the transform directly.
|
|
||||||
Otherwise it provides methods to rotate, scale and so on.
|
|
||||||
Here we make a group which is rotated and translated:
|
|
||||||
""")
|
|
||||||
|
|
||||||
eg("""
|
|
||||||
>>> g =Group(shape1, shape2, shape3)
|
|
||||||
>>> g.rotate(30)
|
|
||||||
>>> g.translate(50, 200)
|
|
||||||
""")
|
|
||||||
|
|
||||||
disc("""
|
|
||||||
Groups provide a tool for reuse.
|
|
||||||
You can make a bunch of shapes to represent some component - say,
|
|
||||||
a coordinate system - and put them in one group called "Axis".
|
|
||||||
You can then put that group into other groups, each with a different
|
|
||||||
translation and rotation, and you get a bunch of axis.
|
|
||||||
It is still the same group, being drawn in different places.
|
|
||||||
""")
|
|
||||||
|
|
||||||
disc("""Let's do this with some only slightly more code:""")
|
|
||||||
|
|
||||||
eg("""
|
|
||||||
d = Drawing(400, 200)
|
|
||||||
|
|
||||||
Axis = Group(
|
|
||||||
Line(0,0,100,0), # x axis
|
|
||||||
Line(0,0,0,50), # y axis
|
|
||||||
Line(0,10,10,10), # ticks on y axis
|
|
||||||
Line(0,20,10,20),
|
|
||||||
Line(0,30,10,30),
|
|
||||||
Line(0,40,10,40),
|
|
||||||
Line(10,0,10,10), # ticks on x axis
|
|
||||||
Line(20,0,20,10),
|
|
||||||
Line(30,0,30,10),
|
|
||||||
Line(40,0,40,10),
|
|
||||||
Line(50,0,50,10),
|
|
||||||
Line(60,0,60,10),
|
|
||||||
Line(70,0,70,10),
|
|
||||||
Line(80,0,80,10),
|
|
||||||
Line(90,0,90,10),
|
|
||||||
String(20, 35, 'Axes', fill=colors.black)
|
|
||||||
)
|
|
||||||
|
|
||||||
firstAxisGroup = Group(Axis)
|
|
||||||
firstAxisGroup.translate(10,10)
|
|
||||||
d.add(firstAxisGroup)
|
|
||||||
|
|
||||||
secondAxisGroup = Group(Axis)
|
|
||||||
secondAxisGroup.translate(150,10)
|
|
||||||
secondAxisGroup.rotate(15)
|
|
||||||
|
|
||||||
d.add(secondAxisGroup)
|
|
||||||
|
|
||||||
thirdAxisGroup = Group(Axis,
|
|
||||||
transform=mmult(translate(300,10),
|
|
||||||
rotate(30)))
|
|
||||||
d.add(thirdAxisGroup)
|
|
||||||
""")
|
|
||||||
|
|
||||||
d = Drawing(400, 200)
|
|
||||||
Axis = Group(
|
|
||||||
Line(0,0,100,0), # x axis
|
|
||||||
Line(0,0,0,50), # y axis
|
|
||||||
Line(0,10,10,10), # ticks on y axis
|
|
||||||
Line(0,20,10,20),
|
|
||||||
Line(0,30,10,30),
|
|
||||||
Line(0,40,10,40),
|
|
||||||
Line(10,0,10,10), # ticks on x axis
|
|
||||||
Line(20,0,20,10),
|
|
||||||
Line(30,0,30,10),
|
|
||||||
Line(40,0,40,10),
|
|
||||||
Line(50,0,50,10),
|
|
||||||
Line(60,0,60,10),
|
|
||||||
Line(70,0,70,10),
|
|
||||||
Line(80,0,80,10),
|
|
||||||
Line(90,0,90,10),
|
|
||||||
String(20, 35, 'Axes', fill=colors.black)
|
|
||||||
)
|
|
||||||
firstAxisGroup = Group(Axis)
|
|
||||||
firstAxisGroup.translate(10,10)
|
|
||||||
d.add(firstAxisGroup)
|
|
||||||
secondAxisGroup = Group(Axis)
|
|
||||||
secondAxisGroup.translate(150,10)
|
|
||||||
secondAxisGroup.rotate(15)
|
|
||||||
d.add(secondAxisGroup)
|
|
||||||
thirdAxisGroup = Group(Axis,
|
|
||||||
transform=mmult(translate(300,10),
|
|
||||||
rotate(30)))
|
|
||||||
d.add(thirdAxisGroup)
|
|
||||||
draw(d, "Groups examples")
|
|
|
@ -1,422 +0,0 @@
|
||||||
#Copyright ReportLab Europe Ltd. 2000-2004
|
|
||||||
#see license.txt for license details
|
|
||||||
#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/docs/graphguide/ch4_widgets.py
|
|
||||||
|
|
||||||
from reportlab.tools.docco.rl_doc_utils import *
|
|
||||||
from reportlab.graphics.shapes import *
|
|
||||||
from reportlab.graphics.widgets import signsandsymbols
|
|
||||||
|
|
||||||
heading1("Widgets")
|
|
||||||
|
|
||||||
disc("""
|
|
||||||
We now describe widgets and how they relate to shapes.
|
|
||||||
Using many examples it is shown how widgets make reusable
|
|
||||||
graphics components.
|
|
||||||
""")
|
|
||||||
|
|
||||||
|
|
||||||
heading2("Shapes vs. Widgets")
|
|
||||||
|
|
||||||
disc("""Up until now, Drawings have been 'pure data'. There is no code in them
|
|
||||||
to actually do anything, except assist the programmer in checking and
|
|
||||||
inspecting the drawing. In fact, that's the cornerstone of the whole
|
|
||||||
concept and is what lets us achieve portability - a renderer only
|
|
||||||
needs to implement the primitive shapes.""")
|
|
||||||
|
|
||||||
disc("""We want to build reusable graphic objects, including a powerful chart
|
|
||||||
library. To do this we need to reuse more tangible things than
|
|
||||||
rectangles and circles. We should be able to write objects for other
|
|
||||||
to reuse - arrows, gears, text boxes, UML diagram nodes, even fully
|
|
||||||
fledged charts.""")
|
|
||||||
|
|
||||||
disc("""
|
|
||||||
The Widget standard is a standard built on top of the shapes module.
|
|
||||||
Anyone can write new widgets, and we can build up libraries of them.
|
|
||||||
Widgets support the $getProperties()$ and $setProperties()$ methods,
|
|
||||||
so you can inspect and modify as well as document them in a uniform
|
|
||||||
way.
|
|
||||||
""")
|
|
||||||
|
|
||||||
bullet("A widget is a reusable shape ")
|
|
||||||
bullet("""it can be initialized with no arguments
|
|
||||||
when its $draw()$ method is called it creates a primitive Shape or a
|
|
||||||
Group to represent itself""")
|
|
||||||
bullet("""It can have any parameters you want, and they can drive the way it is
|
|
||||||
drawn""")
|
|
||||||
bullet("""it has a $demo()$ method which should return an attractively drawn
|
|
||||||
example of itself in a 200x100 rectangle. This is the cornerstone of
|
|
||||||
the automatic documentation tools. The $demo()$ method should also have
|
|
||||||
a well written docstring, since that is printed too!""")
|
|
||||||
|
|
||||||
disc("""Widgets run contrary to the idea that a drawing is just a bundle of
|
|
||||||
shapes; surely they have their own code? The way they work is that a
|
|
||||||
widget can convert itself to a group of primitive shapes. If some of
|
|
||||||
its components are themselves widgets, they will get converted too.
|
|
||||||
This happens automatically during rendering; the renderer will not see
|
|
||||||
your chart widget, but just a collection of rectangles, lines and
|
|
||||||
strings. You can also explicitly 'flatten out' a drawing, causing all
|
|
||||||
widgets to be converted to primitives.""")
|
|
||||||
|
|
||||||
|
|
||||||
heading2("Using a Widget")
|
|
||||||
|
|
||||||
disc("""
|
|
||||||
Let's imagine a simple new widget.
|
|
||||||
We will use a widget to draw a face, then show how it was implemented.""")
|
|
||||||
|
|
||||||
eg("""
|
|
||||||
>>> from reportlab.lib import colors
|
|
||||||
>>> from reportlab.graphics import shapes
|
|
||||||
>>> from reportlab.graphics import widgetbase
|
|
||||||
>>> from reportlab.graphics import renderPDF
|
|
||||||
>>> d = shapes.Drawing(200, 100)
|
|
||||||
>>> f = widgetbase.Face()
|
|
||||||
>>> f.skinColor = colors.yellow
|
|
||||||
>>> f.mood = "sad"
|
|
||||||
>>> d.add(f)
|
|
||||||
>>> renderPDF.drawToFile(d, 'face.pdf', 'A Face')
|
|
||||||
""")
|
|
||||||
|
|
||||||
from reportlab.graphics import widgetbase
|
|
||||||
d = Drawing(200, 120)
|
|
||||||
f = widgetbase.Face()
|
|
||||||
f.x = 50
|
|
||||||
f.y = 10
|
|
||||||
f.skinColor = colors.yellow
|
|
||||||
f.mood = "sad"
|
|
||||||
d.add(f)
|
|
||||||
draw(d, 'A sample widget')
|
|
||||||
|
|
||||||
disc("""
|
|
||||||
Let's see what properties it has available, using the $setProperties()$
|
|
||||||
method we have seen earlier:
|
|
||||||
""")
|
|
||||||
|
|
||||||
eg("""
|
|
||||||
>>> f.dumpProperties()
|
|
||||||
eyeColor = Color(0.00,0.00,1.00)
|
|
||||||
mood = sad
|
|
||||||
size = 80
|
|
||||||
skinColor = Color(1.00,1.00,0.00)
|
|
||||||
x = 10
|
|
||||||
y = 10
|
|
||||||
>>>
|
|
||||||
""")
|
|
||||||
|
|
||||||
disc("""
|
|
||||||
One thing which seems strange about the above code is that we did not
|
|
||||||
set the size or position when we made the face.
|
|
||||||
This is a necessary trade-off to allow a uniform interface for
|
|
||||||
constructing widgets and documenting them - they cannot require
|
|
||||||
arguments in their $__init__()$ method.
|
|
||||||
Instead, they are generally designed to fit in a 200 x 100
|
|
||||||
window, and you move or resize them by setting properties such as
|
|
||||||
x, y, width and so on after creation.
|
|
||||||
""")
|
|
||||||
|
|
||||||
disc("""
|
|
||||||
In addition, a widget always provides a $demo()$ method.
|
|
||||||
Simple ones like this always do something sensible before setting
|
|
||||||
properties, but more complex ones like a chart would not have any
|
|
||||||
data to plot.
|
|
||||||
The documentation tool calls $demo()$ so that your fancy new chart
|
|
||||||
class can create a drawing showing what it can do.
|
|
||||||
""")
|
|
||||||
|
|
||||||
disc("""
|
|
||||||
Here are a handful of simple widgets available in the module
|
|
||||||
<i>signsandsymbols.py</i>:
|
|
||||||
""")
|
|
||||||
|
|
||||||
from reportlab.graphics.shapes import Drawing
|
|
||||||
from reportlab.graphics.widgets import signsandsymbols
|
|
||||||
|
|
||||||
d = Drawing(230, 230)
|
|
||||||
|
|
||||||
ne = signsandsymbols.NoEntry()
|
|
||||||
ds = signsandsymbols.DangerSign()
|
|
||||||
fd = signsandsymbols.FloppyDisk()
|
|
||||||
ns = signsandsymbols.NoSmoking()
|
|
||||||
|
|
||||||
ne.x, ne.y = 10, 10
|
|
||||||
ds.x, ds.y = 120, 10
|
|
||||||
fd.x, fd.y = 10, 120
|
|
||||||
ns.x, ns.y = 120, 120
|
|
||||||
|
|
||||||
d.add(ne)
|
|
||||||
d.add(ds)
|
|
||||||
d.add(fd)
|
|
||||||
d.add(ns)
|
|
||||||
|
|
||||||
draw(d, 'A few samples from signsandsymbols.py')
|
|
||||||
|
|
||||||
disc("""
|
|
||||||
And this is the code needed to generate them as seen in the drawing above:
|
|
||||||
""")
|
|
||||||
|
|
||||||
eg("""
|
|
||||||
from reportlab.graphics.shapes import Drawing
|
|
||||||
from reportlab.graphics.widgets import signsandsymbols
|
|
||||||
|
|
||||||
d = Drawing(230, 230)
|
|
||||||
|
|
||||||
ne = signsandsymbols.NoEntry()
|
|
||||||
ds = signsandsymbols.DangerSign()
|
|
||||||
fd = signsandsymbols.FloppyDisk()
|
|
||||||
ns = signsandsymbols.NoSmoking()
|
|
||||||
|
|
||||||
ne.x, ne.y = 10, 10
|
|
||||||
ds.x, ds.y = 120, 10
|
|
||||||
fd.x, fd.y = 10, 120
|
|
||||||
ns.x, ns.y = 120, 120
|
|
||||||
|
|
||||||
d.add(ne)
|
|
||||||
d.add(ds)
|
|
||||||
d.add(fd)
|
|
||||||
d.add(ns)
|
|
||||||
""")
|
|
||||||
|
|
||||||
|
|
||||||
heading2("Compound Widgets")
|
|
||||||
|
|
||||||
disc("""Let's imagine a compound widget which draws two faces side by side.
|
|
||||||
This is easy to build when you have the Face widget.""")
|
|
||||||
|
|
||||||
eg("""
|
|
||||||
>>> tf = widgetbase.TwoFaces()
|
|
||||||
>>> tf.faceOne.mood
|
|
||||||
'happy'
|
|
||||||
>>> tf.faceTwo.mood
|
|
||||||
'sad'
|
|
||||||
>>> tf.dumpProperties()
|
|
||||||
faceOne.eyeColor = Color(0.00,0.00,1.00)
|
|
||||||
faceOne.mood = happy
|
|
||||||
faceOne.size = 80
|
|
||||||
faceOne.skinColor = None
|
|
||||||
faceOne.x = 10
|
|
||||||
faceOne.y = 10
|
|
||||||
faceTwo.eyeColor = Color(0.00,0.00,1.00)
|
|
||||||
faceTwo.mood = sad
|
|
||||||
faceTwo.size = 80
|
|
||||||
faceTwo.skinColor = None
|
|
||||||
faceTwo.x = 100
|
|
||||||
faceTwo.y = 10
|
|
||||||
>>>
|
|
||||||
""")
|
|
||||||
|
|
||||||
disc("""The attributes 'faceOne' and 'faceTwo' are deliberately exposed so you
|
|
||||||
can get at them directly. There could also be top-level attributes,
|
|
||||||
but there aren't in this case.""")
|
|
||||||
|
|
||||||
|
|
||||||
heading2("Verifying Widgets")
|
|
||||||
|
|
||||||
disc("""The widget designer decides the policy on verification, but by default
|
|
||||||
they work like shapes - checking every assignment - if the designer
|
|
||||||
has provided the checking information.""")
|
|
||||||
|
|
||||||
|
|
||||||
heading2("Implementing Widgets")
|
|
||||||
|
|
||||||
disc("""We tried to make it as easy to implement widgets as possible. Here's
|
|
||||||
the code for a Face widget which does not do any type checking:""")
|
|
||||||
|
|
||||||
eg("""
|
|
||||||
class Face(Widget):
|
|
||||||
\"\"\"This draws a face with two eyes, mouth and nose.\"\"\"
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
self.x = 10
|
|
||||||
self.y = 10
|
|
||||||
self.size = 80
|
|
||||||
self.skinColor = None
|
|
||||||
self.eyeColor = colors.blue
|
|
||||||
self.mood = 'happy'
|
|
||||||
|
|
||||||
def draw(self):
|
|
||||||
s = self.size # abbreviate as we will use this a lot
|
|
||||||
g = shapes.Group()
|
|
||||||
g.transform = [1,0,0,1,self.x, self.y]
|
|
||||||
# background
|
|
||||||
g.add(shapes.Circle(s * 0.5, s * 0.5, s * 0.5,
|
|
||||||
fillColor=self.skinColor))
|
|
||||||
# CODE OMITTED TO MAKE MORE SHAPES
|
|
||||||
return g
|
|
||||||
""")
|
|
||||||
|
|
||||||
disc("""We left out all the code to draw the shapes in this document, but you
|
|
||||||
can find it in the distribution in $widgetbase.py$.""")
|
|
||||||
|
|
||||||
disc("""By default, any attribute without a leading underscore is returned by
|
|
||||||
setProperties. This is a deliberate policy to encourage consistent
|
|
||||||
coding conventions.""")
|
|
||||||
|
|
||||||
disc("""Once your widget works, you probably want to add support for
|
|
||||||
verification. This involves adding a dictionary to the class called
|
|
||||||
$_verifyMap$, which map from attribute names to 'checking functions'.
|
|
||||||
The $widgetbase.py$ module defines a bunch of checking functions with names
|
|
||||||
like $isNumber$, $isListOfShapes$ and so on. You can also simply use $None$,
|
|
||||||
which means that the attribute must be present but can have any type.
|
|
||||||
And you can and should write your own checking functions. We want to
|
|
||||||
restrict the "mood" custom attribute to the values "happy", "sad" or
|
|
||||||
"ok". So we do this:""")
|
|
||||||
|
|
||||||
eg("""
|
|
||||||
class Face(Widget):
|
|
||||||
\"\"\"This draws a face with two eyes. It exposes a
|
|
||||||
couple of properties to configure itself and hides
|
|
||||||
all other details\"\"\"
|
|
||||||
def checkMood(moodName):
|
|
||||||
return (moodName in ('happy','sad','ok'))
|
|
||||||
_verifyMap = {
|
|
||||||
'x': shapes.isNumber,
|
|
||||||
'y': shapes.isNumber,
|
|
||||||
'size': shapes.isNumber,
|
|
||||||
'skinColor':shapes.isColorOrNone,
|
|
||||||
'eyeColor': shapes.isColorOrNone,
|
|
||||||
'mood': checkMood
|
|
||||||
}
|
|
||||||
""")
|
|
||||||
|
|
||||||
disc("""This checking will be performed on every attribute assignment; or, if
|
|
||||||
$config.shapeChecking$ is off, whenever you call $myFace.verify()$.""")
|
|
||||||
|
|
||||||
|
|
||||||
heading2("Documenting Widgets")
|
|
||||||
|
|
||||||
disc("""
|
|
||||||
We are working on a generic tool to document any Python package or
|
|
||||||
module; this is already checked into ReportLab and will be used to
|
|
||||||
generate a reference for the ReportLab package.
|
|
||||||
When it encounters widgets, it adds extra sections to the
|
|
||||||
manual including:""")
|
|
||||||
|
|
||||||
bullet("the doc string for your widget class ")
|
|
||||||
bullet("the code snippet from your <i>demo()</i> method, so people can see how to use it")
|
|
||||||
bullet("the drawing produced by the <i>demo()</i> method ")
|
|
||||||
bullet("the property dump for the widget in the drawing. ")
|
|
||||||
|
|
||||||
disc("""
|
|
||||||
This tool will mean that we can have guaranteed up-to-date
|
|
||||||
documentation on our widgets and charts, both on the web site
|
|
||||||
and in print; and that you can do the same for your own widgets,
|
|
||||||
too!
|
|
||||||
""")
|
|
||||||
|
|
||||||
|
|
||||||
heading2("Widget Design Strategies")
|
|
||||||
|
|
||||||
disc("""We could not come up with a consistent architecture for designing
|
|
||||||
widgets, so we are leaving that problem to the authors! If you do not
|
|
||||||
like the default verification strategy, or the way
|
|
||||||
$setProperties/getProperties$ works, you can override them yourself.""")
|
|
||||||
|
|
||||||
disc("""For simple widgets it is recommended that you do what we did above:
|
|
||||||
select non-overlapping properties, initialize every property on
|
|
||||||
$__init__$ and construct everything when $draw()$ is called. You can
|
|
||||||
instead have $__setattr__$ hooks and have things updated when certain
|
|
||||||
attributes are set. Consider a pie chart. If you want to expose the
|
|
||||||
individual wedges, you might write code like this:""")
|
|
||||||
|
|
||||||
eg("""
|
|
||||||
from reportlab.graphics.charts import piecharts
|
|
||||||
pc = piecharts.Pie()
|
|
||||||
pc.defaultColors = [navy, blue, skyblue] #used in rotation
|
|
||||||
pc.data = [10,30,50,25]
|
|
||||||
pc.slices[7].strokeWidth = 5
|
|
||||||
""")
|
|
||||||
#removed 'pc.backColor = yellow' from above code example
|
|
||||||
|
|
||||||
disc("""The last line is problematic as we have only created four wedges - in
|
|
||||||
fact we might not have created them yet. Does $pc.wedges[7]$ raise an
|
|
||||||
error? Is it a prescription for what should happen if a seventh wedge
|
|
||||||
is defined, used to override the default settings? We dump this
|
|
||||||
problem squarely on the widget author for now, and recommend that you
|
|
||||||
get a simple one working before exposing 'child objects' whose
|
|
||||||
existence depends on other properties' values :-)""")
|
|
||||||
|
|
||||||
disc("""We also discussed rules by which parent widgets could pass properties
|
|
||||||
to their children. There seems to be a general desire for a global way
|
|
||||||
to say that 'all wedges get their lineWidth from the lineWidth of
|
|
||||||
their parent' without a lot of repetitive coding. We do not have a
|
|
||||||
universal solution, so again leave that to widget authors. We hope
|
|
||||||
people will experiment with push-down, pull-down and pattern-matching
|
|
||||||
approaches and come up with something nice. In the meantime, we
|
|
||||||
certainly can write monolithic chart widgets which work like the ones
|
|
||||||
in, say, Visual Basic and Delphi.""")
|
|
||||||
|
|
||||||
disc("""For now have a look at the following sample code using an early
|
|
||||||
version of a pie chart widget and the output it generates:""")
|
|
||||||
|
|
||||||
eg("""
|
|
||||||
from reportlab.lib.colors import *
|
|
||||||
from reportlab.graphics import shapes,renderPDF
|
|
||||||
from reportlab.graphics.charts.piecharts import Pie
|
|
||||||
|
|
||||||
d = Drawing(400,200)
|
|
||||||
d.add(String(100,175,"Without labels", textAnchor="middle"))
|
|
||||||
d.add(String(300,175,"With labels", textAnchor="middle"))
|
|
||||||
|
|
||||||
pc = Pie()
|
|
||||||
pc.x = 25
|
|
||||||
pc.y = 50
|
|
||||||
pc.data = [10,20,30,40,50,60]
|
|
||||||
pc.slices[0].popout = 5
|
|
||||||
d.add(pc, 'pie1')
|
|
||||||
|
|
||||||
pc2 = Pie()
|
|
||||||
pc2.x = 150
|
|
||||||
pc2.y = 50
|
|
||||||
pc2.data = [10,20,30,40,50,60]
|
|
||||||
pc2.labels = ['a','b','c','d','e','f']
|
|
||||||
d.add(pc2, 'pie2')
|
|
||||||
|
|
||||||
pc3 = Pie()
|
|
||||||
pc3.x = 275
|
|
||||||
pc3.y = 50
|
|
||||||
pc3.data = [10,20,30,40,50,60]
|
|
||||||
pc3.labels = ['a','b','c','d','e','f']
|
|
||||||
pc3.wedges.labelRadius = 0.65
|
|
||||||
pc3.wedges.fontName = "Helvetica-Bold"
|
|
||||||
pc3.wedges.fontSize = 16
|
|
||||||
pc3.wedges.fontColor = colors.yellow
|
|
||||||
d.add(pc3, 'pie3')
|
|
||||||
""")
|
|
||||||
|
|
||||||
# Hack to force a new paragraph before the todo() :-(
|
|
||||||
disc("")
|
|
||||||
|
|
||||||
from reportlab.lib.colors import *
|
|
||||||
from reportlab.graphics import shapes,renderPDF
|
|
||||||
from reportlab.graphics.charts.piecharts import Pie
|
|
||||||
|
|
||||||
d = Drawing(400,200)
|
|
||||||
d.add(String(100,175,"Without labels", textAnchor="middle"))
|
|
||||||
d.add(String(300,175,"With labels", textAnchor="middle"))
|
|
||||||
|
|
||||||
pc = Pie()
|
|
||||||
pc.x = 25
|
|
||||||
pc.y = 50
|
|
||||||
pc.data = [10,20,30,40,50,60]
|
|
||||||
pc.slices[0].popout = 5
|
|
||||||
d.add(pc, 'pie1')
|
|
||||||
|
|
||||||
pc2 = Pie()
|
|
||||||
pc2.x = 150
|
|
||||||
pc2.y = 50
|
|
||||||
pc2.data = [10,20,30,40,50,60]
|
|
||||||
pc2.labels = ['a','b','c','d','e','f']
|
|
||||||
d.add(pc2, 'pie2')
|
|
||||||
|
|
||||||
pc3 = Pie()
|
|
||||||
pc3.x = 275
|
|
||||||
pc3.y = 50
|
|
||||||
pc3.data = [10,20,30,40,50,60]
|
|
||||||
pc3.labels = ['a','b','c','d','e','f']
|
|
||||||
pc3.slices.labelRadius = 0.65
|
|
||||||
pc3.slices.fontName = "Helvetica-Bold"
|
|
||||||
pc3.slices.fontSize = 16
|
|
||||||
pc3.slices.fontColor = colors.yellow
|
|
||||||
d.add(pc3, 'pie3')
|
|
||||||
|
|
||||||
draw(d, 'Some sample Pies')
|
|
|
@ -1,59 +0,0 @@
|
||||||
#!/bin/env python
|
|
||||||
#Copyright ReportLab Europe Ltd. 2000-2004
|
|
||||||
#see license.txt for license details
|
|
||||||
#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/docs/graphguide/gengraphguide.py
|
|
||||||
__version__=''' $Id: gengraphguide.py 2385 2004-06-17 15:26:05Z rgbecker $ '''
|
|
||||||
__doc__ = """
|
|
||||||
This module contains the script for building the graphics guide.
|
|
||||||
"""
|
|
||||||
def run(pagesize=None, verbose=1, outDir=None):
|
|
||||||
import os
|
|
||||||
from reportlab.tools.docco.rl_doc_utils import setStory, getStory, RLDocTemplate, defaultPageSize
|
|
||||||
from reportlab.tools.docco import rl_doc_utils
|
|
||||||
from reportlab.lib.utils import open_and_read, _RL_DIR
|
|
||||||
if not outDir: outDir = os.path.join(_RL_DIR,'docs')
|
|
||||||
destfn = os.path.join(outDir,'graphguide.pdf')
|
|
||||||
doc = RLDocTemplate(destfn,pagesize = pagesize or defaultPageSize)
|
|
||||||
|
|
||||||
#this builds the story
|
|
||||||
setStory()
|
|
||||||
G = {}
|
|
||||||
exec 'from reportlab.tools.docco.rl_doc_utils import *' in G, G
|
|
||||||
doc = RLDocTemplate(destfn,pagesize = pagesize or defaultPageSize)
|
|
||||||
for f in (
|
|
||||||
'ch1_intro',
|
|
||||||
'ch2_concepts',
|
|
||||||
'ch3_shapes',
|
|
||||||
'ch4_widgets',
|
|
||||||
'ch5_charts',
|
|
||||||
):
|
|
||||||
exec open_and_read(f+'.py',mode='t') in G, G
|
|
||||||
del G
|
|
||||||
|
|
||||||
story = getStory()
|
|
||||||
if verbose: print 'Built story contains %d flowables...' % len(story)
|
|
||||||
doc.build(story)
|
|
||||||
if verbose: print 'Saved "%s"' % destfn
|
|
||||||
|
|
||||||
def makeSuite():
|
|
||||||
"standard test harness support - run self as separate process"
|
|
||||||
from reportlab.test.utils import ScriptThatMakesFileTest
|
|
||||||
return ScriptThatMakesFileTest('../docs/graphguide', 'gengraphguide.py', 'graphguide.pdf')
|
|
||||||
|
|
||||||
def main():
|
|
||||||
import sys
|
|
||||||
verbose = '-s' not in sys.argv
|
|
||||||
if not verbose: sys.argv.remove('-s')
|
|
||||||
if len(sys.argv) > 1:
|
|
||||||
try:
|
|
||||||
pagesize = eval(sys.argv[1])
|
|
||||||
except:
|
|
||||||
print 'Expected page size in argument 1', sys.argv[1]
|
|
||||||
raise
|
|
||||||
print 'set page size to',sys.argv[1]
|
|
||||||
else:
|
|
||||||
pagesize = None
|
|
||||||
run(pagesize,verbose)
|
|
||||||
|
|
||||||
if __name__=="__main__":
|
|
||||||
main()
|
|
Before Width: | Height: | Size: 9.5 KiB |
Before Width: | Height: | Size: 27 KiB |
Before Width: | Height: | Size: 28 KiB |
Before Width: | Height: | Size: 37 KiB |
Before Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 33 KiB |
Before Width: | Height: | Size: 12 KiB |
|
@ -1,439 +0,0 @@
|
||||||
BI
|
|
||||||
/W 283 /H 181 /BPC 8 /CS /RGB /F [/A85 /Fl]
|
|
||||||
ID
|
|
||||||
Gb"/lGEc'<R`9K?ST/gccciOJc<4e(<bO6Z:4`rg,Y/Cp6(efX#nfXW*Hb1H
|
|
||||||
&OfM-Pamra&k""R8@/E>,,kl8"&2NBd%Kaogum165&NYZBITmkcQDo:\lEnA
|
|
||||||
GJETphXmY&(BXdG&0O5g!!*-(#S8+DJ,fTO":,P]5_&h8!X&eu$f\(VI^5_q
|
|
||||||
jm)jbosgIBN01+GE&G/,0E`$J,.ogcq?_*e\?bYbO$E4*_M&A1cC@I!9Dd\'
|
|
||||||
+4<JpceXMW:S/-3%Sg@m:0f)>?4QDr'3BbF=2qLTa,V3(b)tC;HhK!DHiEs=
|
|
||||||
#e!]_gC"9\WeUZ]%d_rZDm+pZHhMjerV*pJn(tTrkH8+&G'_fGA&n<1gUDI\
|
|
||||||
)T%dl>;gEVr7uSSD=[2`\)2)Y\8d.Vp$:55>ISM;g9k_IXBE5_odUJ&4fhbj
|
|
||||||
+t4u*CCeUS/R,f.kn4Mb(GB+%B[KmgIC4%Q_Cr\%1,1M'_1Dj^kKfbZDr.!E
|
|
||||||
F(WairP]R[bk1DXB:hnj5(2_GWLnY9BgP/'PUTNpnM"tB`!I)(N#O]nba:,[
|
|
||||||
7un]KC=L=e,=d\00l5LHTgOSF))>aLTKrYb%NI015l^i^qt9:L_$;(Sl-nh/
|
|
||||||
pYP5l]D"DU][No_Y;b?do<#LYHN!Ng-V^$t+XoX`J[UDW6NstgiU^:#aXJYf
|
|
||||||
@T/-h#kbk9#UMApV<$^um2e#j'7`g:&J1#=2rAubQ**M!it6f3,*@pVZY%I6
|
|
||||||
6o/H!J4.k`9lkR_-;U4ln3a&Bj2[3$3u/IFT]*eDlR2scpu.!iLu\C$^CPtn
|
|
||||||
H@SNS^AI?QDnk,W"$D00h*&FIlDn4&G3't(Uo5[]m!,=p/AQ+U,=dd\o,oLh
|
|
||||||
`[7$Kifm(afHdLC\5iol9&c'@AXlrl+e2TcVCt*"D43P1/tBd4IK''*Z:Y1g
|
|
||||||
&tsE#;eD`AXDo:8;HP?kPAJ]<++5EP`)3&A0_Gc=bj+0GNAo^=jcrnRI.>2-
|
|
||||||
?@.[$o*RI.f9aJ`&3E/CRVaf0!I//;.6.N@dhWJ":#_Dm4P7SLk/f_K3$fY"
|
|
||||||
@lB&<Ha6I;-o4cM4MW0dOj"KZ#pDR1Yo[/:choqG8q9ig4MWML'j_uP_D&eT
|
|
||||||
fr<8f4$thGNQ*)+Mfaj\\>>=ZT!#RpKMq"Z6@j)ODfZ2)o?9+0oW[2)q0V"P
|
|
||||||
i5SJT2Aq@18&2N,MORue((.$!o41l.*at,LGSoZG'&i)k+<?5^(d*$sCsl0%
|
|
||||||
TKio3:;J7p.=$?[:u/^*Hhlcis6p!eY>_>o\;RS\8iUnSbf.@["h4W^daDC<
|
|
||||||
[+g5DH18l\"e-&NZV?P&#\?tUg#Q>8N;^H/+c'&47a2<,ZndtDk/m0)o'Y9I
|
|
||||||
"Af)p(^a#(U5qJ$"NO<.;GoitP^.[:Han@R-<rae6mU=%"(\3,<62Ukk;'3j
|
|
||||||
J8R*"'.6O_PqZ""f(6jTifp8^JCuAY/jbZCkH[Ypb0gkk-"GD*+ddO1:J[AF
|
|
||||||
6%f;38gK(_r17IFK[Cs1b9qdXYQm/LVYK6:5p]/rr:?Q\KM?DX(()r39Q@pt
|
|
||||||
-n"irbC8u%rSmof*/>"EJk':6\TnrLq,.c]R(i1M-HV6>$DWiJn<kX[9Ho]=
|
|
||||||
-M`!\n]=,n#c/6->Wu`pbXf8^#tsp$Blf[o2f0&&E8Fj;UkH;2KPeTN'9<-`
|
|
||||||
_E>O(dS$eNYO@/]?Jm>IbNhuC)(3i7QGjt[o_n[]],>Y46C;2%ZOfU,n6RL6
|
|
||||||
pQXGh.Ts48AWn&r=TE#APblWI!jfTH5qs'iZ7I([Rcm#)4]s3/IWG@.!hl8%
|
|
||||||
k"5Ai2hk31"fITC&?Wl%R3r7DZT<9r#jE`;DgM?VnOPTHJ6NEF=DX0oV=;jR
|
|
||||||
N"ee-'gN(,,1IMA,*\r!P!#ZJb/Yi%P$?h-bS)ApoF1$2S`.D2UXU#ifa/5t
|
|
||||||
gE^EM\-Jt.FMpZj7&UVY^aA_J@B/D`\QM+D5?Y%:r8SkQX)mILmG#,5Qa$@m
|
|
||||||
8\`%IN+JM>F2lkn-6bE9+'C"P4$:%88J]Hko.1njrgkG16n$[KbII#_p%Qu7
|
|
||||||
AE+ku0`TXT)a,W-TbqN<#jU93V[&HNckm'+eCt(+$2Wka!3/NpDgDtsPjuY!
|
|
||||||
&Osl;iABPJ;B\[/"UG*'SN<VH-9&JJ!>M&U8$BW">.&+qW`H6*^OH,o4LAeL
|
|
||||||
6:XCa%o6PiB$Hd`4)!?TBp0l<RU-?(2"pE<#`Tf\CtPsX#k]jiP>=C<`"Pp/
|
|
||||||
m\$/,\foSn:tn@>>.&+rXN:c=IYfebFG+3NbiMdW>Wh$%Tb9lJ,Z=l?0N&Gh
|
|
||||||
%hOq[K8PLeJg*I1A&jUDW1GZEB0I@H?K&bTn$qD<]C*82Gk'c!.Um,ORPB1S
|
|
||||||
X.(7fr^"6`JoT&6a&>e<rVQ>Uf-b?fn#),:EYL950ZtXh\U7\pG(iO\9lOq#
|
|
||||||
XrZT?%Us\o*&sSo7H!XR3"?ul#`4f!Zb])99lfrHjSo#-^]+6,^OPUTa&(g]
|
|
||||||
,XjL"\_WnqMA+tu)_%J0.!LUQk"/iU(.j?`YBf'ePuh98R"@p`^A)0l[V`R2
|
|
||||||
gPKuukg6#2UgK&_!SG#.Y9G=NE!Qjc;%hIeB-:05)Dr'p+snP*bJ%1&<a&H-
|
|
||||||
d2;tLNJt8rI#=V><!lpKq*ZV^P`gLqn=[E9NZXdCFAo)4?+kRWdA'4V2?cQ"
|
|
||||||
#''Y?Gm#\OTi1HX*Ot!Vs#lg`@)7Fce;=tkcCI&kdqoTOHN*j<QmB4io(Ot;
|
|
||||||
ehV]m8eigT:`-&!&shVmQ)n2I@ttO7i"S,?3HHMS-S6o%d]FE1@eeo;+(13'
|
|
||||||
3`u]j''#Ki^Q=kX7>i@((p'?IgAp1[a)GE\GTFH2IL`l^S2o(A6m?6E<2l$L
|
|
||||||
;Y'R(,(@?ZKiF0"q<+@tT&PK,F%g#(oSZil5CQj_]O[q`Br_?MX^AjBUQ7T"
|
|
||||||
@`4'cHhKk=]U=<Bo^jkJ`aG&9HE[l.fdY]@]YS4)Rb`8PSbLra?^irj/Cu8Q
|
|
||||||
c\<To:*/p$0N&I>,@T<#7>mE&],fEsJ/^mkC9$&OG7q&6HTG<AG3snm51lMo
|
|
||||||
K24`1oZ3.>\$(lqac@H(VP3,j+0.uF2VMDhWa371^%^Af^3o`WX]fl=THUV8
|
|
||||||
;J-N15UCZnZ$lNCS@!1iRd72#ac[r!.4H&c>e#3_/&6:Xk!k\T$ULAU46i)h
|
|
||||||
KH%IBj[)gcEr,Fm8/F9"JG-bqWJ=P0'Sa_a8O+\a'e<<9=gB:gLMs_T<?eHK
|
|
||||||
8.*orJ-,WQ2A&<m->H*TS32)&7f!:AMCG&[J)1AofJl$\;g^ZSq!^Cknt;Dc
|
|
||||||
rq^D)=Ld1@AECt!d)V]q@7Ekg^RdT8Q7,V)DV_mO20\KDKS0K@4tMr8-me_+
|
|
||||||
6ALSS;34Fo$;PSrC"&qrfWZNdMAT:eiJ/N9h/.>n:)McoCDR=<?bUp+k'W,J
|
|
||||||
,td=ERtjQRjM;IW,*Y-3iCdE\>g&EbB?cmQGOOBGCXu1L3)ObU2-@^S6cq05
|
|
||||||
nc5;/mQ%?`p.hBGdaHQ]G81hMC-X9k5CORVD$e$*2eaH?-X<`[^HO@dZM4=l
|
|
||||||
hab9!R=Or%o^Ut^'0p7ELITD^<^"<p,DLe3;l35k\od2,3HN3")ooGD]6@>+
|
|
||||||
Y!l5XU?A.u?="`9krB9aJ"#6+F6Cj4:db>5(9+:4@Z;6SRum`F3</-1Dp"@t
|
|
||||||
>IA4r#nnWs&Oa4CR=Gqu:(/!q"AuW/k>2KVoY(R4ecMKT7N&8fNoIBR'&aTu
|
|
||||||
_%a8nlP$gt1hjg.A&aJ$<eK?EYlH<h7L.MsTEnDO'2?O*I.?48\T6du2J17)
|
|
||||||
H0Wm.pV4G+gBG35ml!)WpcUWa&5$Go1dk@P+))q3AL')`eqWcF@;^>sW.\U=
|
|
||||||
KOa]?e`5]MM*PY*DnCl6E32BB-7:0ra%\H4"(mN22(Eh<gdh^pEgKV4IDo^P
|
|
||||||
\ojA`d74\<dX"0rc?02dZreESV(X\/>njW*?28pfK@J`[[!MH!B8[HQK"!jf
|
|
||||||
o&R]D%nHJm"5F<T3HIZ]T'Pj5H!7t3rJhm[:s4.TjMA`:]6<T/9-OJAY[G9K
|
|
||||||
NKLGMKV5#/(.N#&X<tn*M>eBE%?@jo3S$g"8jar0l.N@RCY?!>0pJlFiV":,
|
|
||||||
&g1[(4*Jhq4I[qd=t#2]@%_3GZgHMV8%Q>l$=k!K4DE4hF[E))8cG<U-`H*$
|
|
||||||
K+_uu+jaaJq0j6(oV)S%q9/s%Udg=^3HO?DH;[X=0Ma/fOIDUFG@6N6P.S5]
|
|
||||||
B&.q-br=b,Q)e0#o&.?:p[-]Ol1/EfdaHQu%3&m]@R"g>MmOmSJk?qlY!GSG
|
|
||||||
'_ngSBXG<dP7i9`EK?enfs5/s'I^bFW)t"$HSI3J;%8+g&Lr/7AM*klB[NRP
|
|
||||||
q=?O]fPoigM+<UJNHBG%!@]HHRKb&NR:28VB?MC]ME\c75rf;JIb62sI2Xl7
|
|
||||||
'VB3*@OP!/(+oC06P!W)G4!uul'Dm!1P#)'D;-sNj',d-ep<$Wj2[5,Pq*Y#
|
|
||||||
GP:CV;(s9slbGS3<jMZ*6n8`-dlK(HYBVu/A!9r]^h,$4me05_Mi:MCH1U1#
|
|
||||||
=gR8SdA%3n4;d>W%WF4CmbBfH-3VQK-:?/N?Na1r%YB8a/6fP63Waj9$Pl!n
|
|
||||||
+l7<N)f[,bjj)KnKB2T;SX.kj=GN-QoVFP/``"Hm["sWuCYH/[Zt\"E^4lVb
|
|
||||||
N74\#qc56<mGbKuoB)>j>8A\fLp2K7U;6T>E8cDG6!4WXc7"?Hb4Wk7fh,^l
|
|
||||||
Gu-2k?<out[ZIS+RrJ9bJ)Z;4YjD.6nFu;&Z]T_*!&k"u6jf/`q"CA[=0E[]
|
|
||||||
ouA/(S=H)hl1/EfIe_lWTgOTm39K8S"p+"-Z<a2@oe?(+\oEpSk*GEKZYYFK
|
|
||||||
-P\.4,kg;3.or_W"s(Ygp@$lLCYWLn<em?]S<bg8%\$;'6rL"`WKAfO-3,\8
|
|
||||||
P6##/2f;mF6e/2:FVGng:o-c!4&uib;Pd"4UdBbB%3%4ToI+(%Ph:YO_EsM9
|
|
||||||
;5.$h;UEl>3!Y7cm+Skj[`5QE>n)0T9M@!8U^1;abaC7_SidkoJoUefH/9?E
|
|
||||||
m[7^Z+i%f8.H9CJ_lri9)WY8p75:$OAAe5ELDN`>?bUp7P;"RLL(age=oA'T
|
|
||||||
O+^%JPcaC(_^&i&\OMW$?*I^&jCHZ47FAA,!\WBYV+\4^X;sfD#_(f_`)u%D
|
|
||||||
ioZYkE\3NqppFke.P<?V;56So-VfV\iPYaPJ.)mLo#?Y.f7`'7l*"+cq3F"7
|
|
||||||
B[NQmj*Vdd]:Z!tV=!'K;tKsk$!Q@pM\]cGYY+K\ZuqUUprZuQ`H(+bft)=l
|
|
||||||
cThFlQsCOR3-!p\4E>j4>CKh1_F!5H&-r[]0L18NU;5m^/LH4?c?Q`Oc+.f>
|
|
||||||
e'lc1r:!(0,K&T&5#R9!O*0a;.9*5W'_;b]%M6T=E8Z8c8n+!r1V8tF-R:'I
|
|
||||||
+$4X^F::4AG[id#s#kMi'IFbKBbr[3@`!<Mi)lsVV$A)iYZJ#7dF$PWi+df*
|
|
||||||
"-;79S)!D,3@6("&jS<lgj$ht)97aX],3sPl:12lbGRXSRU`:d\`'BO?G]#l
|
|
||||||
r:%T=.T@6EE+!e[2$E\W+B4ggD1Y&;o43A_P9s#HOX!B.G4!$Ank3Hf8/F:M
|
|
||||||
ESWu^W>p_(!L,Yf9&7*"3cmV!eZ2bigph@OdFSrfZ>_)pa+!F0[?c:Jha3e$
|
|
||||||
Wb9YLH1U1ceu]n+M;lM9Y4]K4f5O5c]&>)%37YT_leK$l5PsfR1M=uAFuG9k
|
|
||||||
S2bN*&eV9`ZAkZR>FRNf$5=o8Vb`p:>IHrQH3h7\I94<8qod?[6Y2su'8LFG
|
|
||||||
A#!QR2/8@+="a7_215o7IMTF;cpp)Z910VdA>cG-X&c)k)<>H!hS%<OSur_"
|
|
||||||
qIsYXn%A7P"s*Xe"C%<*n?I<4lF;4[^A2BjoJHdH=mXkM9;t&*dp'+gM)N'/
|
|
||||||
4>ARq=Ku>CCY/RHP>@o_)Usb^#_d/Y<H?-M>Q.l\.UBW-ac[tIp$9)0ldcnS
|
|
||||||
7'Q`g\Me<Y"HUbb_'!S<X_lb%5%Vm$S2kXKCY!=igpt6FN<V^BCm-3bgH\p!
|
|
||||||
3\?_s`u]SDo]X\"e(5-R"$hk<oQ7_X&^&oig@gTI%t+--GOsn;e#?7n^;Y#I
|
|
||||||
AO<rr5&8_#\7;R5.+`,1:]sV^g9o+i/K8,_oecl.HCR*C&h8`%"O;qCY\M^g
|
|
||||||
[Vab_nA#,sc@ucIU+@O/h1/Y'7ZG8+g"Jsa#1f6Vqt/?5a2Q$*DVZ5'3VrRj
|
|
||||||
!aHM?WN!0q_P.SWL;:bhFm9MG]`8!.4*Ku#(_p>f2ib4WcY?R`Dh*LdI@Sfe
|
|
||||||
<D0qR&>'.EM&=(sh7GC/YXT4glkDrBMHTQ5bpueo<4-Jk7ZJI6i4snO\K1c?
|
|
||||||
oYqhgpuXipp4\#IjiWi=;P_CgDniu2oB(rc?XlqA[q3$bf`Gq?"eh]Y"fM.j
|
|
||||||
l)109b*<A',p=Gj`/,->/G:ZL[;4B'K.OcM)%!,a1qTu2iPK0LQMp$BO&AkR
|
|
||||||
/d?ud!>#gE>IRAG0akrjfr;0hqML2`"qA_A0-B`WJcuO6Y"?Od\T>#Gm\6$d
|
|
||||||
cGr?W:`VT=aI+hsh7@aLN>lMG0*e#)8m"T5H@!.X'&cYVJo?#.k^fU"@(uX:
|
|
||||||
lQ?`MW4E9K-9rP&dNX0:*P9Hb5c^2&>@u-K(!AdN`f,_TFEH?aGskaV*NJ8_
|
|
||||||
acl#'21JE1amp(+#hr=aABE]AHY^aP8/M(<`0K13r&T_6?b_'DD+tcQ._2#c
|
|
||||||
o!)N7HFV*<4*ID<,IQUdD*b#^J?&%QJHP[iQI+[i9+RQQi,QW!3d'^eXB<k.
|
|
||||||
@>RT]<fl*p5&IK^"TFunNjUajD7_4A.o`HDY[Bc"XBrS$JqA=AO1suXhFp='
|
|
||||||
^2(cL*JoW0@MAB*oDk.ujN=Q,Zc\L]e"B!Zace)BrK=&r==E'.]6E^mn%JHV
|
|
||||||
GOF8!H1M4]lIW6>Uk^<a^oX;h`f;(/P*-G,$+\4Ah?r"e6iidmL(,*[%VBC:
|
|
||||||
"'I#amF+iqc''MNldi1Nc^l/H9:fIuIF"KJ91?Plo#oR`<NC'fb*Od-Z!=h[
|
|
||||||
:i+dg@Ap7uM99s*m^<mFiT@lV:V:Z*6;g^Eq0;K)k0KZ\D;mNnhE5>G:S0iH
|
|
||||||
o&W5ZLp3V&4To+BADdNcV57$RIJ`_dl`IXBHI-,gm`DtYj)C0""m(6!\CCq3
|
|
||||||
i@8gGr-=;eL(N$>3.:Y1m#fWIRhrr),tg)R[!1uqfWqG_nl-8MBbrl$iF5@C
|
|
||||||
NZC3iG3rI-+1#t+2O05B^=p>O^Up#Z`Q1X;<R1*F'uY.=Sit8`0=Rd/gm*Ju
|
|
||||||
i/*c."1CN4%ACHm[r1#Z#!6e^^ki^P;#WD'N#XQL@Mn6SJ5%"<>6G,+9:,YQ
|
|
||||||
j,\EPgj,*AG8$(0Sj%Adh!*:cc^6EhkB;`8:HV=@30E-=bEaa7[r3X3;R5`7
|
|
||||||
R58(f4aQaf0k3\3(kH$Q1^)QTJ3Rr+[Z,e(h7GW*].20N4aQaFrUndSGOM[u
|
|
||||||
BYh"Q0>-ekW)9=7_;s\<eC;tR%bH@V<;Y1E;e_Ja1M7?;'5Pg[1IiA?6@1^l
|
|
||||||
)U+tXR_fi2j%ZX[[uT'k=Z<\XWFlso2,Efan2_(Er,C2d<MO0nAKMC&3eZ8=
|
|
||||||
.g4T7i6=4<jJXr[]63EDA]B`NMf)KQ>Y7VGG1>&==R1G$g9n!4?>eAt*TB,#
|
|
||||||
jN'oV->hJDaXhe7gqRRPVJ16(:s-\.*_MYF;7q-n^;?q#m^_LoAV=2P?_iVp
|
|
||||||
:/O>E_@-qdlLW=ds5Jqi+OjX,MFZGE`PR+IS*G[1THSel[VQhbF8Yh'S)[bU
|
|
||||||
?b`1!;RQ`=.'j9,3I3Z'hRnge2s.e1kic&gZYpDE2fI9eC4`kuE;9[k[Vboc
|
|
||||||
1XGPBIYD+#'3"Ji%Yi\`6YP>h3IetT7EVpJR1Lnro[/!OO$<E=E[K_,7ij*X
|
|
||||||
VTdrtrcalo\om>uh@C\l>b5[ub)Ft?"Pa)^W6[TA$TKFo,u/Hu&4g%\!A:pA
|
|
||||||
@aL!>i[8[qI)UO2pANF:$>#.)cU3-+Sk@9YE8J?Zru=%>Zu=ini^MrU/8-`)
|
|
||||||
@XApFT'o-$9GJaQZY%HJK*GI;*3BO4ZtZqmKX/#1TiK-n*O,cBRP_iI7<ZRm
|
|
||||||
fbt,+CW*!RA]os!!ZhWfOsNa6.AI%8HQtLTP=K,SW&+o/[%]8,<\Xtp8[Pc'
|
|
||||||
hn"09$e7`tEJfJi2f9U1+Ir#5$CQgZZo"^5Z5`*i;W]Vp:gb1c:S'[>$Qa$S
|
|
||||||
X`VMIn<spOI1P%<c.uff,;ddV6!@F7P4L>3,=hshgs0.:8h45VC=NY[UjX;d
|
|
||||||
Y9?emeEPG[)`26$AA5J[JQ&jK!(([Qi.2,Rk0/Z'5S>9>3Urpn9UINUK*[0Z
|
|
||||||
JQ!QY!+i8H<Cp4;]!>9"8C\;1=fB:]Bh+V.jiWkVW`,>B(2-I^H:gJB0&'oi
|
|
||||||
U-/,7f.U+%&2H_&*V!*/VLpfo',!ZHB.2C%':WAFd1Ff@e#!Qh,&/6=r8(^<
|
|
||||||
9P_X_W>;T\JH2aS!=Kb7^4oq*G#b_P*?Jqp*F(eL,uXuL0Me3Lc^qghiPK74
|
|
||||||
c.e+T'5u[9ER^paY&0>IZ?V8%B_An@R4SHeQ!LW877>&l)_bY"TeV$BT).OQ
|
|
||||||
UN1Dj'dJWA&M>N929>m$:pk&g0k855HU^C4TqKE0@DfIiRd9+X6I>p`Ctc71
|
|
||||||
JH?"J$,JCHi8"nb4S[K3Dn0Y;Bm3kM!=JUZBEe4m5-jMk,<?M>c)g-Nh:#!a
|
|
||||||
TBm94\@=MYf]Z&!2Vn9D)K=+oN#7,$L-\-'OA&e*V.""_0]12>](l\nEWS+-
|
|
||||||
!V#iM?@$>Ti5*>kmUcorgDi_D*20=L7ocQgOZu\A;Gb8,4RiQnYeL*mO7gY#
|
|
||||||
Uo(#(QYLA?BXCYS"e3#"]68&.Gu6peHGbIHBQ?`Jg.o7^/,=NGMi\'-V<Vm7
|
|
||||||
pBXe?X\<018Xq>rg00.2G^[*0]]nqE_tWNNF#2#%!mQAY;hhPea$M1s=<r,&
|
|
||||||
VduBL=Jdd%NX,1S/?>X-9,IC)KVG`]rlZUg+VY*tq7"U!Q2&mO*aKaGEocZE
|
|
||||||
]Cl%L@$"bLo%eNIaktPon)BIib4Tb=1+0:`C7k[:)jL$U\D+`-TjE!&"oQ'&
|
|
||||||
2/=BKiQZRfWmQ7;7j!0OcpJ?>`^cH?#P]'$2/CbOdBtC_ju54b4oQLU6L*JC
|
|
||||||
[>2LbOlZKjOcD/jjs_l/'qISlRH#Z,5oIkN><%87U<.'bmUKa.I)$K8V'c`s
|
|
||||||
NoN(q4OAK&K^7UQ`8NZ6a&k\fBQ?B^.FQM444Sn2m+ATAqtKDBr/p063-!r5
|
|
||||||
++C^<J(s^Mo^m8)bMBZCKh!HK6ud]mnX21G\=m-tn=*crVuK1=)!;K8[r-m-
|
|
||||||
HhZs)bX`,,A+s?AF=Ae^V>MCTmbPM8mpW)-=XmJP_A1I+Enb$ISZrRV[V_Kk
|
|
||||||
CY&:UGkEq!P@Wt!4H#ktE+[Y#<SP!$a5<b7O>hR:C[H0(pTP_,\,hi<P;i_<
|
|
||||||
PUMYMB27Ls>sWP5?b[YE^epm9ZqHjCn:o4n`c3rk#fQt@#VkiF-;9f"(drO>
|
|
||||||
[%^q;)`G@hN]Z[?l`MF#MELR];lC'_N0<XB_rl+eN1tH`2fHu)PEV3d&MKM?
|
|
||||||
j;G-g.7)T63p9Tg,CqQJ#uX1'-mJ@FCn)VLCY#T7.X_/#4^sk\X)mQlMT%\p
|
|
||||||
hRo7SnLhJ:ln<Mk;;pLnhg>OdW:QY+nFK&$!SQDVfPZr"3"KL^n=&,J$7"O)
|
|
||||||
nB_rmGCrD03[>6d=;3@+Bi)o3iU=E@omid3JT0lY%NO-9It*oDVt7XfUu7S%
|
|
||||||
g:"$dg@1X`U):lX-\3*Albe/?aU]@N^n$=$D;?o!.@[B=/0BA`Pq,qJG3sOs
|
|
||||||
2,/%eEG7CWrTpl:@P=>D^^6a\.o861[VXpiW.-oR.SX,"Nq>"L"H&'^:fQLS
|
|
||||||
$A@tXOt;+rVi4c+otUY?'J!4CVmL?tRZJBtOH651Q,7bhKL:J&J_IK%`9EVp
|
|
||||||
>\hroV:n05puA&8L[lY%eEG;T,pD7a@Y=/Hk.C)rRhnBTnOrdp)cInqjb?jr
|
|
||||||
"`="nM_?u%d"!)++sI^&L@cYor.Vmi5F84YAaF#S^sD'ja?9$MIR=TnofuW4
|
|
||||||
%.fs&*orQ7l9R$QD_u%J'Q0aXKi*[K9_O"2k"WBWOgnU"X']<bd'VS,]5;Sa
|
|
||||||
EY&Q;'Gma^@Ddii:d4GT6kN.6o]T.Z:'N5DJN\WJ;#-V+iQ0bD*bZ,m7QnX!
|
|
||||||
IPBCV5N<u_hQpijT$ED%5$0"QcZd3g''e!n9&^1*OG.b5c0Os9!>H>1Z!eB5
|
|
||||||
56lus/g"=c6<n(=%ei"Vnr5Tb&<E9+B\^*Pk:#Efp$6*6T6oudlA$/6>h`m1
|
|
||||||
>dD.s:7XFC$-3+XQYNU&X]d"]qLNFXFMnN-;5O]pYB9$WYXo5Z(+N:^@)7GJ
|
|
||||||
=3q;nMfud7RsXhD5gU:R(IlOQ29(9F.C]HXGt4)?6A89e23M;-eWm`X$Y6]a
|
|
||||||
'IrhH`ujJFl:f.U,I_QJ@nm;W'Urp[PIS`ZjbTYQ0f5h$f1p2<\5@tbLj_=A
|
|
||||||
@N5tuatEt@i7b`$G2ihK.TOaBnZ4@tIt86j0@Z%_No9>).lAj?=XW!gB2N0;
|
|
||||||
=kCaSKM^Vp085dlImFbC`G[`fa?%_+qF@][H0,un8XV;;>i@+&Uja`F.A\B@
|
|
||||||
+iT:?"&5A9qIp(t]CAb3:1fu3%5CT2Ppl4\/+!NUStNmc[VXV0qXB^b/QuNu
|
|
||||||
["C8c,:?2J4Se'%>j(>_%8PID:tl*B>)aFXGer[qX)irH'U8lsd8A1(4_TcW
|
|
||||||
-^Jk[6m$qR%soug4o!rf]EK"u^a+A&Fj\R7kf`\thB:"g4NK"PN2P#fU6W/L
|
|
||||||
<fR5i6]KR)H1L$ArqY^hF\%(d7e`YH/IcCXFI-%L[]2dplkjW]bO=i=mSX"l
|
|
||||||
/A7^JAPNdd7$LkDY[)U8&Q`T_&<E]C+Icrt&Y!=)1ji9Seb-qo+?AY5*Wc"@
|
|
||||||
hiDEDd&7E(M3NtcO`?8aWR?/F6gTqPF`>5IlenYmLp[/XUJXpBU+O<e@31AI
|
|
||||||
R#h1@o=&JbM?:j$m,u1d.+(OaZ48=+7:D.dG3t/'KL\Etis0%SI%7L=@W<uE
|
|
||||||
&13%micerV6Ilnh19;#)Ja]g7S'/G@cHBUP;uAm=$O>h9&0CRBf)l58i.;ml
|
|
||||||
r7mfGKeI>\gkHl8%7.[)lJ^jC`qsJk+.SLnW`?)8Q&iLGXbCu%h&rDIa(Z@>
|
|
||||||
QTPMA7_ZH=&[a'HldDV&[VXVn>kn5A01:1#:(pnf9@c]u<YUonTM_XfcM7i;
|
|
||||||
LL457(.ZQigVm;=fL7NZ59_6b"sWNn^L&#f8)ae13bE\;.N`Y8o;$;%'^h'!
|
|
||||||
j;A,]n7,;MeV:&HA7"-lp^k@p+;TbF7L`XcX[dJqA:@,"K2tjkTe1_arR,(s
|
|
||||||
bofeXXrt%+TgK'b'S3.^1mX;O$C"eB`)1"iGdOE&8&pb#+Ddr5\YhMm>8nC4
|
|
||||||
FrOV33A)pZ':c7<mH'Rs'S7-',;oQtGaoVhFjWf4isL!r/(gdtUP9i./8S?M
|
|
||||||
%i?*,\<gNJ$0?d`_P4je\VTO.P-pIhP,WJiV%`j&5nmq>?l#7V\3lPn*o`""
|
|
||||||
Fo80CW@o1/fL0R,9d01#gJQqZ!uJ4=K_];(%HnO3EE++`^\DB\NZGH:ab&&t
|
|
||||||
"ZEp,r'O>BF8hnRK4^qG/,MJ7UDW3-,SPr3]ioX^r'c[te#am)QCCt'7:"kb
|
|
||||||
@Zq.;IDPK4GTr0?U)p$YAK)=V$K@;q'g7t%)LEoDXcRhP!VaW#,t<McPR"LQ
|
|
||||||
%)G8N4.#Cf%oBCDml<lndBo,O1?(3JCNB5a5)u.S@UYL7V%AK)*%L_SX@/2^
|
|
||||||
LHrqU"XUOQK[LPs.!t[SD;)@j*Ii6k">@_%+sF'T^p5)iE!d5o.IICs6m$qf
|
|
||||||
&o4/$lk/EjI%9-o@6q+rLje7!KddiG%CM7`#_T-XX_M:2`Dc&]&q,^8Z;rks
|
|
||||||
<o[fR#NGW#*IU8dj*P>)#UQ!Ie2"U.h1+!(kX0HeS..?Y['<e;=UW4/apQ9I
|
|
||||||
'G+-h.S#L=3/T+XCm-pb_bS,O(rR/W!1>G9R<`[BJ7qt4e5N9.\odU5h+Fmk
|
|
||||||
L$g(a,W;X#^2XiPSHT9NoLEdR`39suS$1>2+MPAEP"pB=0Gi$O."+K.+lmm\
|
|
||||||
Qrh4Y)p5QHlXgpTG[sc/6?,EG&/_@a\$XQ'*]S^O>l;qNA8tm=BLSc>=s6Wh
|
|
||||||
C4<t/\oa3qd/4CHlJh\\K!.r!N2a[urp5jMe/`o[H@eZ5KHRJE%A4VpTH&Y;
|
|
||||||
>60/j0*h``GXkL_:O:K<qtAu#F7HM.!OH$+Pm]n=&sZ_>A6rX5il.T@V5!7@
|
|
||||||
/R*u.c,AIRoFHs%Q)QgBo$u8#Xd!pJj(1j`MFXD(Gtf'ME9+H,l(*%TM.u*b
|
|
||||||
E("_!,NWqQ'>r'&'I`I_hu2u7Hg\^m(T1f5P\Mn-ORh=2:k86)f.1&28)4$6
|
|
||||||
SbS^9;+Wc2Yts6qS+Ja<VLA@8Lgjh&imFMkQRl9kg_tfqYsoO@P"%NPNKb$:
|
|
||||||
F+b<1MC5-:OUr*mf`n!d71?I/\)2.VEl1&uoaq^o"Q50=P)YJuIa>*Y:#(7M
|
|
||||||
b3$MSbS,Ol/IRW(b-TJ-2?9^tkK^Se80``,cYTb>%sF([PFGrG=upmd"JmI+
|
|
||||||
rY\*Y@oe3nPQh7)&r.,5;:VR>SK\-;i^#],*.m9#6n%AedVr?KSUaG#nIr%_
|
|
||||||
'SPIH]H+gZ-LT/HS^?.K1aZP+?d,65Uth1fOqT6rA`=u'O]e+9!4de.[-ugp
|
|
||||||
hu/<pr.-q>SnosAn]3%`_9Kanm;I4(EQ>84rG*mYD;2K)][&SP!BK=_\.]R5
|
|
||||||
)TM)q<]j6)#'U?FSVU#2(qGLL,`,#,'Bodr&3M$1h07cHXhC]:,s)@%lWKaV
|
|
||||||
B$=G8H?N%(.M]16p$-\ceu:F\)6tlmP]#$XUr7?s=p1mW5k7d\$#6E^M$iDo
|
|
||||||
HXm/A#c50U5NBG=;C%Ypas>)"a[3Od;U>b/+fH)Z]..&WIP-@1HO?5"NWLU9
|
|
||||||
agjMs=X?gf2#a'YVnNB>9lfsgmr="3pYUHbIn+="$8i5dWd:lLb(^sO*f$R@
|
|
||||||
D#Ch<ld1;QbP6W47>gRj!]7iJ(X0Bfi9Gm^6%u)#1oH^u'q;:K6QVmoQ.!;l
|
|
||||||
2C;_\=+um5q2nnRgL'sN(H=`=,!Q@2H"@b+=aCVq"W^%ne(i`B]C?i.,g4ni
|
|
||||||
)?MjKEG/=a^=:Y3JkG?4ZI$^d.m+`FL/WZ3Pl1D8J7rNWgn2BSIk=d,J/>Sr
|
|
||||||
XNS]o&/V=6kF[6qpmi'W.:KbNX!k!.![-G0(G8+n+OV#InWg\[e@Wa]-iAZ*
|
|
||||||
_&/kIX&lKb)PL$,=9O8BU)s(=V?GkUBuDGZVe8Z(d&+mal#!5PB&tVUKciH'
|
|
||||||
$Y@cqnTtWZ/4cUCG/JNrK1*gi&G8<(e_1+D.Z2WT@MXtGi>Y2CT$YWc&-d4R
|
|
||||||
A>(D$/g&5qlGDN@V'$7S."mbGP.tYRi^h:Y2/?6Qr)XSrKp(J/S._r#(S"=&
|
|
||||||
e2*OLZUSi1UnjZABK37g/ftOXkFeDjUj[auAc/>aJ`O`Y_\G,X=M,PO_V%<,
|
|
||||||
Cu;sTf</C9MP]#XHXK"@*7Y@L&[@G<c$N[/:5$JWLEI-;`PPtMA/j-/#mZkG
|
|
||||||
:8mkJ4T+lUJ*k[\Mm@VG*+Zail?>T.d'T>4>]]Di/=]B<jJ%OU[aNM02U+.j
|
|
||||||
!Khs_RLaC<('S'jMW*"1$m3.]c#8'Kc2[HDrtPQ"Bd73m*KXt&gpt'Z21s\Y
|
|
||||||
$#(XG+5V$Gb8*AZm[ulj?o0Sj*&Rh`hJduFn4V$^J6&n,i0X>9oJ3#8GPq5U
|
|
||||||
]Q@Hh*[qoa=t=lBYUOMI]G(@FaIPE%fOWj0W8o;L\Y>#cKj:rr%DaU'JQu@A
|
|
||||||
Uu](\-_71Z5dkL^po#a9AL*45ErQhI!N8^[CtuNTZf[N_51%:f+__]a?0>l]
|
|
||||||
V.9U[;N=(`Ym)YfrA)G&3fUVDm=+`/?h&:RFj$(&(VM!d;W\bib?A'F_RO4)
|
|
||||||
=-c1[n40-oBKO<.mC1uaSt>CX4:[K@0PEl>\QH+G-t+Mh@4W=;j^0B_X%Y1M
|
|
||||||
7VR:`E.<`BUVjVuTc^:e3lFo)%mdrb"/H2:)+,-8Ec/$pe.55=M5m)=oC'Be
|
|
||||||
?:t4[5Q;TMV>a>0MM@5^_hSb420a>)Dc'\6,:1j3CW#s-U?&iqKu=ee/%eU3
|
|
||||||
&tTJHCr:Hoan"VF)T\6u+8Y0$YJ0QR_.9-R,TbD/S-'.i`%55_,P%p:i1A8A
|
|
||||||
BG:M&PWV38>uSt!J5Cbt5URX9GYhm"WdBhi13*D5X8?PUSm<@OmUTX!e0/E&
|
|
||||||
T%J]en2MnZ=3)p#779Ul;/]Xl)G[9@6A1MmOp<TaKks:`bX..e>f#HcFCCX6
|
|
||||||
0D2!\a1[V1Ab7\nc=S^:DHbA8i!sH4I:r\N8b;8YX`&n8H]"Cc*E"A%O4&LU
|
|
||||||
p"*:N,F$k"=-@hFPUGJOVAI6W$L%dFME;J!;BG.GPKm#P_-K5qYp["LQ8!0q
|
|
||||||
15hOTKUrf$h#'rtqNEu>@)9-[b;?h0&N!"\'1cjlP4e+o:ik;@c?UG;*4hRh
|
|
||||||
P+8nd1tn;YQCi(/F_rL&:2nO@2\[eI,3/H-rquSj+Cu_?a0X!Bn6c4g?bB-b
|
|
||||||
!X0tn9gG,3Mm+bD]KT!bTqF)ME/1HG9l&@`jW+,1pk0h?QVdG4G9?S*R564J
|
|
||||||
Ab"d!Zp$t%VT,*pB1""j23*n+]2)inDr.!e(5c.Y#=Wa3)4F_/7k7lg"6>sh
|
|
||||||
62=W*Hq6Rt>pF\%(mX^Ihu4B9`>)@NlC[3k-C?L=d's.\5s[E7cF$8:*V_62
|
|
||||||
ba<aA!7'Ga,ti>mbCR9f%OW61(]8$ll^g7d:7XF7dtfI56ElCO(HI%F1`93-
|
|
||||||
X2u*`,_Kl/\2baTf3NUZT:j)_*'&U`9C\fs$mL`.O#TEH+;FS"5R7fF;?5ga
|
|
||||||
O&DZZ@0+mg"d%ngkK]V\H1Jb%"'2\i4D`c\N>@XJP!'Gf,!p:$B]6Ut[$-Z9
|
|
||||||
Ukb.tPI6eRAQ*u6Up(>J&=Y2hGZgsj<'EKu3IB>XCBGi<U]9h=5N%AaI-u_=
|
|
||||||
5V^,&R3s^^.<IP'D1YU2R/j6YP:#')H]$B#8tu#oGc2[8aG%^kF3d#5F2X=N
|
|
||||||
_$*P,9Z.p;$,N,p2p2Gf%1#BABdSGFe?u/#m.[`IC.GPeol]WR&YDcRM8Uk.
|
|
||||||
>[;pYJ>XnlAA4ZMXNN.5Pd]WHBMMcZJ4>;jLleEV63ra%BLRjFASF=0E%*2n
|
|
||||||
9Pm1Tgh^7aL1qP-aY1F,dUe3%%uWiK)![6udj!\!s#lOE1q3]WD@EHWA=EqX
|
|
||||||
^`Eku/73&J>H.rDa'3%;&6(W#_d\&nSd:$2im(LY[hYB3H^5g6Rmm"uf!IUD
|
|
||||||
caY&=o7>2R6YKtkA?aRg9+.JOO[bp[agr3;<bTdjD7:ZacaM:k$m5W,8#a?S
|
|
||||||
NRgq77*-,<R@5<c!MGK7F"ItIW!<Yr@6:uZGs-KGW73eiX@.`LXE?sOK'?-%
|
|
||||||
JnP`.icQB"YmDJn4NU>AFUpV(!)GnN1dqZnjK386(E]d?rY7(m*00&l+s]*,
|
|
||||||
e]WE#l)3fC.9$JY#H_ZW@U9M&PY6OmiuS0[5Q1cRh1gS3#O!Ke?l?h3V*dmP
|
|
||||||
D^d+ane4hQW$@]a>I22>.)7SBe;WZ.h$"12Xp.Hqq5S(fZiZ<8GQSDDPs)DR
|
|
||||||
F`.,o'hX/aV>sd8e[gVh$Xk.Q0t6/_Zjr@O\Zp,^EcKp'"aU_EUE11'>e"qB
|
|
||||||
<\1-=2T?,KKEL>S!Q^lt58DB%1euB\k&s15B9Z)U/6Pg'G`ir*hF+0q!IMOH
|
|
||||||
jtmjT5r);']\refqt2V"C/+V@#SoR;mY6b7+I]aP#:-*^gU:tQ[,Hfq_P?/j
|
|
||||||
Zmdker,C%Q8#RM%VFk?XC=]K.I;nnaj&?iHBh(,0B0&u&'L.TP<82X@6F]f&
|
|
||||||
VeudSOgMWGE/2K]7^Or$BPDN"b/PG>Tf@)ms8CjTV/2AG;\lsB;5dn,P114O
|
|
||||||
E6XU!KS#qdjF0?QotP:RNTM7kOoR1N8u$h3mC.s`:S0h10;i?m0+>*_VF?@V
|
|
||||||
_.6JAq"#CWb!h=Q!Z%hnq24<eI67uILi(*7Jtu!Z=>jB9;;FN"Y1^,NX#A_L
|
|
||||||
L+)9+L^,MifP</F(L;2$>\9\=\KMbh)\tfoi,F7fdJDXd[i"@(DUOTBn-=9d
|
|
||||||
X::cBqk?2ePRM.IaYSKfEpse+qA*dr>VI?S/e2BB;_@<cU8,)!kt#)W4Zk.E
|
|
||||||
B+m6!J_;SYoF`8Nig4t[1p!Eoldk'aS*O!ZJtnJk:lIm)a,67c@i*cG5%Q"D
|
|
||||||
)F]Q"`@4,$i.p7-nJdFL)aIO:Y;1Qo@#jdpi<p966dXX4'B]'U=DA[+&Xl@_
|
|
||||||
>W$Z[]i!,u2jIIA.]B'X?[P__CIr_IX,:#)4.]3BaAH"saW:%%R6a.^)AS93
|
|
||||||
i*!&T1tKrM)8<X)U'(.cH>-+!;4d:)k)=7J@]t_IJ)kL7"5&:0F(\1*E^jjA
|
|
||||||
7e2@'iPSK/(k[q\mUXY8SbhO(%3%3]K5Qa5%$MVPa[:-?,!42.+PCYnSA:Q4
|
|
||||||
Q!:1>-Q+T7Z&Y'(Wm7(m0<KapQU1>%YG`jR6,GR$:I??<HYs!!#V/aE;K;V4
|
|
||||||
K8W,&bO=[`EO6]MGgsifGU1j*)9@hh5X]<D*bQ>sZUu.NV_27Lfmkr#1ZZZi
|
|
||||||
WH,Q>k(iF\?bUnU7T9Yr%UfrV)4N0oY,NC;8SN5b,GBB\[ap*9pfVdW-?T1M
|
|
||||||
:Ob^G;>@hOpKeqi"Eu1)4su&^&uZ6g)>!\U39?YGTnIed1V)@r#1F'$JFL?H
|
|
||||||
>)J[NJ_/s`g_+/;bs/hGD;u1<36$WK%(hhE685!e!+YSujL&dTg9`(`Rk8>5
|
|
||||||
Id-"[06UhOG?g7/"aL]=hu2te8A_ZDb75LOJCeGIDs"e`]carRNYE*H1br=u
|
|
||||||
gO]p,70W\a\%bAa:^EqJ`26t0JtaGO-o#:l1XMGdDB<>`ok`#0%4h`h6km0H
|
|
||||||
@W-$8'4mg^h0!1DGe%'CnVDA<rpOHae:0&16*V*!+I39508N2OBGq6Dp-<?A
|
|
||||||
KSK*7/8UN0jb%#79"<rFT21l]B$H4D6)-dHji`ulo5N@sT63](DqB6p:5r?;
|
|
||||||
>b&Z%EPr=#qsM)l08A4%pNq=sao4a-o*Vd"e<TS;e>*?r8"'e/OWt)a:8>=o
|
|
||||||
_UO1Ora01Be&Hft$FD="U9p:eP8:K'1,ku@\N^u`-Z`YtHc,t'#giZg8-@8F
|
|
||||||
#iUmj7kfN5X_g2njhShXRr.,-k.7.U$9#bjXS4sZo07a#b?oM+K*Mo"]\lVS
|
|
||||||
B.M-W\Tm1=-'#9$k.IZs-k)C:lX0Z1O.Dn?Cp#:hj+dA^6K"A'7m-QI)=f;c
|
|
||||||
1=U7J*P%r+C]G)GL\tO%[qs_A06-[MeJKW-K<dVMCSu!VQ7a5s.G>J>[IYM&
|
|
||||||
V5[AB)mr)/LhM;P=Y]`!`\0uYbAnb'@[<k8XWIo0i/I2M]Pa7EC"&rq'HH7=
|
|
||||||
[;'F2/EbtZ-HolNe9E4)jrR^P'\3*Xhu=`*`Oi(LoL]7sH7I$[:eN2A9rR-l
|
|
||||||
dEb]JkTZ.I>/aBLJ,-%Z@lDhDIIS6HVfpR7>!>ibS7@or+$PWaeC6TK`EYS]
|
|
||||||
CLe:+^0!UdA7!.d:g=Mnn,R<fPa"T35Pse'L%+&U8Ukge`VG7*%gu*cYilVk
|
|
||||||
d>iG+E<g@i-0P9k`im,Qapf*Am"%JKF6[SYMa!TOes6:7n,MOqk[(9@Ro<B@
|
|
||||||
mf_Yb*5NrZa-^SObuXs)=q_%oAM)g0?u%FUh@R+o"1G)Xm+J_=$m\_iA><S@
|
|
||||||
b`Q_*PcCmR_*T1oN'^_;NF>+>63=TV#@W@O2HQX!#+:B8(,=LP5$9D4c/!gs
|
|
||||||
BSGNB_g1IoV+YM..S;/(C*EAb5'PsFXb3Ddb#/mjQ4J"Uo/lERZZ+m?";a!P
|
|
||||||
EPiT6V5C,)pkSDoW"CLICdR)WDq#q1c&38h?5sSlre5G45(dN[S<>8'@C5rp
|
|
||||||
Atl;Lm+@GlSrT815(,K'g'uI^ql5R?*-7;DAoo_)#T/hWV1r,dX]),mBm>Ap
|
|
||||||
<7-]kQ#Xc0)i`ElZi)L6\.q>Nf3NSLRAlTTfN,j;DGJ<5I=h"@HB)5H:2^8*
|
|
||||||
Pc;H4VrVebqA)!oU7$.F8!WBbFc3?L*/'MiBk\GAk^isGW-MU.`Hn:q[9/@t
|
|
||||||
.L8VCiNVQ]G:JeGe1,i&V(HDafnCW4ki;:2d-'d`J-Bq4EC^IKTTsdOacFG4
|
|
||||||
fRS#hcY]=+Os\poaZURc@JN&M,&[Vl'L97.J)gi;Jifnm%$S8s1g!)D$&fHd
|
|
||||||
S%pOVkiAm+Te#U<htu=1mGE0AN2Ih+/$71iP`>7Wpprp%';O8K43:_O<9r/>
|
|
||||||
s8)&bjXQXgd[1C;m2Th435p#Weq!0Lj^H]><H*`%p8t&s!Ji7u-D_^Dmnm.)
|
|
||||||
K7V[rj$hi8%kW9>-a$<6Srj0$AP.&'$ZKVgA::\R8/F/-,A:uDj?NY=,7uXR
|
|
||||||
%hFp`csJt[TFP*tJ.H\9UCh5<,4u3aEYlDa"H<r$),-L;A2Dmf-N],tJ5kPK
|
|
||||||
]&!*E5=F9`$Y=`(b"E!sU/q(Zm);[+^i..9)]dmTrbag4$X/upCf1[1bA'>j
|
|
||||||
4F-Me@p0YJ''A\-V=I#&Rh)?'asIrLC%uLbo5$UTfVF(Gb",o4W,m>i*,jn:
|
|
||||||
Cm*DJ'C4D,<lcYAE_#lWeD3V7E07Y+%?9[E(k1L]E^:,ZUFAEbJhh,)Z52"F
|
|
||||||
>#BEq[$2[&i9_opc"BBmTG=1LhbP`LneG+f(H"ql-*q[U:-:AeRbta)]$@/,
|
|
||||||
?,Ifm^a:+XZI6N`=r)DY.C9D>1<_?m/M>LS3!*;j"MU8HkCu<L"La0=q=C6Y
|
|
||||||
bTlTaeu`/#dE1.9$A^Z\]dPeuC0&l]Q+L"XjWuah;<.&S3DW_[*"5gXX@jjn
|
|
||||||
T,Nb\naL?&(+b9U_P>:^ef.W*j4*1$>=$q1$oni%QCPruZ!rEQaIkP,25QcV
|
|
||||||
3n_A$[bZ\^P8l\%UaSLa:HF&6G`maf(#j3mZ"M@PiCM4'TqG3V!$iF/H$tJ^
|
|
||||||
A5puQrgng@10_D?[VXW9lF>gCj2X)rg^&`ZNfQeQr+*?OB_7%BkI1#Z)(A\k
|
|
||||||
fYjOB8/OPr%?S,:!ji$Z6?tEV=NodlG]CtBUF)rX'G2'H@!M%jYsX9V+ff:*
|
|
||||||
R_T3*G`',TF]Y"u".?#[R6jme7\OHqY$?fQjd3=mUiQdqR%H9![*g:dM5@!H
|
|
||||||
n%%U&C>drqmmU71?7Lc^Z=V57`O/qe:+dE6s35J2K/u=EXQR[qhTBt+!T%!I
|
|
||||||
TM(S%l)S?Kn9aot?*n8Ws&JR6D)G/.2+El6Q]oP;LjX1beTUlC`fY/mZq8G<
|
|
||||||
C-7,C=1<6gY3A(H-@F$ub:,!AL,%F,@+MNWc47"MY)=#F7WWtOh01=N"pLkP
|
|
||||||
FQsc2k7F;Lndc_fV6W__8FnTQC'K4ZLb#<uIOGrF;ds2[AVNp@`<I@9?jWnL
|
|
||||||
Pf]IX2f8i:VM/1>BN[OcPVNj_%1S7/_!fgT%6)8H8X0j3a8c(`s.m*^Md/>:
|
|
||||||
S'1Pi[GoT@numN8.rIOro&NJO4cC=X"/mFmYKT*u<\m6V+-#!=)1fU^8RpdC
|
|
||||||
5ROM*9u9tX4"6.-+](TU0j$e>:K;0P?;?Sa-jJoMTYX5'5_D)I\&DE>,L+8'
|
|
||||||
rq<h96&5@*]8mk_!fCcp*q7*YU"iJc!]5APc2[RZ5A&$#KteiglCjO-,(<dj
|
|
||||||
h,.tfnh:"EB6aZnfuqqZ5q=[A`]2onWOGVqgUW=[pIBu]"um@)n%\o"^Io/9
|
|
||||||
::DGS(;N"")HXt"<pK4Yjah/7%+%$0H`'Z3^3Jau'?UdU8n#(fONEt`SU;UE
|
|
||||||
5V(IkF32PGe=14*k.4`RbSIf(Caji!ee+,sK9A'i4E,$A>qoj/%%<>,WioGm
|
|
||||||
#@X9Ar.V"9ltI%?)<*rM-#tn4>sa?Z6`t)L\RYg]W!F6<YAA<]NQ4Cr&>*um
|
|
||||||
,ndbj]?WFOjBGlC]C3HQ=+M0=Vn^S^HrtkT2HiWPj3qG0GZZBHUeu(I]f>uF
|
|
||||||
Dnd;WIQWn;=7Hd],R]ttfWfDS6<iF3U=Td@R[Ug"bo1+$iCASGSGX45d\'Qq
|
|
||||||
"tgOMC-b1dKbcuLD2C[pEYR+QT6qhU&T43$EDZpYcS#(];C,2S,.<D(.D
|
|
||||||
.8kh4S`\i;L@qj1c<7R8>bQp]*85ptL/!gKj<'MZeL7t3QACABP)R6q'3^1<
|
|
||||||
D*Bf)e>Z@i<)X$>2*uBF#<VL_MkV?3M\e%$HL@GD[im^sDnm]DX9Eoap?^I;
|
|
||||||
Qf51!]p^t.heaEu$bM2r_2WOEWL^4)=%kMU&J#,D;qk7]Fb8M2"ei&4*K!7C
|
|
||||||
Qs1t+VVD@<r/j(f,-!S:%%a2E/\,<=JZYQ&`utsQ;>cR-TnI(o#XtNX:rEZD
|
|
||||||
6#'(OrfXI)1CI:oW1i<C7UH2CNAt_>eu]]dn?6-0G/8thQ`fc?%Yl"b9od,Q
|
|
||||||
@?Q"`76$!&N97s56E\\cBQujRUqN<$Zn=ZK6(k$.TD@nBI'(3hQ6e<pAI/BZ
|
|
||||||
,Lm'hW+t)lCmMA51lXsV,#kL>b,GQM19P%6bj,gKBkpQe"Ojn#79*6M@WP>`
|
|
||||||
Rl@'BM7bYr_$=:ZqXj#iSQk9_eXj`A#%-&4$EY1@Lh_cChq^+!"N0NZnH9U$
|
|
||||||
`>;n6-tTl[cGo&%+I:F#68\*js+9gUKJQ5]+qaD(q/@";!KBJ4i.6d&PV6=R
|
|
||||||
'ELHV\[OG!dSXdtQjQ9I_%_*6NDJCZ$q*:(n/u04S2kZ#8@-`$3"3/%mOMkA
|
|
||||||
l"A;3W>46=$"r_$-efQhJYSfa"_cM+o2tY*UIUC)!epZt]u"VBfR96Di7fhf
|
|
||||||
[Nm.F+eRsT>LuMhI^t9.Vi&?X)-7\(Q+B\,#b:GfqP]3>3?Q=24]=6WOkR73
|
|
||||||
^JljMPq>)^NZP`/NU]&C9?]He#SESm7'e=G*2+t921<:>kdrXYLOuhCo$sOJ
|
|
||||||
2hs1D]O5!\Mt9r\misd#J[IDr"52.HP>nhG8XUM!kAUm2LYgs%1kET_$H\,G
|
|
||||||
Z$Z65bGkrM#[N3iM`u\m[&bTN;c2>9lP+t9L)8SOXQ=6-O4>2@X]o;omu&-8
|
|
||||||
7.EoL#?M2]GnHXH=[J@(7rr=8`P:qOHbR,6gpepWNlu>3VsD'hKn$c%6f"[3
|
|
||||||
K#@rKEC5XPEIn6^jD[sOhR%mC[e9,q=Bs/YhP6(S7jh]!0JV9+K.C-@s#MW.
|
|
||||||
;j1T)2bA#)7sC^/X4ZpV>K)>hr93+FQ$+lU"r*L%Rd?ctPi;X/>hs,)QI\u\
|
|
||||||
*2(^b$lGZ)'sqjMpha;N^[+$[Z*Z&MTNZ#0g/Kq>H7?MZG%Z"GUO1B9PJ46U
|
|
||||||
7mee+Z&'-TcCgl76NAmU_i<P-'CjPJauk=6d+k"2/a_cMap@\6gMc8sPD;C-
|
|
||||||
Q0\9/iOA[:B4W]#q$_c(caJ0)Rd+YQ%5WtgA:7.B*-ChGiFc=d'S3pn)Ru4k
|
|
||||||
>Xs?pM)MrBU;d8ARB-T(C,mVNQIa:#bsNuOk08eg`bE2NXcGd+kUO+m$`+><
|
|
||||||
%5EH5*$J:8$5HaQCl)5`nYi>c;#kS:-X<u$X?T\U<GhVi_1MtOC!reRTgIB\
|
|
||||||
m<okq,Vi1,b1PH6#Rr;iDHtKIj5]aoc`d;r1EQ3'+DO.iU]j?Nj2otQpSa'r
|
|
||||||
Y>V2hWD]Ps%NK08f)S49]mB@W%UA%V7RTKcY10CIUN#2/\9t>XXnq.&(_:RY
|
|
||||||
@K$&q]mKM)gdW6h4YR$:4m8cg+9DR&FW]Wd:B0KAs(V96GOhF'ft"&\%@ZU&
|
|
||||||
Xo!5F\4o7kUi!s[!6p4Fku+m,hnD@,!c?Yu=h&FE"rd]TkcL.e;_p7h>XpSP
|
|
||||||
*BJ;hEo/*3NkE2"X9!2`*'Sapo&Rp;Rl@g1!o3U=3bd0HR4Y+OnI!D$=Ms<o
|
|
||||||
Iu[lKe(\`WGD0!-$5Jd]B?iu+)C6XSfW`Ht;%L=LM%_8r]kE(s_;s[B='h:M
|
|
||||||
A]hiA#:!O&>6q0iiPUF<-o:'FJ'\U)I!g=mXBAc8C,Kqd]YLhn%e)q'04(09
|
|
||||||
R&F<QI6\X8/6N%D0&*oSZT*$g%">M:l_M$^@?f&Qe>SKkGAd%Npt2JEd>I#G
|
|
||||||
AF1>7;.jB+Rr[5WA>;VR;D18LZlq[8cpF+<ZV?aPYbW@2S7c<0GMdi#k'@/f
|
|
||||||
.(V_A@[4=lN`NQ9dt;6lC3goP[2m&a=gNpX!g+&FXBC/nP%3iJ:NH@iKXF@5
|
|
||||||
,(uS@"qB!'q259IU)gW0Ue-bVHgN[fWFlM1Yue,8`)b-O2+(,NcC>bo9cCb@
|
|
||||||
if!NtgY39'X_4K<=d1ISh7@a3>e+V#XGQ1+FQcPsb8]AOi9].'++3R/(-&^e
|
|
||||||
:3A0NKtGdGH1R1F9;6QcUk5a@5oC!R0%&s'3jCQ&+B3`9HsBo]-E]u]i78.%
|
|
||||||
'/S^p]@Rt<2YF\+n(s<[="[UZ+CD6X^HNJ=.A2Kq2l6:cd#gClJeMr6(5h"B
|
|
||||||
Lk:RRN*tDKD6J<5RS35DCh45J;FK*tb*D;M^V=\"DCj9%^juTL8?"_R)I_rq
|
|
||||||
gQHPfbEa_ZW6hTK^8fSAOY1[)Rt6hT<tYRJ"VQI/V.6uPh22pt<i8]`;56V6
|
|
||||||
:(mZZY$J*%H:jQ?I^'Yrd2FS*X*F0oV+R$$im3`>Dh+;[/U;*N8.5FL_HGTj
|
|
||||||
,@A04/R#NK5(.a=1H3OI2c%&dBK9Wi+1:rSX&c?CkK[A8>.(%kLP@=R=>oSE
|
|
||||||
%/c2!,=ZeBg/ZmZ=Qj;K#sPe8h)^&br2AV/$Q!,`(RS;,F;QoP(c>erG*p$W
|
|
||||||
+.n#Ykq#uC1i:rgb*4=mmnXt^G!ri4r5i0$!..+9D2U5c!F)[VWqA7lJ,Ji2
|
|
||||||
7>l36G+&\uSt8]j6(C]:_UdTH[;4C>Sio/9D!p)hQX)-d5SV"(X*sm=C&FPl
|
|
||||||
le-.f>4n,<=K%)l/!_eng3/p@<69&-*'8>F9:%:!16o9cm^(5'X.:caQS4D2
|
|
||||||
V](mT?QLVi(r<4PWt5^,$u>0L)m][k'7ms_dA)?I#Gt:[PERS!ZHSn(En%\C
|
|
||||||
b"02Bm^jDsD@@4VN'O][?4K97PI@ta=7?E8jiU"7TN.`h_%;cmP&0;.gB5<[
|
|
||||||
"Y`gX$PogI[Hc,46[.qO($Y8npncnY"HbRQeD7K&4L,unEF\"fiXhKq`5H'1
|
|
||||||
OT@n(G=U14@WGs,*M7W>)>q0;N>bN]3&,e:oDha=E8u!4lrcY#/O6>pB=:a+
|
|
||||||
3/%T>[r1$W\T?g+X^&F-qUXG[8rOTAJlkSk8\<EX#ltconVWBgc]]^eFpdM?
|
|
||||||
C!!a_d/b1J["&+)9G0Beo66-og9t=%M@((J'J<Y#GOOCX2fB2YoM]G&fH*2%
|
|
||||||
BLd(\.?;_9J7#^!e\^Fsp&mUlTR).C([gq2<NB0JoQ'YAp47%DdLf`>:$'N_
|
|
||||||
,7qA^'cNhdLCYIrs/<7qCpK09(.hA;,taEceZ7<up$:H4h#]tdN#+9^beQHJ
|
|
||||||
jl6ab9HbcDF69Q*YMS?^P@Jll7\;#Y,MFHgZK9fJ;`bDrB[MF8MA.L;^3o`B
|
|
||||||
K*L_qYp3/c%e*-/*@UpIO$=*l1'C0F*,YR%]<jm_2#C\Sm+FZoiaSkOk004q
|
|
||||||
EAV(Lh(B.W)6NJlc!.)@Saka\B?Gt8!H*>DfY@F^lNi#aV3D(W/q3<u,DA\L
|
|
||||||
cCGn9[=%f@)#%6>,;j5cW92?o_]Ua5Z;X`"1(m<N2FS4ONb6S,)%8Q`DO=h-
|
|
||||||
VP`A<,66GedeY7!==DEXGiOcB2Jh"W"AhF2H@#>Rh5,qO2X-aSKNXgYd#g6-
|
|
||||||
p?hSg+Bl$beLI@]4)!AVBnb1`'^%oC(4G8+8,DoE[r^`X8=0'O!O3n>>U_iU
|
|
||||||
)=+k`S=k]/kKf25>.8BT0^'oSnC_HH`Q-$B0H"\"APNdIr&p;3Y[PF#&;XIN
|
|
||||||
AM:c3djA1,Z')C(mZt$nW=g79pu$d4&bt"pi5(%cooY-p6BJf'^aZD4a>"h]
|
|
||||||
+UA2ZoB+:BJ84,;[0,\*MWN$JYB&aI,=ddL[r5WLb8]AOi7tka0>IFj'"g+@
|
|
||||||
9bp>Z$\,?\X]egT4MV+.('N*Z%+(eQZY#LT[tM<3R=ch\RUjO'V.%8re##hI
|
|
||||||
k$pkDIL9rV.pXX*,YhKQXhiFsb*4>WY[FqXh3*qa@Q_VBTH8FQm&<.s73_$D
|
|
||||||
[S>q<O*0a;acReQ'cSQqW.ut#]Of8%3WIO4E`\Ln'=g(O>.!K\%mAF?:EQ<c
|
|
||||||
8/FCOE.agI-Vn3`WB(nA=f%E-5V"=jI/3>YoB,Go[Hi\b.fr-R,?VU'0qO4-
|
|
||||||
,pT7*TN)9=$0J3j[;&'`%,&=@GGNE/X'j\jD43mA7:\b1TT[M7(JBu@JM)97
|
|
||||||
Xg_+p5t3%fOH>RnjHE@`$mlqQO`aS8-KN:4#LZ><4?;&I`@uf@r2*2XWno;G
|
|
||||||
f/s1"Em0k'hkQ5r3cO#m4aZlj]"A&mEQIJ3gUHVb:HT42*tN?B"99&IbYs,u
|
|
||||||
%3SW^G4"G;SSNk<!e!.Jgpj8RHHQ.K<L;>`$e;b,j)!DSTrc7o*NQT7K1_&J
|
|
||||||
S3(qt9:%7f3j"IWKiZc[,2#4\W`5rqW)00h??p,Xo&[^S"%3+frRKLBgLN!)
|
|
||||||
4oY6:EH-!FD8qMP_)/)-!La\>e>Q4h,EuuAA>)V\.TNcbakj^XJ,AWB_31_5
|
|
||||||
KaS^.FmDMVM*'^ZC/!eg7(@VJE\/,M'I-.@9#<]I:ddl!P[LAg5_0!%.THi,
|
|
||||||
6Z@&;kYZ_8ji2][^E?M/Z=Sq//(UIcO4C:4#Dsqp+b)[!+e_Ab)FR0ildp(-
|
|
||||||
YT_5*kb56dIE\9G45Wf%1eARA+:nMl2f=kgOZbJ+DVd.m_Q9(S=0>oFITs:!
|
|
||||||
NHBFJr3EQK:jr67Khqp(cprcK]TEHHMEp,!qsT%&\`[SNSfIq8!qhkHQq704
|
|
||||||
nR+4DFZgH@[Q]3?r3^=l*]7\38kM^OM]CYei;EEBGke[]eWPUf%6PaP10@(<
|
|
||||||
`JPCOjN,`F9:)JJQNe<6_oRrbW@3Zch7us4$;OH:>b='"C+N/"NY0?!*BJ9J
|
|
||||||
imar+LG6jsPkfd,hr;V5UM8V7qe^IM#6*IJYP*d2Pi?aS7p+oD,IRnFFm@H_
|
|
||||||
>8F<R*$\Rj@Kl`PKR1Lf#hA-:'J3Lq;Pctf"q9.g@Da5n`)DGo&K1N.8m`SK
|
|
||||||
iE@=F*Q>W/*'AK8aH7]op?ZMFY+9,l3Q?D2s1gCM>U4VF:nU)JmbLsH=qs_`
|
|
||||||
M@nd+i6aiha$YrdB>G;[JnUZ)5AVcX2bU6G!mcVZNjWihJCoZ)_+"ab%SG&S
|
|
||||||
$JSh-5$e]$oG;^)N6D4W*P88T_&5qhi!!*qb48[f_SB6lMA=SsW*g54fKnE/
|
|
||||||
I4J9qJ9+Vl?G3pmlZ0r<K2'Kc.(H\gKsAjUP3iT&KVff<K.\EinqC=@\t[j:
|
|
||||||
;HO:[#TGrr_KE55!u3BAApm%\451<Iqj1M^hiE17EW+_f;V'ZsHGE<G=UcH:
|
|
||||||
gbsNj&Db,46##B)iUt_0TiS:!(bC]O%#SK"?l;F+7'l;2;+;0E.5I<57F&;G
|
|
||||||
G/X[#RA_'9>_;a0SLo1$DYWQ'4sT'I=nOMp-&u8NWFlC@hq2_%W\/Lk7U3o!
|
|
||||||
7:j`4PtF\?8-gs`0`n'X-r[Zsjm!n%G]9&s:-g;=JqBInfAI"4Ngkcr_+QE"
|
|
||||||
>QM8F9dZ1o(akq)nT!#(RZWE$H"EE_l$@78ML"bQD3l+^>CGZe+6D4e4l(#5
|
|
||||||
\(DrI)9YiKEH-!rGM]uX_3/RgK%6o'=8#IuC=R(LWejP9*W\&mn-J$P\(=<=
|
|
||||||
6-N5NE4?Q0Q<fe!G%0%@[;1qQ4#,L>b(4?:>_g[s9OYrj-i"a1mUc07AZ+<J
|
|
||||||
>!9q]DB+82o)fusgpqKMmbBfO.4Np=4[q,RqXcOSb2DI#W@Ar1HgEJSAHo3$
|
|
||||||
#=VahQ)3'L]7<2"&<q)lK-`Ft>+'M0$t@f1JhR(#*u1BWGn46^AOaVPEnP)4
|
|
||||||
\HB]#S`4\7kU+k"f3F<o;T9.eD7l1^NOooY?G'[^Jj"GP#Y%%u%R0g[_Zq92
|
|
||||||
Y\FHJ#p.4A;^>ApK:Rfs7Oqu7jX]*l.pNdR`3>;SfejF9Y*eSa+06FLqjHR:
|
|
||||||
)5cnZa\@Nb7b#&dTX*Ip*IAeAVF>/q,9PTuVc"$#oB/VH;8Y[h,'M>@CY+=_
|
|
||||||
H:VA7b`dE==X]YkOfIdL:732;ek0mAjdu@*-?J<1LU'E6V_S0KR)s<b.q=A#
|
|
||||||
VmX$VnZJd\KcSs"^2Bt.noF/*HsrRK7Q2oulhZMg"P<1=*jTA#7ZEBF1Zl"7
|
|
||||||
0nV$'o\nT9EsMu'.T`cXq=<VhIItoZPq!ZX'N1@Z-T7BlZIm<YPWV*QjH<.a
|
|
||||||
bJP9a_?hMHfiQE<=<>6ujE%0$gaL:26ZDFTBP%3d$g&5TCo*95[BmT`q<%-C
|
|
||||||
7JG\h5NgLm9fmL_<2?!E^s0Zg6\hm9\0ZgSO*Z=QYXuPN8$U1;Q0aa)VNC90
|
|
||||||
H@VNt,;(O='#dP"9.%1dIQ,+0)F$$ajk;A[9lVaX3(GODYrngN6bYh3jgb1D
|
|
||||||
[m*V8ekbJc8hB"`.&`XrF1,l9DS^o._<AoV_=H^299^jpnA>d[4=N,gQ,KZe
|
|
||||||
%[9VQOg8@%(`-F=(![(DO>jAhkNVdHQhkab^4'D?a(p.gCjhXTc(+SDQGX6Z
|
|
||||||
Hi&Q+\i4`[(XG),7CWYFZ\HJ,ISd=\aT;VQ]_^ooX&c?[F?9`;8_CCae&sDZ
|
|
||||||
$mSB2/mYhtd:+/SH[ocU0?&Pb8qg)&KSMmER"'<V`k`lC\EBOOV'pNk^iqm1
|
|
||||||
SK$q9);OC9i%(CiW:8b1YkKVFU^5+Rd\KINju'CG>`lR<kJ$H&$^dM(f7N9h
|
|
||||||
LWf\fZ58ALPRIkR^:gA9,$jGu!EL6d21GJu@$AWu_VrCf'+tq(p[8":HggXt
|
|
||||||
d'mrN4oqeo<egab.:?)UiR"8cX&lLEaoB?_kMHEP@AlBan)#4k.FrIp_hAI7
|
|
||||||
FHW5em^j+i3hU5p8e,MLb6V99EtQo7bTaB^hX2&uOq?,1J,fTO":,P]5_&h8
|
|
||||||
!X&c?+M]Rhrs*>,Oe2~>
|
|
||||||
EI
|
|
Before Width: | Height: | Size: 16 KiB |
|
@ -1,3 +0,0 @@
|
||||||
del reference.pdf
|
|
||||||
python ..\tools\yaml2pdf.py reference.yml
|
|
||||||
start reference.pdf
|
|
|
@ -1,29 +0,0 @@
|
||||||
#!/bin/env python
|
|
||||||
#Copyright ReportLab Europe Ltd. 2000-2004
|
|
||||||
#see license.txt for license details
|
|
||||||
#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/docs/reference/genreference.py
|
|
||||||
__version__=''' $Id: genreference.py 2385 2004-06-17 15:26:05Z rgbecker $ '''
|
|
||||||
__doc__ = """
|
|
||||||
This module contains the script for building the reference.
|
|
||||||
"""
|
|
||||||
def run(verbose=None, outDir=None):
|
|
||||||
import os, sys, shutil
|
|
||||||
from reportlab.tools.docco import yaml2pdf
|
|
||||||
from reportlab.lib.utils import _RL_DIR
|
|
||||||
if verbose is None: verbose=('-s' not in sys.argv)
|
|
||||||
yaml2pdf.run('reference.yml','reference.pdf')
|
|
||||||
if verbose: print 'Saved reference.pdf'
|
|
||||||
docdir = os.path.join(_RL_DIR,'docs')
|
|
||||||
if outDir: docDir = outDir
|
|
||||||
destfn = docdir + os.sep + 'reference.pdf'
|
|
||||||
shutil.copyfile('reference.pdf', destfn)
|
|
||||||
if verbose: print 'copied to %s' % destfn
|
|
||||||
|
|
||||||
def makeSuite():
|
|
||||||
"standard test harness support - run self as separate process"
|
|
||||||
from reportlab.test.utils import ScriptThatMakesFileTest
|
|
||||||
return ScriptThatMakesFileTest('../docs/reference', 'genreference.py', 'reference.pdf')
|
|
||||||
|
|
||||||
|
|
||||||
if __name__=='__main__':
|
|
||||||
run()
|
|
|
@ -1,217 +0,0 @@
|
||||||
.t ReportLab API Reference
|
|
||||||
|
|
||||||
.nextPageTemplate Normal
|
|
||||||
|
|
||||||
.h1 Introduction
|
|
||||||
|
|
||||||
This is the API reference for the ReportLab library. All public
|
|
||||||
classes, functions and methods are documented here.
|
|
||||||
|
|
||||||
Most of the reference text is built automatically from the
|
|
||||||
documentation strings in each class, method and function.
|
|
||||||
That's why it uses preformatted text and doesn't look very
|
|
||||||
pretty.
|
|
||||||
|
|
||||||
Please note the following points:
|
|
||||||
.bu <seqdefault id='list'/><bullet>(<seq/>)</bullet>Items with one leading underscore are considered private
|
|
||||||
to the modules they are defined in; they are not documented
|
|
||||||
here and we make no commitment to their maintenance.
|
|
||||||
.bu <bullet>(<seq/>)</bullet>Items ending in a digit (usually zero) are experimental;
|
|
||||||
they are released to allow widespread testing, but are
|
|
||||||
guaranteed to be broken in future (if only by dropping the
|
|
||||||
zero). By all means play with these and give feedback, but
|
|
||||||
do not use them in production scripts.
|
|
||||||
|
|
||||||
.h2 Package Architecture
|
|
||||||
|
|
||||||
The reportlab package is broken into a number of subpackages.
|
|
||||||
These are as follows:
|
|
||||||
|
|
||||||
.df <font name="Courier"><b>reportlab.pdfgen</b></font>
|
|
||||||
- this is the programming
|
|
||||||
interface to the PDF file format. The Canvas (and its co-workers,
|
|
||||||
TextObject and PathObject) provide everything you need to
|
|
||||||
create PDF output working at a low level - individual shapes
|
|
||||||
and lines of text. Internally, it constructs blocks of
|
|
||||||
<i>page marking operators</i> which match your drawing commands,
|
|
||||||
and hand them over to the <font name="Courier">pdfbase</font>
|
|
||||||
package for drawing.
|
|
||||||
|
|
||||||
.df <font name="Courier"><b>reportlab.pdfbase</b></font>
|
|
||||||
- this is not part of the
|
|
||||||
public interface. It contains code to handle the 'outer
|
|
||||||
structure' of PDF files, and utilities to handle text metrics
|
|
||||||
and compressed streams.
|
|
||||||
|
|
||||||
.df <font name="Courier"><b>reportlab.platypus</b></font>
|
|
||||||
- PLATYPUS stands for
|
|
||||||
"Page Layout and Typography Using Scripts". It provides a
|
|
||||||
higher level of abstraction dealing with paragraphs, frames
|
|
||||||
on the page, and document templates. This is used for multi-
|
|
||||||
page documents such as this reference.
|
|
||||||
|
|
||||||
.df <font name="Courier"><b>reportlab.lib</b></font>
|
|
||||||
- this contains code of
|
|
||||||
interest to application developers which cuts across
|
|
||||||
both of our libraries, such as standard colors, units, and
|
|
||||||
page sizes. It will also contain more drawable and flowable
|
|
||||||
objects in future.
|
|
||||||
|
|
||||||
There is also a demos directory containing various demonstrations,
|
|
||||||
and a docs directory. These can be accessed with package
|
|
||||||
notation but should not be thought of as packages.
|
|
||||||
|
|
||||||
Each package is documented in turn.
|
|
||||||
|
|
||||||
.pageBreak
|
|
||||||
.h1 <i>reportlab.pdfgen</i> subpackage
|
|
||||||
|
|
||||||
This package contains three modules, canvas.py, textobject.py
|
|
||||||
and pathobject.py, which define three classes of corresponding
|
|
||||||
names. The only class users should construct directly is
|
|
||||||
the Canvas, defined in reportlab.pdfgen.canvas; it provides
|
|
||||||
methods to obtain PathObjects and TextObjects.
|
|
||||||
|
|
||||||
|
|
||||||
.getClassDoc reportlab.pdfgen.canvas Canvas
|
|
||||||
.pageBreak
|
|
||||||
The method Canvas.beginPath allows users to construct
|
|
||||||
a PDFPathObject, which is defined in reportlab/pdfgen/pathobject.py.
|
|
||||||
|
|
||||||
.getClassDoc reportlab.pdfgen.pathobject PDFPathObject
|
|
||||||
.pageBreak
|
|
||||||
The method Canvas.beginText allows users to construct
|
|
||||||
a PDFTextObject, which is defined in reportlab/pdfgen/textobject.py.
|
|
||||||
|
|
||||||
.getClassDoc reportlab.pdfgen.textobject PDFTextObject
|
|
||||||
|
|
||||||
.pageBreak
|
|
||||||
.h1 <i>reportlab.platypus</i> subpackage
|
|
||||||
|
|
||||||
The platypus package defines our high-level page layout API.
|
|
||||||
The division into modules is far from final and has been
|
|
||||||
based more on balancing the module lengths than on any
|
|
||||||
particular programming interface. The __init__ module
|
|
||||||
imports the key classes into the top level of the package.
|
|
||||||
|
|
||||||
.h2 Overall Structure
|
|
||||||
|
|
||||||
Abstractly Platypus currently can be thought of has having four
|
|
||||||
levels: documents, pages, frames and flowables (things which can fit into frames in some way).
|
|
||||||
In practice there is a fifth level, the canvas, so that if you want
|
|
||||||
you can do anything that pdfgen's canvas allows.
|
|
||||||
|
|
||||||
.h2 Document Templates
|
|
||||||
.h3 BaseDocTemplate
|
|
||||||
|
|
||||||
The basic document template class; it provides for initialisation and
|
|
||||||
rendering of documents. A whole bunch of methods
|
|
||||||
<b><font name=courier>handle_XXX</font></b> handle document
|
|
||||||
rendering events. These event routines all contain some
|
|
||||||
significant semantics so while these may be overridden that
|
|
||||||
may require some detailed knowledge. Some other methods are
|
|
||||||
completely virtual and are designed to be overridden.
|
|
||||||
.h3 BaseDocTemplate
|
|
||||||
.getClassDoc reportlab.platypus.doctemplate BaseDocTemplate
|
|
||||||
|
|
||||||
.pageBreak
|
|
||||||
A simple document processor can be made using derived class,
|
|
||||||
<b><font name=courier>SimpleDocTemplate</font></b>.
|
|
||||||
|
|
||||||
.pageBreak
|
|
||||||
.h3 SimpleDocTemplate
|
|
||||||
.getClassDoc reportlab.platypus.doctemplate SimpleDocTemplate
|
|
||||||
|
|
||||||
.pageBreak
|
|
||||||
.h2 Flowables
|
|
||||||
.getClassDoc reportlab.platypus.paragraph Paragraph
|
|
||||||
.getClassDoc reportlab.platypus.flowables Flowable
|
|
||||||
.getClassDoc reportlab.platypus.flowables XBox
|
|
||||||
.getClassDoc reportlab.platypus.flowables Preformatted
|
|
||||||
.getClassDoc reportlab.platypus.flowables Image
|
|
||||||
.getClassDoc reportlab.platypus.flowables Spacer
|
|
||||||
.getClassDoc reportlab.platypus.flowables PageBreak
|
|
||||||
.getClassDoc reportlab.platypus.flowables CondPageBreak
|
|
||||||
.getClassDoc reportlab.platypus.flowables KeepTogether
|
|
||||||
.getClassDoc reportlab.platypus.flowables Macro
|
|
||||||
.getClassDoc reportlab.platypus.xpreformatted XPreformatted
|
|
||||||
.getClassDoc reportlab.platypus.xpreformatted PythonPreformatted
|
|
||||||
|
|
||||||
.pageBreak
|
|
||||||
.h1 <i>reportlab.lib</i> subpackage
|
|
||||||
|
|
||||||
This package contains a number of modules which either add utility
|
|
||||||
to pdfgen and platypus, or which are of general use in graphics
|
|
||||||
applications.
|
|
||||||
|
|
||||||
.h2 <i>reportlab.lib.colors</i> module
|
|
||||||
.getModuleDoc reportlab.lib.colors
|
|
||||||
|
|
||||||
.h2 <i>reportlab.lib.corp</i> module
|
|
||||||
.getModuleDoc reportlab.lib.corp
|
|
||||||
|
|
||||||
.h2 <i>reportlab.lib.enums</i> module
|
|
||||||
.getModuleDoc reportlab.lib.enums
|
|
||||||
|
|
||||||
.h2 <i>reportlab.lib.fonts</i> module
|
|
||||||
.getModuleDoc reportlab.lib.fonts
|
|
||||||
|
|
||||||
.h2 <i>reportlab.lib.pagesizes</i> module
|
|
||||||
.getModuleDoc reportlab.lib.pagesizes
|
|
||||||
|
|
||||||
.h2 <i>reportlab.lib.sequencer</i> module
|
|
||||||
.getModuleDoc reportlab.lib.sequencer
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.pageBreak
|
|
||||||
.h1 Appendix A - CVS Revision History
|
|
||||||
.beginPre Code
|
|
||||||
$Log: reference.yml,v $
|
|
||||||
Revision 1.1 2001/10/05 12:33:33 rgbecker
|
|
||||||
Moved from original project docs, history lost
|
|
||||||
|
|
||||||
Revision 1.13 2001/08/30 10:32:38 dinu_gherman
|
|
||||||
Added missing flowables.
|
|
||||||
|
|
||||||
Revision 1.12 2001/07/11 09:21:27 rgbecker
|
|
||||||
Typo fix from Jerome Alet
|
|
||||||
|
|
||||||
Revision 1.11 2000/07/10 23:56:09 andy_robinson
|
|
||||||
Paragraphs chapter pretty much complete. Fancy cover.
|
|
||||||
|
|
||||||
Revision 1.10 2000/07/03 15:39:51 rgbecker
|
|
||||||
Documentation fixes
|
|
||||||
|
|
||||||
Revision 1.9 2000/06/28 14:52:43 rgbecker
|
|
||||||
Documentation changes
|
|
||||||
|
|
||||||
Revision 1.8 2000/06/19 23:52:31 andy_robinson
|
|
||||||
rltemplate now simple, based on UserDocTemplate
|
|
||||||
|
|
||||||
Revision 1.7 2000/06/17 07:46:45 andy_robinson
|
|
||||||
Small text changes
|
|
||||||
|
|
||||||
Revision 1.6 2000/06/14 21:22:52 andy_robinson
|
|
||||||
Added docs for library
|
|
||||||
|
|
||||||
Revision 1.5 2000/06/12 11:26:34 andy_robinson
|
|
||||||
Numbered list added
|
|
||||||
|
|
||||||
Revision 1.4 2000/06/12 11:13:09 andy_robinson
|
|
||||||
Added sequencer tags to paragraph parser
|
|
||||||
|
|
||||||
Revision 1.3 2000/06/09 01:44:24 aaron_watters
|
|
||||||
added automatic generation for pathobject and textobject modules.
|
|
||||||
|
|
||||||
Revision 1.2 2000/06/07 13:39:22 andy_robinson
|
|
||||||
Added some text to the first page of reference, and a build batch file
|
|
||||||
|
|
||||||
Revision 1.1.1.1 2000/06/05 16:39:04 andy_robinson
|
|
||||||
initial import
|
|
||||||
|
|
||||||
.endPre
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,113 +0,0 @@
|
||||||
#Copyright ReportLab Europe Ltd. 2000-2004
|
|
||||||
#see license.txt for license details
|
|
||||||
#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/docs/userguide/app_demos.py
|
|
||||||
from reportlab.tools.docco.rl_doc_utils import *
|
|
||||||
|
|
||||||
Appendix1("ReportLab Demos")
|
|
||||||
disc("""In the subdirectories of $reportlab/demos$ there are a number of working examples showing
|
|
||||||
almost all aspects of reportlab in use.""")
|
|
||||||
|
|
||||||
heading2("""Odyssey""")
|
|
||||||
disc("""
|
|
||||||
The three scripts odyssey.py, dodyssey.py and fodyssey.py all take the file odyssey.txt
|
|
||||||
and produce PDF documents. The included odyssey.txt is short; a longer and more testing version
|
|
||||||
can be found at ftp://ftp.reportlab.com/odyssey.full.zip.
|
|
||||||
""")
|
|
||||||
eg("""
|
|
||||||
Windows
|
|
||||||
cd reportlab\\demos\\odyssey
|
|
||||||
python odyssey.py
|
|
||||||
start odyssey.pdf
|
|
||||||
|
|
||||||
Linux
|
|
||||||
cd reportlab/demos/odyssey
|
|
||||||
python odyssey.py
|
|
||||||
acrord odyssey.pdf
|
|
||||||
""")
|
|
||||||
disc("""Simple formatting is shown by the odyssey.py script. It runs quite fast,
|
|
||||||
but all it does is gather the text and force it onto the canvas pages. It does no paragraph
|
|
||||||
manipulation at all so you get to see the XML < & > tags.
|
|
||||||
""")
|
|
||||||
disc("""The scripts fodyssey.py and dodyssey.py handle paragraph formatting so you get
|
|
||||||
to see colour changes etc. Both scripts
|
|
||||||
use the document template class and the dodyssey.py script shows the ability to do dual column
|
|
||||||
layout and uses multiple page templates.
|
|
||||||
""")
|
|
||||||
|
|
||||||
heading2("""Standard Fonts and Colors""")
|
|
||||||
disc("""In $reportlab/demos/stdfonts$ the script stdfonts.py can be used to illustrate
|
|
||||||
ReportLab's standard fonts. Run the script using""")
|
|
||||||
eg("""
|
|
||||||
cd reportlab\\demos\\stdfonts
|
|
||||||
python stdfonts.py
|
|
||||||
""")
|
|
||||||
disc("""
|
|
||||||
to produce two PDF documents, StandardFonts_MacRoman.pdf &
|
|
||||||
StandardFonts_WinAnsi.pdf which show the two most common built in
|
|
||||||
font encodings.
|
|
||||||
""")
|
|
||||||
disc("""The colortest.py script in $reportlab/demos/colors$ demonstrates the different ways in which
|
|
||||||
reportlab can set up and use colors.""")
|
|
||||||
disc("""Try running the script and viewing the output document, colortest.pdf. This shows
|
|
||||||
different color spaces and a large selection of the colors which are named
|
|
||||||
in the $reportlab.lib.colors$ module.
|
|
||||||
""")
|
|
||||||
heading2("""Py2pdf""")
|
|
||||||
disc("""Dinu Gherman (<gherman@europemail.com>) contributed this useful script
|
|
||||||
which uses reportlab to produce nicely colorized PDF documents from Python
|
|
||||||
scripts including bookmarks for classes, methods and functions.
|
|
||||||
To get a nice version of the main script try""")
|
|
||||||
eg("""
|
|
||||||
cd reportlab/demos/py2pdf
|
|
||||||
python py2pdf.py py2pdf.py
|
|
||||||
acrord py2pdf.pdf
|
|
||||||
""")
|
|
||||||
disc("""i.e. we used py2pdf to produce a nice version of py2pdf.py in
|
|
||||||
the document with the same rootname and a .pdf extension.
|
|
||||||
""")
|
|
||||||
disc("""
|
|
||||||
The py2pdf.py script has many options which are beyond the scope of this
|
|
||||||
simple introduction; consult the comments at the start of the script.
|
|
||||||
""")
|
|
||||||
heading2("Gadflypaper")
|
|
||||||
disc("""
|
|
||||||
The Python script, gfe.py, in $reportlab/demos/gadflypaper$ uses an inline style of
|
|
||||||
document preparation. The script almost entirely produced by Aaron Watters produces a document
|
|
||||||
describing Aaron's $gadfly$ in memory database for Python. To generate the document use
|
|
||||||
""")
|
|
||||||
eg("""
|
|
||||||
cd reportlab\\gadflypaper
|
|
||||||
python gfe.py
|
|
||||||
start gfe.pdf
|
|
||||||
""")
|
|
||||||
disc("""
|
|
||||||
everything in the PDF document was produced by the script which is why this is an inline style
|
|
||||||
of document production. So, to produce a header followed by some text the script uses functions
|
|
||||||
$header$ and $p$ which take some text and append to a global story list.
|
|
||||||
""")
|
|
||||||
eg('''
|
|
||||||
header("Conclusion")
|
|
||||||
|
|
||||||
p("""The revamped query engine design in Gadfly 2 supports
|
|
||||||
..........
|
|
||||||
and integration.""")
|
|
||||||
''')
|
|
||||||
heading2("""Pythonpoint""")
|
|
||||||
disc("""Andy Robinson has refined the pythonpoint.py script (in $reportlab\\demos\\pythonpoint$)
|
|
||||||
until it is a really useful script. It takes an input file containing an XML markup
|
|
||||||
and uses an xmllib style parser to map the tags into PDF slides. When run in its own directory
|
|
||||||
pythonpoint.py takes as a default input the file pythonpoint.xml and produces pythonpoint.pdf
|
|
||||||
which is documentation for Pythonpoint! You can also see it in action with an older paper
|
|
||||||
""")
|
|
||||||
eg("""
|
|
||||||
cd reportlab\\demos\\pythonpoint
|
|
||||||
python pythonpoint.py monterey.xml
|
|
||||||
start monterey.pdf
|
|
||||||
""")
|
|
||||||
disc("""
|
|
||||||
Not only is pythonpoint self documenting, but it also demonstrates reportlab and PDF. It uses
|
|
||||||
many features of reportlab (document templates, tables etc).
|
|
||||||
Exotic features of PDF such as fadeins and bookmarks are also shown to good effect. The use of
|
|
||||||
an XML document can be contrasted with the <i>inline</i> style of the gadflypaper demo; the
|
|
||||||
content is completely separate from the formatting
|
|
||||||
""")
|
|
|
@ -1,693 +0,0 @@
|
||||||
#Copyright ReportLab Europe Ltd. 2000-2004
|
|
||||||
#see license.txt for license details
|
|
||||||
#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/docs/userguide/ch1_intro.py
|
|
||||||
from reportlab.tools.docco.rl_doc_utils import *
|
|
||||||
import reportlab
|
|
||||||
|
|
||||||
title("ReportLab PDF Library")
|
|
||||||
title("User Guide")
|
|
||||||
centred('ReportLab Version ' + reportlab.Version)
|
|
||||||
|
|
||||||
nextTemplate("Normal")
|
|
||||||
|
|
||||||
########################################################################
|
|
||||||
#
|
|
||||||
# Chapter 1
|
|
||||||
#
|
|
||||||
########################################################################
|
|
||||||
|
|
||||||
|
|
||||||
heading1("Introduction")
|
|
||||||
|
|
||||||
|
|
||||||
heading2("About this document")
|
|
||||||
disc("""This document is an introduction to the ReportLab PDF library.
|
|
||||||
Some previous programming experience
|
|
||||||
is presumed and familiarity with the Python Programming language is
|
|
||||||
recommended. If you are new to Python, we tell you in the next section
|
|
||||||
where to go for orientation.
|
|
||||||
""")
|
|
||||||
|
|
||||||
disc("""
|
|
||||||
This manual does not cover 100% of the features, but should explain all
|
|
||||||
the main concepts and help you get started, and point you at other
|
|
||||||
learning resources.
|
|
||||||
After working your way through this, you should be ready to begin
|
|
||||||
writing programs to produce sophisticated reports.
|
|
||||||
""")
|
|
||||||
|
|
||||||
disc("""In this chapter, we will cover the groundwork:""")
|
|
||||||
bullet("What is ReportLab all about, and why should I use it?")
|
|
||||||
bullet("What is Python?")
|
|
||||||
bullet("How do I get everything set up and running?")
|
|
||||||
|
|
||||||
todo("""
|
|
||||||
We need your help to make sure this manual is complete and helpful.
|
|
||||||
Please send any feedback to our user mailing list,
|
|
||||||
which is signposted from <a href="http://www.reportlab.org/">www.reportlab.org</a>.
|
|
||||||
""")
|
|
||||||
|
|
||||||
heading2("What is the ReportLab PDF Library?")
|
|
||||||
disc("""This is a software library that lets you directly
|
|
||||||
create documents in Adobe's Portable Document Format (PDF) using
|
|
||||||
the Python programming language. It also creates charts and data graphics
|
|
||||||
in various bitmap and vector formats as well as PDF.""")
|
|
||||||
|
|
||||||
disc("""PDF is the global standard for electronic documents. It
|
|
||||||
supports high-quality printing yet is totally portable across
|
|
||||||
platforms, thanks to the freely available Acrobat Reader. Any
|
|
||||||
application which previously generated hard copy reports or driving a printer
|
|
||||||
can benefit from making PDF documents instead; these can be archived,
|
|
||||||
emailed, placed on the web, or printed out the old-fashioned way.
|
|
||||||
However, the PDF file format is a complex
|
|
||||||
indexed binary format which is impossible to type directly.
|
|
||||||
The PDF format specification is more than 600 pages long and
|
|
||||||
PDF files must provide precise byte offsets -- a single extra
|
|
||||||
character placed anywhere in a valid PDF document can render it
|
|
||||||
invalid. This makes it harder to generate than HTML.""")
|
|
||||||
|
|
||||||
disc("""Most of the world's PDF documents have been produced
|
|
||||||
by Adobe's Acrobat tools, or rivals such as JAWS PDF Creator, which act
|
|
||||||
as 'print drivers'. Anyone wanting to automate PDF production would
|
|
||||||
typically use a product like Quark, Word or Framemaker running in a loop
|
|
||||||
with macros or plugins, connected to Acrobat. Pipelines of several
|
|
||||||
languages and products can be slow and somewhat unwieldy.
|
|
||||||
""")
|
|
||||||
|
|
||||||
|
|
||||||
disc("""The ReportLab library directly creates PDF based on
|
|
||||||
your graphics commands. There are no intervening steps. Your applications
|
|
||||||
can generate reports extremely fast - sometimes orders
|
|
||||||
of magnitude faster than traditional report-writing
|
|
||||||
tools. This approach is shared by several other libraries - PDFlib for C,
|
|
||||||
iText for Java, iTextSharp for .NET and others. However, The ReportLab library
|
|
||||||
differs in that it can work at much higher levels, with a full featured engine
|
|
||||||
for laying out documents complete with tables and charts. """)
|
|
||||||
|
|
||||||
|
|
||||||
disc("""In addition, because you are writing a program
|
|
||||||
in a powerful general purpose language, there are no
|
|
||||||
restrictions at all on where you get your data from,
|
|
||||||
how you transform it, and the kind of output
|
|
||||||
you can create. And you can reuse code across
|
|
||||||
whole families of reports.""")
|
|
||||||
|
|
||||||
disc("""The ReportLab library is expected to be useful
|
|
||||||
in at least the following contexts:""")
|
|
||||||
bullet("Dynamic PDF generation on the web")
|
|
||||||
bullet("High-volume corporate reporting and database publishing")
|
|
||||||
bullet("""An embeddable print engine for other applications, including
|
|
||||||
a 'report language' so that users can customize their own reports. <i>
|
|
||||||
This is particularly relevant to cross-platform apps which cannot
|
|
||||||
rely on a consistent printing or previewing API on each operating
|
|
||||||
system</i>.""")
|
|
||||||
bullet("""A 'build system' for complex documents with charts, tables
|
|
||||||
and text such as management accounts, statistical reports and
|
|
||||||
scientific papers """)
|
|
||||||
bullet("""Going from XML to PDF in one step!""")
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
heading2("What is Python?")
|
|
||||||
disc("""
|
|
||||||
Python is an <i>interpreted, interactive, object-oriented</i> programming language. It is often compared to Tcl, Perl,
|
|
||||||
Scheme or Java.
|
|
||||||
""")
|
|
||||||
|
|
||||||
disc("""
|
|
||||||
Python combines remarkable power with very clear syntax. It has modules, classes, exceptions, very high level
|
|
||||||
dynamic data types, and dynamic typing. There are interfaces to many system calls and libraries, as well as to
|
|
||||||
various windowing systems (X11, Motif, Tk, Mac, MFC). New built-in modules are easily written in C or C++.
|
|
||||||
Python is also usable as an extension language for applications that need a programmable interface.
|
|
||||||
""")
|
|
||||||
|
|
||||||
|
|
||||||
disc("""
|
|
||||||
Python is as old as Java and has been growing steadily in popularity for 13 years; since our
|
|
||||||
library first came out it has entered the mainstream. Many ReportLab library users are
|
|
||||||
already Python devotees, but if you are not, we feel that the language is an excellent
|
|
||||||
choice for document-generation apps because of its expressiveness and ability to get
|
|
||||||
data from anywhere.
|
|
||||||
""")
|
|
||||||
|
|
||||||
disc("""
|
|
||||||
Python is copyrighted but <b>freely usable and distributable, even for commercial use</b>.
|
|
||||||
""")
|
|
||||||
|
|
||||||
heading2("Acknowledgements")
|
|
||||||
disc("""Many people have contributed to ReportLab. We would like to thank
|
|
||||||
in particular (in approximately chronological order) Chris Lee, Magnus Lie Hetland,
|
|
||||||
Robert Kern, Jeff Bauer (who contributed normalDate.py); Jerome Alet (numerous patches
|
|
||||||
and the rlzope demo), Andre Reitz, Max M, Albertas Agejevas, T Blatter, Ron Peleg,
|
|
||||||
Gary Poster, Steve Halasz, Andrew Mercer, Paul McNett, Chad Miller, Tim Roberts,
|
|
||||||
Jorge Godoy and Benn B.""")
|
|
||||||
|
|
||||||
disc("""Special thanks go to Just van Rossum for his valuable assistance with
|
|
||||||
font technicalities and the LettErrorRobot-Chrome type 1 font.""")
|
|
||||||
|
|
||||||
disc("""Marius Gedminas deserves a big hand for contributing the work on TrueType fonts and we
|
|
||||||
are glad to include these in the toolkit. Finally we thank Bigelow & Holmes Inc ($design@bigelowandholmes.com$)
|
|
||||||
for Luxi Serif Regular and Ray Larabie ($http://www.larabiefonts.com$) for the Rina TrueType font.""")
|
|
||||||
|
|
||||||
heading2("Installation and Setup")
|
|
||||||
|
|
||||||
disc("""
|
|
||||||
Below we provide an abbreviated setup procedure for Python experts and a more
|
|
||||||
verbose procedure for people who are new to Python.
|
|
||||||
""")
|
|
||||||
|
|
||||||
heading3("Installation for experts")
|
|
||||||
disc("""First of all, we'll give you the high-speed version for experienced
|
|
||||||
Python developers:""")
|
|
||||||
list("""Install Python 2.3 or later (2.4 recommended). ReportLab 2.x uses
|
|
||||||
Python 2.3 features and will use 2.4 going forwards. We also maintain
|
|
||||||
a 1.x branch which works back to Python 2.1.
|
|
||||||
""")
|
|
||||||
list("""If you want to produce compressed PDF files (recommended),
|
|
||||||
check that zlib is installed.""")
|
|
||||||
list("""If you want to work with bitmap images, install and
|
|
||||||
test the Python Imaging Library""")
|
|
||||||
list("""Unpack the reportlab package (reportlab.zip
|
|
||||||
or reportlab.tgz) into a directory on your path. (You can also use ^python setup.py install^ if you wish)""")
|
|
||||||
list("""Unpack the rl_addons package and build the C extensions with distutils; or grab the
|
|
||||||
corresponding .pyd files from our download page. """)
|
|
||||||
|
|
||||||
|
|
||||||
list("""$cd$ to ^reportlab/test^ and execute $runAll.py$.
|
|
||||||
This will create many PDF files. """)
|
|
||||||
list("""You may also want to download and run the ^rl_check.py^ on our site, which
|
|
||||||
health-checks an installation and reports on any missing options. """)
|
|
||||||
disc(" ")
|
|
||||||
disc("""If you have any problems, check the 'Detailed Instructions' section below.""")
|
|
||||||
|
|
||||||
heading3("A note on available versions")
|
|
||||||
disc("""The $reportlab$ library can be found at $ftp.reportlab.com$ in
|
|
||||||
the top-level directory or at http://www.reportlab.com/ftp/.
|
|
||||||
Each successive version is stored in both zip
|
|
||||||
and tgz format, but the contents are identical apart from line endings.
|
|
||||||
Versions are numbered: $ReportLab_1_00.zip$, $ReportLab_1_01.zip$ and so on. The
|
|
||||||
latest stable version is also available as just $reportlab.zip$ (or
|
|
||||||
$reportlab.tgz$), which is actually a symbolic link to the latest
|
|
||||||
numbered version. Finally, daily snapshots off the trunk are available as
|
|
||||||
$current.zip$ (or $current.tgz$).
|
|
||||||
""")
|
|
||||||
|
|
||||||
|
|
||||||
heading3("Instructions for novices: Windows")
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
disc("""This section assumes you
|
|
||||||
don't know much about Python. We cover all of the steps for three
|
|
||||||
common platforms, including how to verify that each one is complete.
|
|
||||||
While this may seem like a long list, everything takes 5 minutes if
|
|
||||||
you have the binaries at hand.""")
|
|
||||||
|
|
||||||
|
|
||||||
restartList()
|
|
||||||
|
|
||||||
list("""Get and install Python from $http://www.python.org/.$
|
|
||||||
Reportlab 2.x works with Python 2.3 upwards but we strongly recommend to use
|
|
||||||
the latest stable version of Python (2.4.3 at the time of writing).
|
|
||||||
Follow the links to 'Download' and get the latest
|
|
||||||
official version. This will install itself into $C:\Python24$
|
|
||||||
After installing, you should be able to run the
|
|
||||||
'Python (command line)' option from the Start Menu.""")
|
|
||||||
|
|
||||||
list("""If on Windows, we strongly recommend installing the Python Windows
|
|
||||||
Extensions, which let you use access all the Windows data sources, and provide
|
|
||||||
a very nice IDE. This can be found at ^http://sourceforge.net/projects/pywin32/^.
|
|
||||||
Once this is installed, you can start
|
|
||||||
Pythonwin from the Start Menu and get a GUI application.""")
|
|
||||||
|
|
||||||
list("""The next step is optional and only necessary if you want to
|
|
||||||
include images in your reports; it can also be carried out later. However
|
|
||||||
we always recommend a full installation if time permits.""")
|
|
||||||
|
|
||||||
list("""Install the Python Imaging Library ($PIL$) from $http://www.pythonware.com/products/pil/$.
|
|
||||||
""")
|
|
||||||
|
|
||||||
|
|
||||||
list("""Now you are ready to install reportlab itself. Unzip the archive straight into
|
|
||||||
your Python directory; it creates a subdirectory named
|
|
||||||
$reportlab$. You should now be able to go to a Python
|
|
||||||
command line interpreter and type $import reportlab$ without getting
|
|
||||||
an error message.""")
|
|
||||||
|
|
||||||
list("""Download the zip file of precompiled DLLs for your Python version from
|
|
||||||
the bottom of the ^http://www.reportlab.org/downloads.html^ downloads page, and unzip
|
|
||||||
them into ^C:\Python24\lib\site-packages^ (or its equivalent for other Python versions""")
|
|
||||||
|
|
||||||
list("""Open up a $MS-DOS$ command prompt and CD to
|
|
||||||
"$..\\reportlab\\test$". Enter "$runAll.py$". You should see lots of dots
|
|
||||||
and no error messages. This will also create many PDF files and generate
|
|
||||||
the manuals in ^reportlab/docs^ (including this one). """)
|
|
||||||
|
|
||||||
list("""
|
|
||||||
Finally, we recommend you download and run the script ^rl_check.py^ from
|
|
||||||
^^http://www.reportlab.org/ftp/^. This will health-check all the above
|
|
||||||
steps and warn you if anything is missing or mismatched.""")
|
|
||||||
|
|
||||||
heading3("Instructions for Python novices: Unix")
|
|
||||||
|
|
||||||
restartList()
|
|
||||||
list("""On a large number of Unix and Linux distributions, Python is already installed,
|
|
||||||
or is avaialable as a standard package you can install with the relevant package manager.""")
|
|
||||||
|
|
||||||
list("""If you want to compile from
|
|
||||||
source download the latest
|
|
||||||
sources from http://www.python.org (currently the latest source is
|
|
||||||
in http://www.python.org/ftp/python/2.4.3/Python-2.4.3.tgz). If you wish to use
|
|
||||||
binaries
|
|
||||||
get the latest RPM or DEB or whatever package and install (or get your
|
|
||||||
super user (system administrator) to do the work).""")
|
|
||||||
|
|
||||||
list("""If you are building Python yourself, unpack the sources into a
|
|
||||||
temporary directory using a tar command e.g. $tar xzvf Python-2.4.3.tgz$;
|
|
||||||
this will create a subdirectory called Python-2.4.3 (or whatever). cd
|
|
||||||
into this directory. Then read the file $README$! It contains the
|
|
||||||
latest information on how to install Python.""")
|
|
||||||
|
|
||||||
list("""If your system has the gzip libz library installed
|
|
||||||
check that the zlib extension will be installed by default by editing
|
|
||||||
the file Modules/Setup.in and ensuring that (near line 405) the line
|
|
||||||
containing zlib zlibmodule.c is uncommented i.e. has no hash '#' character at the
|
|
||||||
beginning. You also need to decide if you will be installing in the default location
|
|
||||||
(/usr/local/) or in some other place.
|
|
||||||
The zlib module is needed if you want compressed PDF and for some images.""")
|
|
||||||
|
|
||||||
list("""Invoke the command $./configure --prefix=/usr/local$ this should configure
|
|
||||||
the source directory for building. Then you can build the binaries with
|
|
||||||
a $make$ command. If your $make$ command is not up to it try building
|
|
||||||
with $make MAKE=make$. If all goes well install with $make install$.""")
|
|
||||||
|
|
||||||
list("""If all has gone well and python is in the execution search path
|
|
||||||
you should now be able to type $python$ and see a <b>Python</b> prompt.""")
|
|
||||||
|
|
||||||
list("""
|
|
||||||
Once you can do that it's time to try and install ReportLab.
|
|
||||||
First get the latest reportlab.tgz.
|
|
||||||
If ReportLab is to be available to all then the reportlab archive should be unpacked in
|
|
||||||
the lib/site-python directory (typically /usr/local/lib/site-python) if necessary by
|
|
||||||
a superuser.
|
|
||||||
Otherwise unpack in a directory of your choice and arrange for that directory to be on your
|
|
||||||
$PYTHONPATH$ variable.
|
|
||||||
""")
|
|
||||||
eg("""
|
|
||||||
#put something like this in your
|
|
||||||
#shell rcfile
|
|
||||||
PYTHONPATH=$HOME/mypythonpackages
|
|
||||||
export PYTHONPATH
|
|
||||||
""",after=0.1)
|
|
||||||
|
|
||||||
list("""You should now be able to run python and execute the python statement
|
|
||||||
""",doBullet=0)
|
|
||||||
eg("""import reportlab""",after=0.1)
|
|
||||||
list("""If you want to use images you should certainly consider
|
|
||||||
getting & installing the Python Imaging Library - follow the
|
|
||||||
directions from
|
|
||||||
$http://www.python.org/sigs/image-sig/index.html$ or get it directly from
|
|
||||||
$http://www.pythonware.com/products/pil/$.""")
|
|
||||||
|
|
||||||
|
|
||||||
heading3("Instructions for Python novices: Mac")
|
|
||||||
disc("""
|
|
||||||
This is much, much easier with Mac OS X since Python (usually 2.3) is installed on your
|
|
||||||
system as standard. Just follow the instructions for installing the ReportLab archive
|
|
||||||
above.
|
|
||||||
""")
|
|
||||||
|
|
||||||
|
|
||||||
heading3("Instructions for Jython (Java implementation of Python) users")
|
|
||||||
|
|
||||||
disc("""
|
|
||||||
A port to Java was done in 2004. This involved some changes to the framework
|
|
||||||
and creating Java equivalents of the C extensions. At the end of this work
|
|
||||||
the entire output of the test suite produced byte-for-byte identical output.
|
|
||||||
However, we have not been testng against Jython since, because (a) as far as
|
|
||||||
we know no one used it, and (b) Jython has not kept up with Python features
|
|
||||||
which we need to use. We suggest you use ReportLab v1.19 or v1.20 which
|
|
||||||
were Python-2.1 compatible. We'd welcome test reports and/or a volunteer to
|
|
||||||
refresh things now that Jython is progressing.""")
|
|
||||||
|
|
||||||
disc("""
|
|
||||||
The Jython version was tested under Sun's J2SDK 1.3.1. It is known that under
|
|
||||||
J2SDK 1.4.0_01 $test_pdfbase_ttfonts.py$ fails horribly with an outOfMemory
|
|
||||||
exception, probably caused by a JVM bug.
|
|
||||||
""")
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
restartList()
|
|
||||||
|
|
||||||
list("""
|
|
||||||
Before installing Jython, make sure you have a supported version of
|
|
||||||
Java Virtual Machine installed. For the list of supported JVM's see
|
|
||||||
$http://www.jython.org/platform.html$
|
|
||||||
""")
|
|
||||||
|
|
||||||
list("""
|
|
||||||
To install Jython, download the setup package from $www.jython.org$ and
|
|
||||||
follow installation instructions.
|
|
||||||
""")
|
|
||||||
|
|
||||||
list("""
|
|
||||||
To set ReportLab toolkit under Jython PATH, edit $JYTHON_HOME/registry$ file
|
|
||||||
and include line that tells Jython where to look for packages. To include
|
|
||||||
ReportLab toolkit under Jython PATH, directory that contains Reportlab
|
|
||||||
should be included: $python.path=REPORTLAB_HOME_PARENT_DIR$
|
|
||||||
For example, if your Reportlab toolkit is installed under $C:\code\\reportlab$
|
|
||||||
the path line should be: $python.path=C:\\\\code$ (note two backslashes!)
|
|
||||||
""")
|
|
||||||
|
|
||||||
heading3("Instructions for IronPython (Python for .NET) users")
|
|
||||||
|
|
||||||
disc("""
|
|
||||||
We haven't tackled this yet officially, but IronPython can apparently
|
|
||||||
run much of our code. We do need to go through the same exercises we did for Jython
|
|
||||||
- finding the .NET equivalents of _rl_accel, pyRXP, _renderPM and PIL -
|
|
||||||
to get 100% managed code. Hopefully this will happen soon and we'd be
|
|
||||||
delighted to work with anyone on this.
|
|
||||||
""")
|
|
||||||
|
|
||||||
|
|
||||||
heading2("Getting Involved")
|
|
||||||
disc("""ReportLab is an Open Source project. Although we are
|
|
||||||
a commercial company we provide the core PDF generation
|
|
||||||
sources freely, even for commercial purposes, and we make no income directly
|
|
||||||
from these modules. We also welcome help from the community
|
|
||||||
as much as any other Open Source project. There are many
|
|
||||||
ways in which you can help:""")
|
|
||||||
|
|
||||||
bullet("""General feedback on the core API. Does it work for you?
|
|
||||||
Are there any rough edges? Does anything feel clunky and awkward?""")
|
|
||||||
|
|
||||||
bullet("""New objects to put in reports, or useful utilities for the library.
|
|
||||||
We have an open standard for report objects, so if you have written a nice
|
|
||||||
chart or table class, why not contribute it?""")
|
|
||||||
|
|
||||||
bullet("""Demonstrations and Case Studies: If you have produced some nice
|
|
||||||
output, send it to us (with or without scripts). If ReportLab solved a
|
|
||||||
problem for you at work, write a little 'case study' and send it in.
|
|
||||||
And if your web site uses our tools to make reports, let us link to it.
|
|
||||||
We will be happy to display your work (and credit it with your name
|
|
||||||
and company) on our site!""")
|
|
||||||
|
|
||||||
bullet("""Working on the core code: we have a long list of things
|
|
||||||
to refine or to implement. If you are missing some features or
|
|
||||||
just want to help out, let us know!""")
|
|
||||||
|
|
||||||
disc("""The first step for anyone wanting to learn more or
|
|
||||||
get involved is to join the mailing list. To Subscribe visit
|
|
||||||
$http://two.pairlist.net/mailman/listinfo/reportlab-users$.
|
|
||||||
From there you can also browse through the group's archives
|
|
||||||
and contributions. The mailing list is
|
|
||||||
the place to report bugs and get support. """)
|
|
||||||
|
|
||||||
|
|
||||||
heading2("Site Configuration")
|
|
||||||
disc("""There are a number of options which most likely need to be configured globally for a site.
|
|
||||||
The python script module $reportlab/rl_config.py$ may be edited to change the values of several
|
|
||||||
important sitewide properties.""")
|
|
||||||
bullet("""verbose: set to integer values to control diagnostic output.""")
|
|
||||||
bullet("""shapeChecking: set this to zero to turn off a lot of error checking in the graphics modules""")
|
|
||||||
bullet("""defaultEncoding: set this to WinAnsiEncoding or MacRomanEncoding.""")
|
|
||||||
bullet("""defaultPageSize: set this to one of the values defined in reportlab/lib/pagesizes.py; as delivered
|
|
||||||
it is set to pagesizes.A4; other values are pagesizes.letter etc.""")
|
|
||||||
bullet("""defaultImageCaching: set to zero to inhibit the creation of .a85 files on your
|
|
||||||
hard-drive. The default is to create these preprocessed PDF compatible image files for faster loading""")
|
|
||||||
bullet("""T1SearchPath: this is a python list of strings representing directories that
|
|
||||||
may be queried for information on Type 1 fonts""")
|
|
||||||
bullet("""TTFSearchPath: this is a python list of strings representing directories that
|
|
||||||
may be queried for information on TrueType fonts""")
|
|
||||||
bullet("""CMapSearchPath: this is a python list of strings representing directories that
|
|
||||||
may be queried for information on font code maps.""")
|
|
||||||
bullet("""showBoundary: set to non-zero to get boundary lines drawn.""")
|
|
||||||
bullet("""ZLIB_WARNINGS: set to non-zero to get warnings if the Python compression extension is not found.""")
|
|
||||||
bullet("""pageComression: set to non-zero to try and get compressed PDF.""")
|
|
||||||
bullet("""allowtableBoundsErrors: set to 0 to force an error on very large Platypus table elements""")
|
|
||||||
bullet("""emptyTableAction: Controls behaviour for empty tables, can be 'error' (default), 'indicate' or 'ignore'.""")
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
heading2("Learning More About Python")
|
|
||||||
|
|
||||||
disc("""
|
|
||||||
If you are a total beginner to Python, you should check out one or more from the
|
|
||||||
growing number of resources on Python programming. The following are freely
|
|
||||||
available on the web:
|
|
||||||
""")
|
|
||||||
|
|
||||||
|
|
||||||
bullet("""<b>Introductory Material on Python. </b>
|
|
||||||
A list of tutorials on the Python.org web site.
|
|
||||||
$http://www.python.org/doc/Intros.html$
|
|
||||||
""")
|
|
||||||
|
|
||||||
|
|
||||||
bullet("""<b>Python Tutorial. </b>
|
|
||||||
The official Python Tutorial by Guido van Rossum (edited by Fred L. Drake, Jr.)
|
|
||||||
$http://www.python.org/doc/tut/$
|
|
||||||
""")
|
|
||||||
|
|
||||||
|
|
||||||
bullet("""<b>Learning to Program. </b>
|
|
||||||
A tutorial on programming by Alan Gauld. Has a heavy emphasis on
|
|
||||||
Python, but also uses other languages.
|
|
||||||
$http://www.freenetpages.co.uk/hp/alan.gauld/$
|
|
||||||
""")
|
|
||||||
|
|
||||||
|
|
||||||
bullet("""<b>How to think like a computer scientist</b> (Python version)</b>.
|
|
||||||
$http://www.ibiblio.org/obp/thinkCSpy/$
|
|
||||||
""")
|
|
||||||
|
|
||||||
|
|
||||||
bullet("""<b>Instant Python</b>.
|
|
||||||
A 6-page minimal crash course by Magnus Lie Hetland.
|
|
||||||
$http://www.hetland.org/python/instant-python.php$
|
|
||||||
""")
|
|
||||||
|
|
||||||
|
|
||||||
bullet("""<b>Dive Into Python</b>.
|
|
||||||
A free Python tutorial for experienced programmers.
|
|
||||||
$http://diveintopython.org/$
|
|
||||||
""")
|
|
||||||
|
|
||||||
|
|
||||||
from reportlab.lib.codecharts import SingleByteEncodingChart
|
|
||||||
from reportlab.tools.docco.stylesheet import getStyleSheet
|
|
||||||
styles = getStyleSheet()
|
|
||||||
indent0_style = styles['Indent0']
|
|
||||||
indent1_style = styles['Indent1']
|
|
||||||
|
|
||||||
heading2("What's New in ReportLab 2.0")
|
|
||||||
disc("""
|
|
||||||
Many new features have been added, foremost amongst which is the support
|
|
||||||
for unicode. This page documents what has changed since version 1.20.""")
|
|
||||||
|
|
||||||
disc("""
|
|
||||||
Adding full unicode support meant that we had to break backwards-compatibility,
|
|
||||||
so old code written for ReportLab 1 will sometimes need changes before it will
|
|
||||||
run correctly with ReportLab 2. Now that we have made the clean break to
|
|
||||||
introduce this important new feature, we intend to keep the API
|
|
||||||
backwards-compatible throughout the 2.* series.
|
|
||||||
""")
|
|
||||||
heading3("Goals for the 2.x series")
|
|
||||||
disc("""
|
|
||||||
The main rationale for 2.0 was an incompatible change at the character level:
|
|
||||||
to properly support Unicode input. Now that it's out we will maintain compatibility
|
|
||||||
with 2.0. There are no pressing feature wishlists and new features will be driven,
|
|
||||||
as always, by contributions and the demands of projects.""")
|
|
||||||
|
|
||||||
disc("""
|
|
||||||
Our 1.x code base is still Python 2.1 compatible. The new version lets us move forwards
|
|
||||||
with a baseline of Python 2.4 (2.3 will work too, for the moment, but we don't promise
|
|
||||||
that going forwards) so we can use newer language features freely in our development.""")
|
|
||||||
|
|
||||||
disc("""
|
|
||||||
One area where we do want to make progress from release to release is with documentation
|
|
||||||
and installability. We'll be looking into better support for distutils, setuptools,
|
|
||||||
eggs and so on; and into better examples and tools to help people learn what's in the
|
|
||||||
(substantial) code base.""")
|
|
||||||
|
|
||||||
disc("""
|
|
||||||
Bigger ideas and more substantial rewrites are deferred to Version 3.0, with no particular
|
|
||||||
target dates.
|
|
||||||
""")
|
|
||||||
|
|
||||||
heading3("Contributions")
|
|
||||||
disc("""Thanks to everybody who has contributed to the open-source toolkit in the run-up
|
|
||||||
to the 2.0 release, whether by reporting bugs, sending patches, or contributing to the
|
|
||||||
reportlab-users mailing list. Thanks especially to the following people, who contributed
|
|
||||||
code that has gone into 2.0: Andre Reitz, Max M, Albertas Agejevas, T Blatter, Ron Peleg,
|
|
||||||
Gary Poster, Steve Halasz, Andrew Mercer, Paul McNett, Chad Miller.
|
|
||||||
""")
|
|
||||||
todo("""If we missed you, please let us know!""")
|
|
||||||
|
|
||||||
heading3("Unicode support")
|
|
||||||
disc("""
|
|
||||||
This is the Big One, and the reason some apps may break. You must now pass in text either
|
|
||||||
in UTF-8 or as unicode string objects. The library will handle everything to do with output
|
|
||||||
encoding. There is more information on this below.
|
|
||||||
Since this is the biggest change, we'll start by reviewing how it worked in the past.""")
|
|
||||||
|
|
||||||
disc("""
|
|
||||||
In ReportLab 1.x, any string input you passed to our APIs was supposed to be in the same
|
|
||||||
encoding as the font you selected for output. If using the default fonts in Acrobat Reader
|
|
||||||
(Helvetica/Times/Courier), you would have implicitly used WinAnsi encoding, which is almost
|
|
||||||
exactly the same as Latin-1. However, if using TrueType fonts, you would have been using UTF-8.""")
|
|
||||||
|
|
||||||
disc("""For Asian fonts, you had a wide choice of encodings but had to specify which one
|
|
||||||
(e.g Shift-JIS or EUC for Japanese). This state of affairs meant that you had
|
|
||||||
to make sure that every piece of text input was in the same encoding as the font used
|
|
||||||
to display it.""")
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
disc("""Input text encoding is UTF-8 or Python Unicode strings""")
|
|
||||||
disc("""
|
|
||||||
Any text you pass to a canvas API (drawString etc.), Paragraph or other flowable
|
|
||||||
constructor, into a table cell, or as an attribute of a graphic (e.g. chart.title.text),
|
|
||||||
is supposed to be unicode. If you use a traditional Python string, it is assumed to be UTF-8.
|
|
||||||
If you pass a Unicode object, we know it's unicode.""", style=indent1_style)
|
|
||||||
|
|
||||||
disc("""Font encodings""")
|
|
||||||
disc("""
|
|
||||||
Fonts still work in different ways, and the built-in ones will still use WinAnsi or MacRoman
|
|
||||||
internally while TrueType will use UTF-8. However, the library hides this from you; it converts
|
|
||||||
as it writes out the PDF file. As before, it's still your job to make sure the font you use has
|
|
||||||
the characters you need, or you may get either a traceback or a visible error character.""",style=indent1_style)
|
|
||||||
|
|
||||||
disc("""Asian CID fonts""")
|
|
||||||
disc("""
|
|
||||||
You no longer need to specify the encoding for the built-in Asian fonts, just the face name.
|
|
||||||
ReportLab knows about the standard fonts in Adobe's Asian Language Packs
|
|
||||||
""", style=indent1_style)
|
|
||||||
|
|
||||||
disc("""Asian Truetype fonts""")
|
|
||||||
disc("""
|
|
||||||
The standard Truetype fonts differ slightly for Asian languages (e.g msmincho.ttc).
|
|
||||||
These can now be read and used, albeit somewhat inefficiently.
|
|
||||||
""", style=indent1_style)
|
|
||||||
|
|
||||||
disc("""Asian word wrapping""")
|
|
||||||
disc("""
|
|
||||||
Previously we could display strings in Asian languages, but could not properly
|
|
||||||
wrap paragraphs as there are no gaps between the words. We now have a basic word wrapping
|
|
||||||
algorithm.
|
|
||||||
""", style=indent1_style)
|
|
||||||
|
|
||||||
disc("""unichar tag""")
|
|
||||||
disc("""
|
|
||||||
A convenience tag, <unichar/> has also been added. You can now do <unichar code="0xfc"/>
|
|
||||||
or <unichar name='LATIN SMALL LETTER U WITH DIAERESIS'/> and
|
|
||||||
get a lowercase u umlaut. Names should be those in the Unicode Character Database.
|
|
||||||
""", style=indent1_style)
|
|
||||||
|
|
||||||
disc("""Accents, greeks and symbols""")
|
|
||||||
disc("""
|
|
||||||
The correct way to refer to all non-ASCII characters is to use their unicode representation.
|
|
||||||
This can be literal Unicode or UTF-8. Special symbols and Greek letters (collectively, "greeks")
|
|
||||||
inserted in paragraphs using the greek tag (e.g. <greek>lambda</greek>) or using the entity
|
|
||||||
references (e.g. λ) are now processed in a different way than in version 1.""", style=indent1_style)
|
|
||||||
disc("""
|
|
||||||
Previously, these were always rendered using the Zapf Dingbats font. Now they are always output
|
|
||||||
in the font you specified, unless that font does not support that character. If the font does
|
|
||||||
not support the character, and the font you specified was an Adobe Type 1 font, Zapf Dingbats
|
|
||||||
is used as a fallback. However, at present there is no fallback in the case of TTF fonts.
|
|
||||||
Note that this means that documents that contain greeks and specify a TTF font may need
|
|
||||||
changing to explicitly specify the font to use for the greek character, or you will see a black
|
|
||||||
square in place of that character when you view your PDF output in Acrobat Reader.
|
|
||||||
""", style=indent1_style)
|
|
||||||
|
|
||||||
# Other New Features Section #######################
|
|
||||||
heading3("Other New Features")
|
|
||||||
disc("""PDF""")
|
|
||||||
disc("""Improved low-level annotation support for PDF "free text annotations"
|
|
||||||
""", style=indent0_style)
|
|
||||||
disc("""FreeTextAnnotation allows showing and hiding of an arbitrary PDF "form"
|
|
||||||
(reusable chunk of PDF content) depending on whether the document is printed or
|
|
||||||
viewed on-screen, or depending on whether the mouse is hovered over the content, etc.
|
|
||||||
""", style=indent1_style)
|
|
||||||
|
|
||||||
disc("""TTC font collection files are now readable"
|
|
||||||
""", style=indent0_style)
|
|
||||||
disc("""ReportLab now supports using TTF fonts packaged in .TTC files""", style=indent1_style)
|
|
||||||
|
|
||||||
disc("""East Asian font support (CID and TTF)""", style=indent0_style)
|
|
||||||
disc("""You no longer need to specify the encoding for the built-in Asian fonts,
|
|
||||||
just the face name. ReportLab knows about the standard fonts in Adobe's Asian Language Packs.
|
|
||||||
""", style=indent1_style)
|
|
||||||
|
|
||||||
disc("""Native support for JPEG CMYK images""", style=indent0_style)
|
|
||||||
disc("""ReportLab now takes advantage of PDF's native JPEG CMYK image support,
|
|
||||||
so that JPEG CMYK images are no longer (lossily) converted to RGB format before including
|
|
||||||
them in PDF.""", style=indent1_style)
|
|
||||||
|
|
||||||
|
|
||||||
disc("""Platypus""")
|
|
||||||
disc("""Link support in paragraphs""", style=indent0_style)
|
|
||||||
disc("""
|
|
||||||
Platypus paragraphs can now contain link elements, which support both internal links
|
|
||||||
to the same PDF document, links to other local PDF documents, and URL links to pages on
|
|
||||||
the web. Some examples:""", style=indent1_style)
|
|
||||||
disc("""Web links:""", style=indent1_style)
|
|
||||||
disc("""<link href="http://www.reportlab.com/">ReportLab<link>""", style=styles['Link'])
|
|
||||||
|
|
||||||
disc("""Internal link to current PDF document:""", style=indent1_style)
|
|
||||||
disc("""<link href="summary">ReportLab<link>""", style=styles['Link'])
|
|
||||||
|
|
||||||
disc("""External link to a PDF document on the local filesystem:""", style=indent1_style)
|
|
||||||
disc("""<link href="pdf:C:/john/report.pdf">ReportLab<link>""", style=styles['Link'])
|
|
||||||
|
|
||||||
disc("""Improved wrapping support""", style=indent0_style)
|
|
||||||
disc("""Support for wrapping arbitrary sequence of flowables around an image, using
|
|
||||||
reportlab.platypus.flowables.ImageAndFlowables (similar to ParagraphAndImage)."""
|
|
||||||
,style=indent1_style)
|
|
||||||
|
|
||||||
disc("""KeepInFrame""", style=indent0_style)
|
|
||||||
disc("""Sometimes the length of a piece of text you'd like to include in a fixed piece
|
|
||||||
of page "real estate" is not guaranteed to be constrained to a fixed maximum length.
|
|
||||||
In these cases, KeepInFrame allows you to specify an appropriate action to take when
|
|
||||||
the text is too long for the space allocated for it. In particular, it can shrink the text
|
|
||||||
to fit, mask (truncate) overflowing text, allow the text to overflow into the rest of the document,
|
|
||||||
or raise an error.""",style=indent1_style)
|
|
||||||
|
|
||||||
|
|
||||||
disc("""Improved convenience features for inserting unicode symbols and other characters
|
|
||||||
""", style=indent0_style)
|
|
||||||
disc("""<unichar/> lets you conveniently insert unicode characters using the standard long name
|
|
||||||
or code point. Characters inserted with the <greek> tags (e.g. <greek>lambda</greek>) or corresponding
|
|
||||||
entity references (e.g. λ) support arbitrary fonts (rather than only Zapf Dingbats).""",style=indent1_style)
|
|
||||||
|
|
||||||
disc("""Improvements to Legending""", style=indent0_style)
|
|
||||||
disc("""Instead of manual placement, there is now a attachment point (N, S, E, W, etc.), so that
|
|
||||||
the legend is always automatically positioned correctly relative to the chart. Swatches (the small
|
|
||||||
sample squares of colour / pattern fill sometimes displayed in the legend) can now be automatically
|
|
||||||
created from the graph data. Legends can now have automatically-computed totals (useful for
|
|
||||||
financial applications).""",style=indent1_style)
|
|
||||||
|
|
||||||
disc("""More and better ways to place piechart labels""", style=indent0_style)
|
|
||||||
disc("""New smart algorithms for automatic pie chart label positioning have been added.
|
|
||||||
You can now produce nice-looking labels without manual positioning even for awkward cases in
|
|
||||||
big runs of charts.""",style=indent1_style)
|
|
||||||
|
|
||||||
disc("""Adjustable piechart slice ordering""", style=indent0_style)
|
|
||||||
disc("""For example. pie charts with lots of small slices can be configured to alternate thin and
|
|
||||||
thick slices to help the lagel placememt algorithm work better.""",style=indent1_style)
|
|
||||||
disc("""Improved spiderplots""", style=indent0_style)
|
|
||||||
|
|
||||||
|
|
||||||
# Noteworthy bug fixes Section #######################
|
|
||||||
heading3("Noteworthy bug fixes")
|
|
||||||
disc("""Fixes to TTF splitting (patch from Albertas Agejevas)""")
|
|
||||||
disc("""This affected some documents using font subsetting""", style=indent0_style)
|
|
||||||
|
|
||||||
disc("""Tables with spans improved splitting""")
|
|
||||||
disc("""Splitting of tables across pages did not work correctly when the table had
|
|
||||||
row/column spans""", style=indent0_style)
|
|
||||||
|
|
||||||
disc("""Fix runtime error affecting keepWithNext""")
|
|
|
@ -1,513 +0,0 @@
|
||||||
#Copyright ReportLab Europe Ltd. 2000-2004
|
|
||||||
#see license.txt for license details
|
|
||||||
#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/docs/userguide/ch2a_fonts.py
|
|
||||||
from reportlab.tools.docco.rl_doc_utils import *
|
|
||||||
from reportlab.lib.codecharts import SingleByteEncodingChart
|
|
||||||
from reportlab.platypus import Image
|
|
||||||
import reportlab
|
|
||||||
|
|
||||||
heading1("Fonts and encodings")
|
|
||||||
|
|
||||||
disc("""
|
|
||||||
This chapter covers fonts, encodings and Asian language capabilities.
|
|
||||||
If you are purely concerned with generating PDFs for Western
|
|
||||||
European languages, you can just read the "Unicode is the default" section
|
|
||||||
below and skip the rest on a first reading.
|
|
||||||
We expect this section to grow considerably over time. We
|
|
||||||
hope that Open Source will enable us to give better support for
|
|
||||||
more of the world's languages than other tools, and we welcome
|
|
||||||
feedback and help in this area.
|
|
||||||
""")
|
|
||||||
|
|
||||||
heading2("Unicode and UTF8 are the default input encodings")
|
|
||||||
|
|
||||||
disc("""
|
|
||||||
Starting with reportlab Version 2.0 (May 2006), all text input you
|
|
||||||
provide to our APIs should be in UTF8 or as Python Unicode objects.
|
|
||||||
This applies to arguments to canvas.drawString and related APIs,
|
|
||||||
table cell content, drawing object parameters, and paragraph source
|
|
||||||
text.
|
|
||||||
""")
|
|
||||||
|
|
||||||
|
|
||||||
disc("""
|
|
||||||
We considered making the input encoding configurable or even locale-dependent,
|
|
||||||
but decided that "explicit is better than implicit".""")
|
|
||||||
|
|
||||||
disc("""
|
|
||||||
This simplifies many things we used to do previously regarding greek
|
|
||||||
letters, symbols and so on. To display any character, find out its
|
|
||||||
unicode code point, and make sure the font you are using is able
|
|
||||||
to display it.""")
|
|
||||||
|
|
||||||
disc("""
|
|
||||||
If you are adapting a ReportLab 1.x application, or reading data from
|
|
||||||
another source which contains single-byte data (e.g. latin-1 or WinAnsi),
|
|
||||||
you need to do a conversion into Unicode. The Python codecs package now
|
|
||||||
includes converters for all the common encodings, including Asian ones.
|
|
||||||
""")
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
disc(u"""
|
|
||||||
If your data is not encoded as UTF8, you will get a UnicodeDecodeError as
|
|
||||||
soon as you feed in a non-ASCII character. For example, this snippet below is
|
|
||||||
attempting to read in and print a series of names, including one with a French
|
|
||||||
accent: ^Marc-Andr\u00e9 Lemburg^. The standard error is quite helpful and tells you
|
|
||||||
what character it doesn't like:
|
|
||||||
""")
|
|
||||||
|
|
||||||
eg(u"""
|
|
||||||
>>> from reportlab.pdfgen.canvas import Canvas
|
|
||||||
>>> c = Canvas('temp.pdf')
|
|
||||||
>>> y = 700
|
|
||||||
>>> for line in file('latin_python_gurus.txt','r'):
|
|
||||||
... c.drawString(100, y, line.strip())
|
|
||||||
...
|
|
||||||
Traceback (most recent call last):
|
|
||||||
...
|
|
||||||
UnicodeDecodeError: 'utf8' codec can't decode bytes in position 9-11: invalid data
|
|
||||||
-->\u00e9 L<--emburg
|
|
||||||
>>>
|
|
||||||
""")
|
|
||||||
|
|
||||||
|
|
||||||
disc("""
|
|
||||||
The simplest fix is just to convert your data to unicode, saying which encoding
|
|
||||||
it comes from, like this:""")
|
|
||||||
|
|
||||||
eg("""
|
|
||||||
>>> for line in file('latin_input.txt','r'):
|
|
||||||
... uniLine = unicode(line, 'latin-1')
|
|
||||||
... c.drawString(100, y, uniLine.strip())
|
|
||||||
>>>
|
|
||||||
>>> c.save()
|
|
||||||
""")
|
|
||||||
|
|
||||||
|
|
||||||
heading2("Changing the built-in fonts output encoding")
|
|
||||||
|
|
||||||
disc("""
|
|
||||||
There are still a number of places in the code, including the rl_config
|
|
||||||
defaultEncoding parameter, and arguments passed to various Font constructors.
|
|
||||||
These generally relate to the OUTPUT encoding used when we write data in the font
|
|
||||||
file. This affects which characters are actually available in the font if
|
|
||||||
you are using Type 1 fonts, since only 256 glyphs can be available at
|
|
||||||
one time. Unless you have a very specific need for
|
|
||||||
MacRoman or MacExpert encoding characters, we advise you to ignore
|
|
||||||
this. By default the standard fonts (Helvetica, Courier, Times Roman)
|
|
||||||
will offer the glyphs available in Latin-1. If you try to print a non-Latin-1
|
|
||||||
character using the built-in Helvetica, you'll see a rectangle or blob.
|
|
||||||
|
|
||||||
""")
|
|
||||||
|
|
||||||
|
|
||||||
heading2("Using non-standard Type 1 fonts")
|
|
||||||
|
|
||||||
disc("""
|
|
||||||
As discussed in the previous chapter, every copy of Acrobat Reader
|
|
||||||
comes with 14 standard fonts built in. Therefore, the ReportLab
|
|
||||||
PDF Library only needs to refer to these by name. If you want
|
|
||||||
to use other fonts, they must be available to your code and
|
|
||||||
will be embedded in the PDF document.""")
|
|
||||||
|
|
||||||
disc("""
|
|
||||||
You can use the mechanism described below to include arbitrary
|
|
||||||
fonts in your documents. Just van Rossum has kindly donated a Type 1
|
|
||||||
font named <i>LettErrorRobot-Chrome</i> which we may
|
|
||||||
use for testing and/or documenting purposes (and which you may
|
|
||||||
use as well). It comes bundled with the ReportLab distribution in the
|
|
||||||
directory $reportlab/fonts$.
|
|
||||||
""")
|
|
||||||
|
|
||||||
disc("""
|
|
||||||
Right now font-embedding relies on font description files in the Adobe
|
|
||||||
AFM ('Adobe Font Metrics') and PFB ('Printer Font Binary') format. The
|
|
||||||
former is an ASCII file and contains information about the characters
|
|
||||||
('glyphs') in the font such as height, width, bounding box info and
|
|
||||||
other 'metrics', while the latter is a binary file that describes the
|
|
||||||
shapes of the font. The $reportlab/fonts$ directory contains the files
|
|
||||||
$'LeERC___.AFM'$ and $'LeERC___.PFB'$ that are used as an example
|
|
||||||
font.
|
|
||||||
""")
|
|
||||||
|
|
||||||
disc("""
|
|
||||||
In the following example locate the folder containing the test font and
|
|
||||||
register it for future use with the $pdfmetrics$ module,
|
|
||||||
after which we can use it like any other standard font.
|
|
||||||
""")
|
|
||||||
|
|
||||||
|
|
||||||
eg("""
|
|
||||||
import os
|
|
||||||
import reportlab
|
|
||||||
folder = os.path.dirname(reportlab.__file__) + os.sep + 'fonts'
|
|
||||||
afmFile = os.path.join(folder, 'LeERC___.AFM')
|
|
||||||
pfbFile = os.path.join(folder, 'LeERC___.PFB')
|
|
||||||
|
|
||||||
from reportlab.pdfbase import pdfmetrics
|
|
||||||
justFace = pdfmetrics.EmbeddedType1Face(afmFile, pfbFile)
|
|
||||||
faceName = 'LettErrorRobot-Chrome' # pulled from AFM file
|
|
||||||
pdfmetrics.registerTypeFace(justFace)
|
|
||||||
justFont = pdfmetrics.Font('LettErrorRobot-Chrome',
|
|
||||||
faceName,
|
|
||||||
'WinAnsiEncoding')
|
|
||||||
pdfmetrics.registerFont(justFont)
|
|
||||||
|
|
||||||
canvas.setFont('LettErrorRobot-Chrome', 32)
|
|
||||||
canvas.drawString(10, 150, 'This should be in')
|
|
||||||
canvas.drawString(10, 100, 'LettErrorRobot-Chrome')
|
|
||||||
""")
|
|
||||||
|
|
||||||
|
|
||||||
disc("""
|
|
||||||
Note that the argument "WinAnsiEncoding" has nothing to do with the input;
|
|
||||||
it's to say which set of characters within the font file will be active
|
|
||||||
and available.
|
|
||||||
""")
|
|
||||||
|
|
||||||
illust(examples.customfont1, "Using a very non-standard font")
|
|
||||||
|
|
||||||
disc("""
|
|
||||||
The font's facename comes from the AFM file's $FontName$ field.
|
|
||||||
In the example above we knew the name in advance, but quite
|
|
||||||
often the names of font description files are pretty cryptic
|
|
||||||
and then you might want to retrieve the name from an AFM file
|
|
||||||
automatically.
|
|
||||||
When lacking a more sophisticated method you can use some
|
|
||||||
code as simple as this:
|
|
||||||
""")
|
|
||||||
|
|
||||||
eg("""
|
|
||||||
class FontNameNotFoundError(Exception):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def findFontName(path):
|
|
||||||
"Extract a font name from an AFM file."
|
|
||||||
|
|
||||||
f = open(path)
|
|
||||||
|
|
||||||
found = 0
|
|
||||||
while not found:
|
|
||||||
line = f.readline()[:-1]
|
|
||||||
if not found and line[:16] == 'StartCharMetrics':
|
|
||||||
raise FontNameNotFoundError, path
|
|
||||||
if line[:8] == 'FontName':
|
|
||||||
fontName = line[9:]
|
|
||||||
found = 1
|
|
||||||
|
|
||||||
return fontName
|
|
||||||
""")
|
|
||||||
|
|
||||||
disc("""
|
|
||||||
In the <i>LettErrorRobot-Chrome</i> example we explicitely specified
|
|
||||||
the place of the font description files to be loaded.
|
|
||||||
In general, you'll prefer to store your fonts in some canonic
|
|
||||||
locations and make the embedding mechanism aware of them.
|
|
||||||
Using the same configuration mechanism we've already seen at the
|
|
||||||
beginning of this section we can indicate a default search path
|
|
||||||
for Type-1 fonts.
|
|
||||||
""")
|
|
||||||
|
|
||||||
disc("""
|
|
||||||
Unfortunately, there is no reliable standard yet for such
|
|
||||||
locations (not even on the same platform) and, hence, you might
|
|
||||||
have to edit the file $reportlab/rl_config.py$ to modify the
|
|
||||||
value of the $T1SearchPath$ identifier to contain additional
|
|
||||||
directories. Our own recommendation is to use the ^reportlab/fonts^
|
|
||||||
folder in development; and to have any needed fonts as packaged parts of
|
|
||||||
your application in any kind of controlled server deployment. This insulates
|
|
||||||
you from fonts being installed and uninstalled by other software or system
|
|
||||||
administrator.
|
|
||||||
""")
|
|
||||||
|
|
||||||
heading3("Warnings about missing glyphs")
|
|
||||||
disc("""If you specify an encoding, it is generally assumed that
|
|
||||||
the font designer has provided all the needed glyphs. However,
|
|
||||||
this is not always true. In the case of our example font,
|
|
||||||
the letters of the alphabet are present, but many symbols and
|
|
||||||
accents are missing. The default behaviour is for the font to
|
|
||||||
print a 'notdef' character - typically a blob, dot or space -
|
|
||||||
when passed a character it cannot draw. However, you can ask
|
|
||||||
the library to warn you instead; the code below (executed
|
|
||||||
before loading a font) will cause warnings to be generated
|
|
||||||
for any glyphs not in the font when you register it.""")
|
|
||||||
|
|
||||||
eg("""
|
|
||||||
import reportlab.rl_config
|
|
||||||
reportlab.rl_config.warnOnMissingFontGlyphs = 0
|
|
||||||
""")
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
heading2("Standard Single-Byte Font Encodings")
|
|
||||||
disc("""
|
|
||||||
This section shows you the glyphs available in the common encodings.
|
|
||||||
""")
|
|
||||||
|
|
||||||
|
|
||||||
disc("""The code chart below shows the characters in the $WinAnsiEncoding$.
|
|
||||||
This is the standard encoding on Windows and many Unix systems in America
|
|
||||||
and Western Europe. It is also knows as Code Page 1252, and is practically
|
|
||||||
identical to ISO-Latin-1 (it contains one or two extra characters). This
|
|
||||||
is the default encoding used by the Reportlab PDF Library. It was generated from
|
|
||||||
a standard routine in $reportlab/lib$, $codecharts.py$,
|
|
||||||
which can be used to display the contents of fonts. The index numbers
|
|
||||||
along the edges are in hex.""")
|
|
||||||
|
|
||||||
cht1 = SingleByteEncodingChart(encodingName='WinAnsiEncoding',charsPerRow=32, boxSize=12)
|
|
||||||
illust(lambda canv: cht1.drawOn(canv, 0, 0), "WinAnsi Encoding", cht1.width, cht1.height)
|
|
||||||
|
|
||||||
disc("""The code chart below shows the characters in the $MacRomanEncoding$.
|
|
||||||
as it sounds, this is the standard encoding on Macintosh computers in
|
|
||||||
America and Western Europe. As usual with non-unicode encodings, the first
|
|
||||||
128 code points (top 4 rows in this case) are the ASCII standard and agree
|
|
||||||
with the WinAnsi code chart above; but the bottom 4 rows differ.""")
|
|
||||||
cht2 = SingleByteEncodingChart(encodingName='MacRomanEncoding',charsPerRow=32, boxSize=12)
|
|
||||||
illust(lambda canv: cht2.drawOn(canv, 0, 0), "MacRoman Encoding", cht2.width, cht2.height)
|
|
||||||
|
|
||||||
disc("""These two encodings are available for the standard fonts (Helvetica,
|
|
||||||
Times-Roman and Courier and their variants) and will be available for most
|
|
||||||
commercial fonts including those from Adobe. However, some fonts contain non-
|
|
||||||
text glyphs and the concept does not really apply. For example, ZapfDingbats
|
|
||||||
and Symbol can each be treated as having their own encoding.""")
|
|
||||||
|
|
||||||
cht3 = SingleByteEncodingChart(faceName='ZapfDingbats',encodingName='ZapfDingbatsEncoding',charsPerRow=32, boxSize=12)
|
|
||||||
illust(lambda canv: cht3.drawOn(canv, 0, 0), "ZapfDingbats and its one and only encoding", cht3.width, cht3.height)
|
|
||||||
|
|
||||||
cht4 = SingleByteEncodingChart(faceName='Symbol',encodingName='SymbolEncoding',charsPerRow=32, boxSize=12)
|
|
||||||
illust(lambda canv: cht4.drawOn(canv, 0, 0), "Symbol and its one and only encoding", cht4.width, cht4.height)
|
|
||||||
|
|
||||||
|
|
||||||
CPage(5)
|
|
||||||
heading2("TrueType Font Support")
|
|
||||||
disc("""
|
|
||||||
Marius Gedminas ($mgedmin@delfi.lt$) with the help of Viktorija Zaksiene ($vika@pov.lt$)
|
|
||||||
have contributed support for embedded TrueType fonts. TrueType fonts work in Unicode/UTF8
|
|
||||||
and are not limited to 256 characters.""")
|
|
||||||
|
|
||||||
|
|
||||||
CPage(3)
|
|
||||||
disc("""We use <b>$reportlab.pdfbase.ttfonts.TTFont$</b> to create a true type
|
|
||||||
font object and register using <b>$reportlab.pdfbase.pdfmetrics.registerFont$</b>.
|
|
||||||
In pdfgen drawing directly to the canvas we can do""")
|
|
||||||
eg("""
|
|
||||||
# we know some glyphs are missing, suppress warnings
|
|
||||||
import reportlab.rl_config
|
|
||||||
reportlab.rl_config.warnOnMissingFontGlyphs = 0
|
|
||||||
|
|
||||||
from reportlab.pdfbase import pdfmetrics
|
|
||||||
from reportlab.pdfbase.ttfonts import TTFont
|
|
||||||
pdfmetrics.registerFont(TTFont('Rina', 'rina.ttf'))
|
|
||||||
canvas.setFont(Rina, 32)
|
|
||||||
canvas.drawString(10, 150, "Some text encoded in UTF-8")
|
|
||||||
canvas.drawString(10, 100, "In the Rina TT Font!")
|
|
||||||
""")
|
|
||||||
illust(examples.ttffont1, "Using a the Rina TrueType Font")
|
|
||||||
disc("""In the above example the true type font object is created using""")
|
|
||||||
eg("""
|
|
||||||
TTFont(name,filename)
|
|
||||||
""")
|
|
||||||
disc("""so that the ReportLab internal name is given by the first argument and the second argument
|
|
||||||
is a string(or file like object) denoting the font's TTF file. In Marius' original patch the filename
|
|
||||||
was supposed to be exactly correct, but we have modified things so that if the filename is relative
|
|
||||||
then a search for the corresponding file is done in the current directory and then in directories
|
|
||||||
specified by $reportlab.rl_config.TTFSearchpath$!""")
|
|
||||||
|
|
||||||
from reportlab.lib.styles import ParagraphStyle
|
|
||||||
|
|
||||||
from reportlab.lib.fonts import addMapping
|
|
||||||
addMapping('Rina', 0, 0, 'Rina')
|
|
||||||
addMapping('Rina', 0, 1, 'Rina')
|
|
||||||
addMapping('Rina', 1, 0, 'Rina')
|
|
||||||
addMapping('Rina', 1, 1, 'Rina')
|
|
||||||
|
|
||||||
disc("""Before using the TT Fonts in Platypus we should add a mapping from the family name to the
|
|
||||||
individual font names that describe the behaviour under the $<b>$ and $<i>$ attributes.""")
|
|
||||||
|
|
||||||
eg("""
|
|
||||||
from reportlab.lib.fonts import addMapping
|
|
||||||
addMapping('Rina', 0, 0, 'Rina') #normal
|
|
||||||
addMapping('Rina', 0, 1, 'Rina') #italic
|
|
||||||
addMapping('Rina', 1, 0, 'Rina') #bold
|
|
||||||
addMapping('Rina', 1, 1, 'Rina') #italic and bold
|
|
||||||
""")
|
|
||||||
|
|
||||||
disc("""We only have a Rina regular font, no bold or italic, so we must map all to the
|
|
||||||
same internal fontname. ^<b>^ and ^<i>^ tags may now be used safely, but
|
|
||||||
have no effect.
|
|
||||||
After registering and mapping
|
|
||||||
the Rina font as above we can use paragraph text like""")
|
|
||||||
parabox2("""<font name="Times-Roman" size="14">This is in Times-Roman</font>
|
|
||||||
<font name="Rina" color="magenta" size="14">and this is in magenta <b>Rina!</b></font>""","Using TTF fonts in paragraphs")
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
heading2("Asian Font Support")
|
|
||||||
disc("""The Reportlab PDF Library aims to expose full support for Asian fonts.
|
|
||||||
PDF is the first really portable solution for Asian text handling. There are
|
|
||||||
two main approaches for this: Adobe's Asian Language Packs, or TrueType fonts.
|
|
||||||
""")
|
|
||||||
|
|
||||||
heading3("Asian Language Packs")
|
|
||||||
disc("""
|
|
||||||
This approach offers the best performance since nothing needs embedding in the PDF file;
|
|
||||||
as with the standard fonts, everything is on the reader.""")
|
|
||||||
|
|
||||||
disc("""
|
|
||||||
Adobe makes available add-ons for each main language. In Adobe Reader 6.0 and 7.0, you
|
|
||||||
will be prompted to download and install these as soon as you try to open a document
|
|
||||||
using them. In earlier versions, you would see an error message on opening an Asian document
|
|
||||||
and had to know what to do.
|
|
||||||
""")
|
|
||||||
|
|
||||||
disc("""
|
|
||||||
Japanese, Traditional Chinese (Taiwan/Hong Kong), Simplified Chinese (mainland China)
|
|
||||||
and Korean are all supported and our software knows about the following fonts:
|
|
||||||
""")
|
|
||||||
bullet("""
|
|
||||||
$chs$ = Chinese Simplified (mainland): '$STSong-Light$'
|
|
||||||
""")
|
|
||||||
bullet("""
|
|
||||||
$cht$ = Chinese Traditional (Taiwan): '$MSung-Light$', '$MHei-Medium$'
|
|
||||||
""")
|
|
||||||
bullet("""
|
|
||||||
$kor$ = Korean: '$HYSMyeongJoStd-Medium$','$HYGothic-Medium$'
|
|
||||||
""")
|
|
||||||
bullet("""
|
|
||||||
$jpn$ = Japanese: '$HeiseiMin-W3$', '$HeiseiKakuGo-W5$'
|
|
||||||
""")
|
|
||||||
|
|
||||||
|
|
||||||
disc("""Since many users will not have the font packs installed, we have included
|
|
||||||
a rather grainy ^bitmap^ of some Japanese characters. We will discuss below what is needed to
|
|
||||||
generate them.""")
|
|
||||||
# include a bitmap of some Asian text
|
|
||||||
I=os.path.join(os.path.dirname(reportlab.__file__),'docs','images','jpnchars.jpg')
|
|
||||||
try:
|
|
||||||
getStory().append(Image(I))
|
|
||||||
except:
|
|
||||||
disc("""An image should have appeared here.""")
|
|
||||||
|
|
||||||
disc("""Prior to Version 2.0, you had to specify one of many native encodings
|
|
||||||
when registering a CID Font. In version 2.0 you should a new UnicodeCIDFont
|
|
||||||
class.""")
|
|
||||||
|
|
||||||
eg("""
|
|
||||||
from reportlab.pdfbase import pdfmetrics
|
|
||||||
from reportlab.pdfbase.cidfonts import UnicodeCIDFont
|
|
||||||
pdfmetrics.registerFont(UnicodeCIDFont('HeiseiMin-W3'))
|
|
||||||
canvas.setFont('HeiseiMin-W3', 16)
|
|
||||||
|
|
||||||
# the two unicode characters below are "Tokyo"
|
|
||||||
msg = u'\u6771\u4EAC : Unicode font, unicode input'
|
|
||||||
canvas.drawString(100, 675, msg)
|
|
||||||
""")
|
|
||||||
#had to double-escape the slashes above to get escapes into the PDF
|
|
||||||
|
|
||||||
disc("""The old coding style with explicit encodings should still work, but is now
|
|
||||||
only relevant if you need to construct vertical text. We aim to add more readable options
|
|
||||||
for horizontal and vertical text to the UnicodeCIDFont constructor in future.
|
|
||||||
The following four test scripts generate samples in the corresponding languages:""")
|
|
||||||
eg("""reportlab/test/test_multibyte_jpn.py
|
|
||||||
reportlab/test/test_multibyte_kor.py
|
|
||||||
reportlab/test/test_multibyte_chs.py
|
|
||||||
reportlab/test/test_multibyte_cht.py""")
|
|
||||||
|
|
||||||
## put back in when we have vertical text...
|
|
||||||
##disc("""The illustration below shows part of the first page
|
|
||||||
##of the Japanese output sample. It shows both horizontal and vertical
|
|
||||||
##writing, and illustrates the ability to mix variable-width Latin
|
|
||||||
##characters in Asian sentences. The choice of horizontal and vertical
|
|
||||||
##writing is determined by the encoding, which ends in 'H' or 'V'.
|
|
||||||
##Whether an encoding uses fixed-width or variable-width versions
|
|
||||||
##of Latin characters also depends on the encoding used; see the definitions
|
|
||||||
##below.""")
|
|
||||||
##
|
|
||||||
##Illustration(image("../images/jpn.gif", width=531*0.50,
|
|
||||||
##height=435*0.50), 'Output from test_multibyte_jpn.py')
|
|
||||||
##
|
|
||||||
##caption("""
|
|
||||||
##Output from test_multibyte_jpn.py
|
|
||||||
##""")
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
disc("""In previous versions of the ReportLab PDF Library, we had to make
|
|
||||||
use of Adobe's CMap files (located near Acrobat Reader if the Asian Language
|
|
||||||
packs were installed). Now that we only have one encoding to deal with, the
|
|
||||||
character width data is embedded in the package, and CMap files are not needed
|
|
||||||
for generation. The CMap search path in ^rl_config.py^ is now deprecated
|
|
||||||
and has no effect if you restrict yourself to UnicodeCIDFont.
|
|
||||||
""")
|
|
||||||
|
|
||||||
|
|
||||||
heading3("TrueType fonts with Asian characters")
|
|
||||||
disc("""
|
|
||||||
This is the easy way to do it. No special handling at all is needed to
|
|
||||||
work with Asian TrueType fonts. Windows users who have installed, for example,
|
|
||||||
Japanese as an option in Control Panel, will have a font "msmincho.ttf" which
|
|
||||||
can be used. However, be aware that it takes time to parse the fonts, and that
|
|
||||||
quite large subsets may need to be embedded in your PDFs. We can also now parse
|
|
||||||
files ending in .ttc, which are a slight variation of .ttf.
|
|
||||||
|
|
||||||
""")
|
|
||||||
|
|
||||||
|
|
||||||
heading3("To Do")
|
|
||||||
disc("""We expect to be developing this area of the package for some time.accept2dyear
|
|
||||||
Here is an outline of the main priorities. We welcome help!""")
|
|
||||||
|
|
||||||
bullet("""
|
|
||||||
Ensure that we have accurate character metrics for all encodings in horizontal and
|
|
||||||
vertical writing.""")
|
|
||||||
|
|
||||||
bullet("""
|
|
||||||
Add options to ^UnicodeCIDFont^ to allow vertical and proportional variants where the font permits it.""")
|
|
||||||
|
|
||||||
|
|
||||||
bullet("""
|
|
||||||
Improve the word wrapping code in paragraphs and allow vertical writing.""")
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
CPage(5)
|
|
||||||
heading2("RenderPM tests")
|
|
||||||
|
|
||||||
disc("""This may also be the best place to mention the test function of $reportlab/graphics/renderPM.py$,
|
|
||||||
which can be considered the cannonical place for tests which exercise renderPM (the "PixMap Renderer",
|
|
||||||
as opposed to renderPDF, renderPS or renderSVG).""")
|
|
||||||
|
|
||||||
disc("""If you run this from the command line, you should see lots of output like the following.""")
|
|
||||||
|
|
||||||
eg("""C:\\code\\reportlab\\graphics>renderPM.py
|
|
||||||
wrote pmout\\renderPM0.gif
|
|
||||||
wrote pmout\\renderPM0.tif
|
|
||||||
wrote pmout\\renderPM0.png
|
|
||||||
wrote pmout\\renderPM0.jpg
|
|
||||||
wrote pmout\\renderPM0.pct
|
|
||||||
...
|
|
||||||
wrote pmout\\renderPM12.gif
|
|
||||||
wrote pmout\\renderPM12.tif
|
|
||||||
wrote pmout\\renderPM12.png
|
|
||||||
wrote pmout\\renderPM12.jpg
|
|
||||||
wrote pmout\\renderPM12.pct
|
|
||||||
wrote pmout\\index.html""")
|
|
||||||
|
|
||||||
disc("""This runs a number of tests progressing from a "Hello World" test, through various tests of
|
|
||||||
Lines; text strings in a number of sizes, fonts, colours and alignments; the basic shapes; translated
|
|
||||||
and rotated groups; scaled coordinates; rotated strings; nested groups; anchoring and non-standard fonts.""")
|
|
||||||
|
|
||||||
disc("""It creates a subdirectory called $pmout$, writes the image files into it, and writes an
|
|
||||||
$index.html$ page which makes it easy to refer to all the results.""")
|
|
||||||
|
|
||||||
disc("""The font-related tests which you may wish to look at are test #11 ('Text strings in a non-standard font')
|
|
||||||
and test #12 ('Test Various Fonts').""")
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
##### FILL THEM IN
|
|
|
@ -1,271 +0,0 @@
|
||||||
#Copyright ReportLab Europe Ltd. 2000-2004
|
|
||||||
#see license.txt for license details
|
|
||||||
#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/docs/userguide/ch3_pdffeatures.py
|
|
||||||
from reportlab.tools.docco.rl_doc_utils import *
|
|
||||||
|
|
||||||
|
|
||||||
heading1("Exposing PDF Special Capabilities")
|
|
||||||
disc("""PDF provides a number of features to make electronic
|
|
||||||
document viewing more efficient and comfortable, and
|
|
||||||
our library exposes a number of these.""")
|
|
||||||
|
|
||||||
heading2("Forms")
|
|
||||||
disc("""The Form feature lets you create a block of graphics and text
|
|
||||||
once near the start of a PDF file, and then simply refer to it on
|
|
||||||
subsequent pages. If you are dealing with a run of 5000 repetitive
|
|
||||||
business forms - for example, one-page invoices or payslips - you
|
|
||||||
only need to store the backdrop once and simply draw the changing
|
|
||||||
text on each page. Used correctly, forms can dramatically cut
|
|
||||||
file size and production time, and apparently even speed things
|
|
||||||
up on the printer.
|
|
||||||
""")
|
|
||||||
disc("""Forms do not need to refer to a whole page; anything which
|
|
||||||
might be repeated often should be placed in a form.""")
|
|
||||||
disc("""The example below shows the basic sequence used. A real
|
|
||||||
program would probably define the forms up front and refer to
|
|
||||||
them from another location.""")
|
|
||||||
|
|
||||||
|
|
||||||
eg(examples.testforms)
|
|
||||||
|
|
||||||
heading2("Links and Destinations")
|
|
||||||
disc("""PDF supports internal hyperlinks. There is a very wide
|
|
||||||
range of link types, destination types and events which
|
|
||||||
can be triggered by a click. At the moment we just
|
|
||||||
support the basic ability to jump from one part of a document
|
|
||||||
to another, and to control the zoom level of the window after
|
|
||||||
the jump. The bookmarkPage method defines a destination that
|
|
||||||
is the endpoint of a jump.""")
|
|
||||||
#todo("code example here...")
|
|
||||||
|
|
||||||
eg("""
|
|
||||||
canvas.bookmarkPage(name,
|
|
||||||
fitType="Fit",
|
|
||||||
left=None,
|
|
||||||
top=None,
|
|
||||||
bottom=None,
|
|
||||||
right=None,
|
|
||||||
zoom=None
|
|
||||||
)
|
|
||||||
""")
|
|
||||||
disc("""
|
|
||||||
By default the $bookmarkPage$ method defines the page itself as the
|
|
||||||
destination. After jumping to an endpoint defined by bookmarkPage,
|
|
||||||
the PDF browser will display the whole page, scaling it to fit the
|
|
||||||
screen:""")
|
|
||||||
|
|
||||||
eg("""canvas.bookmarkPage(name)""")
|
|
||||||
|
|
||||||
disc("""The $bookmarkPage$ method can be instructed to display the
|
|
||||||
page in a number of different ways by providing a $fitType$
|
|
||||||
parameter.""")
|
|
||||||
|
|
||||||
eg("")
|
|
||||||
|
|
||||||
t = Table([
|
|
||||||
['fitType','Parameters Required','Meaning'],
|
|
||||||
['Fit',None,'Entire page fits in window (the default)'],
|
|
||||||
['FitH','top','Top coord at top of window, width scaled to fit'],
|
|
||||||
['FitV','left','Left coord at left of window, height scaled to fit'],
|
|
||||||
['FitR','left bottom right top','Scale window to fit the specified rectangle'],
|
|
||||||
['XYZ','left top zoom','Fine grained control. If you omit a parameter\nthe PDF browser interprets it as "leave as is"']
|
|
||||||
])
|
|
||||||
t.setStyle(TableStyle([
|
|
||||||
('FONT',(0,0),(-1,1),'Times-Bold',10,12),
|
|
||||||
('VALIGN',(0,0),(-1,-1),'MIDDLE'),
|
|
||||||
('INNERGRID', (0,0), (-1,-1), 0.25, colors.black),
|
|
||||||
('BOX', (0,0), (-1,-1), 0.25, colors.black),
|
|
||||||
]))
|
|
||||||
|
|
||||||
getStory().append(t)
|
|
||||||
caption("""Table <seq template="%(Chapter)s-%(Table+)s"/> - Required attributes for different fit types""")
|
|
||||||
|
|
||||||
disc("""
|
|
||||||
Note : $fitType$ settings are case-sensitive so $fitType="FIT"$ is invalid$
|
|
||||||
""")
|
|
||||||
|
|
||||||
|
|
||||||
disc("""
|
|
||||||
Sometimes you want the destination of a jump to be some part of a page.
|
|
||||||
The $FitR$ fitType allows you to identify a particular rectangle, scaling
|
|
||||||
the area to fit the entire page.
|
|
||||||
""")
|
|
||||||
|
|
||||||
disc("""
|
|
||||||
To set the display to a particular x and y coordinate of the page and to
|
|
||||||
control the zoom directly use fitType="XYZ".
|
|
||||||
""")
|
|
||||||
|
|
||||||
eg("""
|
|
||||||
canvas.bookmarkPage('my_bookmark',fitType="XYZ",left=0,top=200)
|
|
||||||
""")
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
disc("""
|
|
||||||
This destination is at the leftmost of the page with the top of the screen
|
|
||||||
at position 200. Because $zoom$ was not set the zoom remains at whatever the
|
|
||||||
user had it set to.
|
|
||||||
""")
|
|
||||||
|
|
||||||
eg("""
|
|
||||||
canvas.bookmarkPage('my_bookmark',fitType="XYZ",left=0,top=200,zoom=2)
|
|
||||||
""")
|
|
||||||
|
|
||||||
disc("""This time zoom is set to expand the page 2X its normal size.""")
|
|
||||||
|
|
||||||
disc("""
|
|
||||||
Note : Both $XYZ$ and $FitR$ fitTypes require that their positional parameters
|
|
||||||
($top, bottom, left, right$) be specified in terms of the default user space.
|
|
||||||
They ignore any geometric transform in effect in the canvas graphic state.
|
|
||||||
""")
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
pencilnote()
|
|
||||||
|
|
||||||
disc("""
|
|
||||||
<i>Note:</i> Two previous bookmark methods are supported but deprecated now
|
|
||||||
that bookmarkPage is so general. These are $bookmarkHorizontalAbsolute$
|
|
||||||
and $bookmarkHorizontal$.
|
|
||||||
""")
|
|
||||||
|
|
||||||
heading3("Defining internal links")
|
|
||||||
eg("""
|
|
||||||
canvas.linkAbsolute(contents, destinationname, Rect=None, addtopage=1, name=None, **kw)
|
|
||||||
""")
|
|
||||||
|
|
||||||
disc("""
|
|
||||||
The $linkAbsolute$ method defines a starting point for a jump. When the user
|
|
||||||
is browsing the generated document using a dynamic viewer (such as Acrobat Reader)
|
|
||||||
when the mouse is clicked when the pointer is within the rectangle specified
|
|
||||||
by $Rect$ the viewer will jump to the endpoint associated with $destinationname$.
|
|
||||||
As in the case with $bookmarkHorizontalAbsolute$ the rectangle $Rect$ must be
|
|
||||||
specified in terms of the default user space. The $contents$ parameter specifies
|
|
||||||
a chunk of text which displays in the viewer if the user left-clicks on the region.
|
|
||||||
""")
|
|
||||||
|
|
||||||
disc("""
|
|
||||||
The rectangle $Rect$ must be specified in terms of a tuple ^(x1,y1,x2,y2)^ identifying
|
|
||||||
the lower left and upper right points of the rectangle in default user space.
|
|
||||||
""")
|
|
||||||
|
|
||||||
disc("""
|
|
||||||
For example the code
|
|
||||||
""")
|
|
||||||
|
|
||||||
eg("""
|
|
||||||
canvas.bookmarkPage("Meaning_of_life")
|
|
||||||
""")
|
|
||||||
|
|
||||||
disc("""
|
|
||||||
defines a location as the whole of the current page with the identifier
|
|
||||||
$Meaning_of_life$. To create a rectangular link to it while drawing a possibly
|
|
||||||
different page, we would use this code:
|
|
||||||
""")
|
|
||||||
|
|
||||||
eg("""
|
|
||||||
canvas.linkAbsolute("Find the Meaning of Life", "Meaning_of_life",
|
|
||||||
(inch, inch, 6*inch, 2*inch))
|
|
||||||
""")
|
|
||||||
|
|
||||||
disc("""
|
|
||||||
By default during interactive viewing a rectangle appears around the
|
|
||||||
link. Use the keyword argument $Border='[0 0 0]'$ to
|
|
||||||
suppress the visible rectangle around the during viewing link.
|
|
||||||
For example
|
|
||||||
""")
|
|
||||||
|
|
||||||
eg("""
|
|
||||||
canvas.linkAbsolute("Meaning of Life", "Meaning_of_life",
|
|
||||||
(inch, inch, 6*inch, 2*inch), Border='[0 0 0]')
|
|
||||||
""")
|
|
||||||
|
|
||||||
|
|
||||||
heading2("Outline Trees")
|
|
||||||
disc("""Acrobat Reader has a navigation page which can hold a
|
|
||||||
document outline; it should normally be visible when you
|
|
||||||
open this guide. We provide some simple methods to add
|
|
||||||
outline entries. Typically, a program to make a document
|
|
||||||
(such as this user guide) will call the method
|
|
||||||
$canvas.addOutlineEntry(^self, title, key, level=0,
|
|
||||||
closed=None^)$ as it reaches each heading in the document.
|
|
||||||
""")
|
|
||||||
|
|
||||||
disc("""^title^ is the caption which will be displayed in
|
|
||||||
the left pane. The ^key^ must be a string which is
|
|
||||||
unique within the document and which names a bookmark,
|
|
||||||
as with the hyperlinks. The ^level^ is zero - the
|
|
||||||
uppermost level - unless otherwise specified, and
|
|
||||||
it is an error to go down more than one level at a time
|
|
||||||
(for example to follow a level 0 heading by a level 2
|
|
||||||
heading). Finally, the ^closed^ argument specifies
|
|
||||||
whether the node in the outline pane is closed
|
|
||||||
or opened by default.""")
|
|
||||||
|
|
||||||
disc("""The snippet below is taken from the document template
|
|
||||||
that formats this user guide. A central processor looks
|
|
||||||
at each paragraph in turn, and makes a new outline entry
|
|
||||||
when a new chapter occurs, taking the chapter heading text
|
|
||||||
as the caption text. The key is obtained from the
|
|
||||||
chapter number (not shown here), so Chapter 2 has the
|
|
||||||
key 'ch2'. The bookmark to which the
|
|
||||||
outline entry points aims at the whole page, but it could
|
|
||||||
as easily have been an individual paragraph.
|
|
||||||
""")
|
|
||||||
|
|
||||||
eg("""
|
|
||||||
#abridged code from our document template
|
|
||||||
if paragraph.style == 'Heading1':
|
|
||||||
self.chapter = paragraph.getPlainText()
|
|
||||||
key = 'ch%d' % self.chapterNo
|
|
||||||
self.canv.bookmarkPage(key)
|
|
||||||
self.canv.addOutlineEntry(paragraph.getPlainText(),
|
|
||||||
key, 0, 0)
|
|
||||||
""")
|
|
||||||
|
|
||||||
heading2("Page Transition Effects")
|
|
||||||
|
|
||||||
|
|
||||||
eg("""
|
|
||||||
canvas.setPageTransition(self, effectname=None, duration=1,
|
|
||||||
direction=0,dimension='H',motion='I')
|
|
||||||
""")
|
|
||||||
|
|
||||||
disc("""
|
|
||||||
The $setPageTransition$ method specifies how one page will be replaced with
|
|
||||||
the next. By setting the page transition effect to "dissolve" for example
|
|
||||||
the current page will appear to melt away when it is replaced by the next
|
|
||||||
page during interactive viewing. These effects are useful in spicing up
|
|
||||||
slide presentations, among other places.
|
|
||||||
Please see the reference manual for more detail on how to use this method.
|
|
||||||
""")
|
|
||||||
|
|
||||||
heading2("Internal File Annotations")
|
|
||||||
|
|
||||||
eg("""
|
|
||||||
canvas.setAuthor(name)
|
|
||||||
canvas.setTitle(title)
|
|
||||||
canvas.setSubject(subj)
|
|
||||||
""")
|
|
||||||
|
|
||||||
disc("""
|
|
||||||
These methods have no automatically seen visible effect on the document.
|
|
||||||
They add internal annotations to the document. These annotations can be
|
|
||||||
viewed using the "Document Info" menu item of the browser and they also can
|
|
||||||
be used as a simple standard way of providing basic information about the
|
|
||||||
document to archiving software which need not parse the entire
|
|
||||||
file. To find the annotations view the $*.pdf$ output file using a standard
|
|
||||||
text editor (such as $notepad$ on MS/Windows or $vi$ or $emacs$ on unix) and look
|
|
||||||
for the string $/Author$ in the file contents.
|
|
||||||
""")
|
|
||||||
|
|
||||||
eg(examples.testannotations)
|
|
||||||
|
|
||||||
disc("""
|
|
||||||
If you want the subject, title, and author to automatically display
|
|
||||||
in the document when viewed and printed you must paint them onto the
|
|
||||||
document like any other text.
|
|
||||||
""")
|
|
||||||
|
|
||||||
illust(examples.annotations, "Setting document internal annotations")
|
|
|
@ -1,509 +0,0 @@
|
||||||
#Copyright ReportLab Europe Ltd. 2000-2004
|
|
||||||
#see license.txt for license details
|
|
||||||
#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/docs/userguide/ch4_platypus_concepts.py
|
|
||||||
from reportlab.tools.docco.rl_doc_utils import *
|
|
||||||
|
|
||||||
#####################################################################################################3
|
|
||||||
|
|
||||||
|
|
||||||
heading1("PLATYPUS - Page Layout and Typography Using Scripts")
|
|
||||||
|
|
||||||
heading2("Design Goals")
|
|
||||||
|
|
||||||
disc("""
|
|
||||||
Platypus stands for "Page Layout and Typography Using Scripts". It is a high
|
|
||||||
level page layout library which lets you programmatically create complex
|
|
||||||
documents with a minimum of effort.
|
|
||||||
""")
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
disc("""
|
|
||||||
The design of Platypus seeks to separate "high level" layout decisions
|
|
||||||
from the document content as much as possible. Thus, for example, paragraphs
|
|
||||||
are constructed using paragraph styles and pages are constructed
|
|
||||||
using page templates with the intention that hundreds of
|
|
||||||
documents with thousands of pages can be reformatted to different
|
|
||||||
style specifications with the modifications of a few lines in a single
|
|
||||||
shared file which contains the paragraph styles and page layout specifications.
|
|
||||||
""")
|
|
||||||
|
|
||||||
|
|
||||||
disc("""
|
|
||||||
The overall design of Platypus can be thought of has having
|
|
||||||
several layers, top down, these are""")
|
|
||||||
|
|
||||||
disc("<b>$DocTemplates$</b> the outermost container for the document;")
|
|
||||||
|
|
||||||
disc("<b>$PageTemplates$</b> specifications for layouts of pages of various kinds;")
|
|
||||||
|
|
||||||
disc("<b>$Frames$</b> specifications of regions in pages that can contain flowing text or graphics.")
|
|
||||||
|
|
||||||
disc("""<b>$Flowables$</b> text or graphic elements that should be "flowed
|
|
||||||
into the document (i.e. things like images, paragraphs and tables, but not things
|
|
||||||
like page footers or fixed page graphics).""")
|
|
||||||
|
|
||||||
disc("""<b>$pdfgen.Canvas$</b> the lowest level which ultimately receives the painting of the
|
|
||||||
document from the other layers.""")
|
|
||||||
|
|
||||||
illust(examples.doctemplateillustration, "Illustration of DocTemplate structure")
|
|
||||||
|
|
||||||
disc("""
|
|
||||||
The illustration above graphically illustrates the concepts of $DocTemplates$,
|
|
||||||
$PageTemplates$ and $Flowables$. It is deceptive, however, because each
|
|
||||||
of the $PageTemplates$ actually may specify the format for any number of pages
|
|
||||||
(not just one as might be inferred from the diagram).
|
|
||||||
""")
|
|
||||||
|
|
||||||
disc("""
|
|
||||||
$DocTemplates$ contain one or more $PageTemplates$ each of which contain one or more
|
|
||||||
$Frames$. $Flowables$ are things which can be <i>flowed</i> into a $Frame$ e.g.
|
|
||||||
a $Paragraph$ or a $Table$.
|
|
||||||
""")
|
|
||||||
|
|
||||||
disc("""
|
|
||||||
To use platypus you create a document from a $DocTemplate$ class and pass
|
|
||||||
a list of $Flowable$s to its $build$ method. The document
|
|
||||||
$build$ method knows how to process the list of flowables
|
|
||||||
into something reasonable.
|
|
||||||
""")
|
|
||||||
|
|
||||||
disc("""
|
|
||||||
Internally the $DocTemplate$ class implements page layout and formatting
|
|
||||||
using various events. Each of the events has a corresponding handler method
|
|
||||||
called $handle_XXX$ where $XXX$ is the event name. A typical event is
|
|
||||||
$frameBegin$ which occurs when the machinery begins to use a frame for the
|
|
||||||
first time.
|
|
||||||
""")
|
|
||||||
|
|
||||||
disc("""
|
|
||||||
A Platypus story consists of a sequence of basic elements called $Flowables$
|
|
||||||
and these elements drive the data driven Platypus formatting engine.
|
|
||||||
To modify the behavior of the engine
|
|
||||||
a special kind of flowable, $ActionFlowables$, tell the layout engine to,
|
|
||||||
for example, skip to the next
|
|
||||||
column or change to another $PageTemplate$.
|
|
||||||
""")
|
|
||||||
|
|
||||||
|
|
||||||
heading2("""Getting started""")
|
|
||||||
|
|
||||||
disc("""Consider the following code sequence which provides
|
|
||||||
a very simple "hello world" example for Platypus.""")
|
|
||||||
|
|
||||||
eg(examples.platypussetup)
|
|
||||||
|
|
||||||
disc("""First we import some constructors, some paragraph styles
|
|
||||||
and other conveniences from other modules.""")
|
|
||||||
|
|
||||||
eg(examples.platypusfirstpage)
|
|
||||||
|
|
||||||
disc("""We define the fixed features of the first page of the document
|
|
||||||
with the function above.""")
|
|
||||||
|
|
||||||
eg(examples.platypusnextpage)
|
|
||||||
|
|
||||||
disc("""Since we want pages after the first to look different from the
|
|
||||||
first we define an alternate layout for the fixed features
|
|
||||||
of the other pages. Note that the two functions above use
|
|
||||||
the $pdfgen$ level canvas operations to paint the annotations for
|
|
||||||
the pages.
|
|
||||||
""")
|
|
||||||
|
|
||||||
eg(examples.platypusgo)
|
|
||||||
|
|
||||||
disc("""
|
|
||||||
Finally, we create a story and build the document.
|
|
||||||
Note that we are using a "canned" document template here which
|
|
||||||
comes pre-built with page templates. We are also using a pre-built
|
|
||||||
paragraph style. We are only using two types of flowables here
|
|
||||||
-- $Spacers$ and $Paragraphs$. The first $Spacer$ ensures that the
|
|
||||||
Paragraphs skip past the title string.
|
|
||||||
""")
|
|
||||||
|
|
||||||
disc("""
|
|
||||||
To see the output of this example program run the module
|
|
||||||
$docs/userguide/examples.py$ (from the ReportLab $docs$ distribution)
|
|
||||||
as a "top level script". The script interpretation $python examples.py$ will
|
|
||||||
generate the Platypus output $phello.pdf$.
|
|
||||||
""")
|
|
||||||
|
|
||||||
|
|
||||||
heading2("$Flowables$")
|
|
||||||
disc("""
|
|
||||||
$Flowables$ are things which can be drawn and which have $wrap$, $draw$ and perhaps $split$ methods.
|
|
||||||
$Flowable$ is an abstract base class for things to be drawn and an instance knows its size
|
|
||||||
and draws in its own coordinate system (this requires the base API to provide an absolute coordinate
|
|
||||||
system when the $Flowable.draw$ method is called). To get an instance use $f=Flowable()$.
|
|
||||||
""")
|
|
||||||
disc("""
|
|
||||||
It should be noted that the $Flowable$ class is an <i>abstract</i> class and is normally
|
|
||||||
only used as a base class.
|
|
||||||
""")
|
|
||||||
k=startKeep()
|
|
||||||
disc("""
|
|
||||||
To illustrate the general way in which $Flowables$ are used we show how a derived class $Paragraph$
|
|
||||||
is used and drawn on a canvas. $Paragraphs$ are so important they will get a whole chapter
|
|
||||||
to themselves.
|
|
||||||
""")
|
|
||||||
eg("""
|
|
||||||
from reportlab.lib.styles import getSampleStyleSheet
|
|
||||||
from reportlab.platypus import Paragraph
|
|
||||||
from reportlab.pdfgen.canvas import Canvas
|
|
||||||
styleSheet = getSampleStyleSheet()
|
|
||||||
style = styleSheet['BodyText']
|
|
||||||
P=Paragraph('This is a very silly example',style)
|
|
||||||
canv = Canvas('doc.pdf')
|
|
||||||
aW = 460 # available width and height
|
|
||||||
aH = 800
|
|
||||||
w,h = P.wrap(aW, aH) # find required space
|
|
||||||
if w<=aW and h<=aH:
|
|
||||||
P.drawOn(canv,0,aH)
|
|
||||||
aH = aH - h # reduce the available height
|
|
||||||
canv.save()
|
|
||||||
else:
|
|
||||||
raise ValueError, "Not enough room"
|
|
||||||
""")
|
|
||||||
endKeep(k)
|
|
||||||
heading3("$Flowable$ User Methods")
|
|
||||||
eg("""
|
|
||||||
Flowable.draw()
|
|
||||||
""")
|
|
||||||
disc("""This will be called to ask the flowable to actually render itself.
|
|
||||||
The $Flowable$ class does not implement $draw$.
|
|
||||||
The calling code should ensure that the flowable has an attribute $canv$
|
|
||||||
which is the $pdfgen.Canvas$ which should be drawn to an that the $Canvas$
|
|
||||||
is in an appropriate state (as regards translations rotations, etc). Normally
|
|
||||||
this method will only be called internally by the $drawOn$ method. Derived classes
|
|
||||||
must implement this method.
|
|
||||||
""")
|
|
||||||
eg("""
|
|
||||||
Flowable.drawOn(canvas,x,y)
|
|
||||||
""")
|
|
||||||
disc("""
|
|
||||||
This is the method which controlling programs use to render the flowable to a particular
|
|
||||||
canvas. It handles the translation to the canvas coordinate (<i>x</i>,<i>y</i>) and ensuring that
|
|
||||||
the flowable has a $canv$ attribute so that the
|
|
||||||
$draw$ method (which is not implemented in the base class) can render in an
|
|
||||||
absolute coordinate frame.
|
|
||||||
""")
|
|
||||||
eg("""
|
|
||||||
Flowable.wrap(availWidth, availHeight)
|
|
||||||
""")
|
|
||||||
disc("""This will be called by the enclosing frame before objects
|
|
||||||
are asked their size, drawn or whatever. It returns the
|
|
||||||
size actually used.""")
|
|
||||||
eg("""
|
|
||||||
Flowable.split(self, availWidth, availheight):
|
|
||||||
""")
|
|
||||||
disc("""This will be called by more sophisticated frames when
|
|
||||||
wrap fails. Stupid flowables should return [] meaning that they are unable to split.
|
|
||||||
Clever flowables should split themselves and return a list of flowables. It is up to
|
|
||||||
the client code to ensure that repeated attempts to split are avoided.
|
|
||||||
If the space is sufficient the split method should return [self].
|
|
||||||
Otherwise
|
|
||||||
the flowable should rearrange itself and return a list $[f0,...]$ of flowables
|
|
||||||
which will be considered in order. The implemented split method should avoid
|
|
||||||
changing $self$ as this will allow sophisticated layout mechanisms to do multiple
|
|
||||||
passes over a list of flowables.
|
|
||||||
""")
|
|
||||||
|
|
||||||
heading2("Guidelines for flowable positioning")
|
|
||||||
|
|
||||||
disc("""Two methods, which by default return zero, provide guidance on vertical
|
|
||||||
spacing of flowables:
|
|
||||||
""")
|
|
||||||
|
|
||||||
eg("""
|
|
||||||
Flowable.getSpaceAfter(self):
|
|
||||||
Flowable.getSpaceBefore(self):
|
|
||||||
""")
|
|
||||||
disc("""These methods return how much space should follow or precede
|
|
||||||
the flowable. The space doesn't belong to the flowable itself i.e. the flowable's
|
|
||||||
$draw$ method shouldn't consider it when rendering. Controlling programs
|
|
||||||
will use the values returned in determining how much space is required by
|
|
||||||
a particular flowable in context.
|
|
||||||
""")
|
|
||||||
|
|
||||||
disc("""All flowables have an $hAlign$ property: $('LEFT', 'RIGHT', 'CENTER' or 'CENTRE')$.
|
|
||||||
For paragraphs, which fill the full width of the frame, this has no effect. For tables,
|
|
||||||
images or other objects which are less than the width of the frame, this determines their
|
|
||||||
horizontal placement.
|
|
||||||
|
|
||||||
""")
|
|
||||||
|
|
||||||
|
|
||||||
disc("""The chapters which follow will cover the most important
|
|
||||||
specific types of flowables: Paragraphs and Tables.""")
|
|
||||||
|
|
||||||
|
|
||||||
heading2("Frames")
|
|
||||||
disc("""
|
|
||||||
$Frames$ are active containers which are themselves contained in $PageTemplates$.
|
|
||||||
$Frames$ have a location and size and maintain a concept of remaining drawable
|
|
||||||
space. The command
|
|
||||||
""")
|
|
||||||
|
|
||||||
eg("""
|
|
||||||
Frame(x1, y1, width,height, leftPadding=6, bottomPadding=6,
|
|
||||||
rightPadding=6, topPadding=6, id=None, showBoundary=0)
|
|
||||||
""")
|
|
||||||
disc("""creates a $Frame$ instance with lower left hand corner at coordinate $(x1,y1)$
|
|
||||||
(relative to the canvas at use time) and with dimensions $width$ x $height$. The $Padding$
|
|
||||||
arguments are positive quantities used to reduce the space available for drawing.
|
|
||||||
The $id$ argument is an identifier for use at runtime e.g. 'LeftColumn' or 'RightColumn' etc.
|
|
||||||
If the $showBoundary$ argument is non-zero then the boundary of the frame will get drawn
|
|
||||||
at run time (this is useful sometimes).
|
|
||||||
""")
|
|
||||||
heading3("$Frame$ User Methods")
|
|
||||||
eg("""
|
|
||||||
Frame.addFromList(drawlist, canvas)
|
|
||||||
""")
|
|
||||||
disc("""consumes $Flowables$ from the front of $drawlist$ until the
|
|
||||||
frame is full. If it cannot fit one object, raises
|
|
||||||
an exception.""")
|
|
||||||
|
|
||||||
eg("""
|
|
||||||
Frame.split(flowable,canv)
|
|
||||||
""")
|
|
||||||
disc('''Asks the flowable to split using up the available space and return
|
|
||||||
the list of flowables.
|
|
||||||
''')
|
|
||||||
|
|
||||||
eg("""
|
|
||||||
Frame.drawBoundary(canvas)
|
|
||||||
""")
|
|
||||||
disc("draws the frame boundary as a rectangle (primarily for debugging).")
|
|
||||||
heading3("Using $Frames$")
|
|
||||||
disc("""
|
|
||||||
$Frames$ can be used directly with canvases and flowables to create documents.
|
|
||||||
The $Frame.addFromList$ method handles the $wrap$ & $drawOn$ calls for you.
|
|
||||||
You don't need all of the Platypus machinery to get something useful into
|
|
||||||
PDF.
|
|
||||||
""")
|
|
||||||
eg("""
|
|
||||||
from reportlab.pdfgen.canvas import Canvas
|
|
||||||
from reportlab.lib.styles import getSampleStyleSheet
|
|
||||||
from reportlab.lib.units import inch
|
|
||||||
from reportlab.platypus import Paragraph, Frame
|
|
||||||
styles = getSampleStyleSheet()
|
|
||||||
styleN = styles['Normal']
|
|
||||||
styleH = styles['Heading1']
|
|
||||||
story = []
|
|
||||||
|
|
||||||
#add some flowables
|
|
||||||
story.append(Paragraph("This is a Heading",styleH))
|
|
||||||
story.append(Paragraph("This is a paragraph in <i>Normal</i> style.",
|
|
||||||
styleN))
|
|
||||||
c = Canvas('mydoc.pdf')
|
|
||||||
f = Frame(inch, inch, 6*inch, 9*inch, showBoundary=1)
|
|
||||||
f.addFromList(story,c)
|
|
||||||
c.save()
|
|
||||||
""")
|
|
||||||
|
|
||||||
heading2("Documents and Templates")
|
|
||||||
|
|
||||||
disc("""
|
|
||||||
The $BaseDocTemplate$ class implements the basic machinery for document
|
|
||||||
formatting. An instance of the class contains a list of one or more
|
|
||||||
$PageTemplates$ that can be used to describe the layout of information
|
|
||||||
on a single page. The $build$ method can be used to process
|
|
||||||
a list of $Flowables$ to produce a <b>PDF</b> document.
|
|
||||||
""")
|
|
||||||
|
|
||||||
CPage(3.0)
|
|
||||||
heading3("The $BaseDocTemplate$ class")
|
|
||||||
|
|
||||||
eg("""
|
|
||||||
BaseDocTemplate(self, filename,
|
|
||||||
pagesize=defaultPageSize,
|
|
||||||
pageTemplates=[],
|
|
||||||
showBoundary=0,
|
|
||||||
leftMargin=inch,
|
|
||||||
rightMargin=inch,
|
|
||||||
topMargin=inch,
|
|
||||||
bottomMargin=inch,
|
|
||||||
allowSplitting=1,
|
|
||||||
title=None,
|
|
||||||
author=None,
|
|
||||||
_pageBreakQuick=1)
|
|
||||||
""")
|
|
||||||
|
|
||||||
disc("""
|
|
||||||
creates a document template suitable for creating a basic document. It comes with quite a lot
|
|
||||||
of internal machinery, but no default page templates. The required $filename$ can be a string,
|
|
||||||
the name of a file to receive the created <b>PDF</b> document; alternatively it
|
|
||||||
can be an object which has a $write$ method such as a $StringIO$ or $file$ or $socket$.
|
|
||||||
""")
|
|
||||||
|
|
||||||
disc("""
|
|
||||||
The allowed arguments should be self explanatory, but $showBoundary$ controls whether or
|
|
||||||
not $Frame$ boundaries are drawn which can be useful for debugging purposes. The
|
|
||||||
$allowSplitting$ argument determines whether the builtin methods should try to <i>split</i>
|
|
||||||
individual $Flowables$ across $Frames$. The $_pageBreakQuick$ argument determines whether
|
|
||||||
an attempt to do a page break should try to end all the frames on the page or not, before ending
|
|
||||||
the page.
|
|
||||||
""")
|
|
||||||
|
|
||||||
heading4("User $BaseDocTemplate$ Methods")
|
|
||||||
|
|
||||||
disc("""These are of direct interest to client programmers
|
|
||||||
in that they are normally expected to be used.
|
|
||||||
""")
|
|
||||||
eg("""
|
|
||||||
BaseDocTemplate.addPageTemplates(self,pageTemplates)
|
|
||||||
""")
|
|
||||||
disc("""
|
|
||||||
This method is used to add one or a list of $PageTemplates$ to an existing documents.
|
|
||||||
""")
|
|
||||||
eg("""
|
|
||||||
BaseDocTemplate.build(self, flowables, filename=None, canvasmaker=canvas.Canvas)
|
|
||||||
""")
|
|
||||||
disc("""
|
|
||||||
This is the main method which is of interest to the application
|
|
||||||
programmer. Assuming that the document instance is correctly set up the
|
|
||||||
$build$ method takes the <i>story</i> in the shape of the list of flowables
|
|
||||||
(the $flowables$ argument) and loops through the list forcing the flowables
|
|
||||||
one at a time through the formatting machinery. Effectively this causes
|
|
||||||
the $BaseDocTemplate$ instance to issue calls to the instance $handle_XXX$ methods
|
|
||||||
to process the various events.
|
|
||||||
""")
|
|
||||||
heading4("User Virtual $BaseDocTemplate$ Methods")
|
|
||||||
disc("""
|
|
||||||
These have no semantics at all in the base class. They are intended as pure virtual hooks
|
|
||||||
into the layout machinery. Creators of immediately derived classes can override these
|
|
||||||
without worrying about affecting the properties of the layout engine.
|
|
||||||
""")
|
|
||||||
eg("""
|
|
||||||
BaseDocTemplate.afterInit(self)
|
|
||||||
""")
|
|
||||||
disc("""
|
|
||||||
This is called after initialisation of the base class; a derived class could overide
|
|
||||||
the method to add default $PageTemplates$.
|
|
||||||
""")
|
|
||||||
|
|
||||||
eg("""
|
|
||||||
BaseDocTemplate.afterPage(self)
|
|
||||||
""")
|
|
||||||
disc("""This is called after page processing, and
|
|
||||||
immediately after the afterDrawPage method
|
|
||||||
of the current page template. A derived class could
|
|
||||||
use this to do things which are dependent on information in the page
|
|
||||||
such as the first and last word on the page of a dictionary.
|
|
||||||
""")
|
|
||||||
|
|
||||||
eg("""
|
|
||||||
BaseDocTemplate.beforeDocument(self)
|
|
||||||
""")
|
|
||||||
|
|
||||||
disc("""This is called before any processing is
|
|
||||||
done on the document, but after the processing machinery
|
|
||||||
is ready. It can therefore be used to do things to the instance\'s
|
|
||||||
$pdfgen.canvas$ and the like.
|
|
||||||
""")
|
|
||||||
|
|
||||||
eg("""
|
|
||||||
BaseDocTemplate.beforePage(self)
|
|
||||||
""")
|
|
||||||
|
|
||||||
disc("""This is called at the beginning of page
|
|
||||||
processing, and immediately before the
|
|
||||||
beforeDrawPage method of the current page
|
|
||||||
template. It could be used to reset page specific
|
|
||||||
information holders.""")
|
|
||||||
|
|
||||||
eg("""
|
|
||||||
BaseDocTemplate.filterFlowables(self,flowables)
|
|
||||||
""")
|
|
||||||
|
|
||||||
disc("""This is called to filter flowables at the start of the main handle_flowable method.
|
|
||||||
Upon return if flowables[0] has been set to None it is discarded and the main
|
|
||||||
method returns immediately.
|
|
||||||
""")
|
|
||||||
|
|
||||||
eg("""
|
|
||||||
BaseDocTemplate.afterFlowable(self, flowable)
|
|
||||||
""")
|
|
||||||
|
|
||||||
disc("""Called after a flowable has been rendered. An interested class could use this
|
|
||||||
hook to gather information about what information is present on a particular page or frame.""")
|
|
||||||
|
|
||||||
heading4("$BaseDocTemplate$ Event handler Methods")
|
|
||||||
disc("""
|
|
||||||
These methods constitute the greater part of the layout engine. Programmers shouldn't
|
|
||||||
have to call or override these methods directly unless they are trying to modify the layout engine.
|
|
||||||
Of course, the experienced programmer who wants to intervene at a particular event, $XXX$,
|
|
||||||
which does not correspond to one of the virtual methods can always override and
|
|
||||||
call the base method from the drived class version. We make this easy by providing
|
|
||||||
a base class synonym for each of the handler methods with the same name prefixed by an underscore '_'.
|
|
||||||
""")
|
|
||||||
|
|
||||||
eg("""
|
|
||||||
def handle_pageBegin(self):
|
|
||||||
doStuff()
|
|
||||||
BaseDocTemplate.handle_pageBegin(self)
|
|
||||||
doMoreStuff()
|
|
||||||
|
|
||||||
#using the synonym
|
|
||||||
def handle_pageEnd(self):
|
|
||||||
doStuff()
|
|
||||||
self._handle_pageEnd()
|
|
||||||
doMoreStuff()
|
|
||||||
""")
|
|
||||||
disc("""
|
|
||||||
Here we list the methods only as an indication of the events that are being
|
|
||||||
handled.
|
|
||||||
Interested programmers can take a look at the source.
|
|
||||||
""")
|
|
||||||
eg("""
|
|
||||||
handle_currentFrame(self,fx)
|
|
||||||
handle_documentBegin(self)
|
|
||||||
handle_flowable(self,flowables)
|
|
||||||
handle_frameBegin(self,*args)
|
|
||||||
handle_frameEnd(self)
|
|
||||||
handle_nextFrame(self,fx)
|
|
||||||
handle_nextPageTemplate(self,pt)
|
|
||||||
handle_pageBegin(self)
|
|
||||||
handle_pageBreak(self)
|
|
||||||
handle_pageEnd(self)
|
|
||||||
""")
|
|
||||||
|
|
||||||
disc("""
|
|
||||||
Using document templates can be very easy; $SimpleDoctemplate$ is a class derived from
|
|
||||||
$BaseDocTemplate$ which provides its own $PageTemplate$ and $Frame$ setup.
|
|
||||||
""")
|
|
||||||
|
|
||||||
eg("""
|
|
||||||
from reportlab.lib.styles import getSampleStyleSheet
|
|
||||||
from reportlab.lib.pagesizes import letter
|
|
||||||
from reportlab.platypus import Paragraph, SimpleDocTemplate
|
|
||||||
styles = getSampleStyleSheet()
|
|
||||||
styleN = styles['Normal']
|
|
||||||
styleH = styles['Heading1']
|
|
||||||
story = []
|
|
||||||
|
|
||||||
#add some flowables
|
|
||||||
story.append(Paragraph("This is a Heading",styleH))
|
|
||||||
story.append(Paragraph("This is a paragraph in <i>Normal</i> style.",
|
|
||||||
styleN))
|
|
||||||
doc = SimpleDocTemplate('mydoc.pdf',pagesize = letter)
|
|
||||||
doc.build(story)
|
|
||||||
""")
|
|
||||||
heading3("$PageTemplates$")
|
|
||||||
disc("""
|
|
||||||
The $PageTemplate$ class is a container class with fairly minimal semantics. Each instance
|
|
||||||
contains a list of $Frames$ and has methods which should be called at the start and end
|
|
||||||
of each page.
|
|
||||||
""")
|
|
||||||
eg("PageTemplate(id=None,frames=[],onPage=_doNothing,onPageEnd=_doNothing)")
|
|
||||||
disc("""
|
|
||||||
is used to initialize an instance, the $frames$ argument should be a list of $Frames$
|
|
||||||
whilst the optional $onPage$ and $onPageEnd$ arguments are callables which should have signature
|
|
||||||
$def XXX(canvas,document)$ where $canvas$ and $document$
|
|
||||||
are the canvas and document being drawn. These routines are intended to be used to paint non-flowing (i.e. standard)
|
|
||||||
parts of pages. These attribute functions are exactly parallel to the pure virtual methods
|
|
||||||
$PageTemplate.beforPage$ and $PageTemplate.afterPage$ which have signature
|
|
||||||
$beforPage(self,canvas,document)$. The methods allow class derivation to be used to define
|
|
||||||
standard behaviour, whilst the attributes allow instance changes. The $id$ argument is used at
|
|
||||||
run time to perform $PageTemplate$ switching so $id='FirstPage'$ or $id='TwoColumns'$ are typical.
|
|
||||||
""")
|
|
|
@ -1,319 +0,0 @@
|
||||||
#Copyright ReportLab Europe Ltd. 2000-2004
|
|
||||||
#see license.txt for license details
|
|
||||||
#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/docs/userguide/ch5_paragraphs.py
|
|
||||||
from reportlab.tools.docco.rl_doc_utils import *
|
|
||||||
|
|
||||||
#begin chapter oon paragraphs
|
|
||||||
heading1("Paragraphs")
|
|
||||||
disc("""
|
|
||||||
The $reportlab.platypus.Paragraph$ class is one of the most useful of the Platypus $Flowables$;
|
|
||||||
it can format fairly arbitrary text and provides for inline font style and colour changes using
|
|
||||||
an XML style markup. The overall shape of the formatted text can be justified, right or left ragged
|
|
||||||
or centered. The XML markup can even be used to insert greek characters or to do subscripts.
|
|
||||||
""")
|
|
||||||
disc("""The following text creates an instance of the $Paragraph$ class:""")
|
|
||||||
eg("""Paragraph(text, style, bulletText=None)""")
|
|
||||||
disc("""The $text$ argument contains the text of the
|
|
||||||
paragraph; excess white space is removed from the text at the ends and internally after
|
|
||||||
linefeeds. This allows easy use of indented triple quoted text in <b>Python</b> scripts.
|
|
||||||
The $bulletText$ argument provides the text of a default bullet for the paragraph.
|
|
||||||
The font and other properties for the paragraph text and bullet are set using the style argument.
|
|
||||||
""")
|
|
||||||
disc("""
|
|
||||||
The $style$ argument should be an instance of class $ParagraphStyle$ obtained typically
|
|
||||||
using""")
|
|
||||||
eg("""
|
|
||||||
from reportlab.lib.styles import ParagraphStyle
|
|
||||||
""")
|
|
||||||
disc("""
|
|
||||||
this container class provides for the setting of multiple default paragraph attributes
|
|
||||||
in a structured way. The styles are arranged in a dictionary style object called a $stylesheet$
|
|
||||||
which allows for the styles to be accessed as $stylesheet['BodyText']$. A sample style
|
|
||||||
sheet is provided.
|
|
||||||
""")
|
|
||||||
eg("""
|
|
||||||
from reportlab.lib.styles import getSampleStyleSheet
|
|
||||||
stylesheet=getSampleStyleSheet()
|
|
||||||
normalStyle = stylesheet['Normal']
|
|
||||||
""")
|
|
||||||
disc("""
|
|
||||||
The options which can be set for a $Paragraph$ can be seen from the $ParagraphStyle$ defaults.
|
|
||||||
""")
|
|
||||||
heading4("$class ParagraphStyle$")
|
|
||||||
eg("""
|
|
||||||
class ParagraphStyle(PropertySet):
|
|
||||||
defaults = {
|
|
||||||
'fontName':'Times-Roman',
|
|
||||||
'fontSize':10,
|
|
||||||
'leading':12,
|
|
||||||
'leftIndent':0,
|
|
||||||
'rightIndent':0,
|
|
||||||
'firstLineIndent':0,
|
|
||||||
'alignment':TA_LEFT,
|
|
||||||
'spaceBefore':0,
|
|
||||||
'spaceAfter':0,
|
|
||||||
'bulletFontName':'Times-Roman',
|
|
||||||
'bulletFontSize':10,
|
|
||||||
'bulletIndent':0,
|
|
||||||
'textColor': black
|
|
||||||
}
|
|
||||||
""")
|
|
||||||
|
|
||||||
heading2("Using Paragraph Styles")
|
|
||||||
|
|
||||||
#this will be used in the ParaBox demos.
|
|
||||||
sample = """You are hereby charged that on the 28th day of May, 1970, you did
|
|
||||||
willfully, unlawfully, and with malice of forethought, publish an
|
|
||||||
alleged English-Hungarian phrase book with intent to cause a breach
|
|
||||||
of the peace. How do you plead?"""
|
|
||||||
|
|
||||||
|
|
||||||
disc("""The $Paragraph$ and $ParagraphStyle$ classes together
|
|
||||||
handle most common formatting needs. The following examples
|
|
||||||
draw paragraphs in various styles, and add a bounding box
|
|
||||||
so that you can see exactly what space is taken up.""")
|
|
||||||
|
|
||||||
s1 = ParagraphStyle('Normal')
|
|
||||||
parabox(sample, s1, 'The default $ParagraphStyle$')
|
|
||||||
|
|
||||||
disc("""The two attributes $spaceBefore$ and $spaceAfter$ do what they
|
|
||||||
say, except at the top or bottom of a frame. At the top of a frame,
|
|
||||||
$spaceBefore$ is ignored, and at the bottom, $spaceAfter$ is ignored.
|
|
||||||
This means that you could specify that a 'Heading2' style had two
|
|
||||||
inches of space before when it occurs in mid-page, but will not
|
|
||||||
get acres of whitespace at the top of a page. These two attributes
|
|
||||||
should be thought of as 'requests' to the Frame and are not part
|
|
||||||
of the space occupied by the Paragraph itself.""")
|
|
||||||
|
|
||||||
disc("""The $fontSize$ and $fontName$ tags are obvious, but it is
|
|
||||||
important to set the $leading$. This is the spacing between
|
|
||||||
adjacent lines of text; a good rule of thumb is to make this
|
|
||||||
20% larger than the point size. To get double-spaced text,
|
|
||||||
use a high $leading$.""")
|
|
||||||
|
|
||||||
disc("""The figure below shows space before and after and an
|
|
||||||
increased leading:""")
|
|
||||||
|
|
||||||
parabox(sample,
|
|
||||||
ParagraphStyle('Spaced',
|
|
||||||
spaceBefore=6,
|
|
||||||
spaceAfter=6,
|
|
||||||
leading=16),
|
|
||||||
'Space before and after and increased leading'
|
|
||||||
)
|
|
||||||
|
|
||||||
disc("""The $leftIndent$ and $rightIndent$ attributes do exactly
|
|
||||||
what you would expect; $firstLineIndent$ is added to the $leftIndent$ of the
|
|
||||||
first line. If you want a straight left edge, remember
|
|
||||||
to set $firstLineIndent$ equal to 0.""")
|
|
||||||
|
|
||||||
parabox(sample,
|
|
||||||
ParagraphStyle('indented',
|
|
||||||
firstLineIndent=+24,
|
|
||||||
leftIndent=24,
|
|
||||||
rightIndent=24),
|
|
||||||
'one third inch indents at left and right, two thirds on first line'
|
|
||||||
)
|
|
||||||
|
|
||||||
disc("""Setting $firstLineIndent$ equal to a negative number, $leftIndent$
|
|
||||||
much higher, and using a
|
|
||||||
different font (we'll show you how later!) can give you a
|
|
||||||
definition list:.""")
|
|
||||||
|
|
||||||
parabox('<b><i>Judge Pickles: </i></b>' + sample,
|
|
||||||
ParagraphStyle('dl',
|
|
||||||
leftIndent=36),
|
|
||||||
'Definition Lists'
|
|
||||||
)
|
|
||||||
|
|
||||||
disc("""There are four possible values of $alignment$, defined as
|
|
||||||
constants in the module <i>reportlab.lib.enums</i>. These are
|
|
||||||
TA_LEFT, TA_CENTER or TA_CENTRE, TA_RIGHT and
|
|
||||||
TA_JUSTIFY, with values of 0, 1, 2 and 4 respectively. These
|
|
||||||
do exactly what you would expect.""")
|
|
||||||
|
|
||||||
|
|
||||||
heading2("Paragraph XML Markup Tags")
|
|
||||||
disc("""XML markup can be used to modify or specify the
|
|
||||||
overall paragraph style, and also to specify intra-
|
|
||||||
paragraph markup.""")
|
|
||||||
|
|
||||||
heading3("The outermost < para > tag")
|
|
||||||
|
|
||||||
|
|
||||||
disc("""
|
|
||||||
The paragraph text may optionally be surrounded by
|
|
||||||
<para attributes....>
|
|
||||||
</para>
|
|
||||||
tags. The attributes if any of the opening <para> tag affect the style that is used
|
|
||||||
with the $Paragraph$ $text$ and/or $bulletText$.
|
|
||||||
""")
|
|
||||||
disc(" ")
|
|
||||||
|
|
||||||
from reportlab.platypus.paraparser import _addAttributeNames, _paraAttrMap, _bulletAttrMap
|
|
||||||
|
|
||||||
def getAttrs(A):
|
|
||||||
_addAttributeNames(A)
|
|
||||||
S={}
|
|
||||||
for k, v in A.items():
|
|
||||||
a = v[0]
|
|
||||||
if not S.has_key(a):
|
|
||||||
S[a] = k
|
|
||||||
else:
|
|
||||||
S[a] = "%s, %s" %(S[a],k)
|
|
||||||
|
|
||||||
K = S.keys()
|
|
||||||
K.sort()
|
|
||||||
D=[('Attribute','Synonyms')]
|
|
||||||
for k in K:
|
|
||||||
D.append((k,S[k]))
|
|
||||||
cols=2*[None]
|
|
||||||
rows=len(D)*[None]
|
|
||||||
return D,cols,rows
|
|
||||||
|
|
||||||
t=apply(Table,getAttrs(_paraAttrMap))
|
|
||||||
t.setStyle(TableStyle([
|
|
||||||
('FONT',(0,0),(-1,1),'Times-Bold',10,12),
|
|
||||||
('FONT',(0,1),(-1,-1),'Courier',8,8),
|
|
||||||
('VALIGN',(0,0),(-1,-1),'MIDDLE'),
|
|
||||||
('INNERGRID', (0,0), (-1,-1), 0.25, colors.black),
|
|
||||||
('BOX', (0,0), (-1,-1), 0.25, colors.black),
|
|
||||||
]))
|
|
||||||
getStory().append(t)
|
|
||||||
caption("""Table <seq template="%(Chapter)s-%(Table+)s"/> - Synonyms for style attributes""")
|
|
||||||
|
|
||||||
disc("""Some useful synonyms have been provided for our Python attribute
|
|
||||||
names, including lowercase versions, and the equivalent properties
|
|
||||||
from the HTML standard where they exist. These additions make
|
|
||||||
it much easier to build XML-printing applications, since
|
|
||||||
much intra-paragraph markup may not need translating. The
|
|
||||||
table below shows the allowed attributes and synonyms in the
|
|
||||||
outermost paragraph tag.""")
|
|
||||||
|
|
||||||
|
|
||||||
heading2("Intra-paragraph markup")
|
|
||||||
disc("""'<![CDATA[Within each paragraph, we use a basic set of XML tags
|
|
||||||
to provide markup. The most basic of these are bold (<b>...</b>)
|
|
||||||
and italic (<i>...</i>). It is also legal to use an underline
|
|
||||||
tag (<u>...</u> but it has no effect; PostScript fonts don't
|
|
||||||
support underlining, and neither do we, yet.]]>""")
|
|
||||||
|
|
||||||
parabox2("""<b>You are hereby charged</b> that on the 28th day of May, 1970, you did
|
|
||||||
willfully, unlawfully, and <i>with malice of forethought</i>, publish an
|
|
||||||
alleged English-Hungarian phrase book with intent to cause a breach
|
|
||||||
of the peace. <u>How do you plead</u>?""", "Simple bold and italic tags")
|
|
||||||
|
|
||||||
heading3("The $<font>$ tag")
|
|
||||||
disc("""The $<font>$ tag can be used to change the font name,
|
|
||||||
size and text color for any substring within the paragraph.
|
|
||||||
Legal attributes are $size$, $face$, $name$ (which is the same as $face$),
|
|
||||||
$color$, and $fg$ (which is the same as $color$). The $name$ is
|
|
||||||
the font family name, without any 'bold' or 'italic' suffixes.
|
|
||||||
Colors may be
|
|
||||||
HTML color names or a hex string encoded in a variety of ways;
|
|
||||||
see ^reportlab.lib.colors^ for the formats allowed.""")
|
|
||||||
|
|
||||||
parabox2("""<font face="times" color="red">
|
|
||||||
You are hereby charged</font> that on the 28th day of May, 1970, you did
|
|
||||||
willfully, unlawfully, and <font size=14>with malice of forethought</font>,
|
|
||||||
publish an
|
|
||||||
alleged English-Hungarian phrase book with intent to cause a breach
|
|
||||||
of the peace. How do you plead?""", "The $font$ tag")
|
|
||||||
|
|
||||||
heading3("Superscripts and Subscripts")
|
|
||||||
disc("""Superscripts and subscripts are supported with the
|
|
||||||
<![CDATA[<super> and <sub> tags, which work exactly
|
|
||||||
as you might expect. In addition, most greek letters
|
|
||||||
can be accessed by using the <greek></greek>
|
|
||||||
tag, or with mathML entity names.]]>""")
|
|
||||||
|
|
||||||
##parabox2("""<greek>epsilon</greek><super><greek>iota</greek>
|
|
||||||
##<greek>pi</greek></super> = -1""", "Greek letters and subscripts")
|
|
||||||
|
|
||||||
parabox2("""Equation (α): <greek>e</greek> <super><greek>ip</greek></super> = -1""",
|
|
||||||
"Greek letters and superscripts")
|
|
||||||
|
|
||||||
heading3("Numbering Paragraphs and Lists")
|
|
||||||
disc("""The $<seq>$ tag provides comprehensive support
|
|
||||||
for numbering lists, chapter headings and so on. It acts as
|
|
||||||
an interface to the $Sequencer$ class in ^reportlab.lib.sequencer^.
|
|
||||||
These are used to number headings and figures throughout this
|
|
||||||
document.
|
|
||||||
You may create as many separate 'counters' as you wish, accessed
|
|
||||||
with the $id$ attribute; these will be incremented by one each
|
|
||||||
time they are accessed. The $seqreset$ tag resets a counter.
|
|
||||||
If you want it to resume from a number other than 1, use
|
|
||||||
the syntax <seqreset id="mycounter" base="42">.
|
|
||||||
Let's have a go:""")
|
|
||||||
|
|
||||||
parabox2("""<seq id="spam"/>, <seq id="spam"/>, <seq id="spam"/>.
|
|
||||||
Reset<seqreset id="spam"/>. <seq id="spam"/>, <seq id="spam"/>,
|
|
||||||
<seq id="spam"/>.""", "Basic sequences")
|
|
||||||
|
|
||||||
disc("""You can save specifying an ID by designating a counter ID
|
|
||||||
as the <i>default</i> using the <seqdefault id="Counter">
|
|
||||||
tag; it will then be used whenever a counter ID
|
|
||||||
is not specified. This saves some typing, especially when
|
|
||||||
doing multi-level lists; you just change counter ID when
|
|
||||||
stepping in or out a level.""")
|
|
||||||
|
|
||||||
parabox2("""<seqdefault id="spam"/>Continued... <seq/>,
|
|
||||||
<seq/>, <seq/>, <seq/>, <seq/>, <seq/>, <seq/>.""",
|
|
||||||
"The default sequence")
|
|
||||||
|
|
||||||
disc("""Finally, one can access multi-level sequences using
|
|
||||||
a variation of Python string formatting and the $template$
|
|
||||||
attribute in a <seq> tags. This is used to do the
|
|
||||||
captions in all of the figures, as well as the level two
|
|
||||||
headings. The substring $%(counter)s$ extracts the current
|
|
||||||
value of a counter without incrementing it; appending a
|
|
||||||
plus sign as in $%(counter)s$ increments the counter.
|
|
||||||
The figure captions use a pattern like the one below:""")
|
|
||||||
|
|
||||||
parabox2("""Figure <seq template="%(Chapter)s-%(FigureNo+)s"/> - Multi-level templates""",
|
|
||||||
"Multi-level templates")
|
|
||||||
|
|
||||||
disc("""We cheated a little - the real document used 'Figure',
|
|
||||||
but the text above uses 'FigureNo' - otherwise we would have
|
|
||||||
messed up our numbering!""")
|
|
||||||
|
|
||||||
heading2("Bullets and Paragraph Numbering")
|
|
||||||
disc("""In addition to the three indent properties, some other
|
|
||||||
parameters are needed to correctly handle bulleted and numbered
|
|
||||||
lists. We discuss this here because you have now seen how
|
|
||||||
to handle numbering. A paragraph may have an optional
|
|
||||||
^bulletText^ argument passed to its constructor; alternatively,
|
|
||||||
bullet text may be placed in a $<![CDATA[<bullet>..</bullet>]]>$
|
|
||||||
tag at its head. The text will be drawn on the first line of
|
|
||||||
the paragraph, with its x origin determined by the $bulletIndent$
|
|
||||||
attribute of the style, and in the font given in the
|
|
||||||
$bulletFontName$ attribute. For genuine bullets, a good
|
|
||||||
idea is to select the Times-Roman font in the style, and
|
|
||||||
use a character such as $\\xe2\\x80\\xa2)$:""")
|
|
||||||
|
|
||||||
t=apply(Table,getAttrs(_bulletAttrMap))
|
|
||||||
t.setStyle([
|
|
||||||
('FONT',(0,0),(-1,1),'Times-Bold',10,12),
|
|
||||||
('FONT',(0,1),(-1,-1),'Courier',8,8),
|
|
||||||
('VALIGN',(0,0),(-1,-1),'MIDDLE'),
|
|
||||||
('INNERGRID', (0,0), (-1,-1), 0.25, colors.black),
|
|
||||||
('BOX', (0,0), (-1,-1), 0.25, colors.black),
|
|
||||||
])
|
|
||||||
getStory().append(t)
|
|
||||||
|
|
||||||
caption("""Table <seq template="%(Chapter)s-%(Table+)s"/> - <bullet> attributes & synonyms""")
|
|
||||||
disc("""The <bullet> tag is only allowed once in a given paragraph and its use
|
|
||||||
overrides the implied bullet style and ^bulletText^ specified in the ^Paragraph^
|
|
||||||
creation.
|
|
||||||
""")
|
|
||||||
parabox("""<bullet>\xe2\x80\xa2</bullet>this is a bullet point. Spam
|
|
||||||
spam spam spam spam spam spam spam spam spam spam spam
|
|
||||||
spam spam spam spam spam spam spam spam spam spam """,
|
|
||||||
styleSheet['Bullet'],
|
|
||||||
'Basic use of bullet points')
|
|
||||||
|
|
||||||
disc("""Exactly the same technique is used for numbers,
|
|
||||||
except that a sequence tag is used. It is also possible
|
|
||||||
to put a multi-character string in the bullet; with a deep
|
|
||||||
indent and bold bullet font, you can make a compact
|
|
||||||
definition list.""")
|
|
|
@ -1,419 +0,0 @@
|
||||||
#Copyright ReportLab Europe Ltd. 2000-2004
|
|
||||||
#see license.txt for license details
|
|
||||||
#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/docs/userguide/ch6_tables.py
|
|
||||||
from reportlab.tools.docco.rl_doc_utils import *
|
|
||||||
from reportlab.platypus import Image
|
|
||||||
import reportlab
|
|
||||||
|
|
||||||
heading1("Tables and TableStyles")
|
|
||||||
disc("""
|
|
||||||
The $Table$ and $LongTable$ classes derive from the $Flowable$ class and are intended
|
|
||||||
as a simple textual gridding mechanisms. The $longTable$ class uses a greedy algorithm
|
|
||||||
when calculating column widths and is intended for long tables where speed counts.
|
|
||||||
$Table$ cells can hold anything which can be converted to
|
|
||||||
a <b>Python</b> $string$ or $Flowables$ (or lists of $Flowables$).
|
|
||||||
""")
|
|
||||||
|
|
||||||
disc("""
|
|
||||||
Our present tables are a trade-off between efficient drawing and specification
|
|
||||||
and functionality. We assume the reader has some familiarity with HTML tables.
|
|
||||||
In brief, they have the following characteristics:
|
|
||||||
""")
|
|
||||||
|
|
||||||
bullet("""They can contain anything convertible to a string; flowable
|
|
||||||
objects such as other tables; or entire sub-stories""")
|
|
||||||
|
|
||||||
bullet("""They can work out the row heights to fit the data if you don't supply
|
|
||||||
the row height. (They can also work out the widths, but generally it is better
|
|
||||||
for a designer to set the width manually, and it draws faster).""")
|
|
||||||
|
|
||||||
bullet("""They can split across pages if needed (see the canSplit attribute).
|
|
||||||
You can specify that a number of rows at the top and bottom should be
|
|
||||||
repeated after the split (e.g. show the headers again on page 2,3,4...)""")
|
|
||||||
|
|
||||||
bullet("""For very wide tables, they can also split 'by column'. You can choose
|
|
||||||
whether tou want to split down-and-across or across-and-down""")
|
|
||||||
|
|
||||||
bullet("""They have a simple and powerful notation for specifying shading and
|
|
||||||
gridlines which works well with financial or database tables, where you
|
|
||||||
don't know the number of rows up front. You can easily say 'make the last row
|
|
||||||
bold and put a line above it'""")
|
|
||||||
|
|
||||||
bullet("""The style and data are separated, so you can declare a handful of table
|
|
||||||
styles and use them for a family of reports. Styes can also 'inherit', as with
|
|
||||||
paragraphs.""")
|
|
||||||
|
|
||||||
disc("""There is however one main limitation compared to an HTML table.
|
|
||||||
They define a simple rectangular grid. There is no simple row or column
|
|
||||||
spanning; if you need to span cells, you must nest tables inside table cells instead or use a more
|
|
||||||
complex scheme in which the lead cell of a span contains the actual contents.""")
|
|
||||||
|
|
||||||
disc("""
|
|
||||||
$Tables$ are created by passing the constructor an optional sequence of column widths,
|
|
||||||
an optional sequence of row heights, and the data in row order.
|
|
||||||
Drawing of the table can be controlled by using a $TableStyle$ instance. This allows control of the
|
|
||||||
color and weight of the lines (if any), and the font, alignment and padding of the text.
|
|
||||||
A primitive automatic row height and or column width calculation mechanism is provided for.
|
|
||||||
""")
|
|
||||||
|
|
||||||
heading2('$Table$ User Methods')
|
|
||||||
disc("""These are the main methods which are of interest to the client programmer.""")
|
|
||||||
|
|
||||||
heading4("""$Table(data, colWidths=None, rowHeights=None, style=None, splitByRow=1,
|
|
||||||
repeatRows=0, repeatCols=0)$""")
|
|
||||||
|
|
||||||
disc("""The $data$ argument is a sequence of sequences of cell values each of which
|
|
||||||
should be convertible to a string value using the $str$ function or should be a Flowable instance (such as a $Paragraph$) or a list (or tuple) of such instances.
|
|
||||||
If a cell value is a $Flowable$ or list of $Flowables$ these must either have a determined width
|
|
||||||
or the containing column must have a fixed width.
|
|
||||||
The first row of cell values
|
|
||||||
is in $data[0]$ i.e. the values are in row order. The $i$, $j$<sup>th.</sup> cell value is in
|
|
||||||
$data[i][j]$. Newline characters $'\\n'$ in cell values are treated as line split characters and
|
|
||||||
are used at <i>draw</i> time to format the cell into lines.
|
|
||||||
""")
|
|
||||||
disc("""The other arguments are fairly obvious, the $colWidths$ argument is a sequence
|
|
||||||
of numbers or possibly $None$, representing the widths of the columns. The number of elements
|
|
||||||
in $colWidths$ determines the number of columns in the table.
|
|
||||||
A value of $None$ means that the corresponding column width should be calculated automatically.""")
|
|
||||||
|
|
||||||
disc("""The $rowHeights$ argument is a sequence
|
|
||||||
of numbers or possibly $None$, representing the heights of the rows. The number of elements
|
|
||||||
in $rowHeights$ determines the number of rows in the table.
|
|
||||||
A value of $None$ means that the corresponding row height should be calculated automatically.""")
|
|
||||||
|
|
||||||
disc("""The $style$ argument can be an initial style for the table.""")
|
|
||||||
disc("""The $splitByRow$ argument is only needed for tables both too tall and too wide
|
|
||||||
to fit in the current context. In this case you must decide whether to 'tile'
|
|
||||||
down and across, or across and then down. This parameter is a Boolean indicating that the
|
|
||||||
$Table$ should split itself
|
|
||||||
by row before attempting to split itself by column when too little space is available in
|
|
||||||
the current drawing area and the caller wants the $Table$ to split.""")
|
|
||||||
|
|
||||||
disc("""The $repeatRows$ and $repeatCols$ arguments specify the number of leading rows and columns
|
|
||||||
that should be repeated when the $Table$ is asked to split itself.""")
|
|
||||||
heading4('$Table.setStyle(tblStyle)$')
|
|
||||||
disc("""
|
|
||||||
This method applies a particular instance of class $TableStyle$ (discussed below)
|
|
||||||
to the $Table$ instance. This is the only way to get $tables$ to appear
|
|
||||||
in a nicely formatted way.
|
|
||||||
""")
|
|
||||||
disc("""
|
|
||||||
Successive uses of the $setStyle$ method apply the styles in an additive fashion.
|
|
||||||
That is, later applications override earlier ones where they overlap.
|
|
||||||
""")
|
|
||||||
|
|
||||||
heading2('$TableStyle$')
|
|
||||||
disc("""
|
|
||||||
This class is created by passing it a sequence of <i>commands</i>, each command
|
|
||||||
is a tuple identified by its first element which is a string; the remaining
|
|
||||||
elements of the command tuple represent the start and stop cell coordinates
|
|
||||||
of the command and possibly thickness and colors, etc.
|
|
||||||
""")
|
|
||||||
heading2("$TableStyle$ User Methods")
|
|
||||||
heading3("$TableStyle(commandSequence)$")
|
|
||||||
disc("""The creation method initializes the $TableStyle$ with the argument
|
|
||||||
command sequence as an example:""")
|
|
||||||
eg("""
|
|
||||||
LIST_STYLE = TableStyle(
|
|
||||||
[('LINEABOVE', (0,0), (-1,0), 2, colors.green),
|
|
||||||
('LINEABOVE', (0,1), (-1,-1), 0.25, colors.black),
|
|
||||||
('LINEBELOW', (0,-1), (-1,-1), 2, colors.green),
|
|
||||||
('ALIGN', (1,1), (-1,-1), 'RIGHT')]
|
|
||||||
)
|
|
||||||
""")
|
|
||||||
heading3("$TableStyle.add(commandSequence)$")
|
|
||||||
disc("""This method allows you to add commands to an existing
|
|
||||||
$TableStyle$, i.e. you can build up $TableStyles$ in multiple statements.
|
|
||||||
""")
|
|
||||||
eg("""
|
|
||||||
LIST_STYLE.add('BACKGROUND', (0,0), (-1,0), colors.Color(0,0.7,0.7))
|
|
||||||
""")
|
|
||||||
heading3("$TableStyle.getCommands()$")
|
|
||||||
disc("""This method returns the sequence of commands of the instance.""")
|
|
||||||
eg("""
|
|
||||||
cmds = LIST_STYLE.getCommands()
|
|
||||||
""")
|
|
||||||
heading2("$TableStyle$ Commands")
|
|
||||||
disc("""The commands passed to $TableStyles$ come in three main groups
|
|
||||||
which affect the table background, draw lines, or set cell styles.
|
|
||||||
""")
|
|
||||||
disc("""The first element of each command is its identifier,
|
|
||||||
the second and third arguments determine the cell coordinates of
|
|
||||||
the box of cells which are affected with negative coordinates
|
|
||||||
counting backwards from the limit values as in <b>Python</b>
|
|
||||||
indexing. The coordinates are given as
|
|
||||||
(column, row) which follows the spreadsheet 'A1' model, but not
|
|
||||||
the more natural (for mathematicians) 'RC' ordering.
|
|
||||||
The top left cell is (0, 0) the bottom right is (-1, -1). Depending on
|
|
||||||
the command various extra (???) occur at indices beginning at 3 on.
|
|
||||||
""")
|
|
||||||
heading3("""$TableStyle$ Cell Formatting Commands""")
|
|
||||||
disc("""The cell formatting commands all begin with an identifier, followed by
|
|
||||||
the start and stop cell definitions and the perhaps other arguments.
|
|
||||||
the cell formatting commands are:""")
|
|
||||||
eg("""
|
|
||||||
FONT - takes fontname, optional fontsize and optional leading.
|
|
||||||
FONTNAME (or FACE) - takes fontname.
|
|
||||||
FONTSIZE (or SIZE) - takes fontsize in points; leading may get out of sync.
|
|
||||||
LEADING - takes leading in points.
|
|
||||||
TEXTCOLOR - takes a color name or (R,G,B) tuple.
|
|
||||||
ALIGNMENT (or ALIGN) - takes one of LEFT, RIGHT and CENTRE (or CENTER) or DECIMAL.
|
|
||||||
LEFTPADDING - takes an integer, defaults to 6.
|
|
||||||
RIGHTPADDING - takes an integer, defaults to 6.
|
|
||||||
BOTTOMPADDING - takes an integer, defaults to 3.
|
|
||||||
TOPPADDING - takes an integer, defaults to 3.
|
|
||||||
BACKGROUND - takes a color.
|
|
||||||
ROWBACKGROUNDS - takes a list of colors to be used cyclically.
|
|
||||||
COLBACKGROUNDS - takes a list of colors to be used cyclically.
|
|
||||||
VALIGN - takes one of TOP, MIDDLE or the default BOTTOM
|
|
||||||
""")
|
|
||||||
disc("""This sets the background cell color in the relevant cells.
|
|
||||||
The following example shows the $BACKGROUND$, and $TEXTCOLOR$ commands in action:""")
|
|
||||||
EmbeddedCode("""
|
|
||||||
data= [['00', '01', '02', '03', '04'],
|
|
||||||
['10', '11', '12', '13', '14'],
|
|
||||||
['20', '21', '22', '23', '24'],
|
|
||||||
['30', '31', '32', '33', '34']]
|
|
||||||
t=Table(data)
|
|
||||||
t.setStyle(TableStyle([('BACKGROUND',(1,1),(-2,-2),colors.green),
|
|
||||||
('TEXTCOLOR',(0,0),(1,-1),colors.red)]))
|
|
||||||
""")
|
|
||||||
disc("""To see the effects of the alignment styles we need some widths
|
|
||||||
and a grid, but it should be easy to see where the styles come from.""")
|
|
||||||
EmbeddedCode("""
|
|
||||||
data= [['00', '01', '02', '03', '04'],
|
|
||||||
['10', '11', '12', '13', '14'],
|
|
||||||
['20', '21', '22', '23', '24'],
|
|
||||||
['30', '31', '32', '33', '34']]
|
|
||||||
t=Table(data,5*[0.4*inch], 4*[0.4*inch])
|
|
||||||
t.setStyle(TableStyle([('ALIGN',(1,1),(-2,-2),'RIGHT'),
|
|
||||||
('TEXTCOLOR',(1,1),(-2,-2),colors.red),
|
|
||||||
('VALIGN',(0,0),(0,-1),'TOP'),
|
|
||||||
('TEXTCOLOR',(0,0),(0,-1),colors.blue),
|
|
||||||
('ALIGN',(0,-1),(-1,-1),'CENTER'),
|
|
||||||
('VALIGN',(0,-1),(-1,-1),'MIDDLE'),
|
|
||||||
('TEXTCOLOR',(0,-1),(-1,-1),colors.green),
|
|
||||||
('INNERGRID', (0,0), (-1,-1), 0.25, colors.black),
|
|
||||||
('BOX', (0,0), (-1,-1), 0.25, colors.black),
|
|
||||||
]))
|
|
||||||
""")
|
|
||||||
heading3("""$TableStyle$ Line Commands""")
|
|
||||||
disc("""
|
|
||||||
Line commands begin with the identifier, the start and stop cell coordinates
|
|
||||||
and always follow this with the thickness (in points) and color of the desired lines. Colors can be names,
|
|
||||||
or they can be specified as a (R, G, B) tuple, where R, G and B are floats and (0, 0, 0) is black. The line
|
|
||||||
command names are: GRID, BOX, OUTLINE, INNERGRID, LINEBELOW, LINEABOVE, LINEBEFORE
|
|
||||||
and LINEAFTER. BOX and OUTLINE are equivalent, and GRID is the equivalent of applying both BOX and
|
|
||||||
INNERGRID.
|
|
||||||
""")
|
|
||||||
CPage(4.0)
|
|
||||||
disc("""We can see some line commands in action with the following example.
|
|
||||||
""")
|
|
||||||
EmbeddedCode("""
|
|
||||||
data= [['00', '01', '02', '03', '04'],
|
|
||||||
['10', '11', '12', '13', '14'],
|
|
||||||
['20', '21', '22', '23', '24'],
|
|
||||||
['30', '31', '32', '33', '34']]
|
|
||||||
t=Table(data,style=[('GRID',(1,1),(-2,-2),1,colors.green),
|
|
||||||
('BOX',(0,0),(1,-1),2,colors.red),
|
|
||||||
('LINEABOVE',(1,2),(-2,2),1,colors.blue),
|
|
||||||
('LINEBEFORE',(2,1),(2,-2),1,colors.pink),
|
|
||||||
])
|
|
||||||
""")
|
|
||||||
disc("""Line commands cause problems for tables when they split; the following example
|
|
||||||
shows a table being split in various positions""")
|
|
||||||
EmbeddedCode("""
|
|
||||||
data= [['00', '01', '02', '03', '04'],
|
|
||||||
['10', '11', '12', '13', '14'],
|
|
||||||
['20', '21', '22', '23', '24'],
|
|
||||||
['30', '31', '32', '33', '34']]
|
|
||||||
t=Table(data,style=[
|
|
||||||
('GRID',(0,0),(-1,-1),0.5,colors.grey),
|
|
||||||
('GRID',(1,1),(-2,-2),1,colors.green),
|
|
||||||
('BOX',(0,0),(1,-1),2,colors.red),
|
|
||||||
('BOX',(0,0),(-1,-1),2,colors.black),
|
|
||||||
('LINEABOVE',(1,2),(-2,2),1,colors.blue),
|
|
||||||
('LINEBEFORE',(2,1),(2,-2),1,colors.pink),
|
|
||||||
('BACKGROUND', (0, 0), (0, 1), colors.pink),
|
|
||||||
('BACKGROUND', (1, 1), (1, 2), colors.lavender),
|
|
||||||
('BACKGROUND', (2, 2), (2, 3), colors.orange),
|
|
||||||
])
|
|
||||||
""")
|
|
||||||
t=getStory()[-1]
|
|
||||||
getStory().append(Spacer(0,6))
|
|
||||||
for s in t.split(4*inch,30):
|
|
||||||
getStory().append(s)
|
|
||||||
getStory().append(Spacer(0,6))
|
|
||||||
getStory().append(Spacer(0,6))
|
|
||||||
for s in t.split(4*inch,36):
|
|
||||||
getStory().append(s)
|
|
||||||
getStory().append(Spacer(0,6))
|
|
||||||
|
|
||||||
disc("""When unsplit and split at the first or second row.""")
|
|
||||||
|
|
||||||
CPage(4.0)
|
|
||||||
heading3("""Complex Cell Values""")
|
|
||||||
disc("""
|
|
||||||
As mentioned above we can have complicated cell values including $Paragraphs$, $Images$ and other $Flowables$
|
|
||||||
or lists of the same. To see this in operation consider the following code and the table it produces.
|
|
||||||
Note that the $Image$ has a white background which will obscure any background you choose for the cell.
|
|
||||||
To get better results you should use a transparent background.
|
|
||||||
""")
|
|
||||||
import os, reportlab.platypus
|
|
||||||
I = '../images/replogo.gif'
|
|
||||||
EmbeddedCode("""
|
|
||||||
I = Image('%s')
|
|
||||||
I.drawHeight = 1.25*inch*I.drawHeight / I.drawWidth
|
|
||||||
I.drawWidth = 1.25*inch
|
|
||||||
P0 = Paragraph('''
|
|
||||||
<b>A pa<font color=red>r</font>a<i>graph</i></b>
|
|
||||||
<super><font color=yellow>1</font></super>''',
|
|
||||||
styleSheet["BodyText"])
|
|
||||||
P = Paragraph('''
|
|
||||||
<para align=center spaceb=3>The <b>ReportLab Left
|
|
||||||
<font color=red>Logo</font></b>
|
|
||||||
Image</para>''',
|
|
||||||
styleSheet["BodyText"])
|
|
||||||
data= [['A', 'B', 'C', P0, 'D'],
|
|
||||||
['00', '01', '02', [I,P], '04'],
|
|
||||||
['10', '11', '12', [P,I], '14'],
|
|
||||||
['20', '21', '22', '23', '24'],
|
|
||||||
['30', '31', '32', '33', '34']]
|
|
||||||
|
|
||||||
t=Table(data,style=[('GRID',(1,1),(-2,-2),1,colors.green),
|
|
||||||
('BOX',(0,0),(1,-1),2,colors.red),
|
|
||||||
('LINEABOVE',(1,2),(-2,2),1,colors.blue),
|
|
||||||
('LINEBEFORE',(2,1),(2,-2),1,colors.pink),
|
|
||||||
('BACKGROUND', (0, 0), (0, 1), colors.pink),
|
|
||||||
('BACKGROUND', (1, 1), (1, 2), colors.lavender),
|
|
||||||
('BACKGROUND', (2, 2), (2, 3), colors.orange),
|
|
||||||
('BOX',(0,0),(-1,-1),2,colors.black),
|
|
||||||
('GRID',(0,0),(-1,-1),0.5,colors.black),
|
|
||||||
('VALIGN',(3,0),(3,0),'BOTTOM'),
|
|
||||||
('BACKGROUND',(3,0),(3,0),colors.limegreen),
|
|
||||||
('BACKGROUND',(3,1),(3,1),colors.khaki),
|
|
||||||
('ALIGN',(3,1),(3,1),'CENTER'),
|
|
||||||
('BACKGROUND',(3,2),(3,2),colors.beige),
|
|
||||||
('ALIGN',(3,2),(3,2),'LEFT'),
|
|
||||||
])
|
|
||||||
|
|
||||||
t._argW[3]=1.5*inch
|
|
||||||
"""%I)
|
|
||||||
heading3("""$TableStyle$ Span Commands""")
|
|
||||||
disc("""Our $Table$ classes support the concept of spanning, but it isn't specified in the same way
|
|
||||||
as html. The style specification
|
|
||||||
""")
|
|
||||||
eg("""
|
|
||||||
SPAN, (sc,sr), (ec,er)
|
|
||||||
""")
|
|
||||||
disc("""indicates that the cells in columns $sc$ - $ec$ and rows $sr$ - $er$ should be combined into a super cell
|
|
||||||
with contents determined by the cell $(sc, sr)$. The other cells should be present, but should contain empty strings
|
|
||||||
or you may get unexpected results.
|
|
||||||
""")
|
|
||||||
EmbeddedCode("""
|
|
||||||
data= [['Top\\nLeft', '', '02', '03', '04'],
|
|
||||||
['', '', '12', '13', '14'],
|
|
||||||
['20', '21', '22', 'Bottom\\nRight', ''],
|
|
||||||
['30', '31', '32', '', '']]
|
|
||||||
t=Table(data,style=[
|
|
||||||
('GRID',(0,0),(-1,-1),0.5,colors.grey),
|
|
||||||
('BACKGROUND',(0,0),(1,1),colors.palegreen),
|
|
||||||
('SPAN',(0,0),(1,1)),
|
|
||||||
('BACKGROUND',(-2,-2),(-1,-1), colors.pink),
|
|
||||||
('SPAN',(-2,-2),(-1,-1)),
|
|
||||||
])
|
|
||||||
""")
|
|
||||||
|
|
||||||
disc("""notice that we don't need to be conservative with our $GRID$ command. The spanned cells are not drawn through.
|
|
||||||
""")
|
|
||||||
heading3("""Special $TableStyle$ Indeces""")
|
|
||||||
disc("""In any style command the first row index may be set to one of the special strings
|
|
||||||
$'splitlast'$ or $'splitfirst'$ to indicate that the style should be used only for the last row of
|
|
||||||
a split table, or the first row of a continuation. This allows splitting tables with nicer effects around the split.""")
|
|
||||||
|
|
||||||
heading1("""Other Useful $Flowables$""")
|
|
||||||
heading2("""$Preformatted(text, style, bulletText = None, dedent=0)$""")
|
|
||||||
disc("""
|
|
||||||
Creates a preformatted paragraph which does no wrapping, line splitting or other manipulations.
|
|
||||||
No $XML$ style tags are taken account of in the text.
|
|
||||||
If dedent is non zero $dedent$ common leading
|
|
||||||
spaces will be removed from the front of each line.
|
|
||||||
""")
|
|
||||||
heading2("""$XPreformatted(text, style, bulletText = None, dedent=0, frags=None)$""")
|
|
||||||
disc("""
|
|
||||||
This is a non rearranging form of the $Paragraph$ class; $XML$ tags are allowed in
|
|
||||||
$text$ and have the same meanings as for the $Paragraph$ class.
|
|
||||||
As for $Preformatted$, if dedent is non zero $dedent$ common leading
|
|
||||||
spaces will be removed from the front of each line.
|
|
||||||
""")
|
|
||||||
EmbeddedCode("""
|
|
||||||
from reportlab.lib.styles import getSampleStyleSheet
|
|
||||||
stylesheet=getSampleStyleSheet()
|
|
||||||
normalStyle = stylesheet['Normal']
|
|
||||||
text='''
|
|
||||||
|
|
||||||
This is a non rearranging form of the <b>Paragraph</b> class;
|
|
||||||
<b><font color=red>XML</font></b> tags are allowed in <i>text</i> and have the same
|
|
||||||
|
|
||||||
meanings as for the <b>Paragraph</b> class.
|
|
||||||
As for <b>Preformatted</b>, if dedent is non zero <font color=red size=+1>dedent</font>
|
|
||||||
common leading spaces will be removed from the
|
|
||||||
front of each line.
|
|
||||||
You can have &amp; style entities as well for & < > and ".
|
|
||||||
|
|
||||||
'''
|
|
||||||
t=XPreformatted(text,normalStyle,dedent=3)
|
|
||||||
""")
|
|
||||||
heading2("""$Image(filename, width=None, height=None)$""")
|
|
||||||
disc("""Create a flowable which will contain the image defined by the data in file $filename$.
|
|
||||||
The default <b>PDF</b> image type <i>jpeg</i> is supported and if the <b>PIL</b> extension to <b>Python</b>
|
|
||||||
is installed the other image types can also be handled. If $width$ and or $height$ are specified
|
|
||||||
then they determine the dimension of the displayed image in <i>points</i>. If either dimension is
|
|
||||||
not specified (or specified as $None$) then the corresponding pixel dimension of the image is assumed
|
|
||||||
to be in <i>points</i> and used.
|
|
||||||
""")
|
|
||||||
I=os.path.join(os.path.dirname(reportlab.__file__),'docs','images','lj8100.jpg')
|
|
||||||
eg("""
|
|
||||||
Image("lj8100.jpg")
|
|
||||||
""",after=0.1)
|
|
||||||
disc("""will display as""")
|
|
||||||
try:
|
|
||||||
getStory().append(Image(I))
|
|
||||||
except:
|
|
||||||
disc("""An image should have appeared here.""")
|
|
||||||
disc("""whereas""")
|
|
||||||
eg("""
|
|
||||||
im = Image("lj8100.jpg", width=2*inch, height=2*inch)
|
|
||||||
im.hAlign = 'CENTER'
|
|
||||||
""", after=0.1)
|
|
||||||
disc('produces')
|
|
||||||
try:
|
|
||||||
im = Image(I, width=2*inch, height=2*inch)
|
|
||||||
im.hAlign = 'CENTER'
|
|
||||||
getStory().append(Image(I, width=2*inch, height=2*inch))
|
|
||||||
except:
|
|
||||||
disc("""An image should have appeared here.""")
|
|
||||||
heading2("""$Spacer(width, height)$""")
|
|
||||||
disc("""This does exactly as would be expected; it adds a certain amount of space into the story.
|
|
||||||
At present this only works for vertical space.
|
|
||||||
""")
|
|
||||||
CPage(1)
|
|
||||||
heading2("""$PageBreak()$""")
|
|
||||||
disc("""This $Flowable$ represents a page break. It works by effectively consuming all vertical
|
|
||||||
space given to it. This is sufficient for a single $Frame$ document, but would only be a
|
|
||||||
frame break for multiple frames so the $BaseDocTemplate$ mechanism
|
|
||||||
detects $pageBreaks$ internally and handles them specially.
|
|
||||||
""")
|
|
||||||
CPage(1)
|
|
||||||
heading2("""$CondPageBreak(height)$""")
|
|
||||||
disc("""This $Flowable$ attempts to force a $Frame$ break if insufficient vertical space remains
|
|
||||||
in the current $Frame$. It is thus probably wrongly named and should probably be renamed as
|
|
||||||
$CondFrameBreak$.
|
|
||||||
""")
|
|
||||||
CPage(1)
|
|
||||||
heading2("""$KeepTogether(flowables)$""")
|
|
||||||
disc("""
|
|
||||||
This compound $Flowable$ takes a list of $Flowables$ and attempts to keep them in the same $Frame$.
|
|
||||||
If the total height of the $Flowables$ in the list $flowables$ exceeds the current frame's available
|
|
||||||
space then all the space is used and a frame break is forced.
|
|
||||||
""")
|
|
|
@ -1,81 +0,0 @@
|
||||||
#Copyright ReportLab Europe Ltd. 2000-2004
|
|
||||||
#see license.txt for license details
|
|
||||||
#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/docs/userguide/ch7_custom.py
|
|
||||||
from reportlab.tools.docco.rl_doc_utils import *
|
|
||||||
|
|
||||||
heading1("Writing your own $Flowable$ Objects")
|
|
||||||
disc("""
|
|
||||||
Flowables are intended to be an open standard for creating
|
|
||||||
reusable report content, and you can easily create your
|
|
||||||
own objects. We hope that over time we will build up
|
|
||||||
a library of contributions, giving reportlab users a
|
|
||||||
rich selection of charts, graphics and other "report
|
|
||||||
widgets" they can use in their own reports. This section
|
|
||||||
shows you how to create your own flowables.""")
|
|
||||||
|
|
||||||
todo("""we should put the Figure class in the
|
|
||||||
standard library, as it is a very useful base.""")
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
heading2("A very simple $Flowable$")
|
|
||||||
|
|
||||||
disc("""
|
|
||||||
Recall the $hand$ function from the $pdfgen$ section of this user guide which
|
|
||||||
generated a drawing of a hand as a closed figure composed from Bezier curves.
|
|
||||||
""")
|
|
||||||
illust(examples.hand, "a hand")
|
|
||||||
disc("""
|
|
||||||
To embed this or any other drawing in a Platypus flowable we must define a
|
|
||||||
subclass of $Flowable$
|
|
||||||
with at least a $wrap$ method and a $draw$ method.
|
|
||||||
""")
|
|
||||||
eg(examples.testhandannotation)
|
|
||||||
disc("""
|
|
||||||
The $wrap$ method must provide the size of the drawing -- it is used by
|
|
||||||
the Platypus mainloop to decide whether this element fits in the space remaining
|
|
||||||
on the current frame. The $draw$ method performs the drawing of the object after
|
|
||||||
the Platypus mainloop has translated the $(0,0)$ origin to an appropriate location
|
|
||||||
in an appropriate frame.
|
|
||||||
""")
|
|
||||||
disc("""
|
|
||||||
Below are some example uses of the $HandAnnotation$ flowable.
|
|
||||||
""")
|
|
||||||
|
|
||||||
from reportlab.lib.colors import blue, pink, yellow, cyan, brown
|
|
||||||
from reportlab.lib.units import inch
|
|
||||||
|
|
||||||
handnote()
|
|
||||||
|
|
||||||
disc("""The default.""")
|
|
||||||
|
|
||||||
handnote(size=inch)
|
|
||||||
|
|
||||||
disc("""Just one inch high.""")
|
|
||||||
|
|
||||||
handnote(xoffset=3*inch, size=inch, strokecolor=blue, fillcolor=cyan)
|
|
||||||
|
|
||||||
disc("""One inch high and shifted to the left with blue and cyan.""")
|
|
||||||
|
|
||||||
|
|
||||||
heading2("Modifying a Built in $Flowable$")
|
|
||||||
disc("""To modify an existing flowable, you should create a derived class
|
|
||||||
and override the methods you need to change to get the desired behaviour""")
|
|
||||||
disc("""As an example to create a rotated image you need to override the wrap
|
|
||||||
and draw methods of the existing Image class""")
|
|
||||||
import os
|
|
||||||
from reportlab.platypus import *
|
|
||||||
I = '../images/replogo.gif'
|
|
||||||
|
|
||||||
EmbeddedCode("""
|
|
||||||
class RotatedImage(Image):
|
|
||||||
def wrap(self,availWidth,availHeight):
|
|
||||||
h, w = Image.wrap(self,availHeight,availWidth)
|
|
||||||
return w, h
|
|
||||||
def draw(self):
|
|
||||||
self.canv.rotate(90)
|
|
||||||
Image.draw(self)
|
|
||||||
I = RotatedImage('%s')
|
|
||||||
I.hAlign = 'CENTER'
|
|
||||||
""" % I,'I')
|
|
|
@ -1,35 +0,0 @@
|
||||||
#Copyright ReportLab Europe Ltd. 2000-2004
|
|
||||||
#see license.txt for license details
|
|
||||||
#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/docs/userguide/ch9_future.py
|
|
||||||
from reportlab.tools.docco.rl_doc_utils import *
|
|
||||||
|
|
||||||
heading1("Future Directions")
|
|
||||||
|
|
||||||
disc("""We have a very long list of things we plan to do
|
|
||||||
and what we do first will most likely be inspired by customer
|
|
||||||
or user interest.
|
|
||||||
""")
|
|
||||||
|
|
||||||
disc("""
|
|
||||||
We plan to provide a large number of pre-designed Platypus example
|
|
||||||
document types -- brochure, newsletter, business letter, thesis, memo,
|
|
||||||
etcetera, to give our users a better boost towards the solutions they
|
|
||||||
desire.
|
|
||||||
""")
|
|
||||||
|
|
||||||
disc("""
|
|
||||||
We plan to fully support adding fonts and internationalization, which are
|
|
||||||
not well supported in the current release.""")
|
|
||||||
|
|
||||||
disc("""
|
|
||||||
We plan to fully support some of the more obscure features of PDF
|
|
||||||
such as general hyperlinks, which are not yet well supported.
|
|
||||||
""")
|
|
||||||
|
|
||||||
disc("""
|
|
||||||
We are also open for suggestions. Please let us know what you think
|
|
||||||
is missing. You can also offer patches or contributions. Please
|
|
||||||
look to $http://www.reportlab.com$ for the latest mailing list and
|
|
||||||
contact information.""")
|
|
||||||
|
|
||||||
# this comment is a trivial test of SF checkin rights - delete it some time! AR 2001-04-17
|
|
|
@ -1,85 +0,0 @@
|
||||||
#!/bin/env python
|
|
||||||
#Copyright ReportLab Europe Ltd. 2000-2004
|
|
||||||
#see license.txt for license details
|
|
||||||
#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/docs/userguide/genuserguide.py
|
|
||||||
__version__=''' $Id: genuserguide.py 2900 2006-05-22 21:49:00Z andy $ '''
|
|
||||||
__doc__ = """
|
|
||||||
This module contains the script for building the user guide.
|
|
||||||
"""
|
|
||||||
def run(pagesize=None, verbose=0, outDir=None):
|
|
||||||
import os
|
|
||||||
from reportlab.tools.docco.rl_doc_utils import setStory, getStory, RLDocTemplate, defaultPageSize
|
|
||||||
from reportlab.tools.docco import rl_doc_utils
|
|
||||||
from reportlab.lib.utils import open_and_read, _RL_DIR
|
|
||||||
if not outDir: outDir = os.path.join(_RL_DIR,'docs')
|
|
||||||
destfn = os.path.join(outDir,'userguide.pdf')
|
|
||||||
doc = RLDocTemplate(destfn,pagesize = pagesize or defaultPageSize)
|
|
||||||
|
|
||||||
#this builds the story
|
|
||||||
setStory()
|
|
||||||
G = {}
|
|
||||||
exec 'from reportlab.tools.docco.rl_doc_utils import *' in G, G
|
|
||||||
for f in (
|
|
||||||
'ch1_intro',
|
|
||||||
'ch2_graphics',
|
|
||||||
'ch2a_fonts',
|
|
||||||
'ch3_pdffeatures',
|
|
||||||
'ch4_platypus_concepts',
|
|
||||||
'ch5_paragraphs',
|
|
||||||
'ch6_tables',
|
|
||||||
'ch7_custom',
|
|
||||||
'ch9_future',
|
|
||||||
'app_demos',
|
|
||||||
):
|
|
||||||
exec open_and_read(f+'.py',mode='t') in G, G
|
|
||||||
del G
|
|
||||||
|
|
||||||
story = getStory()
|
|
||||||
if verbose: print 'Built story contains %d flowables...' % len(story)
|
|
||||||
doc.build(story)
|
|
||||||
if verbose: print 'Saved "%s"' % destfn
|
|
||||||
|
|
||||||
def makeSuite():
|
|
||||||
"standard test harness support - run self as separate process"
|
|
||||||
from reportlab.test.utils import ScriptThatMakesFileTest
|
|
||||||
return ScriptThatMakesFileTest('../docs/userguide', 'genuserguide.py', 'userguide.pdf')
|
|
||||||
|
|
||||||
def main():
|
|
||||||
import sys
|
|
||||||
outDir = filter(lambda x: x[:9]=='--outdir=',sys.argv)
|
|
||||||
if outDir:
|
|
||||||
outDir = outDir[0]
|
|
||||||
sys.argv.remove(outDir)
|
|
||||||
outDir = outDir[9:]
|
|
||||||
else:
|
|
||||||
outDir = None
|
|
||||||
verbose = '-s' not in sys.argv
|
|
||||||
if not verbose: sys.argv.remove('-s')
|
|
||||||
timing = '-timing' in sys.argv
|
|
||||||
if timing: sys.argv.remove('-timing')
|
|
||||||
prof = '-prof' in sys.argv
|
|
||||||
if prof: sys.argv.remove('-prof')
|
|
||||||
|
|
||||||
if len(sys.argv) > 1:
|
|
||||||
try:
|
|
||||||
pagesize = (w,h) = eval(sys.argv[1])
|
|
||||||
except:
|
|
||||||
print 'Expected page size in argument 1', sys.argv[1]
|
|
||||||
raise
|
|
||||||
if verbose:
|
|
||||||
print 'set page size to',sys.argv[1]
|
|
||||||
else:
|
|
||||||
pagesize = None
|
|
||||||
if timing:
|
|
||||||
from time import time
|
|
||||||
t0 = time()
|
|
||||||
run(pagesize, verbose,outDir)
|
|
||||||
if verbose:
|
|
||||||
print 'Generation of userguide took %.2f seconds' % (time()-t0)
|
|
||||||
elif prof:
|
|
||||||
import profile
|
|
||||||
profile.run('run(pagesize,verbose,outDir)','genuserguide.stats')
|
|
||||||
else:
|
|
||||||
run(pagesize, verbose,outDir)
|
|
||||||
if __name__=="__main__":
|
|
||||||
main()
|
|
|
@ -1,2 +0,0 @@
|
||||||
Test of ability to create new files in sourceforge CVS, which
|
|
||||||
seems not to be working. Can be removed any time.
|
|
|
@ -1,25 +0,0 @@
|
||||||
This directory is intended to act as a placeholder for extending ReportLab
|
|
||||||
with extra extensions. It has been packagised with an empty __init__.py.
|
|
||||||
|
|
||||||
So a typical extender should add his package extension as
|
|
||||||
|
|
||||||
reportlab/extensions/great_extension/__init__.py
|
|
||||||
/dingo.py
|
|
||||||
/etc etc
|
|
||||||
|
|
||||||
and single modules as
|
|
||||||
|
|
||||||
reportlab/extensions/my_module.py
|
|
||||||
|
|
||||||
Then client code can do
|
|
||||||
|
|
||||||
from reportlab.extensions.great_extension import dingo
|
|
||||||
|
|
||||||
if you extend with just a single module it might be simpler to add that
|
|
||||||
into extensions so that you could do
|
|
||||||
|
|
||||||
from reportlab.extensions import my_module.
|
|
||||||
|
|
||||||
|
|
||||||
ReportLab can take no responsibility for name clashes and problems caused by
|
|
||||||
modules and packages in reportlab/extensions.
|
|
|
@ -1,7 +0,0 @@
|
||||||
#Copyright ReportLab Europe Ltd. 2000-2004
|
|
||||||
#see license.txt for license details
|
|
||||||
#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/extensions/__init__.py
|
|
||||||
__version__=''' $Id: __init__.py 2385 2004-06-17 15:26:05Z rgbecker $ '''
|
|
||||||
__doc__="""
|
|
||||||
"""
|
|
||||||
# No usable content. Do not add to this file!
|
|
|
@ -1,8 +0,0 @@
|
||||||
This directory is a convenient place to put
|
|
||||||
fonts where Reportlab will find them. If running
|
|
||||||
in a server environment, this is a good place to
|
|
||||||
put Type 1 fonts needed by your application. If
|
|
||||||
in a desktop environment, you might prefer to add
|
|
||||||
your font directories to the T1SearchPath in
|
|
||||||
reportlab/rl_config.py instead.
|
|
||||||
|
|
|
@ -1,93 +0,0 @@
|
||||||
StartFontMetrics 2.0
|
|
||||||
Comment Generated by RoboFog 08-06-2001 4:26:40 PM
|
|
||||||
FontName LettErrorRobot-Chrome
|
|
||||||
FullName LettErrorRobot-Chrome
|
|
||||||
FamilyName LettErrorRobot
|
|
||||||
Weight Medium
|
|
||||||
Notice (C) 1998-2001 LettError, Just van Rossum, Erik van Blokland, http://www.letterror.com/
|
|
||||||
ItalicAngle 0
|
|
||||||
IsFixedPitch false
|
|
||||||
UnderlinePosition -133
|
|
||||||
UnderlineThickness 20
|
|
||||||
Version 001.000
|
|
||||||
EncodingScheme AdobeStandardEncoding
|
|
||||||
FontBBox -53 -252 1047 752
|
|
||||||
CapHeight 647
|
|
||||||
XHeight 548
|
|
||||||
Descender -252
|
|
||||||
Ascender 747
|
|
||||||
StartCharMetrics 68
|
|
||||||
C 32 ; WX 300 ; N space ; B 0 0 0 0 ;
|
|
||||||
C 33 ; WX 300 ; N exclam ; B 48 -52 253 652 ;
|
|
||||||
C 38 ; WX 700 ; N ampersand ; B 48 -47 653 647 ;
|
|
||||||
C 42 ; WX 700 ; N asterisk ; B 48 148 653 752 ;
|
|
||||||
C 48 ; WX 700 ; N zero ; B 53 -47 648 548 ;
|
|
||||||
C 49 ; WX 500 ; N one ; B 48 -47 453 553 ;
|
|
||||||
C 50 ; WX 600 ; N two ; B 48 -47 553 548 ;
|
|
||||||
C 51 ; WX 600 ; N three ; B 48 -147 553 548 ;
|
|
||||||
C 52 ; WX 700 ; N four ; B 48 -152 653 553 ;
|
|
||||||
C 53 ; WX 600 ; N five ; B 48 -147 553 548 ;
|
|
||||||
C 54 ; WX 600 ; N six ; B 53 -47 553 647 ;
|
|
||||||
C 55 ; WX 600 ; N seven ; B 48 -152 548 548 ;
|
|
||||||
C 56 ; WX 600 ; N eight ; B 48 -47 553 647 ;
|
|
||||||
C 57 ; WX 600 ; N nine ; B 48 -147 548 548 ;
|
|
||||||
C 63 ; WX 500 ; N question ; B 48 -52 448 647 ;
|
|
||||||
C 64 ; WX 800 ; N at ; B 53 -47 748 647 ;
|
|
||||||
C 65 ; WX 700 ; N A ; B 53 -52 648 652 ;
|
|
||||||
C 66 ; WX 600 ; N B ; B 53 -47 553 647 ;
|
|
||||||
C 67 ; WX 600 ; N C ; B 53 -47 553 647 ;
|
|
||||||
C 68 ; WX 700 ; N D ; B 53 -47 648 647 ;
|
|
||||||
C 69 ; WX 600 ; N E ; B 53 -47 553 647 ;
|
|
||||||
C 70 ; WX 600 ; N F ; B 53 -52 553 647 ;
|
|
||||||
C 71 ; WX 700 ; N G ; B 53 -47 653 647 ;
|
|
||||||
C 72 ; WX 700 ; N H ; B 53 -52 648 652 ;
|
|
||||||
C 73 ; WX 300 ; N I ; B 53 -52 248 652 ;
|
|
||||||
C 74 ; WX 300 ; N J ; B -53 -252 248 652 ;
|
|
||||||
C 75 ; WX 700 ; N K ; B 53 -52 653 652 ;
|
|
||||||
C 76 ; WX 600 ; N L ; B 53 -47 553 652 ;
|
|
||||||
C 77 ; WX 900 ; N M ; B 53 -52 848 652 ;
|
|
||||||
C 78 ; WX 700 ; N N ; B 53 -52 648 652 ;
|
|
||||||
C 79 ; WX 700 ; N O ; B 53 -47 648 647 ;
|
|
||||||
C 80 ; WX 600 ; N P ; B 53 -52 548 647 ;
|
|
||||||
C 81 ; WX 700 ; N Q ; B 53 -252 653 647 ;
|
|
||||||
C 82 ; WX 600 ; N R ; B 53 -52 653 647 ;
|
|
||||||
C 83 ; WX 600 ; N S ; B 48 -47 553 647 ;
|
|
||||||
C 84 ; WX 700 ; N T ; B 48 -52 653 647 ;
|
|
||||||
C 85 ; WX 700 ; N U ; B 53 -47 648 652 ;
|
|
||||||
C 86 ; WX 700 ; N V ; B 53 -52 648 652 ;
|
|
||||||
C 87 ; WX 1100 ; N W ; B 53 -52 1047 652 ;
|
|
||||||
C 88 ; WX 700 ; N X ; B 48 -52 653 652 ;
|
|
||||||
C 89 ; WX 700 ; N Y ; B 53 -52 648 652 ;
|
|
||||||
C 90 ; WX 700 ; N Z ; B 48 -47 653 647 ;
|
|
||||||
C 97 ; WX 600 ; N a ; B 48 -47 548 548 ;
|
|
||||||
C 98 ; WX 600 ; N b ; B 53 -47 548 752 ;
|
|
||||||
C 99 ; WX 600 ; N c ; B 53 -47 553 548 ;
|
|
||||||
C 100 ; WX 600 ; N d ; B 53 -47 548 752 ;
|
|
||||||
C 101 ; WX 600 ; N e ; B 53 -47 553 548 ;
|
|
||||||
C 102 ; WX 400 ; N f ; B -53 -52 453 747 ;
|
|
||||||
C 103 ; WX 600 ; N g ; B 48 -247 548 548 ;
|
|
||||||
C 104 ; WX 600 ; N h ; B 53 -52 548 752 ;
|
|
||||||
C 105 ; WX 300 ; N i ; B 48 -52 253 752 ;
|
|
||||||
C 106 ; WX 300 ; N j ; B -53 -252 253 752 ;
|
|
||||||
C 107 ; WX 600 ; N k ; B 53 -52 553 752 ;
|
|
||||||
C 108 ; WX 300 ; N l ; B 53 -52 248 752 ;
|
|
||||||
C 109 ; WX 900 ; N m ; B 53 -52 848 548 ;
|
|
||||||
C 110 ; WX 600 ; N n ; B 53 -52 548 548 ;
|
|
||||||
C 111 ; WX 600 ; N o ; B 53 -47 548 548 ;
|
|
||||||
C 112 ; WX 600 ; N p ; B 53 -252 548 548 ;
|
|
||||||
C 113 ; WX 600 ; N q ; B 53 -252 548 548 ;
|
|
||||||
C 114 ; WX 400 ; N r ; B 53 -52 453 553 ;
|
|
||||||
C 115 ; WX 600 ; N s ; B 48 -47 553 548 ;
|
|
||||||
C 116 ; WX 400 ; N t ; B -53 -47 453 652 ;
|
|
||||||
C 117 ; WX 600 ; N u ; B 53 -47 548 553 ;
|
|
||||||
C 118 ; WX 600 ; N v ; B 53 -52 548 553 ;
|
|
||||||
C 119 ; WX 900 ; N w ; B 53 -52 848 553 ;
|
|
||||||
C 120 ; WX 700 ; N x ; B 48 -52 653 553 ;
|
|
||||||
C 121 ; WX 600 ; N y ; B 48 -252 548 553 ;
|
|
||||||
C 122 ; WX 500 ; N z ; B -53 -47 553 548 ;
|
|
||||||
EndCharMetrics
|
|
||||||
StartKernData
|
|
||||||
StartKernPairs 0
|
|
||||||
EndKernPairs
|
|
||||||
EndKernData
|
|
||||||
EndFontMetrics
|
|
|
@ -1,40 +0,0 @@
|
||||||
Bigelow & Holmes Inc and URW++ GmbH Luxi font license
|
|
||||||
|
|
||||||
Luxi fonts copyright (c) 2001 by Bigelow & Holmes Inc. Luxi font instruction
|
|
||||||
code copyright (c) 2001 by URW++ GmbH. All Rights Reserved. Luxi is a
|
|
||||||
registered trademark of Bigelow & Holmes Inc.
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of these Fonts and associated documentation files (the "Font Software"), to
|
|
||||||
deal in the Font Software, including without limitation the rights to use,
|
|
||||||
copy, merge, publish, distribute, sublicense, and/or sell copies of the Font
|
|
||||||
Software, and to permit persons to whom the Font Software is furnished to do
|
|
||||||
so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright and trademark notices and this permission notice shall be
|
|
||||||
included in all copies of one or more of the Font Software.
|
|
||||||
|
|
||||||
The Font Software may not be modified, altered, or added to, and in
|
|
||||||
particular the designs of glyphs or characters in the Fonts may not be
|
|
||||||
modified nor may additional glyphs or characters be added to the Fonts. This
|
|
||||||
License becomes null and void when the Fonts or Font Software have been
|
|
||||||
modified.
|
|
||||||
|
|
||||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
|
||||||
OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT,
|
|
||||||
TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL BIGELOW & HOLMES INC. OR URW++
|
|
||||||
GMBH. BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY
|
|
||||||
GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN
|
|
||||||
AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR
|
|
||||||
INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT
|
|
||||||
SOFTWARE.
|
|
||||||
|
|
||||||
Except as contained in this notice, the names of Bigelow & Holmes Inc. and
|
|
||||||
URW++ GmbH. shall not be used in advertising or otherwise to promote the
|
|
||||||
sale, use or other dealings in this Font Software without prior written
|
|
||||||
authorization from Bigelow & Holmes Inc. and URW++ GmbH.
|
|
||||||
|
|
||||||
For further information, contact:
|
|
||||||
|
|
||||||
info@urwpp.de or design@bigelowandholmes.com
|
|
|
@ -1,93 +0,0 @@
|
||||||
The fonts contained in this archive are Freeware.
|
|
||||||
No payment is required for the use of these fonts. They're free!
|
|
||||||
Commercial use? Sure, but a donation or a product sample would be
|
|
||||||
appreciated.
|
|
||||||
|
|
||||||
$40 US is the usual amount per font for commercial use but any amount is appreciated.
|
|
||||||
|
|
||||||
I make all the fonts (around 370 of them) on my web page and they're all free.
|
|
||||||
I offer Deluxe versions of some of my fonts for sale. They contain several
|
|
||||||
weights and styles of each font. Just click on the Deluxe button at Larabie Fonts to see them.
|
|
||||||
|
|
||||||
The page is called Larabie Fonts
|
|
||||||
It can be found at various mirror sites.
|
|
||||||
|
|
||||||
try:
|
|
||||||
http://www.larabiefonts.com
|
|
||||||
http://uk.zarcrom.com/font/
|
|
||||||
http://swankarmy.net/larabiefonts/
|
|
||||||
http://come.to/larabiefonts
|
|
||||||
|
|
||||||
I've provided the world with about 300 free fonts,
|
|
||||||
so if you'd like to make a donation I'd be more than happy to accept it.
|
|
||||||
|
|
||||||
No donation is too small! Music and artwork are good too.
|
|
||||||
If you have some CD's you're not listening to anymore, send 'em along!
|
|
||||||
|
|
||||||
Send anything at all to
|
|
||||||
|
|
||||||
Ray Larabie
|
|
||||||
61 Wesley Ave.
|
|
||||||
Port Credit
|
|
||||||
Ontario, CANADA
|
|
||||||
L5H 2M8
|
|
||||||
|
|
||||||
If you decide to send a cheque (that's how we spell it in Canada)
|
|
||||||
make it payable to Ray Larabie. If you want to double check the address
|
|
||||||
have a look at the donation section on any of my webpages.
|
|
||||||
|
|
||||||
Canadian or US funds? Any funds are fine with me.
|
|
||||||
Whatever's easy for you.
|
|
||||||
|
|
||||||
|
|
||||||
Ray Larabie
|
|
||||||
drowsy@cheerful.com
|
|
||||||
or...
|
|
||||||
rlarabie@hotmail.com
|
|
||||||
|
|
||||||
-------------------------------
|
|
||||||
Larabie Fonts End-user license agreement software product from Larabie Fonts
|
|
||||||
---------------------------------------------------
|
|
||||||
|
|
||||||
SOFTWARE PRODUCT LICENSE
|
|
||||||
|
|
||||||
The SOFTWARE PRODUCT is protected by copyright laws and international copyright treaties,
|
|
||||||
as well as other intellectual property laws and treaties. The SOFTWARE PRODUCT is licensed,
|
|
||||||
not sold.
|
|
||||||
|
|
||||||
1. GRANT OF LICENSE. This document grants you the following rights:
|
|
||||||
|
|
||||||
- Installation and Use. You may install and use an unlimited number of copies of the
|
|
||||||
SOFTWARE PRODUCT.
|
|
||||||
|
|
||||||
- Reproduction and Distribution. You may reproduce and distribute an unlimited number of
|
|
||||||
copies of the SOFTWARE PRODUCT; provided that each copy shall be a true and complete copy,
|
|
||||||
including all copyright and trademark notices (if applicable) , and shall be accompanied by
|
|
||||||
a copy of this text file. Copies of the SOFTWARE PRODUCT may not be distributed for profit
|
|
||||||
either on a standalone basis or included as part of your own product unless by prior
|
|
||||||
permission of Larabie Fonts.
|
|
||||||
|
|
||||||
2. DESCRIPTION OF OTHER RIGHTS AND LIMITATIONS.
|
|
||||||
|
|
||||||
- Restrictions on Alteration. You may not rename, edit or create any derivative works from
|
|
||||||
the SOFTWARE PRODUCT, other than subsetting when embedding them in documents unless you have
|
|
||||||
permission from Larabie Fonts.
|
|
||||||
|
|
||||||
LIMITED WARRANTY
|
|
||||||
NO WARRANTIES. Larabie Fonts expressly disclaims any warranty for the SOFTWARE PRODUCT. The
|
|
||||||
SOFTWARE PRODUCT and any related documentation is provided "as is" without warranty of any
|
|
||||||
kind, either express or implied, including, without limitation, the implied warranties or
|
|
||||||
merchantability, fitness for a particular purpose, or noninfringement. The entire risk
|
|
||||||
arising out of use or performance of the SOFTWARE PRODUCT remains with you.
|
|
||||||
|
|
||||||
NO LIABILITY FOR CONSEQUENTIAL DAMAGES. In no event shall Larabie Fonts be liable for any
|
|
||||||
damages whatsoever (including, without limitation, damages for loss of business profits,
|
|
||||||
business interruption, loss of business information, or any other pecuniary loss) arising
|
|
||||||
out of the use of or inability to use this product, even if Larabie Fonts has been advised
|
|
||||||
of the possibility of such damages.
|
|
||||||
|
|
||||||
3. MISCELLANEOUS
|
|
||||||
|
|
||||||
Should you have any questions concerning this document, or if you desire to contact
|
|
||||||
Larabie Fonts for any reason, please contact rlarabie@hotmail.com , or write: Ray Larabie,
|
|
||||||
61 Wesley Ave. Mississauga, ON Canada L5H 2M8
|
|
|
@ -1,4 +0,0 @@
|
||||||
#Copyright ReportLab Europe Ltd. 2000-2004
|
|
||||||
#see license.txt for license details
|
|
||||||
#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/graphics/__init__.py
|
|
||||||
__version__=''' $Id: __init__.py 2385 2004-06-17 15:26:05Z rgbecker $ '''
|
|
|
@ -1,59 +0,0 @@
|
||||||
Symbologies Currently Supported
|
|
||||||
===============================
|
|
||||||
|
|
||||||
The following have, at a minimum, been verified to scan with a WASP
|
|
||||||
CCD barcode scanner (found one bug in my code, two in the scanner!).
|
|
||||||
Some have had more extensive testing:
|
|
||||||
|
|
||||||
Interleaved 2 of 5
|
|
||||||
MSI
|
|
||||||
Codabar
|
|
||||||
Code 39 (Standard Character Set)
|
|
||||||
Code 39 (Extended Character Set)
|
|
||||||
Code 93 (Standard Character Set)
|
|
||||||
Code 93 (Extended Character Set)
|
|
||||||
Code 128 (Automatic use of A, B, C, with some optimizations --
|
|
||||||
more coming)
|
|
||||||
|
|
||||||
The following have been tested by sending a fair number of mailpieces
|
|
||||||
with them:
|
|
||||||
|
|
||||||
USPS FIM
|
|
||||||
USPS POSTNET
|
|
||||||
|
|
||||||
The following have not been tested, as none of the scanners I have
|
|
||||||
access to support them:
|
|
||||||
|
|
||||||
Code 11
|
|
||||||
|
|
||||||
|
|
||||||
Future Plans, Consulting
|
|
||||||
========================
|
|
||||||
|
|
||||||
Soon:
|
|
||||||
|
|
||||||
I plan to implement the following linear codes soon:
|
|
||||||
|
|
||||||
UPC/EAN(/JAN)
|
|
||||||
|
|
||||||
The following are in progress, but I lack a way to verify them
|
|
||||||
(scanners I have access to don't read them), and I don't have complete
|
|
||||||
specs for the UK style.
|
|
||||||
|
|
||||||
Royal Mail 4-State (UK/NL/etc style, and Australian style)
|
|
||||||
|
|
||||||
Down the road, I'd like to do some 2D symbologies. Likely first candidate
|
|
||||||
is PDF417. MaxiCode, Aztec Code, and some of the stacked symbologies are
|
|
||||||
also good candidates.
|
|
||||||
|
|
||||||
I am available to do implementation of additional symbologies for hire.
|
|
||||||
Because I enjoy hacking barcodes, my rates for work in this particular
|
|
||||||
area are very low and are mainly to help offset costs associated with
|
|
||||||
obtaining related documents and/or to buy or gain access to scanning
|
|
||||||
equipment for symbologies if I don't already have a scanner that
|
|
||||||
supports them. Loans of equipment are also accepted.
|
|
||||||
|
|
||||||
For more information, contact:
|
|
||||||
|
|
||||||
Ty Sarna
|
|
||||||
tsarna@sarna.org
|
|
|
@ -1,24 +0,0 @@
|
||||||
See also README for some plans and info on consulting.
|
|
||||||
|
|
||||||
- Overall framework docs
|
|
||||||
|
|
||||||
- Finish Aussie Rules 4-State, for which I have complete docs now (yay
|
|
||||||
USPS and aupost.com.au for putting specs online. Too bad UKPost doesn't.)
|
|
||||||
|
|
||||||
- Investigate USPS PLANET stuff
|
|
||||||
|
|
||||||
- Higher-level objects that handle barcoded address blocks with correct
|
|
||||||
spacings and such (US, AU, UK/etc?)
|
|
||||||
|
|
||||||
- Even higher-level objects that represent mailpieces and place the
|
|
||||||
above-style address block objects, FIM codes, "place stamp here" blocks,
|
|
||||||
etc, correctly?
|
|
||||||
|
|
||||||
- Framework for laying out labels on various styles of n-up label
|
|
||||||
sheets, like Avery labels, etc?
|
|
||||||
|
|
||||||
- Decide if Plessey is worth doing. MSI-like (MSI is actually derived from
|
|
||||||
it), but specs were never formalized. Probably only useful for legacy
|
|
||||||
applications. If you need it, mail me.
|
|
||||||
|
|
||||||
- Get someone to test Code 11, or find a scanner that handles it
|
|
|
@ -1 +0,0 @@
|
||||||
0.9
|
|
|
@ -1,126 +0,0 @@
|
||||||
#
|
|
||||||
# Copyright (c) 1996-2000 Tyler C. Sarna <tsarna@sarna.org>
|
|
||||||
# All rights reserved.
|
|
||||||
#
|
|
||||||
# Redistribution and use in source and binary forms, with or without
|
|
||||||
# modification, are permitted provided that the following conditions
|
|
||||||
# are met:
|
|
||||||
# 1. Redistributions of source code must retain the above copyright
|
|
||||||
# notice, this list of conditions and the following disclaimer.
|
|
||||||
# 2. Redistributions in binary form must reproduce the above copyright
|
|
||||||
# notice, this list of conditions and the following disclaimer in the
|
|
||||||
# documentation and/or other materials provided with the distribution.
|
|
||||||
# 3. All advertising materials mentioning features or use of this software
|
|
||||||
# must display the following acknowledgement:
|
|
||||||
# This product includes software developed by Tyler C. Sarna.
|
|
||||||
# 4. Neither the name of the author nor the names of contributors
|
|
||||||
# may be used to endorse or promote products derived from this software
|
|
||||||
# without specific prior written permission.
|
|
||||||
#
|
|
||||||
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
|
|
||||||
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
||||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
|
||||||
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS
|
|
||||||
# BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
||||||
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
||||||
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
||||||
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
||||||
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
||||||
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
||||||
# POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
#
|
|
||||||
|
|
||||||
__version__ = '0.9'
|
|
||||||
|
|
||||||
def getCodes():
|
|
||||||
"""Returns a dict mapping code names to widgets"""
|
|
||||||
|
|
||||||
from widgets import (BarcodeI2of5,
|
|
||||||
BarcodeCode128,
|
|
||||||
BarcodeStandard93,
|
|
||||||
BarcodeExtended93,
|
|
||||||
BarcodeStandard39,
|
|
||||||
BarcodeExtended39,
|
|
||||||
BarcodeMSI,
|
|
||||||
BarcodeCodabar,
|
|
||||||
BarcodeCode11,
|
|
||||||
BarcodeFIM,
|
|
||||||
BarcodePOSTNET)
|
|
||||||
|
|
||||||
#newer codes will typically get their own module
|
|
||||||
from eanbc import Ean13BarcodeWidget, Ean8BarcodeWidget
|
|
||||||
|
|
||||||
|
|
||||||
#the module exports a dictionary of names to widgets, to make it easy for
|
|
||||||
#apps and doc tools to display information about them.
|
|
||||||
codes = {}
|
|
||||||
for widget in (
|
|
||||||
BarcodeI2of5,
|
|
||||||
BarcodeCode128,
|
|
||||||
BarcodeStandard93,
|
|
||||||
BarcodeExtended93,
|
|
||||||
BarcodeStandard39,
|
|
||||||
BarcodeExtended39,
|
|
||||||
BarcodeMSI,
|
|
||||||
BarcodeCodabar,
|
|
||||||
BarcodeCode11,
|
|
||||||
BarcodeFIM,
|
|
||||||
BarcodePOSTNET,
|
|
||||||
Ean13BarcodeWidget,
|
|
||||||
Ean8BarcodeWidget,
|
|
||||||
):
|
|
||||||
codeName = widget.codeName
|
|
||||||
codes[codeName] = widget
|
|
||||||
|
|
||||||
return codes
|
|
||||||
|
|
||||||
def getCodeNames():
|
|
||||||
"""Returns sorted list of supported bar code names"""
|
|
||||||
return sorted(getCodes().keys())
|
|
||||||
|
|
||||||
def createBarcodeDrawing(codeName, **options):
|
|
||||||
"""This creates and returns a drawing with a barcode.
|
|
||||||
"""
|
|
||||||
from reportlab.graphics.shapes import Drawing, Group
|
|
||||||
|
|
||||||
codes = getCodes()
|
|
||||||
bcc = codes[codeName]
|
|
||||||
width = options.pop('width',None)
|
|
||||||
height = options.pop('height',None)
|
|
||||||
isoScale = options.pop('isoScale',0)
|
|
||||||
kw = {}
|
|
||||||
for k,v in options.iteritems():
|
|
||||||
if k.startswith('_') or k in bcc._attrMap: kw[k] = v
|
|
||||||
bc = bcc(**kw)
|
|
||||||
|
|
||||||
#size it after setting the data
|
|
||||||
x1, y1, x2, y2 = bc.getBounds()
|
|
||||||
w = float(x2 - x1)
|
|
||||||
h = float(y2 - y1)
|
|
||||||
sx = width not in ('auto',None)
|
|
||||||
sy = height not in ('auto',None)
|
|
||||||
if sx or sy:
|
|
||||||
sx = sx and width/w or 1.0
|
|
||||||
sy = sy and height/h or 1.0
|
|
||||||
if isoScale:
|
|
||||||
if sx<1.0 and sy<1.0:
|
|
||||||
sx = sy = max(sx,sy)
|
|
||||||
else:
|
|
||||||
sx = sy = min(sx,sy)
|
|
||||||
|
|
||||||
w *= sx
|
|
||||||
h *= sy
|
|
||||||
else:
|
|
||||||
sx = sy = 1
|
|
||||||
|
|
||||||
#bc.x = -sx*x1
|
|
||||||
#bc.y = -sy*y1
|
|
||||||
d = Drawing(width=w,height=h,transform=[sx,0,0,sy,-sx*x1,-sy*y1])
|
|
||||||
d.add(bc, "_bc")
|
|
||||||
return d
|
|
||||||
|
|
||||||
def createBarcodeImageInMemory(codeName, **options):
|
|
||||||
"""This creates and returns barcode as an image in memory.
|
|
||||||
"""
|
|
||||||
d = createBarcodeDrawing(codeName, **options)
|
|
||||||
return d.asString(format)
|
|
|
@ -1,322 +0,0 @@
|
||||||
#
|
|
||||||
# Copyright (c) 2000 Tyler C. Sarna <tsarna@sarna.org>
|
|
||||||
# All rights reserved.
|
|
||||||
#
|
|
||||||
# Redistribution and use in source and binary forms, with or without
|
|
||||||
# modification, are permitted provided that the following conditions
|
|
||||||
# are met:
|
|
||||||
# 1. Redistributions of source code must retain the above copyright
|
|
||||||
# notice, this list of conditions and the following disclaimer.
|
|
||||||
# 2. Redistributions in binary form must reproduce the above copyright
|
|
||||||
# notice, this list of conditions and the following disclaimer in the
|
|
||||||
# documentation and/or other materials provided with the distribution.
|
|
||||||
# 3. All advertising materials mentioning features or use of this software
|
|
||||||
# must display the following acknowledgement:
|
|
||||||
# This product includes software developed by Tyler C. Sarna.
|
|
||||||
# 4. Neither the name of the author nor the names of contributors
|
|
||||||
# may be used to endorse or promote products derived from this software
|
|
||||||
# without specific prior written permission.
|
|
||||||
#
|
|
||||||
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
|
|
||||||
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
||||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
|
||||||
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS
|
|
||||||
# BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
||||||
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
||||||
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
||||||
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
||||||
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
||||||
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
||||||
# POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
#
|
|
||||||
|
|
||||||
from reportlab.lib.units import inch
|
|
||||||
from common import MultiWidthBarcode
|
|
||||||
from string import digits
|
|
||||||
|
|
||||||
_patterns = {
|
|
||||||
0 : 'BaBbBb', 1 : 'BbBaBb', 2 : 'BbBbBa',
|
|
||||||
3 : 'AbAbBc', 4 : 'AbAcBb', 5 : 'AcAbBb',
|
|
||||||
6 : 'AbBbAc', 7 : 'AbBcAb', 8 : 'AcBbAb',
|
|
||||||
9 : 'BbAbAc', 10 : 'BbAcAb', 11 : 'BcAbAb',
|
|
||||||
12 : 'AaBbCb', 13 : 'AbBaCb', 14 : 'AbBbCa',
|
|
||||||
15 : 'AaCbBb', 16 : 'AbCaBb', 17 : 'AbCbBa',
|
|
||||||
18 : 'BbCbAa', 19 : 'BbAaCb', 20 : 'BbAbCa',
|
|
||||||
21 : 'BaCbAb', 22 : 'BbCaAb', 23 : 'CaBaCa',
|
|
||||||
24 : 'CaAbBb', 25 : 'CbAaBb', 26 : 'CbAbBa',
|
|
||||||
27 : 'CaBbAb', 28 : 'CbBaAb', 29 : 'CbBbAa',
|
|
||||||
30 : 'BaBaBc', 31 : 'BaBcBa', 32 : 'BcBaBa',
|
|
||||||
33 : 'AaAcBc', 34 : 'AcAaBc', 35 : 'AcAcBa',
|
|
||||||
36 : 'AaBcAc', 37 : 'AcBaAc', 38 : 'AcBcAa',
|
|
||||||
39 : 'BaAcAc', 40 : 'BcAaAc', 41 : 'BcAcAa',
|
|
||||||
42 : 'AaBaCc', 43 : 'AaBcCa', 44 : 'AcBaCa',
|
|
||||||
45 : 'AaCaBc', 46 : 'AaCcBa', 47 : 'AcCaBa',
|
|
||||||
48 : 'CaCaBa', 49 : 'BaAcCa', 50 : 'BcAaCa',
|
|
||||||
51 : 'BaCaAc', 52 : 'BaCcAa', 53 : 'BaCaCa',
|
|
||||||
54 : 'CaAaBc', 55 : 'CaAcBa', 56 : 'CcAaBa',
|
|
||||||
57 : 'CaBaAc', 58 : 'CaBcAa', 59 : 'CcBaAa',
|
|
||||||
60 : 'CaDaAa', 61 : 'BbAdAa', 62 : 'DcAaAa',
|
|
||||||
63 : 'AaAbBd', 64 : 'AaAdBb', 65 : 'AbAaBd',
|
|
||||||
66 : 'AbAdBa', 67 : 'AdAaBb', 68 : 'AdAbBa',
|
|
||||||
69 : 'AaBbAd', 70 : 'AaBdAb', 71 : 'AbBaAd',
|
|
||||||
72 : 'AbBdAa', 73 : 'AdBaAb', 74 : 'AdBbAa',
|
|
||||||
75 : 'BdAbAa', 76 : 'BbAaAd', 77 : 'DaCaAa',
|
|
||||||
78 : 'BdAaAb', 79 : 'AcDaAa', 80 : 'AaAbDb',
|
|
||||||
81 : 'AbAaDb', 82 : 'AbAbDa', 83 : 'AaDbAb',
|
|
||||||
84 : 'AbDaAb', 85 : 'AbDbAa', 86 : 'DaAbAb',
|
|
||||||
87 : 'DbAaAb', 88 : 'DbAbAa', 89 : 'BaBaDa',
|
|
||||||
90 : 'BaDaBa', 91 : 'DaBaBa', 92 : 'AaAaDc',
|
|
||||||
93 : 'AaAcDa', 94 : 'AcAaDa', 95 : 'AaDaAc',
|
|
||||||
96 : 'AaDcAa', 97 : 'DaAaAc', 98 : 'DaAcAa',
|
|
||||||
99 : 'AaCaDa', 100 : 'AaDaCa', 101 : 'CaAaDa',
|
|
||||||
102 : 'DaAaCa', 103 : 'BaAdAb', 104 : 'BaAbAd',
|
|
||||||
105 : 'BaAbCb', 106 : 'BcCaAaB'
|
|
||||||
}
|
|
||||||
|
|
||||||
starta, startb, startc, stop = 103, 104, 105, 106
|
|
||||||
|
|
||||||
seta = {
|
|
||||||
' ' : 0, '!' : 1, '"' : 2, '#' : 3,
|
|
||||||
'$' : 4, '%' : 5, '&' : 6, '\'' : 7,
|
|
||||||
'(' : 8, ')' : 9, '*' : 10, '+' : 11,
|
|
||||||
',' : 12, '-' : 13, '.' : 14, '/' : 15,
|
|
||||||
'0' : 16, '1' : 17, '2' : 18, '3' : 19,
|
|
||||||
'4' : 20, '5' : 21, '6' : 22, '7' : 23,
|
|
||||||
'8' : 24, '9' : 25, ':' : 26, ';' : 27,
|
|
||||||
'<' : 28, '=' : 29, '>' : 30, '?' : 31,
|
|
||||||
'@' : 32, 'A' : 33, 'B' : 34, 'C' : 35,
|
|
||||||
'D' : 36, 'E' : 37, 'F' : 38, 'G' : 39,
|
|
||||||
'H' : 40, 'I' : 41, 'J' : 42, 'K' : 43,
|
|
||||||
'L' : 44, 'M' : 45, 'N' : 46, 'O' : 47,
|
|
||||||
'P' : 48, 'Q' : 49, 'R' : 50, 'S' : 51,
|
|
||||||
'T' : 52, 'U' : 53, 'V' : 54, 'W' : 55,
|
|
||||||
'X' : 56, 'Y' : 57, 'Z' : 58, '[' : 59,
|
|
||||||
'\\' : 60, ']' : 61, '^' : 62, '_' : 63,
|
|
||||||
'\x00' : 64, '\x01' : 65, '\x02' : 66, '\x03' : 67,
|
|
||||||
'\x04' : 68, '\x05' : 69, '\x06' : 70, '\x07' : 71,
|
|
||||||
'\x08' : 72, '\x09' : 73, '\x0a' : 74, '\x0b' : 75,
|
|
||||||
'\x0c' : 76, '\x0d' : 77, '\x0e' : 78, '\x0f' : 79,
|
|
||||||
'\x10' : 80, '\x11' : 81, '\x12' : 82, '\x13' : 83,
|
|
||||||
'\x14' : 84, '\x15' : 85, '\x16' : 86, '\x17' : 87,
|
|
||||||
'\x18' : 88, '\x19' : 89, '\x1a' : 90, '\x1b' : 91,
|
|
||||||
'\x1c' : 92, '\x1d' : 93, '\x1e' : 94, '\x1f' : 95,
|
|
||||||
'\xf3' : 96, '\xf2' : 97, 'SHIFT' : 98, 'TO_C' : 99,
|
|
||||||
'TO_B' : 100, '\xf4' : 101, '\xf1' : 102
|
|
||||||
}
|
|
||||||
|
|
||||||
setb = {
|
|
||||||
' ' : 0, '!' : 1, '"' : 2, '#' : 3,
|
|
||||||
'$' : 4, '%' : 5, '&' : 6, '\'' : 7,
|
|
||||||
'(' : 8, ')' : 9, '*' : 10, '+' : 11,
|
|
||||||
',' : 12, '-' : 13, '.' : 14, '/' : 15,
|
|
||||||
'0' : 16, '1' : 17, '2' : 18, '3' : 19,
|
|
||||||
'4' : 20, '5' : 21, '6' : 22, '7' : 23,
|
|
||||||
'8' : 24, '9' : 25, ':' : 26, ';' : 27,
|
|
||||||
'<' : 28, '=' : 29, '>' : 30, '?' : 31,
|
|
||||||
'@' : 32, 'A' : 33, 'B' : 34, 'C' : 35,
|
|
||||||
'D' : 36, 'E' : 37, 'F' : 38, 'G' : 39,
|
|
||||||
'H' : 40, 'I' : 41, 'J' : 42, 'K' : 43,
|
|
||||||
'L' : 44, 'M' : 45, 'N' : 46, 'O' : 47,
|
|
||||||
'P' : 48, 'Q' : 49, 'R' : 50, 'S' : 51,
|
|
||||||
'T' : 52, 'U' : 53, 'V' : 54, 'W' : 55,
|
|
||||||
'X' : 56, 'Y' : 57, 'Z' : 58, '[' : 59,
|
|
||||||
'\\' : 60, ']' : 61, '^' : 62, '_' : 63,
|
|
||||||
'`' : 64, 'a' : 65, 'b' : 66, 'c' : 67,
|
|
||||||
'd' : 68, 'e' : 69, 'f' : 70, 'g' : 71,
|
|
||||||
'h' : 72, 'i' : 73, 'j' : 74, 'k' : 75,
|
|
||||||
'l' : 76, 'm' : 77, 'n' : 78, 'o' : 79,
|
|
||||||
'p' : 80, 'q' : 81, 'r' : 82, 's' : 83,
|
|
||||||
't' : 84, 'u' : 85, 'v' : 86, 'w' : 87,
|
|
||||||
'x' : 88, 'y' : 89, 'z' : 90, '{' : 91,
|
|
||||||
'|' : 92, '}' : 93, '~' : 94, '\x7f' : 95,
|
|
||||||
'\xf3' : 96, '\xf2' : 97, 'SHIFT' : 98, 'TO_C' : 99,
|
|
||||||
'\xf4' : 100, 'TO_A' : 101, '\xf1' : 102
|
|
||||||
}
|
|
||||||
|
|
||||||
setc = {
|
|
||||||
'00': 0, '01': 1, '02': 2, '03': 3, '04': 4,
|
|
||||||
'05': 5, '06': 6, '07': 7, '08': 8, '09': 9,
|
|
||||||
'10':10, '11':11, '12':12, '13':13, '14':14,
|
|
||||||
'15':15, '16':16, '17':17, '18':18, '19':19,
|
|
||||||
'20':20, '21':21, '22':22, '23':23, '24':24,
|
|
||||||
'25':25, '26':26, '27':27, '28':28, '29':29,
|
|
||||||
'30':30, '31':31, '32':32, '33':33, '34':34,
|
|
||||||
'35':35, '36':36, '37':37, '38':38, '39':39,
|
|
||||||
'40':40, '41':41, '42':42, '43':43, '44':44,
|
|
||||||
'45':45, '46':46, '47':47, '48':48, '49':49,
|
|
||||||
'50':50, '51':51, '52':52, '53':53, '54':54,
|
|
||||||
'55':55, '56':56, '57':57, '58':58, '59':59,
|
|
||||||
'60':60, '61':61, '62':62, '63':63, '64':64,
|
|
||||||
'65':65, '66':66, '67':67, '68':68, '69':69,
|
|
||||||
'70':70, '71':71, '72':72, '73':73, '74':74,
|
|
||||||
'75':75, '76':76, '77':77, '78':78, '79':79,
|
|
||||||
'80':80, '81':81, '82':82, '83':83, '84':84,
|
|
||||||
'85':85, '86':86, '87':87, '88':88, '89':89,
|
|
||||||
'90':90, '91':91, '92':92, '93':93, '94':94,
|
|
||||||
'95':95, '96':96, '97':97, '98':98, '99':99,
|
|
||||||
|
|
||||||
'TO_B' : 100, 'TO_A' : 101, '\xf1' : 102
|
|
||||||
}
|
|
||||||
|
|
||||||
setmap = {
|
|
||||||
'TO_A' : (seta, setb),
|
|
||||||
'TO_B' : (setb, seta),
|
|
||||||
'TO_C' : (setc, None),
|
|
||||||
'START_A' : (starta, seta, setb),
|
|
||||||
'START_B' : (startb, setb, seta),
|
|
||||||
'START_C' : (startc, setc, None),
|
|
||||||
}
|
|
||||||
tos = setmap.keys()
|
|
||||||
|
|
||||||
class Code128(MultiWidthBarcode):
|
|
||||||
"""
|
|
||||||
Code 128 is a very compact symbology that can encode the entire
|
|
||||||
128 character ASCII set, plus 4 special control codes,
|
|
||||||
(FNC1-FNC4, expressed in the input string as \xf1 to \xf4).
|
|
||||||
Code 128 can also encode digits at double density (2 per byte)
|
|
||||||
and has a mandatory checksum. Code 128 is well supported and
|
|
||||||
commonly used -- for example, by UPS for tracking labels.
|
|
||||||
|
|
||||||
Because of these qualities, Code 128 is probably the best choice
|
|
||||||
for a linear symbology today (assuming you have a choice).
|
|
||||||
|
|
||||||
Options that may be passed to constructor:
|
|
||||||
|
|
||||||
value (int, or numeric string. required.):
|
|
||||||
The value to encode.
|
|
||||||
|
|
||||||
barWidth (float, default .0075):
|
|
||||||
X-Dimension, or width of the smallest element
|
|
||||||
Minumum is .0075 inch (7.5 mils).
|
|
||||||
|
|
||||||
barHeight (float, see default below):
|
|
||||||
Height of the symbol. Default is the height of the two
|
|
||||||
bearer bars (if they exist) plus the greater of .25 inch
|
|
||||||
or .15 times the symbol's length.
|
|
||||||
|
|
||||||
quiet (bool, default 1):
|
|
||||||
Wether to include quiet zones in the symbol.
|
|
||||||
|
|
||||||
lquiet (float, see default below):
|
|
||||||
Quiet zone size to left of code, if quiet is true.
|
|
||||||
Default is the greater of .25 inch, or 10 barWidth
|
|
||||||
|
|
||||||
rquiet (float, defaults as above):
|
|
||||||
Quiet zone size to right left of code, if quiet is true.
|
|
||||||
|
|
||||||
Sources of Information on Code 128:
|
|
||||||
|
|
||||||
http://www.semiconductor.agilent.com/barcode/sg/Misc/code_128.html
|
|
||||||
http://www.adams1.com/pub/russadam/128code.html
|
|
||||||
http://www.barcodeman.com/c128.html
|
|
||||||
|
|
||||||
Official Spec, "ANSI/AIM BC4-1999, ISS" is available for US$45 from
|
|
||||||
http://www.aimglobal.org/aimstore/
|
|
||||||
"""
|
|
||||||
barWidth = inch * 0.0075
|
|
||||||
lquiet = None
|
|
||||||
rquiet = None
|
|
||||||
quiet = 1
|
|
||||||
barHeight = None
|
|
||||||
def __init__(self, value='', **args):
|
|
||||||
|
|
||||||
if type(value) is type(1):
|
|
||||||
value = str(value)
|
|
||||||
|
|
||||||
for (k, v) in args.items():
|
|
||||||
setattr(self, k, v)
|
|
||||||
|
|
||||||
if self.quiet:
|
|
||||||
if self.lquiet is None:
|
|
||||||
self.lquiet = max(inch * 0.25, self.barWidth * 10.0)
|
|
||||||
if self.rquiet is None:
|
|
||||||
self.rquiet = max(inch * 0.25, self.barWidth * 10.0)
|
|
||||||
else:
|
|
||||||
self.lquiet = self.rquiet = 0.0
|
|
||||||
|
|
||||||
MultiWidthBarcode.__init__(self, value)
|
|
||||||
|
|
||||||
def validate(self):
|
|
||||||
vval = ""
|
|
||||||
self.valid = 1
|
|
||||||
for c in self.value:
|
|
||||||
if ord(c) > 127 and c not in '\xf1\xf2\xf3\xf4':
|
|
||||||
self.valid = 0
|
|
||||||
continue
|
|
||||||
vval = vval + c
|
|
||||||
self.validated = vval
|
|
||||||
return vval
|
|
||||||
|
|
||||||
def _trailingDigitsToC(self, l):
|
|
||||||
# Optimization: trailing digits -> set C double-digits
|
|
||||||
c = 1
|
|
||||||
savings = -1 # the TO_C costs one character
|
|
||||||
rl = ['STOP']
|
|
||||||
while c < len(l):
|
|
||||||
i = (-c - 1)
|
|
||||||
if l[i] == '\xf1':
|
|
||||||
c = c + 1
|
|
||||||
rl.insert(0, '\xf1')
|
|
||||||
continue
|
|
||||||
elif len(l[i]) == 1 and l[i] in digits \
|
|
||||||
and len(l[i-1]) == 1 and l[i-1] in digits:
|
|
||||||
c += 2
|
|
||||||
savings += 1
|
|
||||||
rl.insert(0, l[i-1] + l[i])
|
|
||||||
continue
|
|
||||||
else:
|
|
||||||
break
|
|
||||||
if savings > 0:
|
|
||||||
return l[:-c] + ['TO_C'] + rl
|
|
||||||
else:
|
|
||||||
return l
|
|
||||||
|
|
||||||
def encode(self):
|
|
||||||
# First, encode using only B
|
|
||||||
s = self.validated
|
|
||||||
l = ['START_B']
|
|
||||||
for c in s:
|
|
||||||
if not setb.has_key(c):
|
|
||||||
l = l + ['TO_A', c, 'TO_B']
|
|
||||||
else:
|
|
||||||
l.append(c)
|
|
||||||
l.append('STOP')
|
|
||||||
|
|
||||||
l = self._trailingDigitsToC(l)
|
|
||||||
|
|
||||||
# Finally, replace START_X,TO_Y with START_Y
|
|
||||||
if l[1] in tos:
|
|
||||||
l[:2] = ['START_' + l[1][-1]]
|
|
||||||
|
|
||||||
# print `l`
|
|
||||||
|
|
||||||
# encode into numbers
|
|
||||||
start, set, shset = setmap[l[0]]
|
|
||||||
e = [start]
|
|
||||||
|
|
||||||
l = l[1:-1]
|
|
||||||
while l:
|
|
||||||
c = l[0]
|
|
||||||
if c == 'SHIFT':
|
|
||||||
e = e + [set[c], shset[l[1]]]
|
|
||||||
l = l[2:]
|
|
||||||
elif c in tos:
|
|
||||||
e.append(set[c])
|
|
||||||
set, shset = setmap[c]
|
|
||||||
l = l[1:]
|
|
||||||
else:
|
|
||||||
e.append(set[c])
|
|
||||||
l = l[1:]
|
|
||||||
|
|
||||||
c = e[0]
|
|
||||||
for i in range(1, len(e)):
|
|
||||||
c = c + i * e[i]
|
|
||||||
self.encoded = e + [c % 103, stop]
|
|
||||||
return self.encoded
|
|
||||||
|
|
||||||
def decompose(self):
|
|
||||||
self.decomposed = ''.join([_patterns[c] for c in self.encoded])
|
|
||||||
return self.decomposed
|
|
||||||
|
|
||||||
def _humanText(self):
|
|
||||||
return self.value
|
|
|
@ -1,248 +0,0 @@
|
||||||
#
|
|
||||||
# Copyright (c) 1996-2000 Tyler C. Sarna <tsarna@sarna.org>
|
|
||||||
# All rights reserved.
|
|
||||||
#
|
|
||||||
# Redistribution and use in source and binary forms, with or without
|
|
||||||
# modification, are permitted provided that the following conditions
|
|
||||||
# are met:
|
|
||||||
# 1. Redistributions of source code must retain the above copyright
|
|
||||||
# notice, this list of conditions and the following disclaimer.
|
|
||||||
# 2. Redistributions in binary form must reproduce the above copyright
|
|
||||||
# notice, this list of conditions and the following disclaimer in the
|
|
||||||
# documentation and/or other materials provided with the distribution.
|
|
||||||
# 3. All advertising materials mentioning features or use of this software
|
|
||||||
# must display the following acknowledgement:
|
|
||||||
# This product includes software developed by Tyler C. Sarna.
|
|
||||||
# 4. Neither the name of the author nor the names of contributors
|
|
||||||
# may be used to endorse or promote products derived from this software
|
|
||||||
# without specific prior written permission.
|
|
||||||
#
|
|
||||||
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
|
|
||||||
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
||||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
|
||||||
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS
|
|
||||||
# BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
||||||
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
||||||
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
||||||
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
||||||
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
||||||
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
||||||
# POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
#
|
|
||||||
|
|
||||||
from reportlab.lib.units import inch
|
|
||||||
from common import Barcode
|
|
||||||
import string
|
|
||||||
|
|
||||||
_patterns = {
|
|
||||||
'0': ("bsbSBsBsb", 0), '1': ("BsbSbsbsB", 1),
|
|
||||||
'2': ("bsBSbsbsB", 2), '3': ("BsBSbsbsb", 3),
|
|
||||||
'4': ("bsbSBsbsB", 4), '5': ("BsbSBsbsb", 5),
|
|
||||||
'6': ("bsBSBsbsb", 6), '7': ("bsbSbsBsB", 7),
|
|
||||||
'8': ("BsbSbsBsb", 8), '9': ("bsBSbsBsb", 9),
|
|
||||||
'A': ("BsbsbSbsB", 10), 'B': ("bsBsbSbsB", 11),
|
|
||||||
'C': ("BsBsbSbsb", 12), 'D': ("bsbsBSbsB", 13),
|
|
||||||
'E': ("BsbsBSbsb", 14), 'F': ("bsBsBSbsb", 15),
|
|
||||||
'G': ("bsbsbSBsB", 16), 'H': ("BsbsbSBsb", 17),
|
|
||||||
'I': ("bsBsbSBsb", 18), 'J': ("bsbsBSBsb", 19),
|
|
||||||
'K': ("BsbsbsbSB", 20), 'L': ("bsBsbsbSB", 21),
|
|
||||||
'M': ("BsBsbsbSb", 22), 'N': ("bsbsBsbSB", 23),
|
|
||||||
'O': ("BsbsBsbSb", 24), 'P': ("bsBsBsbSb", 25),
|
|
||||||
'Q': ("bsbsbsBSB", 26), 'R': ("BsbsbsBSb", 27),
|
|
||||||
'S': ("bsBsbsBSb", 28), 'T': ("bsbsBsBSb", 29),
|
|
||||||
'U': ("BSbsbsbsB", 30), 'V': ("bSBsbsbsB", 31),
|
|
||||||
'W': ("BSBsbsbsb", 32), 'X': ("bSbsBsbsB", 33),
|
|
||||||
'Y': ("BSbsBsbsb", 34), 'Z': ("bSBsBsbsb", 35),
|
|
||||||
'-': ("bSbsbsBsB", 36), '.': ("BSbsbsBsb", 37),
|
|
||||||
' ': ("bSBsbsBsb", 38), '*': ("bSbsBsBsb", 39),
|
|
||||||
'$': ("bSbSbSbsb", 40), '/': ("bSbSbsbSb", 41),
|
|
||||||
'+': ("bSbsbSbSb", 42), '%': ("bsbSbSbSb", 43)
|
|
||||||
}
|
|
||||||
|
|
||||||
_valchars = [
|
|
||||||
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A',
|
|
||||||
'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L',
|
|
||||||
'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W',
|
|
||||||
'X', 'Y', 'Z', '-', '.', ' ', '*', '$', '/', '+', '%'
|
|
||||||
]
|
|
||||||
|
|
||||||
_extended = {
|
|
||||||
'\0': "%U", '\01': "$A", '\02': "$B", '\03': "$C",
|
|
||||||
'\04': "$D", '\05': "$E", '\06': "$F", '\07': "$G",
|
|
||||||
'\010': "$H", '\011': "$I", '\012': "$J", '\013': "$K",
|
|
||||||
'\014': "$L", '\015': "$M", '\016': "$N", '\017': "$O",
|
|
||||||
'\020': "$P", '\021': "$Q", '\022': "$R", '\023': "$S",
|
|
||||||
'\024': "$T", '\025': "$U", '\026': "$V", '\027': "$W",
|
|
||||||
'\030': "$X", '\031': "$Y", '\032': "$Z", '\033': "%A",
|
|
||||||
'\034': "%B", '\035': "%C", '\036': "%D", '\037': "%E",
|
|
||||||
'!': "/A", '"': "/B", '#': "/C", '$': "/D",
|
|
||||||
'%': "/E", '&': "/F", '\'': "/G", '(': "/H",
|
|
||||||
')': "/I", '*': "/J", '+': "/K", ',': "/L",
|
|
||||||
'/': "/O", ':': "/Z", ';': "%F", '<': "%G",
|
|
||||||
'=': "%H", '>': "%I", '?': "%J", '@': "%V",
|
|
||||||
'[': "%K", '\\': "%L", ']': "%M", '^': "%N",
|
|
||||||
'_': "%O", '`': "%W", 'a': "+A", 'b': "+B",
|
|
||||||
'c': "+C", 'd': "+D", 'e': "+E", 'f': "+F",
|
|
||||||
'g': "+G", 'h': "+H", 'i': "+I", 'j': "+J",
|
|
||||||
'k': "+K", 'l': "+L", 'm': "+M", 'n': "+N",
|
|
||||||
'o': "+O", 'p': "+P", 'q': "+Q", 'r': "+R",
|
|
||||||
's': "+S", 't': "+T", 'u': "+U", 'v': "+V",
|
|
||||||
'w': "+W", 'x': "+X", 'y': "+Y", 'z': "+Z",
|
|
||||||
'{': "%P", '|': "%Q", '}': "%R", '~': "%S",
|
|
||||||
'\177': "%T"
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
_stdchrs = string.digits + string.uppercase + "-. *$/+%"
|
|
||||||
_extchrs = _stdchrs + string.lowercase + \
|
|
||||||
"\000\001\002\003\004\005\006\007\010\011\012\013\014\015\016\017" + \
|
|
||||||
"\020\021\022\023\024\025\026\027\030\031\032\033\034\035\036\037" + \
|
|
||||||
"!'#&\"(),:;<=>?@[\\]^_`{|}~\177"
|
|
||||||
|
|
||||||
def _encode39(value, cksum, stop):
|
|
||||||
v = sum([_patterns[c][1] for c in value]) % 43
|
|
||||||
if cksum:
|
|
||||||
value += _valchars[v]
|
|
||||||
if stop: value = '*'+value+'*'
|
|
||||||
return value
|
|
||||||
|
|
||||||
class _Code39Base(Barcode):
|
|
||||||
barWidth = inch * 0.0075
|
|
||||||
lquiet = None
|
|
||||||
rquiet = None
|
|
||||||
quiet = 1
|
|
||||||
gap = None
|
|
||||||
barHeight = None
|
|
||||||
ratio = 2.2
|
|
||||||
checksum = 1
|
|
||||||
bearers = 0.0
|
|
||||||
stop = 1
|
|
||||||
def __init__(self, value = "", **args):
|
|
||||||
for k, v in args.iteritems():
|
|
||||||
setattr(self, k, v)
|
|
||||||
|
|
||||||
if self.quiet:
|
|
||||||
if self.lquiet is None:
|
|
||||||
self.lquiet = max(inch * 0.25, self.barWidth * 10.0)
|
|
||||||
self.rquiet = max(inch * 0.25, self.barWidth * 10.0)
|
|
||||||
else:
|
|
||||||
self.lquiet = self.rquiet = 0.0
|
|
||||||
|
|
||||||
Barcode.__init__(self, value)
|
|
||||||
|
|
||||||
def decompose(self):
|
|
||||||
dval = ""
|
|
||||||
for c in self.encoded:
|
|
||||||
dval = dval + _patterns[c][0] + 'i'
|
|
||||||
self.decomposed = dval[:-1]
|
|
||||||
return self.decomposed
|
|
||||||
|
|
||||||
def _humanText(self):
|
|
||||||
return self.stop and self.encoded[1:-1] or self.encoded
|
|
||||||
|
|
||||||
class Standard39(_Code39Base):
|
|
||||||
"""
|
|
||||||
Options that may be passed to constructor:
|
|
||||||
|
|
||||||
value (int, or numeric string. required.):
|
|
||||||
The value to encode.
|
|
||||||
|
|
||||||
barWidth (float, default .0075):
|
|
||||||
X-Dimension, or width of the smallest element
|
|
||||||
Minumum is .0075 inch (7.5 mils).
|
|
||||||
|
|
||||||
ratio (float, default 2.2):
|
|
||||||
The ratio of wide elements to narrow elements.
|
|
||||||
Must be between 2.0 and 3.0 (or 2.2 and 3.0 if the
|
|
||||||
barWidth is greater than 20 mils (.02 inch))
|
|
||||||
|
|
||||||
gap (float or None, default None):
|
|
||||||
width of intercharacter gap. None means "use barWidth".
|
|
||||||
|
|
||||||
barHeight (float, see default below):
|
|
||||||
Height of the symbol. Default is the height of the two
|
|
||||||
bearer bars (if they exist) plus the greater of .25 inch
|
|
||||||
or .15 times the symbol's length.
|
|
||||||
|
|
||||||
checksum (bool, default 1):
|
|
||||||
Wether to compute and include the check digit
|
|
||||||
|
|
||||||
bearers (float, in units of barWidth. default 0):
|
|
||||||
Height of bearer bars (horizontal bars along the top and
|
|
||||||
bottom of the barcode). Default is 0 (no bearers).
|
|
||||||
|
|
||||||
quiet (bool, default 1):
|
|
||||||
Wether to include quiet zones in the symbol.
|
|
||||||
|
|
||||||
lquiet (float, see default below):
|
|
||||||
Quiet zone size to left of code, if quiet is true.
|
|
||||||
Default is the greater of .25 inch, or .15 times the symbol's
|
|
||||||
length.
|
|
||||||
|
|
||||||
rquiet (float, defaults as above):
|
|
||||||
Quiet zone size to right left of code, if quiet is true.
|
|
||||||
|
|
||||||
stop (bool, default 1):
|
|
||||||
Whether to include start/stop symbols.
|
|
||||||
|
|
||||||
Sources of Information on Code 39:
|
|
||||||
|
|
||||||
http://www.semiconductor.agilent.com/barcode/sg/Misc/code_39.html
|
|
||||||
http://www.adams1.com/pub/russadam/39code.html
|
|
||||||
http://www.barcodeman.com/c39_1.html
|
|
||||||
|
|
||||||
Official Spec, "ANSI/AIM BC1-1995, USS" is available for US$45 from
|
|
||||||
http://www.aimglobal.org/aimstore/
|
|
||||||
"""
|
|
||||||
def validate(self):
|
|
||||||
vval = ""
|
|
||||||
self.valid = 1
|
|
||||||
for c in self.value:
|
|
||||||
if c in string.lowercase:
|
|
||||||
c = string.upper(c)
|
|
||||||
if c not in _stdchrs:
|
|
||||||
self.valid = 0
|
|
||||||
continue
|
|
||||||
vval = vval + c
|
|
||||||
self.validated = vval
|
|
||||||
return vval
|
|
||||||
|
|
||||||
def encode(self):
|
|
||||||
self.encoded = _encode39(self.validated, self.checksum, self.stop)
|
|
||||||
return self.encoded
|
|
||||||
|
|
||||||
class Extended39(_Code39Base):
|
|
||||||
"""
|
|
||||||
Extended Code 39 is a convention for encoding additional characters
|
|
||||||
not present in stanmdard Code 39 by using pairs of characters to
|
|
||||||
represent the characters missing in Standard Code 39.
|
|
||||||
|
|
||||||
See Standard39 for arguments.
|
|
||||||
|
|
||||||
Sources of Information on Extended Code 39:
|
|
||||||
|
|
||||||
http://www.semiconductor.agilent.com/barcode/sg/Misc/xcode_39.html
|
|
||||||
http://www.barcodeman.com/c39_ext.html
|
|
||||||
"""
|
|
||||||
def validate(self):
|
|
||||||
vval = ""
|
|
||||||
self.valid = 1
|
|
||||||
for c in self.value:
|
|
||||||
if c not in _extchrs:
|
|
||||||
self.valid = 0
|
|
||||||
continue
|
|
||||||
vval = vval + c
|
|
||||||
self.validated = vval
|
|
||||||
return vval
|
|
||||||
|
|
||||||
def encode(self):
|
|
||||||
self.encoded = ""
|
|
||||||
for c in self.validated:
|
|
||||||
if _extended.has_key(c):
|
|
||||||
self.encoded = self.encoded + _extended[c]
|
|
||||||
elif c in _stdchrs:
|
|
||||||
self.encoded = self.encoded + c
|
|
||||||
else:
|
|
||||||
raise ValueError
|
|
||||||
self.encoded = _encode39(self.encoded, self.checksum,self.stop)
|
|
||||||
return self.encoded
|
|
|
@ -1,236 +0,0 @@
|
||||||
#
|
|
||||||
# Copyright (c) 2000 Tyler C. Sarna <tsarna@sarna.org>
|
|
||||||
# All rights reserved.
|
|
||||||
#
|
|
||||||
# Redistribution and use in source and binary forms, with or without
|
|
||||||
# modification, are permitted provided that the following conditions
|
|
||||||
# are met:
|
|
||||||
# 1. Redistributions of source code must retain the above copyright
|
|
||||||
# notice, this list of conditions and the following disclaimer.
|
|
||||||
# 2. Redistributions in binary form must reproduce the above copyright
|
|
||||||
# notice, this list of conditions and the following disclaimer in the
|
|
||||||
# documentation and/or other materials provided with the distribution.
|
|
||||||
# 3. All advertising materials mentioning features or use of this software
|
|
||||||
# must display the following acknowledgement:
|
|
||||||
# This product includes software developed by Tyler C. Sarna.
|
|
||||||
# 4. Neither the name of the author nor the names of contributors
|
|
||||||
# may be used to endorse or promote products derived from this software
|
|
||||||
# without specific prior written permission.
|
|
||||||
#
|
|
||||||
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
|
|
||||||
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
||||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
|
||||||
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS
|
|
||||||
# BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
||||||
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
||||||
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
||||||
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
||||||
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
||||||
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
||||||
# POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
#
|
|
||||||
|
|
||||||
from reportlab.lib.units import inch
|
|
||||||
from common import MultiWidthBarcode
|
|
||||||
import string
|
|
||||||
|
|
||||||
_patterns = {
|
|
||||||
'0' : ('AcAaAb', 0), '1' : ('AaAbAc', 1), '2' : ('AaAcAb', 2),
|
|
||||||
'3' : ('AaAdAa', 3), '4' : ('AbAaAc', 4), '5' : ('AbAbAb', 5),
|
|
||||||
'6' : ('AbAcAa', 6), '7' : ('AaAaAd', 7), '8' : ('AcAbAa', 8),
|
|
||||||
'9' : ('AdAaAa', 9), 'A' : ('BaAaAc', 10), 'B' : ('BaAbAb', 11),
|
|
||||||
'C' : ('BaAcAa', 12), 'D' : ('BbAaAb', 13), 'E' : ('BbAbAa', 14),
|
|
||||||
'F' : ('BcAaAa', 15), 'G' : ('AaBaAc', 16), 'H' : ('AaBbAb', 17),
|
|
||||||
'I' : ('AaBcAa', 18), 'J' : ('AbBaAb', 19), 'K' : ('AcBaAa', 20),
|
|
||||||
'L' : ('AaAaBc', 21), 'M' : ('AaAbBb', 22), 'N' : ('AaAcBa', 23),
|
|
||||||
'O' : ('AbAaBb', 24), 'P' : ('AcAaBa', 25), 'Q' : ('BaBaAb', 26),
|
|
||||||
'R' : ('BaBbAa', 27), 'S' : ('BaAaBb', 28), 'T' : ('BaAbBa', 29),
|
|
||||||
'U' : ('BbAaBa', 30), 'V' : ('BbBaAa', 31), 'W' : ('AaBaBb', 32),
|
|
||||||
'X' : ('AaBbBa', 33), 'Y' : ('AbBaBa', 34), 'Z' : ('AbCaAa', 35),
|
|
||||||
'-' : ('AbAaCa', 36), '.' : ('CaAaAb', 37), ' ' : ('CaAbAa', 38),
|
|
||||||
'$' : ('CbAaAa', 39), '/' : ('AaBaCa', 40), '+' : ('AaCaBa', 41),
|
|
||||||
'%' : ('BaAaCa', 42), '#' : ('AbAbBa', 43), '!' : ('CaBaAa', 44),
|
|
||||||
'=' : ('CaAaBa', 45), '&' : ('AbBbAa', 46),
|
|
||||||
'start' : ('AaAaDa', -1), 'stop' : ('AaAaDaA', -2)
|
|
||||||
}
|
|
||||||
|
|
||||||
_charsbyval = {}
|
|
||||||
for k, v in _patterns.items():
|
|
||||||
_charsbyval[v[1]] = k
|
|
||||||
|
|
||||||
_extended = {
|
|
||||||
'\x00' : '!U', '\x01' : '#A', '\x02' : '#B', '\x03' : '#C',
|
|
||||||
'\x04' : '#D', '\x05' : '#E', '\x06' : '#F', '\x07' : '#G',
|
|
||||||
'\x08' : '#H', '\x09' : '#I', '\x0a' : '#J', '\x0b' : '#K',
|
|
||||||
'\x0c' : '#L', '\x0d' : '#M', '\x0e' : '#N', '\x0f' : '#O',
|
|
||||||
'\x10' : '#P', '\x11' : '#Q', '\x12' : '#R', '\x13' : '#S',
|
|
||||||
'\x14' : '#T', '\x15' : '#U', '\x16' : '#V', '\x17' : '#W',
|
|
||||||
'\x18' : '#X', '\x19' : '#Y', '\x1a' : '#Z', '\x1b' : '!A',
|
|
||||||
'\x1c' : '!B', '\x1d' : '!C', '\x1e' : '!D', '\x1f' : '!E',
|
|
||||||
'!' : '=A', '"' : '=B', '#' : '=C', '$' : '=D',
|
|
||||||
'%' : '=E', '&' : '=F', '\'' : '=G', '(' : '=H',
|
|
||||||
')' : '=I', '*' : '=J', '+' : '=K', ',' : '=L',
|
|
||||||
'/' : '=O', ':' : '=Z', ';' : '!F', '<' : '!G',
|
|
||||||
'=' : '!H', '>' : '!I', '?' : '!J', '@' : '!V',
|
|
||||||
'[' : '!K', '\\' : '!L', ']' : '!M', '^' : '!N',
|
|
||||||
'_' : '!O', '`' : '!W', 'a' : '&A', 'b' : '&B',
|
|
||||||
'c' : '&C', 'd' : '&D', 'e' : '&E', 'f' : '&F',
|
|
||||||
'g' : '&G', 'h' : '&H', 'i' : '&I', 'j' : '&J',
|
|
||||||
'k' : '&K', 'l' : '&L', 'm' : '&M', 'n' : '&N',
|
|
||||||
'o' : '&O', 'p' : '&P', 'q' : '&Q', 'r' : '&R',
|
|
||||||
's' : '&S', 't' : '&T', 'u' : '&U', 'v' : '&V',
|
|
||||||
'w' : '&W', 'x' : '&X', 'y' : '&Y', 'z' : '&Z',
|
|
||||||
'{' : '!P', '|' : '!Q', '}' : '!R', '~' : '!S',
|
|
||||||
'\x7f' : '!T'
|
|
||||||
}
|
|
||||||
|
|
||||||
def _encode93(str):
|
|
||||||
s = map(None, str)
|
|
||||||
s.reverse()
|
|
||||||
|
|
||||||
# compute 'C' checksum
|
|
||||||
i = 0; v = 1; c = 0
|
|
||||||
while i < len(s):
|
|
||||||
c = c + v * _patterns[s[i]][1]
|
|
||||||
i = i + 1; v = v + 1
|
|
||||||
if v > 20:
|
|
||||||
v = 1
|
|
||||||
s.insert(0, _charsbyval[c % 47])
|
|
||||||
|
|
||||||
# compute 'K' checksum
|
|
||||||
i = 0; v = 1; c = 0
|
|
||||||
while i < len(s):
|
|
||||||
c = c + v * _patterns[s[i]][1]
|
|
||||||
i = i + 1; v = v + 1
|
|
||||||
if v > 15:
|
|
||||||
v = 1
|
|
||||||
s.insert(0, _charsbyval[c % 47])
|
|
||||||
|
|
||||||
s.reverse()
|
|
||||||
|
|
||||||
return string.join(s, '')
|
|
||||||
|
|
||||||
class _Code93Base(MultiWidthBarcode):
|
|
||||||
barWidth = inch * 0.0075
|
|
||||||
lquiet = None
|
|
||||||
rquiet = None
|
|
||||||
quiet = 1
|
|
||||||
barHeight = None
|
|
||||||
stop = 1
|
|
||||||
def __init__(self, value='', **args):
|
|
||||||
|
|
||||||
if type(value) is type(1):
|
|
||||||
value = str(value)
|
|
||||||
|
|
||||||
for (k, v) in args.iteritems():
|
|
||||||
setattr(self, k, v)
|
|
||||||
|
|
||||||
if self.quiet:
|
|
||||||
if self.lquiet is None:
|
|
||||||
self.lquiet = max(inch * 0.25, self.barWidth * 10.0)
|
|
||||||
self.rquiet = max(inch * 0.25, self.barWidth * 10.0)
|
|
||||||
else:
|
|
||||||
self.lquiet = self.rquiet = 0.0
|
|
||||||
|
|
||||||
MultiWidthBarcode.__init__(self, value)
|
|
||||||
|
|
||||||
def decompose(self):
|
|
||||||
dval = self.stop and [_patterns['start'][0]] or []
|
|
||||||
dval += [_patterns[c][0] for c in self.encoded]
|
|
||||||
if self.stop: dval.append(_patterns['stop'][0])
|
|
||||||
self.decomposed = ''.join(dval)
|
|
||||||
return self.decomposed
|
|
||||||
|
|
||||||
class Standard93(_Code93Base):
|
|
||||||
"""
|
|
||||||
Code 93 is a Uppercase alphanumeric symbology with some punctuation.
|
|
||||||
See Extended Code 93 for a variant that can represent the entire
|
|
||||||
128 characrter ASCII set.
|
|
||||||
|
|
||||||
Options that may be passed to constructor:
|
|
||||||
|
|
||||||
value (int, or numeric string. required.):
|
|
||||||
The value to encode.
|
|
||||||
|
|
||||||
barWidth (float, default .0075):
|
|
||||||
X-Dimension, or width of the smallest element
|
|
||||||
Minumum is .0075 inch (7.5 mils).
|
|
||||||
|
|
||||||
barHeight (float, see default below):
|
|
||||||
Height of the symbol. Default is the height of the two
|
|
||||||
bearer bars (if they exist) plus the greater of .25 inch
|
|
||||||
or .15 times the symbol's length.
|
|
||||||
|
|
||||||
quiet (bool, default 1):
|
|
||||||
Wether to include quiet zones in the symbol.
|
|
||||||
|
|
||||||
lquiet (float, see default below):
|
|
||||||
Quiet zone size to left of code, if quiet is true.
|
|
||||||
Default is the greater of .25 inch, or 10 barWidth
|
|
||||||
|
|
||||||
rquiet (float, defaults as above):
|
|
||||||
Quiet zone size to right left of code, if quiet is true.
|
|
||||||
|
|
||||||
stop (bool, default 1):
|
|
||||||
Whether to include start/stop symbols.
|
|
||||||
|
|
||||||
Sources of Information on Code 93:
|
|
||||||
|
|
||||||
http://www.semiconductor.agilent.com/barcode/sg/Misc/code_93.html
|
|
||||||
|
|
||||||
Official Spec, "NSI/AIM BC5-1995, USS" is available for US$45 from
|
|
||||||
http://www.aimglobal.org/aimstore/
|
|
||||||
"""
|
|
||||||
def validate(self):
|
|
||||||
vval = ""
|
|
||||||
self.valid = 1
|
|
||||||
for c in self.value:
|
|
||||||
if c in string.lowercase:
|
|
||||||
c = string.upper(c)
|
|
||||||
if not _patterns.has_key(c):
|
|
||||||
self.valid = 0
|
|
||||||
continue
|
|
||||||
vval = vval + c
|
|
||||||
self.validated = vval
|
|
||||||
return vval
|
|
||||||
|
|
||||||
def encode(self):
|
|
||||||
self.encoded = _encode93(self.validated)
|
|
||||||
return self.encoded
|
|
||||||
|
|
||||||
|
|
||||||
class Extended93(_Code93Base):
|
|
||||||
"""
|
|
||||||
Extended Code 93 is a convention for encoding the entire 128 character
|
|
||||||
set using pairs of characters to represent the characters missing in
|
|
||||||
Standard Code 93. It is very much like Extended Code 39 in that way.
|
|
||||||
|
|
||||||
See Standard93 for arguments.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def validate(self):
|
|
||||||
vval = []
|
|
||||||
self.valid = 1
|
|
||||||
a = vval.append
|
|
||||||
for c in self.value:
|
|
||||||
if not _patterns.has_key(c) and not _extended.has_key(c):
|
|
||||||
self.valid = 0
|
|
||||||
continue
|
|
||||||
a(c)
|
|
||||||
self.validated = ''.join(vval)
|
|
||||||
return self.validated
|
|
||||||
|
|
||||||
def encode(self):
|
|
||||||
self.encoded = ""
|
|
||||||
for c in self.validated:
|
|
||||||
if _patterns.has_key(c):
|
|
||||||
self.encoded = self.encoded + c
|
|
||||||
elif _extended.has_key(c):
|
|
||||||
self.encoded = self.encoded + _extended[c]
|
|
||||||
else:
|
|
||||||
raise ValueError
|
|
||||||
self.encoded = _encode93(self.encoded)
|
|
||||||
return self.encoded
|
|
||||||
|
|
||||||
def _humanText(self):
|
|
||||||
return self.validated+self.encoded[-2:]
|
|
|
@ -1,748 +0,0 @@
|
||||||
#
|
|
||||||
# Copyright (c) 1996-2000 Tyler C. Sarna <tsarna@sarna.org>
|
|
||||||
# All rights reserved.
|
|
||||||
#
|
|
||||||
# Redistribution and use in source and binary forms, with or without
|
|
||||||
# modification, are permitted provided that the following conditions
|
|
||||||
# are met:
|
|
||||||
# 1. Redistributions of source code must retain the above copyright
|
|
||||||
# notice, this list of conditions and the following disclaimer.
|
|
||||||
# 2. Redistributions in binary form must reproduce the above copyright
|
|
||||||
# notice, this list of conditions and the following disclaimer in the
|
|
||||||
# documentation and/or other materials provided with the distribution.
|
|
||||||
# 3. All advertising materials mentioning features or use of this software
|
|
||||||
# must display the following acknowledgement:
|
|
||||||
# This product includes software developed by Tyler C. Sarna.
|
|
||||||
# 4. Neither the name of the author nor the names of contributors
|
|
||||||
# may be used to endorse or promote products derived from this software
|
|
||||||
# without specific prior written permission.
|
|
||||||
#
|
|
||||||
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
|
|
||||||
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
||||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
|
||||||
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS
|
|
||||||
# BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
||||||
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
||||||
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
||||||
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
||||||
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
||||||
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
||||||
# POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
#
|
|
||||||
|
|
||||||
from reportlab.platypus.flowables import Flowable
|
|
||||||
from reportlab.lib.units import inch
|
|
||||||
import string
|
|
||||||
|
|
||||||
class Barcode(Flowable):
|
|
||||||
"""Abstract Base for barcodes. Includes implementations of
|
|
||||||
some methods suitable for the more primitive barcode types"""
|
|
||||||
|
|
||||||
fontName = 'Courier'
|
|
||||||
fontSize = 12
|
|
||||||
humanReadable = 0
|
|
||||||
|
|
||||||
def _humanText(self):
|
|
||||||
return self.encoded
|
|
||||||
|
|
||||||
def __init__(self, value='',**args):
|
|
||||||
self.value = value
|
|
||||||
|
|
||||||
for (k, v) in args.items():
|
|
||||||
setattr(self, k, v)
|
|
||||||
|
|
||||||
|
|
||||||
if not hasattr(self, 'gap'):
|
|
||||||
self.gap = None
|
|
||||||
|
|
||||||
self.validate()
|
|
||||||
self.encode()
|
|
||||||
self.decompose()
|
|
||||||
self.computeSize()
|
|
||||||
|
|
||||||
def validate(self):
|
|
||||||
self.valid = 1
|
|
||||||
self.validated = self.value
|
|
||||||
|
|
||||||
def encode(self):
|
|
||||||
self.encoded = self.validated
|
|
||||||
|
|
||||||
def decompose(self):
|
|
||||||
self.decomposed = self.encoded
|
|
||||||
|
|
||||||
def computeSize(self, *args):
|
|
||||||
barWidth = self.barWidth
|
|
||||||
wx = barWidth * self.ratio
|
|
||||||
|
|
||||||
if self.gap == None:
|
|
||||||
self.gap = barWidth
|
|
||||||
|
|
||||||
w = 0.0
|
|
||||||
|
|
||||||
for c in self.decomposed:
|
|
||||||
if c in 'sb':
|
|
||||||
w = w + barWidth
|
|
||||||
elif c in 'SB':
|
|
||||||
w = w + wx
|
|
||||||
else: # 'i'
|
|
||||||
w = w + self.gap
|
|
||||||
|
|
||||||
if self.barHeight is None:
|
|
||||||
self.barHeight = w * 0.15
|
|
||||||
self.barHeight = max(0.25 * inch, self.barHeight)
|
|
||||||
if self.bearers:
|
|
||||||
self.barHeight = self.barHeight + self.bearers * 2.0 * barWidth
|
|
||||||
|
|
||||||
if self.quiet:
|
|
||||||
w += self.lquiet + self.rquiet
|
|
||||||
|
|
||||||
self.height = self.barHeight
|
|
||||||
self.width = w
|
|
||||||
|
|
||||||
def draw(self):
|
|
||||||
barWidth = self.barWidth
|
|
||||||
wx = barWidth * self.ratio
|
|
||||||
|
|
||||||
left = self.quiet and self.lquiet or 0
|
|
||||||
b = self.bearers * barWidth
|
|
||||||
bb = b * 0.5
|
|
||||||
tb = self.barHeight - (b * 1.5)
|
|
||||||
|
|
||||||
for c in self.decomposed:
|
|
||||||
if c == 'i':
|
|
||||||
left = left + self.gap
|
|
||||||
elif c == 's':
|
|
||||||
left = left + barWidth
|
|
||||||
elif c == 'S':
|
|
||||||
left = left + wx
|
|
||||||
elif c == 'b':
|
|
||||||
self.rect(left, bb, barWidth, tb)
|
|
||||||
left = left + barWidth
|
|
||||||
elif c == 'B':
|
|
||||||
self.rect(left, bb, wx, tb)
|
|
||||||
left = left + wx
|
|
||||||
|
|
||||||
if self.bearers:
|
|
||||||
self.rect(self.lquiet, 0, \
|
|
||||||
self.width - (self.lquiet + self.rquiet), b)
|
|
||||||
self.rect(self.lquiet, self.barHeight - b, \
|
|
||||||
self.width - (self.lquiet + self.rquiet), b)
|
|
||||||
|
|
||||||
self.drawHumanReadable()
|
|
||||||
|
|
||||||
def drawHumanReadable(self):
|
|
||||||
if self.humanReadable:
|
|
||||||
#we have text
|
|
||||||
from reportlab.pdfbase.pdfmetrics import getAscent, stringWidth
|
|
||||||
s = str(self._humanText())
|
|
||||||
fontSize = self.fontSize
|
|
||||||
fontName = self.fontName
|
|
||||||
w = stringWidth(s,fontName,fontSize)
|
|
||||||
width = self.width
|
|
||||||
if self.quiet:
|
|
||||||
width -= self.lquiet+self.rquiet
|
|
||||||
x = self.lquiet
|
|
||||||
else:
|
|
||||||
x = 0
|
|
||||||
if w>width: fontSize *= width/float(w)
|
|
||||||
y = 1.07*getAscent(fontName)*fontSize/1000.
|
|
||||||
self.annotate(x+width/2.,-y,s,fontName,fontSize)
|
|
||||||
|
|
||||||
def rect(self, x, y, w, h):
|
|
||||||
self.canv.rect(x, y, w, h, stroke=0, fill=1)
|
|
||||||
|
|
||||||
def annotate(self,x,y,text,fontName,fontSize,anchor='middle'):
|
|
||||||
canv = self.canv
|
|
||||||
canv.saveState()
|
|
||||||
canv.setFont(self.fontName,fontSize)
|
|
||||||
if anchor=='middle': func = 'drawCentredString'
|
|
||||||
elif anchor=='end': func = 'drawRightString'
|
|
||||||
else: func = 'drawString'
|
|
||||||
getattr(canv,func)(text,x,y)
|
|
||||||
canv.restoreState()
|
|
||||||
|
|
||||||
class MultiWidthBarcode(Barcode):
|
|
||||||
"""Base for variable-bar-width codes like Code93 and Code128"""
|
|
||||||
|
|
||||||
def computeSize(self, *args):
|
|
||||||
barWidth = self.barWidth
|
|
||||||
oa, oA = ord('a') - 1, ord('A') - 1
|
|
||||||
|
|
||||||
w = 0.0
|
|
||||||
|
|
||||||
for c in self.decomposed:
|
|
||||||
oc = ord(c)
|
|
||||||
if c in string.lowercase:
|
|
||||||
w = w + barWidth * (oc - oa)
|
|
||||||
elif c in string.uppercase:
|
|
||||||
w = w + barWidth * (oc - oA)
|
|
||||||
|
|
||||||
if self.barHeight is None:
|
|
||||||
self.barHeight = w * 0.15
|
|
||||||
self.barHeight = max(0.25 * inch, self.barHeight)
|
|
||||||
|
|
||||||
if self.quiet:
|
|
||||||
w += self.lquiet + self.rquiet
|
|
||||||
|
|
||||||
self.height = self.barHeight
|
|
||||||
self.width = w
|
|
||||||
|
|
||||||
def draw(self):
|
|
||||||
oa, oA = ord('a') - 1, ord('A') - 1
|
|
||||||
barWidth = self.barWidth
|
|
||||||
left = self.quiet and self.lquiet or 0
|
|
||||||
|
|
||||||
for c in self.decomposed:
|
|
||||||
oc = ord(c)
|
|
||||||
if c in string.lowercase:
|
|
||||||
left = left + (oc - oa) * barWidth
|
|
||||||
elif c in string.uppercase:
|
|
||||||
w = (oc - oA) * barWidth
|
|
||||||
self.rect(left, 0, w, self.barHeight)
|
|
||||||
left += w
|
|
||||||
self.drawHumanReadable()
|
|
||||||
|
|
||||||
class I2of5(Barcode):
|
|
||||||
"""
|
|
||||||
Interleaved 2 of 5 is a numeric-only barcode. It encodes an even
|
|
||||||
number of digits; if an odd number is given, a 0 is prepended.
|
|
||||||
|
|
||||||
Options that may be passed to constructor:
|
|
||||||
|
|
||||||
value (int, or numeric string. required.):
|
|
||||||
The value to encode.
|
|
||||||
|
|
||||||
barWidth (float, default .0075):
|
|
||||||
X-Dimension, or width of the smallest element
|
|
||||||
Minumum is .0075 inch (7.5 mils).
|
|
||||||
|
|
||||||
ratio (float, default 2.2):
|
|
||||||
The ratio of wide elements to narrow elements.
|
|
||||||
Must be between 2.0 and 3.0 (or 2.2 and 3.0 if the
|
|
||||||
barWidth is greater than 20 mils (.02 inch))
|
|
||||||
|
|
||||||
gap (float or None, default None):
|
|
||||||
width of intercharacter gap. None means "use barWidth".
|
|
||||||
|
|
||||||
barHeight (float, see default below):
|
|
||||||
Height of the symbol. Default is the height of the two
|
|
||||||
bearer bars (if they exist) plus the greater of .25 inch
|
|
||||||
or .15 times the symbol's length.
|
|
||||||
|
|
||||||
checksum (bool, default 1):
|
|
||||||
Whether to compute and include the check digit
|
|
||||||
|
|
||||||
bearers (float, in units of barWidth. default 3.0):
|
|
||||||
Height of bearer bars (horizontal bars along the top and
|
|
||||||
bottom of the barcode). Default is 3 x-dimensions.
|
|
||||||
Set to zero for no bearer bars. (Bearer bars help detect
|
|
||||||
misscans, so it is suggested to leave them on).
|
|
||||||
|
|
||||||
quiet (bool, default 1):
|
|
||||||
Whether to include quiet zones in the symbol.
|
|
||||||
|
|
||||||
lquiet (float, see default below):
|
|
||||||
Quiet zone size to left of code, if quiet is true.
|
|
||||||
Default is the greater of .25 inch, or .15 times the symbol's
|
|
||||||
length.
|
|
||||||
|
|
||||||
rquiet (float, defaults as above):
|
|
||||||
Quiet zone size to right left of code, if quiet is true.
|
|
||||||
|
|
||||||
stop (bool, default 1):
|
|
||||||
Whether to include start/stop symbols.
|
|
||||||
|
|
||||||
Sources of Information on Interleaved 2 of 5:
|
|
||||||
|
|
||||||
http://www.semiconductor.agilent.com/barcode/sg/Misc/i_25.html
|
|
||||||
http://www.adams1.com/pub/russadam/i25code.html
|
|
||||||
|
|
||||||
Official Spec, "ANSI/AIM BC2-1995, USS" is available for US$45 from
|
|
||||||
http://www.aimglobal.org/aimstore/
|
|
||||||
"""
|
|
||||||
|
|
||||||
patterns = {
|
|
||||||
'start' : 'bsbs',
|
|
||||||
'stop' : 'Bsb',
|
|
||||||
|
|
||||||
'B0' : 'bbBBb', 'S0' : 'ssSSs',
|
|
||||||
'B1' : 'BbbbB', 'S1' : 'SsssS',
|
|
||||||
'B2' : 'bBbbB', 'S2' : 'sSssS',
|
|
||||||
'B3' : 'BBbbb', 'S3' : 'SSsss',
|
|
||||||
'B4' : 'bbBbB', 'S4' : 'ssSsS',
|
|
||||||
'B5' : 'BbBbb', 'S5' : 'SsSss',
|
|
||||||
'B6' : 'bBBbb', 'S6' : 'sSSss',
|
|
||||||
'B7' : 'bbbBB', 'S7' : 'sssSS',
|
|
||||||
'B8' : 'BbbBb', 'S8' : 'SssSs',
|
|
||||||
'B9' : 'bBbBb', 'S9' : 'sSsSs'
|
|
||||||
}
|
|
||||||
|
|
||||||
barHeight = None
|
|
||||||
barWidth = inch * 0.0075
|
|
||||||
ratio = 2.2
|
|
||||||
checksum = 1
|
|
||||||
bearers = 3.0
|
|
||||||
quiet = 1
|
|
||||||
lquiet = None
|
|
||||||
rquiet = None
|
|
||||||
stop = 1
|
|
||||||
|
|
||||||
def __init__(self, value='', **args):
|
|
||||||
|
|
||||||
if type(value) == type(1):
|
|
||||||
value = str(value)
|
|
||||||
|
|
||||||
for (k, v) in args.items():
|
|
||||||
setattr(self, k, v)
|
|
||||||
|
|
||||||
if self.quiet:
|
|
||||||
if self.lquiet is None:
|
|
||||||
self.lquiet = min(inch * 0.25, self.barWidth * 10.0)
|
|
||||||
self.rquiet = min(inch * 0.25, self.barWidth * 10.0)
|
|
||||||
else:
|
|
||||||
self.lquiet = self.rquiet = 0.0
|
|
||||||
|
|
||||||
Barcode.__init__(self, value)
|
|
||||||
|
|
||||||
def validate(self):
|
|
||||||
vval = ""
|
|
||||||
self.valid = 1
|
|
||||||
for c in string.strip(self.value):
|
|
||||||
if c not in string.digits:
|
|
||||||
self.valid = 0
|
|
||||||
continue
|
|
||||||
vval = vval + c
|
|
||||||
self.validated = vval
|
|
||||||
return vval
|
|
||||||
|
|
||||||
def encode(self):
|
|
||||||
s = self.validated
|
|
||||||
|
|
||||||
# make sure result will be a multiple of 2 digits long,
|
|
||||||
# checksum included
|
|
||||||
if ((len(self.validated) % 2 == 0) and self.checksum) \
|
|
||||||
or ((len(self.validated) % 2 == 1) and not self.checksum):
|
|
||||||
s = '0' + s
|
|
||||||
|
|
||||||
if self.checksum:
|
|
||||||
c = 0
|
|
||||||
cm = 3
|
|
||||||
|
|
||||||
for d in s:
|
|
||||||
c = c + cm * int(d)
|
|
||||||
if cm == 3:
|
|
||||||
cm = 1
|
|
||||||
else:
|
|
||||||
cm = 3
|
|
||||||
|
|
||||||
d = 10 - (int(d) % 10)
|
|
||||||
s = s + `d`
|
|
||||||
|
|
||||||
self.encoded = s
|
|
||||||
|
|
||||||
def decompose(self):
|
|
||||||
dval = self.stop and [self.patterns['start']] or []
|
|
||||||
a = dval.append
|
|
||||||
|
|
||||||
for i in xrange(0, len(self.encoded), 2):
|
|
||||||
b = self.patterns['B' + self.encoded[i]]
|
|
||||||
s = self.patterns['S' + self.encoded[i+1]]
|
|
||||||
|
|
||||||
for i in range(0, len(b)):
|
|
||||||
a(b[i] + s[i])
|
|
||||||
|
|
||||||
if self.stop: a(self.patterns['stop'])
|
|
||||||
self.decomposed = ''.join(dval)
|
|
||||||
return self.decomposed
|
|
||||||
|
|
||||||
class MSI(Barcode):
|
|
||||||
"""
|
|
||||||
MSI is a numeric-only barcode.
|
|
||||||
|
|
||||||
Options that may be passed to constructor:
|
|
||||||
|
|
||||||
value (int, or numeric string. required.):
|
|
||||||
The value to encode.
|
|
||||||
|
|
||||||
barWidth (float, default .0075):
|
|
||||||
X-Dimension, or width of the smallest element
|
|
||||||
|
|
||||||
ratio (float, default 2.2):
|
|
||||||
The ratio of wide elements to narrow elements.
|
|
||||||
|
|
||||||
gap (float or None, default None):
|
|
||||||
width of intercharacter gap. None means "use barWidth".
|
|
||||||
|
|
||||||
barHeight (float, see default below):
|
|
||||||
Height of the symbol. Default is the height of the two
|
|
||||||
bearer bars (if they exist) plus the greater of .25 inch
|
|
||||||
or .15 times the symbol's length.
|
|
||||||
|
|
||||||
checksum (bool, default 1):
|
|
||||||
Wether to compute and include the check digit
|
|
||||||
|
|
||||||
bearers (float, in units of barWidth. default 0):
|
|
||||||
Height of bearer bars (horizontal bars along the top and
|
|
||||||
bottom of the barcode). Default is 0 (no bearers).
|
|
||||||
|
|
||||||
lquiet (float, see default below):
|
|
||||||
Quiet zone size to left of code, if quiet is true.
|
|
||||||
Default is the greater of .25 inch, or 10 barWidths.
|
|
||||||
|
|
||||||
rquiet (float, defaults as above):
|
|
||||||
Quiet zone size to right left of code, if quiet is true.
|
|
||||||
|
|
||||||
stop (bool, default 1):
|
|
||||||
Whether to include start/stop symbols.
|
|
||||||
|
|
||||||
Sources of Information on MSI Bar Code:
|
|
||||||
|
|
||||||
http://www.semiconductor.agilent.com/barcode/sg/Misc/msi_code.html
|
|
||||||
http://www.adams1.com/pub/russadam/plessy.html
|
|
||||||
"""
|
|
||||||
|
|
||||||
patterns = {
|
|
||||||
'start' : 'Bs', 'stop' : 'bSb',
|
|
||||||
|
|
||||||
'0' : 'bSbSbSbS', '1' : 'bSbSbSBs',
|
|
||||||
'2' : 'bSbSBsbS', '3' : 'bSbSBsBs',
|
|
||||||
'4' : 'bSBsbSbS', '5' : 'bSBsbSBs',
|
|
||||||
'6' : 'bSBsBsbS', '7' : 'bSBsBsBs',
|
|
||||||
'8' : 'BsbSbSbS', '9' : 'BsbSbSBs'
|
|
||||||
}
|
|
||||||
|
|
||||||
stop = 1
|
|
||||||
barHeight = None
|
|
||||||
barWidth = inch * 0.0075
|
|
||||||
ratio = 2.2
|
|
||||||
checksum = 1
|
|
||||||
bearers = 0.0
|
|
||||||
quiet = 1
|
|
||||||
lquiet = None
|
|
||||||
rquiet = None
|
|
||||||
|
|
||||||
def __init__(self, value="", **args):
|
|
||||||
|
|
||||||
if type(value) == type(1):
|
|
||||||
value = str(value)
|
|
||||||
|
|
||||||
for (k, v) in args.items():
|
|
||||||
setattr(self, k, v)
|
|
||||||
|
|
||||||
if self.quiet:
|
|
||||||
if self.lquiet is None:
|
|
||||||
self.lquiet = max(inch * 0.25, self.barWidth * 10.0)
|
|
||||||
self.rquiet = max(inch * 0.25, self.barWidth * 10.0)
|
|
||||||
else:
|
|
||||||
self.lquiet = self.rquiet = 0.0
|
|
||||||
|
|
||||||
Barcode.__init__(self, value)
|
|
||||||
|
|
||||||
def validate(self):
|
|
||||||
vval = ""
|
|
||||||
self.valid = 1
|
|
||||||
for c in string.strip(self.value):
|
|
||||||
if c not in string.digits:
|
|
||||||
self.valid = 0
|
|
||||||
continue
|
|
||||||
vval = vval + c
|
|
||||||
self.validated = vval
|
|
||||||
return vval
|
|
||||||
|
|
||||||
def encode(self):
|
|
||||||
s = self.validated
|
|
||||||
|
|
||||||
if self.checksum:
|
|
||||||
c = ''
|
|
||||||
for i in range(1, len(s), 2):
|
|
||||||
c = c + s[i]
|
|
||||||
d = str(int(c) * 2)
|
|
||||||
t = 0
|
|
||||||
for c in d:
|
|
||||||
t = t + int(c)
|
|
||||||
for i in range(0, len(s), 2):
|
|
||||||
t = t + int(s[i])
|
|
||||||
c = 10 - (t % 10)
|
|
||||||
|
|
||||||
s = s + str(c)
|
|
||||||
|
|
||||||
self.encoded = s
|
|
||||||
|
|
||||||
def decompose(self):
|
|
||||||
dval = self.stop and [self.patterns['start']] or []
|
|
||||||
dval += [self.patterns[c] for c in self.encoded]
|
|
||||||
if self.stop: dval.append(self.patterns['stop'])
|
|
||||||
self.decomposed = ''.join(dval)
|
|
||||||
return self.decomposed
|
|
||||||
|
|
||||||
class Codabar(Barcode):
|
|
||||||
"""
|
|
||||||
Codabar is a numeric plus some puntuation ("-$:/.+") barcode
|
|
||||||
with four start/stop characters (A, B, C, and D).
|
|
||||||
|
|
||||||
Options that may be passed to constructor:
|
|
||||||
|
|
||||||
value (string. required.):
|
|
||||||
The value to encode.
|
|
||||||
|
|
||||||
barWidth (float, default .0065):
|
|
||||||
X-Dimension, or width of the smallest element
|
|
||||||
minimum is 6.5 mils (.0065 inch)
|
|
||||||
|
|
||||||
ratio (float, default 2.0):
|
|
||||||
The ratio of wide elements to narrow elements.
|
|
||||||
|
|
||||||
gap (float or None, default None):
|
|
||||||
width of intercharacter gap. None means "use barWidth".
|
|
||||||
|
|
||||||
barHeight (float, see default below):
|
|
||||||
Height of the symbol. Default is the height of the two
|
|
||||||
bearer bars (if they exist) plus the greater of .25 inch
|
|
||||||
or .15 times the symbol's length.
|
|
||||||
|
|
||||||
checksum (bool, default 0):
|
|
||||||
Whether to compute and include the check digit
|
|
||||||
|
|
||||||
bearers (float, in units of barWidth. default 0):
|
|
||||||
Height of bearer bars (horizontal bars along the top and
|
|
||||||
bottom of the barcode). Default is 0 (no bearers).
|
|
||||||
|
|
||||||
quiet (bool, default 1):
|
|
||||||
Whether to include quiet zones in the symbol.
|
|
||||||
|
|
||||||
stop (bool, default 1):
|
|
||||||
Whether to include start/stop symbols.
|
|
||||||
|
|
||||||
lquiet (float, see default below):
|
|
||||||
Quiet zone size to left of code, if quiet is true.
|
|
||||||
Default is the greater of .25 inch, or 10 barWidth
|
|
||||||
|
|
||||||
rquiet (float, defaults as above):
|
|
||||||
Quiet zone size to right left of code, if quiet is true.
|
|
||||||
|
|
||||||
Sources of Information on Codabar
|
|
||||||
|
|
||||||
http://www.semiconductor.agilent.com/barcode/sg/Misc/codabar.html
|
|
||||||
http://www.barcodeman.com/codabar.html
|
|
||||||
|
|
||||||
Official Spec, "ANSI/AIM BC3-1995, USS" is available for US$45 from
|
|
||||||
http://www.aimglobal.org/aimstore/
|
|
||||||
"""
|
|
||||||
|
|
||||||
patterns = {
|
|
||||||
'0': 'bsbsbSB', '1': 'bsbsBSb', '2': 'bsbSbsB',
|
|
||||||
'3': 'BSbsbsb', '4': 'bsBsbSb', '5': 'BsbsbSb',
|
|
||||||
'6': 'bSbsbsB', '7': 'bSbsBsb', '8': 'bSBsbsb',
|
|
||||||
'9': 'BsbSbsb', '-': 'bsbSBsb', '$': 'bsBSbsb',
|
|
||||||
':': 'BsbsBsB', '/': 'BsBsbsB', '.': 'BsBsBsb',
|
|
||||||
'+': 'bsBsBsB', 'A': 'bsBSbSb', 'B': 'bSbSbsB',
|
|
||||||
'C': 'bsbSbSB', 'D': 'bsbSBSb'
|
|
||||||
}
|
|
||||||
|
|
||||||
values = {
|
|
||||||
'0' : 0, '1' : 1, '2' : 2, '3' : 3, '4' : 4,
|
|
||||||
'5' : 5, '6' : 6, '7' : 7, '8' : 8, '9' : 9,
|
|
||||||
'-' : 10, '$' : 11, ':' : 12, '/' : 13, '.' : 14,
|
|
||||||
'+' : 15, 'A' : 16, 'B' : 17, 'C' : 18, 'D' : 19
|
|
||||||
}
|
|
||||||
|
|
||||||
chars = string.digits + "-$:/.+"
|
|
||||||
|
|
||||||
stop = 1
|
|
||||||
barHeight = None
|
|
||||||
barWidth = inch * 0.0065
|
|
||||||
ratio = 2.0 # XXX ?
|
|
||||||
checksum = 0
|
|
||||||
bearers = 0.0
|
|
||||||
quiet = 1
|
|
||||||
lquiet = None
|
|
||||||
rquiet = None
|
|
||||||
|
|
||||||
def __init__(self, value='', **args):
|
|
||||||
if type(value) == type(1):
|
|
||||||
value = str(value)
|
|
||||||
|
|
||||||
for (k, v) in args.items():
|
|
||||||
setattr(self, k, v)
|
|
||||||
|
|
||||||
if self.quiet:
|
|
||||||
if self.lquiet is None:
|
|
||||||
self.lquiet = min(inch * 0.25, self.barWidth * 10.0)
|
|
||||||
self.rquiet = min(inch * 0.25, self.barWidth * 10.0)
|
|
||||||
else:
|
|
||||||
self.lquiet = self.rquiet = 0.0
|
|
||||||
|
|
||||||
Barcode.__init__(self, value)
|
|
||||||
|
|
||||||
def validate(self):
|
|
||||||
vval = ""
|
|
||||||
self.valid = 1
|
|
||||||
s = string.strip(self.value)
|
|
||||||
for i in range(0, len(s)):
|
|
||||||
c = s[i]
|
|
||||||
if c not in self.chars:
|
|
||||||
if ((i != 0) and (i != len(s) - 1)) or (c not in 'ABCD'):
|
|
||||||
self.Valid = 0
|
|
||||||
continue
|
|
||||||
vval = vval + c
|
|
||||||
|
|
||||||
if self.stop:
|
|
||||||
if vval[0] not in 'ABCD':
|
|
||||||
vval = 'A' + vval
|
|
||||||
if vval[-1] not in 'ABCD':
|
|
||||||
vval = vval + vval[0]
|
|
||||||
|
|
||||||
self.validated = vval
|
|
||||||
return vval
|
|
||||||
|
|
||||||
def encode(self):
|
|
||||||
s = self.validated
|
|
||||||
|
|
||||||
if self.checksum:
|
|
||||||
v = sum([self.values[c] for c in s])
|
|
||||||
s += self.chars[v % 16]
|
|
||||||
|
|
||||||
self.encoded = s
|
|
||||||
|
|
||||||
def decompose(self):
|
|
||||||
dval = ''.join([self.patterns[c]+'i' for c in self.encoded])
|
|
||||||
self.decomposed = dval[:-1]
|
|
||||||
return self.decomposed
|
|
||||||
|
|
||||||
class Code11(Barcode):
|
|
||||||
"""
|
|
||||||
Code 11 is an almost-numeric barcode. It encodes the digits 0-9 plus
|
|
||||||
dash ("-"). 11 characters total, hence the name.
|
|
||||||
|
|
||||||
value (int or string. required.):
|
|
||||||
The value to encode.
|
|
||||||
|
|
||||||
barWidth (float, default .0075):
|
|
||||||
X-Dimension, or width of the smallest element
|
|
||||||
|
|
||||||
ratio (float, default 2.2):
|
|
||||||
The ratio of wide elements to narrow elements.
|
|
||||||
|
|
||||||
gap (float or None, default None):
|
|
||||||
width of intercharacter gap. None means "use barWidth".
|
|
||||||
|
|
||||||
barHeight (float, see default below):
|
|
||||||
Height of the symbol. Default is the height of the two
|
|
||||||
bearer bars (if they exist) plus the greater of .25 inch
|
|
||||||
or .15 times the symbol's length.
|
|
||||||
|
|
||||||
checksum (0 none, 1 1-digit, 2 2-digit, -1 auto, default -1):
|
|
||||||
How many checksum digits to include. -1 ("auto") means
|
|
||||||
1 if the number of digits is 10 or less, else 2.
|
|
||||||
|
|
||||||
bearers (float, in units of barWidth. default 0):
|
|
||||||
Height of bearer bars (horizontal bars along the top and
|
|
||||||
bottom of the barcode). Default is 0 (no bearers).
|
|
||||||
|
|
||||||
quiet (bool, default 1):
|
|
||||||
Wether to include quiet zones in the symbol.
|
|
||||||
|
|
||||||
lquiet (float, see default below):
|
|
||||||
Quiet zone size to left of code, if quiet is true.
|
|
||||||
Default is the greater of .25 inch, or 10 barWidth
|
|
||||||
|
|
||||||
rquiet (float, defaults as above):
|
|
||||||
Quiet zone size to right left of code, if quiet is true.
|
|
||||||
|
|
||||||
Sources of Information on Code 11:
|
|
||||||
|
|
||||||
http://www.cwi.nl/people/dik/english/codes/barcodes.html
|
|
||||||
"""
|
|
||||||
|
|
||||||
chars = string.digits + '-'
|
|
||||||
|
|
||||||
patterns = {
|
|
||||||
'0' : 'bsbsB', '1' : 'BsbsB', '2' : 'bSbsB',
|
|
||||||
'3' : 'BSbsb', '4' : 'bsBsB', '5' : 'BsBsb',
|
|
||||||
'6' : 'bSBsb', '7' : 'bsbSB', '8' : 'BsbSb',
|
|
||||||
'9' : 'Bsbsb', '-' : 'bsBsb', 'S' : 'bsBSb' # Start/Stop
|
|
||||||
}
|
|
||||||
|
|
||||||
values = {
|
|
||||||
'0' : 0, '1' : 1, '2' : 2, '3' : 3, '4' : 4,
|
|
||||||
'5' : 5, '6' : 6, '7' : 7, '8' : 8, '9' : 9,
|
|
||||||
'-' : 10,
|
|
||||||
}
|
|
||||||
|
|
||||||
stop = 1
|
|
||||||
barHeight = None
|
|
||||||
barWidth = inch * 0.0075
|
|
||||||
ratio = 2.2 # XXX ?
|
|
||||||
checksum = -1 # Auto
|
|
||||||
bearers = 0.0
|
|
||||||
quiet = 1
|
|
||||||
lquiet = None
|
|
||||||
rquiet = None
|
|
||||||
def __init__(self, value='', **args):
|
|
||||||
if type(value) == type(1):
|
|
||||||
value = str(value)
|
|
||||||
|
|
||||||
for (k, v) in args.items():
|
|
||||||
setattr(self, k, v)
|
|
||||||
|
|
||||||
if self.quiet:
|
|
||||||
if self.lquiet is None:
|
|
||||||
self.lquiet = min(inch * 0.25, self.barWidth * 10.0)
|
|
||||||
self.rquiet = min(inch * 0.25, self.barWidth * 10.0)
|
|
||||||
else:
|
|
||||||
self.lquiet = self.rquiet = 0.0
|
|
||||||
|
|
||||||
Barcode.__init__(self, value)
|
|
||||||
|
|
||||||
def validate(self):
|
|
||||||
vval = ""
|
|
||||||
self.valid = 1
|
|
||||||
s = string.strip(self.value)
|
|
||||||
for i in range(0, len(s)):
|
|
||||||
c = s[i]
|
|
||||||
if c not in self.chars:
|
|
||||||
self.Valid = 0
|
|
||||||
continue
|
|
||||||
vval = vval + c
|
|
||||||
|
|
||||||
self.validated = vval
|
|
||||||
return vval
|
|
||||||
|
|
||||||
def encode(self):
|
|
||||||
s = self.validated
|
|
||||||
|
|
||||||
if self.checksum == -1:
|
|
||||||
if len(s) <= 10:
|
|
||||||
self.checksum = 1
|
|
||||||
else:
|
|
||||||
self.checksum = 2
|
|
||||||
|
|
||||||
if self.checksum > 0:
|
|
||||||
# compute first checksum
|
|
||||||
i = 0; v = 1; c = 0
|
|
||||||
while i < len(s):
|
|
||||||
c = c + v * string.index(self.chars, s[-(i+1)])
|
|
||||||
i = i + 1; v = v + 1
|
|
||||||
if v > 10:
|
|
||||||
v = 1
|
|
||||||
s = s + self.chars[c % 11]
|
|
||||||
|
|
||||||
if self.checksum > 1:
|
|
||||||
# compute second checksum
|
|
||||||
i = 0; v = 1; c = 0
|
|
||||||
while i < len(s):
|
|
||||||
c = c + v * string.index(self.chars, s[-(i+1)])
|
|
||||||
i = i + 1; v = v + 1
|
|
||||||
if v > 9:
|
|
||||||
v = 1
|
|
||||||
s = s + self.chars[c % 10]
|
|
||||||
|
|
||||||
self.encoded = self.stop and ('S' + s + 'S') or s
|
|
||||||
|
|
||||||
def decompose(self):
|
|
||||||
dval = [self.patterns[c]+'i' for c in self.encoded]
|
|
||||||
self.decomposed = ''.join(dval[:-1])
|
|
||||||
return self.decomposed
|
|
||||||
|
|
||||||
def _humanText(self):
|
|
||||||
return self.stop and self.encoded[1:-1] or self.encoded
|
|
|
@ -1,339 +0,0 @@
|
||||||
__all__=(
|
|
||||||
'Ean13BarcodeWidget','isEanString',
|
|
||||||
)
|
|
||||||
from reportlab.graphics.shapes import Group, String, Rect
|
|
||||||
from reportlab.lib import colors
|
|
||||||
from reportlab.pdfbase.pdfmetrics import stringWidth
|
|
||||||
from reportlab.lib.validators import isNumber, isColor, isString, Validator, isBoolean
|
|
||||||
from reportlab.lib.attrmap import *
|
|
||||||
from reportlab.graphics.charts.areas import PlotArea
|
|
||||||
from reportlab.lib.units import mm
|
|
||||||
|
|
||||||
#work out a list of manufacturer codes....
|
|
||||||
_eanNumberSystems = [
|
|
||||||
('00-13', 'USA & Canada'),
|
|
||||||
('20-29', 'In-Store Functions'),
|
|
||||||
('30-37', 'France'),
|
|
||||||
('40-44', 'Germany'),
|
|
||||||
('45', 'Japan (also 49)'),
|
|
||||||
('46', 'Russian Federation'),
|
|
||||||
('471', 'Taiwan'),
|
|
||||||
('474', 'Estonia'),
|
|
||||||
('475', 'Latvia'),
|
|
||||||
('477', 'Lithuania'),
|
|
||||||
('479', 'Sri Lanka'),
|
|
||||||
('480', 'Philippines'),
|
|
||||||
('482', 'Ukraine'),
|
|
||||||
('484', 'Moldova'),
|
|
||||||
('485', 'Armenia'),
|
|
||||||
('486', 'Georgia'),
|
|
||||||
('487', 'Kazakhstan'),
|
|
||||||
('489', 'Hong Kong'),
|
|
||||||
('49', 'Japan (JAN-13)'),
|
|
||||||
('50', 'United Kingdom'),
|
|
||||||
('520', 'Greece'),
|
|
||||||
('528', 'Lebanon'),
|
|
||||||
('529', 'Cyprus'),
|
|
||||||
('531', 'Macedonia'),
|
|
||||||
('535', 'Malta'),
|
|
||||||
('539', 'Ireland'),
|
|
||||||
('54', 'Belgium & Luxembourg'),
|
|
||||||
('560', 'Portugal'),
|
|
||||||
('569', 'Iceland'),
|
|
||||||
('57', 'Denmark'),
|
|
||||||
('590', 'Poland'),
|
|
||||||
('594', 'Romania'),
|
|
||||||
('599', 'Hungary'),
|
|
||||||
('600-601', 'South Africa'),
|
|
||||||
('609', 'Mauritius'),
|
|
||||||
('611', 'Morocco'),
|
|
||||||
('613', 'Algeria'),
|
|
||||||
('619', 'Tunisia'),
|
|
||||||
('622', 'Egypt'),
|
|
||||||
('625', 'Jordan'),
|
|
||||||
('626', 'Iran'),
|
|
||||||
('64', 'Finland'),
|
|
||||||
('690-692', 'China'),
|
|
||||||
('70', 'Norway'),
|
|
||||||
('729', 'Israel'),
|
|
||||||
('73', 'Sweden'),
|
|
||||||
('740', 'Guatemala'),
|
|
||||||
('741', 'El Salvador'),
|
|
||||||
('742', 'Honduras'),
|
|
||||||
('743', 'Nicaragua'),
|
|
||||||
('744', 'Costa Rica'),
|
|
||||||
('746', 'Dominican Republic'),
|
|
||||||
('750', 'Mexico'),
|
|
||||||
('759', 'Venezuela'),
|
|
||||||
('76', 'Switzerland'),
|
|
||||||
('770', 'Colombia'),
|
|
||||||
('773', 'Uruguay'),
|
|
||||||
('775', 'Peru'),
|
|
||||||
('777', 'Bolivia'),
|
|
||||||
('779', 'Argentina'),
|
|
||||||
('780', 'Chile'),
|
|
||||||
('784', 'Paraguay'),
|
|
||||||
('785', 'Peru'),
|
|
||||||
('786', 'Ecuador'),
|
|
||||||
('789', 'Brazil'),
|
|
||||||
('80-83', 'Italy'),
|
|
||||||
('84', 'Spain'),
|
|
||||||
('850', 'Cuba'),
|
|
||||||
('858', 'Slovakia'),
|
|
||||||
('859', 'Czech Republic'),
|
|
||||||
('860', 'Yugloslavia'),
|
|
||||||
('869', 'Turkey'),
|
|
||||||
('87', 'Netherlands'),
|
|
||||||
('880', 'South Korea'),
|
|
||||||
('885', 'Thailand'),
|
|
||||||
('888', 'Singapore'),
|
|
||||||
('890', 'India'),
|
|
||||||
('893', 'Vietnam'),
|
|
||||||
('899', 'Indonesia'),
|
|
||||||
('90-91', 'Austria'),
|
|
||||||
('93', 'Australia'),
|
|
||||||
('94', 'New Zealand'),
|
|
||||||
('955', 'Malaysia'),
|
|
||||||
('977', 'International Standard Serial Number for Periodicals (ISSN)'),
|
|
||||||
('978', 'International Standard Book Numbering (ISBN)'),
|
|
||||||
('979', 'International Standard Music Number (ISMN)'),
|
|
||||||
('980', 'Refund receipts'),
|
|
||||||
('981-982', 'Common Currency Coupons'),
|
|
||||||
('99', 'Coupons')
|
|
||||||
]
|
|
||||||
|
|
||||||
manufacturerCodes = {}
|
|
||||||
for (k, v) in _eanNumberSystems:
|
|
||||||
words = k.split('-')
|
|
||||||
if len(words)==2:
|
|
||||||
fromCode = int(words[0])
|
|
||||||
toCode = int(words[1])
|
|
||||||
for code in range(fromCode, toCode+1):
|
|
||||||
manufacturerCodes[code] = v
|
|
||||||
else:
|
|
||||||
manufacturerCodes[int(k)] = v
|
|
||||||
|
|
||||||
class isEan13String(Validator):
|
|
||||||
def test(self,x):
|
|
||||||
return type(x) is str and len(x)<=12 and len([c for c in x if c in "0123456789"])==12
|
|
||||||
isEan13String = isEan13String()
|
|
||||||
|
|
||||||
class Ean13BarcodeWidget(PlotArea):
|
|
||||||
codeName = "EAN13"
|
|
||||||
_attrMap = AttrMap(BASE=PlotArea,
|
|
||||||
value = AttrMapValue(isEan13String, desc='the number'),
|
|
||||||
fontName = AttrMapValue(isString, desc='fontName'),
|
|
||||||
fontSize = AttrMapValue(isNumber, desc='font size'),
|
|
||||||
x = AttrMapValue(isNumber, desc='x-coord'),
|
|
||||||
y = AttrMapValue(isNumber, desc='y-coord'),
|
|
||||||
barFillColor = AttrMapValue(isColor, desc='bar color'),
|
|
||||||
barHeight = AttrMapValue(isNumber, desc='Height of bars.'),
|
|
||||||
barWidth = AttrMapValue(isNumber, desc='Width of bars.'),
|
|
||||||
barStrokeWidth = AttrMapValue(isNumber, desc='Width of bar borders.'),
|
|
||||||
barStrokeColor = AttrMapValue(isColor, desc='Color of bar borders.'),
|
|
||||||
textColor = AttrMapValue(isColor, desc='human readable text color'),
|
|
||||||
humanReadable = AttrMapValue(isBoolean, desc='if human readable'),
|
|
||||||
quiet = AttrMapValue(isBoolean, desc='if quiet zone to be used'),
|
|
||||||
lquiet = AttrMapValue(isBoolean, desc='left quiet zone length'),
|
|
||||||
rquiet = AttrMapValue(isBoolean, desc='right quiet zone length'),
|
|
||||||
)
|
|
||||||
_digits=12
|
|
||||||
_start_right = 7 #for ean-13 left = [0:7] right=[7:13]
|
|
||||||
_nbars = 113
|
|
||||||
barHeight = 25.93*mm #millimeters
|
|
||||||
barWidth = (37.29/_nbars)*mm
|
|
||||||
humanReadable = 1
|
|
||||||
_0csw = 1
|
|
||||||
_1csw = 3
|
|
||||||
|
|
||||||
#Left Hand Digits.
|
|
||||||
_left = ( ("0001101", "0011001", "0010011", "0111101",
|
|
||||||
"0100011", "0110001", "0101111", "0111011",
|
|
||||||
"0110111", "0001011",
|
|
||||||
), #odd left hand digits
|
|
||||||
("0100111", "0110011", "0011011", "0100001",
|
|
||||||
"0011101", "0111001", "0000101", "0010001",
|
|
||||||
"0001001", "0010111"), #even left hand digits
|
|
||||||
)
|
|
||||||
|
|
||||||
_right = ("1110010", "1100110", "1101100", "1000010",
|
|
||||||
"1011100", "1001110", "1010000", "1000100",
|
|
||||||
"1001000", "1110100")
|
|
||||||
|
|
||||||
quiet = 1
|
|
||||||
rquiet = lquiet = None
|
|
||||||
_tail = "101"
|
|
||||||
_sep = "01010"
|
|
||||||
|
|
||||||
_lhconvert={
|
|
||||||
"0": (0,0,0,0,0,0),
|
|
||||||
"1": (0,0,1,0,1,1),
|
|
||||||
"2": (0,0,1,1,0,1),
|
|
||||||
"3": (0,0,1,1,1,0),
|
|
||||||
"4": (0,1,0,0,1,1),
|
|
||||||
"5": (0,1,1,0,0,1),
|
|
||||||
"6": (0,1,1,1,0,0),
|
|
||||||
"7": (0,1,0,1,0,1),
|
|
||||||
"8": (0,1,0,1,1,0),
|
|
||||||
"9": (0,1,1,0,1,0)
|
|
||||||
}
|
|
||||||
fontSize = 8 #millimeters
|
|
||||||
fontName = 'Helvetica'
|
|
||||||
textColor = barFillColor = barStrokeColor = colors.black
|
|
||||||
barStrokeWidth = 0
|
|
||||||
x = 0
|
|
||||||
y = 0
|
|
||||||
def __init__(self,value='123456789012',**kw):
|
|
||||||
self.value=max(self._digits-len(value),0)*'0'+value[:self._digits]
|
|
||||||
for k, v in kw.iteritems():
|
|
||||||
setattr(self, k, v)
|
|
||||||
|
|
||||||
width = property(lambda self: self.barWidth*(self._nbars-18+self._calc_quiet(self.lquiet)+self._calc_quiet(self.rquiet)))
|
|
||||||
|
|
||||||
def wrap(self,aW,aH):
|
|
||||||
return self.width,self.barHeight
|
|
||||||
|
|
||||||
def _encode_left(self,s,a):
|
|
||||||
cp = self._lhconvert[s[0]] #convert the left hand numbers
|
|
||||||
_left = self._left
|
|
||||||
z = ord('0')
|
|
||||||
for i,c in enumerate(s[1:self._start_right]):
|
|
||||||
a(_left[cp[i]][ord(c)-z])
|
|
||||||
|
|
||||||
def _short_bar(self,i):
|
|
||||||
i += 9 - self._lquiet
|
|
||||||
return self.humanReadable and ((12<i<55) or (57<i<101))
|
|
||||||
|
|
||||||
def _calc_quiet(self,v):
|
|
||||||
if self.quiet:
|
|
||||||
if v is None:
|
|
||||||
v = 9
|
|
||||||
else:
|
|
||||||
x = float(max(v,0))/self.barWidth
|
|
||||||
v = int(x)
|
|
||||||
if v-x>0: v += 1
|
|
||||||
else:
|
|
||||||
v = 0
|
|
||||||
return v
|
|
||||||
|
|
||||||
def draw(self):
|
|
||||||
g = Group()
|
|
||||||
gAdd = g.add
|
|
||||||
barWidth = self.barWidth
|
|
||||||
width = self.width
|
|
||||||
barHeight = self.barHeight
|
|
||||||
x = self.x
|
|
||||||
y = self.y
|
|
||||||
gAdd(Rect(x,y,width,barHeight,fillColor=None,strokeColor=None,strokeWidth=0))
|
|
||||||
s = self.value+self._checkdigit(self.value)
|
|
||||||
self._lquiet = lquiet = self._calc_quiet(self.lquiet)
|
|
||||||
rquiet = self._calc_quiet(self.rquiet)
|
|
||||||
b = [lquiet*'0',self._tail] #the signal string
|
|
||||||
a = b.append
|
|
||||||
self._encode_left(s,a)
|
|
||||||
a(self._sep)
|
|
||||||
|
|
||||||
z = ord('0')
|
|
||||||
_right = self._right
|
|
||||||
for c in s[self._start_right:]:
|
|
||||||
a(_right[ord(c)-z])
|
|
||||||
a(self._tail)
|
|
||||||
a(rquiet*'0')
|
|
||||||
|
|
||||||
fontSize = self.fontSize
|
|
||||||
barFillColor = self.barFillColor
|
|
||||||
barStrokeWidth = self.barStrokeWidth
|
|
||||||
|
|
||||||
fth = fontSize*1.2
|
|
||||||
b = ''.join(b)
|
|
||||||
|
|
||||||
lrect = None
|
|
||||||
for i,c in enumerate(b):
|
|
||||||
if c=="1":
|
|
||||||
dh = self._short_bar(i) and fth or 0
|
|
||||||
yh = y+dh
|
|
||||||
if lrect and lrect.y==yh:
|
|
||||||
lrect.width += barWidth
|
|
||||||
else:
|
|
||||||
lrect = Rect(x,yh,barWidth,barHeight-dh,fillColor=barFillColor,strokeWidth=barStrokeWidth,strokeColor=barFillColor)
|
|
||||||
gAdd(lrect)
|
|
||||||
else:
|
|
||||||
lrect = None
|
|
||||||
x += barWidth
|
|
||||||
|
|
||||||
if self.humanReadable: self._add_human_readable(s,gAdd)
|
|
||||||
return g
|
|
||||||
|
|
||||||
def _add_human_readable(self,s,gAdd):
|
|
||||||
barWidth = self.barWidth
|
|
||||||
fontSize = self.fontSize
|
|
||||||
textColor = self.textColor
|
|
||||||
fontName = self.fontName
|
|
||||||
fth = fontSize*1.2
|
|
||||||
# draw the num below the line.
|
|
||||||
c = s[0]
|
|
||||||
w = stringWidth(c,fontName,fontSize)
|
|
||||||
x = self.x+barWidth*(self._lquiet-8)
|
|
||||||
y = self.y + 0.2*fth
|
|
||||||
|
|
||||||
gAdd(String(x,y,c,fontName=fontName,fontSize=fontSize,fillColor=textColor))
|
|
||||||
x = self.x + (33-9+self._lquiet)*barWidth
|
|
||||||
|
|
||||||
c = s[1:7]
|
|
||||||
gAdd(String(x,y,c,fontName=fontName,fontSize=fontSize,fillColor=textColor,textAnchor='middle'))
|
|
||||||
|
|
||||||
x += 47*barWidth
|
|
||||||
c = s[7:]
|
|
||||||
gAdd(String(x,y,c,fontName=fontName,fontSize=fontSize,fillColor=textColor,textAnchor='middle'))
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def _checkdigit(cls,num):
|
|
||||||
z = ord('0')
|
|
||||||
iSum = cls._0csw*sum([(ord(x)-z) for x in num[::2]]) \
|
|
||||||
+ cls._1csw*sum([(ord(x)-z) for x in num[1::2]])
|
|
||||||
return chr(z+((10-(iSum%10))%10))
|
|
||||||
|
|
||||||
class isEan8String(Validator):
|
|
||||||
def test(self,x):
|
|
||||||
return type(x) is str and len(x)<=7 and len([c for c in x if c in "0123456789"])==7
|
|
||||||
isEan8String = isEan8String()
|
|
||||||
|
|
||||||
class Ean8BarcodeWidget(Ean13BarcodeWidget):
|
|
||||||
codeName = "EAN8"
|
|
||||||
_attrMap = AttrMap(BASE=Ean13BarcodeWidget,
|
|
||||||
value = AttrMapValue(isEan8String, desc='the number'),
|
|
||||||
)
|
|
||||||
_start_right = 4 #for ean-13 left = [0:7] right=[7:13]
|
|
||||||
_nbars = 85
|
|
||||||
_digits=7
|
|
||||||
_0csw = 3
|
|
||||||
_1csw = 1
|
|
||||||
|
|
||||||
def _encode_left(self,s,a):
|
|
||||||
cp = self._lhconvert[s[0]] #convert the left hand numbers
|
|
||||||
_left = self._left[0]
|
|
||||||
z = ord('0')
|
|
||||||
for i,c in enumerate(s[0:self._start_right]):
|
|
||||||
a(_left[ord(c)-z])
|
|
||||||
|
|
||||||
def _short_bar(self,i):
|
|
||||||
i += 9 - self._lquiet
|
|
||||||
return self.humanReadable and ((12<i<41) or (43<i<73))
|
|
||||||
|
|
||||||
def _add_human_readable(self,s,gAdd):
|
|
||||||
barWidth = self.barWidth
|
|
||||||
fontSize = self.fontSize
|
|
||||||
textColor = self.textColor
|
|
||||||
fontName = self.fontName
|
|
||||||
fth = fontSize*1.2
|
|
||||||
# draw the num below the line.
|
|
||||||
y = self.y + 0.2*fth
|
|
||||||
|
|
||||||
x = (26.5-9+self._lquiet)*barWidth
|
|
||||||
|
|
||||||
c = s[0:4]
|
|
||||||
gAdd(String(x,y,c,fontName=fontName,fontSize=fontSize,fillColor=textColor,textAnchor='middle'))
|
|
||||||
|
|
||||||
x = (59.5-9+self._lquiet)*barWidth
|
|
||||||
c = s[4:]
|
|
||||||
gAdd(String(x,y,c,fontName=fontName,fontSize=fontSize,fillColor=textColor,textAnchor='middle'))
|
|
|
@ -1,81 +0,0 @@
|
||||||
#
|
|
||||||
# Copyright (c) 2000 Tyler C. Sarna <tsarna@sarna.org>
|
|
||||||
# All rights reserved.
|
|
||||||
#
|
|
||||||
# Redistribution and use in source and binary forms, with or without
|
|
||||||
# modification, are permitted provided that the following conditions
|
|
||||||
# are met:
|
|
||||||
# 1. Redistributions of source code must retain the above copyright
|
|
||||||
# notice, this list of conditions and the following disclaimer.
|
|
||||||
# 2. Redistributions in binary form must reproduce the above copyright
|
|
||||||
# notice, this list of conditions and the following disclaimer in the
|
|
||||||
# documentation and/or other materials provided with the distribution.
|
|
||||||
# 3. All advertising materials mentioning features or use of this software
|
|
||||||
# must display the following acknowledgement:
|
|
||||||
# This product includes software developed by Tyler C. Sarna.
|
|
||||||
# 4. Neither the name of the author nor the names of contributors
|
|
||||||
# may be used to endorse or promote products derived from this software
|
|
||||||
# without specific prior written permission.
|
|
||||||
#
|
|
||||||
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
|
|
||||||
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
||||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
|
||||||
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS
|
|
||||||
# BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
||||||
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
||||||
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
||||||
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
||||||
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
||||||
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
||||||
# POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
#
|
|
||||||
|
|
||||||
from reportlab.lib.units import inch
|
|
||||||
from common import Barcode
|
|
||||||
import string
|
|
||||||
|
|
||||||
# . 3 T Tracker
|
|
||||||
# , 2 D Descender
|
|
||||||
# ' 1 A Ascender
|
|
||||||
# | 0 H Ascender/Descender
|
|
||||||
|
|
||||||
_rm_patterns = {
|
|
||||||
"0" : "--||", "1" : "-',|", "2" : "-'|,", "3" : "'-,|",
|
|
||||||
"4" : "'-|,", "5" : "'',,", "6" : "-,'|", "7" : "-|-|",
|
|
||||||
"8" : "-|',", "9" : "',-|", "A" : "',',", "B" : "'|-,",
|
|
||||||
"C" : "-,|'", "D" : "-|,'", "E" : "-||-", "F" : "',,'",
|
|
||||||
"G" : "',|-", "H" : "'|,-", "I" : ",-'|", "J" : ",'-|",
|
|
||||||
"K" : ",'',", "L" : "|--|", "M" : "|-',", "N" : "|'-,",
|
|
||||||
"O" : ",-|'", "P" : ",','", "Q" : ",'|-", "R" : "|-,'",
|
|
||||||
"S" : "|-|-", "T" : "|',-", "U" : ",,''", "V" : ",|-'",
|
|
||||||
"W" : ",|'-", "X" : "|,-'", "Y" : "|,'-", "Z" : "||--",
|
|
||||||
|
|
||||||
# start, stop
|
|
||||||
"(" : "'-,'", ")" : "'|,|"
|
|
||||||
}
|
|
||||||
|
|
||||||
_ozN_patterns = {
|
|
||||||
"0" : "||", "1" : "|'", "2" : "|,", "3" : "'|", "4" : "''",
|
|
||||||
"5" : "',", "6" : ",|", "7" : ",'", "8" : ",,", "9" : ".|"
|
|
||||||
}
|
|
||||||
|
|
||||||
_ozC_patterns = {
|
|
||||||
"A" : "|||", "B" : "||'", "C" : "||,", "D" : "|'|",
|
|
||||||
"E" : "|''", "F" : "|',", "G" : "|,|", "H" : "|,'",
|
|
||||||
"I" : "|,,", "J" : "'||", "K" : "'|'", "L" : "'|,",
|
|
||||||
"M" : "''|", "N" : "'''", "O" : "'',", "P" : "',|",
|
|
||||||
"Q" : "','", "R" : "',,", "S" : ",||", "T" : ",|'",
|
|
||||||
"U" : ",|,", "V" : ",'|", "W" : ",''", "X" : ",',",
|
|
||||||
"Y" : ",,|", "Z" : ",,'", "a" : "|,.", "b" : "|.|",
|
|
||||||
"c" : "|.'", "d" : "|.,", "e" : "|..", "f" : "'|.",
|
|
||||||
"g" : "''.", "h" : "',.", "i" : "'.|", "j" : "'.'",
|
|
||||||
"k" : "'.,", "l" : "'..", "m" : ",|.", "n" : ",'.",
|
|
||||||
"o" : ",,.", "p" : ",.|", "q" : ",.'", "r" : ",.,",
|
|
||||||
"s" : ",..", "t" : ".|.", "u" : ".'.", "v" : ".,.",
|
|
||||||
"w" : "..|", "x" : "..'", "y" : "..,", "z" : "...",
|
|
||||||
"0" : ",,,", "1" : ".||", "2" : ".|'", "3" : ".|,",
|
|
||||||
"4" : ".'|", "5" : ".''", "6" : ".',", "7" : ".,|",
|
|
||||||
"8" : ".,'", "9" : ".,,", " " : "||.", "#" : "|'.",
|
|
||||||
}
|
|
||||||
|
|
||||||
#http://www.auspost.com.au/futurepost/
|
|
|
@ -1,185 +0,0 @@
|
||||||
#!/usr/pkg/bin/python
|
|
||||||
|
|
||||||
import os, sys, time
|
|
||||||
|
|
||||||
from reportlab.graphics.barcode.common import *
|
|
||||||
from reportlab.graphics.barcode.code39 import *
|
|
||||||
from reportlab.graphics.barcode.code93 import *
|
|
||||||
from reportlab.graphics.barcode.code128 import *
|
|
||||||
from reportlab.graphics.barcode.usps import *
|
|
||||||
|
|
||||||
|
|
||||||
from reportlab.test import unittest
|
|
||||||
from reportlab.test.utils import makeSuiteForClasses, outputfile, printLocation
|
|
||||||
from reportlab.platypus import Spacer, SimpleDocTemplate, Table, TableStyle, Preformatted, PageBreak
|
|
||||||
from reportlab.lib.units import inch, cm
|
|
||||||
from reportlab.lib import colors
|
|
||||||
|
|
||||||
from reportlab.pdfgen.canvas import Canvas
|
|
||||||
from reportlab.lib.styles import getSampleStyleSheet
|
|
||||||
from reportlab.platypus.paragraph import Paragraph
|
|
||||||
from reportlab.platypus.frames import Frame
|
|
||||||
from reportlab.platypus.flowables import XBox, KeepTogether
|
|
||||||
from reportlab.graphics.shapes import Drawing
|
|
||||||
|
|
||||||
from reportlab.graphics.barcode import getCodes, getCodeNames, createBarcodeDrawing
|
|
||||||
def run():
|
|
||||||
styles = getSampleStyleSheet()
|
|
||||||
styleN = styles['Normal']
|
|
||||||
styleH = styles['Heading1']
|
|
||||||
story = []
|
|
||||||
|
|
||||||
#for codeNames in code
|
|
||||||
story.append(Paragraph('I2of5', styleN))
|
|
||||||
story.append(I2of5(1234, barWidth = inch*0.02, checksum=0))
|
|
||||||
story.append(Paragraph('MSI', styleN))
|
|
||||||
story.append(MSI(1234))
|
|
||||||
story.append(Paragraph('Codabar', styleN))
|
|
||||||
story.append(Codabar("A012345B", barWidth = inch*0.02))
|
|
||||||
story.append(Paragraph('Code 11', styleN))
|
|
||||||
story.append(Code11("01234545634563"))
|
|
||||||
story.append(Paragraph('Code 39', styleN))
|
|
||||||
story.append(Standard39("A012345B%R"))
|
|
||||||
story.append(Paragraph('Extended Code 39', styleN))
|
|
||||||
story.append(Extended39("A012345B}"))
|
|
||||||
story.append(Paragraph('Code93', styleN))
|
|
||||||
story.append(Standard93("CODE 93"))
|
|
||||||
story.append(Paragraph('Extended Code93', styleN))
|
|
||||||
story.append(Extended93("L@@K! Code 93 :-)")) #, barWidth=0.005 * inch))
|
|
||||||
story.append(Paragraph('Code 128', styleN))
|
|
||||||
c=Code128("AB-12345678") #, barWidth=0.005 * inch)
|
|
||||||
#print 'WIDTH =', (c.width / inch), 'barWidth =', (c.barWidth / inch)
|
|
||||||
#print 'LQ =', (c.lquiet / inch), 'RQ =', (c.rquiet / inch)
|
|
||||||
story.append(c)
|
|
||||||
story.append(Paragraph('USPS FIM', styleN))
|
|
||||||
story.append(FIM("A"))
|
|
||||||
story.append(Paragraph('USPS POSTNET', styleN))
|
|
||||||
story.append(POSTNET('78247-1043'))
|
|
||||||
|
|
||||||
from reportlab.graphics.barcode import createBarcodeDrawing
|
|
||||||
story.append(Paragraph('EAN13', styleN))
|
|
||||||
bcd = createBarcodeDrawing('EAN13', value='123456789012')
|
|
||||||
story.append(bcd)
|
|
||||||
story.append(Paragraph('EAN8', styleN))
|
|
||||||
bcd = createBarcodeDrawing('EAN8', value='1234567')
|
|
||||||
story.append(bcd)
|
|
||||||
|
|
||||||
story.append(Paragraph('Label Size', styleN))
|
|
||||||
story.append(XBox((2.0 + 5.0/8.0)*inch, 1 * inch, '1x2-5/8"'))
|
|
||||||
story.append(Paragraph('Label Size', styleN))
|
|
||||||
story.append(XBox((1.75)*inch, .5 * inch, '1/2x1-3/4"'))
|
|
||||||
c = Canvas('out.pdf')
|
|
||||||
f = Frame(inch, inch, 6*inch, 9*inch, showBoundary=1)
|
|
||||||
f.addFromList(story, c)
|
|
||||||
c.save()
|
|
||||||
print 'saved out.pdf'
|
|
||||||
|
|
||||||
def fullTest(fileName="test_full.pdf"):
|
|
||||||
"""Creates large-ish test document with a variety of parameters"""
|
|
||||||
|
|
||||||
story = []
|
|
||||||
|
|
||||||
styles = getSampleStyleSheet()
|
|
||||||
styleN = styles['Normal']
|
|
||||||
styleH = styles['Heading1']
|
|
||||||
styleH2 = styles['Heading2']
|
|
||||||
story = []
|
|
||||||
|
|
||||||
story.append(Paragraph('ReportLab Barcode Test Suite - full output', styleH))
|
|
||||||
story.append(Paragraph('Generated on %s' % time.ctime(time.time()), styleN))
|
|
||||||
|
|
||||||
story.append(Paragraph('', styleN))
|
|
||||||
story.append(Paragraph('Repository information for this build:', styleN))
|
|
||||||
#see if we can figure out where it was built, if we're running in source
|
|
||||||
if os.path.split(os.getcwd())[-1] == 'barcode' and os.path.isdir('.svn'):
|
|
||||||
#runnning in a filesystem svn copy
|
|
||||||
infoLines = os.popen('svn info').read()
|
|
||||||
story.append(Preformatted(infoLines, styles["Code"]))
|
|
||||||
|
|
||||||
story.append(Paragraph('About this document', styleH2))
|
|
||||||
story.append(Paragraph('History and Status', styleH2))
|
|
||||||
|
|
||||||
story.append(Paragraph("""
|
|
||||||
This is the test suite and docoumentation for the ReportLab open source barcode API,
|
|
||||||
being re-released as part of the forthcoming ReportLab 2.0 release.
|
|
||||||
""", styleN))
|
|
||||||
|
|
||||||
story.append(Paragraph("""
|
|
||||||
Several years ago Ty Sarna contributed a barcode module to the ReportLab community.
|
|
||||||
Several of the codes were used by him in hiw work and to the best of our knowledge
|
|
||||||
this was correct. These were written as flowable objects and were available in PDFs,
|
|
||||||
but not in our graphics framework. However, we had no knowledge of barcodes ourselves
|
|
||||||
and did not advertise or extend the package.
|
|
||||||
""", styleN))
|
|
||||||
|
|
||||||
story.append(Paragraph("""
|
|
||||||
We "wrapped" the barcodes to be usable within our graphics framework; they are now available
|
|
||||||
as Drawing objects which can be rendered to EPS files or bitmaps. For the last 2 years this
|
|
||||||
has been available in our Diagra and Report Markup Language products. However, we did not
|
|
||||||
charge separately and use was on an "as is" basis.
|
|
||||||
""", styleN))
|
|
||||||
|
|
||||||
story.append(Paragraph("""
|
|
||||||
A major licensee of our technology has kindly agreed to part-fund proper productisation
|
|
||||||
of this code on an open source basis in Q1 2006. This has involved addition of EAN codes
|
|
||||||
as well as a proper testing program. Henceforth we intend to publicise the code more widely,
|
|
||||||
gather feedback, accept contributions of code and treat it as "supported".
|
|
||||||
""", styleN))
|
|
||||||
|
|
||||||
story.append(Paragraph("""
|
|
||||||
This involved making available both downloads and testing resources. This PDF document
|
|
||||||
is the output of the current test suite. It contains codes you can scan (if you use a nice sharp
|
|
||||||
laser printer!), and will be extended over coming weeks to include usage examples and notes on
|
|
||||||
each barcode and how widely tested they are. This is being done through documentation strings in
|
|
||||||
the barcode objects themselves so should always be up to date.
|
|
||||||
""", styleN))
|
|
||||||
|
|
||||||
story.append(Paragraph('Usage examples', styleH2))
|
|
||||||
story.append(Paragraph("""
|
|
||||||
To be completed
|
|
||||||
""", styleN))
|
|
||||||
|
|
||||||
story.append(Paragraph('The codes', styleH2))
|
|
||||||
story.append(Paragraph("""
|
|
||||||
Below we show a scannable code from each barcode, with and without human-readable text.
|
|
||||||
These are magnified about 2x from the natural size done by the original author to aid
|
|
||||||
inspection. This will be expanded to include several test cases per code, and to add
|
|
||||||
explanations of checksums. Be aware that (a) if you enter numeric codes which are too
|
|
||||||
short they may be prefixed for you (e.g. "123" for an 8-digit code becomes "00000123"),
|
|
||||||
and that the scanned results and readable text will generally include extra checksums
|
|
||||||
at the end.
|
|
||||||
""", styleN))
|
|
||||||
|
|
||||||
codeNames = getCodeNames()
|
|
||||||
from reportlab.lib.utils import flatten
|
|
||||||
width = [float(x[8:]) for x in sys.argv if x.startswith('--width=')]
|
|
||||||
height = [float(x[9:]) for x in sys.argv if x.startswith('--height=')]
|
|
||||||
isoScale = [int(x[11:]) for x in sys.argv if x.startswith('--isoscale=')]
|
|
||||||
options = {}
|
|
||||||
if width: options['width'] = width[0]
|
|
||||||
if height: options['height'] = height[0]
|
|
||||||
if isoScale: options['isoScale'] = isoScale[0]
|
|
||||||
scales = [x[8:].split(',') for x in sys.argv if x.startswith('--scale=')]
|
|
||||||
scales = map(float,scales and flatten(scales) or [1])
|
|
||||||
scales = map(float,scales and flatten(scales) or [1])
|
|
||||||
for scale in scales:
|
|
||||||
story.append(PageBreak())
|
|
||||||
story.append(Paragraph('Scale = %.1f'%scale, styleH2))
|
|
||||||
story.append(Spacer(36, 12))
|
|
||||||
for codeName in codeNames:
|
|
||||||
s = [Paragraph('Code: ' + codeName, styleH2)]
|
|
||||||
for hr in (0,1):
|
|
||||||
s.append(Spacer(36, 12))
|
|
||||||
dr = createBarcodeDrawing(codeName, humanReadable=hr,**options)
|
|
||||||
dr.renderScale = scale
|
|
||||||
s.append(dr)
|
|
||||||
s.append(Spacer(36, 12))
|
|
||||||
s.append(Paragraph('Barcode should say: ' + dr._bc.value, styleN))
|
|
||||||
story.append(KeepTogether(s))
|
|
||||||
|
|
||||||
SimpleDocTemplate(fileName).build(story)
|
|
||||||
print 'created', fileName
|
|
||||||
|
|
||||||
if __name__=='__main__':
|
|
||||||
run()
|
|
||||||
fullTest()
|
|
|
@ -1,228 +0,0 @@
|
||||||
#
|
|
||||||
# Copyright (c) 1996-2000 Tyler C. Sarna <tsarna@sarna.org>
|
|
||||||
# All rights reserved.
|
|
||||||
#
|
|
||||||
# Redistribution and use in source and binary forms, with or without
|
|
||||||
# modification, are permitted provided that the following conditions
|
|
||||||
# are met:
|
|
||||||
# 1. Redistributions of source code must retain the above copyright
|
|
||||||
# notice, this list of conditions and the following disclaimer.
|
|
||||||
# 2. Redistributions in binary form must reproduce the above copyright
|
|
||||||
# notice, this list of conditions and the following disclaimer in the
|
|
||||||
# documentation and/or other materials provided with the distribution.
|
|
||||||
# 3. All advertising materials mentioning features or use of this software
|
|
||||||
# must display the following acknowledgement:
|
|
||||||
# This product includes software developed by Tyler C. Sarna.
|
|
||||||
# 4. Neither the name of the author nor the names of contributors
|
|
||||||
# may be used to endorse or promote products derived from this software
|
|
||||||
# without specific prior written permission.
|
|
||||||
#
|
|
||||||
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
|
|
||||||
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
||||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
|
||||||
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS
|
|
||||||
# BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
||||||
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
||||||
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
||||||
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
||||||
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
||||||
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
||||||
# POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
#
|
|
||||||
|
|
||||||
from reportlab.lib.units import inch
|
|
||||||
from common import Barcode
|
|
||||||
import string
|
|
||||||
|
|
||||||
_fim_patterns = {
|
|
||||||
'A' : "|| | ||",
|
|
||||||
'B' : "| || || |",
|
|
||||||
'C' : "|| | | ||",
|
|
||||||
'D' : "||| | |||",
|
|
||||||
# XXX There is an E.
|
|
||||||
# The below has been seen, but dunno if it is E or not:
|
|
||||||
# 'E' : '|||| ||||'
|
|
||||||
}
|
|
||||||
|
|
||||||
_postnet_patterns = {
|
|
||||||
'1' : "...||", '2' : "..|.|", '3' : "..||.", '4' : ".|..|",
|
|
||||||
'5' : ".|.|.", '6' : ".||..", '7' : "|...|", '8' : "|..|.",
|
|
||||||
'9' : "|.|..", '0' : "||...", 'S' : "|",
|
|
||||||
}
|
|
||||||
|
|
||||||
class FIM(Barcode):
|
|
||||||
""""
|
|
||||||
FIM (Facing ID Marks) encode only one letter.
|
|
||||||
There are currently four defined:
|
|
||||||
|
|
||||||
A Courtesy reply mail with pre-printed POSTNET
|
|
||||||
B Business reply mail without pre-printed POSTNET
|
|
||||||
C Business reply mail with pre-printed POSTNET
|
|
||||||
D OCR Readable mail without pre-printed POSTNET
|
|
||||||
|
|
||||||
Options that may be passed to constructor:
|
|
||||||
|
|
||||||
value (single character string from the set A - D. required.):
|
|
||||||
The value to encode.
|
|
||||||
|
|
||||||
quiet (bool, default 0):
|
|
||||||
Whether to include quiet zones in the symbol.
|
|
||||||
|
|
||||||
The following may also be passed, but doing so will generate nonstandard
|
|
||||||
symbols which should not be used. This is mainly documented here to
|
|
||||||
show the defaults:
|
|
||||||
|
|
||||||
barHeight (float, default 5/8 inch):
|
|
||||||
Height of the code. This might legitimately be overriden to make
|
|
||||||
a taller symbol that will 'bleed' off the edge of the paper,
|
|
||||||
leaving 5/8 inch remaining.
|
|
||||||
|
|
||||||
lquiet (float, default 1/4 inch):
|
|
||||||
Quiet zone size to left of code, if quiet is true.
|
|
||||||
Default is the greater of .25 inch, or .15 times the symbol's
|
|
||||||
length.
|
|
||||||
|
|
||||||
rquiet (float, default 15/32 inch):
|
|
||||||
Quiet zone size to right left of code, if quiet is true.
|
|
||||||
|
|
||||||
Sources of information on FIM:
|
|
||||||
|
|
||||||
USPS Publication 25, A Guide to Business Mail Preparation
|
|
||||||
http://new.usps.com/cpim/ftp/pubs/pub25.pdf
|
|
||||||
"""
|
|
||||||
barWidth = inch * (1.0/32.0)
|
|
||||||
spaceWidth = inch * (1.0/16.0)
|
|
||||||
barHeight = inch * (5.0/8.0)
|
|
||||||
rquiet = inch * (0.25)
|
|
||||||
lquiet = inch * (15.0/32.0)
|
|
||||||
quiet = 0
|
|
||||||
def __init__(self, value='', **args):
|
|
||||||
for (k, v) in args.items():
|
|
||||||
setattr(self, k, v)
|
|
||||||
|
|
||||||
Barcode.__init__(self, value)
|
|
||||||
|
|
||||||
def validate(self):
|
|
||||||
self.valid = 1
|
|
||||||
self.validated = ''
|
|
||||||
for c in self.value:
|
|
||||||
if c in string.whitespace:
|
|
||||||
continue
|
|
||||||
elif c in "abcdABCD":
|
|
||||||
self.validated = self.validated + string.upper(c)
|
|
||||||
else:
|
|
||||||
self.valid = 0
|
|
||||||
|
|
||||||
if len(self.validated) != 1:
|
|
||||||
raise ValueError, "Input must be exactly one character"
|
|
||||||
|
|
||||||
return self.validated
|
|
||||||
|
|
||||||
def decompose(self):
|
|
||||||
self.decomposed = ''
|
|
||||||
for c in self.encoded:
|
|
||||||
self.decomposed = self.decomposed + _fim_patterns[c]
|
|
||||||
|
|
||||||
return self.decomposed
|
|
||||||
|
|
||||||
def computeSize(self):
|
|
||||||
self.width = (len(self.decomposed) - 1) * self.spaceWidth + self.barWidth
|
|
||||||
if self.quiet:
|
|
||||||
self.width += self.lquiet + self.rquiet
|
|
||||||
self.height = self.barHeight
|
|
||||||
|
|
||||||
def draw(self):
|
|
||||||
left = self.quiet and self.lquiet or 0
|
|
||||||
for c in self.decomposed:
|
|
||||||
if c == '|':
|
|
||||||
self.rect(left, 0.0, self.barWidth, self.barHeight)
|
|
||||||
left += self.spaceWidth
|
|
||||||
self.drawHumanReadable()
|
|
||||||
|
|
||||||
def _humanText(self):
|
|
||||||
return self.value
|
|
||||||
|
|
||||||
class POSTNET(Barcode):
|
|
||||||
""""
|
|
||||||
POSTNET is used in the US to encode "zip codes" (postal codes) on
|
|
||||||
mail. It can encode 5, 9, or 11 digit codes. I've read that it's
|
|
||||||
pointless to do 5 digits, since USPS will just have to re-print
|
|
||||||
them with 9 or 11 digits.
|
|
||||||
|
|
||||||
Sources of information on POSTNET:
|
|
||||||
|
|
||||||
USPS Publication 25, A Guide to Business Mail Preparation
|
|
||||||
http://new.usps.com/cpim/ftp/pubs/pub25.pdf
|
|
||||||
"""
|
|
||||||
quiet = 0
|
|
||||||
shortHeight = inch * 0.050
|
|
||||||
barHeight = inch * 0.125
|
|
||||||
barWidth = inch * 0.018
|
|
||||||
spaceWidth = inch * 0.0275
|
|
||||||
def __init__(self, value='', **args):
|
|
||||||
|
|
||||||
for (k, v) in args.items():
|
|
||||||
setattr(self, k, v)
|
|
||||||
|
|
||||||
Barcode.__init__(self, value)
|
|
||||||
|
|
||||||
def validate(self):
|
|
||||||
self.validated = ''
|
|
||||||
self.valid = 1
|
|
||||||
count = 0
|
|
||||||
for c in self.value:
|
|
||||||
if c in (string.whitespace + '-'):
|
|
||||||
pass
|
|
||||||
elif c in string.digits:
|
|
||||||
count = count + 1
|
|
||||||
if count == 6:
|
|
||||||
self.validated = self.validated + '-'
|
|
||||||
self.validated = self.validated + c
|
|
||||||
else:
|
|
||||||
self.valid = 0
|
|
||||||
|
|
||||||
if len(self.validated) not in [5, 10, 12]:
|
|
||||||
self.valid = 0
|
|
||||||
|
|
||||||
return self.validated
|
|
||||||
|
|
||||||
def encode(self):
|
|
||||||
self.encoded = "S"
|
|
||||||
check = 0
|
|
||||||
for c in self.validated:
|
|
||||||
if c in string.digits:
|
|
||||||
self.encoded = self.encoded + c
|
|
||||||
check = check + string.atoi(c)
|
|
||||||
elif c == '-':
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
raise ValueError, "Invalid character in input"
|
|
||||||
check = (10 - (check % 10)) % 10
|
|
||||||
self.encoded = self.encoded + `check` + 'S'
|
|
||||||
return self.encoded
|
|
||||||
|
|
||||||
def decompose(self):
|
|
||||||
self.decomposed = ''
|
|
||||||
for c in self.encoded:
|
|
||||||
self.decomposed = self.decomposed + _postnet_patterns[c]
|
|
||||||
return self.decomposed
|
|
||||||
|
|
||||||
def computeSize(self):
|
|
||||||
self.width = len(self.decomposed) * self.barWidth + (len(self.decomposed) - 1) * self.spaceWidth
|
|
||||||
self.height = self.barHeight
|
|
||||||
|
|
||||||
def draw(self):
|
|
||||||
sdown = self.barHeight - self.shortHeight
|
|
||||||
left = 0
|
|
||||||
|
|
||||||
for c in self.decomposed:
|
|
||||||
if c == '.':
|
|
||||||
h = self.shortHeight
|
|
||||||
else:
|
|
||||||
h = self.barHeight
|
|
||||||
self.rect(left, 0.0, self.barWidth, h)
|
|
||||||
left = left + self.barWidth + self.spaceWidth
|
|
||||||
self.drawHumanReadable()
|
|
||||||
|
|
||||||
def _humanText(self):
|
|
||||||
return self.encoded[1:-1]
|
|
|
@ -1,304 +0,0 @@
|
||||||
#copyright ReportLab Europe Limited. 2000-2006
|
|
||||||
#see license.txt for license details
|
|
||||||
__version__=''' $Id: widgets.py 2851 2006-05-08 14:34:45Z rgbecker $ '''
|
|
||||||
__all__= (
|
|
||||||
'BarcodeI2of5',
|
|
||||||
'BarcodeCode128',
|
|
||||||
'BarcodeStandard93',
|
|
||||||
'BarcodeExtended93',
|
|
||||||
'BarcodeStandard39',
|
|
||||||
'BarcodeExtended39',
|
|
||||||
'BarcodeMSI',
|
|
||||||
'BarcodeCodabar',
|
|
||||||
'BarcodeCode11',
|
|
||||||
'BarcodeFIM',
|
|
||||||
'BarcodePOSTNET',
|
|
||||||
)
|
|
||||||
|
|
||||||
from reportlab.lib.validators import isInt, isNumber, isColor, isString, isColorOrNone, OneOf, isBoolean, EitherOr, isNumberOrNone
|
|
||||||
from reportlab.lib.attrmap import AttrMap, AttrMapValue
|
|
||||||
from reportlab.lib.colors import black
|
|
||||||
from reportlab.graphics.shapes import Line, Rect, Group, NotImplementedError, String
|
|
||||||
from reportlab.graphics.charts.areas import PlotArea
|
|
||||||
|
|
||||||
'''
|
|
||||||
#snippet
|
|
||||||
|
|
||||||
#first make your Drawing
|
|
||||||
from reportlab.graphics.shapes import Drawing
|
|
||||||
d= Drawing(100,50)
|
|
||||||
|
|
||||||
#create and set up the widget
|
|
||||||
from reportlab.graphics.barcode.widgets import BarcodeStandard93
|
|
||||||
bc = BarcodeStandard93()
|
|
||||||
bc.value = 'RGB-123456'
|
|
||||||
|
|
||||||
#add to the drawing and save
|
|
||||||
d.add(bc)
|
|
||||||
# d.save(formats=['gif','pict'],fnRoot='bc_sample')
|
|
||||||
'''
|
|
||||||
|
|
||||||
class _BarcodeWidget(PlotArea):
|
|
||||||
_attrMap = AttrMap(BASE=PlotArea,
|
|
||||||
barStrokeColor = AttrMapValue(isColorOrNone, desc='Color of bar borders.'),
|
|
||||||
barFillColor = AttrMapValue(isColorOrNone, desc='Color of bar interior areas.'),
|
|
||||||
barStrokeWidth = AttrMapValue(isNumber, desc='Width of bar borders.'),
|
|
||||||
value = AttrMapValue(EitherOr((isString,isNumber)), desc='Value.'),
|
|
||||||
textColor = AttrMapValue(isColorOrNone, desc='Color of human readable text.'),
|
|
||||||
valid = AttrMapValue(isBoolean),
|
|
||||||
validated = AttrMapValue(isString,desc="validated form of input"),
|
|
||||||
encoded = AttrMapValue(None,desc="encoded form of input"),
|
|
||||||
decomposed = AttrMapValue(isString,desc="decomposed form of input"),
|
|
||||||
canv = AttrMapValue(None,desc="temporarily used for internal methods"),
|
|
||||||
gap = AttrMapValue(isNumberOrNone, desc='Width of inter character gaps.'),
|
|
||||||
)
|
|
||||||
|
|
||||||
barStrokeColor = barFillColor = textColor = black
|
|
||||||
barStrokeWidth = 0
|
|
||||||
_BCC = None
|
|
||||||
def __init__(self,BCC=None,_value='',**kw):
|
|
||||||
self._BCC = BCC
|
|
||||||
class Combiner(self.__class__,BCC):
|
|
||||||
__name__ = self.__class__.__name__
|
|
||||||
self.__class__ = Combiner
|
|
||||||
PlotArea.__init__(self)
|
|
||||||
self.x = self.y = 0
|
|
||||||
kw.setdefault('value',_value)
|
|
||||||
BCC.__init__(self,**kw)
|
|
||||||
|
|
||||||
def rect(self,x,y,w,h,**kw):
|
|
||||||
self._Gadd(Rect(self.x+x,self.y+y,w,h,
|
|
||||||
strokeColor=self.barStrokeColor,strokeWidth=self.barStrokeWidth, fillColor=self.barFillColor))
|
|
||||||
|
|
||||||
def draw(self):
|
|
||||||
if not self._BCC: raise NotImplementedError("Abstract class %s cannot be drawn" % self.__class__.__name__)
|
|
||||||
self.canv = self
|
|
||||||
G = Group()
|
|
||||||
self._Gadd = G.add
|
|
||||||
self._Gadd(Rect(self.x,self.y,self.width,self.height,fillColor=None,strokeColor=None,strokeWidth=0.0001))
|
|
||||||
self._BCC.draw(self)
|
|
||||||
del self.canv, self._Gadd
|
|
||||||
return G
|
|
||||||
|
|
||||||
def annotate(self,x,y,text,fontName,fontSize,anchor='middle'):
|
|
||||||
self._Gadd(String(self.x+x,self.y+y,text,fontName=fontName,fontSize=fontSize,
|
|
||||||
textAnchor=anchor,fillColor=self.textColor))
|
|
||||||
|
|
||||||
class BarcodeI2of5(_BarcodeWidget):
|
|
||||||
"""Interleaved 2 of 5 is used in distribution and warehouse industries.
|
|
||||||
|
|
||||||
It encodes an even-numbered sequence of numeric digits. There is an optional
|
|
||||||
module 10 check digit; if including this, the total length must be odd so that
|
|
||||||
it becomes even after including the check digit. Otherwise the length must be
|
|
||||||
even. Since the check digit is optional, our library does not check it.
|
|
||||||
"""
|
|
||||||
|
|
||||||
_tests = [
|
|
||||||
'12',
|
|
||||||
'1234',
|
|
||||||
'123456',
|
|
||||||
'12345678',
|
|
||||||
'1234567890'
|
|
||||||
]
|
|
||||||
codeName = "I2of5"
|
|
||||||
_attrMap = AttrMap(BASE=_BarcodeWidget,
|
|
||||||
barWidth = AttrMapValue(isNumber,'''(float, default .0075):
|
|
||||||
X-Dimension, or width of the smallest element
|
|
||||||
Minumum is .0075 inch (7.5 mils).'''),
|
|
||||||
ratio = AttrMapValue(isNumber,'''(float, default 2.2):
|
|
||||||
The ratio of wide elements to narrow elements.
|
|
||||||
Must be between 2.0 and 3.0 (or 2.2 and 3.0 if the
|
|
||||||
barWidth is greater than 20 mils (.02 inch))'''),
|
|
||||||
gap = AttrMapValue(isNumberOrNone,'''(float or None, default None):
|
|
||||||
width of intercharacter gap. None means "use barWidth".'''),
|
|
||||||
barHeight = AttrMapValue(isNumber,'''(float, see default below):
|
|
||||||
Height of the symbol. Default is the height of the two
|
|
||||||
bearer bars (if they exist) plus the greater of .25 inch
|
|
||||||
or .15 times the symbol's length.'''),
|
|
||||||
checksum = AttrMapValue(isBoolean,'''(bool, default 1):
|
|
||||||
Whether to compute and include the check digit'''),
|
|
||||||
bearers = AttrMapValue(isNumber,'''(float, in units of barWidth. default 3.0):
|
|
||||||
Height of bearer bars (horizontal bars along the top and
|
|
||||||
bottom of the barcode). Default is 3 x-dimensions.
|
|
||||||
Set to zero for no bearer bars. (Bearer bars help detect
|
|
||||||
misscans, so it is suggested to leave them on).'''),
|
|
||||||
quiet = AttrMapValue(isBoolean,'''(bool, default 1):
|
|
||||||
Whether to include quiet zones in the symbol.'''),
|
|
||||||
|
|
||||||
lquiet = AttrMapValue(isNumber,'''(float, see default below):
|
|
||||||
Quiet zone size to left of code, if quiet is true.
|
|
||||||
Default is the greater of .25 inch, or .15 times the symbol's
|
|
||||||
length.'''),
|
|
||||||
|
|
||||||
rquiet = AttrMapValue(isNumber,'''(float, defaults as above):
|
|
||||||
Quiet zone size to right left of code, if quiet is true.'''),
|
|
||||||
fontName = AttrMapValue(isString, desc='human readable font'),
|
|
||||||
fontSize = AttrMapValue(isNumber, desc='human readable font size'),
|
|
||||||
humanReadable = AttrMapValue(isBoolean, desc='if human readable'),
|
|
||||||
stop = AttrMapValue(isBoolean, desc='if we use start/stop symbols (default 1)'),
|
|
||||||
)
|
|
||||||
_bcTransMap = {}
|
|
||||||
|
|
||||||
def __init__(self,**kw):
|
|
||||||
from reportlab.graphics.barcode.common import I2of5
|
|
||||||
_BarcodeWidget.__init__(self,I2of5,1234,**kw)
|
|
||||||
|
|
||||||
class BarcodeCode128(BarcodeI2of5):
|
|
||||||
"""Code 128 encodes any number of characters in the ASCII character set.
|
|
||||||
"""
|
|
||||||
_tests = [
|
|
||||||
'ReportLab Rocks!'
|
|
||||||
]
|
|
||||||
codeName = "Code128"
|
|
||||||
_attrMap = AttrMap(BASE=BarcodeI2of5,UNWANTED=('bearers','checksum','ratio','checksum','stop'))
|
|
||||||
def __init__(self,**kw):
|
|
||||||
from reportlab.graphics.barcode.code128 import Code128
|
|
||||||
_BarcodeWidget.__init__(self,Code128,"AB-12345678",**kw)
|
|
||||||
|
|
||||||
class BarcodeStandard93(BarcodeCode128):
|
|
||||||
"""This is a compressed form of Code 39"""
|
|
||||||
codeName = "Standard93"
|
|
||||||
_attrMap = AttrMap(BASE=BarcodeCode128,
|
|
||||||
stop = AttrMapValue(isBoolean, desc='if we use start/stop symbols (default 1)'),
|
|
||||||
)
|
|
||||||
def __init__(self,**kw):
|
|
||||||
from reportlab.graphics.barcode.code93 import Standard93
|
|
||||||
_BarcodeWidget.__init__(self,Standard93,"CODE 93",**kw)
|
|
||||||
|
|
||||||
class BarcodeExtended93(BarcodeStandard93):
|
|
||||||
"""This is a compressed form of Code 39, allowing the full ASCII charset"""
|
|
||||||
codeName = "Extended93"
|
|
||||||
def __init__(self,**kw):
|
|
||||||
from reportlab.graphics.barcode.code93 import Extended93
|
|
||||||
_BarcodeWidget.__init__(self,Extended93,"L@@K! Code 93 ;-)",**kw)
|
|
||||||
|
|
||||||
class BarcodeStandard39(BarcodeI2of5):
|
|
||||||
"""Code39 is widely used in non-retail, especially US defence and health.
|
|
||||||
Allowed characters are 0-9, A-Z (caps only), space, and -.$/+%*.
|
|
||||||
"""
|
|
||||||
|
|
||||||
codeName = "Standard39"
|
|
||||||
def __init__(self,**kw):
|
|
||||||
from reportlab.graphics.barcode.code39 import Standard39
|
|
||||||
_BarcodeWidget.__init__(self,Standard39,"A012345B%R",**kw)
|
|
||||||
|
|
||||||
class BarcodeExtended39(BarcodeI2of5):
|
|
||||||
"""Extended 39 encodes the full ASCII character set by encoding
|
|
||||||
characters as pairs of Code 39 characters; $, /, % and + are used as
|
|
||||||
shift characters."""
|
|
||||||
|
|
||||||
codeName = "Extended39"
|
|
||||||
def __init__(self,**kw):
|
|
||||||
from reportlab.graphics.barcode.code39 import Extended39
|
|
||||||
_BarcodeWidget.__init__(self,Extended39,"A012345B}",**kw)
|
|
||||||
|
|
||||||
class BarcodeMSI(BarcodeI2of5):
|
|
||||||
"""MSI is used for inventory control in retail applications.
|
|
||||||
|
|
||||||
There are several methods for calculating check digits so we
|
|
||||||
do not implement one.
|
|
||||||
"""
|
|
||||||
codeName = "MSI"
|
|
||||||
def __init__(self,**kw):
|
|
||||||
from reportlab.graphics.barcode.common import MSI
|
|
||||||
_BarcodeWidget.__init__(self,MSI,1234,**kw)
|
|
||||||
|
|
||||||
class BarcodeCodabar(BarcodeI2of5):
|
|
||||||
"""Used in blood banks, photo labs and FedEx labels.
|
|
||||||
Encodes 0-9, -$:/.+, and four start/stop characters A-D.
|
|
||||||
"""
|
|
||||||
codeName = "Codabar"
|
|
||||||
def __init__(self,**kw):
|
|
||||||
from reportlab.graphics.barcode.common import Codabar
|
|
||||||
_BarcodeWidget.__init__(self,Codabar,"A012345B",**kw)
|
|
||||||
|
|
||||||
class BarcodeCode11(BarcodeI2of5):
|
|
||||||
"""Used mostly for labelling telecommunications equipment.
|
|
||||||
It encodes numeric digits.
|
|
||||||
"""
|
|
||||||
codeName = "Code11"
|
|
||||||
_attrMap = AttrMap(BASE=BarcodeI2of5,
|
|
||||||
checksum = AttrMapValue(isInt,'''(integer, default 2):
|
|
||||||
Whether to compute and include the check digit(s).
|
|
||||||
(0 none, 1 1-digit, 2 2-digit, -1 auto, default -1):
|
|
||||||
How many checksum digits to include. -1 ("auto") means
|
|
||||||
1 if the number of digits is 10 or less, else 2.'''),
|
|
||||||
)
|
|
||||||
def __init__(self,**kw):
|
|
||||||
from reportlab.graphics.barcode.common import Code11
|
|
||||||
_BarcodeWidget.__init__(self,Code11,"01234545634563",**kw)
|
|
||||||
|
|
||||||
class BarcodeFIM(_BarcodeWidget):
|
|
||||||
"""
|
|
||||||
FIM was developed as part of the POSTNET barcoding system. FIM (Face Identification Marking) is used by the cancelling machines to sort mail according to whether or not they have bar code and their postage requirements. There are four types of FIM called FIM A, FIM B, FIM C, and FIM D.
|
|
||||||
|
|
||||||
The four FIM types have the following meanings:
|
|
||||||
FIM A- Postage required pre-barcoded
|
|
||||||
FIM B - Postage pre-paid, no bar code exists
|
|
||||||
FIM C- Postage prepaid prebarcoded
|
|
||||||
FIM D- Postage required, no bar code exists
|
|
||||||
"""
|
|
||||||
codeName = "FIM"
|
|
||||||
_attrMap = AttrMap(BASE=_BarcodeWidget,
|
|
||||||
barWidth = AttrMapValue(isNumber,'''(float, default 1/32in): the bar width.'''),
|
|
||||||
spaceWidth = AttrMapValue(isNumber,'''(float or None, default 1/16in):
|
|
||||||
width of intercharacter gap. None means "use barWidth".'''),
|
|
||||||
barHeight = AttrMapValue(isNumber,'''(float, default 5/8in): The bar height.'''),
|
|
||||||
quiet = AttrMapValue(isBoolean,'''(bool, default 0):
|
|
||||||
Whether to include quiet zones in the symbol.'''),
|
|
||||||
lquiet = AttrMapValue(isNumber,'''(float, default: 15/32in):
|
|
||||||
Quiet zone size to left of code, if quiet is true.'''),
|
|
||||||
rquiet = AttrMapValue(isNumber,'''(float, default 1/4in):
|
|
||||||
Quiet zone size to right left of code, if quiet is true.'''),
|
|
||||||
fontName = AttrMapValue(isString, desc='human readable font'),
|
|
||||||
fontSize = AttrMapValue(isNumber, desc='human readable font size'),
|
|
||||||
humanReadable = AttrMapValue(isBoolean, desc='if human readable'),
|
|
||||||
)
|
|
||||||
def __init__(self,**kw):
|
|
||||||
from reportlab.graphics.barcode.usps import FIM
|
|
||||||
_BarcodeWidget.__init__(self,FIM,"A",**kw)
|
|
||||||
|
|
||||||
class BarcodePOSTNET(_BarcodeWidget):
|
|
||||||
codeName = "POSTNET"
|
|
||||||
_attrMap = AttrMap(BASE=_BarcodeWidget,
|
|
||||||
barWidth = AttrMapValue(isNumber,'''(float, default 0.018*in): the bar width.'''),
|
|
||||||
spaceWidth = AttrMapValue(isNumber,'''(float or None, default 0.0275in): width of intercharacter gap.'''),
|
|
||||||
shortHeight = AttrMapValue(isNumber,'''(float, default 0.05in): The short bar height.'''),
|
|
||||||
barHeight = AttrMapValue(isNumber,'''(float, default 0.125in): The full bar height.'''),
|
|
||||||
fontName = AttrMapValue(isString, desc='human readable font'),
|
|
||||||
fontSize = AttrMapValue(isNumber, desc='human readable font size'),
|
|
||||||
humanReadable = AttrMapValue(isBoolean, desc='if human readable'),
|
|
||||||
)
|
|
||||||
def __init__(self,**kw):
|
|
||||||
from reportlab.graphics.barcode.usps import POSTNET
|
|
||||||
_BarcodeWidget.__init__(self,POSTNET,"78247-1043",**kw)
|
|
||||||
|
|
||||||
if __name__=='__main__':
|
|
||||||
import os, sys, glob
|
|
||||||
from reportlab.graphics.shapes import Drawing
|
|
||||||
os.chdir(os.path.dirname(sys.argv[0]))
|
|
||||||
if not os.path.isdir('out'):
|
|
||||||
os.mkdir('out')
|
|
||||||
map(os.remove,glob.glob(os.path.join('out','*')))
|
|
||||||
html = ['<html><head></head><body>']
|
|
||||||
a = html.append
|
|
||||||
for C in (BarcodeI2of5,
|
|
||||||
BarcodeCode128,
|
|
||||||
BarcodeStandard93,
|
|
||||||
BarcodeExtended93,
|
|
||||||
BarcodeStandard39,
|
|
||||||
BarcodeExtended39,
|
|
||||||
BarcodeMSI,
|
|
||||||
BarcodeCodabar,
|
|
||||||
BarcodeCode11,
|
|
||||||
BarcodeFIM,
|
|
||||||
BarcodePOSTNET,
|
|
||||||
):
|
|
||||||
name = C.__name__
|
|
||||||
i = C()
|
|
||||||
D = Drawing(100,50)
|
|
||||||
D.add(i)
|
|
||||||
D.save(formats=['gif','pict'],outDir='out',fnRoot=name)
|
|
||||||
a('<h2>%s</h2><img src="%s.gif"><br>' % (name, name))
|
|
||||||
a('</body></html>')
|
|
||||||
open(os.path.join('out','index.html'),'w').write('\n'.join(html))
|
|
|
@ -1,4 +0,0 @@
|
||||||
#Copyright ReportLab Europe Ltd. 2000-2004
|
|
||||||
#see license.txt for license details
|
|
||||||
#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/graphics/charts/__init__.py
|
|
||||||
__version__=''' $Id: __init__.py 2385 2004-06-17 15:26:05Z rgbecker $ '''
|
|
|
@ -1,92 +0,0 @@
|
||||||
#Copyright ReportLab Europe Ltd. 2000-2004
|
|
||||||
#see license.txt for license details
|
|
||||||
#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/graphics/charts/areas.py
|
|
||||||
"""This module defines a Area mixin classes
|
|
||||||
"""
|
|
||||||
__version__=''' $Id: areas.py 2385 2004-06-17 15:26:05Z rgbecker $ '''
|
|
||||||
from reportlab.lib.validators import isNumber, isColor, isColorOrNone, isNoneOrShape
|
|
||||||
from reportlab.graphics.widgetbase import Widget
|
|
||||||
from reportlab.graphics.shapes import Rect, Group, Line, Polygon
|
|
||||||
from reportlab.lib.attrmap import AttrMap, AttrMapValue
|
|
||||||
|
|
||||||
class PlotArea(Widget):
|
|
||||||
"Abstract base class representing a chart's plot area, pretty unusable by itself."
|
|
||||||
_attrMap = AttrMap(
|
|
||||||
x = AttrMapValue(isNumber, desc='X position of the lower-left corner of the chart.'),
|
|
||||||
y = AttrMapValue(isNumber, desc='Y position of the lower-left corner of the chart.'),
|
|
||||||
width = AttrMapValue(isNumber, desc='Width of the chart.'),
|
|
||||||
height = AttrMapValue(isNumber, desc='Height of the chart.'),
|
|
||||||
strokeColor = AttrMapValue(isColorOrNone, desc='Color of the plot area border.'),
|
|
||||||
strokeWidth = AttrMapValue(isNumber, desc='Width plot area border.'),
|
|
||||||
fillColor = AttrMapValue(isColorOrNone, desc='Color of the plot area interior.'),
|
|
||||||
background = AttrMapValue(isNoneOrShape, desc='Handle to background object.'),
|
|
||||||
debug = AttrMapValue(isNumber, desc='Used only for debugging.'),
|
|
||||||
)
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
self.x = 20
|
|
||||||
self.y = 10
|
|
||||||
self.height = 85
|
|
||||||
self.width = 180
|
|
||||||
self.strokeColor = None
|
|
||||||
self.strokeWidth = 1
|
|
||||||
self.fillColor = None
|
|
||||||
self.background = None
|
|
||||||
self.debug = 0
|
|
||||||
|
|
||||||
def makeBackground(self):
|
|
||||||
if self.background is not None:
|
|
||||||
BG = self.background
|
|
||||||
if isinstance(BG,Group):
|
|
||||||
g = BG
|
|
||||||
for bg in g.contents:
|
|
||||||
bg.x = self.x
|
|
||||||
bg.y = self.y
|
|
||||||
bg.width = self.width
|
|
||||||
bg.height = self.height
|
|
||||||
else:
|
|
||||||
g = Group()
|
|
||||||
if type(BG) not in (type(()),type([])): BG=(BG,)
|
|
||||||
for bg in BG:
|
|
||||||
bg.x = self.x
|
|
||||||
bg.y = self.y
|
|
||||||
bg.width = self.width
|
|
||||||
bg.height = self.height
|
|
||||||
g.add(bg)
|
|
||||||
return g
|
|
||||||
else:
|
|
||||||
strokeColor,strokeWidth,fillColor=self.strokeColor, self.strokeWidth, self.fillColor
|
|
||||||
if (strokeWidth and strokeColor) or fillColor:
|
|
||||||
g = Group()
|
|
||||||
_3d_dy = getattr(self,'_3d_dy',None)
|
|
||||||
x = self.x
|
|
||||||
y = self.y
|
|
||||||
h = self.height
|
|
||||||
w = self.width
|
|
||||||
if _3d_dy is not None:
|
|
||||||
_3d_dx = self._3d_dx
|
|
||||||
if fillColor and not strokeColor:
|
|
||||||
from reportlab.lib.colors import Blacker
|
|
||||||
c = Blacker(fillColor, getattr(self,'_3d_blacken',0.7))
|
|
||||||
else:
|
|
||||||
c = strokeColor
|
|
||||||
if not strokeWidth: strokeWidth = 0.5
|
|
||||||
if fillColor or strokeColor or c:
|
|
||||||
bg = Polygon([x,y,x,y+h,x+_3d_dx,y+h+_3d_dy,x+w+_3d_dx,y+h+_3d_dy,x+w+_3d_dx,y+_3d_dy,x+w,y],
|
|
||||||
strokeColor=strokeColor or c or grey, strokeWidth=strokeWidth, fillColor=fillColor)
|
|
||||||
g.add(bg)
|
|
||||||
g.add(Line(x,y,x+_3d_dx,y+_3d_dy, strokeWidth=0.5, strokeColor=c))
|
|
||||||
g.add(Line(x+_3d_dx,y+_3d_dy, x+_3d_dx,y+h+_3d_dy,strokeWidth=0.5, strokeColor=c))
|
|
||||||
fc = Blacker(c, getattr(self,'_3d_blacken',0.8))
|
|
||||||
g.add(Polygon([x,y,x+_3d_dx,y+_3d_dy,x+w+_3d_dx,y+_3d_dy,x+w,y],
|
|
||||||
strokeColor=strokeColor or c or grey, strokeWidth=strokeWidth, fillColor=fc))
|
|
||||||
bg = Line(x+_3d_dx,y+_3d_dy, x+w+_3d_dx,y+_3d_dy,strokeWidth=0.5, strokeColor=c)
|
|
||||||
else:
|
|
||||||
bg = None
|
|
||||||
else:
|
|
||||||
bg = Rect(x, y, w, h,
|
|
||||||
strokeColor=strokeColor, strokeWidth=strokeWidth, fillColor=fillColor)
|
|
||||||
if bg: g.add(bg)
|
|
||||||
return g
|
|
||||||
else:
|
|
||||||
return None
|
|
|
@ -1,165 +0,0 @@
|
||||||
from reportlab.lib.colors import blue, _PCMYK_black
|
|
||||||
from reportlab.graphics.charts.textlabels import Label
|
|
||||||
from reportlab.graphics.shapes import Circle, Drawing, Group, Line, Rect, String
|
|
||||||
from reportlab.graphics.widgetbase import Widget
|
|
||||||
from reportlab.lib.attrmap import *
|
|
||||||
from reportlab.lib.validators import *
|
|
||||||
from reportlab.lib.units import cm
|
|
||||||
from reportlab.pdfbase.pdfmetrics import getFont
|
|
||||||
from reportlab.graphics.charts.lineplots import _maxWidth
|
|
||||||
|
|
||||||
class DotBox(Widget):
|
|
||||||
"""Returns a dotbox widget."""
|
|
||||||
|
|
||||||
#Doesn't use TypedPropertyCollection for labels - this can be a later improvement
|
|
||||||
_attrMap = AttrMap(
|
|
||||||
xlabels = AttrMapValue(isNoneOrListOfNoneOrStrings,
|
|
||||||
desc="List of text labels for boxes on left hand side"),
|
|
||||||
ylabels = AttrMapValue(isNoneOrListOfNoneOrStrings,
|
|
||||||
desc="Text label for second box on left hand side"),
|
|
||||||
labelFontName = AttrMapValue(isString,
|
|
||||||
desc="Name of font used for the labels"),
|
|
||||||
labelFontSize = AttrMapValue(isNumber,
|
|
||||||
desc="Size of font used for the labels"),
|
|
||||||
labelOffset = AttrMapValue(isNumber,
|
|
||||||
desc="Space between label text and grid edge"),
|
|
||||||
strokeWidth = AttrMapValue(isNumber,
|
|
||||||
desc='Width of the grid and dot outline'),
|
|
||||||
gridDivWidth = AttrMapValue(isNumber,
|
|
||||||
desc="Width of each 'box'"),
|
|
||||||
gridColor = AttrMapValue(isColor,
|
|
||||||
desc='Colour for the box and gridding'),
|
|
||||||
dotDiameter = AttrMapValue(isNumber,
|
|
||||||
desc="Diameter of the circle used for the 'dot'"),
|
|
||||||
dotColor = AttrMapValue(isColor,
|
|
||||||
desc='Colour of the circle on the box'),
|
|
||||||
dotXPosition = AttrMapValue(isNumber,
|
|
||||||
desc='X Position of the circle'),
|
|
||||||
dotYPosition = AttrMapValue(isNumber,
|
|
||||||
desc='X Position of the circle'),
|
|
||||||
x = AttrMapValue(isNumber,
|
|
||||||
desc='X Position of dotbox'),
|
|
||||||
y = AttrMapValue(isNumber,
|
|
||||||
desc='Y Position of dotbox'),
|
|
||||||
)
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
self.xlabels=["Value", "Blend", "Growth"]
|
|
||||||
self.ylabels=["Small", "Medium", "Large"]
|
|
||||||
self.labelFontName = "Helvetica"
|
|
||||||
self.labelFontSize = 6
|
|
||||||
self.labelOffset = 5
|
|
||||||
self.strokeWidth = 0.5
|
|
||||||
self.gridDivWidth=0.5*cm
|
|
||||||
self.gridColor=colors.Color(25/255.0,77/255.0,135/255.0)
|
|
||||||
self.dotDiameter=0.4*cm
|
|
||||||
self.dotColor=colors.Color(232/255.0,224/255.0,119/255.0)
|
|
||||||
self.dotXPosition = 1
|
|
||||||
self.dotYPosition = 1
|
|
||||||
self.x = 30
|
|
||||||
self.y = 5
|
|
||||||
|
|
||||||
|
|
||||||
def _getDrawingDimensions(self):
|
|
||||||
leftPadding=rightPadding=topPadding=bottomPadding=5
|
|
||||||
#find width of grid
|
|
||||||
tx=len(self.xlabels)*self.gridDivWidth
|
|
||||||
#add padding (and offset)
|
|
||||||
tx=tx+leftPadding+rightPadding+self.labelOffset
|
|
||||||
#add in maximum width of text
|
|
||||||
tx=tx+_maxWidth(self.xlabels, self.labelFontName, self.labelFontSize)
|
|
||||||
#find height of grid
|
|
||||||
ty=len(self.ylabels)*self.gridDivWidth
|
|
||||||
#add padding (and offset)
|
|
||||||
ty=ty+topPadding+bottomPadding+self.labelOffset
|
|
||||||
#add in maximum width of text
|
|
||||||
ty=ty+_maxWidth(self.ylabels, self.labelFontName, self.labelFontSize)
|
|
||||||
#print (tx, ty)
|
|
||||||
return (tx,ty)
|
|
||||||
|
|
||||||
def demo(self,drawing=None):
|
|
||||||
if not drawing:
|
|
||||||
tx,ty=self._getDrawingDimensions()
|
|
||||||
drawing = Drawing(tx,ty)
|
|
||||||
drawing.add(self.draw())
|
|
||||||
return drawing
|
|
||||||
|
|
||||||
def draw(self):
|
|
||||||
g = Group()
|
|
||||||
|
|
||||||
#box
|
|
||||||
g.add(Rect(self.x,self.y,len(self.xlabels)*self.gridDivWidth,len(self.ylabels)*self.gridDivWidth,
|
|
||||||
strokeColor=self.gridColor,
|
|
||||||
strokeWidth=self.strokeWidth,
|
|
||||||
fillColor=None))
|
|
||||||
|
|
||||||
#internal gridding
|
|
||||||
for f in range (1,len(self.ylabels)):
|
|
||||||
#horizontal
|
|
||||||
g.add(Line(strokeColor=self.gridColor,
|
|
||||||
strokeWidth=self.strokeWidth,
|
|
||||||
x1 = self.x,
|
|
||||||
y1 = self.y+f*self.gridDivWidth,
|
|
||||||
x2 = self.x+len(self.xlabels)*self.gridDivWidth,
|
|
||||||
y2 = self.y+f*self.gridDivWidth))
|
|
||||||
for f in range (1,len(self.xlabels)):
|
|
||||||
#vertical
|
|
||||||
g.add(Line(strokeColor=self.gridColor,
|
|
||||||
strokeWidth=self.strokeWidth,
|
|
||||||
x1 = self.x+f*self.gridDivWidth,
|
|
||||||
y1 = self.y,
|
|
||||||
x2 = self.x+f*self.gridDivWidth,
|
|
||||||
y2 = self.y+len(self.ylabels)*self.gridDivWidth))
|
|
||||||
|
|
||||||
# draw the 'dot'
|
|
||||||
g.add(Circle(strokeColor=self.gridColor,
|
|
||||||
strokeWidth=self.strokeWidth,
|
|
||||||
fillColor=self.dotColor,
|
|
||||||
cx = self.x+(self.dotXPosition*self.gridDivWidth),
|
|
||||||
cy = self.y+(self.dotYPosition*self.gridDivWidth),
|
|
||||||
r = self.dotDiameter/2.0))
|
|
||||||
|
|
||||||
#used for centering y-labels (below)
|
|
||||||
ascent=getFont(self.labelFontName).face.ascent
|
|
||||||
if ascent==0:
|
|
||||||
ascent=0.718 # default (from helvetica)
|
|
||||||
ascent=ascent*self.labelFontSize # normalize
|
|
||||||
|
|
||||||
#do y-labels
|
|
||||||
if self.ylabels != None:
|
|
||||||
for f in range (len(self.ylabels)-1,-1,-1):
|
|
||||||
if self.ylabels[f]!= None:
|
|
||||||
g.add(String(strokeColor=self.gridColor,
|
|
||||||
text = self.ylabels[f],
|
|
||||||
fontName = self.labelFontName,
|
|
||||||
fontSize = self.labelFontSize,
|
|
||||||
fillColor=_PCMYK_black,
|
|
||||||
x = self.x-self.labelOffset,
|
|
||||||
y = self.y+(f*self.gridDivWidth+(self.gridDivWidth-ascent)/2.0),
|
|
||||||
textAnchor = 'end'))
|
|
||||||
|
|
||||||
#do x-labels
|
|
||||||
if self.xlabels != None:
|
|
||||||
for f in range (0,len(self.xlabels)):
|
|
||||||
if self.xlabels[f]!= None:
|
|
||||||
l=Label()
|
|
||||||
l.x=self.x+(f*self.gridDivWidth)+(self.gridDivWidth+ascent)/2.0
|
|
||||||
l.y=self.y+(len(self.ylabels)*self.gridDivWidth)+self.labelOffset
|
|
||||||
l.angle=90
|
|
||||||
l.textAnchor='start'
|
|
||||||
l.fontName = self.labelFontName
|
|
||||||
l.fontSize = self.labelFontSize
|
|
||||||
l.fillColor = _PCMYK_black
|
|
||||||
l.setText(self.xlabels[f])
|
|
||||||
l.boxAnchor = 'sw'
|
|
||||||
l.draw()
|
|
||||||
g.add(l)
|
|
||||||
|
|
||||||
return g
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
d = DotBox()
|
|
||||||
d.demo().save(fnRoot="dotbox")
|
|
|
@ -1,349 +0,0 @@
|
||||||
#Copyright ReportLab Europe Ltd. 2000-2004
|
|
||||||
#see license.txt for license details
|
|
||||||
#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/graphics/charts/doughnut.py
|
|
||||||
# doughnut chart
|
|
||||||
|
|
||||||
"""Doughnut chart
|
|
||||||
|
|
||||||
Produces a circular chart like the doughnut charts produced by Excel.
|
|
||||||
Can handle multiple series (which produce concentric 'rings' in the chart).
|
|
||||||
|
|
||||||
"""
|
|
||||||
__version__=''' $Id: doughnut.py 2499 2004-12-29 17:12:34Z rgbecker $ '''
|
|
||||||
|
|
||||||
import copy
|
|
||||||
from math import sin, cos, pi
|
|
||||||
from types import ListType, TupleType
|
|
||||||
from reportlab.lib import colors
|
|
||||||
from reportlab.lib.validators import isColor, isNumber, isListOfNumbersOrNone,\
|
|
||||||
isListOfNumbers, isColorOrNone, isString,\
|
|
||||||
isListOfStringsOrNone, OneOf, SequenceOf,\
|
|
||||||
isBoolean, isListOfColors,\
|
|
||||||
isNoneOrListOfNoneOrStrings,\
|
|
||||||
isNoneOrListOfNoneOrNumbers,\
|
|
||||||
isNumberOrNone
|
|
||||||
from reportlab.lib.attrmap import *
|
|
||||||
from reportlab.pdfgen.canvas import Canvas
|
|
||||||
from reportlab.graphics.shapes import Group, Drawing, Line, Rect, Polygon, Ellipse, \
|
|
||||||
Wedge, String, SolidShape, UserNode, STATE_DEFAULTS
|
|
||||||
from reportlab.graphics.widgetbase import Widget, TypedPropertyCollection, PropHolder
|
|
||||||
from reportlab.graphics.charts.piecharts import AbstractPieChart, WedgeProperties, _addWedgeLabel
|
|
||||||
from reportlab.graphics.charts.textlabels import Label
|
|
||||||
from reportlab.graphics.widgets.markers import Marker
|
|
||||||
|
|
||||||
class SectorProperties(WedgeProperties):
|
|
||||||
"""This holds descriptive information about the sectors in a doughnut chart.
|
|
||||||
|
|
||||||
It is not to be confused with the 'sector itself'; this just holds
|
|
||||||
a recipe for how to format one, and does not allow you to hack the
|
|
||||||
angles. It can format a genuine Sector object for you with its
|
|
||||||
format method.
|
|
||||||
"""
|
|
||||||
_attrMap = AttrMap(BASE=WedgeProperties,
|
|
||||||
)
|
|
||||||
|
|
||||||
class Doughnut(AbstractPieChart):
|
|
||||||
_attrMap = AttrMap(
|
|
||||||
x = AttrMapValue(isNumber, desc='X position of the chart within its container.'),
|
|
||||||
y = AttrMapValue(isNumber, desc='Y position of the chart within its container.'),
|
|
||||||
width = AttrMapValue(isNumber, desc='width of doughnut bounding box. Need not be same as width.'),
|
|
||||||
height = AttrMapValue(isNumber, desc='height of doughnut bounding box. Need not be same as height.'),
|
|
||||||
data = AttrMapValue(None, desc='list of numbers defining sector sizes; need not sum to 1'),
|
|
||||||
labels = AttrMapValue(isListOfStringsOrNone, desc="optional list of labels to use for each data point"),
|
|
||||||
startAngle = AttrMapValue(isNumber, desc="angle of first slice; like the compass, 0 is due North"),
|
|
||||||
direction = AttrMapValue(OneOf('clockwise', 'anticlockwise'), desc="'clockwise' or 'anticlockwise'"),
|
|
||||||
slices = AttrMapValue(None, desc="collection of sector descriptor objects"),
|
|
||||||
simpleLabels = AttrMapValue(isBoolean, desc="If true(default) use String not super duper WedgeLabel"),
|
|
||||||
)
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
self.x = 0
|
|
||||||
self.y = 0
|
|
||||||
self.width = 100
|
|
||||||
self.height = 100
|
|
||||||
self.data = [1,1]
|
|
||||||
self.labels = None # or list of strings
|
|
||||||
self.startAngle = 90
|
|
||||||
self.direction = "clockwise"
|
|
||||||
self.simpleLabels = 1
|
|
||||||
|
|
||||||
self.slices = TypedPropertyCollection(SectorProperties)
|
|
||||||
self.slices[0].fillColor = colors.darkcyan
|
|
||||||
self.slices[1].fillColor = colors.blueviolet
|
|
||||||
self.slices[2].fillColor = colors.blue
|
|
||||||
self.slices[3].fillColor = colors.cyan
|
|
||||||
|
|
||||||
def demo(self):
|
|
||||||
d = Drawing(200, 100)
|
|
||||||
|
|
||||||
dn = Doughnut()
|
|
||||||
dn.x = 50
|
|
||||||
dn.y = 10
|
|
||||||
dn.width = 100
|
|
||||||
dn.height = 80
|
|
||||||
dn.data = [10,20,30,40,50,60]
|
|
||||||
dn.labels = ['a','b','c','d','e','f']
|
|
||||||
|
|
||||||
dn.slices.strokeWidth=0.5
|
|
||||||
dn.slices[3].popout = 10
|
|
||||||
dn.slices[3].strokeWidth = 2
|
|
||||||
dn.slices[3].strokeDashArray = [2,2]
|
|
||||||
dn.slices[3].labelRadius = 1.75
|
|
||||||
dn.slices[3].fontColor = colors.red
|
|
||||||
dn.slices[0].fillColor = colors.darkcyan
|
|
||||||
dn.slices[1].fillColor = colors.blueviolet
|
|
||||||
dn.slices[2].fillColor = colors.blue
|
|
||||||
dn.slices[3].fillColor = colors.cyan
|
|
||||||
dn.slices[4].fillColor = colors.aquamarine
|
|
||||||
dn.slices[5].fillColor = colors.cadetblue
|
|
||||||
dn.slices[6].fillColor = colors.lightcoral
|
|
||||||
|
|
||||||
d.add(dn)
|
|
||||||
return d
|
|
||||||
|
|
||||||
def normalizeData(self, data=None):
|
|
||||||
from operator import add
|
|
||||||
sum = float(reduce(add,data,0))
|
|
||||||
return abs(sum)>=1e-8 and map(lambda x,f=360./sum: f*x, data) or len(data)*[0]
|
|
||||||
|
|
||||||
def makeSectors(self):
|
|
||||||
# normalize slice data
|
|
||||||
if type(self.data) in (ListType, TupleType) and type(self.data[0]) in (ListType, TupleType):
|
|
||||||
#it's a nested list, more than one sequence
|
|
||||||
normData = []
|
|
||||||
n = []
|
|
||||||
for l in self.data:
|
|
||||||
t = self.normalizeData(l)
|
|
||||||
normData.append(t)
|
|
||||||
n.append(len(t))
|
|
||||||
self._seriesCount = max(n)
|
|
||||||
else:
|
|
||||||
normData = self.normalizeData(self.data)
|
|
||||||
n = len(normData)
|
|
||||||
self._seriesCount = n
|
|
||||||
|
|
||||||
#labels
|
|
||||||
if self.labels is None:
|
|
||||||
labels = []
|
|
||||||
if type(n) not in (ListType,TupleType):
|
|
||||||
labels = [''] * n
|
|
||||||
else:
|
|
||||||
for m in n:
|
|
||||||
labels = list(labels) + [''] * m
|
|
||||||
else:
|
|
||||||
labels = self.labels
|
|
||||||
#there's no point in raising errors for less than enough labels if
|
|
||||||
#we silently create all for the extreme case of no labels.
|
|
||||||
if type(n) not in (ListType,TupleType):
|
|
||||||
i = n-len(labels)
|
|
||||||
if i>0:
|
|
||||||
labels = list(labels) + [''] * i
|
|
||||||
else:
|
|
||||||
tlab = 0
|
|
||||||
for m in n:
|
|
||||||
tlab = tlab+m
|
|
||||||
i = tlab-len(labels)
|
|
||||||
if i>0:
|
|
||||||
labels = list(labels) + [''] * i
|
|
||||||
|
|
||||||
xradius = self.width/2.0
|
|
||||||
yradius = self.height/2.0
|
|
||||||
centerx = self.x + xradius
|
|
||||||
centery = self.y + yradius
|
|
||||||
|
|
||||||
if self.direction == "anticlockwise":
|
|
||||||
whichWay = 1
|
|
||||||
else:
|
|
||||||
whichWay = -1
|
|
||||||
|
|
||||||
g = Group()
|
|
||||||
sn = 0
|
|
||||||
|
|
||||||
startAngle = self.startAngle #% 360
|
|
||||||
styleCount = len(self.slices)
|
|
||||||
if type(self.data[0]) in (ListType, TupleType):
|
|
||||||
#multi-series doughnut
|
|
||||||
iradius = (self.height/5.0)/len(self.data)
|
|
||||||
for series in normData:
|
|
||||||
i = 0
|
|
||||||
for angle in series:
|
|
||||||
endAngle = (startAngle + (angle * whichWay)) #% 360
|
|
||||||
if abs(startAngle-endAngle)>=1e-5:
|
|
||||||
if startAngle < endAngle:
|
|
||||||
a1 = startAngle
|
|
||||||
a2 = endAngle
|
|
||||||
else:
|
|
||||||
a1 = endAngle
|
|
||||||
a2 = startAngle
|
|
||||||
|
|
||||||
#if we didn't use %stylecount here we'd end up with the later sectors
|
|
||||||
#all having the default style
|
|
||||||
sectorStyle = self.slices[i%styleCount]
|
|
||||||
|
|
||||||
# is it a popout?
|
|
||||||
cx, cy = centerx, centery
|
|
||||||
if sectorStyle.popout != 0:
|
|
||||||
# pop out the sector
|
|
||||||
averageAngle = (a1+a2)/2.0
|
|
||||||
aveAngleRadians = averageAngle * pi/180.0
|
|
||||||
popdistance = sectorStyle.popout
|
|
||||||
cx = centerx + popdistance * cos(aveAngleRadians)
|
|
||||||
cy = centery + popdistance * sin(aveAngleRadians)
|
|
||||||
|
|
||||||
if type(n) in (ListType,TupleType):
|
|
||||||
theSector = Wedge(cx, cy, xradius+(sn*iradius)-iradius, a1, a2, yradius=yradius+(sn*iradius)-iradius, radius1=yradius+(sn*iradius)-(2*iradius))
|
|
||||||
else:
|
|
||||||
theSector = Wedge(cx, cy, xradius, a1, a2, yradius=yradius, radius1=iradius)
|
|
||||||
|
|
||||||
theSector.fillColor = sectorStyle.fillColor
|
|
||||||
theSector.strokeColor = sectorStyle.strokeColor
|
|
||||||
theSector.strokeWidth = sectorStyle.strokeWidth
|
|
||||||
theSector.strokeDashArray = sectorStyle.strokeDashArray
|
|
||||||
|
|
||||||
g.add(theSector)
|
|
||||||
startAngle = endAngle
|
|
||||||
|
|
||||||
text = self.getSeriesName(i,'')
|
|
||||||
if text:
|
|
||||||
averageAngle = (a1+a2)/2.0
|
|
||||||
aveAngleRadians = averageAngle*pi/180.0
|
|
||||||
labelRadius = sectorStyle.labelRadius
|
|
||||||
labelX = centerx + (0.5 * self.width * cos(aveAngleRadians) * labelRadius)
|
|
||||||
labelY = centery + (0.5 * self.height * sin(aveAngleRadians) * labelRadius)
|
|
||||||
_addWedgeLabel(self,text,g.add,averageAngle,labelX,labelY,sectorStyle)
|
|
||||||
i = i + 1
|
|
||||||
sn = sn + 1
|
|
||||||
|
|
||||||
else:
|
|
||||||
i = 0
|
|
||||||
#single series doughnut
|
|
||||||
iradius = self.height/5.0
|
|
||||||
for angle in normData:
|
|
||||||
endAngle = (startAngle + (angle * whichWay)) #% 360
|
|
||||||
if abs(startAngle-endAngle)>=1e-5:
|
|
||||||
if startAngle < endAngle:
|
|
||||||
a1 = startAngle
|
|
||||||
a2 = endAngle
|
|
||||||
else:
|
|
||||||
a1 = endAngle
|
|
||||||
a2 = startAngle
|
|
||||||
|
|
||||||
#if we didn't use %stylecount here we'd end up with the later sectors
|
|
||||||
#all having the default style
|
|
||||||
sectorStyle = self.slices[i%styleCount]
|
|
||||||
|
|
||||||
# is it a popout?
|
|
||||||
cx, cy = centerx, centery
|
|
||||||
if sectorStyle.popout != 0:
|
|
||||||
# pop out the sector
|
|
||||||
averageAngle = (a1+a2)/2.0
|
|
||||||
aveAngleRadians = averageAngle * pi/180.0
|
|
||||||
popdistance = sectorStyle.popout
|
|
||||||
cx = centerx + popdistance * cos(aveAngleRadians)
|
|
||||||
cy = centery + popdistance * sin(aveAngleRadians)
|
|
||||||
|
|
||||||
if n > 1:
|
|
||||||
theSector = Wedge(cx, cy, xradius, a1, a2, yradius=yradius, radius1=iradius)
|
|
||||||
elif n==1:
|
|
||||||
theSector = Wedge(cx, cy, xradius, a1, a2, yradius=yradius, iradius=iradius)
|
|
||||||
|
|
||||||
theSector.fillColor = sectorStyle.fillColor
|
|
||||||
theSector.strokeColor = sectorStyle.strokeColor
|
|
||||||
theSector.strokeWidth = sectorStyle.strokeWidth
|
|
||||||
theSector.strokeDashArray = sectorStyle.strokeDashArray
|
|
||||||
|
|
||||||
g.add(theSector)
|
|
||||||
|
|
||||||
# now draw a label
|
|
||||||
if labels[i] != "":
|
|
||||||
averageAngle = (a1+a2)/2.0
|
|
||||||
aveAngleRadians = averageAngle*pi/180.0
|
|
||||||
labelRadius = sectorStyle.labelRadius
|
|
||||||
labelX = centerx + (0.5 * self.width * cos(aveAngleRadians) * labelRadius)
|
|
||||||
labelY = centery + (0.5 * self.height * sin(aveAngleRadians) * labelRadius)
|
|
||||||
|
|
||||||
theLabel = String(labelX, labelY, labels[i])
|
|
||||||
theLabel.textAnchor = "middle"
|
|
||||||
theLabel.fontSize = sectorStyle.fontSize
|
|
||||||
theLabel.fontName = sectorStyle.fontName
|
|
||||||
theLabel.fillColor = sectorStyle.fontColor
|
|
||||||
|
|
||||||
g.add(theLabel)
|
|
||||||
|
|
||||||
startAngle = endAngle
|
|
||||||
i = i + 1
|
|
||||||
|
|
||||||
return g
|
|
||||||
|
|
||||||
def draw(self):
|
|
||||||
g = Group()
|
|
||||||
g.add(self.makeSectors())
|
|
||||||
return g
|
|
||||||
|
|
||||||
|
|
||||||
def sample1():
|
|
||||||
"Make up something from the individual Sectors"
|
|
||||||
|
|
||||||
d = Drawing(400, 400)
|
|
||||||
g = Group()
|
|
||||||
|
|
||||||
s1 = Wedge(centerx=200, centery=200, radius=150, startangledegrees=0, endangledegrees=120, radius1=100)
|
|
||||||
s1.fillColor=colors.red
|
|
||||||
s1.strokeColor=None
|
|
||||||
d.add(s1)
|
|
||||||
s2 = Wedge(centerx=200, centery=200, radius=150, startangledegrees=120, endangledegrees=240, radius1=100)
|
|
||||||
s2.fillColor=colors.green
|
|
||||||
s2.strokeColor=None
|
|
||||||
d.add(s2)
|
|
||||||
s3 = Wedge(centerx=200, centery=200, radius=150, startangledegrees=240, endangledegrees=260, radius1=100)
|
|
||||||
s3.fillColor=colors.blue
|
|
||||||
s3.strokeColor=None
|
|
||||||
d.add(s3)
|
|
||||||
s4 = Wedge(centerx=200, centery=200, radius=150, startangledegrees=260, endangledegrees=360, radius1=100)
|
|
||||||
s4.fillColor=colors.gray
|
|
||||||
s4.strokeColor=None
|
|
||||||
d.add(s4)
|
|
||||||
|
|
||||||
return d
|
|
||||||
|
|
||||||
def sample2():
|
|
||||||
"Make a simple demo"
|
|
||||||
|
|
||||||
d = Drawing(400, 400)
|
|
||||||
|
|
||||||
dn = Doughnut()
|
|
||||||
dn.x = 50
|
|
||||||
dn.y = 50
|
|
||||||
dn.width = 300
|
|
||||||
dn.height = 300
|
|
||||||
dn.data = [10,20,30,40,50,60]
|
|
||||||
|
|
||||||
d.add(dn)
|
|
||||||
|
|
||||||
return d
|
|
||||||
|
|
||||||
def sample3():
|
|
||||||
"Make a more complex demo"
|
|
||||||
|
|
||||||
d = Drawing(400, 400)
|
|
||||||
dn = Doughnut()
|
|
||||||
dn.x = 50
|
|
||||||
dn.y = 50
|
|
||||||
dn.width = 300
|
|
||||||
dn.height = 300
|
|
||||||
dn.data = [[10,20,30,40,50,60], [10,20,30,40]]
|
|
||||||
dn.labels = ['a','b','c','d','e','f']
|
|
||||||
|
|
||||||
d.add(dn)
|
|
||||||
|
|
||||||
return d
|
|
||||||
|
|
||||||
if __name__=='__main__':
|
|
||||||
|
|
||||||
from reportlab.graphics.renderPDF import drawToFile
|
|
||||||
d = sample1()
|
|
||||||
drawToFile(d, 'doughnut1.pdf')
|
|
||||||
d = sample2()
|
|
||||||
drawToFile(d, 'doughnut2.pdf')
|
|
||||||
d = sample3()
|
|
||||||
drawToFile(d, 'doughnut3.pdf')
|
|
|
@ -1,611 +0,0 @@
|
||||||
#Copyright ReportLab Europe Ltd. 2000-2004
|
|
||||||
#see license.txt for license details
|
|
||||||
#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/graphics/charts/legends.py
|
|
||||||
"""This will be a collection of legends to be used with charts.
|
|
||||||
"""
|
|
||||||
__version__=''' $Id: legends.py 2604 2005-06-08 10:12:46Z rgbecker $ '''
|
|
||||||
|
|
||||||
import copy, operator
|
|
||||||
|
|
||||||
from reportlab.lib import colors
|
|
||||||
from reportlab.lib.validators import isNumber, OneOf, isString, isColorOrNone,\
|
|
||||||
isNumberOrNone, isListOfNumbersOrNone, isStringOrNone, isBoolean,\
|
|
||||||
NoneOr, AutoOr, isAuto, Auto, isBoxAnchor, SequenceOf
|
|
||||||
from reportlab.lib.attrmap import *
|
|
||||||
from reportlab.pdfbase.pdfmetrics import stringWidth, getFont
|
|
||||||
from reportlab.graphics.widgetbase import Widget, TypedPropertyCollection, PropHolder
|
|
||||||
from reportlab.graphics.shapes import Drawing, Group, String, Rect, Line, STATE_DEFAULTS
|
|
||||||
from reportlab.graphics.charts.areas import PlotArea
|
|
||||||
from reportlab.graphics.widgets.markers import uSymbol2Symbol, isSymbol
|
|
||||||
from reportlab.lib.utils import isSeqType
|
|
||||||
|
|
||||||
|
|
||||||
def _getStr(s):
|
|
||||||
if isSeqType(s):
|
|
||||||
return map(str,s)
|
|
||||||
else:
|
|
||||||
return str(s)
|
|
||||||
|
|
||||||
def _getLines(s):
|
|
||||||
if isSeqType(s):
|
|
||||||
return tuple([(x or '').split('\n') for x in s])
|
|
||||||
else:
|
|
||||||
return (s or '').split('\n')
|
|
||||||
|
|
||||||
def _getLineCount(s):
|
|
||||||
T = _getLines(s)
|
|
||||||
if isSeqType(s):
|
|
||||||
return max([len(x) for x in T])
|
|
||||||
else:
|
|
||||||
return len(T)
|
|
||||||
|
|
||||||
def _getWidth(s,fontName, fontSize, sepSpace=0):
|
|
||||||
if isSeqType(s):
|
|
||||||
sum = 0
|
|
||||||
for t in s:
|
|
||||||
m = [stringWidth(x, fontName, fontSize) for x in t.split('\n')]
|
|
||||||
sum += m and max(m) or 0
|
|
||||||
sum += (len(s)-1)*sepSpace
|
|
||||||
return sum
|
|
||||||
m = [stringWidth(x, fontName, fontSize) for x in s.split('\n')]
|
|
||||||
return m and max(m) or 0
|
|
||||||
|
|
||||||
class Legend(Widget):
|
|
||||||
"""A simple legend containing rectangular swatches and strings.
|
|
||||||
|
|
||||||
The swatches are filled rectangles whenever the respective
|
|
||||||
color object in 'colorNamePairs' is a subclass of Color in
|
|
||||||
reportlab.lib.colors. Otherwise the object passed instead is
|
|
||||||
assumed to have 'x', 'y', 'width' and 'height' attributes.
|
|
||||||
A legend then tries to set them or catches any error. This
|
|
||||||
lets you plug-in any widget you like as a replacement for
|
|
||||||
the default rectangular swatches.
|
|
||||||
|
|
||||||
Strings can be nicely aligned left or right to the swatches.
|
|
||||||
"""
|
|
||||||
|
|
||||||
_attrMap = AttrMap(
|
|
||||||
x = AttrMapValue(isNumber, desc="x-coordinate of upper-left reference point"),
|
|
||||||
y = AttrMapValue(isNumber, desc="y-coordinate of upper-left reference point"),
|
|
||||||
deltax = AttrMapValue(isNumberOrNone, desc="x-distance between neighbouring swatches"),
|
|
||||||
deltay = AttrMapValue(isNumberOrNone, desc="y-distance between neighbouring swatches"),
|
|
||||||
dxTextSpace = AttrMapValue(isNumber, desc="Distance between swatch rectangle and text"),
|
|
||||||
autoXPadding = AttrMapValue(isNumber, desc="x Padding between columns if deltax=None"),
|
|
||||||
autoYPadding = AttrMapValue(isNumber, desc="y Padding between rows if deltay=None"),
|
|
||||||
yGap = AttrMapValue(isNumber, desc="Additional gap between rows"),
|
|
||||||
dx = AttrMapValue(isNumber, desc="Width of swatch rectangle"),
|
|
||||||
dy = AttrMapValue(isNumber, desc="Height of swatch rectangle"),
|
|
||||||
columnMaximum = AttrMapValue(isNumber, desc="Max. number of items per column"),
|
|
||||||
alignment = AttrMapValue(OneOf("left", "right"), desc="Alignment of text with respect to swatches"),
|
|
||||||
colorNamePairs = AttrMapValue(None, desc="List of color/name tuples (color can also be widget)"),
|
|
||||||
fontName = AttrMapValue(isString, desc="Font name of the strings"),
|
|
||||||
fontSize = AttrMapValue(isNumber, desc="Font size of the strings"),
|
|
||||||
fillColor = AttrMapValue(isColorOrNone, desc=""),
|
|
||||||
strokeColor = AttrMapValue(isColorOrNone, desc="Border color of the swatches"),
|
|
||||||
strokeWidth = AttrMapValue(isNumber, desc="Width of the border color of the swatches"),
|
|
||||||
swatchMarker = AttrMapValue(NoneOr(AutoOr(isSymbol)), desc="None, Auto() or makeMarker('Diamond') ..."),
|
|
||||||
callout = AttrMapValue(None, desc="a user callout(self,g,x,y,(color,text))"),
|
|
||||||
boxAnchor = AttrMapValue(isBoxAnchor,'Anchor point for the legend area'),
|
|
||||||
variColumn = AttrMapValue(isBoolean,'If true column widths may vary (default is false)'),
|
|
||||||
dividerLines = AttrMapValue(OneOf(0,1,2,3,4,5,6,7),'If 1 we have dividers between the rows | 2 for extra top | 4 for bottom'),
|
|
||||||
dividerWidth = AttrMapValue(isNumber, desc="dividerLines width"),
|
|
||||||
dividerColor = AttrMapValue(isColorOrNone, desc="dividerLines color"),
|
|
||||||
dividerDashArray = AttrMapValue(isListOfNumbersOrNone, desc='Dash array for dividerLines.'),
|
|
||||||
dividerOffsX = AttrMapValue(SequenceOf(isNumber,emptyOK=0,lo=2,hi=2), desc='divider lines X offsets'),
|
|
||||||
dividerOffsY = AttrMapValue(isNumber, desc="dividerLines Y offset"),
|
|
||||||
sepSpace = AttrMapValue(isNumber, desc="separator spacing"),
|
|
||||||
colEndCallout = AttrMapValue(None, desc="a user callout(self,g, x, xt, y,width, lWidth)"),
|
|
||||||
)
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
# Upper-left reference point.
|
|
||||||
self.x = 0
|
|
||||||
self.y = 0
|
|
||||||
|
|
||||||
# Alginment of text with respect to swatches.
|
|
||||||
self.alignment = "left"
|
|
||||||
|
|
||||||
# x- and y-distances between neighbouring swatches.
|
|
||||||
self.deltax = 75
|
|
||||||
self.deltay = 20
|
|
||||||
self.autoXPadding = 5
|
|
||||||
self.autoYPadding = 2
|
|
||||||
|
|
||||||
# Size of swatch rectangle.
|
|
||||||
self.dx = 10
|
|
||||||
self.dy = 10
|
|
||||||
|
|
||||||
# Distance between swatch rectangle and text.
|
|
||||||
self.dxTextSpace = 10
|
|
||||||
|
|
||||||
# Max. number of items per column.
|
|
||||||
self.columnMaximum = 3
|
|
||||||
|
|
||||||
# Color/name pairs.
|
|
||||||
self.colorNamePairs = [ (colors.red, "red"),
|
|
||||||
(colors.blue, "blue"),
|
|
||||||
(colors.green, "green"),
|
|
||||||
(colors.pink, "pink"),
|
|
||||||
(colors.yellow, "yellow") ]
|
|
||||||
|
|
||||||
# Font name and size of the labels.
|
|
||||||
self.fontName = STATE_DEFAULTS['fontName']
|
|
||||||
self.fontSize = STATE_DEFAULTS['fontSize']
|
|
||||||
self.fillColor = STATE_DEFAULTS['fillColor']
|
|
||||||
self.strokeColor = STATE_DEFAULTS['strokeColor']
|
|
||||||
self.strokeWidth = STATE_DEFAULTS['strokeWidth']
|
|
||||||
self.swatchMarker = None
|
|
||||||
self.boxAnchor = 'nw'
|
|
||||||
self.yGap = 0
|
|
||||||
self.variColumn = 0
|
|
||||||
self.dividerLines = 0
|
|
||||||
self.dividerWidth = 0.5
|
|
||||||
self.dividerDashArray = None
|
|
||||||
self.dividerColor = colors.black
|
|
||||||
self.dividerOffsX = (0,0)
|
|
||||||
self.dividerOffsY = 0
|
|
||||||
self.sepSpace = 0
|
|
||||||
self.colEndCallout = None
|
|
||||||
|
|
||||||
def _getChartStyleName(self,chart):
|
|
||||||
for a in 'lines', 'bars', 'slices', 'strands':
|
|
||||||
if hasattr(chart,a): return a
|
|
||||||
return None
|
|
||||||
|
|
||||||
def _getChartStyle(self,chart):
|
|
||||||
return getattr(chart,self._getChartStyleName(chart),None)
|
|
||||||
|
|
||||||
def _getTexts(self,colorNamePairs):
|
|
||||||
if not isAuto(colorNamePairs):
|
|
||||||
texts = [_getStr(p[1]) for p in colorNamePairs]
|
|
||||||
else:
|
|
||||||
chart = colorNamePairs.chart
|
|
||||||
texts = [str(chart.getSeriesName(i,'series %d' % i)) for i in xrange(chart._seriesCount)]
|
|
||||||
return texts
|
|
||||||
|
|
||||||
def _calculateMaxWidth(self, colorNamePairs):
|
|
||||||
"Calculate the maximum width of some given strings."
|
|
||||||
M = []
|
|
||||||
a = M.append
|
|
||||||
for t in self._getTexts(colorNamePairs):
|
|
||||||
M.append(_getWidth(t, self.fontName, self.fontSize,self.sepSpace))
|
|
||||||
if not M: return 0
|
|
||||||
if self.variColumn:
|
|
||||||
columnMaximum = self.columnMaximum
|
|
||||||
return [max(M[r:r+columnMaximum]) for r in range(0,len(M),self.columnMaximum)]
|
|
||||||
else:
|
|
||||||
return max(M)
|
|
||||||
|
|
||||||
def _calcHeight(self):
|
|
||||||
dy = self.dy
|
|
||||||
yGap = self.yGap
|
|
||||||
thisy = upperlefty = self.y - dy
|
|
||||||
fontSize = self.fontSize
|
|
||||||
ascent=getFont(self.fontName).face.ascent/1000.
|
|
||||||
if ascent==0: ascent=0.718 # default (from helvetica)
|
|
||||||
ascent *= fontSize
|
|
||||||
leading = fontSize*1.2
|
|
||||||
deltay = self.deltay
|
|
||||||
if not deltay: deltay = max(dy,leading)+self.autoYPadding
|
|
||||||
columnCount = 0
|
|
||||||
count = 0
|
|
||||||
lowy = upperlefty
|
|
||||||
lim = self.columnMaximum - 1
|
|
||||||
for name in self._getTexts(self.colorNamePairs):
|
|
||||||
y0 = thisy+(dy-ascent)*0.5
|
|
||||||
y = y0 - _getLineCount(name)*leading
|
|
||||||
leadingMove = 2*y0-y-thisy
|
|
||||||
newy = thisy-max(deltay,leadingMove)-yGap
|
|
||||||
lowy = min(y,newy,lowy)
|
|
||||||
if count==lim:
|
|
||||||
count = 0
|
|
||||||
thisy = upperlefty
|
|
||||||
columnCount = columnCount + 1
|
|
||||||
else:
|
|
||||||
thisy = newy
|
|
||||||
count = count+1
|
|
||||||
return upperlefty - lowy
|
|
||||||
|
|
||||||
def _defaultSwatch(self,x,thisy,dx,dy,fillColor,strokeWidth,strokeColor):
|
|
||||||
return Rect(x, thisy, dx, dy,
|
|
||||||
fillColor = fillColor,
|
|
||||||
strokeColor = strokeColor,
|
|
||||||
strokeWidth = strokeWidth,
|
|
||||||
)
|
|
||||||
|
|
||||||
def draw(self):
|
|
||||||
colorNamePairs = self.colorNamePairs
|
|
||||||
autoCP = isAuto(colorNamePairs)
|
|
||||||
if autoCP:
|
|
||||||
chart = getattr(colorNamePairs,'chart',getattr(colorNamePairs,'obj',None))
|
|
||||||
swatchMarker = None
|
|
||||||
autoCP = Auto(obj=chart)
|
|
||||||
n = chart._seriesCount
|
|
||||||
chartTexts = self._getTexts(colorNamePairs)
|
|
||||||
else:
|
|
||||||
swatchMarker = getattr(self,'swatchMarker',None)
|
|
||||||
if isAuto(swatchMarker):
|
|
||||||
chart = getattr(swatchMarker,'chart',getattr(swatchMarker,'obj',None))
|
|
||||||
swatchMarker = Auto(obj=chart)
|
|
||||||
n = len(colorNamePairs)
|
|
||||||
dx = self.dx
|
|
||||||
dy = self.dy
|
|
||||||
alignment = self.alignment
|
|
||||||
columnMaximum = self.columnMaximum
|
|
||||||
deltax = self.deltax
|
|
||||||
deltay = self.deltay
|
|
||||||
dxTextSpace = self.dxTextSpace
|
|
||||||
fontName = self.fontName
|
|
||||||
fontSize = self.fontSize
|
|
||||||
fillColor = self.fillColor
|
|
||||||
strokeWidth = self.strokeWidth
|
|
||||||
strokeColor = self.strokeColor
|
|
||||||
leading = fontSize*1.2
|
|
||||||
yGap = self.yGap
|
|
||||||
if not deltay:
|
|
||||||
deltay = max(dy,leading)+self.autoYPadding
|
|
||||||
ba = self.boxAnchor
|
|
||||||
baw = ba not in ('nw','w','sw','autox')
|
|
||||||
maxWidth = self._calculateMaxWidth(colorNamePairs)
|
|
||||||
nCols = int((n+columnMaximum-1)/columnMaximum)
|
|
||||||
xW = dx+dxTextSpace+self.autoXPadding
|
|
||||||
variColumn = self.variColumn
|
|
||||||
if variColumn:
|
|
||||||
width = reduce(operator.add,maxWidth,0)+xW*nCols
|
|
||||||
else:
|
|
||||||
deltax = max(maxWidth+xW,deltax)
|
|
||||||
width = nCols*deltax
|
|
||||||
maxWidth = nCols*[maxWidth]
|
|
||||||
|
|
||||||
thisx = self.x
|
|
||||||
thisy = self.y - self.dy
|
|
||||||
if ba not in ('ne','n','nw','autoy'):
|
|
||||||
height = self._calcHeight()
|
|
||||||
if ba in ('e','c','w'):
|
|
||||||
thisy += height/2.
|
|
||||||
else:
|
|
||||||
thisy += height
|
|
||||||
if baw:
|
|
||||||
if ba in ('n','c','s'):
|
|
||||||
thisx -= width/2
|
|
||||||
else:
|
|
||||||
thisx -= width
|
|
||||||
upperlefty = thisy
|
|
||||||
|
|
||||||
g = Group()
|
|
||||||
def gAdd(t,g=g,fontName=fontName,fontSize=fontSize,fillColor=fillColor):
|
|
||||||
t.fontName = fontName
|
|
||||||
t.fontSize = fontSize
|
|
||||||
t.fillColor = fillColor
|
|
||||||
return g.add(t)
|
|
||||||
|
|
||||||
ascent=getFont(fontName).face.ascent/1000.
|
|
||||||
if ascent==0: ascent=0.718 # default (from helvetica)
|
|
||||||
ascent *= fontSize # normalize
|
|
||||||
|
|
||||||
lim = columnMaximum - 1
|
|
||||||
callout = getattr(self,'callout',None)
|
|
||||||
dividerLines = self.dividerLines
|
|
||||||
if dividerLines:
|
|
||||||
dividerWidth = self.dividerWidth
|
|
||||||
dividerColor = self.dividerColor
|
|
||||||
dividerDashArray = self.dividerDashArray
|
|
||||||
dividerOffsX = self.dividerOffsX
|
|
||||||
dividerOffsY = self.dividerOffsY
|
|
||||||
|
|
||||||
for i in xrange(n):
|
|
||||||
if autoCP:
|
|
||||||
col = autoCP
|
|
||||||
col.index = i
|
|
||||||
name = chartTexts[i]
|
|
||||||
else:
|
|
||||||
col, name = colorNamePairs[i]
|
|
||||||
if isAuto(swatchMarker):
|
|
||||||
col = swatchMarker
|
|
||||||
col.index = i
|
|
||||||
if isAuto(name):
|
|
||||||
name = getattr(swatchMarker,'chart',getattr(swatchMarker,'obj',None)).getSeriesName(i,'series %d' % i)
|
|
||||||
T = _getLines(name)
|
|
||||||
S = []
|
|
||||||
j = int(i/columnMaximum)
|
|
||||||
|
|
||||||
# thisy+dy/2 = y+leading/2
|
|
||||||
y = y0 = thisy+(dy-ascent)*0.5
|
|
||||||
|
|
||||||
if callout: callout(self,g,thisx,y,(col,name))
|
|
||||||
if alignment == "left":
|
|
||||||
if isSeqType(name):
|
|
||||||
for t in T[0]:
|
|
||||||
S.append(String(thisx,y,t,fontName=fontName,fontSize=fontSize,fillColor=fillColor,
|
|
||||||
textAnchor = "start"))
|
|
||||||
y -= leading
|
|
||||||
yd = y
|
|
||||||
y = y0
|
|
||||||
for t in T[1]:
|
|
||||||
S.append(String(thisx+maxWidth[j],y,t,fontName=fontName,fontSize=fontSize,fillColor=fillColor,
|
|
||||||
textAnchor = "end"))
|
|
||||||
y -= leading
|
|
||||||
y = min(yd,y)
|
|
||||||
else:
|
|
||||||
for t in T:
|
|
||||||
# align text to left
|
|
||||||
S.append(String(thisx+maxWidth[j],y,t,fontName=fontName,fontSize=fontSize,fillColor=fillColor,
|
|
||||||
textAnchor = "end"))
|
|
||||||
y -= leading
|
|
||||||
x = thisx+maxWidth[j]+dxTextSpace
|
|
||||||
elif alignment == "right":
|
|
||||||
if isSeqType(name):
|
|
||||||
y0 = y
|
|
||||||
for t in T[0]:
|
|
||||||
S.append(String(thisx+dx+dxTextSpace,y,t,fontName=fontName,fontSize=fontSize,fillColor=fillColor,
|
|
||||||
textAnchor = "start"))
|
|
||||||
y -= leading
|
|
||||||
yd = y
|
|
||||||
y = y0
|
|
||||||
for t in T[1]:
|
|
||||||
S.append(String(thisx+dx+dxTextSpace+maxWidth[j],y,t,fontName=fontName,fontSize=fontSize,fillColor=fillColor,
|
|
||||||
textAnchor = "end"))
|
|
||||||
y -= leading
|
|
||||||
y = min(yd,y)
|
|
||||||
else:
|
|
||||||
for t in T:
|
|
||||||
# align text to right
|
|
||||||
S.append(String(thisx+dx+dxTextSpace,y,t,fontName=fontName,fontSize=fontSize,fillColor=fillColor,
|
|
||||||
textAnchor = "start"))
|
|
||||||
y -= leading
|
|
||||||
x = thisx
|
|
||||||
else:
|
|
||||||
raise ValueError, "bad alignment"
|
|
||||||
leadingMove = 2*y0-y-thisy
|
|
||||||
|
|
||||||
if dividerLines:
|
|
||||||
xd = thisx+dx+dxTextSpace+maxWidth[j]+dividerOffsX[1]
|
|
||||||
yd = thisy+dy*0.5+dividerOffsY
|
|
||||||
if ((dividerLines&1) and i%columnMaximum) or ((dividerLines&2) and not i%columnMaximum):
|
|
||||||
g.add(Line(thisx+dividerOffsX[0],yd,xd,yd,
|
|
||||||
strokeColor=dividerColor, strokeWidth=dividerWidth, strokeDashArray=dividerDashArray))
|
|
||||||
|
|
||||||
if (dividerLines&4) and (i%columnMaximum==lim or i==(n-1)):
|
|
||||||
yd -= max(deltay,leadingMove)+yGap
|
|
||||||
g.add(Line(thisx+dividerOffsX[0],yd,xd,yd,
|
|
||||||
strokeColor=dividerColor, strokeWidth=dividerWidth, strokeDashArray=dividerDashArray))
|
|
||||||
|
|
||||||
# Make a 'normal' color swatch...
|
|
||||||
if isAuto(col):
|
|
||||||
chart = getattr(col,'chart',getattr(col,'obj',None))
|
|
||||||
g.add(chart.makeSwatchSample(getattr(col,'index',i),x,thisy,dx,dy))
|
|
||||||
elif isinstance(col, colors.Color):
|
|
||||||
if isSymbol(swatchMarker):
|
|
||||||
g.add(uSymbol2Symbol(swatchMarker,x+dx/2.,thisy+dy/2.,col))
|
|
||||||
else:
|
|
||||||
g.add(self._defaultSwatch(x,thisy,dx,dy,fillColor=col,strokeWidth=strokeWidth,strokeColor=strokeColor))
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
c = copy.deepcopy(col)
|
|
||||||
c.x = x
|
|
||||||
c.y = thisy
|
|
||||||
c.width = dx
|
|
||||||
c.height = dy
|
|
||||||
g.add(c)
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
map(gAdd,S)
|
|
||||||
if self.colEndCallout and (i%columnMaximum==lim or i==(n-1)):
|
|
||||||
if alignment == "left":
|
|
||||||
xt = thisx
|
|
||||||
else:
|
|
||||||
xt = thisx+dx+dxTextSpace
|
|
||||||
yd = thisy+dy*0.5+dividerOffsY - (max(deltay,leadingMove)+yGap)
|
|
||||||
self.colEndCallout(self, g, thisx, xt, yd, maxWidth[j], maxWidth[j]+dx+dxTextSpace)
|
|
||||||
|
|
||||||
if i%columnMaximum==lim:
|
|
||||||
if variColumn:
|
|
||||||
thisx += maxWidth[j]+xW
|
|
||||||
else:
|
|
||||||
thisx = thisx+deltax
|
|
||||||
thisy = upperlefty
|
|
||||||
else:
|
|
||||||
thisy = thisy-max(deltay,leadingMove)-yGap
|
|
||||||
|
|
||||||
return g
|
|
||||||
|
|
||||||
def demo(self):
|
|
||||||
"Make sample legend."
|
|
||||||
|
|
||||||
d = Drawing(200, 100)
|
|
||||||
|
|
||||||
legend = Legend()
|
|
||||||
legend.alignment = 'left'
|
|
||||||
legend.x = 0
|
|
||||||
legend.y = 100
|
|
||||||
legend.dxTextSpace = 5
|
|
||||||
items = 'red green blue yellow pink black white'.split()
|
|
||||||
items = map(lambda i:(getattr(colors, i), i), items)
|
|
||||||
legend.colorNamePairs = items
|
|
||||||
|
|
||||||
d.add(legend, 'legend')
|
|
||||||
|
|
||||||
return d
|
|
||||||
|
|
||||||
class TotalAnnotator:
|
|
||||||
def __init__(self, lText='Total', rText='0.0', fontName='Times-Roman', fontSize=10,
|
|
||||||
fillColor=colors.black, strokeWidth=0.5, strokeColor=colors.black, strokeDashArray=None,
|
|
||||||
dx=0, dy=0, dly=0, dlx=(0,0)):
|
|
||||||
self.lText = lText
|
|
||||||
self.rText = rText
|
|
||||||
self.fontName = fontName
|
|
||||||
self.fontSize = fontSize
|
|
||||||
self.fillColor = fillColor
|
|
||||||
self.dy = dy
|
|
||||||
self.dx = dx
|
|
||||||
self.dly = dly
|
|
||||||
self.dlx = dlx
|
|
||||||
self.strokeWidth = strokeWidth
|
|
||||||
self.strokeColor = strokeColor
|
|
||||||
self.strokeDashArray = strokeDashArray
|
|
||||||
|
|
||||||
def __call__(self,legend, g, x, xt, y, width, lWidth):
|
|
||||||
from reportlab.graphics.shapes import String, Line
|
|
||||||
fontSize = self.fontSize
|
|
||||||
fontName = self.fontName
|
|
||||||
fillColor = self.fillColor
|
|
||||||
strokeColor = self.strokeColor
|
|
||||||
strokeWidth = self.strokeWidth
|
|
||||||
ascent=getFont(fontName).face.ascent/1000.
|
|
||||||
if ascent==0: ascent=0.718 # default (from helvetica)
|
|
||||||
ascent *= fontSize
|
|
||||||
leading = fontSize*1.2
|
|
||||||
yt = y+self.dy-ascent*1.3
|
|
||||||
if self.lText and fillColor:
|
|
||||||
g.add(String(xt,yt,self.lText,
|
|
||||||
fontName=fontName,
|
|
||||||
fontSize=fontSize,
|
|
||||||
fillColor=fillColor,
|
|
||||||
textAnchor = "start"))
|
|
||||||
if self.rText:
|
|
||||||
g.add(String(xt+width,yt,self.rText,
|
|
||||||
fontName=fontName,
|
|
||||||
fontSize=fontSize,
|
|
||||||
fillColor=fillColor,
|
|
||||||
textAnchor = "end"))
|
|
||||||
if strokeWidth and strokeColor:
|
|
||||||
yL = y+self.dly-leading
|
|
||||||
g.add(Line(x+self.dlx[0],yL,x+self.dlx[1]+lWidth,yL,
|
|
||||||
strokeColor=strokeColor, strokeWidth=strokeWidth,
|
|
||||||
strokeDashArray=self.strokeDashArray))
|
|
||||||
|
|
||||||
class LineSwatch(Widget):
|
|
||||||
"""basically a Line with properties added so it can be used in a LineLegend"""
|
|
||||||
_attrMap = AttrMap(
|
|
||||||
x = AttrMapValue(isNumber, desc="x-coordinate for swatch line start point"),
|
|
||||||
y = AttrMapValue(isNumber, desc="y-coordinate for swatch line start point"),
|
|
||||||
width = AttrMapValue(isNumber, desc="length of swatch line"),
|
|
||||||
height = AttrMapValue(isNumber, desc="used for line strokeWidth"),
|
|
||||||
strokeColor = AttrMapValue(isColorOrNone, desc="color of swatch line"),
|
|
||||||
strokeDashArray = AttrMapValue(isListOfNumbersOrNone, desc="dash array for swatch line"),
|
|
||||||
)
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
from reportlab.lib.colors import red
|
|
||||||
from reportlab.graphics.shapes import Line
|
|
||||||
self.x = 0
|
|
||||||
self.y = 0
|
|
||||||
self.width = 20
|
|
||||||
self.height = 1
|
|
||||||
self.strokeColor = red
|
|
||||||
self.strokeDashArray = None
|
|
||||||
|
|
||||||
def draw(self):
|
|
||||||
l = Line(self.x,self.y,self.x+self.width,self.y)
|
|
||||||
l.strokeColor = self.strokeColor
|
|
||||||
l.strokeDashArray = self.strokeDashArray
|
|
||||||
l.strokeWidth = self.height
|
|
||||||
return l
|
|
||||||
|
|
||||||
class LineLegend(Legend):
|
|
||||||
"""A subclass of Legend for drawing legends with lines as the
|
|
||||||
swatches rather than rectangles. Useful for lineCharts and
|
|
||||||
linePlots. Should be similar in all other ways the the standard
|
|
||||||
Legend class.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
Legend.__init__(self)
|
|
||||||
|
|
||||||
# Size of swatch rectangle.
|
|
||||||
self.dx = 10
|
|
||||||
self.dy = 2
|
|
||||||
|
|
||||||
def _defaultSwatch(self,x,thisy,dx,dy,fillColor,strokeWidth,strokeColor):
|
|
||||||
l = LineSwatch()
|
|
||||||
l.x = x
|
|
||||||
l.y = thisy
|
|
||||||
l.width = dx
|
|
||||||
l.height = dy
|
|
||||||
l.strokeColor = fillColor
|
|
||||||
return l
|
|
||||||
|
|
||||||
def sample1c():
|
|
||||||
"Make sample legend."
|
|
||||||
|
|
||||||
d = Drawing(200, 100)
|
|
||||||
|
|
||||||
legend = Legend()
|
|
||||||
legend.alignment = 'right'
|
|
||||||
legend.x = 0
|
|
||||||
legend.y = 100
|
|
||||||
legend.dxTextSpace = 5
|
|
||||||
items = 'red green blue yellow pink black white'.split()
|
|
||||||
items = map(lambda i:(getattr(colors, i), i), items)
|
|
||||||
legend.colorNamePairs = items
|
|
||||||
|
|
||||||
d.add(legend, 'legend')
|
|
||||||
|
|
||||||
return d
|
|
||||||
|
|
||||||
|
|
||||||
def sample2c():
|
|
||||||
"Make sample legend."
|
|
||||||
|
|
||||||
d = Drawing(200, 100)
|
|
||||||
|
|
||||||
legend = Legend()
|
|
||||||
legend.alignment = 'right'
|
|
||||||
legend.x = 20
|
|
||||||
legend.y = 90
|
|
||||||
legend.deltax = 60
|
|
||||||
legend.dxTextSpace = 10
|
|
||||||
legend.columnMaximum = 4
|
|
||||||
items = 'red green blue yellow pink black white'.split()
|
|
||||||
items = map(lambda i:(getattr(colors, i), i), items)
|
|
||||||
legend.colorNamePairs = items
|
|
||||||
|
|
||||||
d.add(legend, 'legend')
|
|
||||||
|
|
||||||
return d
|
|
||||||
|
|
||||||
def sample3():
|
|
||||||
"Make sample legend with line swatches."
|
|
||||||
|
|
||||||
d = Drawing(200, 100)
|
|
||||||
|
|
||||||
legend = LineLegend()
|
|
||||||
legend.alignment = 'right'
|
|
||||||
legend.x = 20
|
|
||||||
legend.y = 90
|
|
||||||
legend.deltax = 60
|
|
||||||
legend.dxTextSpace = 10
|
|
||||||
legend.columnMaximum = 4
|
|
||||||
items = 'red green blue yellow pink black white'.split()
|
|
||||||
items = map(lambda i:(getattr(colors, i), i), items)
|
|
||||||
legend.colorNamePairs = items
|
|
||||||
d.add(legend, 'legend')
|
|
||||||
|
|
||||||
return d
|
|
||||||
|
|
||||||
|
|
||||||
def sample3a():
|
|
||||||
"Make sample legend with line swatches and dasharrays on the lines."
|
|
||||||
|
|
||||||
d = Drawing(200, 100)
|
|
||||||
|
|
||||||
legend = LineLegend()
|
|
||||||
legend.alignment = 'right'
|
|
||||||
legend.x = 20
|
|
||||||
legend.y = 90
|
|
||||||
legend.deltax = 60
|
|
||||||
legend.dxTextSpace = 10
|
|
||||||
legend.columnMaximum = 4
|
|
||||||
items = 'red green blue yellow pink black white'.split()
|
|
||||||
darrays = ([2,1], [2,5], [2,2,5,5], [1,2,3,4], [4,2,3,4], [1,2,3,4,5,6], [1])
|
|
||||||
cnp = []
|
|
||||||
for i in range(0, len(items)):
|
|
||||||
l = LineSwatch()
|
|
||||||
l.strokeColor = getattr(colors, items[i])
|
|
||||||
l.strokeDashArray = darrays[i]
|
|
||||||
cnp.append((l, items[i]))
|
|
||||||
legend.colorNamePairs = cnp
|
|
||||||
d.add(legend, 'legend')
|
|
||||||
|
|
||||||
return d
|
|
|
@ -1,695 +0,0 @@
|
||||||
#Copyright ReportLab Europe Ltd. 2000-2004
|
|
||||||
#see license.txt for license details
|
|
||||||
#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/graphics/charts/linecharts.py
|
|
||||||
"""
|
|
||||||
This modules defines a very preliminary Line Chart example.
|
|
||||||
"""
|
|
||||||
__version__=''' $Id: linecharts.py 2493 2004-12-22 16:14:25Z rgbecker $ '''
|
|
||||||
|
|
||||||
import string
|
|
||||||
from types import FunctionType, StringType
|
|
||||||
|
|
||||||
from reportlab.lib import colors
|
|
||||||
from reportlab.lib.validators import isNumber, isColor, isColorOrNone, isListOfStrings, \
|
|
||||||
isListOfStringsOrNone, SequenceOf, isBoolean, NoneOr, \
|
|
||||||
isListOfNumbersOrNone, isStringOrNone
|
|
||||||
from reportlab.lib.attrmap import *
|
|
||||||
from reportlab.lib.formatters import Formatter
|
|
||||||
from reportlab.graphics.widgetbase import Widget, TypedPropertyCollection, PropHolder
|
|
||||||
from reportlab.graphics.shapes import Line, Rect, Group, Drawing, Polygon, PolyLine
|
|
||||||
from reportlab.graphics.widgets.signsandsymbols import NoEntry
|
|
||||||
from reportlab.graphics.charts.axes import XCategoryAxis, YValueAxis
|
|
||||||
from reportlab.graphics.charts.textlabels import Label
|
|
||||||
from reportlab.graphics.widgets.markers import uSymbol2Symbol, isSymbol, makeMarker
|
|
||||||
from reportlab.graphics.charts.areas import PlotArea
|
|
||||||
|
|
||||||
class LineChartProperties(PropHolder):
|
|
||||||
_attrMap = AttrMap(
|
|
||||||
strokeWidth = AttrMapValue(isNumber, desc='Width of a line.'),
|
|
||||||
strokeColor = AttrMapValue(isColorOrNone, desc='Color of a line.'),
|
|
||||||
strokeDashArray = AttrMapValue(isListOfNumbersOrNone, desc='Dash array of a line.'),
|
|
||||||
symbol = AttrMapValue(NoneOr(isSymbol), desc='Widget placed at data points.'),
|
|
||||||
shader = AttrMapValue(None, desc='Shader Class.'),
|
|
||||||
filler = AttrMapValue(None, desc='Filler Class.'),
|
|
||||||
name = AttrMapValue(isStringOrNone, desc='Name of the line.'),
|
|
||||||
)
|
|
||||||
|
|
||||||
class AbstractLineChart(PlotArea):
|
|
||||||
|
|
||||||
def makeSwatchSample(self,rowNo, x, y, width, height):
|
|
||||||
baseStyle = self.lines
|
|
||||||
styleIdx = rowNo % len(baseStyle)
|
|
||||||
style = baseStyle[styleIdx]
|
|
||||||
color = style.strokeColor
|
|
||||||
y = y+height/2.
|
|
||||||
if self.joinedLines:
|
|
||||||
dash = getattr(style, 'strokeDashArray', getattr(baseStyle,'strokeDashArray',None))
|
|
||||||
strokeWidth= getattr(style, 'strokeWidth', getattr(style, 'strokeWidth',None))
|
|
||||||
L = Line(x,y,x+width,y,strokeColor=color,strokeLineCap=0)
|
|
||||||
if strokeWidth: L.strokeWidth = strokeWidth
|
|
||||||
if dash: L.strokeDashArray = dash
|
|
||||||
else:
|
|
||||||
L = None
|
|
||||||
|
|
||||||
if hasattr(style, 'symbol'):
|
|
||||||
S = style.symbol
|
|
||||||
elif hasattr(baseStyle, 'symbol'):
|
|
||||||
S = baseStyle.symbol
|
|
||||||
else:
|
|
||||||
S = None
|
|
||||||
|
|
||||||
if S: S = uSymbol2Symbol(S,x+width/2.,y,color)
|
|
||||||
if S and L:
|
|
||||||
g = Group()
|
|
||||||
g.add(L)
|
|
||||||
g.add(S)
|
|
||||||
return g
|
|
||||||
return S or L
|
|
||||||
|
|
||||||
def getSeriesName(self,i,default=None):
|
|
||||||
'''return series name i or default'''
|
|
||||||
return getattr(self.lines[i],'name',default)
|
|
||||||
|
|
||||||
class LineChart(AbstractLineChart):
|
|
||||||
pass
|
|
||||||
|
|
||||||
# This is conceptually similar to the VerticalBarChart.
|
|
||||||
# Still it is better named HorizontalLineChart... :-/
|
|
||||||
|
|
||||||
class HorizontalLineChart(LineChart):
|
|
||||||
"""Line chart with multiple lines.
|
|
||||||
|
|
||||||
A line chart is assumed to have one category and one value axis.
|
|
||||||
Despite its generic name this particular line chart class has
|
|
||||||
a vertical value axis and a horizontal category one. It may
|
|
||||||
evolve into individual horizontal and vertical variants (like
|
|
||||||
with the existing bar charts).
|
|
||||||
|
|
||||||
Available attributes are:
|
|
||||||
|
|
||||||
x: x-position of lower-left chart origin
|
|
||||||
y: y-position of lower-left chart origin
|
|
||||||
width: chart width
|
|
||||||
height: chart height
|
|
||||||
|
|
||||||
useAbsolute: disables auto-scaling of chart elements (?)
|
|
||||||
lineLabelNudge: distance of data labels to data points
|
|
||||||
lineLabels: labels associated with data values
|
|
||||||
lineLabelFormat: format string or callback function
|
|
||||||
groupSpacing: space between categories
|
|
||||||
|
|
||||||
joinedLines: enables drawing of lines
|
|
||||||
|
|
||||||
strokeColor: color of chart lines (?)
|
|
||||||
fillColor: color for chart background (?)
|
|
||||||
lines: style list, used cyclically for data series
|
|
||||||
|
|
||||||
valueAxis: value axis object
|
|
||||||
categoryAxis: category axis object
|
|
||||||
categoryNames: category names
|
|
||||||
|
|
||||||
data: chart data, a list of data series of equal length
|
|
||||||
"""
|
|
||||||
|
|
||||||
_attrMap = AttrMap(BASE=LineChart,
|
|
||||||
useAbsolute = AttrMapValue(isNumber, desc='Flag to use absolute spacing values.'),
|
|
||||||
lineLabelNudge = AttrMapValue(isNumber, desc='Distance between a data point and its label.'),
|
|
||||||
lineLabels = AttrMapValue(None, desc='Handle to the list of data point labels.'),
|
|
||||||
lineLabelFormat = AttrMapValue(None, desc='Formatting string or function used for data point labels.'),
|
|
||||||
lineLabelArray = AttrMapValue(None, desc='explicit array of line label values, must match size of data if present.'),
|
|
||||||
groupSpacing = AttrMapValue(isNumber, desc='? - Likely to disappear.'),
|
|
||||||
joinedLines = AttrMapValue(isNumber, desc='Display data points joined with lines if true.'),
|
|
||||||
lines = AttrMapValue(None, desc='Handle of the lines.'),
|
|
||||||
valueAxis = AttrMapValue(None, desc='Handle of the value axis.'),
|
|
||||||
categoryAxis = AttrMapValue(None, desc='Handle of the category axis.'),
|
|
||||||
categoryNames = AttrMapValue(isListOfStringsOrNone, desc='List of category names.'),
|
|
||||||
data = AttrMapValue(None, desc='Data to be plotted, list of (lists of) numbers.'),
|
|
||||||
inFill = AttrMapValue(isBoolean, desc='Whether infilling should be done.'),
|
|
||||||
reversePlotOrder = AttrMapValue(isBoolean, desc='If true reverse plot order.'),
|
|
||||||
annotations = AttrMapValue(None, desc='list of callables, will be called with self, xscale, yscale.'),
|
|
||||||
)
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
LineChart.__init__(self)
|
|
||||||
|
|
||||||
# Allow for a bounding rectangle.
|
|
||||||
self.strokeColor = None
|
|
||||||
self.fillColor = None
|
|
||||||
|
|
||||||
# Named so we have less recoding for the horizontal one :-)
|
|
||||||
self.categoryAxis = XCategoryAxis()
|
|
||||||
self.valueAxis = YValueAxis()
|
|
||||||
|
|
||||||
# This defines two series of 3 points. Just an example.
|
|
||||||
self.data = [(100,110,120,130),
|
|
||||||
(70, 80, 80, 90)]
|
|
||||||
self.categoryNames = ('North','South','East','West')
|
|
||||||
|
|
||||||
self.lines = TypedPropertyCollection(LineChartProperties)
|
|
||||||
self.lines.strokeWidth = 1
|
|
||||||
self.lines[0].strokeColor = colors.red
|
|
||||||
self.lines[1].strokeColor = colors.green
|
|
||||||
self.lines[2].strokeColor = colors.blue
|
|
||||||
|
|
||||||
# control spacing. if useAbsolute = 1 then
|
|
||||||
# the next parameters are in points; otherwise
|
|
||||||
# they are 'proportions' and are normalized to
|
|
||||||
# fit the available space.
|
|
||||||
self.useAbsolute = 0 #- not done yet
|
|
||||||
self.groupSpacing = 1 #5
|
|
||||||
|
|
||||||
self.lineLabels = TypedPropertyCollection(Label)
|
|
||||||
self.lineLabelFormat = None
|
|
||||||
self.lineLabelArray = None
|
|
||||||
|
|
||||||
# This says whether the origin is above or below
|
|
||||||
# the data point. +10 means put the origin ten points
|
|
||||||
# above the data point if value > 0, or ten
|
|
||||||
# points below if data value < 0. This is different
|
|
||||||
# to label dx/dy which are not dependent on the
|
|
||||||
# sign of the data.
|
|
||||||
self.lineLabelNudge = 10
|
|
||||||
# If you have multiple series, by default they butt
|
|
||||||
# together.
|
|
||||||
|
|
||||||
# New line chart attributes.
|
|
||||||
self.joinedLines = 1 # Connect items with straight lines.
|
|
||||||
self.inFill = 0
|
|
||||||
self.reversePlotOrder = 0
|
|
||||||
|
|
||||||
|
|
||||||
def demo(self):
|
|
||||||
"""Shows basic use of a line chart."""
|
|
||||||
|
|
||||||
drawing = Drawing(200, 100)
|
|
||||||
|
|
||||||
data = [
|
|
||||||
(13, 5, 20, 22, 37, 45, 19, 4),
|
|
||||||
(14, 10, 21, 28, 38, 46, 25, 5)
|
|
||||||
]
|
|
||||||
|
|
||||||
lc = HorizontalLineChart()
|
|
||||||
|
|
||||||
lc.x = 20
|
|
||||||
lc.y = 10
|
|
||||||
lc.height = 85
|
|
||||||
lc.width = 170
|
|
||||||
lc.data = data
|
|
||||||
lc.lines.symbol = makeMarker('Circle')
|
|
||||||
|
|
||||||
drawing.add(lc)
|
|
||||||
|
|
||||||
return drawing
|
|
||||||
|
|
||||||
|
|
||||||
def calcPositions(self):
|
|
||||||
"""Works out where they go.
|
|
||||||
|
|
||||||
Sets an attribute _positions which is a list of
|
|
||||||
lists of (x, y) matching the data.
|
|
||||||
"""
|
|
||||||
|
|
||||||
self._seriesCount = len(self.data)
|
|
||||||
self._rowLength = max(map(len,self.data))
|
|
||||||
|
|
||||||
if self.useAbsolute:
|
|
||||||
# Dimensions are absolute.
|
|
||||||
normFactor = 1.0
|
|
||||||
else:
|
|
||||||
# Dimensions are normalized to fit.
|
|
||||||
normWidth = self.groupSpacing
|
|
||||||
availWidth = self.categoryAxis.scale(0)[1]
|
|
||||||
normFactor = availWidth / normWidth
|
|
||||||
|
|
||||||
self._positions = []
|
|
||||||
for rowNo in range(len(self.data)):
|
|
||||||
lineRow = []
|
|
||||||
for colNo in range(len(self.data[rowNo])):
|
|
||||||
datum = self.data[rowNo][colNo]
|
|
||||||
if datum is not None:
|
|
||||||
(groupX, groupWidth) = self.categoryAxis.scale(colNo)
|
|
||||||
x = groupX + (0.5 * self.groupSpacing * normFactor)
|
|
||||||
y = self.valueAxis.scale(0)
|
|
||||||
height = self.valueAxis.scale(datum) - y
|
|
||||||
lineRow.append((x, y+height))
|
|
||||||
self._positions.append(lineRow)
|
|
||||||
|
|
||||||
|
|
||||||
def _innerDrawLabel(self, rowNo, colNo, x, y):
|
|
||||||
"Draw a label for a given item in the list."
|
|
||||||
|
|
||||||
labelFmt = self.lineLabelFormat
|
|
||||||
labelValue = self.data[rowNo][colNo]
|
|
||||||
|
|
||||||
if labelFmt is None:
|
|
||||||
labelText = None
|
|
||||||
elif type(labelFmt) is StringType:
|
|
||||||
if labelFmt == 'values':
|
|
||||||
labelText = self.lineLabelArray[rowNo][colNo]
|
|
||||||
else:
|
|
||||||
labelText = labelFmt % labelValue
|
|
||||||
elif type(labelFmt) is FunctionType:
|
|
||||||
labelText = labelFmt(labelValue)
|
|
||||||
elif isinstance(labelFmt, Formatter):
|
|
||||||
labelText = labelFmt(labelValue)
|
|
||||||
else:
|
|
||||||
msg = "Unknown formatter type %s, expected string or function"
|
|
||||||
raise Exception, msg % labelFmt
|
|
||||||
|
|
||||||
if labelText:
|
|
||||||
label = self.lineLabels[(rowNo, colNo)]
|
|
||||||
# Make sure labels are some distance off the data point.
|
|
||||||
if y > 0:
|
|
||||||
label.setOrigin(x, y + self.lineLabelNudge)
|
|
||||||
else:
|
|
||||||
label.setOrigin(x, y - self.lineLabelNudge)
|
|
||||||
label.setText(labelText)
|
|
||||||
else:
|
|
||||||
label = None
|
|
||||||
return label
|
|
||||||
|
|
||||||
def drawLabel(self, G, rowNo, colNo, x, y):
|
|
||||||
'''Draw a label for a given item in the list.
|
|
||||||
G must have an add method'''
|
|
||||||
G.add(self._innerDrawLabel(rowNo,colNo,x,y))
|
|
||||||
|
|
||||||
def makeLines(self):
|
|
||||||
g = Group()
|
|
||||||
|
|
||||||
labelFmt = self.lineLabelFormat
|
|
||||||
P = range(len(self._positions))
|
|
||||||
if self.reversePlotOrder: P.reverse()
|
|
||||||
inFill = self.inFill
|
|
||||||
if inFill:
|
|
||||||
inFillY = self.categoryAxis._y
|
|
||||||
inFillX0 = self.valueAxis._x
|
|
||||||
inFillX1 = inFillX0 + self.categoryAxis._length
|
|
||||||
inFillG = getattr(self,'_inFillG',g)
|
|
||||||
|
|
||||||
# Iterate over data rows.
|
|
||||||
for rowNo in P:
|
|
||||||
row = self._positions[rowNo]
|
|
||||||
styleCount = len(self.lines)
|
|
||||||
styleIdx = rowNo % styleCount
|
|
||||||
rowStyle = self.lines[styleIdx]
|
|
||||||
rowColor = rowStyle.strokeColor
|
|
||||||
dash = getattr(rowStyle, 'strokeDashArray', None)
|
|
||||||
|
|
||||||
if hasattr(self.lines[styleIdx], 'strokeWidth'):
|
|
||||||
strokeWidth = self.lines[styleIdx].strokeWidth
|
|
||||||
elif hasattr(self.lines, 'strokeWidth'):
|
|
||||||
strokeWidth = self.lines.strokeWidth
|
|
||||||
else:
|
|
||||||
strokeWidth = None
|
|
||||||
|
|
||||||
# Iterate over data columns.
|
|
||||||
if self.joinedLines:
|
|
||||||
points = []
|
|
||||||
for colNo in range(len(row)):
|
|
||||||
points += row[colNo]
|
|
||||||
if inFill:
|
|
||||||
points = points + [inFillX1,inFillY,inFillX0,inFillY]
|
|
||||||
inFillG.add(Polygon(points,fillColor=rowColor,strokeColor=rowColor,strokeWidth=0.1))
|
|
||||||
else:
|
|
||||||
line = PolyLine(points,strokeColor=rowColor,strokeLineCap=0,strokeLineJoin=1)
|
|
||||||
if strokeWidth:
|
|
||||||
line.strokeWidth = strokeWidth
|
|
||||||
if dash:
|
|
||||||
line.strokeDashArray = dash
|
|
||||||
g.add(line)
|
|
||||||
|
|
||||||
if hasattr(self.lines[styleIdx], 'symbol'):
|
|
||||||
uSymbol = self.lines[styleIdx].symbol
|
|
||||||
elif hasattr(self.lines, 'symbol'):
|
|
||||||
uSymbol = self.lines.symbol
|
|
||||||
else:
|
|
||||||
uSymbol = None
|
|
||||||
|
|
||||||
if uSymbol:
|
|
||||||
for colNo in range(len(row)):
|
|
||||||
x1, y1 = row[colNo]
|
|
||||||
symbol = uSymbol2Symbol(uSymbol,x1,y1,rowStyle.strokeColor)
|
|
||||||
if symbol: g.add(symbol)
|
|
||||||
|
|
||||||
# Draw item labels.
|
|
||||||
for colNo in range(len(row)):
|
|
||||||
x1, y1 = row[colNo]
|
|
||||||
self.drawLabel(g, rowNo, colNo, x1, y1)
|
|
||||||
|
|
||||||
return g
|
|
||||||
|
|
||||||
def draw(self):
|
|
||||||
"Draws itself."
|
|
||||||
|
|
||||||
vA, cA = self.valueAxis, self.categoryAxis
|
|
||||||
vA.setPosition(self.x, self.y, self.height)
|
|
||||||
if vA: vA.joinAxis = cA
|
|
||||||
if cA: cA.joinAxis = vA
|
|
||||||
vA.configure(self.data)
|
|
||||||
|
|
||||||
# If zero is in chart, put x axis there, otherwise
|
|
||||||
# use bottom.
|
|
||||||
xAxisCrossesAt = vA.scale(0)
|
|
||||||
if ((xAxisCrossesAt > self.y + self.height) or (xAxisCrossesAt < self.y)):
|
|
||||||
y = self.y
|
|
||||||
else:
|
|
||||||
y = xAxisCrossesAt
|
|
||||||
|
|
||||||
cA.setPosition(self.x, y, self.width)
|
|
||||||
cA.configure(self.data)
|
|
||||||
|
|
||||||
self.calcPositions()
|
|
||||||
|
|
||||||
g = Group()
|
|
||||||
g.add(self.makeBackground())
|
|
||||||
if self.inFill:
|
|
||||||
self._inFillG = Group()
|
|
||||||
g.add(self._inFillG)
|
|
||||||
|
|
||||||
g.add(cA)
|
|
||||||
g.add(vA)
|
|
||||||
vA.gridStart = cA._x
|
|
||||||
vA.gridEnd = cA._x+cA._length
|
|
||||||
cA.gridStart = vA._y
|
|
||||||
cA.gridEnd = vA._y+vA._length
|
|
||||||
cA.makeGrid(g,parent=self)
|
|
||||||
vA.makeGrid(g,parent=self)
|
|
||||||
g.add(self.makeLines())
|
|
||||||
for a in getattr(self,'annotations',()): g.add(a(self,cA.scale,vA.scale))
|
|
||||||
return g
|
|
||||||
|
|
||||||
def _cmpFakeItem(a,b):
|
|
||||||
'''t, z0, z1, x, y = a[:5]'''
|
|
||||||
return cmp((-a[1],a[3],a[0],-a[4]),(-b[1],b[3],b[0],-b[4]))
|
|
||||||
|
|
||||||
class _FakeGroup:
|
|
||||||
def __init__(self):
|
|
||||||
self._data = []
|
|
||||||
|
|
||||||
def add(self,what):
|
|
||||||
if what: self._data.append(what)
|
|
||||||
|
|
||||||
def value(self):
|
|
||||||
return self._data
|
|
||||||
|
|
||||||
def sort(self):
|
|
||||||
self._data.sort(_cmpFakeItem)
|
|
||||||
#for t in self._data: print t
|
|
||||||
|
|
||||||
class HorizontalLineChart3D(HorizontalLineChart):
|
|
||||||
_attrMap = AttrMap(BASE=HorizontalLineChart,
|
|
||||||
theta_x = AttrMapValue(isNumber, desc='dx/dz'),
|
|
||||||
theta_y = AttrMapValue(isNumber, desc='dy/dz'),
|
|
||||||
zDepth = AttrMapValue(isNumber, desc='depth of an individual series'),
|
|
||||||
zSpace = AttrMapValue(isNumber, desc='z gap around series'),
|
|
||||||
)
|
|
||||||
theta_x = .5
|
|
||||||
theta_y = .5
|
|
||||||
zDepth = 10
|
|
||||||
zSpace = 3
|
|
||||||
|
|
||||||
def calcPositions(self):
|
|
||||||
HorizontalLineChart.calcPositions(self)
|
|
||||||
nSeries = self._seriesCount
|
|
||||||
zSpace = self.zSpace
|
|
||||||
zDepth = self.zDepth
|
|
||||||
if self.categoryAxis.style=='parallel_3d':
|
|
||||||
_3d_depth = nSeries*zDepth+(nSeries+1)*zSpace
|
|
||||||
else:
|
|
||||||
_3d_depth = zDepth + 2*zSpace
|
|
||||||
self._3d_dx = self.theta_x*_3d_depth
|
|
||||||
self._3d_dy = self.theta_y*_3d_depth
|
|
||||||
|
|
||||||
def _calc_z0(self,rowNo):
|
|
||||||
zSpace = self.zSpace
|
|
||||||
if self.categoryAxis.style=='parallel_3d':
|
|
||||||
z0 = rowNo*(self.zDepth+zSpace)+zSpace
|
|
||||||
else:
|
|
||||||
z0 = zSpace
|
|
||||||
return z0
|
|
||||||
|
|
||||||
def _zadjust(self,x,y,z):
|
|
||||||
return x+z*self.theta_x, y+z*self.theta_y
|
|
||||||
|
|
||||||
def makeLines(self):
|
|
||||||
labelFmt = self.lineLabelFormat
|
|
||||||
P = range(len(self._positions))
|
|
||||||
if self.reversePlotOrder: P.reverse()
|
|
||||||
inFill = self.inFill
|
|
||||||
assert not inFill, "inFill not supported for 3d yet"
|
|
||||||
#if inFill:
|
|
||||||
#inFillY = self.categoryAxis._y
|
|
||||||
#inFillX0 = self.valueAxis._x
|
|
||||||
#inFillX1 = inFillX0 + self.categoryAxis._length
|
|
||||||
#inFillG = getattr(self,'_inFillG',g)
|
|
||||||
zDepth = self.zDepth
|
|
||||||
_zadjust = self._zadjust
|
|
||||||
theta_x = self.theta_x
|
|
||||||
theta_y = self.theta_y
|
|
||||||
F = _FakeGroup()
|
|
||||||
from utils3d import _make_3d_line_info
|
|
||||||
tileWidth = getattr(self,'_3d_tilewidth',None)
|
|
||||||
if not tileWidth and self.categoryAxis.style!='parallel_3d': tileWidth = 1
|
|
||||||
|
|
||||||
# Iterate over data rows.
|
|
||||||
for rowNo in P:
|
|
||||||
row = self._positions[rowNo]
|
|
||||||
n = len(row)
|
|
||||||
styleCount = len(self.lines)
|
|
||||||
styleIdx = rowNo % styleCount
|
|
||||||
rowStyle = self.lines[styleIdx]
|
|
||||||
rowColor = rowStyle.strokeColor
|
|
||||||
dash = getattr(rowStyle, 'strokeDashArray', None)
|
|
||||||
z0 = self._calc_z0(rowNo)
|
|
||||||
z1 = z0 + zDepth
|
|
||||||
|
|
||||||
if hasattr(self.lines[styleIdx], 'strokeWidth'):
|
|
||||||
strokeWidth = self.lines[styleIdx].strokeWidth
|
|
||||||
elif hasattr(self.lines, 'strokeWidth'):
|
|
||||||
strokeWidth = self.lines.strokeWidth
|
|
||||||
else:
|
|
||||||
strokeWidth = None
|
|
||||||
|
|
||||||
# Iterate over data columns.
|
|
||||||
if self.joinedLines:
|
|
||||||
if n:
|
|
||||||
x0, y0 = row[0]
|
|
||||||
for colNo in xrange(1,n):
|
|
||||||
x1, y1 = row[colNo]
|
|
||||||
_make_3d_line_info( F, x0, x1, y0, y1, z0, z1,
|
|
||||||
theta_x, theta_y,
|
|
||||||
rowColor, fillColorShaded=None, tileWidth=tileWidth,
|
|
||||||
strokeColor=None, strokeWidth=None, strokeDashArray=None,
|
|
||||||
shading=0.1)
|
|
||||||
x0, y0 = x1, y1
|
|
||||||
|
|
||||||
if hasattr(self.lines[styleIdx], 'symbol'):
|
|
||||||
uSymbol = self.lines[styleIdx].symbol
|
|
||||||
elif hasattr(self.lines, 'symbol'):
|
|
||||||
uSymbol = self.lines.symbol
|
|
||||||
else:
|
|
||||||
uSymbol = None
|
|
||||||
|
|
||||||
if uSymbol:
|
|
||||||
for colNo in xrange(n):
|
|
||||||
x1, y1 = row[colNo]
|
|
||||||
x1, y1 = _zadjust(x1,y1,z0)
|
|
||||||
symbol = uSymbol2Symbol(uSymbol,x1,y1,rowColor)
|
|
||||||
if symbol: F.add((2,z0,z0,x1,y1,symbol))
|
|
||||||
|
|
||||||
# Draw item labels.
|
|
||||||
for colNo in xrange(n):
|
|
||||||
x1, y1 = row[colNo]
|
|
||||||
x1, y1 = _zadjust(x1,y1,z0)
|
|
||||||
L = self._innerDrawLabel(rowNo, colNo, x1, y1)
|
|
||||||
if L: F.add((2,z0,z0,x1,y1,L))
|
|
||||||
|
|
||||||
F.sort()
|
|
||||||
g = Group()
|
|
||||||
map(lambda x,a=g.add: a(x[-1]),F.value())
|
|
||||||
return g
|
|
||||||
|
|
||||||
class VerticalLineChart(LineChart):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def sample1():
|
|
||||||
drawing = Drawing(400, 200)
|
|
||||||
|
|
||||||
data = [
|
|
||||||
(13, 5, 20, 22, 37, 45, 19, 4),
|
|
||||||
(5, 20, 46, 38, 23, 21, 6, 14)
|
|
||||||
]
|
|
||||||
|
|
||||||
lc = HorizontalLineChart()
|
|
||||||
|
|
||||||
lc.x = 50
|
|
||||||
lc.y = 50
|
|
||||||
lc.height = 125
|
|
||||||
lc.width = 300
|
|
||||||
lc.data = data
|
|
||||||
lc.joinedLines = 1
|
|
||||||
lc.lines.symbol = makeMarker('FilledDiamond')
|
|
||||||
lc.lineLabelFormat = '%2.0f'
|
|
||||||
|
|
||||||
catNames = string.split('Jan Feb Mar Apr May Jun Jul Aug', ' ')
|
|
||||||
lc.categoryAxis.categoryNames = catNames
|
|
||||||
lc.categoryAxis.labels.boxAnchor = 'n'
|
|
||||||
|
|
||||||
lc.valueAxis.valueMin = 0
|
|
||||||
lc.valueAxis.valueMax = 60
|
|
||||||
lc.valueAxis.valueStep = 15
|
|
||||||
|
|
||||||
drawing.add(lc)
|
|
||||||
|
|
||||||
return drawing
|
|
||||||
|
|
||||||
|
|
||||||
class SampleHorizontalLineChart(HorizontalLineChart):
|
|
||||||
"Sample class overwriting one method to draw additional horizontal lines."
|
|
||||||
|
|
||||||
def demo(self):
|
|
||||||
"""Shows basic use of a line chart."""
|
|
||||||
|
|
||||||
drawing = Drawing(200, 100)
|
|
||||||
|
|
||||||
data = [
|
|
||||||
(13, 5, 20, 22, 37, 45, 19, 4),
|
|
||||||
(14, 10, 21, 28, 38, 46, 25, 5)
|
|
||||||
]
|
|
||||||
|
|
||||||
lc = SampleHorizontalLineChart()
|
|
||||||
|
|
||||||
lc.x = 20
|
|
||||||
lc.y = 10
|
|
||||||
lc.height = 85
|
|
||||||
lc.width = 170
|
|
||||||
lc.data = data
|
|
||||||
lc.strokeColor = colors.white
|
|
||||||
lc.fillColor = colors.HexColor(0xCCCCCC)
|
|
||||||
|
|
||||||
drawing.add(lc)
|
|
||||||
|
|
||||||
return drawing
|
|
||||||
|
|
||||||
|
|
||||||
def makeBackground(self):
|
|
||||||
g = Group()
|
|
||||||
|
|
||||||
g.add(HorizontalLineChart.makeBackground(self))
|
|
||||||
|
|
||||||
valAxis = self.valueAxis
|
|
||||||
valTickPositions = valAxis._tickValues
|
|
||||||
|
|
||||||
for y in valTickPositions:
|
|
||||||
y = valAxis.scale(y)
|
|
||||||
g.add(Line(self.x, y, self.x+self.width, y,
|
|
||||||
strokeColor = self.strokeColor))
|
|
||||||
|
|
||||||
return g
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def sample1a():
|
|
||||||
drawing = Drawing(400, 200)
|
|
||||||
|
|
||||||
data = [
|
|
||||||
(13, 5, 20, 22, 37, 45, 19, 4),
|
|
||||||
(5, 20, 46, 38, 23, 21, 6, 14)
|
|
||||||
]
|
|
||||||
|
|
||||||
lc = SampleHorizontalLineChart()
|
|
||||||
|
|
||||||
lc.x = 50
|
|
||||||
lc.y = 50
|
|
||||||
lc.height = 125
|
|
||||||
lc.width = 300
|
|
||||||
lc.data = data
|
|
||||||
lc.joinedLines = 1
|
|
||||||
lc.strokeColor = colors.white
|
|
||||||
lc.fillColor = colors.HexColor(0xCCCCCC)
|
|
||||||
lc.lines.symbol = makeMarker('FilledDiamond')
|
|
||||||
lc.lineLabelFormat = '%2.0f'
|
|
||||||
|
|
||||||
catNames = string.split('Jan Feb Mar Apr May Jun Jul Aug', ' ')
|
|
||||||
lc.categoryAxis.categoryNames = catNames
|
|
||||||
lc.categoryAxis.labels.boxAnchor = 'n'
|
|
||||||
|
|
||||||
lc.valueAxis.valueMin = 0
|
|
||||||
lc.valueAxis.valueMax = 60
|
|
||||||
lc.valueAxis.valueStep = 15
|
|
||||||
|
|
||||||
drawing.add(lc)
|
|
||||||
|
|
||||||
return drawing
|
|
||||||
|
|
||||||
|
|
||||||
def sample2():
|
|
||||||
drawing = Drawing(400, 200)
|
|
||||||
|
|
||||||
data = [
|
|
||||||
(13, 5, 20, 22, 37, 45, 19, 4),
|
|
||||||
(5, 20, 46, 38, 23, 21, 6, 14)
|
|
||||||
]
|
|
||||||
|
|
||||||
lc = HorizontalLineChart()
|
|
||||||
|
|
||||||
lc.x = 50
|
|
||||||
lc.y = 50
|
|
||||||
lc.height = 125
|
|
||||||
lc.width = 300
|
|
||||||
lc.data = data
|
|
||||||
lc.joinedLines = 1
|
|
||||||
lc.lines.symbol = makeMarker('Smiley')
|
|
||||||
lc.lineLabelFormat = '%2.0f'
|
|
||||||
lc.strokeColor = colors.black
|
|
||||||
lc.fillColor = colors.lightblue
|
|
||||||
|
|
||||||
catNames = string.split('Jan Feb Mar Apr May Jun Jul Aug', ' ')
|
|
||||||
lc.categoryAxis.categoryNames = catNames
|
|
||||||
lc.categoryAxis.labels.boxAnchor = 'n'
|
|
||||||
|
|
||||||
lc.valueAxis.valueMin = 0
|
|
||||||
lc.valueAxis.valueMax = 60
|
|
||||||
lc.valueAxis.valueStep = 15
|
|
||||||
|
|
||||||
drawing.add(lc)
|
|
||||||
|
|
||||||
return drawing
|
|
||||||
|
|
||||||
|
|
||||||
def sample3():
|
|
||||||
drawing = Drawing(400, 200)
|
|
||||||
|
|
||||||
data = [
|
|
||||||
(13, 5, 20, 22, 37, 45, 19, 4),
|
|
||||||
(5, 20, 46, 38, 23, 21, 6, 14)
|
|
||||||
]
|
|
||||||
|
|
||||||
lc = HorizontalLineChart()
|
|
||||||
|
|
||||||
lc.x = 50
|
|
||||||
lc.y = 50
|
|
||||||
lc.height = 125
|
|
||||||
lc.width = 300
|
|
||||||
lc.data = data
|
|
||||||
lc.joinedLines = 1
|
|
||||||
lc.lineLabelFormat = '%2.0f'
|
|
||||||
lc.strokeColor = colors.black
|
|
||||||
|
|
||||||
lc.lines[0].symbol = makeMarker('Smiley')
|
|
||||||
lc.lines[1].symbol = NoEntry
|
|
||||||
lc.lines[0].strokeWidth = 2
|
|
||||||
lc.lines[1].strokeWidth = 4
|
|
||||||
|
|
||||||
catNames = string.split('Jan Feb Mar Apr May Jun Jul Aug', ' ')
|
|
||||||
lc.categoryAxis.categoryNames = catNames
|
|
||||||
lc.categoryAxis.labels.boxAnchor = 'n'
|
|
||||||
|
|
||||||
lc.valueAxis.valueMin = 0
|
|
||||||
lc.valueAxis.valueMax = 60
|
|
||||||
lc.valueAxis.valueStep = 15
|
|
||||||
|
|
||||||
drawing.add(lc)
|
|
||||||
|
|
||||||
return drawing
|
|
|
@ -1,81 +0,0 @@
|
||||||
#Copyright ReportLab Europe Ltd. 2000-2004
|
|
||||||
#see license.txt for license details
|
|
||||||
#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/graphics/charts/markers.py
|
|
||||||
"""
|
|
||||||
This modules defines a collection of markers used in charts.
|
|
||||||
|
|
||||||
The make* functions return a simple shape or a widget as for
|
|
||||||
the smiley.
|
|
||||||
"""
|
|
||||||
__version__=''' $Id: markers.py 2385 2004-06-17 15:26:05Z rgbecker $ '''
|
|
||||||
from reportlab.lib import colors
|
|
||||||
from reportlab.graphics.shapes import Rect, Line, Circle, Polygon
|
|
||||||
from reportlab.graphics.widgets.signsandsymbols import SmileyFace
|
|
||||||
|
|
||||||
|
|
||||||
def makeEmptySquare(x, y, size, color):
|
|
||||||
"Make an empty square marker."
|
|
||||||
|
|
||||||
d = size/2.0
|
|
||||||
rect = Rect(x-d, y-d, 2*d, 2*d)
|
|
||||||
rect.strokeColor = color
|
|
||||||
rect.fillColor = None
|
|
||||||
|
|
||||||
return rect
|
|
||||||
|
|
||||||
|
|
||||||
def makeFilledSquare(x, y, size, color):
|
|
||||||
"Make a filled square marker."
|
|
||||||
|
|
||||||
d = size/2.0
|
|
||||||
rect = Rect(x-d, y-d, 2*d, 2*d)
|
|
||||||
rect.strokeColor = color
|
|
||||||
rect.fillColor = color
|
|
||||||
|
|
||||||
return rect
|
|
||||||
|
|
||||||
|
|
||||||
def makeFilledDiamond(x, y, size, color):
|
|
||||||
"Make a filled diamond marker."
|
|
||||||
|
|
||||||
d = size/2.0
|
|
||||||
poly = Polygon((x-d,y, x,y+d, x+d,y, x,y-d))
|
|
||||||
poly.strokeColor = color
|
|
||||||
poly.fillColor = color
|
|
||||||
|
|
||||||
return poly
|
|
||||||
|
|
||||||
|
|
||||||
def makeEmptyCircle(x, y, size, color):
|
|
||||||
"Make a hollow circle marker."
|
|
||||||
|
|
||||||
d = size/2.0
|
|
||||||
circle = Circle(x, y, d)
|
|
||||||
circle.strokeColor = color
|
|
||||||
circle.fillColor = colors.white
|
|
||||||
|
|
||||||
return circle
|
|
||||||
|
|
||||||
|
|
||||||
def makeFilledCircle(x, y, size, color):
|
|
||||||
"Make a hollow circle marker."
|
|
||||||
|
|
||||||
d = size/2.0
|
|
||||||
circle = Circle(x, y, d)
|
|
||||||
circle.strokeColor = color
|
|
||||||
circle.fillColor = color
|
|
||||||
|
|
||||||
return circle
|
|
||||||
|
|
||||||
|
|
||||||
def makeSmiley(x, y, size, color):
|
|
||||||
"Make a smiley marker."
|
|
||||||
|
|
||||||
d = size
|
|
||||||
s = SmileyFace()
|
|
||||||
s.fillColor = color
|
|
||||||
s.x = x-d
|
|
||||||
s.y = y-d
|
|
||||||
s.size = d*2
|
|
||||||
|
|
||||||
return s
|
|
|
@ -1,186 +0,0 @@
|
||||||
from reportlab.lib.colors import Color, white, black
|
|
||||||
from reportlab.graphics.charts.textlabels import Label
|
|
||||||
from reportlab.graphics.shapes import Polygon, Line, Circle, String, Drawing, PolyLine, Group, Rect
|
|
||||||
from reportlab.graphics.widgetbase import Widget, TypedPropertyCollection
|
|
||||||
from reportlab.lib.attrmap import *
|
|
||||||
from reportlab.lib.validators import *
|
|
||||||
from reportlab.lib.units import cm
|
|
||||||
from reportlab.pdfbase.pdfmetrics import stringWidth, getFont
|
|
||||||
from reportlab.graphics.widgets.grids import ShadedRect, Grid
|
|
||||||
|
|
||||||
class SlideBox(Widget):
|
|
||||||
"""Returns a slidebox widget"""
|
|
||||||
_attrMap = AttrMap(
|
|
||||||
labelFontName = AttrMapValue(isString, desc="Name of font used for the labels"),
|
|
||||||
labelFontSize = AttrMapValue(isNumber, desc="Size of font used for the labels"),
|
|
||||||
labelStrokeColor = AttrMapValue(isColorOrNone, desc="Colour for for number outlines"),
|
|
||||||
labelFillColor = AttrMapValue(isColorOrNone, desc="Colour for number insides"),
|
|
||||||
startColor = AttrMapValue(isColor, desc='Color of first box'),
|
|
||||||
endColor = AttrMapValue(isColor, desc='Color of last box'),
|
|
||||||
numberOfBoxes = AttrMapValue(isInt, desc='How many boxes there are'),
|
|
||||||
trianglePosition = AttrMapValue(isInt, desc='Which box is highlighted by the triangles'),
|
|
||||||
triangleHeight = AttrMapValue(isNumber, desc="Height of indicator triangles"),
|
|
||||||
triangleWidth = AttrMapValue(isNumber, desc="Width of indicator triangles"),
|
|
||||||
triangleFillColor = AttrMapValue(isColor, desc="Colour of indicator triangles"),
|
|
||||||
triangleStrokeColor = AttrMapValue(isColorOrNone, desc="Colour of indicator triangle outline"),
|
|
||||||
triangleStrokeWidth = AttrMapValue(isNumber, desc="Colour of indicator triangle outline"),
|
|
||||||
boxHeight = AttrMapValue(isNumber, desc="Height of the boxes"),
|
|
||||||
boxWidth = AttrMapValue(isNumber, desc="Width of the boxes"),
|
|
||||||
boxSpacing = AttrMapValue(isNumber, desc="Space between the boxes"),
|
|
||||||
boxOutlineColor = AttrMapValue(isColorOrNone, desc="Colour used to outline the boxes (if any)"),
|
|
||||||
boxOutlineWidth = AttrMapValue(isNumberOrNone, desc="Width of the box outline (if any)"),
|
|
||||||
leftPadding = AttrMapValue(isNumber, desc='Padding on left of drawing'),
|
|
||||||
rightPadding = AttrMapValue(isNumber, desc='Padding on right of drawing'),
|
|
||||||
topPadding = AttrMapValue(isNumber, desc='Padding at top of drawing'),
|
|
||||||
bottomPadding = AttrMapValue(isNumber, desc='Padding at bottom of drawing'),
|
|
||||||
background = AttrMapValue(isColorOrNone, desc='Colour of the background to the drawing (if any)'),
|
|
||||||
sourceLabelText = AttrMapValue(isNoneOrString, desc="Text used for the 'source' label (can be empty)"),
|
|
||||||
sourceLabelOffset = AttrMapValue(isNumber, desc='Padding at bottom of drawing'),
|
|
||||||
sourceLabelFontName = AttrMapValue(isString, desc="Name of font used for the 'source' label"),
|
|
||||||
sourceLabelFontSize = AttrMapValue(isNumber, desc="Font size for the 'source' label"),
|
|
||||||
sourceLabelFillColor = AttrMapValue(isColorOrNone, desc="Colour ink for the 'source' label (bottom right)"),
|
|
||||||
)
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
self.labelFontName = "Helvetica-Bold"
|
|
||||||
self.labelFontSize = 10
|
|
||||||
self.labelStrokeColor = black
|
|
||||||
self.labelFillColor = white
|
|
||||||
self.startColor = colors.Color(232/255.0,224/255.0,119/255.0)
|
|
||||||
self.endColor = colors.Color(25/255.0,77/255.0,135/255.0)
|
|
||||||
self.numberOfBoxes = 7
|
|
||||||
self.trianglePosition = 7
|
|
||||||
self.triangleHeight = 0.12*cm
|
|
||||||
self.triangleWidth = 0.38*cm
|
|
||||||
self.triangleFillColor = white
|
|
||||||
self.triangleStrokeColor = black
|
|
||||||
self.triangleStrokeWidth = 0.58
|
|
||||||
self.boxHeight = 0.55*cm
|
|
||||||
self.boxWidth = 0.73*cm
|
|
||||||
self.boxSpacing = 0.075*cm
|
|
||||||
self.boxOutlineColor = black
|
|
||||||
self.boxOutlineWidth = 0.58
|
|
||||||
self.leftPadding=5
|
|
||||||
self.rightPadding=5
|
|
||||||
self.topPadding=5
|
|
||||||
self.bottomPadding=5
|
|
||||||
self.background=None
|
|
||||||
self.sourceLabelText = "Source: ReportLab"
|
|
||||||
self.sourceLabelOffset = 0.2*cm
|
|
||||||
self.sourceLabelFontName = "Helvetica-Oblique"
|
|
||||||
self.sourceLabelFontSize = 6
|
|
||||||
self.sourceLabelFillColor = black
|
|
||||||
|
|
||||||
def _getDrawingDimensions(self):
|
|
||||||
tx=(self.numberOfBoxes*self.boxWidth)
|
|
||||||
if self.numberOfBoxes>1: tx=tx+((self.numberOfBoxes-1)*self.boxSpacing)
|
|
||||||
tx=tx+self.leftPadding+self.rightPadding
|
|
||||||
ty=self.boxHeight+self.triangleHeight
|
|
||||||
ty=ty+self.topPadding+self.bottomPadding+self.sourceLabelOffset+self.sourceLabelFontSize
|
|
||||||
return (tx,ty)
|
|
||||||
|
|
||||||
def _getColors(self):
|
|
||||||
# for calculating intermediate colors...
|
|
||||||
numShades = self.numberOfBoxes+1
|
|
||||||
fillColorStart = self.startColor
|
|
||||||
fillColorEnd = self.endColor
|
|
||||||
colorsList =[]
|
|
||||||
|
|
||||||
for i in range(0,numShades):
|
|
||||||
colorsList.append(colors.linearlyInterpolatedColor(fillColorStart, fillColorEnd, 0, numShades-1, i))
|
|
||||||
return colorsList
|
|
||||||
|
|
||||||
def demo(self,drawing=None):
|
|
||||||
from reportlab.lib import colors
|
|
||||||
if not drawing:
|
|
||||||
tx,ty=self._getDrawingDimensions()
|
|
||||||
drawing = Drawing(tx,ty)
|
|
||||||
drawing.add(self.draw())
|
|
||||||
return drawing
|
|
||||||
|
|
||||||
def draw(self):
|
|
||||||
g = Group()
|
|
||||||
ys = self.bottomPadding+(self.triangleHeight/2)+self.sourceLabelOffset+self.sourceLabelFontSize
|
|
||||||
if self.background:
|
|
||||||
x,y = self._getDrawingDimensions()
|
|
||||||
g.add(Rect(-self.leftPadding,-ys,x,y,
|
|
||||||
strokeColor=None,
|
|
||||||
strokeWidth=0,
|
|
||||||
fillColor=self.background))
|
|
||||||
|
|
||||||
ascent=getFont(self.labelFontName).face.ascent/1000.
|
|
||||||
if ascent==0: ascent=0.718 # default (from helvetica)
|
|
||||||
ascent=ascent*self.labelFontSize # normalize
|
|
||||||
|
|
||||||
colorsList = self._getColors()
|
|
||||||
|
|
||||||
# Draw the boxes - now uses ShadedRect from grids
|
|
||||||
x=0
|
|
||||||
for f in range (0,self.numberOfBoxes):
|
|
||||||
sr=ShadedRect()
|
|
||||||
sr.x=x
|
|
||||||
sr.y=0
|
|
||||||
sr.width=self.boxWidth
|
|
||||||
sr.height=self.boxHeight
|
|
||||||
sr.orientation = 'vertical'
|
|
||||||
sr.numShades = 30
|
|
||||||
sr.fillColorStart = colorsList[f]
|
|
||||||
sr.fillColorEnd = colorsList[f+1]
|
|
||||||
sr.strokeColor = None
|
|
||||||
sr.strokeWidth = 0
|
|
||||||
|
|
||||||
g.add(sr)
|
|
||||||
|
|
||||||
g.add(Rect(x,0,self.boxWidth,self.boxHeight,
|
|
||||||
strokeColor=self.boxOutlineColor,
|
|
||||||
strokeWidth=self.boxOutlineWidth,
|
|
||||||
fillColor=None))
|
|
||||||
|
|
||||||
g.add(String(x+self.boxWidth/2.,(self.boxHeight-ascent)/2.,
|
|
||||||
text = str(f+1),
|
|
||||||
fillColor = self.labelFillColor,
|
|
||||||
strokeColor=self.labelStrokeColor,
|
|
||||||
textAnchor = 'middle',
|
|
||||||
fontName = self.labelFontName,
|
|
||||||
fontSize = self.labelFontSize))
|
|
||||||
x=x+self.boxWidth+self.boxSpacing
|
|
||||||
|
|
||||||
#do triangles
|
|
||||||
xt = (self.trianglePosition*self.boxWidth)
|
|
||||||
if self.trianglePosition>1:
|
|
||||||
xt = xt+(self.trianglePosition-1)*self.boxSpacing
|
|
||||||
xt = xt-(self.boxWidth/2)
|
|
||||||
g.add(Polygon(
|
|
||||||
strokeColor = self.triangleStrokeColor,
|
|
||||||
strokeWidth = self.triangleStrokeWidth,
|
|
||||||
fillColor = self.triangleFillColor,
|
|
||||||
points=[xt,self.boxHeight-(self.triangleHeight/2),
|
|
||||||
xt-(self.triangleWidth/2),self.boxHeight+(self.triangleHeight/2),
|
|
||||||
xt+(self.triangleWidth/2),self.boxHeight+(self.triangleHeight/2),
|
|
||||||
xt,self.boxHeight-(self.triangleHeight/2)]))
|
|
||||||
g.add(Polygon(
|
|
||||||
strokeColor = self.triangleStrokeColor,
|
|
||||||
strokeWidth = self.triangleStrokeWidth,
|
|
||||||
fillColor = self.triangleFillColor,
|
|
||||||
points=[xt,0+(self.triangleHeight/2),
|
|
||||||
xt-(self.triangleWidth/2),0-(self.triangleHeight/2),
|
|
||||||
xt+(self.triangleWidth/2),0-(self.triangleHeight/2),
|
|
||||||
xt,0+(self.triangleHeight/2)]))
|
|
||||||
|
|
||||||
#source label
|
|
||||||
if self.sourceLabelText != None:
|
|
||||||
g.add(String(x-self.boxSpacing,0-(self.triangleHeight/2)-self.sourceLabelOffset-(self.sourceLabelFontSize),
|
|
||||||
text = self.sourceLabelText,
|
|
||||||
fillColor = self.sourceLabelFillColor,
|
|
||||||
textAnchor = 'end',
|
|
||||||
fontName = self.sourceLabelFontName,
|
|
||||||
fontSize = self.sourceLabelFontSize))
|
|
||||||
|
|
||||||
g.shift(self.leftPadding, ys)
|
|
||||||
|
|
||||||
return g
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
d = SlideBox()
|
|
||||||
d.demo().save(fnRoot="slidebox")
|
|
|
@ -1,407 +0,0 @@
|
||||||
#Copyright ReportLab Europe Ltd. 2000-2004
|
|
||||||
#see license.txt for license details
|
|
||||||
#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/graphics/charts/spider.py
|
|
||||||
# spider chart, also known as radar chart
|
|
||||||
|
|
||||||
"""Spider Chart
|
|
||||||
|
|
||||||
Normal use shows variation of 5-10 parameters against some 'norm' or target.
|
|
||||||
When there is more than one series, place the series with the largest
|
|
||||||
numbers first, as it will be overdrawn by each successive one.
|
|
||||||
"""
|
|
||||||
__version__=''' $Id: spider.py 2676 2005-09-06 10:25:00Z rgbecker $ '''
|
|
||||||
|
|
||||||
import copy
|
|
||||||
from math import sin, cos, pi
|
|
||||||
|
|
||||||
from reportlab.lib import colors
|
|
||||||
from reportlab.lib.validators import isColor, isNumber, isListOfNumbersOrNone,\
|
|
||||||
isListOfNumbers, isColorOrNone, isString,\
|
|
||||||
isListOfStringsOrNone, OneOf, SequenceOf,\
|
|
||||||
isBoolean, isListOfColors, isNumberOrNone,\
|
|
||||||
isNoneOrListOfNoneOrStrings, isTextAnchor,\
|
|
||||||
isNoneOrListOfNoneOrNumbers, isBoxAnchor,\
|
|
||||||
isStringOrNone, isStringOrNone, EitherOr,\
|
|
||||||
isCallable
|
|
||||||
from reportlab.lib.attrmap import *
|
|
||||||
from reportlab.pdfgen.canvas import Canvas
|
|
||||||
from reportlab.graphics.shapes import Group, Drawing, Line, Rect, Polygon, PolyLine, Ellipse, \
|
|
||||||
Wedge, String, STATE_DEFAULTS
|
|
||||||
from reportlab.graphics.widgetbase import Widget, TypedPropertyCollection, PropHolder
|
|
||||||
from reportlab.graphics.charts.areas import PlotArea
|
|
||||||
from piecharts import WedgeLabel
|
|
||||||
from reportlab.graphics.widgets.markers import makeMarker, uSymbol2Symbol, isSymbol
|
|
||||||
|
|
||||||
class StrandProperty(PropHolder):
|
|
||||||
|
|
||||||
_attrMap = AttrMap(
|
|
||||||
strokeWidth = AttrMapValue(isNumber),
|
|
||||||
fillColor = AttrMapValue(isColorOrNone),
|
|
||||||
strokeColor = AttrMapValue(isColorOrNone),
|
|
||||||
strokeDashArray = AttrMapValue(isListOfNumbersOrNone),
|
|
||||||
symbol = AttrMapValue(EitherOr((isStringOrNone,isSymbol)), desc='Widget placed at data points.'),
|
|
||||||
symbolSize= AttrMapValue(isNumber, desc='Symbol size.'),
|
|
||||||
name = AttrMapValue(isStringOrNone, desc='Name of the strand.'),
|
|
||||||
)
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
self.strokeWidth = 1
|
|
||||||
self.fillColor = None
|
|
||||||
self.strokeColor = STATE_DEFAULTS["strokeColor"]
|
|
||||||
self.strokeDashArray = STATE_DEFAULTS["strokeDashArray"]
|
|
||||||
self.symbol = None
|
|
||||||
self.symbolSize = 5
|
|
||||||
self.name = None
|
|
||||||
|
|
||||||
class SpokeProperty(PropHolder):
|
|
||||||
_attrMap = AttrMap(
|
|
||||||
strokeWidth = AttrMapValue(isNumber),
|
|
||||||
fillColor = AttrMapValue(isColorOrNone),
|
|
||||||
strokeColor = AttrMapValue(isColorOrNone),
|
|
||||||
strokeDashArray = AttrMapValue(isListOfNumbersOrNone),
|
|
||||||
labelRadius = AttrMapValue(isNumber),
|
|
||||||
visible = AttrMapValue(isBoolean,desc="True if the spoke line is to be drawn"),
|
|
||||||
)
|
|
||||||
|
|
||||||
def __init__(self,**kw):
|
|
||||||
self.strokeWidth = 0.5
|
|
||||||
self.fillColor = None
|
|
||||||
self.strokeColor = STATE_DEFAULTS["strokeColor"]
|
|
||||||
self.strokeDashArray = STATE_DEFAULTS["strokeDashArray"]
|
|
||||||
self.visible = 1
|
|
||||||
self.labelRadius = 1.05
|
|
||||||
|
|
||||||
class SpokeLabel(WedgeLabel):
|
|
||||||
def __init__(self,**kw):
|
|
||||||
WedgeLabel.__init__(self,**kw)
|
|
||||||
if '_text' not in kw.keys(): self._text = ''
|
|
||||||
|
|
||||||
class StrandLabel(SpokeLabel):
|
|
||||||
_attrMap = AttrMap(BASE=SpokeLabel,
|
|
||||||
format = AttrMapValue(EitherOr((isStringOrNone,isCallable)),"Format for the label"),
|
|
||||||
dR = AttrMapValue(isNumberOrNone,"radial shift for label"),
|
|
||||||
)
|
|
||||||
def __init__(self,**kw):
|
|
||||||
self.format = ''
|
|
||||||
self.dR = 0
|
|
||||||
SpokeLabel.__init__(self,**kw)
|
|
||||||
|
|
||||||
def _setupLabel(labelClass, text, radius, cx, cy, angle, car, sar, sty):
|
|
||||||
L = labelClass()
|
|
||||||
L._text = text
|
|
||||||
L.x = cx + radius*car
|
|
||||||
L.y = cy + radius*sar
|
|
||||||
L._pmv = angle*180/pi
|
|
||||||
L.boxAnchor = sty.boxAnchor
|
|
||||||
L.dx = sty.dx
|
|
||||||
L.dy = sty.dy
|
|
||||||
L.angle = sty.angle
|
|
||||||
L.boxAnchor = sty.boxAnchor
|
|
||||||
L.boxStrokeColor = sty.boxStrokeColor
|
|
||||||
L.boxStrokeWidth = sty.boxStrokeWidth
|
|
||||||
L.boxFillColor = sty.boxFillColor
|
|
||||||
L.strokeColor = sty.strokeColor
|
|
||||||
L.strokeWidth = sty.strokeWidth
|
|
||||||
L.leading = sty.leading
|
|
||||||
L.width = sty.width
|
|
||||||
L.maxWidth = sty.maxWidth
|
|
||||||
L.height = sty.height
|
|
||||||
L.textAnchor = sty.textAnchor
|
|
||||||
L.visible = sty.visible
|
|
||||||
L.topPadding = sty.topPadding
|
|
||||||
L.leftPadding = sty.leftPadding
|
|
||||||
L.rightPadding = sty.rightPadding
|
|
||||||
L.bottomPadding = sty.bottomPadding
|
|
||||||
L.fontName = sty.fontName
|
|
||||||
L.fontSize = sty.fontSize
|
|
||||||
L.fillColor = sty.fillColor
|
|
||||||
return L
|
|
||||||
|
|
||||||
class SpiderChart(PlotArea):
|
|
||||||
_attrMap = AttrMap(BASE=PlotArea,
|
|
||||||
data = AttrMapValue(None, desc='Data to be plotted, list of (lists of) numbers.'),
|
|
||||||
labels = AttrMapValue(isListOfStringsOrNone, desc="optional list of labels to use for each data point"),
|
|
||||||
startAngle = AttrMapValue(isNumber, desc="angle of first slice; like the compass, 0 is due North"),
|
|
||||||
direction = AttrMapValue( OneOf('clockwise', 'anticlockwise'), desc="'clockwise' or 'anticlockwise'"),
|
|
||||||
strands = AttrMapValue(None, desc="collection of strand descriptor objects"),
|
|
||||||
spokes = AttrMapValue(None, desc="collection of spoke descriptor objects"),
|
|
||||||
strandLabels = AttrMapValue(None, desc="collection of strand label descriptor objects"),
|
|
||||||
spokeLabels = AttrMapValue(None, desc="collection of spoke label descriptor objects"),
|
|
||||||
)
|
|
||||||
|
|
||||||
def makeSwatchSample(self, rowNo, x, y, width, height):
|
|
||||||
baseStyle = self.strands
|
|
||||||
styleIdx = rowNo % len(baseStyle)
|
|
||||||
style = baseStyle[styleIdx]
|
|
||||||
strokeColor = getattr(style, 'strokeColor', getattr(baseStyle,'strokeColor',None))
|
|
||||||
fillColor = getattr(style, 'fillColor', getattr(baseStyle,'fillColor',None))
|
|
||||||
strokeDashArray = getattr(style, 'strokeDashArray', getattr(baseStyle,'strokeDashArray',None))
|
|
||||||
strokeWidth = getattr(style, 'strokeWidth', getattr(baseStyle, 'strokeWidth',0))
|
|
||||||
symbol = getattr(style, 'symbol', getattr(baseStyle, 'symbol',None))
|
|
||||||
ym = y+height/2.0
|
|
||||||
if fillColor is None and strokeColor is not None and strokeWidth>0:
|
|
||||||
bg = Line(x,ym,x+width,ym,strokeWidth=strokeWidth,strokeColor=strokeColor,
|
|
||||||
strokeDashArray=strokeDashArray)
|
|
||||||
elif fillColor is not None:
|
|
||||||
bg = Rect(x,y,width,height,strokeWidth=strokeWidth,strokeColor=strokeColor,
|
|
||||||
strokeDashArray=strokeDashArray,fillColor=fillColor)
|
|
||||||
else:
|
|
||||||
bg = None
|
|
||||||
if symbol:
|
|
||||||
symbol = uSymbol2Symbol(symbol,x+width/2.,ym,color)
|
|
||||||
if bg:
|
|
||||||
g = Group()
|
|
||||||
g.add(bg)
|
|
||||||
g.add(symbol)
|
|
||||||
return g
|
|
||||||
return symbol or bg
|
|
||||||
|
|
||||||
def getSeriesName(self,i,default=None):
|
|
||||||
'''return series name i or default'''
|
|
||||||
return getattr(self.strands[i],'name',default)
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
PlotArea.__init__(self)
|
|
||||||
|
|
||||||
self.data = [[10,12,14,16,14,12], [6,8,10,12,9,11]]
|
|
||||||
self.labels = None # or list of strings
|
|
||||||
self.labels = ['a','b','c','d','e','f']
|
|
||||||
self.startAngle = 90
|
|
||||||
self.direction = "clockwise"
|
|
||||||
|
|
||||||
self.strands = TypedPropertyCollection(StrandProperty)
|
|
||||||
self.spokes = TypedPropertyCollection(SpokeProperty)
|
|
||||||
self.spokeLabels = TypedPropertyCollection(SpokeLabel)
|
|
||||||
self.spokeLabels._text = None
|
|
||||||
self.strandLabels = TypedPropertyCollection(StrandLabel)
|
|
||||||
self.x = 10
|
|
||||||
self.y = 10
|
|
||||||
self.width = 180
|
|
||||||
self.height = 180
|
|
||||||
|
|
||||||
def demo(self):
|
|
||||||
d = Drawing(200, 200)
|
|
||||||
d.add(SpiderChart())
|
|
||||||
return d
|
|
||||||
|
|
||||||
def normalizeData(self, outer = 0.0):
|
|
||||||
"""Turns data into normalized ones where each datum is < 1.0,
|
|
||||||
and 1.0 = maximum radius. Adds 10% at outside edge by default"""
|
|
||||||
data = self.data
|
|
||||||
assert min(map(min,data)) >=0, "Cannot do spider plots of negative numbers!"
|
|
||||||
norm = max(map(max,data))
|
|
||||||
norm *= (1.0+outer)
|
|
||||||
if norm<1e-9: norm = 1.0
|
|
||||||
self._norm = norm
|
|
||||||
return [[e/norm for e in row] for row in data]
|
|
||||||
|
|
||||||
def _innerDrawLabel(self, sty, radius, cx, cy, angle, car, sar, labelClass=StrandLabel):
|
|
||||||
"Draw a label for a given item in the list."
|
|
||||||
fmt = sty.format
|
|
||||||
value = radius*self._norm
|
|
||||||
if not fmt:
|
|
||||||
text = None
|
|
||||||
elif isinstance(fmt,str):
|
|
||||||
if fmt == 'values':
|
|
||||||
text = sty._text
|
|
||||||
else:
|
|
||||||
text = fmt % value
|
|
||||||
elif callable(fmt):
|
|
||||||
text = fmt(value)
|
|
||||||
else:
|
|
||||||
raise ValueError("Unknown formatter type %s, expected string or function" % fmt)
|
|
||||||
|
|
||||||
if text:
|
|
||||||
dR = sty.dR
|
|
||||||
if dR:
|
|
||||||
radius += dR/self._radius
|
|
||||||
L = _setupLabel(labelClass, text, radius, cx, cy, angle, car, sar, sty)
|
|
||||||
if dR<0: L._anti = 1
|
|
||||||
else:
|
|
||||||
L = None
|
|
||||||
return L
|
|
||||||
|
|
||||||
def draw(self):
|
|
||||||
# normalize slice data
|
|
||||||
g = self.makeBackground() or Group()
|
|
||||||
|
|
||||||
xradius = self.width/2.0
|
|
||||||
yradius = self.height/2.0
|
|
||||||
self._radius = radius = min(xradius, yradius)
|
|
||||||
cx = self.x + xradius
|
|
||||||
cy = self.y + yradius
|
|
||||||
|
|
||||||
data = self.normalizeData()
|
|
||||||
|
|
||||||
self._seriesCount = len(data)
|
|
||||||
n = len(data[0])
|
|
||||||
|
|
||||||
#labels
|
|
||||||
if self.labels is None:
|
|
||||||
labels = [''] * n
|
|
||||||
else:
|
|
||||||
labels = self.labels
|
|
||||||
#there's no point in raising errors for less than enough errors if
|
|
||||||
#we silently create all for the extreme case of no labels.
|
|
||||||
i = n-len(labels)
|
|
||||||
if i>0:
|
|
||||||
labels = labels + ['']*i
|
|
||||||
|
|
||||||
S = []
|
|
||||||
STRANDS = []
|
|
||||||
STRANDAREAS = []
|
|
||||||
syms = []
|
|
||||||
labs = []
|
|
||||||
csa = []
|
|
||||||
angle = self.startAngle*pi/180
|
|
||||||
direction = self.direction == "clockwise" and -1 or 1
|
|
||||||
angleBetween = direction*(2 * pi)/float(n)
|
|
||||||
spokes = self.spokes
|
|
||||||
spokeLabels = self.spokeLabels
|
|
||||||
for i in xrange(n):
|
|
||||||
car = cos(angle)*radius
|
|
||||||
sar = sin(angle)*radius
|
|
||||||
csa.append((car,sar,angle))
|
|
||||||
si = self.spokes[i]
|
|
||||||
if si.visible:
|
|
||||||
spoke = Line(cx, cy, cx + car, cy + sar, strokeWidth = si.strokeWidth, strokeColor=si.strokeColor, strokeDashArray=si.strokeDashArray)
|
|
||||||
S.append(spoke)
|
|
||||||
sli = spokeLabels[i]
|
|
||||||
text = sli._text
|
|
||||||
if not text: text = labels[i]
|
|
||||||
if text:
|
|
||||||
S.append(_setupLabel(WedgeLabel, text, si.labelRadius, cx, cy, angle, car, sar, sli))
|
|
||||||
angle += angleBetween
|
|
||||||
|
|
||||||
# now plot the polygons
|
|
||||||
rowIdx = 0
|
|
||||||
strands = self.strands
|
|
||||||
strandLabels = self.strandLabels
|
|
||||||
for row in data:
|
|
||||||
# series plot
|
|
||||||
rsty = strands[rowIdx]
|
|
||||||
points = []
|
|
||||||
car, sar = csa[-1][:2]
|
|
||||||
r = row[-1]
|
|
||||||
points.append(cx+car*r)
|
|
||||||
points.append(cy+sar*r)
|
|
||||||
for i in xrange(n):
|
|
||||||
car, sar, angle = csa[i]
|
|
||||||
r = row[i]
|
|
||||||
points.append(cx+car*r)
|
|
||||||
points.append(cy+sar*r)
|
|
||||||
L = self._innerDrawLabel(strandLabels[(rowIdx,i)], r, cx, cy, angle, car, sar, labelClass=StrandLabel)
|
|
||||||
if L: labs.append(L)
|
|
||||||
sty = strands[(rowIdx,i)]
|
|
||||||
uSymbol = sty.symbol
|
|
||||||
|
|
||||||
# put in a marker, if it needs one
|
|
||||||
if uSymbol:
|
|
||||||
s_x = cx+car*r
|
|
||||||
s_y = cy+sar*r
|
|
||||||
s_fillColor = sty.fillColor
|
|
||||||
s_strokeColor = sty.strokeColor
|
|
||||||
s_strokeWidth = sty.strokeWidth
|
|
||||||
s_angle = 0
|
|
||||||
s_size = sty.symbolSize
|
|
||||||
if type(uSymbol) is type(''):
|
|
||||||
symbol = makeMarker(uSymbol,
|
|
||||||
size = s_size,
|
|
||||||
x = s_x,
|
|
||||||
y = s_y,
|
|
||||||
fillColor = s_fillColor,
|
|
||||||
strokeColor = s_strokeColor,
|
|
||||||
strokeWidth = s_strokeWidth,
|
|
||||||
angle = s_angle,
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
symbol = uSymbol2Symbol(uSymbol,s_x,s_y,s_fillColor)
|
|
||||||
for k,v in (('size', s_size), ('fillColor', s_fillColor),
|
|
||||||
('x', s_x), ('y', s_y),
|
|
||||||
('strokeColor',s_strokeColor), ('strokeWidth',s_strokeWidth),
|
|
||||||
('angle',s_angle),):
|
|
||||||
if getattr(symbol,k,None) is None:
|
|
||||||
try:
|
|
||||||
setattr(symbol,k,v)
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
syms.append(symbol)
|
|
||||||
|
|
||||||
# make up the 'strand'
|
|
||||||
if rsty.fillColor:
|
|
||||||
strand = Polygon(points)
|
|
||||||
strand.fillColor = rsty.fillColor
|
|
||||||
strand.strokeColor = None
|
|
||||||
strand.strokeWidth = 0
|
|
||||||
STRANDAREAS.append(strand)
|
|
||||||
if rsty.strokeColor and rsty.strokeWidth:
|
|
||||||
strand = PolyLine(points)
|
|
||||||
strand.strokeColor = rsty.strokeColor
|
|
||||||
strand.strokeWidth = rsty.strokeWidth
|
|
||||||
strand.strokeDashArray = rsty.strokeDashArray
|
|
||||||
STRANDS.append(strand)
|
|
||||||
rowIdx += 1
|
|
||||||
|
|
||||||
map(g.add,STRANDAREAS+STRANDS+syms+S+labs)
|
|
||||||
return g
|
|
||||||
|
|
||||||
def sample1():
|
|
||||||
"Make a simple spider chart"
|
|
||||||
d = Drawing(400, 400)
|
|
||||||
sp = SpiderChart()
|
|
||||||
sp.x = 50
|
|
||||||
sp.y = 50
|
|
||||||
sp.width = 300
|
|
||||||
sp.height = 300
|
|
||||||
sp.data = [[10,12,14,16,14,12], [6,8,10,12,9,15],[7,8,17,4,12,8]]
|
|
||||||
sp.labels = ['a','b','c','d','e','f']
|
|
||||||
sp.strands[0].strokeColor = colors.cornsilk
|
|
||||||
sp.strands[1].strokeColor = colors.cyan
|
|
||||||
sp.strands[2].strokeColor = colors.palegreen
|
|
||||||
sp.strands[0].fillColor = colors.cornsilk
|
|
||||||
sp.strands[1].fillColor = colors.cyan
|
|
||||||
sp.strands[2].fillColor = colors.palegreen
|
|
||||||
sp.spokes.strokeDashArray = (2,2)
|
|
||||||
d.add(sp)
|
|
||||||
return d
|
|
||||||
|
|
||||||
|
|
||||||
def sample2():
|
|
||||||
"Make a spider chart with markers, but no fill"
|
|
||||||
d = Drawing(400, 400)
|
|
||||||
sp = SpiderChart()
|
|
||||||
sp.x = 50
|
|
||||||
sp.y = 50
|
|
||||||
sp.width = 300
|
|
||||||
sp.height = 300
|
|
||||||
sp.data = [[10,12,14,16,14,12], [6,8,10,12,9,15],[7,8,17,4,12,8]]
|
|
||||||
sp.labels = ['U','V','W','X','Y','Z']
|
|
||||||
sp.strands.strokeWidth = 1
|
|
||||||
sp.strands[0].fillColor = colors.pink
|
|
||||||
sp.strands[1].fillColor = colors.lightblue
|
|
||||||
sp.strands[2].fillColor = colors.palegreen
|
|
||||||
sp.strands[0].strokeColor = colors.red
|
|
||||||
sp.strands[1].strokeColor = colors.blue
|
|
||||||
sp.strands[2].strokeColor = colors.green
|
|
||||||
sp.strands.symbol = "FilledDiamond"
|
|
||||||
sp.strands[1].symbol = makeMarker("Circle")
|
|
||||||
sp.strands[1].symbol.strokeWidth = 0.5
|
|
||||||
sp.strands[1].symbol.fillColor = colors.yellow
|
|
||||||
sp.strands.symbolSize = 6
|
|
||||||
sp.strandLabels[0,3]._text = 'special'
|
|
||||||
sp.strandLabels[0,1]._text = 'one'
|
|
||||||
sp.strandLabels[0,0]._text = 'zero'
|
|
||||||
sp.strandLabels[1,0]._text = 'Earth'
|
|
||||||
sp.strandLabels[2,2]._text = 'Mars'
|
|
||||||
sp.strandLabels.format = 'values'
|
|
||||||
sp.strandLabels.dR = -5
|
|
||||||
d.add(sp)
|
|
||||||
return d
|
|
||||||
|
|
||||||
|
|
||||||
if __name__=='__main__':
|
|
||||||
d = sample1()
|
|
||||||
from reportlab.graphics.renderPDF import drawToFile
|
|
||||||
drawToFile(d, 'spider.pdf')
|
|
||||||
d = sample2()
|
|
||||||
drawToFile(d, 'spider2.pdf')
|
|
|
@ -1,442 +0,0 @@
|
||||||
#Copyright ReportLab Europe Ltd. 2000-2004
|
|
||||||
#see license.txt for license details
|
|
||||||
#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/graphics/charts/textlabels.py
|
|
||||||
__version__=''' $Id: textlabels.py 2647 2005-07-26 13:47:51Z rgbecker $ '''
|
|
||||||
import string
|
|
||||||
|
|
||||||
from reportlab.lib import colors
|
|
||||||
from reportlab.lib.validators import isNumber, isNumberOrNone, OneOf, isColorOrNone, isString, \
|
|
||||||
isTextAnchor, isBoxAnchor, isBoolean, NoneOr, isInstanceOf, isNoneOrString
|
|
||||||
from reportlab.lib.attrmap import *
|
|
||||||
from reportlab.pdfbase.pdfmetrics import stringWidth
|
|
||||||
from reportlab.graphics.shapes import Drawing, Group, Circle, Rect, String, STATE_DEFAULTS
|
|
||||||
from reportlab.graphics.shapes import _PATH_OP_ARG_COUNT, _PATH_OP_NAMES, definePath
|
|
||||||
from reportlab.graphics.widgetbase import Widget, PropHolder
|
|
||||||
|
|
||||||
_gs = None
|
|
||||||
_A2BA= {
|
|
||||||
'x': {0:'n', 45:'ne', 90:'e', 135:'se', 180:'s', 225:'sw', 270:'w', 315: 'nw', -45: 'nw'},
|
|
||||||
'y': {0:'e', 45:'se', 90:'s', 135:'sw', 180:'w', 225:'nw', 270:'n', 315: 'ne', -45: 'ne'},
|
|
||||||
}
|
|
||||||
def _simpleSplit(txt,mW,SW):
|
|
||||||
L = []
|
|
||||||
ws = SW(' ')
|
|
||||||
O = []
|
|
||||||
w = -ws
|
|
||||||
for t in string.split(txt):
|
|
||||||
lt = SW(t)
|
|
||||||
if w+ws+lt<=mW or O==[]:
|
|
||||||
O.append(t)
|
|
||||||
w = w + ws + lt
|
|
||||||
else:
|
|
||||||
L.append(string.join(O,' '))
|
|
||||||
O = [t]
|
|
||||||
w = lt
|
|
||||||
if O!=[]: L.append(string.join(O,' '))
|
|
||||||
return L
|
|
||||||
|
|
||||||
def _pathNumTrunc(n):
|
|
||||||
if int(n)==n: return int(n)
|
|
||||||
return round(n,5)
|
|
||||||
|
|
||||||
def _processGlyph(G, truncate=1, pathReverse=0):
|
|
||||||
O = []
|
|
||||||
P = []
|
|
||||||
R = []
|
|
||||||
for g in G+(('end',),):
|
|
||||||
op = g[0]
|
|
||||||
if O and op in ['moveTo', 'moveToClosed','end']:
|
|
||||||
if O[0]=='moveToClosed':
|
|
||||||
O = O[1:]
|
|
||||||
if pathReverse:
|
|
||||||
for i in xrange(0,len(P),2):
|
|
||||||
P[i+1], P[i] = P[i:i+2]
|
|
||||||
P.reverse()
|
|
||||||
O.reverse()
|
|
||||||
O.insert(0,'moveTo')
|
|
||||||
O.append('closePath')
|
|
||||||
i = 0
|
|
||||||
if truncate: P = map(_pathNumTrunc,P)
|
|
||||||
for o in O:
|
|
||||||
j = i + _PATH_OP_ARG_COUNT[_PATH_OP_NAMES.index(o)]
|
|
||||||
if o=='closePath':
|
|
||||||
R.append(o)
|
|
||||||
else:
|
|
||||||
R.append((o,)+ tuple(P[i:j]))
|
|
||||||
i = j
|
|
||||||
O = []
|
|
||||||
P = []
|
|
||||||
O.append(op)
|
|
||||||
P.extend(g[1:])
|
|
||||||
return R
|
|
||||||
|
|
||||||
def _text2PathDescription(text, x=0, y=0, fontName='Times-Roman', fontSize=1000,
|
|
||||||
anchor='start', truncate=1, pathReverse=0):
|
|
||||||
global _gs
|
|
||||||
if not _gs:
|
|
||||||
import _renderPM
|
|
||||||
_gs = _renderPM.gstate(1,1)
|
|
||||||
from reportlab.graphics import renderPM
|
|
||||||
renderPM._setFont(_gs,fontName,fontSize)
|
|
||||||
P = []
|
|
||||||
if not anchor =='start':
|
|
||||||
textLen = stringWidth(text, fontName,fontSize)
|
|
||||||
if text_anchor=='end':
|
|
||||||
x = x-textLen
|
|
||||||
elif text_anchor=='middle':
|
|
||||||
x = x - textLen/2.
|
|
||||||
for g in _gs._stringPath(text,x,y):
|
|
||||||
P.extend(_processGlyph(g,truncate=truncate,pathReverse=pathReverse))
|
|
||||||
return P
|
|
||||||
|
|
||||||
def _text2Path(text, x=0, y=0, fontName='Times-Roman', fontSize=1000,
|
|
||||||
anchor='start', truncate=1, pathReverse=0):
|
|
||||||
return definePath(_text2PathDescription(text,x=x,y=y,fontName=fontName,
|
|
||||||
fontSize=fontSize,anchor=anchor,truncate=truncate,pathReverse=pathReverse))
|
|
||||||
|
|
||||||
_BA2TA={'w':'start','nw':'start','sw':'start','e':'end', 'ne': 'end', 'se':'end', 'n':'middle','s':'middle','c':'middle'}
|
|
||||||
class Label(Widget):
|
|
||||||
"""A text label to attach to something else, such as a chart axis.
|
|
||||||
|
|
||||||
This allows you to specify an offset, angle and many anchor
|
|
||||||
properties relative to the label's origin. It allows, for example,
|
|
||||||
angled multiline axis labels.
|
|
||||||
"""
|
|
||||||
# fairly straight port of Robin Becker's textbox.py to new widgets
|
|
||||||
# framework.
|
|
||||||
|
|
||||||
_attrMap = AttrMap(
|
|
||||||
x = AttrMapValue(isNumber),
|
|
||||||
y = AttrMapValue(isNumber),
|
|
||||||
dx = AttrMapValue(isNumber),
|
|
||||||
dy = AttrMapValue(isNumber),
|
|
||||||
angle = AttrMapValue(isNumber),
|
|
||||||
boxAnchor = AttrMapValue(isBoxAnchor),
|
|
||||||
boxStrokeColor = AttrMapValue(isColorOrNone),
|
|
||||||
boxStrokeWidth = AttrMapValue(isNumber),
|
|
||||||
boxFillColor = AttrMapValue(isColorOrNone),
|
|
||||||
boxTarget = AttrMapValue(isString),
|
|
||||||
fillColor = AttrMapValue(isColorOrNone),
|
|
||||||
strokeColor = AttrMapValue(isColorOrNone),
|
|
||||||
strokeWidth = AttrMapValue(isNumber),
|
|
||||||
text = AttrMapValue(isString),
|
|
||||||
fontName = AttrMapValue(isString),
|
|
||||||
fontSize = AttrMapValue(isNumber),
|
|
||||||
leading = AttrMapValue(isNumberOrNone),
|
|
||||||
width = AttrMapValue(isNumberOrNone),
|
|
||||||
maxWidth = AttrMapValue(isNumberOrNone),
|
|
||||||
height = AttrMapValue(isNumberOrNone),
|
|
||||||
textAnchor = AttrMapValue(isTextAnchor),
|
|
||||||
visible = AttrMapValue(isBoolean,desc="True if the label is to be drawn"),
|
|
||||||
topPadding = AttrMapValue(isNumber,'padding at top of box'),
|
|
||||||
leftPadding = AttrMapValue(isNumber,'padding at left of box'),
|
|
||||||
rightPadding = AttrMapValue(isNumber,'padding at right of box'),
|
|
||||||
bottomPadding = AttrMapValue(isNumber,'padding at bottom of box'),
|
|
||||||
)
|
|
||||||
|
|
||||||
def __init__(self,**kw):
|
|
||||||
self._setKeywords(**kw)
|
|
||||||
self._setKeywords(
|
|
||||||
_text = 'Multi-Line\nString',
|
|
||||||
boxAnchor = 'c',
|
|
||||||
angle = 0,
|
|
||||||
x = 0,
|
|
||||||
y = 0,
|
|
||||||
dx = 0,
|
|
||||||
dy = 0,
|
|
||||||
topPadding = 0,
|
|
||||||
leftPadding = 0,
|
|
||||||
rightPadding = 0,
|
|
||||||
bottomPadding = 0,
|
|
||||||
boxStrokeWidth = 0.5,
|
|
||||||
boxStrokeColor = None,
|
|
||||||
boxTarget = 'normal',
|
|
||||||
strokeColor = None,
|
|
||||||
boxFillColor = None,
|
|
||||||
leading = None,
|
|
||||||
width = None,
|
|
||||||
maxWidth = None,
|
|
||||||
height = None,
|
|
||||||
fillColor = STATE_DEFAULTS['fillColor'],
|
|
||||||
fontName = STATE_DEFAULTS['fontName'],
|
|
||||||
fontSize = STATE_DEFAULTS['fontSize'],
|
|
||||||
strokeWidth = 0.1,
|
|
||||||
textAnchor = 'start',
|
|
||||||
visible = 1,
|
|
||||||
)
|
|
||||||
|
|
||||||
def setText(self, text):
|
|
||||||
"""Set the text property. May contain embedded newline characters.
|
|
||||||
Called by the containing chart or axis."""
|
|
||||||
self._text = text
|
|
||||||
|
|
||||||
|
|
||||||
def setOrigin(self, x, y):
|
|
||||||
"""Set the origin. This would be the tick mark or bar top relative to
|
|
||||||
which it is defined. Called by the containing chart or axis."""
|
|
||||||
self.x = x
|
|
||||||
self.y = y
|
|
||||||
|
|
||||||
|
|
||||||
def demo(self):
|
|
||||||
"""This shows a label positioned with its top right corner
|
|
||||||
at the top centre of the drawing, and rotated 45 degrees."""
|
|
||||||
|
|
||||||
d = Drawing(200, 100)
|
|
||||||
|
|
||||||
# mark the origin of the label
|
|
||||||
d.add(Circle(100,90, 5, fillColor=colors.green))
|
|
||||||
|
|
||||||
lab = Label()
|
|
||||||
lab.setOrigin(100,90)
|
|
||||||
lab.boxAnchor = 'ne'
|
|
||||||
lab.angle = 45
|
|
||||||
lab.dx = 0
|
|
||||||
lab.dy = -20
|
|
||||||
lab.boxStrokeColor = colors.green
|
|
||||||
lab.setText('Another\nMulti-Line\nString')
|
|
||||||
d.add(lab)
|
|
||||||
|
|
||||||
return d
|
|
||||||
|
|
||||||
def _getBoxAnchor(self):
|
|
||||||
'''hook for allowing special box anchor effects'''
|
|
||||||
ba = self.boxAnchor
|
|
||||||
if ba in ('autox', 'autoy'):
|
|
||||||
angle = self.angle
|
|
||||||
na = (int((angle%360)/45.)*45)%360
|
|
||||||
if not (na % 90): # we have a right angle case
|
|
||||||
da = (angle - na) % 360
|
|
||||||
if abs(da)>5:
|
|
||||||
na = na + (da>0 and 45 or -45)
|
|
||||||
ba = _A2BA[ba[-1]][na]
|
|
||||||
return ba
|
|
||||||
|
|
||||||
def computeSize(self):
|
|
||||||
# the thing will draw in its own coordinate system
|
|
||||||
self._lines = string.split(self._text, '\n')
|
|
||||||
self._lineWidths = []
|
|
||||||
topPadding = self.topPadding
|
|
||||||
leftPadding = self.leftPadding
|
|
||||||
rightPadding = self.rightPadding
|
|
||||||
bottomPadding = self.bottomPadding
|
|
||||||
SW = lambda text, fN=self.fontName, fS=self.fontSize: stringWidth(text, fN, fS)
|
|
||||||
if self.maxWidth:
|
|
||||||
L = []
|
|
||||||
for l in self._lines:
|
|
||||||
L[-1:-1] = _simpleSplit(l,self.maxWidth,SW)
|
|
||||||
self._lines = L
|
|
||||||
if not self.width:
|
|
||||||
w = 0
|
|
||||||
for line in self._lines:
|
|
||||||
thisWidth = SW(line)
|
|
||||||
self._lineWidths.append(thisWidth)
|
|
||||||
w = max(w,thisWidth)
|
|
||||||
self._width = w+leftPadding+rightPadding
|
|
||||||
else:
|
|
||||||
self._width = self.width
|
|
||||||
self._height = self.height or ((self.leading or 1.2*self.fontSize) * len(self._lines)+topPadding+bottomPadding)
|
|
||||||
self._ewidth = (self._width-leftPadding-rightPadding)
|
|
||||||
self._eheight = (self._height-topPadding-bottomPadding)
|
|
||||||
boxAnchor = self._getBoxAnchor()
|
|
||||||
if boxAnchor in ['n','ne','nw']:
|
|
||||||
self._top = -topPadding
|
|
||||||
elif boxAnchor in ['s','sw','se']:
|
|
||||||
self._top = self._height-topPadding
|
|
||||||
else:
|
|
||||||
self._top = 0.5*self._eheight
|
|
||||||
self._bottom = self._top - self._eheight
|
|
||||||
|
|
||||||
if boxAnchor in ['ne','e','se']:
|
|
||||||
self._left = leftPadding - self._width
|
|
||||||
elif boxAnchor in ['nw','w','sw']:
|
|
||||||
self._left = leftPadding
|
|
||||||
else:
|
|
||||||
self._left = -self._ewidth*0.5
|
|
||||||
self._right = self._left+self._ewidth
|
|
||||||
|
|
||||||
def _getTextAnchor(self):
|
|
||||||
'''This can be overridden to allow special effects'''
|
|
||||||
ta = self.textAnchor
|
|
||||||
if ta=='boxauto': ta = _BA2TA[self._getBoxAnchor()]
|
|
||||||
return ta
|
|
||||||
|
|
||||||
def draw(self):
|
|
||||||
_text = self._text
|
|
||||||
self._text = _text or ''
|
|
||||||
self.computeSize()
|
|
||||||
self._text = _text
|
|
||||||
g = Group()
|
|
||||||
g.translate(self.x + self.dx, self.y + self.dy)
|
|
||||||
g.rotate(self.angle)
|
|
||||||
|
|
||||||
y = self._top - self.fontSize
|
|
||||||
textAnchor = self._getTextAnchor()
|
|
||||||
if textAnchor == 'start':
|
|
||||||
x = self._left
|
|
||||||
elif textAnchor == 'middle':
|
|
||||||
x = self._left + self._ewidth*0.5
|
|
||||||
else:
|
|
||||||
x = self._right
|
|
||||||
|
|
||||||
# paint box behind text just in case they
|
|
||||||
# fill it
|
|
||||||
if self.boxFillColor or (self.boxStrokeColor and self.boxStrokeWidth):
|
|
||||||
g.add(Rect( self._left-self.leftPadding,
|
|
||||||
self._bottom-self.bottomPadding,
|
|
||||||
self._width,
|
|
||||||
self._height,
|
|
||||||
strokeColor=self.boxStrokeColor,
|
|
||||||
strokeWidth=self.boxStrokeWidth,
|
|
||||||
fillColor=self.boxFillColor)
|
|
||||||
)
|
|
||||||
|
|
||||||
fillColor, fontName, fontSize = self.fillColor, self.fontName, self.fontSize
|
|
||||||
strokeColor, strokeWidth, leading = self.strokeColor, self.strokeWidth, (self.leading or 1.2*fontSize)
|
|
||||||
if strokeColor:
|
|
||||||
for line in self._lines:
|
|
||||||
s = _text2Path(line, x, y, fontName, fontSize, textAnchor)
|
|
||||||
s.fillColor = fillColor
|
|
||||||
s.strokeColor = strokeColor
|
|
||||||
s.strokeWidth = strokeWidth
|
|
||||||
g.add(s)
|
|
||||||
y = y - leading
|
|
||||||
else:
|
|
||||||
for line in self._lines:
|
|
||||||
s = String(x, y, line)
|
|
||||||
s.textAnchor = textAnchor
|
|
||||||
s.fontName = fontName
|
|
||||||
s.fontSize = fontSize
|
|
||||||
s.fillColor = fillColor
|
|
||||||
g.add(s)
|
|
||||||
y = y - leading
|
|
||||||
|
|
||||||
return g
|
|
||||||
|
|
||||||
class LabelDecorator:
|
|
||||||
_attrMap = AttrMap(
|
|
||||||
x = AttrMapValue(isNumberOrNone),
|
|
||||||
y = AttrMapValue(isNumberOrNone),
|
|
||||||
dx = AttrMapValue(isNumberOrNone),
|
|
||||||
dy = AttrMapValue(isNumberOrNone),
|
|
||||||
angle = AttrMapValue(isNumberOrNone),
|
|
||||||
boxAnchor = AttrMapValue(isBoxAnchor),
|
|
||||||
boxStrokeColor = AttrMapValue(isColorOrNone),
|
|
||||||
boxStrokeWidth = AttrMapValue(isNumberOrNone),
|
|
||||||
boxFillColor = AttrMapValue(isColorOrNone),
|
|
||||||
fillColor = AttrMapValue(isColorOrNone),
|
|
||||||
strokeColor = AttrMapValue(isColorOrNone),
|
|
||||||
strokeWidth = AttrMapValue(isNumberOrNone),
|
|
||||||
fontName = AttrMapValue(isNoneOrString),
|
|
||||||
fontSize = AttrMapValue(isNumberOrNone),
|
|
||||||
leading = AttrMapValue(isNumberOrNone),
|
|
||||||
width = AttrMapValue(isNumberOrNone),
|
|
||||||
maxWidth = AttrMapValue(isNumberOrNone),
|
|
||||||
height = AttrMapValue(isNumberOrNone),
|
|
||||||
textAnchor = AttrMapValue(isTextAnchor),
|
|
||||||
visible = AttrMapValue(isBoolean,desc="True if the label is to be drawn"),
|
|
||||||
)
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
self.textAnchor = 'start'
|
|
||||||
self.boxAnchor = 'w'
|
|
||||||
for a in self._attrMap.keys():
|
|
||||||
if not hasattr(self,a): setattr(self,a,None)
|
|
||||||
|
|
||||||
def decorate(self,l,L):
|
|
||||||
chart,g,rowNo,colNo,x,y,width,height,x00,y00,x0,y0 = l._callOutInfo
|
|
||||||
L.setText(chart.categoryAxis.categoryNames[colNo])
|
|
||||||
g.add(L)
|
|
||||||
|
|
||||||
def __call__(self,l):
|
|
||||||
from copy import deepcopy
|
|
||||||
L = Label()
|
|
||||||
for a,v in self.__dict__.items():
|
|
||||||
if v is None: v = getattr(l,a,None)
|
|
||||||
setattr(L,a,v)
|
|
||||||
self.decorate(l,L)
|
|
||||||
|
|
||||||
isOffsetMode=OneOf('high','low','bar','axis')
|
|
||||||
class LabelOffset(PropHolder):
|
|
||||||
_attrMap = AttrMap(
|
|
||||||
posMode = AttrMapValue(isOffsetMode,desc="Where to base +ve offset"),
|
|
||||||
pos = AttrMapValue(isNumber,desc='Value for positive elements'),
|
|
||||||
negMode = AttrMapValue(isOffsetMode,desc="Where to base -ve offset"),
|
|
||||||
neg = AttrMapValue(isNumber,desc='Value for negative elements'),
|
|
||||||
)
|
|
||||||
def __init__(self):
|
|
||||||
self.posMode=self.negMode='axis'
|
|
||||||
self.pos = self.neg = 0
|
|
||||||
|
|
||||||
def _getValue(self, chart, val):
|
|
||||||
flipXY = chart._flipXY
|
|
||||||
A = chart.categoryAxis
|
|
||||||
jA = A.joinAxis
|
|
||||||
if val>=0:
|
|
||||||
mode = self.posMode
|
|
||||||
delta = self.pos
|
|
||||||
else:
|
|
||||||
mode = self.negMode
|
|
||||||
delta = self.neg
|
|
||||||
if flipXY:
|
|
||||||
v = A._x
|
|
||||||
else:
|
|
||||||
v = A._y
|
|
||||||
if jA:
|
|
||||||
if flipXY:
|
|
||||||
_v = jA._x
|
|
||||||
else:
|
|
||||||
_v = jA._y
|
|
||||||
if mode=='high':
|
|
||||||
v = _v + jA._length
|
|
||||||
elif mode=='low':
|
|
||||||
v = _v
|
|
||||||
elif mode=='bar':
|
|
||||||
v = _v+val
|
|
||||||
return v+delta
|
|
||||||
|
|
||||||
NoneOrInstanceOfLabelOffset=NoneOr(isInstanceOf(LabelOffset))
|
|
||||||
|
|
||||||
class BarChartLabel(Label):
|
|
||||||
"""
|
|
||||||
An extended Label allowing for nudging, lines visibility etc
|
|
||||||
"""
|
|
||||||
_attrMap = AttrMap(
|
|
||||||
BASE=Label,
|
|
||||||
lineStrokeWidth = AttrMapValue(isNumberOrNone, desc="Non-zero for a drawn line"),
|
|
||||||
lineStrokeColor = AttrMapValue(isColorOrNone, desc="Color for a drawn line"),
|
|
||||||
fixedEnd = AttrMapValue(NoneOrInstanceOfLabelOffset, desc="None or fixed draw ends +/-"),
|
|
||||||
fixedStart = AttrMapValue(NoneOrInstanceOfLabelOffset, desc="None or fixed draw starts +/-"),
|
|
||||||
nudge = AttrMapValue(isNumber, desc="Non-zero sign dependent nudge"),
|
|
||||||
)
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
Label.__init__(self)
|
|
||||||
self.lineStrokeWidth = 0
|
|
||||||
self.lineStrokeColor = None
|
|
||||||
self.nudge = 0
|
|
||||||
self.fixedStart = self.fixedEnd = None
|
|
||||||
self._pmv = 0
|
|
||||||
|
|
||||||
def _getBoxAnchor(self):
|
|
||||||
a = self.boxAnchor
|
|
||||||
if self._pmv<0: a = {'nw':'se','n':'s','ne':'sw','w':'e','c':'c','e':'w','sw':'ne','s':'n','se':'nw'}[a]
|
|
||||||
return a
|
|
||||||
|
|
||||||
def _getTextAnchor(self):
|
|
||||||
a = self.textAnchor
|
|
||||||
if self._pmv<0: a = {'start':'end', 'middle':'middle', 'end':'start'}[a]
|
|
||||||
return a
|
|
||||||
|
|
||||||
class NA_Label(BarChartLabel):
|
|
||||||
"""
|
|
||||||
An extended Label allowing for nudging, lines visibility etc
|
|
||||||
"""
|
|
||||||
_attrMap = AttrMap(
|
|
||||||
BASE=BarChartLabel,
|
|
||||||
text = AttrMapValue(isNoneOrString, desc="Text to be used for N/A values"),
|
|
||||||
)
|
|
||||||
def __init__(self):
|
|
||||||
BarChartLabel.__init__(self)
|
|
||||||
self.text = 'n/a'
|
|
||||||
NoneOrInstanceOfNA_Label=NoneOr(isInstanceOf(NA_Label))
|
|
|
@ -1,191 +0,0 @@
|
||||||
#Copyright ReportLab Europe Ltd. 2000-2004
|
|
||||||
#see license.txt for license details
|
|
||||||
#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/graphics/charts/utils.py
|
|
||||||
"Utilities used here and there."
|
|
||||||
__version__=''' $Id: utils.py 2385 2004-06-17 15:26:05Z rgbecker $ '''
|
|
||||||
|
|
||||||
from time import mktime, gmtime, strftime
|
|
||||||
import string
|
|
||||||
|
|
||||||
|
|
||||||
### Dinu's stuff used in some line plots (likely to vansih).
|
|
||||||
|
|
||||||
def mkTimeTuple(timeString):
|
|
||||||
"Convert a 'dd/mm/yyyy' formatted string to a tuple for use in the time module."
|
|
||||||
|
|
||||||
list = [0] * 9
|
|
||||||
dd, mm, yyyy = map(int, string.split(timeString, '/'))
|
|
||||||
list[:3] = [yyyy, mm, dd]
|
|
||||||
|
|
||||||
return tuple(list)
|
|
||||||
|
|
||||||
|
|
||||||
def str2seconds(timeString):
|
|
||||||
"Convert a number of seconds since the epoch into a date string."
|
|
||||||
|
|
||||||
return mktime(mkTimeTuple(timeString))
|
|
||||||
|
|
||||||
|
|
||||||
def seconds2str(seconds):
|
|
||||||
"Convert a date string into the number of seconds since the epoch."
|
|
||||||
|
|
||||||
return strftime('%Y-%m-%d', gmtime(seconds))
|
|
||||||
|
|
||||||
|
|
||||||
### Aaron's rounding function for making nice values on axes.
|
|
||||||
|
|
||||||
from math import log10
|
|
||||||
|
|
||||||
def nextRoundNumber(x):
|
|
||||||
"""Return the first 'nice round number' greater than or equal to x
|
|
||||||
|
|
||||||
Used in selecting apropriate tick mark intervals; we say we want
|
|
||||||
an interval which places ticks at least 10 points apart, work out
|
|
||||||
what that is in chart space, and ask for the nextRoundNumber().
|
|
||||||
Tries the series 1,2,5,10,20,50,100.., going up or down as needed.
|
|
||||||
"""
|
|
||||||
|
|
||||||
#guess to nearest order of magnitude
|
|
||||||
if x in (0, 1):
|
|
||||||
return x
|
|
||||||
|
|
||||||
if x < 0:
|
|
||||||
return -1.0 * nextRoundNumber(-x)
|
|
||||||
else:
|
|
||||||
lg = int(log10(x))
|
|
||||||
|
|
||||||
if lg == 0:
|
|
||||||
if x < 1:
|
|
||||||
base = 0.1
|
|
||||||
else:
|
|
||||||
base = 1.0
|
|
||||||
elif lg < 0:
|
|
||||||
base = 10.0 ** (lg - 1)
|
|
||||||
else:
|
|
||||||
base = 10.0 ** lg # e.g. base(153) = 100
|
|
||||||
# base will always be lower than x
|
|
||||||
|
|
||||||
if base >= x:
|
|
||||||
return base * 1.0
|
|
||||||
elif (base * 2) >= x:
|
|
||||||
return base * 2.0
|
|
||||||
elif (base * 5) >= x:
|
|
||||||
return base * 5.0
|
|
||||||
else:
|
|
||||||
return base * 10.0
|
|
||||||
|
|
||||||
|
|
||||||
### Robin's stuff from rgb_ticks.
|
|
||||||
|
|
||||||
from math import log10, floor
|
|
||||||
|
|
||||||
_intervals=(.1, .2, .25, .5)
|
|
||||||
_j_max=len(_intervals)-1
|
|
||||||
|
|
||||||
|
|
||||||
def find_interval(lo,hi,I=5):
|
|
||||||
'determine tick parameters for range [lo, hi] using I intervals'
|
|
||||||
|
|
||||||
if lo >= hi:
|
|
||||||
if lo==hi:
|
|
||||||
if lo==0:
|
|
||||||
lo = -.1
|
|
||||||
hi = .1
|
|
||||||
else:
|
|
||||||
lo = 0.9*lo
|
|
||||||
hi = 1.1*hi
|
|
||||||
else:
|
|
||||||
raise ValueError, "lo>hi"
|
|
||||||
x=(hi - lo)/float(I)
|
|
||||||
b= (x>0 and (x<1 or x>10)) and 10**floor(log10(x)) or 1
|
|
||||||
b = b
|
|
||||||
while 1:
|
|
||||||
a = x/b
|
|
||||||
if a<=_intervals[-1]: break
|
|
||||||
b = b*10
|
|
||||||
|
|
||||||
j = 0
|
|
||||||
while a>_intervals[j]: j = j + 1
|
|
||||||
|
|
||||||
while 1:
|
|
||||||
ss = _intervals[j]*b
|
|
||||||
n = lo/ss
|
|
||||||
l = int(n)-(n<0)
|
|
||||||
n = ss*l
|
|
||||||
x = ss*(l+I)
|
|
||||||
a = I*ss
|
|
||||||
if n>0:
|
|
||||||
if a>=hi:
|
|
||||||
n = 0.0
|
|
||||||
x = a
|
|
||||||
elif hi<0:
|
|
||||||
a = -a
|
|
||||||
if lo>a:
|
|
||||||
n = a
|
|
||||||
x = 0
|
|
||||||
if hi<=x and n<=lo: break
|
|
||||||
j = j + 1
|
|
||||||
if j>_j_max:
|
|
||||||
j = 0
|
|
||||||
b = b*10
|
|
||||||
return n, x, ss, lo - n + x - hi
|
|
||||||
|
|
||||||
|
|
||||||
def find_good_grid(lower,upper,n=(4,5,6,7,8,9), grid=None):
|
|
||||||
if grid:
|
|
||||||
t = divmod(lower,grid)[0] * grid
|
|
||||||
hi, z = divmod(upper,grid)
|
|
||||||
if z>1e-8: hi = hi+1
|
|
||||||
hi = hi*grid
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
n[0]
|
|
||||||
except TypeError:
|
|
||||||
n = xrange(max(1,n-2),max(n+3,2))
|
|
||||||
|
|
||||||
w = 1e308
|
|
||||||
for i in n:
|
|
||||||
z=find_interval(lower,upper,i)
|
|
||||||
if z[3]<w:
|
|
||||||
t, hi, grid = z[:3]
|
|
||||||
w=z[3]
|
|
||||||
return t, hi, grid
|
|
||||||
|
|
||||||
|
|
||||||
def ticks(lower, upper, n=(4,5,6,7,8,9), split=1, percent=0, grid=None):
|
|
||||||
'''
|
|
||||||
return tick positions and labels for range lower<=x<=upper
|
|
||||||
n=number of intervals to try (can be a list or sequence)
|
|
||||||
split=1 return ticks then labels else (tick,label) pairs
|
|
||||||
'''
|
|
||||||
t, hi, grid = find_good_grid(lower, upper, n, grid)
|
|
||||||
power = floor(log10(grid))
|
|
||||||
if power==0: power = 1
|
|
||||||
w = grid/10.**power
|
|
||||||
w = int(w)!=w
|
|
||||||
|
|
||||||
if power > 3 or power < -3:
|
|
||||||
format = '%+'+`w+7`+'.0e'
|
|
||||||
else:
|
|
||||||
if power >= 0:
|
|
||||||
digits = int(power)+w
|
|
||||||
format = '%' + `digits`+'.0f'
|
|
||||||
else:
|
|
||||||
digits = w-int(power)
|
|
||||||
format = '%'+`digits+2`+'.'+`digits`+'f'
|
|
||||||
|
|
||||||
if percent: format=format+'%%'
|
|
||||||
T = []
|
|
||||||
n = int(float(hi-t)/grid+0.1)+1
|
|
||||||
if split:
|
|
||||||
labels = []
|
|
||||||
for i in xrange(n):
|
|
||||||
v = t+grid*i
|
|
||||||
T.append(v)
|
|
||||||
labels.append(format % v)
|
|
||||||
return T, labels
|
|
||||||
else:
|
|
||||||
for i in xrange(n):
|
|
||||||
v = t+grid*i
|
|
||||||
T.append((v, format % v))
|
|
||||||
return T
|
|
|
@ -1,233 +0,0 @@
|
||||||
from reportlab.lib import colors
|
|
||||||
from reportlab.lib.attrmap import *
|
|
||||||
from reportlab.pdfgen.canvas import Canvas
|
|
||||||
from reportlab.graphics.shapes import Group, Drawing, Ellipse, Wedge, String, STATE_DEFAULTS, Polygon, Line
|
|
||||||
|
|
||||||
def _getShaded(col,shd=None,shading=0.1):
|
|
||||||
if shd is None:
|
|
||||||
from reportlab.lib.colors import Blacker
|
|
||||||
if col: shd = Blacker(col,1-shading)
|
|
||||||
return shd
|
|
||||||
|
|
||||||
def _getLit(col,shd=None,lighting=0.1):
|
|
||||||
if shd is None:
|
|
||||||
from reportlab.lib.colors import Whiter
|
|
||||||
if col: shd = Whiter(col,1-lighting)
|
|
||||||
return shd
|
|
||||||
|
|
||||||
|
|
||||||
def _draw_3d_bar(G, x1, x2, y0, yhigh, xdepth, ydepth,
|
|
||||||
fillColor=None, fillColorShaded=None,
|
|
||||||
strokeColor=None, strokeWidth=1, shading=0.1):
|
|
||||||
fillColorShaded = _getShaded(fillColor,None,shading)
|
|
||||||
fillColorShadedTop = _getShaded(fillColor,None,shading/2.0)
|
|
||||||
|
|
||||||
def _add_3d_bar(x1, x2, y1, y2, xoff, yoff,
|
|
||||||
G=G,strokeColor=strokeColor, strokeWidth=strokeWidth, fillColor=fillColor):
|
|
||||||
G.add(Polygon((x1,y1, x1+xoff,y1+yoff, x2+xoff,y2+yoff, x2,y2),
|
|
||||||
strokeWidth=strokeWidth, strokeColor=strokeColor, fillColor=fillColor,strokeLineJoin=1))
|
|
||||||
|
|
||||||
usd = max(y0, yhigh)
|
|
||||||
if xdepth or ydepth:
|
|
||||||
if y0!=yhigh: #non-zero height
|
|
||||||
_add_3d_bar( x2, x2, y0, yhigh, xdepth, ydepth, fillColor=fillColorShaded) #side
|
|
||||||
|
|
||||||
_add_3d_bar(x1, x2, usd, usd, xdepth, ydepth, fillColor=fillColorShadedTop) #top
|
|
||||||
|
|
||||||
G.add(Polygon((x1,y0,x2,y0,x2,yhigh,x1,yhigh),
|
|
||||||
strokeColor=strokeColor, strokeWidth=strokeWidth, fillColor=fillColor,strokeLineJoin=1)) #front
|
|
||||||
|
|
||||||
if xdepth or ydepth:
|
|
||||||
G.add(Line( x1, usd, x2, usd, strokeWidth=strokeWidth, strokeColor=strokeColor or fillColorShaded))
|
|
||||||
|
|
||||||
class _YStrip:
|
|
||||||
def __init__(self,y0,y1, slope, fillColor, fillColorShaded, shading=0.1):
|
|
||||||
self.y0 = y0
|
|
||||||
self.y1 = y1
|
|
||||||
self.slope = slope
|
|
||||||
self.fillColor = fillColor
|
|
||||||
self.fillColorShaded = _getShaded(fillColor,fillColorShaded,shading)
|
|
||||||
|
|
||||||
def _ystrip_poly( x0, x1, y0, y1, xoff, yoff):
|
|
||||||
return [x0,y0,x0+xoff,y0+yoff,x1+xoff,y1+yoff,x1,y1]
|
|
||||||
|
|
||||||
|
|
||||||
def _make_3d_line_info( G, x0, x1, y0, y1, z0, z1,
|
|
||||||
theta_x, theta_y,
|
|
||||||
fillColor, fillColorShaded=None, tileWidth=1,
|
|
||||||
strokeColor=None, strokeWidth=None, strokeDashArray=None,
|
|
||||||
shading=0.1):
|
|
||||||
zwidth = abs(z1-z0)
|
|
||||||
xdepth = zwidth*theta_x
|
|
||||||
ydepth = zwidth*theta_y
|
|
||||||
depth_slope = xdepth==0 and 1e150 or -ydepth/float(xdepth)
|
|
||||||
|
|
||||||
x = float(x1-x0)
|
|
||||||
slope = x==0 and 1e150 or (y1-y0)/x
|
|
||||||
|
|
||||||
c = slope>depth_slope and _getShaded(fillColor,fillColorShaded,shading) or fillColor
|
|
||||||
zy0 = z0*theta_y
|
|
||||||
zx0 = z0*theta_x
|
|
||||||
|
|
||||||
tileStrokeWidth = 0.6
|
|
||||||
if tileWidth is None:
|
|
||||||
D = [(x1,y1)]
|
|
||||||
else:
|
|
||||||
T = ((y1-y0)**2+(x1-x0)**2)**0.5
|
|
||||||
tileStrokeWidth *= tileWidth
|
|
||||||
if T<tileWidth:
|
|
||||||
D = [(x1,y1)]
|
|
||||||
else:
|
|
||||||
n = int(T/float(tileWidth))+1
|
|
||||||
dx = float(x1-x0)/n
|
|
||||||
dy = float(y1-y0)/n
|
|
||||||
D = []
|
|
||||||
a = D.append
|
|
||||||
for i in xrange(1,n):
|
|
||||||
a((x0+dx*i,y0+dy*i))
|
|
||||||
|
|
||||||
a = G.add
|
|
||||||
x_0 = x0+zx0
|
|
||||||
y_0 = y0+zy0
|
|
||||||
for x,y in D:
|
|
||||||
x_1 = x+zx0
|
|
||||||
y_1 = y+zy0
|
|
||||||
P = Polygon(_ystrip_poly(x_0, x_1, y_0, y_1, xdepth, ydepth),
|
|
||||||
fillColor = c, strokeColor=c, strokeWidth=tileStrokeWidth)
|
|
||||||
a((0,z0,z1,x_0,y_0,P))
|
|
||||||
x_0 = x_1
|
|
||||||
y_0 = y_1
|
|
||||||
|
|
||||||
from math import pi, sin, cos
|
|
||||||
_pi_2 = pi*0.5
|
|
||||||
_2pi = 2*pi
|
|
||||||
_180_pi=180./pi
|
|
||||||
|
|
||||||
def _2rad(angle):
|
|
||||||
return angle/_180_pi
|
|
||||||
|
|
||||||
def mod_2pi(radians):
|
|
||||||
radians = radians % _2pi
|
|
||||||
if radians<-1e-6: radians += _2pi
|
|
||||||
return radians
|
|
||||||
|
|
||||||
def _2deg(o):
|
|
||||||
return o*_180_pi
|
|
||||||
|
|
||||||
def _360(a):
|
|
||||||
a %= 360
|
|
||||||
if a<-1e-6: a += 360
|
|
||||||
return a
|
|
||||||
|
|
||||||
_ZERO = 1e-8
|
|
||||||
_ONE = 1-_ZERO
|
|
||||||
class _Segment:
|
|
||||||
def __init__(self,s,i,data):
|
|
||||||
S = data[s]
|
|
||||||
x0 = S[i-1][0]
|
|
||||||
y0 = S[i-1][1]
|
|
||||||
x1 = S[i][0]
|
|
||||||
y1 = S[i][1]
|
|
||||||
if x1<x0:
|
|
||||||
x0,y0,x1,y1 = x1,y1,x0,y0
|
|
||||||
# (y-y0)*(x1-x0) = (y1-y0)*(x-x0)
|
|
||||||
# (x1-x0)*y + (y0-y1)*x = y0*(x1-x0)+x0*(y0-y1)
|
|
||||||
# a*y+b*x = c
|
|
||||||
self.a = float(x1-x0)
|
|
||||||
self.b = float(y1-y0)
|
|
||||||
self.x0 = x0
|
|
||||||
self.x1 = x1
|
|
||||||
self.y0 = y0
|
|
||||||
self.y1 = y1
|
|
||||||
self.series = s
|
|
||||||
self.i = i
|
|
||||||
self.s = s
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return '[(%s,%s),(%s,%s)]' % (self.x0,self.y0,self.x1,self.y1)
|
|
||||||
|
|
||||||
__repr__ = __str__
|
|
||||||
|
|
||||||
def intersect(self,o,I):
|
|
||||||
'''try to find an intersection with _Segment o
|
|
||||||
'''
|
|
||||||
x0 = self.x0
|
|
||||||
ox0 = o.x0
|
|
||||||
assert x0<=ox0
|
|
||||||
if ox0>self.x1: return 1
|
|
||||||
if o.s==self.s and o.i in (self.i-1,self.i+1): return
|
|
||||||
a = self.a
|
|
||||||
b = self.b
|
|
||||||
oa = o.a
|
|
||||||
ob = o.b
|
|
||||||
det = ob*a - oa*b
|
|
||||||
if -1e-8<det<1e-8: return
|
|
||||||
dx = x0 - ox0
|
|
||||||
dy = self.y0 - o.y0
|
|
||||||
u = (oa*dy - ob*dx)/det
|
|
||||||
ou = (a*dy - b*dx)/det
|
|
||||||
if u<0 or u>1 or ou<0 or ou>1: return
|
|
||||||
x = x0 + u*a
|
|
||||||
y = self.y0 + u*b
|
|
||||||
if _ZERO<u<_ONE:
|
|
||||||
t = self.s,self.i,x,y
|
|
||||||
if t not in I: I.append(t)
|
|
||||||
if _ZERO<ou<_ONE:
|
|
||||||
t = o.s,o.i,x,y
|
|
||||||
if t not in I: I.append(t)
|
|
||||||
|
|
||||||
def _segCmp(a,b):
|
|
||||||
return cmp((a.x0,a.x1,a.y0,a.y1,a.s,a.i),(b.x0,b.x1,b.y0,b.y1,b.s,b.i))
|
|
||||||
|
|
||||||
def find_intersections(data,small=0):
|
|
||||||
'''
|
|
||||||
data is a sequence of series
|
|
||||||
each series is a list of (x,y) coordinates
|
|
||||||
where x & y are ints or floats
|
|
||||||
|
|
||||||
find_intersections returns a sequence of 4-tuples
|
|
||||||
i, j, x, y
|
|
||||||
|
|
||||||
where i is a data index j is an insertion position for data[i]
|
|
||||||
and x, y are coordinates of an intersection of series data[i]
|
|
||||||
with some other series. If correctly implemented we get all such
|
|
||||||
intersections. We don't count endpoint intersections and consider
|
|
||||||
parallel lines as non intersecting (even when coincident).
|
|
||||||
We ignore segments that have an estimated size less than small.
|
|
||||||
'''
|
|
||||||
|
|
||||||
#find all line segments
|
|
||||||
S = []
|
|
||||||
a = S.append
|
|
||||||
for s in xrange(len(data)):
|
|
||||||
ds = data[s]
|
|
||||||
if not ds: continue
|
|
||||||
n = len(ds)
|
|
||||||
if n==1: continue
|
|
||||||
for i in xrange(1,n):
|
|
||||||
seg = _Segment(s,i,data)
|
|
||||||
if seg.a+abs(seg.b)>=small: a(seg)
|
|
||||||
S.sort(_segCmp)
|
|
||||||
I = []
|
|
||||||
n = len(S)
|
|
||||||
for i in xrange(0,n-1):
|
|
||||||
s = S[i]
|
|
||||||
for j in xrange(i+1,n):
|
|
||||||
if s.intersect(S[j],I)==1: break
|
|
||||||
I.sort()
|
|
||||||
return I
|
|
||||||
|
|
||||||
if __name__=='__main__':
|
|
||||||
from reportlab.graphics.shapes import Drawing
|
|
||||||
from reportlab.lib.colors import lightgrey, pink
|
|
||||||
D = Drawing(300,200)
|
|
||||||
_draw_3d_bar(D, 10, 20, 10, 50, 5, 5, fillColor=lightgrey, strokeColor=pink)
|
|
||||||
_draw_3d_bar(D, 30, 40, 10, 45, 5, 5, fillColor=lightgrey, strokeColor=pink)
|
|
||||||
|
|
||||||
D.save(formats=['pdf'],outDir='.',fnRoot='_draw_3d_bar')
|
|
||||||
|
|
||||||
print find_intersections([[(0,0.5),(1,0.5),(0.5,0),(0.5,1)],[(.2666666667,0.4),(0.1,0.4),(0.1,0.2),(0,0),(1,1)],[(0,1),(0.4,0.1),(1,0.1)]])
|
|
||||||
print find_intersections([[(0.1, 0.2), (0.1, 0.4)], [(0, 1), (0.4, 0.1)]])
|
|
||||||
print find_intersections([[(0.2, 0.4), (0.1, 0.4)], [(0.1, 0.8), (0.4, 0.1)]])
|
|
||||||
print find_intersections([[(0,0),(1,1)],[(0.4,0.1),(1,0.1)]])
|
|
||||||
print find_intersections([[(0,0.5),(1,0.5),(0.5,0),(0.5,1)],[(0,0),(1,1)],[(0.1,0.8),(0.4,0.1),(1,0.1)]])
|
|
|
@ -1,360 +0,0 @@
|
||||||
#Copyright ReportLab Europe Ltd. 2000-2004
|
|
||||||
#see license.txt for license details
|
|
||||||
#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/graphics/renderPDF.py
|
|
||||||
# renderPDF - draws Drawings onto a canvas
|
|
||||||
"""Usage:
|
|
||||||
import renderpdf
|
|
||||||
renderpdf.draw(drawing, canvas, x, y)
|
|
||||||
Execute the script to see some test drawings.
|
|
||||||
changed
|
|
||||||
"""
|
|
||||||
__version__=''' $Id: renderPDF.py 2830 2006-04-05 15:18:32Z rgbecker $ '''
|
|
||||||
|
|
||||||
from reportlab.graphics.shapes import *
|
|
||||||
from reportlab.pdfgen.canvas import Canvas
|
|
||||||
from reportlab.pdfbase.pdfmetrics import stringWidth
|
|
||||||
from reportlab.lib.utils import getStringIO
|
|
||||||
from reportlab import rl_config
|
|
||||||
from renderbase import Renderer, StateTracker, getStateDelta, renderScaledDrawing
|
|
||||||
|
|
||||||
# the main entry point for users...
|
|
||||||
def draw(drawing, canvas, x, y, showBoundary=rl_config._unset_):
|
|
||||||
"""As it says"""
|
|
||||||
R = _PDFRenderer()
|
|
||||||
R.draw(renderScaledDrawing(drawing), canvas, x, y, showBoundary=showBoundary)
|
|
||||||
|
|
||||||
class _PDFRenderer(Renderer):
|
|
||||||
"""This draws onto a PDF document. It needs to be a class
|
|
||||||
rather than a function, as some PDF-specific state tracking is
|
|
||||||
needed outside of the state info in the SVG model."""
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
self._stroke = 0
|
|
||||||
self._fill = 0
|
|
||||||
self._tracker = StateTracker()
|
|
||||||
|
|
||||||
def drawNode(self, node):
|
|
||||||
"""This is the recursive method called for each node
|
|
||||||
in the tree"""
|
|
||||||
#print "pdf:drawNode", self
|
|
||||||
#if node.__class__ is Wedge: stop
|
|
||||||
if not (isinstance(node, Path) and node.isClipPath):
|
|
||||||
self._canvas.saveState()
|
|
||||||
|
|
||||||
#apply state changes
|
|
||||||
deltas = getStateDelta(node)
|
|
||||||
self._tracker.push(deltas)
|
|
||||||
self.applyStateChanges(deltas, {})
|
|
||||||
|
|
||||||
#draw the object, or recurse
|
|
||||||
self.drawNodeDispatcher(node)
|
|
||||||
|
|
||||||
self._tracker.pop()
|
|
||||||
if not (isinstance(node, Path) and node.isClipPath):
|
|
||||||
self._canvas.restoreState()
|
|
||||||
|
|
||||||
def drawRect(self, rect):
|
|
||||||
if rect.rx == rect.ry == 0:
|
|
||||||
#plain old rectangle
|
|
||||||
self._canvas.rect(
|
|
||||||
rect.x, rect.y,
|
|
||||||
rect.width, rect.height,
|
|
||||||
stroke=self._stroke,
|
|
||||||
fill=self._fill
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
#cheat and assume ry = rx; better to generalize
|
|
||||||
#pdfgen roundRect function. TODO
|
|
||||||
self._canvas.roundRect(
|
|
||||||
rect.x, rect.y,
|
|
||||||
rect.width, rect.height, rect.rx,
|
|
||||||
fill=self._fill,
|
|
||||||
stroke=self._stroke
|
|
||||||
)
|
|
||||||
|
|
||||||
def drawImage(self, image):
|
|
||||||
# currently not implemented in other renderers
|
|
||||||
if image.path and os.path.exists(image.path):
|
|
||||||
self._canvas.drawInlineImage(
|
|
||||||
image.path,
|
|
||||||
image.x, image.y,
|
|
||||||
image.width, image.height
|
|
||||||
)
|
|
||||||
|
|
||||||
def drawLine(self, line):
|
|
||||||
if self._stroke:
|
|
||||||
self._canvas.line(line.x1, line.y1, line.x2, line.y2)
|
|
||||||
|
|
||||||
def drawCircle(self, circle):
|
|
||||||
self._canvas.circle(
|
|
||||||
circle.cx, circle.cy, circle.r,
|
|
||||||
fill=self._fill,
|
|
||||||
stroke=self._stroke
|
|
||||||
)
|
|
||||||
|
|
||||||
def drawPolyLine(self, polyline):
|
|
||||||
if self._stroke:
|
|
||||||
assert len(polyline.points) >= 2, 'Polyline must have 2 or more points'
|
|
||||||
head, tail = polyline.points[0:2], polyline.points[2:],
|
|
||||||
path = self._canvas.beginPath()
|
|
||||||
path.moveTo(head[0], head[1])
|
|
||||||
for i in range(0, len(tail), 2):
|
|
||||||
path.lineTo(tail[i], tail[i+1])
|
|
||||||
self._canvas.drawPath(path)
|
|
||||||
|
|
||||||
def drawWedge(self, wedge):
|
|
||||||
centerx, centery, radius, startangledegrees, endangledegrees = \
|
|
||||||
wedge.centerx, wedge.centery, wedge.radius, wedge.startangledegrees, wedge.endangledegrees
|
|
||||||
yradius, radius1, yradius1 = wedge._xtraRadii()
|
|
||||||
if yradius is None: yradius = radius
|
|
||||||
angle = endangledegrees-startangledegrees
|
|
||||||
path = self._canvas.beginPath()
|
|
||||||
if (radius1==0 or radius1 is None) and (yradius1==0 or yradius1 is None):
|
|
||||||
path.moveTo(centerx, centery)
|
|
||||||
path.arcTo(centerx-radius, centery-yradius, centerx+radius, centery+yradius,
|
|
||||||
startangledegrees, angle)
|
|
||||||
else:
|
|
||||||
path.arc(centerx-radius, centery-yradius, centerx+radius, centery+yradius,
|
|
||||||
startangledegrees, angle)
|
|
||||||
path.arcTo(centerx-radius1, centery-yradius1, centerx+radius1, centery+yradius1,
|
|
||||||
endangledegrees, -angle)
|
|
||||||
path.close()
|
|
||||||
self._canvas.drawPath(path,
|
|
||||||
fill=self._fill,
|
|
||||||
stroke=self._stroke)
|
|
||||||
|
|
||||||
def drawEllipse(self, ellipse):
|
|
||||||
#need to convert to pdfgen's bounding box representation
|
|
||||||
x1 = ellipse.cx - ellipse.rx
|
|
||||||
x2 = ellipse.cx + ellipse.rx
|
|
||||||
y1 = ellipse.cy - ellipse.ry
|
|
||||||
y2 = ellipse.cy + ellipse.ry
|
|
||||||
self._canvas.ellipse(x1,y1,x2,y2,fill=self._fill,stroke=self._stroke)
|
|
||||||
|
|
||||||
def drawPolygon(self, polygon):
|
|
||||||
assert len(polygon.points) >= 2, 'Polyline must have 2 or more points'
|
|
||||||
head, tail = polygon.points[0:2], polygon.points[2:],
|
|
||||||
path = self._canvas.beginPath()
|
|
||||||
path.moveTo(head[0], head[1])
|
|
||||||
for i in range(0, len(tail), 2):
|
|
||||||
path.lineTo(tail[i], tail[i+1])
|
|
||||||
path.close()
|
|
||||||
self._canvas.drawPath(
|
|
||||||
path,
|
|
||||||
stroke=self._stroke,
|
|
||||||
fill=self._fill
|
|
||||||
)
|
|
||||||
|
|
||||||
def drawString(self, stringObj):
|
|
||||||
if self._fill:
|
|
||||||
S = self._tracker.getState()
|
|
||||||
text_anchor, x, y, text, enc = S['textAnchor'], stringObj.x,stringObj.y,stringObj.text, stringObj.encoding
|
|
||||||
if not text_anchor in ['start','inherited']:
|
|
||||||
font, font_size = S['fontName'], S['fontSize']
|
|
||||||
textLen = stringWidth(text, font, font_size, enc)
|
|
||||||
if text_anchor=='end':
|
|
||||||
x = x-textLen
|
|
||||||
elif text_anchor=='middle':
|
|
||||||
x = x - textLen/2
|
|
||||||
else:
|
|
||||||
raise ValueError, 'bad value for textAnchor '+str(text_anchor)
|
|
||||||
t = self._canvas.beginText(x,y)
|
|
||||||
t.textLine(text)
|
|
||||||
self._canvas.drawText(t)
|
|
||||||
|
|
||||||
def drawPath(self, path):
|
|
||||||
from reportlab.graphics.shapes import _renderPath
|
|
||||||
pdfPath = self._canvas.beginPath()
|
|
||||||
drawFuncs = (pdfPath.moveTo, pdfPath.lineTo, pdfPath.curveTo, pdfPath.close)
|
|
||||||
isClosed = _renderPath(path, drawFuncs)
|
|
||||||
if isClosed:
|
|
||||||
fill = self._fill
|
|
||||||
else:
|
|
||||||
fill = 0
|
|
||||||
if path.isClipPath:
|
|
||||||
self._canvas.clipPath(pdfPath, fill=fill, stroke=self._stroke)
|
|
||||||
else:
|
|
||||||
self._canvas.drawPath(pdfPath,
|
|
||||||
fill=fill,
|
|
||||||
stroke=self._stroke)
|
|
||||||
|
|
||||||
def applyStateChanges(self, delta, newState):
|
|
||||||
"""This takes a set of states, and outputs the PDF operators
|
|
||||||
needed to set those properties"""
|
|
||||||
for key, value in delta.items():
|
|
||||||
if key == 'transform':
|
|
||||||
self._canvas.transform(value[0], value[1], value[2],
|
|
||||||
value[3], value[4], value[5])
|
|
||||||
elif key == 'strokeColor':
|
|
||||||
#this has different semantics in PDF to SVG;
|
|
||||||
#we always have a color, and either do or do
|
|
||||||
#not apply it; in SVG one can have a 'None' color
|
|
||||||
if value is None:
|
|
||||||
self._stroke = 0
|
|
||||||
else:
|
|
||||||
self._stroke = 1
|
|
||||||
self._canvas.setStrokeColor(value)
|
|
||||||
elif key == 'strokeWidth':
|
|
||||||
self._canvas.setLineWidth(value)
|
|
||||||
elif key == 'strokeLineCap': #0,1,2
|
|
||||||
self._canvas.setLineCap(value)
|
|
||||||
elif key == 'strokeLineJoin':
|
|
||||||
self._canvas.setLineJoin(value)
|
|
||||||
# elif key == 'stroke_dasharray':
|
|
||||||
# self._canvas.setDash(array=value)
|
|
||||||
elif key == 'strokeDashArray':
|
|
||||||
if value:
|
|
||||||
self._canvas.setDash(value)
|
|
||||||
else:
|
|
||||||
self._canvas.setDash()
|
|
||||||
elif key == 'fillColor':
|
|
||||||
#this has different semantics in PDF to SVG;
|
|
||||||
#we always have a color, and either do or do
|
|
||||||
#not apply it; in SVG one can have a 'None' color
|
|
||||||
if value is None:
|
|
||||||
self._fill = 0
|
|
||||||
else:
|
|
||||||
self._fill = 1
|
|
||||||
self._canvas.setFillColor(value)
|
|
||||||
elif key in ['fontSize', 'fontName']:
|
|
||||||
# both need setting together in PDF
|
|
||||||
# one or both might be in the deltas,
|
|
||||||
# so need to get whichever is missing
|
|
||||||
fontname = delta.get('fontName', self._canvas._fontname)
|
|
||||||
fontsize = delta.get('fontSize', self._canvas._fontsize)
|
|
||||||
self._canvas.setFont(fontname, fontsize)
|
|
||||||
|
|
||||||
from reportlab.platypus import Flowable
|
|
||||||
class GraphicsFlowable(Flowable):
|
|
||||||
"""Flowable wrapper around a Pingo drawing"""
|
|
||||||
def __init__(self, drawing):
|
|
||||||
self.drawing = drawing
|
|
||||||
self.width = self.drawing.width
|
|
||||||
self.height = self.drawing.height
|
|
||||||
|
|
||||||
def draw(self):
|
|
||||||
draw(self.drawing, self.canv, 0, 0)
|
|
||||||
|
|
||||||
def drawToFile(d, fn, msg="", showBoundary=rl_config._unset_, autoSize=1):
|
|
||||||
"""Makes a one-page PDF with just the drawing.
|
|
||||||
|
|
||||||
If autoSize=1, the PDF will be the same size as
|
|
||||||
the drawing; if 0, it will place the drawing on
|
|
||||||
an A4 page with a title above it - possibly overflowing
|
|
||||||
if too big."""
|
|
||||||
d = renderScaledDrawing(d)
|
|
||||||
c = Canvas(fn)
|
|
||||||
c.setFont('Times-Roman', 36)
|
|
||||||
c.drawString(80, 750, msg)
|
|
||||||
c.setTitle(msg)
|
|
||||||
|
|
||||||
if autoSize:
|
|
||||||
c.setPageSize((d.width, d.height))
|
|
||||||
draw(d, c, 0, 0, showBoundary=showBoundary)
|
|
||||||
else:
|
|
||||||
#show with a title
|
|
||||||
c.setFont('Times-Roman', 12)
|
|
||||||
y = 740
|
|
||||||
i = 1
|
|
||||||
y = y - d.height
|
|
||||||
draw(d, c, 80, y, showBoundary=showBoundary)
|
|
||||||
|
|
||||||
c.showPage()
|
|
||||||
c.save()
|
|
||||||
if sys.platform=='mac' and not hasattr(fn, "write"):
|
|
||||||
try:
|
|
||||||
import macfs, macostools
|
|
||||||
macfs.FSSpec(fn).SetCreatorType("CARO", "PDF ")
|
|
||||||
macostools.touched(fn)
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def drawToString(d, msg="", showBoundary=rl_config._unset_,autoSize=1):
|
|
||||||
"Returns a PDF as a string in memory, without touching the disk"
|
|
||||||
s = getStringIO()
|
|
||||||
drawToFile(d, s, msg=msg, showBoundary=showBoundary,autoSize=autoSize)
|
|
||||||
return s.getvalue()
|
|
||||||
|
|
||||||
|
|
||||||
#########################################################
|
|
||||||
#
|
|
||||||
# test code. First, defin a bunch of drawings.
|
|
||||||
# Routine to draw them comes at the end.
|
|
||||||
#
|
|
||||||
#########################################################
|
|
||||||
|
|
||||||
|
|
||||||
def test():
|
|
||||||
c = Canvas('renderPDF.pdf')
|
|
||||||
c.setFont('Times-Roman', 36)
|
|
||||||
c.drawString(80, 750, 'Graphics Test')
|
|
||||||
|
|
||||||
# print all drawings and their doc strings from the test
|
|
||||||
# file
|
|
||||||
|
|
||||||
#grab all drawings from the test module
|
|
||||||
from reportlab.graphics import testshapes
|
|
||||||
drawings = []
|
|
||||||
for funcname in dir(testshapes):
|
|
||||||
if funcname[0:10] == 'getDrawing':
|
|
||||||
drawing = eval('testshapes.' + funcname + '()') #execute it
|
|
||||||
docstring = eval('testshapes.' + funcname + '.__doc__')
|
|
||||||
drawings.append((drawing, docstring))
|
|
||||||
|
|
||||||
#print in a loop, with their doc strings
|
|
||||||
c.setFont('Times-Roman', 12)
|
|
||||||
y = 740
|
|
||||||
i = 1
|
|
||||||
for (drawing, docstring) in drawings:
|
|
||||||
assert (docstring is not None), "Drawing %d has no docstring!" % i
|
|
||||||
if y < 300: #allows 5-6 lines of text
|
|
||||||
c.showPage()
|
|
||||||
y = 740
|
|
||||||
# draw a title
|
|
||||||
y = y - 30
|
|
||||||
c.setFont('Times-BoldItalic',12)
|
|
||||||
c.drawString(80, y, 'Drawing %d' % i)
|
|
||||||
c.setFont('Times-Roman',12)
|
|
||||||
y = y - 14
|
|
||||||
textObj = c.beginText(80, y)
|
|
||||||
textObj.textLines(docstring)
|
|
||||||
c.drawText(textObj)
|
|
||||||
y = textObj.getY()
|
|
||||||
y = y - drawing.height
|
|
||||||
draw(drawing, c, 80, y)
|
|
||||||
i = i + 1
|
|
||||||
if y!=740: c.showPage()
|
|
||||||
|
|
||||||
c.save()
|
|
||||||
print 'saved renderPDF.pdf'
|
|
||||||
|
|
||||||
##def testFlowable():
|
|
||||||
## """Makes a platypus document"""
|
|
||||||
## from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer
|
|
||||||
## from reportlab.lib.styles import getSampleStyleSheet
|
|
||||||
## styles = getSampleStyleSheet()
|
|
||||||
## styNormal = styles['Normal']
|
|
||||||
##
|
|
||||||
## doc = SimpleDocTemplate('test_flowable.pdf')
|
|
||||||
## story = []
|
|
||||||
## story.append(Paragraph("This sees is a drawing can work as a flowable", styNormal))
|
|
||||||
##
|
|
||||||
## import testdrawings
|
|
||||||
## drawings = []
|
|
||||||
##
|
|
||||||
## for funcname in dir(testdrawings):
|
|
||||||
## if funcname[0:10] == 'getDrawing':
|
|
||||||
## drawing = eval('testdrawings.' + funcname + '()') #execute it
|
|
||||||
## docstring = eval('testdrawings.' + funcname + '.__doc__')
|
|
||||||
## story.append(Paragraph(docstring, styNormal))
|
|
||||||
## story.append(Spacer(18,18))
|
|
||||||
## story.append(drawing)
|
|
||||||
## story.append(Spacer(36,36))
|
|
||||||
##
|
|
||||||
## doc.build(story)
|
|
||||||
## print 'saves test_flowable.pdf'
|
|
||||||
|
|
||||||
if __name__=='__main__':
|
|
||||||
test()
|
|
||||||
#testFlowable()
|
|
|
@ -1,663 +0,0 @@
|
||||||
#Copyright ReportLab Europe Ltd. 2000-2004
|
|
||||||
#see license.txt for license details
|
|
||||||
#history www.reportlab.co.uk/rl-cgi/viewcvs.cgi/rlextra/graphics/Csrc/renderPM/renderP.py
|
|
||||||
__version__=''' $Id: renderPM.py 2830 2006-04-05 15:18:32Z rgbecker $ '''
|
|
||||||
"""Usage:
|
|
||||||
from reportlab.graphics import renderPM
|
|
||||||
renderPM.drawToFile(drawing,filename,fmt='GIF',configPIL={....})
|
|
||||||
Other functions let you create a PM drawing as string or into a PM buffer.
|
|
||||||
Execute the script to see some test drawings."""
|
|
||||||
|
|
||||||
from reportlab.graphics.shapes import *
|
|
||||||
from reportlab.graphics.renderbase import StateTracker, getStateDelta, renderScaledDrawing
|
|
||||||
from reportlab.pdfbase.pdfmetrics import getFont, unicode2T1
|
|
||||||
from math import sin, cos, pi, ceil
|
|
||||||
from reportlab.lib.utils import getStringIO, open_and_read
|
|
||||||
from reportlab import rl_config
|
|
||||||
|
|
||||||
class RenderPMError(Exception):
|
|
||||||
pass
|
|
||||||
|
|
||||||
import string, os, sys
|
|
||||||
|
|
||||||
try:
|
|
||||||
import _renderPM
|
|
||||||
except ImportError, errMsg:
|
|
||||||
raise ImportError, "No module named _renderPM\n" + \
|
|
||||||
(str(errMsg)!='No module named _renderPM' and "it may be the wrong version or badly installed!" or
|
|
||||||
"see http://www.reportlab.org/rl_addons.html")
|
|
||||||
|
|
||||||
from types import TupleType, ListType
|
|
||||||
_SeqTypes = (TupleType,ListType)
|
|
||||||
|
|
||||||
def _getImage():
|
|
||||||
try:
|
|
||||||
from PIL import Image
|
|
||||||
except ImportError:
|
|
||||||
import Image
|
|
||||||
return Image
|
|
||||||
|
|
||||||
def Color2Hex(c):
|
|
||||||
#assert isinstance(colorobj, colors.Color) #these checks don't work well RGB
|
|
||||||
if c: return ((0xFF&int(255*c.red)) << 16) | ((0xFF&int(255*c.green)) << 8) | (0xFF&int(255*c.blue))
|
|
||||||
return c
|
|
||||||
|
|
||||||
# the main entry point for users...
|
|
||||||
def draw(drawing, canvas, x, y, showBoundary=rl_config._unset_):
|
|
||||||
"""As it says"""
|
|
||||||
R = _PMRenderer()
|
|
||||||
R.draw(renderScaledDrawing(drawing), canvas, x, y, showBoundary=showBoundary)
|
|
||||||
|
|
||||||
from reportlab.graphics.renderbase import Renderer
|
|
||||||
class _PMRenderer(Renderer):
|
|
||||||
"""This draws onto a pix map image. It needs to be a class
|
|
||||||
rather than a function, as some image-specific state tracking is
|
|
||||||
needed outside of the state info in the SVG model."""
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
self._tracker = StateTracker()
|
|
||||||
|
|
||||||
def pop(self):
|
|
||||||
self._tracker.pop()
|
|
||||||
self.applyState()
|
|
||||||
|
|
||||||
def push(self,node):
|
|
||||||
deltas = getStateDelta(node)
|
|
||||||
self._tracker.push(deltas)
|
|
||||||
self.applyState()
|
|
||||||
|
|
||||||
def applyState(self):
|
|
||||||
s = self._tracker.getState()
|
|
||||||
self._canvas.ctm = s['ctm']
|
|
||||||
self._canvas.strokeWidth = s['strokeWidth']
|
|
||||||
self._canvas.strokeColor = Color2Hex(s['strokeColor'])
|
|
||||||
self._canvas.lineCap = s['strokeLineCap']
|
|
||||||
self._canvas.lineJoin = s['strokeLineJoin']
|
|
||||||
da = s['strokeDashArray']
|
|
||||||
da = da and (0,da) or None
|
|
||||||
self._canvas.dashArray = da
|
|
||||||
self._canvas.fillColor = Color2Hex(s['fillColor'])
|
|
||||||
self._canvas.setFont(s['fontName'], s['fontSize'])
|
|
||||||
|
|
||||||
def initState(self,x,y):
|
|
||||||
deltas = STATE_DEFAULTS.copy()
|
|
||||||
deltas['transform'] = self._canvas._baseCTM[0:4]+(x,y)
|
|
||||||
self._tracker.push(deltas)
|
|
||||||
self.applyState()
|
|
||||||
|
|
||||||
def drawNode(self, node):
|
|
||||||
"""This is the recursive method called for each node
|
|
||||||
in the tree"""
|
|
||||||
|
|
||||||
#apply state changes
|
|
||||||
self.push(node)
|
|
||||||
|
|
||||||
#draw the object, or recurse
|
|
||||||
self.drawNodeDispatcher(node)
|
|
||||||
|
|
||||||
# restore the state
|
|
||||||
self.pop()
|
|
||||||
|
|
||||||
def drawRect(self, rect):
|
|
||||||
c = self._canvas
|
|
||||||
if rect.rx == rect.ry == 0:
|
|
||||||
#plain old rectangle, draw clockwise (x-axis to y-axis) direction
|
|
||||||
c.rect(rect.x,rect.y, rect.width, rect.height)
|
|
||||||
else:
|
|
||||||
c.roundRect(rect.x,rect.y, rect.width, rect.height, rect.rx, rect.ry)
|
|
||||||
|
|
||||||
def drawLine(self, line):
|
|
||||||
self._canvas.line(line.x1,line.y1,line.x2,line.y2)
|
|
||||||
|
|
||||||
def drawImage(self, image):
|
|
||||||
if image.path and os.path.exists(image.path):
|
|
||||||
if type(image.path) is type(''):
|
|
||||||
im = _getImage().open(image.path).convert('RGB')
|
|
||||||
else:
|
|
||||||
im = image.path.convert('RGB')
|
|
||||||
srcW, srcH = im.size
|
|
||||||
dstW, dstH = image.width, image.height
|
|
||||||
if dstW is None: dstW = srcW
|
|
||||||
if dstH is None: dstH = srcH
|
|
||||||
self._canvas._aapixbuf(
|
|
||||||
image.x, image.y, dstW, dstH,
|
|
||||||
im.tostring(), srcW, srcH, 3,
|
|
||||||
)
|
|
||||||
|
|
||||||
def drawCircle(self, circle):
|
|
||||||
c = self._canvas
|
|
||||||
c.circle(circle.cx,circle.cy, circle.r)
|
|
||||||
c.fillstrokepath()
|
|
||||||
|
|
||||||
def drawPolyLine(self, polyline, _doClose=0):
|
|
||||||
P = polyline.points
|
|
||||||
assert len(P) >= 2, 'Polyline must have 1 or more points'
|
|
||||||
c = self._canvas
|
|
||||||
c.pathBegin()
|
|
||||||
c.moveTo(P[0], P[1])
|
|
||||||
for i in range(2, len(P), 2):
|
|
||||||
c.lineTo(P[i], P[i+1])
|
|
||||||
if _doClose:
|
|
||||||
c.pathClose()
|
|
||||||
c.pathFill()
|
|
||||||
c.pathStroke()
|
|
||||||
|
|
||||||
def drawEllipse(self, ellipse):
|
|
||||||
c=self._canvas
|
|
||||||
c.ellipse(ellipse.cx, ellipse.cy, ellipse.rx,ellipse.ry)
|
|
||||||
c.fillstrokepath()
|
|
||||||
|
|
||||||
def drawPolygon(self, polygon):
|
|
||||||
self.drawPolyLine(polygon,_doClose=1)
|
|
||||||
|
|
||||||
def drawString(self, stringObj):
|
|
||||||
canv = self._canvas
|
|
||||||
fill = canv.fillColor
|
|
||||||
if fill is not None:
|
|
||||||
S = self._tracker.getState()
|
|
||||||
text_anchor = S['textAnchor']
|
|
||||||
fontName = S['fontName']
|
|
||||||
fontSize = S['fontSize']
|
|
||||||
font = getFont(fontName)
|
|
||||||
text = stringObj.text
|
|
||||||
x = stringObj.x
|
|
||||||
y = stringObj.y
|
|
||||||
if not text_anchor in ['start','inherited']:
|
|
||||||
textLen = stringWidth(text, fontName,fontSize)
|
|
||||||
if text_anchor=='end':
|
|
||||||
x = x-textLen
|
|
||||||
elif text_anchor=='middle':
|
|
||||||
x = x - textLen/2
|
|
||||||
else:
|
|
||||||
raise ValueError, 'bad value for textAnchor '+str(text_anchor)
|
|
||||||
if getattr(font,'_dynamicFont',None):
|
|
||||||
if isinstance(text,unicode): text = text.encode('utf8')
|
|
||||||
canv.drawString(x,y,text)
|
|
||||||
else:
|
|
||||||
fc = font
|
|
||||||
if not isinstance(text,unicode):
|
|
||||||
try:
|
|
||||||
text = text.decode('utf8')
|
|
||||||
except UnicodeDecodeError,e:
|
|
||||||
i,j = e.args[2:4]
|
|
||||||
raise UnicodeDecodeError(*(e.args[:4]+('%s\n%s-->%s<--%s' % (e.args[4],text[i-10:i],text[i:j],text[j:j+10]),)))
|
|
||||||
|
|
||||||
FT = unicode2T1(text,[font]+font.substitutionFonts)
|
|
||||||
n = len(FT)
|
|
||||||
nm1 = n-1
|
|
||||||
wscale = 0.001*fontSize
|
|
||||||
for i in xrange(n):
|
|
||||||
f, t = FT[i]
|
|
||||||
if f!=fc:
|
|
||||||
canv.setFont(f.fontName,fontSize)
|
|
||||||
fc = f
|
|
||||||
canv.drawString(x,y,t)
|
|
||||||
if i!=nm1:
|
|
||||||
x += wscale*sum(map(f.widths.__getitem__,map(ord,t)))
|
|
||||||
if font!=fc:
|
|
||||||
canv.setFont(fontName,fontSize)
|
|
||||||
|
|
||||||
def drawPath(self, path):
|
|
||||||
c = self._canvas
|
|
||||||
if path is EmptyClipPath:
|
|
||||||
del c._clipPaths[-1]
|
|
||||||
if c._clipPaths:
|
|
||||||
P = c._clipPaths[-1]
|
|
||||||
icp = P.isClipPath
|
|
||||||
P.isClipPath = 1
|
|
||||||
self.drawPath(P)
|
|
||||||
P.isClipPath = icp
|
|
||||||
else:
|
|
||||||
c.clipPathClear()
|
|
||||||
return
|
|
||||||
c.pathBegin()
|
|
||||||
drawFuncs = (c.moveTo, c.lineTo, c.curveTo, c.pathClose)
|
|
||||||
from reportlab.graphics.shapes import _renderPath
|
|
||||||
isClosed = _renderPath(path, drawFuncs)
|
|
||||||
if path.isClipPath:
|
|
||||||
c.clipPathSet()
|
|
||||||
c._clipPaths.append(path)
|
|
||||||
else:
|
|
||||||
if isClosed: c.pathFill()
|
|
||||||
c.pathStroke()
|
|
||||||
|
|
||||||
def _setFont(gs,fontName,fontSize):
|
|
||||||
try:
|
|
||||||
gs.setFont(fontName,fontSize)
|
|
||||||
except _renderPM.Error, errMsg:
|
|
||||||
if errMsg.args[0]!="Can't find font!": raise
|
|
||||||
#here's where we try to add a font to the canvas
|
|
||||||
try:
|
|
||||||
f = getFont(fontName)
|
|
||||||
if _renderPM._version<='0.98': #added reader arg in 0.99
|
|
||||||
_renderPM.makeT1Font(fontName,f.face.findT1File(),f.encoding.vector)
|
|
||||||
else:
|
|
||||||
_renderPM.makeT1Font(fontName,f.face.findT1File(),f.encoding.vector,open_and_read)
|
|
||||||
except:
|
|
||||||
s1, s2 = map(str,sys.exc_info()[:2])
|
|
||||||
raise RenderPMError, "Can't setFont(%s) missing the T1 files?\nOriginally %s: %s" % (fontName,s1,s2)
|
|
||||||
gs.setFont(fontName,fontSize)
|
|
||||||
|
|
||||||
def _convert2pilp(im):
|
|
||||||
Image = _getImage()
|
|
||||||
return im.convert("P", dither=Image.NONE, palette=Image.ADAPTIVE)
|
|
||||||
|
|
||||||
def _saveAsPICT(im,fn,fmt,transparent=None):
|
|
||||||
im = _convert2pilp(im)
|
|
||||||
cols, rows = im.size
|
|
||||||
#s = _renderPM.pil2pict(cols,rows,im.tostring(),im.im.getpalette(),transparent is not None and Color2Hex(transparent) or -1)
|
|
||||||
s = _renderPM.pil2pict(cols,rows,im.tostring(),im.im.getpalette())
|
|
||||||
if not hasattr(fn,'write'):
|
|
||||||
open(os.path.splitext(fn)[0]+'.'+string.lower(fmt),'wb').write(s)
|
|
||||||
if os.name=='mac':
|
|
||||||
from reportlab.lib.utils import markfilename
|
|
||||||
markfilename(fn,ext='PICT')
|
|
||||||
else:
|
|
||||||
fn.write(s)
|
|
||||||
|
|
||||||
BEZIER_ARC_MAGIC = 0.5522847498 #constant for drawing circular arcs w/ Beziers
|
|
||||||
class PMCanvas:
|
|
||||||
def __init__(self,w,h,dpi=72,bg=0xffffff,configPIL=None):
|
|
||||||
'''configPIL dict is passed to image save method'''
|
|
||||||
scale = dpi/72.0
|
|
||||||
w = int(w*scale+0.5)
|
|
||||||
h = int(h*scale+0.5)
|
|
||||||
self.__dict__['_gs'] = _renderPM.gstate(w,h,bg=bg)
|
|
||||||
self.__dict__['_bg'] = bg
|
|
||||||
self.__dict__['_baseCTM'] = (scale,0,0,scale,0,0)
|
|
||||||
self.__dict__['_clipPaths'] = []
|
|
||||||
self.__dict__['configPIL'] = configPIL
|
|
||||||
self.__dict__['_dpi'] = dpi
|
|
||||||
self.ctm = self._baseCTM
|
|
||||||
|
|
||||||
def _drawTimeResize(self,w,h,bg=None):
|
|
||||||
if bg is None: bg = self._bg
|
|
||||||
self._drawing.width, self._drawing.height = w, h
|
|
||||||
A = {'ctm':None, 'strokeWidth':None, 'strokeColor':None, 'lineCap':None, 'lineJoin':None, 'dashArray':None, 'fillColor':None}
|
|
||||||
gs = self._gs
|
|
||||||
fN,fS = gs.fontName, gs.fontSize
|
|
||||||
for k in A.keys():
|
|
||||||
A[k] = getattr(gs,k)
|
|
||||||
del gs, self._gs
|
|
||||||
gs = self.__dict__['_gs'] = _renderPM.gstate(w,h,bg=bg)
|
|
||||||
for k in A.keys():
|
|
||||||
setattr(self,k,A[k])
|
|
||||||
gs.setFont(fN,fS)
|
|
||||||
|
|
||||||
def toPIL(self):
|
|
||||||
im = _getImage().new('RGB', size=(self._gs.width, self._gs.height))
|
|
||||||
im.fromstring(self._gs.pixBuf)
|
|
||||||
return im
|
|
||||||
|
|
||||||
def saveToFile(self,fn,fmt=None):
|
|
||||||
im = self.toPIL()
|
|
||||||
if fmt is None:
|
|
||||||
if type(fn) is not StringType:
|
|
||||||
raise ValueError, "Invalid type '%s' for fn when fmt is None" % type(fn)
|
|
||||||
fmt = os.path.splitext(fn)[1]
|
|
||||||
if fmt.startswith('.'): fmt = fmt[1:]
|
|
||||||
configPIL = self.configPIL or {}
|
|
||||||
fmt = string.upper(fmt)
|
|
||||||
if fmt in ['GIF']:
|
|
||||||
im = _convert2pilp(im)
|
|
||||||
elif fmt in ['PCT','PICT']:
|
|
||||||
return _saveAsPICT(im,fn,fmt,transparent=configPIL.get('transparent',None))
|
|
||||||
elif fmt in ['PNG','TIFF','BMP', 'PPM', 'TIF']:
|
|
||||||
if fmt=='TIF': fmt = 'TIFF'
|
|
||||||
if fmt=='PNG':
|
|
||||||
try:
|
|
||||||
from PIL import PngImagePlugin
|
|
||||||
except ImportError:
|
|
||||||
import PngImagePlugin
|
|
||||||
elif fmt=='BMP':
|
|
||||||
try:
|
|
||||||
from PIL import BmpImagePlugin
|
|
||||||
except ImportError:
|
|
||||||
import BmpImagePlugin
|
|
||||||
elif fmt in ('JPG','JPEG'):
|
|
||||||
fmt = 'JPEG'
|
|
||||||
else:
|
|
||||||
raise RenderPMError,"Unknown image kind %s" % fmt
|
|
||||||
if fmt=='TIFF':
|
|
||||||
tc = configPIL.get('transparent',None)
|
|
||||||
if tc:
|
|
||||||
from PIL import ImageChops, Image
|
|
||||||
T = 768*[0]
|
|
||||||
for o, c in zip((0,256,512), tc.bitmap_rgb()):
|
|
||||||
T[o+c] = 255
|
|
||||||
#if type(fn) is type(''): ImageChops.invert(im.point(T).convert('L').point(255*[0]+[255])).save(fn+'_mask.gif','GIF')
|
|
||||||
im = Image.merge('RGBA', im.split()+(ImageChops.invert(im.point(T).convert('L').point(255*[0]+[255])),))
|
|
||||||
#if type(fn) is type(''): im.save(fn+'_masked.gif','GIF')
|
|
||||||
for a,d in ('resolution',self._dpi),('resolution unit','inch'):
|
|
||||||
configPIL[a] = configPIL.get(a,d)
|
|
||||||
apply(im.save,(fn,fmt),configPIL)
|
|
||||||
if not hasattr(fn,'write') and os.name=='mac':
|
|
||||||
from reportlab.lib.utils import markfilename
|
|
||||||
markfilename(fn,ext=fmt)
|
|
||||||
|
|
||||||
def saveToString(self,fmt='GIF'):
|
|
||||||
s = getStringIO()
|
|
||||||
self.saveToFile(s,fmt=fmt)
|
|
||||||
return s.getvalue()
|
|
||||||
|
|
||||||
def _saveToBMP(self,f):
|
|
||||||
'''
|
|
||||||
Niki Spahiev, <niki@vintech.bg>, asserts that this is a respectable way to get BMP without PIL
|
|
||||||
f is a file like object to which the BMP is written
|
|
||||||
'''
|
|
||||||
import struct
|
|
||||||
gs = self._gs
|
|
||||||
pix, width, height = gs.pixBuf, gs.width, gs.height
|
|
||||||
f.write(struct.pack('=2sLLLLLLhh24x','BM',len(pix)+54,0,54,40,width,height,1,24))
|
|
||||||
rowb = width * 3
|
|
||||||
for o in range(len(pix),0,-rowb):
|
|
||||||
f.write(pix[o-rowb:o])
|
|
||||||
f.write( '\0' * 14 )
|
|
||||||
|
|
||||||
def setFont(self,fontName,fontSize,leading=None):
|
|
||||||
_setFont(self._gs,fontName,fontSize)
|
|
||||||
|
|
||||||
def __setattr__(self,name,value):
|
|
||||||
setattr(self._gs,name,value)
|
|
||||||
|
|
||||||
def __getattr__(self,name):
|
|
||||||
return getattr(self._gs,name)
|
|
||||||
|
|
||||||
def fillstrokepath(self,stroke=1,fill=1):
|
|
||||||
if fill: self.pathFill()
|
|
||||||
if stroke: self.pathStroke()
|
|
||||||
|
|
||||||
def _bezierArcSegmentCCW(self, cx,cy, rx,ry, theta0, theta1):
|
|
||||||
"""compute the control points for a bezier arc with theta1-theta0 <= 90.
|
|
||||||
Points are computed for an arc with angle theta increasing in the
|
|
||||||
counter-clockwise (CCW) direction. returns a tuple with starting point
|
|
||||||
and 3 control points of a cubic bezier curve for the curvto opertator"""
|
|
||||||
|
|
||||||
# Requires theta1 - theta0 <= 90 for a good approximation
|
|
||||||
assert abs(theta1 - theta0) <= 90
|
|
||||||
cos0 = cos(pi*theta0/180.0)
|
|
||||||
sin0 = sin(pi*theta0/180.0)
|
|
||||||
x0 = cx + rx*cos0
|
|
||||||
y0 = cy + ry*sin0
|
|
||||||
|
|
||||||
cos1 = cos(pi*theta1/180.0)
|
|
||||||
sin1 = sin(pi*theta1/180.0)
|
|
||||||
|
|
||||||
x3 = cx + rx*cos1
|
|
||||||
y3 = cy + ry*sin1
|
|
||||||
|
|
||||||
dx1 = -rx * sin0
|
|
||||||
dy1 = ry * cos0
|
|
||||||
|
|
||||||
#from pdfgeom
|
|
||||||
halfAng = pi*(theta1-theta0)/(2.0 * 180.0)
|
|
||||||
k = abs(4.0 / 3.0 * (1.0 - cos(halfAng) ) /(sin(halfAng)) )
|
|
||||||
x1 = x0 + dx1 * k
|
|
||||||
y1 = y0 + dy1 * k
|
|
||||||
|
|
||||||
dx2 = -rx * sin1
|
|
||||||
dy2 = ry * cos1
|
|
||||||
|
|
||||||
x2 = x3 - dx2 * k
|
|
||||||
y2 = y3 - dy2 * k
|
|
||||||
return ((x0,y0), ((x1,y1), (x2,y2), (x3,y3)) )
|
|
||||||
|
|
||||||
def bezierArcCCW(self, cx,cy, rx,ry, theta0, theta1):
|
|
||||||
"""return a set of control points for Bezier approximation to an arc
|
|
||||||
with angle increasing counter clockwise. No requirement on |theta1-theta0| <= 90
|
|
||||||
However, it must be true that theta1-theta0 > 0."""
|
|
||||||
|
|
||||||
# I believe this is also clockwise
|
|
||||||
# pretty much just like Robert Kern's pdfgeom.BezierArc
|
|
||||||
angularExtent = theta1 - theta0
|
|
||||||
# break down the arc into fragments of <=90 degrees
|
|
||||||
if abs(angularExtent) <= 90.0: # we just need one fragment
|
|
||||||
angleList = [(theta0,theta1)]
|
|
||||||
else:
|
|
||||||
Nfrag = int( ceil( abs(angularExtent)/90.) )
|
|
||||||
fragAngle = float(angularExtent)/ Nfrag # this could be negative
|
|
||||||
angleList = []
|
|
||||||
for ii in range(Nfrag):
|
|
||||||
a = theta0 + ii * fragAngle
|
|
||||||
b = a + fragAngle # hmm.. is I wonder if this is precise enought
|
|
||||||
angleList.append((a,b))
|
|
||||||
|
|
||||||
ctrlpts = []
|
|
||||||
for (a,b) in angleList:
|
|
||||||
if not ctrlpts: # first time
|
|
||||||
[(x0,y0), pts] = self._bezierArcSegmentCCW(cx,cy, rx,ry, a,b)
|
|
||||||
ctrlpts.append(pts)
|
|
||||||
else:
|
|
||||||
[(tmpx,tmpy), pts] = self._bezierArcSegmentCCW(cx,cy, rx,ry, a,b)
|
|
||||||
ctrlpts.append(pts)
|
|
||||||
return ((x0,y0), ctrlpts)
|
|
||||||
|
|
||||||
def addEllipsoidalArc(self, cx,cy, rx, ry, ang1, ang2):
|
|
||||||
"""adds an ellisesoidal arc segment to a path, with an ellipse centered
|
|
||||||
on cx,cy and with radii (major & minor axes) rx and ry. The arc is
|
|
||||||
drawn in the CCW direction. Requires: (ang2-ang1) > 0"""
|
|
||||||
|
|
||||||
((x0,y0), ctrlpts) = self.bezierArcCCW(cx,cy, rx,ry,ang1,ang2)
|
|
||||||
|
|
||||||
self.lineTo(x0,y0)
|
|
||||||
for ((x1,y1), (x2,y2),(x3,y3)) in ctrlpts:
|
|
||||||
self.curveTo(x1,y1,x2,y2,x3,y3)
|
|
||||||
|
|
||||||
def drawCentredString(self, x, y, text, text_anchor='middle'):
|
|
||||||
if self.fillColor is not None:
|
|
||||||
textLen = stringWidth(text, self.fontName,self.fontSize)
|
|
||||||
if text_anchor=='end':
|
|
||||||
x -= textLen
|
|
||||||
elif text_anchor=='middle':
|
|
||||||
x -= textLen/2.
|
|
||||||
self.drawString(x,y,text)
|
|
||||||
|
|
||||||
def drawRightString(self, text, x, y):
|
|
||||||
self.drawCentredString(text,x,y,text_anchor='end')
|
|
||||||
|
|
||||||
def line(self,x1,y1,x2,y2):
|
|
||||||
if self.strokeColor is not None:
|
|
||||||
self.pathBegin()
|
|
||||||
self.moveTo(x1,y1)
|
|
||||||
self.lineTo(x2,y2)
|
|
||||||
self.pathStroke()
|
|
||||||
|
|
||||||
def rect(self,x,y,width,height,stroke=1,fill=1):
|
|
||||||
self.pathBegin()
|
|
||||||
self.moveTo(x, y)
|
|
||||||
self.lineTo(x+width, y)
|
|
||||||
self.lineTo(x+width, y + height)
|
|
||||||
self.lineTo(x, y + height)
|
|
||||||
self.pathClose()
|
|
||||||
self.fillstrokepath(stroke=stroke,fill=fill)
|
|
||||||
|
|
||||||
def roundRect(self, x, y, width, height, rx,ry):
|
|
||||||
"""rect(self, x, y, width, height, rx,ry):
|
|
||||||
Draw a rectangle if rx or rx and ry are specified the corners are
|
|
||||||
rounded with ellipsoidal arcs determined by rx and ry
|
|
||||||
(drawn in the counter-clockwise direction)"""
|
|
||||||
if rx==0: rx = ry
|
|
||||||
if ry==0: ry = rx
|
|
||||||
x2 = x + width
|
|
||||||
y2 = y + height
|
|
||||||
self.pathBegin()
|
|
||||||
self.moveTo(x+rx,y)
|
|
||||||
self.addEllipsoidalArc(x2-rx, y+ry, rx, ry, 270, 360 )
|
|
||||||
self.addEllipsoidalArc(x2-rx, y2-ry, rx, ry, 0, 90)
|
|
||||||
self.addEllipsoidalArc(x+rx, y2-ry, rx, ry, 90, 180)
|
|
||||||
self.addEllipsoidalArc(x+rx, y+ry, rx, ry, 180, 270)
|
|
||||||
self.pathClose()
|
|
||||||
self.fillstrokepath()
|
|
||||||
|
|
||||||
def circle(self, cx, cy, r):
|
|
||||||
"add closed path circle with center cx,cy and axes r: counter-clockwise orientation"
|
|
||||||
self.ellipse(cx,cy,r,r)
|
|
||||||
|
|
||||||
def ellipse(self, cx,cy,rx,ry):
|
|
||||||
"""add closed path ellipse with center cx,cy and axes rx,ry: counter-clockwise orientation
|
|
||||||
(remember y-axis increases downward) """
|
|
||||||
self.pathBegin()
|
|
||||||
# first segment
|
|
||||||
x0 = cx + rx # (x0,y0) start pt
|
|
||||||
y0 = cy
|
|
||||||
|
|
||||||
x3 = cx # (x3,y3) end pt of arc
|
|
||||||
y3 = cy-ry
|
|
||||||
|
|
||||||
x1 = cx+rx
|
|
||||||
y1 = cy-ry*BEZIER_ARC_MAGIC
|
|
||||||
|
|
||||||
x2 = x3 + rx*BEZIER_ARC_MAGIC
|
|
||||||
y2 = y3
|
|
||||||
self.moveTo(x0, y0)
|
|
||||||
self.curveTo(x1,y1,x2,y2,x3,y3)
|
|
||||||
# next segment
|
|
||||||
x0 = x3
|
|
||||||
y0 = y3
|
|
||||||
|
|
||||||
x3 = cx-rx
|
|
||||||
y3 = cy
|
|
||||||
|
|
||||||
x1 = cx-rx*BEZIER_ARC_MAGIC
|
|
||||||
y1 = cy-ry
|
|
||||||
|
|
||||||
x2 = x3
|
|
||||||
y2 = cy- ry*BEZIER_ARC_MAGIC
|
|
||||||
self.curveTo(x1,y1,x2,y2,x3,y3)
|
|
||||||
# next segment
|
|
||||||
x0 = x3
|
|
||||||
y0 = y3
|
|
||||||
|
|
||||||
x3 = cx
|
|
||||||
y3 = cy+ry
|
|
||||||
|
|
||||||
x1 = cx-rx
|
|
||||||
y1 = cy+ry*BEZIER_ARC_MAGIC
|
|
||||||
|
|
||||||
x2 = cx -rx*BEZIER_ARC_MAGIC
|
|
||||||
y2 = cy+ry
|
|
||||||
self.curveTo(x1,y1,x2,y2,x3,y3)
|
|
||||||
#last segment
|
|
||||||
x0 = x3
|
|
||||||
y0 = y3
|
|
||||||
|
|
||||||
x3 = cx+rx
|
|
||||||
y3 = cy
|
|
||||||
|
|
||||||
x1 = cx+rx*BEZIER_ARC_MAGIC
|
|
||||||
y1 = cy+ry
|
|
||||||
|
|
||||||
x2 = cx+rx
|
|
||||||
y2 = cy+ry*BEZIER_ARC_MAGIC
|
|
||||||
self.curveTo(x1,y1,x2,y2,x3,y3)
|
|
||||||
self.pathClose()
|
|
||||||
|
|
||||||
def saveState(self):
|
|
||||||
'''do nothing for compatibility'''
|
|
||||||
pass
|
|
||||||
|
|
||||||
def setFillColor(self,aColor):
|
|
||||||
self.fillColor = Color2Hex(aColor)
|
|
||||||
|
|
||||||
def setStrokeColor(self,aColor):
|
|
||||||
self.strokeColor = Color2Hex(aColor)
|
|
||||||
|
|
||||||
restoreState = saveState
|
|
||||||
|
|
||||||
# compatibility routines
|
|
||||||
def setLineCap(self,cap):
|
|
||||||
self.lineCap = cap
|
|
||||||
|
|
||||||
def setLineWidth(self,width):
|
|
||||||
self.strokeWidth = width
|
|
||||||
|
|
||||||
def drawToPMCanvas(d, dpi=72, bg=0xffffff, configPIL=None, showBoundary=rl_config._unset_):
|
|
||||||
d = renderScaledDrawing(d)
|
|
||||||
c = PMCanvas(d.width, d.height, dpi=dpi, bg=bg, configPIL=configPIL)
|
|
||||||
draw(d, c, 0, 0, showBoundary=showBoundary)
|
|
||||||
return c
|
|
||||||
|
|
||||||
def drawToPIL(d, dpi=72, bg=0xffffff, configPIL=None, showBoundary=rl_config._unset_):
|
|
||||||
return drawToPMCanvas(d, dpi=dpi, bg=bg, configPIL=configPIL, showBoundary=showBoundary).toPIL()
|
|
||||||
|
|
||||||
def drawToPILP(d, dpi=72, bg=0xffffff, configPIL=None, showBoundary=rl_config._unset_):
|
|
||||||
Image = _getImage()
|
|
||||||
im = drawToPIL(d, dpi=dpi, bg=bg, configPIL=configPIL, showBoundary=showBoundary)
|
|
||||||
return im.convert("P", dither=Image.NONE, palette=Image.ADAPTIVE)
|
|
||||||
|
|
||||||
def drawToFile(d,fn,fmt='GIF', dpi=72, bg=0xffffff, configPIL=None, showBoundary=rl_config._unset_):
|
|
||||||
'''create a pixmap and draw drawing, d to it then save as a file
|
|
||||||
configPIL dict is passed to image save method'''
|
|
||||||
c = drawToPMCanvas(d, dpi=dpi, bg=bg, configPIL=configPIL, showBoundary=showBoundary)
|
|
||||||
c.saveToFile(fn,fmt)
|
|
||||||
|
|
||||||
def drawToString(d,fmt='GIF', dpi=72, bg=0xffffff, configPIL=None, showBoundary=rl_config._unset_):
|
|
||||||
s = getStringIO()
|
|
||||||
drawToFile(d,s,fmt=fmt, dpi=dpi, bg=bg, configPIL=configPIL)
|
|
||||||
return s.getvalue()
|
|
||||||
|
|
||||||
save = drawToFile
|
|
||||||
|
|
||||||
def test():
|
|
||||||
def ext(x):
|
|
||||||
if x=='tiff': x='tif'
|
|
||||||
return x
|
|
||||||
#grab all drawings from the test module and write out.
|
|
||||||
#make a page of links in HTML to assist viewing.
|
|
||||||
import os
|
|
||||||
from reportlab.graphics import testshapes
|
|
||||||
getAllTestDrawings = testshapes.getAllTestDrawings
|
|
||||||
drawings = []
|
|
||||||
if not os.path.isdir('pmout'):
|
|
||||||
os.mkdir('pmout')
|
|
||||||
htmlTop = """<html><head><title>renderPM output results</title></head>
|
|
||||||
<body>
|
|
||||||
<h1>renderPM results of output</h1>
|
|
||||||
"""
|
|
||||||
htmlBottom = """</body>
|
|
||||||
</html>
|
|
||||||
"""
|
|
||||||
html = [htmlTop]
|
|
||||||
|
|
||||||
i = 0
|
|
||||||
#print in a loop, with their doc strings
|
|
||||||
for (drawing, docstring, name) in getAllTestDrawings(doTTF=hasattr(_renderPM,'ft_get_face')):
|
|
||||||
fnRoot = 'renderPM%d' % i
|
|
||||||
if 1 or i==10:
|
|
||||||
w = int(drawing.width)
|
|
||||||
h = int(drawing.height)
|
|
||||||
html.append('<hr><h2>Drawing %s %d</h2>\n<pre>%s</pre>' % (name, i, docstring))
|
|
||||||
|
|
||||||
for k in ['gif','tiff', 'png', 'jpg', 'pct']:
|
|
||||||
if k in ['gif','png','jpg','pct']:
|
|
||||||
html.append('<p>%s format</p>\n' % string.upper(k))
|
|
||||||
try:
|
|
||||||
filename = '%s.%s' % (fnRoot, ext(k))
|
|
||||||
fullpath = os.path.join('pmout', filename)
|
|
||||||
if os.path.isfile(fullpath):
|
|
||||||
os.remove(fullpath)
|
|
||||||
if k=='pct':
|
|
||||||
from reportlab.lib.colors import white
|
|
||||||
drawToFile(drawing,fullpath,fmt=k,configPIL={'transparent':white})
|
|
||||||
else:
|
|
||||||
drawToFile(drawing,fullpath,fmt=k)
|
|
||||||
if k in ['gif','png','jpg']:
|
|
||||||
html.append('<img src="%s" border="1"><br>\n' % filename)
|
|
||||||
print 'wrote',fullpath
|
|
||||||
except AttributeError:
|
|
||||||
print 'Problem drawing %s file'%k
|
|
||||||
raise
|
|
||||||
if os.environ.get('RL_NOEPSPREVIEW','0')=='1': drawing.__dict__['preview'] = 0
|
|
||||||
drawing.save(formats=['eps','pdf'],outDir='pmout',fnRoot=fnRoot)
|
|
||||||
i = i + 1
|
|
||||||
#if i==10: break
|
|
||||||
html.append(htmlBottom)
|
|
||||||
htmlFileName = os.path.join('pmout', 'index.html')
|
|
||||||
open(htmlFileName, 'w').writelines(html)
|
|
||||||
if sys.platform=='mac':
|
|
||||||
from reportlab.lib.utils import markfilename
|
|
||||||
markfilename(htmlFileName,ext='HTML')
|
|
||||||
print 'wrote %s' % htmlFileName
|
|
||||||
|
|
||||||
if __name__=='__main__':
|
|
||||||
test()
|
|
|
@ -1,871 +0,0 @@
|
||||||
#Copyright ReportLab Europe Ltd. 2000-2004
|
|
||||||
#see license.txt for license details
|
|
||||||
#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/graphics/renderPS.py
|
|
||||||
__version__=''' $Id: renderPS.py 2808 2006-03-15 16:47:27Z rgbecker $ '''
|
|
||||||
import string, types
|
|
||||||
from reportlab.pdfbase.pdfmetrics import getFont, stringWidth # for font info
|
|
||||||
from reportlab.lib.utils import fp_str, getStringIO
|
|
||||||
from reportlab.lib.colors import black
|
|
||||||
from reportlab.graphics.renderbase import Renderer, StateTracker, getStateDelta, renderScaledDrawing
|
|
||||||
from reportlab.graphics.shapes import STATE_DEFAULTS
|
|
||||||
import math
|
|
||||||
from types import StringType
|
|
||||||
from operator import getitem
|
|
||||||
from reportlab import rl_config
|
|
||||||
|
|
||||||
|
|
||||||
# we need to create encoding vectors for each font we use, or they will
|
|
||||||
# come out in Adobe's old StandardEncoding, which NOBODY uses.
|
|
||||||
PS_WinAnsiEncoding="""
|
|
||||||
/RE { %def
|
|
||||||
findfont begin
|
|
||||||
currentdict dup length dict begin
|
|
||||||
{ %forall
|
|
||||||
1 index /FID ne { def } { pop pop } ifelse
|
|
||||||
} forall
|
|
||||||
/FontName exch def dup length 0 ne { %if
|
|
||||||
/Encoding Encoding 256 array copy def
|
|
||||||
0 exch { %forall
|
|
||||||
dup type /nametype eq { %ifelse
|
|
||||||
Encoding 2 index 2 index put
|
|
||||||
pop 1 add
|
|
||||||
}{ %else
|
|
||||||
exch pop
|
|
||||||
} ifelse
|
|
||||||
} forall
|
|
||||||
} if pop
|
|
||||||
currentdict dup end end
|
|
||||||
/FontName get exch definefont pop
|
|
||||||
} bind def
|
|
||||||
|
|
||||||
/WinAnsiEncoding [
|
|
||||||
39/quotesingle 96/grave 128/euro 130/quotesinglbase/florin/quotedblbase
|
|
||||||
/ellipsis/dagger/daggerdbl/circumflex/perthousand
|
|
||||||
/Scaron/guilsinglleft/OE 145/quoteleft/quoteright
|
|
||||||
/quotedblleft/quotedblright/bullet/endash/emdash
|
|
||||||
/tilde/trademark/scaron/guilsinglright/oe/dotlessi
|
|
||||||
159/Ydieresis 164/currency 166/brokenbar 168/dieresis/copyright
|
|
||||||
/ordfeminine 172/logicalnot 174/registered/macron/ring
|
|
||||||
177/plusminus/twosuperior/threesuperior/acute/mu
|
|
||||||
183/periodcentered/cedilla/onesuperior/ordmasculine
|
|
||||||
188/onequarter/onehalf/threequarters 192/Agrave/Aacute
|
|
||||||
/Acircumflex/Atilde/Adieresis/Aring/AE/Ccedilla
|
|
||||||
/Egrave/Eacute/Ecircumflex/Edieresis/Igrave/Iacute
|
|
||||||
/Icircumflex/Idieresis/Eth/Ntilde/Ograve/Oacute
|
|
||||||
/Ocircumflex/Otilde/Odieresis/multiply/Oslash
|
|
||||||
/Ugrave/Uacute/Ucircumflex/Udieresis/Yacute/Thorn
|
|
||||||
/germandbls/agrave/aacute/acircumflex/atilde/adieresis
|
|
||||||
/aring/ae/ccedilla/egrave/eacute/ecircumflex
|
|
||||||
/edieresis/igrave/iacute/icircumflex/idieresis
|
|
||||||
/eth/ntilde/ograve/oacute/ocircumflex/otilde
|
|
||||||
/odieresis/divide/oslash/ugrave/uacute/ucircumflex
|
|
||||||
/udieresis/yacute/thorn/ydieresis
|
|
||||||
] def
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
class PSCanvas:
|
|
||||||
def __init__(self,size=(300,300), PostScriptLevel=2):
|
|
||||||
self.width, self.height = size
|
|
||||||
xtraState = []
|
|
||||||
self._xtraState_push = xtraState.append
|
|
||||||
self._xtraState_pop = xtraState.pop
|
|
||||||
self.comments = 0
|
|
||||||
self.code = []
|
|
||||||
self._sep = '\n'
|
|
||||||
self._strokeColor = self._fillColor = self._lineWidth = \
|
|
||||||
self._font = self._fontSize = self._lineCap = \
|
|
||||||
self._lineJoin = self._color = None
|
|
||||||
|
|
||||||
|
|
||||||
self._fontsUsed = [] # track them as we go
|
|
||||||
|
|
||||||
self.setFont(STATE_DEFAULTS['fontName'],STATE_DEFAULTS['fontSize'])
|
|
||||||
self.setStrokeColor(STATE_DEFAULTS['strokeColor'])
|
|
||||||
self.setLineCap(2)
|
|
||||||
self.setLineJoin(0)
|
|
||||||
self.setLineWidth(1)
|
|
||||||
|
|
||||||
self.PostScriptLevel=PostScriptLevel
|
|
||||||
|
|
||||||
def comment(self,msg):
|
|
||||||
if self.comments: self.code.append('%'+msg)
|
|
||||||
|
|
||||||
def drawImage(self, image, x1,y1, x2=None,y2=None): # Postscript Level2 version
|
|
||||||
# select between postscript level 1 or level 2
|
|
||||||
if PostScriptLevel==1:
|
|
||||||
self._drawImageLevel1(image, x1,y1, x2=None,y2=None)
|
|
||||||
elif PostScriptLevel == 2 :
|
|
||||||
self._drawImageLevel2(image, x1,y1, x2=None,y2=None)
|
|
||||||
else :
|
|
||||||
raise 'PostScriptLevelException'
|
|
||||||
|
|
||||||
def clear(self):
|
|
||||||
self.code.append('showpage') # ugh, this makes no sense oh well.
|
|
||||||
|
|
||||||
def save(self,f=None):
|
|
||||||
if not hasattr(f,'write'):
|
|
||||||
file = open(f,'wb')
|
|
||||||
else:
|
|
||||||
file = f
|
|
||||||
if self.code[-1]!='showpage': self.clear()
|
|
||||||
self.code.insert(0,'''\
|
|
||||||
%%!PS-Adobe-3.0 EPSF-3.0
|
|
||||||
%%%%BoundingBox: 0 0 %d %d
|
|
||||||
%%%% Initialization:
|
|
||||||
/m {moveto} bind def
|
|
||||||
/l {lineto} bind def
|
|
||||||
/c {curveto} bind def
|
|
||||||
|
|
||||||
%s
|
|
||||||
''' % (self.width,self.height, PS_WinAnsiEncoding))
|
|
||||||
|
|
||||||
# for each font used, reencode the vectors
|
|
||||||
fontReencode = []
|
|
||||||
for fontName in self._fontsUsed:
|
|
||||||
fontReencode.append('WinAnsiEncoding /%s /%s RE' % (fontName, fontName))
|
|
||||||
self.code.insert(1, string.join(fontReencode, self._sep))
|
|
||||||
|
|
||||||
file.write(string.join(self.code,self._sep))
|
|
||||||
if file is not f:
|
|
||||||
file.close()
|
|
||||||
from reportlab.lib.utils import markfilename
|
|
||||||
markfilename(f,creatorcode='XPR3',filetype='EPSF')
|
|
||||||
|
|
||||||
def saveState(self):
|
|
||||||
self._xtraState_push((self._fontCodeLoc,))
|
|
||||||
self.code.append('gsave')
|
|
||||||
|
|
||||||
def restoreState(self):
|
|
||||||
self.code.append('grestore')
|
|
||||||
self._fontCodeLoc, = self._xtraState_pop()
|
|
||||||
|
|
||||||
def stringWidth(self, s, font=None, fontSize=None):
|
|
||||||
"""Return the logical width of the string if it were drawn
|
|
||||||
in the current font (defaults to self.font)."""
|
|
||||||
font = font or self._font
|
|
||||||
fontSize = fontSize or self._fontSize
|
|
||||||
return stringWidth(s, font, fontSize)
|
|
||||||
|
|
||||||
def setLineCap(self,v):
|
|
||||||
if self._lineCap!=v:
|
|
||||||
self._lineCap = v
|
|
||||||
self.code.append('%d setlinecap'%v)
|
|
||||||
|
|
||||||
def setLineJoin(self,v):
|
|
||||||
if self._lineJoin!=v:
|
|
||||||
self._lineJoin = v
|
|
||||||
self.code.append('%d setlinejoin'%v)
|
|
||||||
|
|
||||||
def setDash(self, array=[], phase=0):
|
|
||||||
"""Two notations. pass two numbers, or an array and phase"""
|
|
||||||
# copied and modified from reportlab.canvas
|
|
||||||
psoperation = "setdash"
|
|
||||||
if type(array) == types.IntType or type(array) == types.FloatType:
|
|
||||||
self._code.append('[%s %s] 0 %s' % (array, phase, psoperation))
|
|
||||||
elif type(array) == types.ListType or type(array) == types.TupleType:
|
|
||||||
assert phase >= 0, "phase is a length in user space"
|
|
||||||
textarray = string.join(map(str, array))
|
|
||||||
self.code.append('[%s] %s %s' % (textarray, phase, psoperation))
|
|
||||||
|
|
||||||
def setStrokeColor(self, color):
|
|
||||||
self._strokeColor = color
|
|
||||||
self.setColor(color)
|
|
||||||
|
|
||||||
def setColor(self, color):
|
|
||||||
if self._color!=color:
|
|
||||||
self._color = color
|
|
||||||
if color:
|
|
||||||
if hasattr(color, "cyan"):
|
|
||||||
self.code.append('%s setcmykcolor' % fp_str(color.cyan, color.magenta, color.yellow, color.black))
|
|
||||||
else:
|
|
||||||
self.code.append('%s setrgbcolor' % fp_str(color.red, color.green, color.blue))
|
|
||||||
|
|
||||||
def setFillColor(self, color):
|
|
||||||
self._fillColor = color
|
|
||||||
self.setColor(color)
|
|
||||||
|
|
||||||
def setLineWidth(self, width):
|
|
||||||
if width != self._lineWidth:
|
|
||||||
self._lineWidth = width
|
|
||||||
self.code.append('%s setlinewidth' % width)
|
|
||||||
|
|
||||||
def setFont(self,font,fontSize,leading=None):
|
|
||||||
if self._font!=font or self._fontSize!=fontSize:
|
|
||||||
self._fontCodeLoc = len(self.code)
|
|
||||||
self._font = font
|
|
||||||
self._fontSize = fontSize
|
|
||||||
self.code.append('')
|
|
||||||
|
|
||||||
def line(self, x1, y1, x2, y2):
|
|
||||||
if self._strokeColor != None:
|
|
||||||
self.setColor(self._strokeColor)
|
|
||||||
self.code.append('%s m %s l stroke' % (fp_str(x1, y1), fp_str(x2, y2)))
|
|
||||||
|
|
||||||
def _escape(self, s):
|
|
||||||
'''
|
|
||||||
return a copy of string s with special characters in postscript strings
|
|
||||||
escaped with backslashes.
|
|
||||||
Have not handled characters that are converted normally in python strings
|
|
||||||
i.e. \n -> newline
|
|
||||||
'''
|
|
||||||
str = string.replace(s, chr(0x5C), r'\\' )
|
|
||||||
str = string.replace(str, '(', '\(' )
|
|
||||||
str = string.replace(str, ')', '\)')
|
|
||||||
return str
|
|
||||||
|
|
||||||
def drawString(self, x, y, s, angle=0):
|
|
||||||
if self._fillColor != None:
|
|
||||||
if not self.code[self._fontCodeLoc]:
|
|
||||||
psName = getFont(self._font).face.name
|
|
||||||
self.code[self._fontCodeLoc]='(%s) findfont %s scalefont setfont' % (psName,fp_str(self._fontSize))
|
|
||||||
if psName not in self._fontsUsed:
|
|
||||||
self._fontsUsed.append(psName)
|
|
||||||
self.setColor(self._fillColor)
|
|
||||||
s = self._escape(s)
|
|
||||||
## before inverting...
|
|
||||||
## if angle == 0 : # do special case of angle = 0 first. Avoids a bunch of gsave/grestore ops
|
|
||||||
## self.code.append('%s m 1 -1 scale (%s) show 1 -1 scale' % (fp_str(x,y),s))
|
|
||||||
## else : # general case, rotated text
|
|
||||||
## self.code.append('gsave %s %s translate %s rotate' % (x,y,angle))
|
|
||||||
## self.code.append('0 0 m 1 -1 scale (%s) show' % s)
|
|
||||||
## self.code.append('grestore')
|
|
||||||
if angle == 0 : # do special case of angle = 0 first. Avoids a bunch of gsave/grestore ops
|
|
||||||
self.code.append('%s m (%s) show ' % (fp_str(x,y),s))
|
|
||||||
else : # general case, rotated text
|
|
||||||
self.code.append('gsave %s %s translate %s rotate' % (x,y,angle))
|
|
||||||
self.code.append('0 0 m (%s) show' % s)
|
|
||||||
self.code.append('grestore')
|
|
||||||
|
|
||||||
def drawCentredString(self, x, y, text, text_anchor='middle'):
|
|
||||||
if self._fillColor is not None:
|
|
||||||
textLen = stringWidth(text, self._font,self._fontSize)
|
|
||||||
if text_anchor=='end':
|
|
||||||
x -= textLen
|
|
||||||
elif text_anchor=='middle':
|
|
||||||
x -= textLen/2.
|
|
||||||
self.drawString(x,y,text)
|
|
||||||
|
|
||||||
def drawRightString(self, text, x, y):
|
|
||||||
self.drawCentredString(text,x,y,text_anchor='end')
|
|
||||||
|
|
||||||
def drawCurve(self, x1, y1, x2, y2, x3, y3, x4, y4, closed=0):
|
|
||||||
codeline = '%s m %s curveto'
|
|
||||||
data = (fp_str(x1, y1), fp_str(x2, y2, x3, y3, x4, y4))
|
|
||||||
if self._fillColor != None:
|
|
||||||
self.setColor(self._fillColor)
|
|
||||||
self.code.append((codeline % data) + ' eofill')
|
|
||||||
if self._strokeColor != None:
|
|
||||||
self.setColor(self._strokeColor)
|
|
||||||
self.code.append((codeline % data)
|
|
||||||
+ ((closed and ' closepath') or '')
|
|
||||||
+ ' stroke')
|
|
||||||
|
|
||||||
########################################################################################
|
|
||||||
|
|
||||||
def rect(self, x1,y1, x2,y2, stroke=1, fill=1):
|
|
||||||
"Draw a rectangle between x1,y1, and x2,y2"
|
|
||||||
# Path is drawn in counter-clockwise direction"
|
|
||||||
|
|
||||||
x1, x2 = min(x1,x2), max(x1, x2) # from piddle.py
|
|
||||||
y1, y2 = min(y1,y2), max(y1, y2)
|
|
||||||
self.polygon(((x1,y1),(x2,y1),(x2,y2),(x1,y2)), closed=1, stroke=stroke, fill = fill)
|
|
||||||
|
|
||||||
def roundRect(self, x1,y1, x2,y2, rx=8, ry=8):
|
|
||||||
"""Draw a rounded rectangle between x1,y1, and x2,y2,
|
|
||||||
with corners inset as ellipses with x radius rx and y radius ry.
|
|
||||||
These should have x1<x2, y1<y2, rx>0, and ry>0."""
|
|
||||||
# Path is drawn in counter-clockwise direction
|
|
||||||
|
|
||||||
x1, x2 = min(x1,x2), max(x1, x2) # from piddle.py
|
|
||||||
y1, y2 = min(y1,y2), max(y1, y2)
|
|
||||||
|
|
||||||
# Note: arcto command draws a line from current point to beginning of arc
|
|
||||||
# save current matrix, translate to center of ellipse, scale by rx ry, and draw
|
|
||||||
# a circle of unit radius in counterclockwise dir, return to original matrix
|
|
||||||
# arguments are (cx, cy, rx, ry, startAngle, endAngle)
|
|
||||||
ellipsePath = 'matrix currentmatrix %s %s translate %s %s scale 0 0 1 %s %s arc setmatrix'
|
|
||||||
|
|
||||||
# choice between newpath and moveTo beginning of arc
|
|
||||||
# go with newpath for precision, does this violate any assumptions in code???
|
|
||||||
rrCode = ['newpath'] # Round Rect code path
|
|
||||||
# upper left corner ellipse is first
|
|
||||||
rrCode.append(ellipsePath % (x1+rx, y1+ry, rx, -ry, 90, 180))
|
|
||||||
rrCode.append(ellipsePath % (x1+rx, y2-ry, rx, -ry, 180, 270))
|
|
||||||
rrCode.append(ellipsePath % (x2-rx, y2-ry, rx, -ry, 270, 360))
|
|
||||||
rrCode.append(ellipsePath % (x2-rx, y1+ry, rx, -ry, 0, 90) )
|
|
||||||
rrCode.append('closepath')
|
|
||||||
|
|
||||||
self._fillAndStroke(rrCode)
|
|
||||||
|
|
||||||
def ellipse(self, x1,y1, x2,y2):
|
|
||||||
"""Draw an orthogonal ellipse inscribed within the rectangle x1,y1,x2,y2.
|
|
||||||
These should have x1<x2 and y1<y2."""
|
|
||||||
#Just invoke drawArc to actually draw the ellipse
|
|
||||||
self.drawArc(x1,y1, x2,y2)
|
|
||||||
|
|
||||||
def circle(self, xc, yc, r):
|
|
||||||
self.ellipse(xc-r,yc-r, xc+r,yc+r)
|
|
||||||
|
|
||||||
def drawArc(self, x1,y1, x2,y2, startAng=0, extent=360, fromcenter=0):
|
|
||||||
"""Draw a partial ellipse inscribed within the rectangle x1,y1,x2,y2,
|
|
||||||
starting at startAng degrees and covering extent degrees. Angles
|
|
||||||
start with 0 to the right (+x) and increase counter-clockwise.
|
|
||||||
These should have x1<x2 and y1<y2."""
|
|
||||||
#calculate centre of ellipse
|
|
||||||
#print "x1,y1,x2,y2,startAng,extent,fromcenter", x1,y1,x2,y2,startAng,extent,fromcenter
|
|
||||||
cx, cy = (x1+x2)/2.0, (y1+y2)/2.0
|
|
||||||
rx, ry = (x2-x1)/2.0, (y2-y1)/2.0
|
|
||||||
|
|
||||||
codeline = self._genArcCode(x1, y1, x2, y2, startAng, extent)
|
|
||||||
|
|
||||||
startAngleRadians = math.pi*startAng/180.0
|
|
||||||
extentRadians = math.pi*extent/180.0
|
|
||||||
endAngleRadians = startAngleRadians + extentRadians
|
|
||||||
|
|
||||||
codelineAppended = 0
|
|
||||||
|
|
||||||
# fill portion
|
|
||||||
|
|
||||||
if self._fillColor != None:
|
|
||||||
self.setColor(self._fillColor)
|
|
||||||
self.code.append(codeline)
|
|
||||||
codelineAppended = 1
|
|
||||||
if self._strokeColor!=None: self.code.append('gsave')
|
|
||||||
self.lineTo(cx,cy)
|
|
||||||
self.code.append('eofill')
|
|
||||||
if self._strokeColor!=None: self.code.append('grestore')
|
|
||||||
|
|
||||||
# stroke portion
|
|
||||||
if self._strokeColor != None:
|
|
||||||
# this is a bit hacked up. There is certainly a better way...
|
|
||||||
self.setColor(self._strokeColor)
|
|
||||||
(startx, starty) = (cx+rx*math.cos(startAngleRadians), cy+ry*math.sin(startAngleRadians))
|
|
||||||
if not codelineAppended:
|
|
||||||
self.code.append(codeline)
|
|
||||||
if fromcenter:
|
|
||||||
# move to center
|
|
||||||
self.lineTo(cx,cy)
|
|
||||||
self.lineTo(startx, starty)
|
|
||||||
self.code.append('closepath')
|
|
||||||
self.code.append('stroke')
|
|
||||||
|
|
||||||
def _genArcCode(self, x1, y1, x2, y2, startAng, extent):
|
|
||||||
"Calculate the path for an arc inscribed in rectangle defined by (x1,y1),(x2,y2)"
|
|
||||||
#calculate semi-minor and semi-major axes of ellipse
|
|
||||||
xScale = abs((x2-x1)/2.0)
|
|
||||||
yScale = abs((y2-y1)/2.0)
|
|
||||||
#calculate centre of ellipse
|
|
||||||
x, y = (x1+x2)/2.0, (y1+y2)/2.0
|
|
||||||
|
|
||||||
codeline = 'matrix currentmatrix %s %s translate %s %s scale 0 0 1 %s %s %s setmatrix'
|
|
||||||
|
|
||||||
if extent >= 0:
|
|
||||||
arc='arc'
|
|
||||||
else:
|
|
||||||
arc='arcn'
|
|
||||||
data = (x,y, xScale, yScale, startAng, startAng+extent, arc)
|
|
||||||
|
|
||||||
return codeline % data
|
|
||||||
|
|
||||||
def polygon(self, p, closed=0, stroke=1, fill=1):
|
|
||||||
assert len(p) >= 2, 'Polygon must have 2 or more points'
|
|
||||||
|
|
||||||
start = p[0]
|
|
||||||
p = p[1:]
|
|
||||||
|
|
||||||
polyCode = []
|
|
||||||
polyCode.append("%s m" % fp_str(start))
|
|
||||||
for point in p:
|
|
||||||
polyCode.append("%s l" % fp_str(point))
|
|
||||||
if closed:
|
|
||||||
polyCode.append("closepath")
|
|
||||||
|
|
||||||
self._fillAndStroke(polyCode,stroke=stroke,fill=fill)
|
|
||||||
|
|
||||||
def lines(self, lineList, color=None, width=None):
|
|
||||||
if self._strokeColor != None:
|
|
||||||
self._setColor(self._strokeColor)
|
|
||||||
codeline = '%s m %s l stroke'
|
|
||||||
for line in lineList:
|
|
||||||
self.code.append(codeline % (fp_str(line[0]),fp_str(line[1])))
|
|
||||||
|
|
||||||
def moveTo(self,x,y):
|
|
||||||
self.code.append('%s m' % fp_str(x, y))
|
|
||||||
|
|
||||||
def lineTo(self,x,y):
|
|
||||||
self.code.append('%s l' % fp_str(x, y))
|
|
||||||
|
|
||||||
def curveTo(self,x1,y1,x2,y2,x3,y3):
|
|
||||||
self.code.append('%s c' % fp_str(x1,y1,x2,y2,x3,y3))
|
|
||||||
|
|
||||||
def closePath(self):
|
|
||||||
self.code.append('closepath')
|
|
||||||
|
|
||||||
def polyLine(self, p):
|
|
||||||
assert len(p) >= 1, 'Polyline must have 1 or more points'
|
|
||||||
if self._strokeColor != None:
|
|
||||||
self.setColor(self._strokeColor)
|
|
||||||
self.moveTo(p[0][0], p[0][1])
|
|
||||||
for t in p[1:]:
|
|
||||||
self.lineTo(t[0], t[1])
|
|
||||||
self.code.append('stroke')
|
|
||||||
|
|
||||||
|
|
||||||
def drawFigure(self, partList, closed=0):
|
|
||||||
figureCode = []
|
|
||||||
first = 1
|
|
||||||
|
|
||||||
for part in partList:
|
|
||||||
op = part[0]
|
|
||||||
args = list(part[1:])
|
|
||||||
|
|
||||||
if op == figureLine:
|
|
||||||
if first:
|
|
||||||
first = 0
|
|
||||||
figureCode.append("%s m" % fp_str(args[:2]))
|
|
||||||
else:
|
|
||||||
figureCode.append("%s l" % fp_str(args[:2]))
|
|
||||||
figureCode.append("%s l" % fp_str(args[2:]))
|
|
||||||
|
|
||||||
elif op == figureArc:
|
|
||||||
first = 0
|
|
||||||
x1,y1,x2,y2,startAngle,extent = args[:6]
|
|
||||||
figureCode.append(self._genArcCode(x1,y1,x2,y2,startAngle,extent))
|
|
||||||
|
|
||||||
elif op == figureCurve:
|
|
||||||
if first:
|
|
||||||
first = 0
|
|
||||||
figureCode.append("%s m" % fp_str(args[:2]))
|
|
||||||
else:
|
|
||||||
figureCode.append("%s l" % fp_str(args[:2]))
|
|
||||||
figureCode.append("%s curveto" % fp_str(args[2:]))
|
|
||||||
else:
|
|
||||||
raise TypeError, "unknown figure operator: "+op
|
|
||||||
|
|
||||||
if closed:
|
|
||||||
figureCode.append("closepath")
|
|
||||||
self._fillAndStroke(figureCode)
|
|
||||||
|
|
||||||
def _fillAndStroke(self,code,clip=0,fill=1,stroke=1):
|
|
||||||
fill = self._fillColor and fill
|
|
||||||
stroke = self._strokeColor and stroke
|
|
||||||
if fill or stroke or clip:
|
|
||||||
self.code.extend(code)
|
|
||||||
if fill:
|
|
||||||
if stroke or clip: self.code.append("gsave")
|
|
||||||
self.setColor(self._fillColor)
|
|
||||||
self.code.append("eofill")
|
|
||||||
if stroke or clip: self.code.append("grestore")
|
|
||||||
if stroke:
|
|
||||||
if clip: self.code.append("gsave")
|
|
||||||
self.setColor(self._strokeColor)
|
|
||||||
self.code.append("stroke")
|
|
||||||
if clip: self.code.append("grestore")
|
|
||||||
if clip:
|
|
||||||
self.code.append("clip")
|
|
||||||
self.code.append("newpath")
|
|
||||||
|
|
||||||
|
|
||||||
def translate(self,x,y):
|
|
||||||
self.code.append('%s translate' % fp_str(x,y))
|
|
||||||
|
|
||||||
def scale(self,x,y):
|
|
||||||
self.code.append('%s scale' % fp_str(x,y))
|
|
||||||
|
|
||||||
def transform(self,a,b,c,d,e,f):
|
|
||||||
self.code.append('[%s] concat' % fp_str(a,b,c,d,e,f))
|
|
||||||
|
|
||||||
def _drawTimeResize(self,w,h):
|
|
||||||
'''if this is used we're probably in the wrong world'''
|
|
||||||
self.width, self.height = w, h
|
|
||||||
|
|
||||||
############################################################################################
|
|
||||||
# drawImage(self. image, x1, y1, x2=None, y2=None) is now defined by either _drawImageLevel1
|
|
||||||
# ._drawImageLevel2, the choice is made in .__init__ depending on option
|
|
||||||
def _drawImageLevel1(self, image, x1, y1, x2=None,y2=None):
|
|
||||||
# Postscript Level1 version available for fallback mode when Level2 doesn't work
|
|
||||||
"""drawImage(self,image,x1,y1,x2=None,y2=None) : If x2 and y2 are ommitted, they are
|
|
||||||
calculated from image size. (x1,y1) is upper left of image, (x2,y2) is lower right of
|
|
||||||
image in piddle coordinates."""
|
|
||||||
# For now let's start with 24 bit RGB images (following piddlePDF again)
|
|
||||||
component_depth = 8
|
|
||||||
myimage = image.convert('RGB')
|
|
||||||
imgwidth, imgheight = myimage.size
|
|
||||||
if not x2:
|
|
||||||
x2 = imgwidth + x1
|
|
||||||
if not y2:
|
|
||||||
y2 = y1 + imgheight
|
|
||||||
drawwidth = x2 - x1
|
|
||||||
drawheight = y2 - y1
|
|
||||||
#print 'Image size (%d, %d); Draw size (%d, %d)' % (imgwidth, imgheight, drawwidth, drawheight)
|
|
||||||
# now I need to tell postscript how big image is
|
|
||||||
|
|
||||||
# "image operators assume that they receive sample data from
|
|
||||||
# their data source in x-axis major index order. The coordinate
|
|
||||||
# of the lower-left corner of the first sample is (0,0), of the
|
|
||||||
# second (1,0) and so on" -PS2 ref manual p. 215
|
|
||||||
#
|
|
||||||
# The ImageMatrix maps unit squre of user space to boundary of the source image
|
|
||||||
#
|
|
||||||
|
|
||||||
# The CurrentTransformationMatrix (CTM) maps the unit square of
|
|
||||||
# user space to the rect...on the page that is to receive the
|
|
||||||
# image. A common ImageMatrix is [width 0 0 -height 0 height]
|
|
||||||
# (for a left to right, top to bottom image )
|
|
||||||
|
|
||||||
# first let's map the user coordinates start at offset x1,y1 on page
|
|
||||||
|
|
||||||
self.code.extend([
|
|
||||||
'gsave',
|
|
||||||
'%s %s translate' % (x1,-y1 - drawheight), # need to start are lower left of image
|
|
||||||
'%s %s scale' % (drawwidth,drawheight),
|
|
||||||
'/scanline %d 3 mul string def' % imgwidth # scanline by multiples of image width
|
|
||||||
])
|
|
||||||
|
|
||||||
# now push the dimensions and depth info onto the stack
|
|
||||||
# and push the ImageMatrix to map the source to the target rectangle (see above)
|
|
||||||
# finally specify source (PS2 pp. 225 ) and by exmample
|
|
||||||
self.code.extend([
|
|
||||||
'%s %s %s' % (imgwidth, imgheight, component_depth),
|
|
||||||
'[%s %s %s %s %s %s]' % (imgwidth, 0, 0, -imgheight, 0, imgheight),
|
|
||||||
'{ currentfile scanline readhexstring pop } false 3',
|
|
||||||
'colorimage '
|
|
||||||
])
|
|
||||||
|
|
||||||
# data source output--now we just need to deliver a hex encode
|
|
||||||
# series of lines of the right overall size can follow
|
|
||||||
# piddlePDF again
|
|
||||||
|
|
||||||
rawimage = myimage.tostring()
|
|
||||||
assert(len(rawimage) == imgwidth*imgheight, 'Wrong amount of data for image')
|
|
||||||
#compressed = zlib.compress(rawimage) # no zlib at moment
|
|
||||||
hex_encoded = self._AsciiHexEncode(rawimage)
|
|
||||||
|
|
||||||
# write in blocks of 78 chars per line
|
|
||||||
outstream = getStringIO(hex_encoded)
|
|
||||||
|
|
||||||
dataline = outstream.read(78)
|
|
||||||
while dataline <> "":
|
|
||||||
self.code.append(dataline)
|
|
||||||
dataline= outstream.read(78)
|
|
||||||
self.code.append('% end of image data') # for clarity
|
|
||||||
self.code.append('grestore') # return coordinates to normal
|
|
||||||
|
|
||||||
# end of drawImage
|
|
||||||
def _AsciiHexEncode(self, input): # also based on piddlePDF
|
|
||||||
"Helper function used by images"
|
|
||||||
output = getStringIO()
|
|
||||||
for char in input:
|
|
||||||
output.write('%02x' % ord(char))
|
|
||||||
return output.getvalue()
|
|
||||||
|
|
||||||
def _drawImageLevel2(self, image, x1,y1, x2=None,y2=None): # Postscript Level2 version
|
|
||||||
'''At present we're handling only PIL'''
|
|
||||||
### what sort of image are we to draw
|
|
||||||
if image.mode=='L' :
|
|
||||||
imBitsPerComponent = 8
|
|
||||||
imNumComponents = 1
|
|
||||||
myimage = image
|
|
||||||
elif image.mode == '1':
|
|
||||||
myimage = image.convert('L')
|
|
||||||
imNumComponents = 1
|
|
||||||
myimage = image
|
|
||||||
else :
|
|
||||||
myimage = image.convert('RGB')
|
|
||||||
imNumComponents = 3
|
|
||||||
imBitsPerComponent = 8
|
|
||||||
|
|
||||||
imwidth, imheight = myimage.size
|
|
||||||
if not x2:
|
|
||||||
x2 = imwidth + x1
|
|
||||||
if not y2:
|
|
||||||
y2 = y1 + imheight
|
|
||||||
drawwidth = x2 - x1
|
|
||||||
drawheight = y2 - y1
|
|
||||||
self.code.extend([
|
|
||||||
'gsave',
|
|
||||||
'%s %s translate' % (x1,-y1 - drawheight), # need to start are lower left of image
|
|
||||||
'%s %s scale' % (drawwidth,drawheight)])
|
|
||||||
|
|
||||||
if imNumComponents == 3 :
|
|
||||||
self.code.append('/DeviceRGB setcolorspace')
|
|
||||||
elif imNumComponents == 1 :
|
|
||||||
self.code.append('/DeviceGray setcolorspace')
|
|
||||||
# create the image dictionary
|
|
||||||
self.code.append("""
|
|
||||||
<<
|
|
||||||
/ImageType 1
|
|
||||||
/Width %d /Height %d %% dimensions of source image
|
|
||||||
/BitsPerComponent %d""" % (imwidth, imheight, imBitsPerComponent) )
|
|
||||||
|
|
||||||
if imNumComponents == 1:
|
|
||||||
self.code.append('/Decode [0 1]')
|
|
||||||
if imNumComponents == 3:
|
|
||||||
self.code.append('/Decode [0 1 0 1 0 1] %% decode color values normally')
|
|
||||||
|
|
||||||
self.code.extend([ '/ImageMatrix [%s 0 0 %s 0 %s]' % (imwidth, -imheight, imheight),
|
|
||||||
'/DataSource currentfile /ASCIIHexDecode filter',
|
|
||||||
'>> % End image dictionary',
|
|
||||||
'image'])
|
|
||||||
# after image operator just need to dump image dat to file as hexstring
|
|
||||||
rawimage = myimage.tostring()
|
|
||||||
assert(len(rawimage) == imwidth*imheight, 'Wrong amount of data for image')
|
|
||||||
#compressed = zlib.compress(rawimage) # no zlib at moment
|
|
||||||
hex_encoded = self._AsciiHexEncode(rawimage)
|
|
||||||
|
|
||||||
# write in blocks of 78 chars per line
|
|
||||||
outstream = getStringIO(hex_encoded)
|
|
||||||
|
|
||||||
dataline = outstream.read(78)
|
|
||||||
while dataline <> "":
|
|
||||||
self.code.append(dataline)
|
|
||||||
dataline= outstream.read(78)
|
|
||||||
self.code.append('> % end of image data') # > is EOD for hex encoded filterfor clarity
|
|
||||||
self.code.append('grestore') # return coordinates to normal
|
|
||||||
|
|
||||||
# renderpdf - draws them onto a canvas
|
|
||||||
"""Usage:
|
|
||||||
from reportlab.graphics import renderPS
|
|
||||||
renderPS.draw(drawing, canvas, x, y)
|
|
||||||
Execute the script to see some test drawings."""
|
|
||||||
from shapes import *
|
|
||||||
|
|
||||||
# hack so we only get warnings once each
|
|
||||||
#warnOnce = WarnOnce()
|
|
||||||
|
|
||||||
# the main entry point for users...
|
|
||||||
def draw(drawing, canvas, x=0, y=0, showBoundary=rl_config.showBoundary):
|
|
||||||
"""As it says"""
|
|
||||||
R = _PSRenderer()
|
|
||||||
R.draw(renderScaledDrawing(drawing), canvas, x, y, showBoundary=showBoundary)
|
|
||||||
|
|
||||||
def _pointsFromList(L):
|
|
||||||
'''
|
|
||||||
given a list of coordinates [x0, y0, x1, y1....]
|
|
||||||
produce a list of points [(x0,y0), (y1,y0),....]
|
|
||||||
'''
|
|
||||||
P=[]
|
|
||||||
for i in range(0,len(L),2):
|
|
||||||
P.append((L[i],L[i+1]))
|
|
||||||
return P
|
|
||||||
|
|
||||||
class _PSRenderer(Renderer):
|
|
||||||
"""This draws onto a EPS document. It needs to be a class
|
|
||||||
rather than a function, as some EPS-specific state tracking is
|
|
||||||
needed outside of the state info in the SVG model."""
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
self._tracker = StateTracker()
|
|
||||||
|
|
||||||
def drawNode(self, node):
|
|
||||||
"""This is the recursive method called for each node
|
|
||||||
in the tree"""
|
|
||||||
self._canvas.comment('begin node %s'%`node`)
|
|
||||||
color = self._canvas._color
|
|
||||||
if not (isinstance(node, Path) and node.isClipPath):
|
|
||||||
self._canvas.saveState()
|
|
||||||
|
|
||||||
#apply state changes
|
|
||||||
deltas = getStateDelta(node)
|
|
||||||
self._tracker.push(deltas)
|
|
||||||
self.applyStateChanges(deltas, {})
|
|
||||||
|
|
||||||
#draw the object, or recurse
|
|
||||||
self.drawNodeDispatcher(node)
|
|
||||||
|
|
||||||
rDeltas = self._tracker.pop()
|
|
||||||
if not (isinstance(node, Path) and node.isClipPath):
|
|
||||||
self._canvas.restoreState()
|
|
||||||
self._canvas.comment('end node %s'%`node`)
|
|
||||||
self._canvas._color = color
|
|
||||||
|
|
||||||
#restore things we might have lost (without actually doing anything).
|
|
||||||
for k, v in rDeltas.items():
|
|
||||||
if self._restores.has_key(k):
|
|
||||||
setattr(self._canvas,self._restores[k],v)
|
|
||||||
|
|
||||||
## _restores = {'stroke':'_stroke','stroke_width': '_lineWidth','stroke_linecap':'_lineCap',
|
|
||||||
## 'stroke_linejoin':'_lineJoin','fill':'_fill','font_family':'_font',
|
|
||||||
## 'font_size':'_fontSize'}
|
|
||||||
_restores = {'strokeColor':'_strokeColor','strokeWidth': '_lineWidth','strokeLineCap':'_lineCap',
|
|
||||||
'strokeLineJoin':'_lineJoin','fillColor':'_fillColor','fontName':'_font',
|
|
||||||
'fontSize':'_fontSize'}
|
|
||||||
|
|
||||||
def drawRect(self, rect):
|
|
||||||
if rect.rx == rect.ry == 0:
|
|
||||||
#plain old rectangle
|
|
||||||
self._canvas.rect(
|
|
||||||
rect.x, rect.y,
|
|
||||||
rect.x+rect.width, rect.y+rect.height)
|
|
||||||
else:
|
|
||||||
#cheat and assume ry = rx; better to generalize
|
|
||||||
#pdfgen roundRect function. TODO
|
|
||||||
self._canvas.roundRect(
|
|
||||||
rect.x, rect.y,
|
|
||||||
rect.x+rect.width, rect.y+rect.height, rect.rx, rect.ry
|
|
||||||
)
|
|
||||||
|
|
||||||
def drawLine(self, line):
|
|
||||||
if self._canvas._strokeColor:
|
|
||||||
self._canvas.line(line.x1, line.y1, line.x2, line.y2)
|
|
||||||
|
|
||||||
def drawCircle(self, circle):
|
|
||||||
self._canvas.circle( circle.cx, circle.cy, circle.r)
|
|
||||||
|
|
||||||
def drawWedge(self, wedge):
|
|
||||||
yradius, radius1, yradius1 = wedge._xtraRadii()
|
|
||||||
if (radius1==0 or radius1 is None) and (yradius1==0 or yradius1 is None):
|
|
||||||
startangledegrees = wedge.startangledegrees
|
|
||||||
endangledegrees = wedge.endangledegrees
|
|
||||||
centerx= wedge.centerx
|
|
||||||
centery = wedge.centery
|
|
||||||
radius = wedge.radius
|
|
||||||
extent = endangledegrees - startangledegrees
|
|
||||||
self._canvas.drawArc(centerx-radius, centery-yradius, centerx+radius, centery+yradius,
|
|
||||||
startangledegrees, extent, fromcenter=1)
|
|
||||||
else:
|
|
||||||
self.drawPolygon(wedge.asPolygon())
|
|
||||||
|
|
||||||
def drawPolyLine(self, p):
|
|
||||||
if self._canvas._strokeColor:
|
|
||||||
self._canvas.polyLine(_pointsFromList(p.points))
|
|
||||||
|
|
||||||
def drawEllipse(self, ellipse):
|
|
||||||
#need to convert to pdfgen's bounding box representation
|
|
||||||
x1 = ellipse.cx - ellipse.rx
|
|
||||||
x2 = ellipse.cx + ellipse.rx
|
|
||||||
y1 = ellipse.cy - ellipse.ry
|
|
||||||
y2 = ellipse.cy + ellipse.ry
|
|
||||||
self._canvas.ellipse(x1,y1,x2,y2)
|
|
||||||
|
|
||||||
def drawPolygon(self, p):
|
|
||||||
self._canvas.polygon(_pointsFromList(p.points), closed=1)
|
|
||||||
|
|
||||||
def drawString(self, stringObj):
|
|
||||||
if self._canvas._fillColor:
|
|
||||||
S = self._tracker.getState()
|
|
||||||
text_anchor, x, y, text = S['textAnchor'], stringObj.x,stringObj.y,stringObj.text
|
|
||||||
if not text_anchor in ['start','inherited']:
|
|
||||||
font, fontSize = S['fontName'], S['fontSize']
|
|
||||||
textLen = stringWidth(text, font,fontSize)
|
|
||||||
if text_anchor=='end':
|
|
||||||
x = x-textLen
|
|
||||||
elif text_anchor=='middle':
|
|
||||||
x = x - textLen/2
|
|
||||||
else:
|
|
||||||
raise ValueError, 'bad value for text_anchor '+str(text_anchor)
|
|
||||||
self._canvas.drawString(x,y,text)
|
|
||||||
|
|
||||||
def drawPath(self, path):
|
|
||||||
from reportlab.graphics.shapes import _renderPath
|
|
||||||
c = self._canvas
|
|
||||||
drawFuncs = (c.moveTo, c.lineTo, c.curveTo, c.closePath)
|
|
||||||
isClosed = _renderPath(path, drawFuncs)
|
|
||||||
if not isClosed:
|
|
||||||
c._fillColor = None
|
|
||||||
c._fillAndStroke([], clip=path.isClipPath)
|
|
||||||
|
|
||||||
def applyStateChanges(self, delta, newState):
|
|
||||||
"""This takes a set of states, and outputs the operators
|
|
||||||
needed to set those properties"""
|
|
||||||
for key, value in delta.items():
|
|
||||||
if key == 'transform':
|
|
||||||
self._canvas.transform(value[0], value[1], value[2],
|
|
||||||
value[3], value[4], value[5])
|
|
||||||
elif key == 'strokeColor':
|
|
||||||
#this has different semantics in PDF to SVG;
|
|
||||||
#we always have a color, and either do or do
|
|
||||||
#not apply it; in SVG one can have a 'None' color
|
|
||||||
self._canvas.setStrokeColor(value)
|
|
||||||
elif key == 'strokeWidth':
|
|
||||||
self._canvas.setLineWidth(value)
|
|
||||||
elif key == 'strokeLineCap': #0,1,2
|
|
||||||
self._canvas.setLineCap(value)
|
|
||||||
elif key == 'strokeLineJoin':
|
|
||||||
self._canvas.setLineJoin(value)
|
|
||||||
elif key == 'strokeDashArray':
|
|
||||||
if value:
|
|
||||||
self._canvas.setDash(value)
|
|
||||||
else:
|
|
||||||
self._canvas.setDash()
|
|
||||||
## elif key == 'stroke_opacity':
|
|
||||||
## warnOnce('Stroke Opacity not supported yet')
|
|
||||||
elif key == 'fillColor':
|
|
||||||
#this has different semantics in PDF to SVG;
|
|
||||||
#we always have a color, and either do or do
|
|
||||||
#not apply it; in SVG one can have a 'None' color
|
|
||||||
self._canvas.setFillColor(value)
|
|
||||||
## elif key == 'fill_rule':
|
|
||||||
## warnOnce('Fill rules not done yet')
|
|
||||||
## elif key == 'fill_opacity':
|
|
||||||
## warnOnce('Fill opacity not done yet')
|
|
||||||
elif key in ['fontSize', 'fontName']:
|
|
||||||
# both need setting together in PDF
|
|
||||||
# one or both might be in the deltas,
|
|
||||||
# so need to get whichever is missing
|
|
||||||
fontname = delta.get('fontName', self._canvas._font)
|
|
||||||
fontsize = delta.get('fontSize', self._canvas._fontSize)
|
|
||||||
self._canvas.setFont(fontname, fontsize)
|
|
||||||
|
|
||||||
def drawImage(self, image):
|
|
||||||
from reportlab.lib.utils import ImageReader
|
|
||||||
im = ImageReader(image.path)
|
|
||||||
x0 = image.x
|
|
||||||
y0 = image.y
|
|
||||||
x1 = image.width
|
|
||||||
if x1 is not None: x1 += x0
|
|
||||||
y1 = image.height
|
|
||||||
if y1 is not None: y1 += y0
|
|
||||||
self._canvas.drawImage(im._image,x0,y0,x1,y1)
|
|
||||||
|
|
||||||
def drawToFile(d,fn, showBoundary=rl_config.showBoundary):
|
|
||||||
d = renderScaledDrawing(d)
|
|
||||||
c = PSCanvas((d.width,d.height))
|
|
||||||
draw(d, c, 0, 0, showBoundary=showBoundary)
|
|
||||||
c.save(fn)
|
|
||||||
|
|
||||||
def drawToString(d, showBoundary=rl_config.showBoundary):
|
|
||||||
"Returns a PS as a string in memory, without touching the disk"
|
|
||||||
s = getStringIO()
|
|
||||||
drawToFile(d, s, showBoundary=showBoundary)
|
|
||||||
return s.getvalue()
|
|
||||||
|
|
||||||
#########################################################
|
|
||||||
#
|
|
||||||
# test code. First, defin a bunch of drawings.
|
|
||||||
# Routine to draw them comes at the end.
|
|
||||||
#
|
|
||||||
#########################################################
|
|
||||||
def test(outdir='epsout'):
|
|
||||||
import os
|
|
||||||
# print all drawings and their doc strings from the test
|
|
||||||
# file
|
|
||||||
if not os.path.isdir(outdir):
|
|
||||||
os.mkdir(outdir)
|
|
||||||
#grab all drawings from the test module
|
|
||||||
import testshapes
|
|
||||||
drawings = []
|
|
||||||
|
|
||||||
for funcname in dir(testshapes):
|
|
||||||
#if funcname[0:11] == 'getDrawing2':
|
|
||||||
# print 'hacked to only show drawing 2'
|
|
||||||
if funcname[0:10] == 'getDrawing':
|
|
||||||
drawing = eval('testshapes.' + funcname + '()') #execute it
|
|
||||||
docstring = eval('testshapes.' + funcname + '.__doc__')
|
|
||||||
drawings.append((drawing, docstring))
|
|
||||||
|
|
||||||
i = 0
|
|
||||||
for (d, docstring) in drawings:
|
|
||||||
filename = outdir + os.sep + 'renderPS_%d.eps'%i
|
|
||||||
drawToFile(d,filename)
|
|
||||||
print 'saved', filename
|
|
||||||
i = i + 1
|
|
||||||
|
|
||||||
if __name__=='__main__':
|
|
||||||
import sys
|
|
||||||
if len(sys.argv)>1:
|
|
||||||
outdir = sys.argv[1]
|
|
||||||
else:
|
|
||||||
outdir = 'epsout'
|
|
||||||
test(outdir)
|
|
|
@ -1,828 +0,0 @@
|
||||||
"""An experimental SVG renderer for the ReportLab graphics framework.
|
|
||||||
|
|
||||||
This will create SVG code from the ReportLab Graphics API (RLG).
|
|
||||||
To read existing SVG code and convert it into ReportLab graphics
|
|
||||||
objects download the svglib module here:
|
|
||||||
|
|
||||||
http://python.net/~gherman/#svglib
|
|
||||||
"""
|
|
||||||
|
|
||||||
import math, string, types, sys, os
|
|
||||||
from types import StringType
|
|
||||||
from operator import getitem
|
|
||||||
|
|
||||||
from reportlab.pdfbase.pdfmetrics import stringWidth # for font info
|
|
||||||
from reportlab.lib.utils import fp_str
|
|
||||||
from reportlab.lib.colors import black
|
|
||||||
from reportlab.graphics.renderbase import StateTracker, getStateDelta, Renderer, renderScaledDrawing
|
|
||||||
from reportlab.graphics.shapes import STATE_DEFAULTS, Path, UserNode
|
|
||||||
from reportlab.graphics.shapes import * # (only for test0)
|
|
||||||
from reportlab import rl_config
|
|
||||||
from reportlab.lib.utils import getStringIO
|
|
||||||
|
|
||||||
from xml.dom import getDOMImplementation
|
|
||||||
|
|
||||||
|
|
||||||
### some constants ###
|
|
||||||
|
|
||||||
sin = math.sin
|
|
||||||
cos = math.cos
|
|
||||||
pi = math.pi
|
|
||||||
|
|
||||||
LINE_STYLES = 'stroke-width stroke-linecap stroke fill stroke-dasharray'
|
|
||||||
TEXT_STYLES = 'font-family font-size'
|
|
||||||
|
|
||||||
|
|
||||||
### top-level user function ###
|
|
||||||
|
|
||||||
def drawToString(d, showBoundary=rl_config.showBoundary):
|
|
||||||
"Returns a SVG as a string in memory, without touching the disk"
|
|
||||||
s = getStringIO()
|
|
||||||
drawToFile(d, s, showBoundary=showBoundary)
|
|
||||||
return s.getvalue()
|
|
||||||
|
|
||||||
def drawToFile(d, fn, showBoundary=rl_config.showBoundary):
|
|
||||||
d = renderScaledDrawing(d)
|
|
||||||
c = SVGCanvas((d.width, d.height))
|
|
||||||
draw(d, c, 0, 0, showBoundary=showBoundary)
|
|
||||||
c.save(fn)
|
|
||||||
|
|
||||||
|
|
||||||
def draw(drawing, canvas, x=0, y=0, showBoundary=rl_config.showBoundary):
|
|
||||||
"""As it says."""
|
|
||||||
r = _SVGRenderer()
|
|
||||||
r.draw(renderScaledDrawing(drawing), canvas, x, y, showBoundary=showBoundary)
|
|
||||||
|
|
||||||
|
|
||||||
### helper functions ###
|
|
||||||
|
|
||||||
def _pointsFromList(L):
|
|
||||||
"""
|
|
||||||
given a list of coordinates [x0, y0, x1, y1....]
|
|
||||||
produce a list of points [(x0,y0), (y1,y0),....]
|
|
||||||
"""
|
|
||||||
|
|
||||||
P=[]
|
|
||||||
for i in range(0,len(L), 2):
|
|
||||||
P.append((L[i], L[i+1]))
|
|
||||||
|
|
||||||
return P
|
|
||||||
|
|
||||||
|
|
||||||
def transformNode(doc, newTag, node=None, **attrDict):
|
|
||||||
"""Transform a DOM node into new node and copy selected attributes.
|
|
||||||
|
|
||||||
Creates a new DOM node with tag name 'newTag' for document 'doc'
|
|
||||||
and copies selected attributes from an existing 'node' as provided
|
|
||||||
in 'attrDict'. The source 'node' can be None. Attribute values will
|
|
||||||
be converted to strings.
|
|
||||||
|
|
||||||
E.g.
|
|
||||||
|
|
||||||
n = transformNode(doc, "node1", x="0", y="1")
|
|
||||||
-> DOM node for <node1 x="0" y="1"/>
|
|
||||||
|
|
||||||
n = transformNode(doc, "node1", x=0, y=1+1)
|
|
||||||
-> DOM node for <node1 x="0" y="2"/>
|
|
||||||
|
|
||||||
n = transformNode(doc, "node1", node0, x="x0", y="x0", zoo=bar())
|
|
||||||
-> DOM node for <node1 x="[node0.x0]" y="[node0.y0]" zoo="[bar()]"/>
|
|
||||||
"""
|
|
||||||
|
|
||||||
newNode = doc.createElement(newTag)
|
|
||||||
for newAttr, attr in attrDict.items():
|
|
||||||
sattr = str(attr)
|
|
||||||
if not node:
|
|
||||||
newNode.setAttribute(newAttr, sattr)
|
|
||||||
else:
|
|
||||||
attrVal = node.getAttribute(sattr)
|
|
||||||
newNode.setAttribute(newAttr, attrVal or sattr)
|
|
||||||
|
|
||||||
return newNode
|
|
||||||
|
|
||||||
|
|
||||||
### classes ###
|
|
||||||
|
|
||||||
class SVGCanvas:
|
|
||||||
def __init__(self, size=(300,300)):
|
|
||||||
self.verbose = 0
|
|
||||||
self.width, self.height = self.size = size
|
|
||||||
# self.height = size[1]
|
|
||||||
self.code = []
|
|
||||||
self.style = {}
|
|
||||||
self.path = ''
|
|
||||||
self._strokeColor = self._fillColor = self._lineWidth = \
|
|
||||||
self._font = self._fontSize = self._lineCap = \
|
|
||||||
self._lineJoin = self._color = None
|
|
||||||
|
|
||||||
implementation = getDOMImplementation('minidom')
|
|
||||||
self.doc = implementation.createDocument(None, "svg", None)
|
|
||||||
self.svg = self.doc.documentElement
|
|
||||||
self.svg.setAttribute("width", str(size[0]))
|
|
||||||
self.svg.setAttribute("height", str(self.height))
|
|
||||||
|
|
||||||
title = self.doc.createElement('title')
|
|
||||||
text = self.doc.createTextNode('...')
|
|
||||||
title.appendChild(text)
|
|
||||||
self.svg.appendChild(title)
|
|
||||||
|
|
||||||
desc = self.doc.createElement('desc')
|
|
||||||
text = self.doc.createTextNode('...')
|
|
||||||
desc.appendChild(text)
|
|
||||||
self.svg.appendChild(desc)
|
|
||||||
|
|
||||||
self.setFont(STATE_DEFAULTS['fontName'], STATE_DEFAULTS['fontSize'])
|
|
||||||
self.setStrokeColor(STATE_DEFAULTS['strokeColor'])
|
|
||||||
self.setLineCap(2)
|
|
||||||
self.setLineJoin(0)
|
|
||||||
self.setLineWidth(1)
|
|
||||||
|
|
||||||
# Add a rectangular clipping path identical to view area.
|
|
||||||
clipPath = transformNode(self.doc, "clipPath", id="clip")
|
|
||||||
clipRect = transformNode(self.doc, "rect", x=0, y=0,
|
|
||||||
width=self.width, height=self.height)
|
|
||||||
clipPath.appendChild(clipRect)
|
|
||||||
self.svg.appendChild(clipPath)
|
|
||||||
|
|
||||||
self.groupTree = transformNode(self.doc, "g",
|
|
||||||
id="group",
|
|
||||||
transform="scale(1,-1) translate(0,-%d)" % self.height,
|
|
||||||
style="clip-path: url(#clip)")
|
|
||||||
self.svg.appendChild(self.groupTree)
|
|
||||||
self.currGroup = self.groupTree
|
|
||||||
|
|
||||||
|
|
||||||
def save(self, f=None):
|
|
||||||
if type(f) is StringType:
|
|
||||||
file = open(f, 'w')
|
|
||||||
else:
|
|
||||||
file = f
|
|
||||||
|
|
||||||
file.write("""\
|
|
||||||
<?xml version="1.0" encoding="iso-8859-1"?>
|
|
||||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20000303 Stylable//EN" "http://www.w3.org/TR/2000/03/WD-SVG-20000303/DTD/svg-20000303-stylable.dtd" >\n""")
|
|
||||||
|
|
||||||
# use = self.doc.createElement('use')
|
|
||||||
# use.setAttribute("xlink:href", "#group")
|
|
||||||
# use.setAttribute("transform", "scale(1, -1)")
|
|
||||||
# self.svg.appendChild(use)
|
|
||||||
|
|
||||||
result = self.svg.toprettyxml(indent=" ")
|
|
||||||
file.write(result)
|
|
||||||
|
|
||||||
if file is not f:
|
|
||||||
file.close()
|
|
||||||
|
|
||||||
|
|
||||||
### helpers ###
|
|
||||||
|
|
||||||
def NOTUSED_stringWidth(self, s, font=None, fontSize=None):
|
|
||||||
"""Return the logical width of the string if it were drawn
|
|
||||||
in the current font (defaults to self.font).
|
|
||||||
"""
|
|
||||||
|
|
||||||
font = font or self._font
|
|
||||||
fontSize = fontSize or self._fontSize
|
|
||||||
|
|
||||||
return stringWidth(s, font, fontSize)
|
|
||||||
|
|
||||||
|
|
||||||
def _formatStyle(self, include=''):
|
|
||||||
str = ''
|
|
||||||
include = string.split(include)
|
|
||||||
keys = self.style.keys()
|
|
||||||
if include:
|
|
||||||
#2.1-safe version of the line below follows:
|
|
||||||
#keys = filter(lambda k: k in include, keys)
|
|
||||||
tmp = []
|
|
||||||
for word in keys:
|
|
||||||
if word in include:
|
|
||||||
tmp.append(word)
|
|
||||||
keys = tmp
|
|
||||||
|
|
||||||
items = []
|
|
||||||
for k in keys:
|
|
||||||
items.append((k, self.style[k]))
|
|
||||||
items = map(lambda i: "%s: %s"%(i[0], i[1]), items)
|
|
||||||
str = string.join(items, '; ') + ';'
|
|
||||||
|
|
||||||
return str
|
|
||||||
|
|
||||||
|
|
||||||
def _escape(self, s):
|
|
||||||
"""
|
|
||||||
return a copy of string s with special characters in postscript strings
|
|
||||||
escaped with backslashes.
|
|
||||||
Have not handled characters that are converted normally in python strings
|
|
||||||
i.e. \n -> newline
|
|
||||||
"""
|
|
||||||
|
|
||||||
str = string.replace(s, chr(0x5C), r'\\' )
|
|
||||||
str = string.replace(str, '(', '\(' )
|
|
||||||
str = string.replace(str, ')', '\)')
|
|
||||||
return str
|
|
||||||
|
|
||||||
|
|
||||||
def _genArcCode(self, x1, y1, x2, y2, startAng, extent):
|
|
||||||
"""Calculate the path for an arc inscribed in rectangle defined
|
|
||||||
by (x1,y1),(x2,y2)."""
|
|
||||||
|
|
||||||
return
|
|
||||||
|
|
||||||
#calculate semi-minor and semi-major axes of ellipse
|
|
||||||
xScale = abs((x2-x1)/2.0)
|
|
||||||
yScale = abs((y2-y1)/2.0)
|
|
||||||
#calculate centre of ellipse
|
|
||||||
x, y = (x1+x2)/2.0, (y1+y2)/2.0
|
|
||||||
|
|
||||||
codeline = 'matrix currentmatrix %s %s translate %s %s scale 0 0 1 %s %s %s setmatrix'
|
|
||||||
|
|
||||||
if extent >= 0:
|
|
||||||
arc='arc'
|
|
||||||
else:
|
|
||||||
arc='arcn'
|
|
||||||
data = (x,y, xScale, yScale, startAng, startAng+extent, arc)
|
|
||||||
|
|
||||||
return codeline % data
|
|
||||||
|
|
||||||
|
|
||||||
def _fillAndStroke(self, code, clip=0):
|
|
||||||
path = transformNode(self.doc, "path",
|
|
||||||
d=self.path, style=self._formatStyle(LINE_STYLES))
|
|
||||||
self.currGroup.appendChild(path)
|
|
||||||
self.path = ''
|
|
||||||
|
|
||||||
return
|
|
||||||
|
|
||||||
"""
|
|
||||||
if self._fillColor or self._strokeColor or clip:
|
|
||||||
self.code.extend(code)
|
|
||||||
if self._fillColor:
|
|
||||||
if self._strokeColor or clip:
|
|
||||||
self.code.append("gsave")
|
|
||||||
self.setColor(self._fillColor)
|
|
||||||
self.code.append("eofill")
|
|
||||||
if self._strokeColor or clip:
|
|
||||||
self.code.append("grestore")
|
|
||||||
if self._strokeColor != None:
|
|
||||||
if clip: self.code.append("gsave")
|
|
||||||
self.setColor(self._strokeColor)
|
|
||||||
self.code.append("stroke")
|
|
||||||
if clip: self.code.append("grestore")
|
|
||||||
if clip:
|
|
||||||
self.code.append("clip")
|
|
||||||
self.code.append("newpath")
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
### styles ###
|
|
||||||
|
|
||||||
def setLineCap(self, v):
|
|
||||||
vals = {0:'butt', 1:'round', 2:'square'}
|
|
||||||
if self._lineCap != v:
|
|
||||||
self._lineCap = v
|
|
||||||
self.style['stroke-linecap'] = vals[v]
|
|
||||||
|
|
||||||
|
|
||||||
def setLineJoin(self, v):
|
|
||||||
vals = {0:'miter', 1:'round', 2:'bevel'}
|
|
||||||
if self._lineJoin != v:
|
|
||||||
self._lineJoin = v
|
|
||||||
self.style['stroke-linecap'] = vals[v]
|
|
||||||
|
|
||||||
|
|
||||||
def setDash(self, array=[], phase=0):
|
|
||||||
"""Two notations. Pass two numbers, or an array and phase."""
|
|
||||||
|
|
||||||
join = string.join
|
|
||||||
if type(array) in (types.IntType, types.FloatType):
|
|
||||||
self.style['stroke-dasharray'] = join(map(str, ([array, phase])), ', ')
|
|
||||||
elif type(array) in (types.ListType, types.TupleType) and len(array) > 0:
|
|
||||||
assert phase >= 0, "phase is a length in user space"
|
|
||||||
self.style['stroke-dasharray'] = join(map(str, (array+[phase])), ', ')
|
|
||||||
|
|
||||||
|
|
||||||
def setStrokeColor(self, color):
|
|
||||||
self._strokeColor = color
|
|
||||||
self.setColor(color)
|
|
||||||
if color == None:
|
|
||||||
self.style['stroke'] = 'none'
|
|
||||||
else:
|
|
||||||
r, g, b = color.red, color.green, color.blue
|
|
||||||
self.style['stroke'] = 'rgb(%d%%,%d%%,%d%%)' % (r*100, g*100, b*100)
|
|
||||||
|
|
||||||
|
|
||||||
def setColor(self, color):
|
|
||||||
if self._color != color:
|
|
||||||
self._color = color
|
|
||||||
|
|
||||||
|
|
||||||
def setFillColor(self, color):
|
|
||||||
self._fillColor = color
|
|
||||||
self.setColor(color)
|
|
||||||
if color == None:
|
|
||||||
self.style['fill'] = 'none'
|
|
||||||
else:
|
|
||||||
r, g, b = color.red, color.green, color.blue
|
|
||||||
self.style['fill'] = 'rgb(%d%%,%d%%,%d%%)' % (r*100, g*100, b*100)
|
|
||||||
|
|
||||||
|
|
||||||
def setLineWidth(self, width):
|
|
||||||
if width != self._lineWidth:
|
|
||||||
self._lineWidth = width
|
|
||||||
self.style['stroke-width'] = width
|
|
||||||
|
|
||||||
|
|
||||||
def setFont(self, font, fontSize):
|
|
||||||
if self._font != font or self._fontSize != fontSize:
|
|
||||||
self._font, self._fontSize = (font, fontSize)
|
|
||||||
self.style['font-family'] = font
|
|
||||||
self.style['font-size'] = fontSize
|
|
||||||
|
|
||||||
|
|
||||||
### shapes ###
|
|
||||||
|
|
||||||
def rect(self, x1,y1, x2,y2, rx=8, ry=8):
|
|
||||||
"Draw a rectangle between x1,y1 and x2,y2."
|
|
||||||
|
|
||||||
if self.verbose: print "+++ SVGCanvas.rect"
|
|
||||||
|
|
||||||
rect = transformNode(self.doc, "rect",
|
|
||||||
x=x1, y=y1, width=x2-x1, height=y2-y1,
|
|
||||||
style=self._formatStyle(LINE_STYLES))
|
|
||||||
|
|
||||||
self.currGroup.appendChild(rect)
|
|
||||||
|
|
||||||
|
|
||||||
def roundRect(self, x1,y1, x2,y2, rx=8, ry=8):
|
|
||||||
"""Draw a rounded rectangle between x1,y1 and x2,y2.
|
|
||||||
|
|
||||||
Corners inset as ellipses with x-radius rx and y-radius ry.
|
|
||||||
These should have x1<x2, y1<y2, rx>0, and ry>0.
|
|
||||||
"""
|
|
||||||
|
|
||||||
rect = transformNode(self.doc, "rect",
|
|
||||||
x=x1, y=y1, width=x2-x1, height=y2-y1, rx=rx, ry=ry,
|
|
||||||
style=self._formatStyle(LINE_STYLES))
|
|
||||||
|
|
||||||
self.currGroup.appendChild(rect)
|
|
||||||
|
|
||||||
|
|
||||||
def drawString(self, s, x, y, angle=0):
|
|
||||||
if self.verbose: print "+++ SVGCanvas.drawString"
|
|
||||||
|
|
||||||
if self._fillColor != None:
|
|
||||||
self.setColor(self._fillColor)
|
|
||||||
s = self._escape(s)
|
|
||||||
st = self._formatStyle(TEXT_STYLES)
|
|
||||||
if angle != 0:
|
|
||||||
st = st + " rotate(%f %f %f);" % (angle, x, y)
|
|
||||||
st = st + " fill: %s;" % self.style['fill']
|
|
||||||
text = transformNode(self.doc, "text",
|
|
||||||
x=x, y=y, style=st,
|
|
||||||
transform="translate(0,%d) scale(1,-1)" % (2*y))
|
|
||||||
content = self.doc.createTextNode(s)
|
|
||||||
text.appendChild(content)
|
|
||||||
|
|
||||||
self.currGroup.appendChild(text)
|
|
||||||
|
|
||||||
def drawCentredString(self, s, x, y, angle=0,text_anchor='middle'):
|
|
||||||
if self.verbose: print "+++ SVGCanvas.drawCentredString"
|
|
||||||
|
|
||||||
if self._fillColor != None:
|
|
||||||
if not text_anchor in ['start', 'inherited']:
|
|
||||||
textLen = stringWidth(s,self._font,self._fontSize)
|
|
||||||
if text_anchor=='end':
|
|
||||||
x -= textLen
|
|
||||||
elif text_anchor=='middle':
|
|
||||||
x -= textLen/2.
|
|
||||||
else:
|
|
||||||
raise ValueError, 'bad value for text_anchor ' + str(text_anchor)
|
|
||||||
self.drawString(x,y,text,angle=angle)
|
|
||||||
|
|
||||||
def drawRightString(self, text, x, y, angle=0):
|
|
||||||
self.drawCentredString(text,x,y,angle=angle,text_anchor='end')
|
|
||||||
|
|
||||||
def comment(self, data):
|
|
||||||
"Add a comment."
|
|
||||||
|
|
||||||
comment = self.doc.createComment(data)
|
|
||||||
# self.currGroup.appendChild(comment)
|
|
||||||
|
|
||||||
|
|
||||||
def drawImage(self, image, x1, y1, x2=None, y2=None):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def line(self, x1, y1, x2, y2):
|
|
||||||
if self._strokeColor != None:
|
|
||||||
if 0: # something is wrong with line in my SVG viewer...
|
|
||||||
line = transformNode(self.doc, "line",
|
|
||||||
x=x1, y=y1, x2=x2, y2=y2,
|
|
||||||
style=self._formatStyle(LINE_STYLES))
|
|
||||||
self.currGroup.appendChild(line)
|
|
||||||
path = transformNode(self.doc, "path",
|
|
||||||
d="M %f,%f L %f,%f Z" % (x1,y1,x2,y2),
|
|
||||||
style=self._formatStyle(LINE_STYLES))
|
|
||||||
self.currGroup.appendChild(path)
|
|
||||||
|
|
||||||
|
|
||||||
def ellipse(self, x1, y1, x2, y2):
|
|
||||||
"""Draw an orthogonal ellipse inscribed within the rectangle x1,y1,x2,y2.
|
|
||||||
|
|
||||||
These should have x1<x2 and y1<y2.
|
|
||||||
"""
|
|
||||||
|
|
||||||
ellipse = transformNode(self.doc, "ellipse",
|
|
||||||
cx=(x1+x2)/2.0, cy=(y1+y2)/2.0, rx=(x2-x1)/2.0, ry=(y2-y1)/2.0,
|
|
||||||
style=self._formatStyle(LINE_STYLES))
|
|
||||||
|
|
||||||
self.currGroup.appendChild(ellipse)
|
|
||||||
|
|
||||||
|
|
||||||
def circle(self, xc, yc, r):
|
|
||||||
circle = transformNode(self.doc, "circle",
|
|
||||||
cx=xc, cy=yc, r=r,
|
|
||||||
style=self._formatStyle(LINE_STYLES))
|
|
||||||
|
|
||||||
self.currGroup.appendChild(circle)
|
|
||||||
|
|
||||||
|
|
||||||
def drawCurve(self, x1, y1, x2, y2, x3, y3, x4, y4, closed=0):
|
|
||||||
pass
|
|
||||||
return
|
|
||||||
|
|
||||||
codeline = '%s m %s curveto'
|
|
||||||
data = (fp_str(x1, y1), fp_str(x2, y2, x3, y3, x4, y4))
|
|
||||||
if self._fillColor != None:
|
|
||||||
self.setColor(self._fillColor)
|
|
||||||
self.code.append((codeline % data) + ' eofill')
|
|
||||||
if self._strokeColor != None:
|
|
||||||
self.setColor(self._strokeColor)
|
|
||||||
self.code.append((codeline % data)
|
|
||||||
+ ((closed and ' closepath') or '')
|
|
||||||
+ ' stroke')
|
|
||||||
|
|
||||||
|
|
||||||
def drawArc(self, x1,y1, x2,y2, startAng=0, extent=360, fromcenter=0):
|
|
||||||
"""Draw a partial ellipse inscribed within the rectangle x1,y1,x2,y2.
|
|
||||||
|
|
||||||
Starting at startAng degrees and covering extent degrees. Angles
|
|
||||||
start with 0 to the right (+x) and increase counter-clockwise.
|
|
||||||
These should have x1<x2 and y1<y2.
|
|
||||||
"""
|
|
||||||
|
|
||||||
cx, cy = (x1+x2)/2.0, (y1+y2)/2.0
|
|
||||||
rx, ry = (x2-x1)/2.0, (y2-y1)/2.0
|
|
||||||
mx = rx * cos(startAng*pi/180) + cx
|
|
||||||
my = ry * sin(startAng*pi/180) + cy
|
|
||||||
ax = rx * cos((startAng+extent)*pi/180) + cx
|
|
||||||
ay = ry * sin((startAng+extent)*pi/180) + cy
|
|
||||||
|
|
||||||
str = ''
|
|
||||||
if fromcenter:
|
|
||||||
str = str + "M %f, %f L %f, %f " % (cx, cy, ax, ay)
|
|
||||||
|
|
||||||
if fromcenter:
|
|
||||||
str = str + "A %f, %f %d %d %d %f, %f " % \
|
|
||||||
(rx, ry, 0, extent>=180, 0, mx, my)
|
|
||||||
else:
|
|
||||||
str = str + "M %f, %f A %f, %f %d %d %d %f, %f Z " % \
|
|
||||||
(mx, my, rx, ry, 0, extent>=180, 0, mx, my)
|
|
||||||
|
|
||||||
if fromcenter:
|
|
||||||
str = str + "L %f, %f Z " % (cx, cy)
|
|
||||||
|
|
||||||
path = transformNode(self.doc, "path",
|
|
||||||
d=str, style=self._formatStyle())
|
|
||||||
self.currGroup.appendChild(path)
|
|
||||||
|
|
||||||
|
|
||||||
def polygon(self, points, closed=0):
|
|
||||||
assert len(points) >= 2, 'Polygon must have 2 or more points'
|
|
||||||
|
|
||||||
if self._strokeColor != None:
|
|
||||||
self.setColor(self._strokeColor)
|
|
||||||
pairs = []
|
|
||||||
for i in xrange(len(points)):
|
|
||||||
pairs.append("%f %f" % (points[i]))
|
|
||||||
pts = string.join(pairs, ', ')
|
|
||||||
polyline = transformNode(self.doc, "polygon",
|
|
||||||
points=pts, style=self._formatStyle(LINE_STYLES))
|
|
||||||
self.currGroup.appendChild(polyline)
|
|
||||||
|
|
||||||
# self._fillAndStroke(polyCode)
|
|
||||||
|
|
||||||
|
|
||||||
def lines(self, lineList, color=None, width=None):
|
|
||||||
# print "### lineList", lineList
|
|
||||||
return
|
|
||||||
|
|
||||||
if self._strokeColor != None:
|
|
||||||
self._setColor(self._strokeColor)
|
|
||||||
codeline = '%s m %s l stroke'
|
|
||||||
for line in lineList:
|
|
||||||
self.code.append(codeline % (fp_str(line[0]), fp_str(line[1])))
|
|
||||||
|
|
||||||
|
|
||||||
def polyLine(self, points):
|
|
||||||
assert len(points) >= 1, 'Polyline must have 1 or more points'
|
|
||||||
|
|
||||||
if self._strokeColor != None:
|
|
||||||
self.setColor(self._strokeColor)
|
|
||||||
pairs = []
|
|
||||||
for i in xrange(len(points)):
|
|
||||||
pairs.append("%f %f" % (points[i]))
|
|
||||||
pts = string.join(pairs, ', ')
|
|
||||||
polyline = transformNode(self.doc, "polyline",
|
|
||||||
points=pts, style=self._formatStyle(LINE_STYLES))
|
|
||||||
self.currGroup.appendChild(polyline)
|
|
||||||
|
|
||||||
|
|
||||||
### groups ###
|
|
||||||
|
|
||||||
def startGroup(self):
|
|
||||||
if self.verbose: print "+++ begin SVGCanvas.startGroup"
|
|
||||||
currGroup, group = self.currGroup, transformNode(self.doc, "g", transform="")
|
|
||||||
currGroup.appendChild(group)
|
|
||||||
self.currGroup = group
|
|
||||||
if self.verbose: print "+++ end SVGCanvas.startGroup"
|
|
||||||
return currGroup
|
|
||||||
|
|
||||||
def endGroup(self,currGroup):
|
|
||||||
if self.verbose: print "+++ begin SVGCanvas.endGroup"
|
|
||||||
self.currGroup = currGroup
|
|
||||||
if self.verbose: print "+++ end SVGCanvas.endGroup"
|
|
||||||
|
|
||||||
|
|
||||||
def transform(self, a, b, c, d, e, f):
|
|
||||||
if self.verbose: print "!!! begin SVGCanvas.transform", a, b, c, d, e, f
|
|
||||||
tr = self.currGroup.getAttribute("transform")
|
|
||||||
t = 'matrix(%f, %f, %f, %f, %f, %f)' % (a,b,c,d,e,f)
|
|
||||||
if (a, b, c, d, e, f) != (1, 0, 0, 1, 0, 0):
|
|
||||||
self.currGroup.setAttribute("transform", "%s %s" % (tr, t))
|
|
||||||
|
|
||||||
|
|
||||||
def translate(self, x, y):
|
|
||||||
# probably never used
|
|
||||||
print "!!! begin SVGCanvas.translate"
|
|
||||||
return
|
|
||||||
|
|
||||||
tr = self.currGroup.getAttribute("transform")
|
|
||||||
t = 'translate(%f, %f)' % (x, y)
|
|
||||||
self.currGroup.setAttribute("transform", "%s %s" % (tr, t))
|
|
||||||
|
|
||||||
|
|
||||||
def scale(self, x, y):
|
|
||||||
# probably never used
|
|
||||||
print "!!! begin SVGCanvas.scale"
|
|
||||||
return
|
|
||||||
|
|
||||||
tr = self.groups[-1].getAttribute("transform")
|
|
||||||
t = 'scale(%f, %f)' % (x, y)
|
|
||||||
self.currGroup.setAttribute("transform", "%s %s" % (tr, t))
|
|
||||||
|
|
||||||
|
|
||||||
### paths ###
|
|
||||||
|
|
||||||
def moveTo(self, x, y):
|
|
||||||
self.path = self.path + 'M %f %f ' % (x, y)
|
|
||||||
|
|
||||||
|
|
||||||
def lineTo(self, x, y):
|
|
||||||
self.path = self.path + 'L %f %f ' % (x, y)
|
|
||||||
|
|
||||||
|
|
||||||
def curveTo(self, x1, y1, x2, y2, x3, y3):
|
|
||||||
self.path = self.path + 'C %f %f %f %f %f %f ' % (x1, y1, x2, y2, x3, y3)
|
|
||||||
|
|
||||||
|
|
||||||
def closePath(self):
|
|
||||||
self.path = self.path + 'Z '
|
|
||||||
|
|
||||||
def saveState(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def restoreState(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
class _SVGRenderer(Renderer):
|
|
||||||
"""This draws onto an SVG document.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
self._tracker = StateTracker()
|
|
||||||
self.verbose = 0
|
|
||||||
|
|
||||||
def drawNode(self, node):
|
|
||||||
"""This is the recursive method called for each node in the tree.
|
|
||||||
"""
|
|
||||||
|
|
||||||
if self.verbose: print "### begin _SVGRenderer.drawNode"
|
|
||||||
|
|
||||||
self._canvas.comment('begin node %s'%`node`)
|
|
||||||
color = self._canvas._color
|
|
||||||
if not (isinstance(node, Path) and node.isClipPath):
|
|
||||||
pass # self._canvas.saveState()
|
|
||||||
|
|
||||||
#apply state changes
|
|
||||||
deltas = getStateDelta(node)
|
|
||||||
self._tracker.push(deltas)
|
|
||||||
self.applyStateChanges(deltas, {})
|
|
||||||
|
|
||||||
#draw the object, or recurse
|
|
||||||
self.drawNodeDispatcher(node)
|
|
||||||
|
|
||||||
rDeltas = self._tracker.pop()
|
|
||||||
if not (isinstance(node, Path) and node.isClipPath):
|
|
||||||
pass # self._canvas.restoreState()
|
|
||||||
self._canvas.comment('end node %s'%`node`)
|
|
||||||
self._canvas._color = color
|
|
||||||
|
|
||||||
#restore things we might have lost (without actually doing anything).
|
|
||||||
for k, v in rDeltas.items():
|
|
||||||
if self._restores.has_key(k):
|
|
||||||
setattr(self._canvas,self._restores[k],v)
|
|
||||||
|
|
||||||
if self.verbose: print "### end _SVGRenderer.drawNode"
|
|
||||||
|
|
||||||
_restores = {'strokeColor':'_strokeColor','strokeWidth': '_lineWidth','strokeLineCap':'_lineCap',
|
|
||||||
'strokeLineJoin':'_lineJoin','fillColor':'_fillColor','fontName':'_font',
|
|
||||||
'fontSize':'_fontSize'}
|
|
||||||
|
|
||||||
|
|
||||||
def drawGroup(self, group):
|
|
||||||
if self.verbose: print "### begin _SVGRenderer.drawGroup"
|
|
||||||
|
|
||||||
currGroup = self._canvas.startGroup()
|
|
||||||
a, b, c, d, e, f = self._tracker.getCTM()
|
|
||||||
for childNode in group.getContents():
|
|
||||||
if isinstance(childNode, UserNode):
|
|
||||||
node2 = childNode.provideNode()
|
|
||||||
else:
|
|
||||||
node2 = childNode
|
|
||||||
self.drawNode(node2)
|
|
||||||
self._canvas.transform(a, b, c, d, e, f)
|
|
||||||
self._canvas.endGroup(currGroup)
|
|
||||||
|
|
||||||
if self.verbose: print "### end _SVGRenderer.drawGroup"
|
|
||||||
|
|
||||||
|
|
||||||
def drawRect(self, rect):
|
|
||||||
if rect.rx == rect.ry == 0:
|
|
||||||
#plain old rectangle
|
|
||||||
self._canvas.rect(
|
|
||||||
rect.x, rect.y,
|
|
||||||
rect.x+rect.width, rect.y+rect.height)
|
|
||||||
else:
|
|
||||||
#cheat and assume ry = rx; better to generalize
|
|
||||||
#pdfgen roundRect function. TODO
|
|
||||||
self._canvas.roundRect(
|
|
||||||
rect.x, rect.y,
|
|
||||||
rect.x+rect.width, rect.y+rect.height,
|
|
||||||
rect.rx, rect.ry
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def drawString(self, stringObj):
|
|
||||||
if self._canvas._fillColor:
|
|
||||||
S = self._tracker.getState()
|
|
||||||
text_anchor, x, y, text = S['textAnchor'], stringObj.x, stringObj.y, stringObj.text
|
|
||||||
if not text_anchor in ['start', 'inherited']:
|
|
||||||
font, fontSize = S['fontName'], S['fontSize']
|
|
||||||
textLen = stringWidth(text, font,fontSize)
|
|
||||||
if text_anchor=='end':
|
|
||||||
x = x-textLen
|
|
||||||
elif text_anchor=='middle':
|
|
||||||
x = x - textLen/2
|
|
||||||
else:
|
|
||||||
raise ValueError, 'bad value for text_anchor ' + str(text_anchor)
|
|
||||||
self._canvas.drawString(text,x,y)
|
|
||||||
|
|
||||||
|
|
||||||
def drawLine(self, line):
|
|
||||||
if self._canvas._strokeColor:
|
|
||||||
self._canvas.line(line.x1, line.y1, line.x2, line.y2)
|
|
||||||
|
|
||||||
|
|
||||||
def drawCircle(self, circle):
|
|
||||||
self._canvas.circle( circle.cx, circle.cy, circle.r)
|
|
||||||
|
|
||||||
|
|
||||||
def drawWedge(self, wedge):
|
|
||||||
centerx, centery, radius, startangledegrees, endangledegrees = \
|
|
||||||
wedge.centerx, wedge.centery, wedge.radius, wedge.startangledegrees, wedge.endangledegrees
|
|
||||||
yradius = wedge.yradius or wedge.radius
|
|
||||||
(x1, y1) = (centerx-radius, centery-yradius)
|
|
||||||
(x2, y2) = (centerx+radius, centery+yradius)
|
|
||||||
extent = endangledegrees - startangledegrees
|
|
||||||
self._canvas.drawArc(x1, y1, x2, y2, startangledegrees, extent, fromcenter=1)
|
|
||||||
|
|
||||||
|
|
||||||
def drawPolyLine(self, p):
|
|
||||||
if self._canvas._strokeColor:
|
|
||||||
self._canvas.polyLine(_pointsFromList(p.points))
|
|
||||||
|
|
||||||
|
|
||||||
def drawEllipse(self, ellipse):
|
|
||||||
#need to convert to pdfgen's bounding box representation
|
|
||||||
x1 = ellipse.cx - ellipse.rx
|
|
||||||
x2 = ellipse.cx + ellipse.rx
|
|
||||||
y1 = ellipse.cy - ellipse.ry
|
|
||||||
y2 = ellipse.cy + ellipse.ry
|
|
||||||
self._canvas.ellipse(x1,y1,x2,y2)
|
|
||||||
|
|
||||||
|
|
||||||
def drawPolygon(self, p):
|
|
||||||
self._canvas.polygon(_pointsFromList(p.points), closed=1)
|
|
||||||
|
|
||||||
|
|
||||||
def drawPath(self, path):
|
|
||||||
# print "### drawPath", path.points
|
|
||||||
from reportlab.graphics.shapes import _renderPath
|
|
||||||
c = self._canvas
|
|
||||||
drawFuncs = (c.moveTo, c.lineTo, c.curveTo, c.closePath)
|
|
||||||
isClosed = _renderPath(path, drawFuncs)
|
|
||||||
if not isClosed:
|
|
||||||
c._fillColor = None
|
|
||||||
c._fillAndStroke([], clip=path.isClipPath)
|
|
||||||
|
|
||||||
|
|
||||||
def applyStateChanges(self, delta, newState):
|
|
||||||
"""This takes a set of states, and outputs the operators
|
|
||||||
needed to set those properties"""
|
|
||||||
|
|
||||||
for key, value in delta.items():
|
|
||||||
if key == 'transform':
|
|
||||||
pass
|
|
||||||
#self._canvas.transform(value[0], value[1], value[2], value[3], value[4], value[5])
|
|
||||||
elif key == 'strokeColor':
|
|
||||||
self._canvas.setStrokeColor(value)
|
|
||||||
elif key == 'strokeWidth':
|
|
||||||
self._canvas.setLineWidth(value)
|
|
||||||
elif key == 'strokeLineCap': #0,1,2
|
|
||||||
self._canvas.setLineCap(value)
|
|
||||||
elif key == 'strokeLineJoin':
|
|
||||||
self._canvas.setLineJoin(value)
|
|
||||||
elif key == 'strokeDashArray':
|
|
||||||
if value:
|
|
||||||
self._canvas.setDash(value)
|
|
||||||
else:
|
|
||||||
self._canvas.setDash()
|
|
||||||
elif key == 'fillColor':
|
|
||||||
self._canvas.setFillColor(value)
|
|
||||||
elif key in ['fontSize', 'fontName']:
|
|
||||||
fontname = delta.get('fontName', self._canvas._font)
|
|
||||||
fontsize = delta.get('fontSize', self._canvas._fontSize)
|
|
||||||
self._canvas.setFont(fontname, fontsize)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def test0(outdir='svgout'):
|
|
||||||
# print all drawings and their doc strings from the test
|
|
||||||
# file
|
|
||||||
if not os.path.isdir(outdir):
|
|
||||||
os.mkdir(outdir)
|
|
||||||
#grab all drawings from the test module
|
|
||||||
from reportlab.graphics import testshapes
|
|
||||||
drawings = []
|
|
||||||
|
|
||||||
for funcname in dir(testshapes):
|
|
||||||
#if funcname[0:11] == 'getDrawing2':
|
|
||||||
# print 'hacked to only show drawing 2'
|
|
||||||
if funcname[0:10] == 'getDrawing':
|
|
||||||
drawing = eval('testshapes.' + funcname + '()')
|
|
||||||
docstring = eval('testshapes.' + funcname + '.__doc__')
|
|
||||||
drawings.append((drawing, docstring))
|
|
||||||
|
|
||||||
# return
|
|
||||||
|
|
||||||
i = 0
|
|
||||||
for (d, docstring) in drawings:
|
|
||||||
filename = outdir + os.sep + 'renderSVG_%d.svg' % i
|
|
||||||
drawToFile(d, filename)
|
|
||||||
# print 'saved', filename
|
|
||||||
i = i + 1
|
|
||||||
|
|
||||||
|
|
||||||
def test1():
|
|
||||||
from reportlab.graphics.testshapes import getDrawing01
|
|
||||||
d = getDrawing01()
|
|
||||||
drawToFile(d, "svgout/test.svg")
|
|
||||||
|
|
||||||
|
|
||||||
def test2():
|
|
||||||
from reportlab.lib.corp import RL_CorpLogo
|
|
||||||
from reportlab.graphics.shapes import Drawing
|
|
||||||
|
|
||||||
rl = RL_CorpLogo()
|
|
||||||
d = Drawing(rl.width,rl.height)
|
|
||||||
d.add(rl)
|
|
||||||
drawToFile(d, "svgout/corplogo.svg")
|
|
||||||
|
|
||||||
|
|
||||||
if __name__=='__main__':
|
|
||||||
test0()
|
|
||||||
test1()
|
|
||||||
test2()
|
|
|
@ -1,351 +0,0 @@
|
||||||
#Copyright ReportLab Europe Ltd. 2000-2004
|
|
||||||
#see license.txt for license details
|
|
||||||
#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/graphics/renderbase.py
|
|
||||||
"""
|
|
||||||
Superclass for renderers to factor out common functionality and default implementations.
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
__version__=''' $Id $ '''
|
|
||||||
|
|
||||||
from reportlab.graphics.shapes import *
|
|
||||||
from reportlab.lib.validators import DerivedValue
|
|
||||||
from reportlab import rl_config
|
|
||||||
|
|
||||||
def inverse(A):
|
|
||||||
"For A affine 2D represented as 6vec return 6vec version of A**(-1)"
|
|
||||||
# I checked this RGB
|
|
||||||
det = float(A[0]*A[3] - A[2]*A[1])
|
|
||||||
R = [A[3]/det, -A[1]/det, -A[2]/det, A[0]/det]
|
|
||||||
return tuple(R+[-R[0]*A[4]-R[2]*A[5],-R[1]*A[4]-R[3]*A[5]])
|
|
||||||
|
|
||||||
def mmult(A, B):
|
|
||||||
"A postmultiplied by B"
|
|
||||||
# I checked this RGB
|
|
||||||
# [a0 a2 a4] [b0 b2 b4]
|
|
||||||
# [a1 a3 a5] * [b1 b3 b5]
|
|
||||||
# [ 1 ] [ 1 ]
|
|
||||||
#
|
|
||||||
return (A[0]*B[0] + A[2]*B[1],
|
|
||||||
A[1]*B[0] + A[3]*B[1],
|
|
||||||
A[0]*B[2] + A[2]*B[3],
|
|
||||||
A[1]*B[2] + A[3]*B[3],
|
|
||||||
A[0]*B[4] + A[2]*B[5] + A[4],
|
|
||||||
A[1]*B[4] + A[3]*B[5] + A[5])
|
|
||||||
|
|
||||||
|
|
||||||
def getStateDelta(shape):
|
|
||||||
"""Used to compute when we need to change the graphics state.
|
|
||||||
For example, if we have two adjacent red shapes we don't need
|
|
||||||
to set the pen color to red in between. Returns the effect
|
|
||||||
the given shape would have on the graphics state"""
|
|
||||||
delta = {}
|
|
||||||
for (prop, value) in shape.getProperties().items():
|
|
||||||
if STATE_DEFAULTS.has_key(prop):
|
|
||||||
delta[prop] = value
|
|
||||||
return delta
|
|
||||||
|
|
||||||
|
|
||||||
class StateTracker:
|
|
||||||
"""Keeps a stack of transforms and state
|
|
||||||
properties. It can contain any properties you
|
|
||||||
want, but the keys 'transform' and 'ctm' have
|
|
||||||
special meanings. The getCTM()
|
|
||||||
method returns the current transformation
|
|
||||||
matrix at any point, without needing to
|
|
||||||
invert matrixes when you pop."""
|
|
||||||
def __init__(self, defaults=None):
|
|
||||||
# one stack to keep track of what changes...
|
|
||||||
self._deltas = []
|
|
||||||
|
|
||||||
# and another to keep track of cumulative effects. Last one in
|
|
||||||
# list is the current graphics state. We put one in to simplify
|
|
||||||
# loops below.
|
|
||||||
self._combined = []
|
|
||||||
if defaults is None:
|
|
||||||
defaults = STATE_DEFAULTS.copy()
|
|
||||||
#ensure that if we have a transform, we have a CTM
|
|
||||||
if defaults.has_key('transform'):
|
|
||||||
defaults['ctm'] = defaults['transform']
|
|
||||||
self._combined.append(defaults)
|
|
||||||
|
|
||||||
def push(self,delta):
|
|
||||||
"""Take a new state dictionary of changes and push it onto
|
|
||||||
the stack. After doing this, the combined state is accessible
|
|
||||||
through getState()"""
|
|
||||||
|
|
||||||
newstate = self._combined[-1].copy()
|
|
||||||
for (key, value) in delta.items():
|
|
||||||
if key == 'transform': #do cumulative matrix
|
|
||||||
newstate['transform'] = delta['transform']
|
|
||||||
newstate['ctm'] = mmult(self._combined[-1]['ctm'], delta['transform'])
|
|
||||||
#print 'statetracker transform = (%0.2f, %0.2f, %0.2f, %0.2f, %0.2f, %0.2f)' % tuple(newstate['transform'])
|
|
||||||
#print 'statetracker ctm = (%0.2f, %0.2f, %0.2f, %0.2f, %0.2f, %0.2f)' % tuple(newstate['ctm'])
|
|
||||||
|
|
||||||
else: #just overwrite it
|
|
||||||
newstate[key] = value
|
|
||||||
|
|
||||||
self._combined.append(newstate)
|
|
||||||
self._deltas.append(delta)
|
|
||||||
|
|
||||||
def pop(self):
|
|
||||||
"""steps back one, and returns a state dictionary with the
|
|
||||||
deltas to reverse out of wherever you are. Depending
|
|
||||||
on your back end, you may not need the return value,
|
|
||||||
since you can get the complete state afterwards with getState()"""
|
|
||||||
del self._combined[-1]
|
|
||||||
newState = self._combined[-1]
|
|
||||||
lastDelta = self._deltas[-1]
|
|
||||||
del self._deltas[-1]
|
|
||||||
#need to diff this against the last one in the state
|
|
||||||
reverseDelta = {}
|
|
||||||
#print 'pop()...'
|
|
||||||
for key, curValue in lastDelta.items():
|
|
||||||
#print ' key=%s, value=%s' % (key, curValue)
|
|
||||||
prevValue = newState[key]
|
|
||||||
if prevValue <> curValue:
|
|
||||||
#print ' state popping "%s"="%s"' % (key, curValue)
|
|
||||||
if key == 'transform':
|
|
||||||
reverseDelta[key] = inverse(lastDelta['transform'])
|
|
||||||
else: #just return to previous state
|
|
||||||
reverseDelta[key] = prevValue
|
|
||||||
return reverseDelta
|
|
||||||
|
|
||||||
def getState(self):
|
|
||||||
"returns the complete graphics state at this point"
|
|
||||||
return self._combined[-1]
|
|
||||||
|
|
||||||
def getCTM(self):
|
|
||||||
"returns the current transformation matrix at this point"""
|
|
||||||
return self._combined[-1]['ctm']
|
|
||||||
|
|
||||||
def __getitem__(self,key):
|
|
||||||
"returns the complete graphics state value of key at this point"
|
|
||||||
return self._combined[-1][key]
|
|
||||||
|
|
||||||
def __setitem__(self,key,value):
|
|
||||||
"sets the complete graphics state value of key to value"
|
|
||||||
self._combined[-1][key] = value
|
|
||||||
|
|
||||||
def testStateTracker():
|
|
||||||
print 'Testing state tracker'
|
|
||||||
defaults = {'fillColor':None, 'strokeColor':None,'fontName':None, 'transform':[1,0,0,1,0,0]}
|
|
||||||
deltas = [
|
|
||||||
{'fillColor':'red'},
|
|
||||||
{'fillColor':'green', 'strokeColor':'blue','fontName':'Times-Roman'},
|
|
||||||
{'transform':[0.5,0,0,0.5,0,0]},
|
|
||||||
{'transform':[0.5,0,0,0.5,2,3]},
|
|
||||||
{'strokeColor':'red'}
|
|
||||||
]
|
|
||||||
|
|
||||||
st = StateTracker(defaults)
|
|
||||||
print 'initial:', st.getState()
|
|
||||||
print
|
|
||||||
for delta in deltas:
|
|
||||||
print 'pushing:', delta
|
|
||||||
st.push(delta)
|
|
||||||
print 'state: ',st.getState(),'\n'
|
|
||||||
|
|
||||||
for delta in deltas:
|
|
||||||
print 'popping:',st.pop()
|
|
||||||
print 'state: ',st.getState(),'\n'
|
|
||||||
|
|
||||||
|
|
||||||
def _expandUserNode(node,canvas):
|
|
||||||
if isinstance(node, UserNode):
|
|
||||||
try:
|
|
||||||
if hasattr(node,'_canvas'):
|
|
||||||
ocanvas = 1
|
|
||||||
else:
|
|
||||||
node._canvas = canvas
|
|
||||||
ocanvas = None
|
|
||||||
onode = node
|
|
||||||
node = node.provideNode()
|
|
||||||
finally:
|
|
||||||
if not ocanvas: del onode._canvas
|
|
||||||
return node
|
|
||||||
|
|
||||||
def renderScaledDrawing(d):
|
|
||||||
renderScale = d.renderScale
|
|
||||||
if renderScale!=1.0:
|
|
||||||
d = d.copy()
|
|
||||||
d.width *= renderScale
|
|
||||||
d.height *= renderScale
|
|
||||||
d.scale(renderScale,renderScale)
|
|
||||||
d.renderScale = 1.0
|
|
||||||
return d
|
|
||||||
|
|
||||||
class Renderer:
|
|
||||||
"""Virtual superclass for graphics renderers."""
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
self._tracker = StateTracker()
|
|
||||||
self._nodeStack = [] #track nodes visited
|
|
||||||
|
|
||||||
def undefined(self, operation):
|
|
||||||
raise ValueError, "%s operation not defined at superclass class=%s" %(operation, self.__class__)
|
|
||||||
|
|
||||||
def draw(self, drawing, canvas, x=0, y=0, showBoundary=rl_config._unset_):
|
|
||||||
"""This is the top level function, which draws the drawing at the given
|
|
||||||
location. The recursive part is handled by drawNode."""
|
|
||||||
#stash references for ease of communication
|
|
||||||
if showBoundary is rl_config._unset_: showBoundary=rl_config.showBoundary
|
|
||||||
self._canvas = canvas
|
|
||||||
canvas.__dict__['_drawing'] = self._drawing = drawing
|
|
||||||
drawing._parent = None
|
|
||||||
try:
|
|
||||||
#bounding box
|
|
||||||
if showBoundary: canvas.rect(x, y, drawing.width, drawing.height)
|
|
||||||
canvas.saveState()
|
|
||||||
self.initState(x,y) #this is the push()
|
|
||||||
self.drawNode(drawing)
|
|
||||||
self.pop()
|
|
||||||
canvas.restoreState()
|
|
||||||
finally:
|
|
||||||
#remove any circular references
|
|
||||||
del self._canvas, self._drawing, canvas._drawing, drawing._parent
|
|
||||||
|
|
||||||
def initState(self,x,y):
|
|
||||||
deltas = STATE_DEFAULTS.copy()
|
|
||||||
deltas['transform'] = [1,0,0,1,x,y]
|
|
||||||
self._tracker.push(deltas)
|
|
||||||
self.applyStateChanges(deltas, {})
|
|
||||||
|
|
||||||
def pop(self):
|
|
||||||
self._tracker.pop()
|
|
||||||
|
|
||||||
def drawNode(self, node):
|
|
||||||
"""This is the recursive method called for each node
|
|
||||||
in the tree"""
|
|
||||||
# Undefined here, but with closer analysis probably can be handled in superclass
|
|
||||||
self.undefined("drawNode")
|
|
||||||
|
|
||||||
def getStateValue(self, key):
|
|
||||||
"""Return current state parameter for given key"""
|
|
||||||
currentState = self._tracker._combined[-1]
|
|
||||||
return currentState[key]
|
|
||||||
|
|
||||||
def fillDerivedValues(self, node):
|
|
||||||
"""Examine a node for any values which are Derived,
|
|
||||||
and replace them with their calculated values.
|
|
||||||
Generally things may look at the drawing or their
|
|
||||||
parent.
|
|
||||||
|
|
||||||
"""
|
|
||||||
for (key, value) in node.__dict__.items():
|
|
||||||
if isinstance(value, DerivedValue):
|
|
||||||
#just replace with default for key?
|
|
||||||
#print ' fillDerivedValues(%s)' % key
|
|
||||||
newValue = value.getValue(self, key)
|
|
||||||
#print ' got value of %s' % newValue
|
|
||||||
node.__dict__[key] = newValue
|
|
||||||
|
|
||||||
def drawNodeDispatcher(self, node):
|
|
||||||
"""dispatch on the node's (super) class: shared code"""
|
|
||||||
|
|
||||||
canvas = getattr(self,'_canvas',None)
|
|
||||||
# replace UserNode with its contents
|
|
||||||
|
|
||||||
try:
|
|
||||||
node = _expandUserNode(node,canvas)
|
|
||||||
if hasattr(node,'_canvas'):
|
|
||||||
ocanvas = 1
|
|
||||||
else:
|
|
||||||
node._canvas = canvas
|
|
||||||
ocanvas = None
|
|
||||||
|
|
||||||
self.fillDerivedValues(node)
|
|
||||||
#draw the object, or recurse
|
|
||||||
if isinstance(node, Line):
|
|
||||||
self.drawLine(node)
|
|
||||||
elif isinstance(node, Image):
|
|
||||||
self.drawImage(node)
|
|
||||||
elif isinstance(node, Rect):
|
|
||||||
self.drawRect(node)
|
|
||||||
elif isinstance(node, Circle):
|
|
||||||
self.drawCircle(node)
|
|
||||||
elif isinstance(node, Ellipse):
|
|
||||||
self.drawEllipse(node)
|
|
||||||
elif isinstance(node, PolyLine):
|
|
||||||
self.drawPolyLine(node)
|
|
||||||
elif isinstance(node, Polygon):
|
|
||||||
self.drawPolygon(node)
|
|
||||||
elif isinstance(node, Path):
|
|
||||||
self.drawPath(node)
|
|
||||||
elif isinstance(node, String):
|
|
||||||
self.drawString(node)
|
|
||||||
elif isinstance(node, Group):
|
|
||||||
self.drawGroup(node)
|
|
||||||
elif isinstance(node, Wedge):
|
|
||||||
self.drawWedge(node)
|
|
||||||
else:
|
|
||||||
print 'DrawingError','Unexpected element %s in drawing!' % str(node)
|
|
||||||
finally:
|
|
||||||
if not ocanvas: del node._canvas
|
|
||||||
|
|
||||||
_restores = {'stroke':'_stroke','stroke_width': '_lineWidth','stroke_linecap':'_lineCap',
|
|
||||||
'stroke_linejoin':'_lineJoin','fill':'_fill','font_family':'_font',
|
|
||||||
'font_size':'_fontSize'}
|
|
||||||
|
|
||||||
def drawGroup(self, group):
|
|
||||||
# just do the contents. Some renderers might need to override this
|
|
||||||
# if they need a flipped transform
|
|
||||||
canvas = getattr(self,'_canvas',None)
|
|
||||||
for node in group.getContents():
|
|
||||||
node = _expandUserNode(node,canvas)
|
|
||||||
|
|
||||||
#here is where we do derived values - this seems to get everything. Touch wood.
|
|
||||||
self.fillDerivedValues(node)
|
|
||||||
try:
|
|
||||||
if hasattr(node,'_canvas'):
|
|
||||||
ocanvas = 1
|
|
||||||
else:
|
|
||||||
node._canvas = canvas
|
|
||||||
ocanvas = None
|
|
||||||
node._parent = group
|
|
||||||
self.drawNode(node)
|
|
||||||
finally:
|
|
||||||
del node._parent
|
|
||||||
if not ocanvas: del node._canvas
|
|
||||||
|
|
||||||
def drawWedge(self, wedge):
|
|
||||||
# by default ask the wedge to make a polygon of itself and draw that!
|
|
||||||
#print "drawWedge"
|
|
||||||
polygon = wedge.asPolygon()
|
|
||||||
self.drawPolygon(polygon)
|
|
||||||
|
|
||||||
def drawPath(self, path):
|
|
||||||
polygons = path.asPolygons()
|
|
||||||
for polygon in polygons:
|
|
||||||
self.drawPolygon(polygon)
|
|
||||||
|
|
||||||
def drawRect(self, rect):
|
|
||||||
# could be implemented in terms of polygon
|
|
||||||
self.undefined("drawRect")
|
|
||||||
|
|
||||||
def drawLine(self, line):
|
|
||||||
self.undefined("drawLine")
|
|
||||||
|
|
||||||
def drawCircle(self, circle):
|
|
||||||
self.undefined("drawCircle")
|
|
||||||
|
|
||||||
def drawPolyLine(self, p):
|
|
||||||
self.undefined("drawPolyLine")
|
|
||||||
|
|
||||||
def drawEllipse(self, ellipse):
|
|
||||||
self.undefined("drawEllipse")
|
|
||||||
|
|
||||||
def drawPolygon(self, p):
|
|
||||||
self.undefined("drawPolygon")
|
|
||||||
|
|
||||||
def drawString(self, stringObj):
|
|
||||||
self.undefined("drawString")
|
|
||||||
|
|
||||||
def applyStateChanges(self, delta, newState):
|
|
||||||
"""This takes a set of states, and outputs the operators
|
|
||||||
needed to set those properties"""
|
|
||||||
self.undefined("applyStateChanges")
|
|
||||||
|
|
||||||
if __name__=='__main__':
|
|
||||||
print "this file has no script interpretation"
|
|
||||||
print __doc__
|
|
|
@ -1,73 +0,0 @@
|
||||||
#Autogenerated by ReportLab guiedit do not edit
|
|
||||||
from reportlab.graphics.charts.legends import Legend
|
|
||||||
from reportlab.graphics.charts.lineplots import ScatterPlot
|
|
||||||
from reportlab.graphics.shapes import Drawing, _DrawingEditorMixin, String
|
|
||||||
from reportlab.graphics.charts.textlabels import Label
|
|
||||||
from excelcolors import *
|
|
||||||
|
|
||||||
class Bubble(_DrawingEditorMixin,Drawing):
|
|
||||||
def __init__(self,width=200,height=150,*args,**kw):
|
|
||||||
apply(Drawing.__init__,(self,width,height)+args,kw)
|
|
||||||
self._add(self,ScatterPlot(),name='chart',validate=None,desc="The main chart")
|
|
||||||
self.chart.width = 115
|
|
||||||
self.chart.height = 80
|
|
||||||
self.chart.x = 30
|
|
||||||
self.chart.y = 40
|
|
||||||
self.chart.lines[0].strokeColor = color01
|
|
||||||
self.chart.lines[1].strokeColor = color02
|
|
||||||
self.chart.lines[2].strokeColor = color03
|
|
||||||
self.chart.lines[3].strokeColor = color04
|
|
||||||
self.chart.lines[4].strokeColor = color05
|
|
||||||
self.chart.lines[5].strokeColor = color06
|
|
||||||
self.chart.lines[6].strokeColor = color07
|
|
||||||
self.chart.lines[7].strokeColor = color08
|
|
||||||
self.chart.lines[8].strokeColor = color09
|
|
||||||
self.chart.lines[9].strokeColor = color10
|
|
||||||
self.chart.lines.symbol.kind ='Circle'
|
|
||||||
self.chart.lines.symbol.size = 15
|
|
||||||
self.chart.fillColor = backgroundGrey
|
|
||||||
self.chart.lineLabels.fontName = 'Helvetica'
|
|
||||||
self.chart.xValueAxis.labels.fontName = 'Helvetica'
|
|
||||||
self.chart.xValueAxis.labels.fontSize = 7
|
|
||||||
self.chart.xValueAxis.forceZero = 0
|
|
||||||
self.chart.data = [((100,100), (200,200), (250,210), (300,300), (350,450))]
|
|
||||||
self.chart.xValueAxis.avoidBoundFrac = 1
|
|
||||||
self.chart.xValueAxis.gridEnd = 115
|
|
||||||
self.chart.xValueAxis.tickDown = 3
|
|
||||||
self.chart.xValueAxis.visibleGrid = 1
|
|
||||||
self.chart.yValueAxis.tickLeft = 3
|
|
||||||
self.chart.yValueAxis.labels.fontName = 'Helvetica'
|
|
||||||
self.chart.yValueAxis.labels.fontSize = 7
|
|
||||||
self._add(self,Label(),name='Title',validate=None,desc="The title at the top of the chart")
|
|
||||||
self.Title.fontName = 'Helvetica-Bold'
|
|
||||||
self.Title.fontSize = 7
|
|
||||||
self.Title.x = 100
|
|
||||||
self.Title.y = 135
|
|
||||||
self.Title._text = 'Chart Title'
|
|
||||||
self.Title.maxWidth = 180
|
|
||||||
self.Title.height = 20
|
|
||||||
self.Title.textAnchor ='middle'
|
|
||||||
self._add(self,Legend(),name='Legend',validate=None,desc="The legend or key for the chart")
|
|
||||||
self.Legend.colorNamePairs = [(color01, 'Widgets')]
|
|
||||||
self.Legend.fontName = 'Helvetica'
|
|
||||||
self.Legend.fontSize = 7
|
|
||||||
self.Legend.x = 153
|
|
||||||
self.Legend.y = 85
|
|
||||||
self.Legend.dxTextSpace = 5
|
|
||||||
self.Legend.dy = 5
|
|
||||||
self.Legend.dx = 5
|
|
||||||
self.Legend.deltay = 5
|
|
||||||
self.Legend.alignment ='right'
|
|
||||||
self.chart.lineLabelFormat = None
|
|
||||||
self.chart.xLabel = 'X Axis'
|
|
||||||
self.chart.y = 30
|
|
||||||
self.chart.yLabel = 'Y Axis'
|
|
||||||
self.chart.yValueAxis.labelTextFormat = '%d'
|
|
||||||
self.chart.yValueAxis.forceZero = 1
|
|
||||||
self.chart.xValueAxis.forceZero = 1
|
|
||||||
|
|
||||||
|
|
||||||
self._add(self,0,name='preview',validate=None,desc=None)
|
|
||||||
|
|
||||||
if __name__=="__main__": #NORUNTESTS
|
|
||||||
Bubble().save(formats=['pdf'],outDir=None,fnRoot='bubble')
|
|
|
@ -1,84 +0,0 @@
|
||||||
#Autogenerated by ReportLab guiedit do not edit
|
|
||||||
from reportlab.graphics.charts.legends import Legend
|
|
||||||
from excelcolors import *
|
|
||||||
from reportlab.graphics.charts.barcharts import HorizontalBarChart
|
|
||||||
from reportlab.graphics.shapes import Drawing, _DrawingEditorMixin, String
|
|
||||||
from reportlab.graphics.charts.textlabels import Label
|
|
||||||
|
|
||||||
class ClusteredBar(_DrawingEditorMixin,Drawing):
|
|
||||||
def __init__(self,width=200,height=150,*args,**kw):
|
|
||||||
apply(Drawing.__init__,(self,width,height)+args,kw)
|
|
||||||
self._add(self,HorizontalBarChart(),name='chart',validate=None,desc="The main chart")
|
|
||||||
self.chart.width = 115
|
|
||||||
self.chart.height = 80
|
|
||||||
self.chart.x = 30
|
|
||||||
self.chart.y = 40
|
|
||||||
self.chart.bars[0].fillColor = color01
|
|
||||||
self.chart.bars[1].fillColor = color02
|
|
||||||
self.chart.bars[2].fillColor = color03
|
|
||||||
self.chart.bars[3].fillColor = color04
|
|
||||||
self.chart.bars[4].fillColor = color05
|
|
||||||
self.chart.bars[5].fillColor = color06
|
|
||||||
self.chart.bars[6].fillColor = color07
|
|
||||||
self.chart.bars[7].fillColor = color08
|
|
||||||
self.chart.bars[8].fillColor = color09
|
|
||||||
self.chart.bars[9].fillColor = color10
|
|
||||||
self.chart.fillColor = backgroundGrey
|
|
||||||
self.chart.barLabels.fontName = 'Helvetica'
|
|
||||||
self.chart.valueAxis.labels.fontName = 'Helvetica'
|
|
||||||
self.chart.valueAxis.labels.fontSize = 6
|
|
||||||
self.chart.valueAxis.forceZero = 1
|
|
||||||
self.chart.data = [(100, 150, 180), (125, 180, 200)]
|
|
||||||
self.chart.groupSpacing = 15
|
|
||||||
self.chart.valueAxis.avoidBoundFrac = 1
|
|
||||||
self.chart.valueAxis.gridEnd = 80
|
|
||||||
self.chart.valueAxis.tickDown = 3
|
|
||||||
self.chart.valueAxis.visibleGrid = 1
|
|
||||||
self.chart.categoryAxis.categoryNames = ['North', 'South', 'Central']
|
|
||||||
self.chart.categoryAxis.tickLeft = 3
|
|
||||||
self.chart.categoryAxis.labels.fontName = 'Helvetica'
|
|
||||||
self.chart.categoryAxis.labels.fontSize = 6
|
|
||||||
self.chart.categoryAxis.labels.dx = -3
|
|
||||||
self._add(self,Label(),name='Title',validate=None,desc="The title at the top of the chart")
|
|
||||||
self.Title.fontName = 'Helvetica-Bold'
|
|
||||||
self.Title.fontSize = 7
|
|
||||||
self.Title.x = 100
|
|
||||||
self.Title.y = 135
|
|
||||||
self.Title._text = 'Chart Title'
|
|
||||||
self.Title.maxWidth = 180
|
|
||||||
self.Title.height = 20
|
|
||||||
self.Title.textAnchor ='middle'
|
|
||||||
self._add(self,Legend(),name='Legend',validate=None,desc="The legend or key for the chart")
|
|
||||||
self.Legend.colorNamePairs = [(color01, 'Widgets'), (color02, 'Sprockets')]
|
|
||||||
self.Legend.fontName = 'Helvetica'
|
|
||||||
self.Legend.fontSize = 7
|
|
||||||
self.Legend.x = 153
|
|
||||||
self.Legend.y = 85
|
|
||||||
self.Legend.dxTextSpace = 5
|
|
||||||
self.Legend.dy = 5
|
|
||||||
self.Legend.dx = 5
|
|
||||||
self.Legend.deltay = 5
|
|
||||||
self.Legend.alignment ='right'
|
|
||||||
self._add(self,Label(),name='XLabel',validate=None,desc="The label on the horizontal axis")
|
|
||||||
self.XLabel.fontName = 'Helvetica'
|
|
||||||
self.XLabel.fontSize = 7
|
|
||||||
self.XLabel.x = 85
|
|
||||||
self.XLabel.y = 10
|
|
||||||
self.XLabel.textAnchor ='middle'
|
|
||||||
self.XLabel.maxWidth = 100
|
|
||||||
self.XLabel.height = 20
|
|
||||||
self.XLabel._text = "X Axis"
|
|
||||||
self._add(self,Label(),name='YLabel',validate=None,desc="The label on the vertical axis")
|
|
||||||
self.YLabel.fontName = 'Helvetica'
|
|
||||||
self.YLabel.fontSize = 7
|
|
||||||
self.YLabel.x = 12
|
|
||||||
self.YLabel.y = 80
|
|
||||||
self.YLabel.angle = 90
|
|
||||||
self.YLabel.textAnchor ='middle'
|
|
||||||
self.YLabel.maxWidth = 100
|
|
||||||
self.YLabel.height = 20
|
|
||||||
self.YLabel._text = "Y Axis"
|
|
||||||
self._add(self,0,name='preview',validate=None,desc=None)
|
|
||||||
|
|
||||||
if __name__=="__main__": #NORUNTESTS
|
|
||||||
ClusteredBar().save(formats=['pdf'],outDir=None,fnRoot='clustered_bar')
|
|
|
@ -1,83 +0,0 @@
|
||||||
#Autogenerated by ReportLab guiedit do not edit
|
|
||||||
from reportlab.graphics.charts.legends import Legend
|
|
||||||
from excelcolors import *
|
|
||||||
from reportlab.graphics.charts.barcharts import VerticalBarChart
|
|
||||||
from reportlab.graphics.shapes import Drawing, _DrawingEditorMixin, String
|
|
||||||
from reportlab.graphics.charts.textlabels import Label
|
|
||||||
|
|
||||||
class ClusteredColumn(_DrawingEditorMixin,Drawing):
|
|
||||||
def __init__(self,width=200,height=150,*args,**kw):
|
|
||||||
apply(Drawing.__init__,(self,width,height)+args,kw)
|
|
||||||
self._add(self,VerticalBarChart(),name='chart',validate=None,desc="The main chart")
|
|
||||||
self.chart.width = 115
|
|
||||||
self.chart.height = 80
|
|
||||||
self.chart.x = 30
|
|
||||||
self.chart.y = 40
|
|
||||||
self.chart.bars[0].fillColor = color01
|
|
||||||
self.chart.bars[1].fillColor = color02
|
|
||||||
self.chart.bars[2].fillColor = color03
|
|
||||||
self.chart.bars[3].fillColor = color04
|
|
||||||
self.chart.bars[4].fillColor = color05
|
|
||||||
self.chart.bars[5].fillColor = color06
|
|
||||||
self.chart.bars[6].fillColor = color07
|
|
||||||
self.chart.bars[7].fillColor = color08
|
|
||||||
self.chart.bars[8].fillColor = color09
|
|
||||||
self.chart.bars[9].fillColor = color10
|
|
||||||
self.chart.fillColor = backgroundGrey
|
|
||||||
self.chart.barLabels.fontName = 'Helvetica'
|
|
||||||
self.chart.valueAxis.labels.fontName = 'Helvetica'
|
|
||||||
self.chart.valueAxis.labels.fontSize = 7
|
|
||||||
self.chart.valueAxis.forceZero = 1
|
|
||||||
self.chart.data = [(100, 150, 180), (125, 180, 200)]
|
|
||||||
self.chart.groupSpacing = 15
|
|
||||||
self.chart.valueAxis.avoidBoundFrac = 1
|
|
||||||
self.chart.valueAxis.gridEnd = 115
|
|
||||||
self.chart.valueAxis.tickLeft = 3
|
|
||||||
self.chart.valueAxis.visibleGrid = 1
|
|
||||||
self.chart.categoryAxis.categoryNames = ['North', 'South', 'Central']
|
|
||||||
self.chart.categoryAxis.tickDown = 3
|
|
||||||
self.chart.categoryAxis.labels.fontName = 'Helvetica'
|
|
||||||
self.chart.categoryAxis.labels.fontSize = 7
|
|
||||||
self._add(self,Label(),name='Title',validate=None,desc="The title at the top of the chart")
|
|
||||||
self.Title.fontName = 'Helvetica-Bold'
|
|
||||||
self.Title.fontSize = 7
|
|
||||||
self.Title.x = 100
|
|
||||||
self.Title.y = 135
|
|
||||||
self.Title._text = 'Chart Title'
|
|
||||||
self.Title.maxWidth = 180
|
|
||||||
self.Title.height = 20
|
|
||||||
self.Title.textAnchor ='middle'
|
|
||||||
self._add(self,Legend(),name='Legend',validate=None,desc="The legend or key for the chart")
|
|
||||||
self.Legend.colorNamePairs = [(color01, 'Widgets'), (color02, 'Sprockets')]
|
|
||||||
self.Legend.fontName = 'Helvetica'
|
|
||||||
self.Legend.fontSize = 7
|
|
||||||
self.Legend.x = 153
|
|
||||||
self.Legend.y = 85
|
|
||||||
self.Legend.dxTextSpace = 5
|
|
||||||
self.Legend.dy = 5
|
|
||||||
self.Legend.dx = 5
|
|
||||||
self.Legend.deltay = 5
|
|
||||||
self.Legend.alignment ='right'
|
|
||||||
self._add(self,Label(),name='XLabel',validate=None,desc="The label on the horizontal axis")
|
|
||||||
self.XLabel.fontName = 'Helvetica'
|
|
||||||
self.XLabel.fontSize = 7
|
|
||||||
self.XLabel.x = 85
|
|
||||||
self.XLabel.y = 10
|
|
||||||
self.XLabel.textAnchor ='middle'
|
|
||||||
self.XLabel.maxWidth = 100
|
|
||||||
self.XLabel.height = 20
|
|
||||||
self.XLabel._text = "X Axis"
|
|
||||||
self._add(self,Label(),name='YLabel',validate=None,desc="The label on the vertical axis")
|
|
||||||
self.YLabel.fontName = 'Helvetica'
|
|
||||||
self.YLabel.fontSize = 7
|
|
||||||
self.YLabel.x = 12
|
|
||||||
self.YLabel.y = 80
|
|
||||||
self.YLabel.angle = 90
|
|
||||||
self.YLabel.textAnchor ='middle'
|
|
||||||
self.YLabel.maxWidth = 100
|
|
||||||
self.YLabel.height = 20
|
|
||||||
self.YLabel._text = "Y Axis"
|
|
||||||
self._add(self,0,name='preview',validate=None,desc=None)
|
|
||||||
|
|
||||||
if __name__=="__main__": #NORUNTESTS
|
|
||||||
ClusteredColumn().save(formats=['pdf'],outDir=None,fnRoot='clustered_column')
|
|
|
@ -1,45 +0,0 @@
|
||||||
# define standard colors to mimic those used by Microsoft Excel
|
|
||||||
from reportlab.lib.colors import CMYKColor, PCMYKColor
|
|
||||||
|
|
||||||
#colour names as comments at the end of each line are as a memory jogger ONLY
|
|
||||||
#NOT HTML named colours!
|
|
||||||
|
|
||||||
#Main colours as used for bars etc
|
|
||||||
color01 = PCMYKColor(40,40,0,0) # Lavender
|
|
||||||
color02 = PCMYKColor(0,66,33,39) # Maroon
|
|
||||||
color03 = PCMYKColor(0,0,20,0) # Yellow
|
|
||||||
color04 = PCMYKColor(20,0,0,0) # Cyan
|
|
||||||
color05 = PCMYKColor(0,100,0,59) # Purple
|
|
||||||
color06 = PCMYKColor(0,49,49,0) # Salmon
|
|
||||||
color07 = PCMYKColor(100,49,0,19) # Blue
|
|
||||||
color08 = PCMYKColor(20,20,0,0) # PaleLavender
|
|
||||||
color09 = PCMYKColor(100,100,0,49) # NavyBlue
|
|
||||||
color10 = PCMYKColor(0,100,0,0) # Purple
|
|
||||||
|
|
||||||
#Highlight colors - eg for the tops of bars
|
|
||||||
color01Light = PCMYKColor(39,39,0,25) # Light Lavender
|
|
||||||
color02Light = PCMYKColor(0,66,33,54) # Light Maroon
|
|
||||||
color03Light = PCMYKColor(0,0,19,25) # Light Yellow
|
|
||||||
color04Light = PCMYKColor(19,0,0,25) # Light Cyan
|
|
||||||
color05Light = PCMYKColor(0,100,0,69) # Light Purple
|
|
||||||
color06Light = PCMYKColor(0,49,49,25) # Light Salmon
|
|
||||||
color07Light = PCMYKColor(100,49,0,39) # Light Blue
|
|
||||||
color08Light = PCMYKColor(19,19,0,25) # Light PaleLavender
|
|
||||||
color09Light = PCMYKColor(100,100,0,62) # Light NavyBlue
|
|
||||||
color10Light = PCMYKColor(0,100,0,25) # Light Purple
|
|
||||||
|
|
||||||
#Lowlight colors - eg for the sides of bars
|
|
||||||
color01Dark = PCMYKColor(39,39,0,49) # Dark Lavender
|
|
||||||
color02Dark = PCMYKColor(0,66,33,69) # Dark Maroon
|
|
||||||
color03Dark = PCMYKColor(0,0,20,49) # Dark Yellow
|
|
||||||
color04Dark = PCMYKColor(20,0,0,49) # Dark Cyan
|
|
||||||
color05Dark = PCMYKColor(0,100,0,80) # Dark Purple
|
|
||||||
color06Dark = PCMYKColor(0,50,50,49) # Dark Salmon
|
|
||||||
color07Dark = PCMYKColor(100,50,0,59) # Dark Blue
|
|
||||||
color08Dark = PCMYKColor(20,20,0,49) # Dark PaleLavender
|
|
||||||
color09Dark = PCMYKColor(100,100,0,79) # Dark NavyBlue
|
|
||||||
color10Dark = PCMYKColor(0,100,0,49) # Dark Purple
|
|
||||||
|
|
||||||
#for standard grey backgrounds
|
|
||||||
backgroundGrey = PCMYKColor(0,0,0,24)
|
|
||||||
|
|
|
@ -1,65 +0,0 @@
|
||||||
#Autogenerated by ReportLab guiedit do not edit
|
|
||||||
from reportlab.graphics.charts.piecharts import Pie
|
|
||||||
from excelcolors import *
|
|
||||||
from reportlab.graphics.widgets.grids import ShadedRect
|
|
||||||
from reportlab.graphics.charts.legends import Legend
|
|
||||||
from reportlab.graphics.shapes import Drawing, _DrawingEditorMixin, String
|
|
||||||
from reportlab.graphics.charts.textlabels import Label
|
|
||||||
|
|
||||||
class ExplodedPie(_DrawingEditorMixin,Drawing):
|
|
||||||
def __init__(self,width=200,height=150,*args,**kw):
|
|
||||||
apply(Drawing.__init__,(self,width,height)+args,kw)
|
|
||||||
self._add(self,Pie(),name='chart',validate=None,desc="The main chart")
|
|
||||||
self.chart.width = 100
|
|
||||||
self.chart.height = 100
|
|
||||||
self.chart.x = 25
|
|
||||||
self.chart.y = 25
|
|
||||||
self.chart.slices[0].fillColor = color01
|
|
||||||
self.chart.slices[1].fillColor = color02
|
|
||||||
self.chart.slices[2].fillColor = color03
|
|
||||||
self.chart.slices[3].fillColor = color04
|
|
||||||
self.chart.slices[4].fillColor = color05
|
|
||||||
self.chart.slices[5].fillColor = color06
|
|
||||||
self.chart.slices[6].fillColor = color07
|
|
||||||
self.chart.slices[7].fillColor = color08
|
|
||||||
self.chart.slices[8].fillColor = color09
|
|
||||||
self.chart.slices[9].fillColor = color10
|
|
||||||
self.chart.data = (100, 150, 180)
|
|
||||||
self.chart.startAngle = -90
|
|
||||||
self._add(self,Label(),name='Title',validate=None,desc="The title at the top of the chart")
|
|
||||||
self.Title.fontName = 'Helvetica-Bold'
|
|
||||||
self.Title.fontSize = 7
|
|
||||||
self.Title.x = 100
|
|
||||||
self.Title.y = 135
|
|
||||||
self.Title._text = 'Chart Title'
|
|
||||||
self.Title.maxWidth = 180
|
|
||||||
self.Title.height = 20
|
|
||||||
self.Title.textAnchor ='middle'
|
|
||||||
self._add(self,Legend(),name='Legend',validate=None,desc="The legend or key for the chart")
|
|
||||||
self.Legend.colorNamePairs = [(color01, 'North'), (color02, 'South'), (color03, 'Central')]
|
|
||||||
self.Legend.fontName = 'Helvetica'
|
|
||||||
self.Legend.fontSize = 7
|
|
||||||
self.Legend.x = 160
|
|
||||||
self.Legend.y = 85
|
|
||||||
self.Legend.dxTextSpace = 5
|
|
||||||
self.Legend.dy = 5
|
|
||||||
self.Legend.dx = 5
|
|
||||||
self.Legend.deltay = 5
|
|
||||||
self.Legend.alignment ='right'
|
|
||||||
self.Legend.columnMaximum = 10
|
|
||||||
self.chart.slices.strokeWidth = 1
|
|
||||||
self.chart.slices.fontName = 'Helvetica'
|
|
||||||
self.background = ShadedRect()
|
|
||||||
self.background.fillColorStart = backgroundGrey
|
|
||||||
self.background.fillColorEnd = backgroundGrey
|
|
||||||
self.background.numShades = 1
|
|
||||||
self.background.strokeWidth = 0.5
|
|
||||||
self.background.x = 20
|
|
||||||
self.background.y = 20
|
|
||||||
self.chart.slices.popout = 5
|
|
||||||
self.background.height = 110
|
|
||||||
self.background.width = 110
|
|
||||||
self._add(self,0,name='preview',validate=None,desc=None)
|
|
||||||
|
|
||||||
if __name__=="__main__": #NORUNTESTS
|
|
||||||
ExplodedPie().save(formats=['pdf'],outDir=None,fnRoot='exploded_pie')
|
|
|
@ -1,54 +0,0 @@
|
||||||
#Autogenerated by ReportLab guiedit do not edit
|
|
||||||
from reportlab.graphics.charts.legends import Legend
|
|
||||||
from reportlab.graphics.charts.spider import SpiderChart
|
|
||||||
from reportlab.graphics.shapes import Drawing, _DrawingEditorMixin, String
|
|
||||||
from reportlab.graphics.charts.textlabels import Label
|
|
||||||
from excelcolors import *
|
|
||||||
|
|
||||||
class FilledRadarChart(_DrawingEditorMixin,Drawing):
|
|
||||||
def __init__(self,width=200,height=150,*args,**kw):
|
|
||||||
apply(Drawing.__init__,(self,width,height)+args,kw)
|
|
||||||
self._add(self,SpiderChart(),name='chart',validate=None,desc="The main chart")
|
|
||||||
self.chart.width = 90
|
|
||||||
self.chart.height = 90
|
|
||||||
self.chart.x = 45
|
|
||||||
self.chart.y = 25
|
|
||||||
self.chart.strands[0].fillColor = color01
|
|
||||||
self.chart.strands[1].fillColor = color02
|
|
||||||
self.chart.strands[2].fillColor = color03
|
|
||||||
self.chart.strands[3].fillColor = color04
|
|
||||||
self.chart.strands[4].fillColor = color05
|
|
||||||
self.chart.strands[5].fillColor = color06
|
|
||||||
self.chart.strands[6].fillColor = color07
|
|
||||||
self.chart.strands[7].fillColor = color08
|
|
||||||
self.chart.strands[8].fillColor = color09
|
|
||||||
self.chart.strands[9].fillColor = color10
|
|
||||||
self.chart.strandLabels.fontName = 'Helvetica'
|
|
||||||
self.chart.strandLabels.fontSize = 6
|
|
||||||
self.chart.fillColor = backgroundGrey
|
|
||||||
self.chart.data = [(125, 180, 200), (100, 150, 180)]
|
|
||||||
self.chart.labels = ['North', 'South', 'Central']
|
|
||||||
self._add(self,Label(),name='Title',validate=None,desc="The title at the top of the chart")
|
|
||||||
self.Title.fontName = 'Helvetica-Bold'
|
|
||||||
self.Title.fontSize = 7
|
|
||||||
self.Title.x = 100
|
|
||||||
self.Title.y = 135
|
|
||||||
self.Title._text = 'Chart Title'
|
|
||||||
self.Title.maxWidth = 180
|
|
||||||
self.Title.height = 20
|
|
||||||
self.Title.textAnchor ='middle'
|
|
||||||
self._add(self,Legend(),name='Legend',validate=None,desc="The legend or key for the chart")
|
|
||||||
self.Legend.colorNamePairs = [(color01, 'Widgets'), (color02, 'Sprockets')]
|
|
||||||
self.Legend.fontName = 'Helvetica'
|
|
||||||
self.Legend.fontSize = 7
|
|
||||||
self.Legend.x = 153
|
|
||||||
self.Legend.y = 85
|
|
||||||
self.Legend.dxTextSpace = 5
|
|
||||||
self.Legend.dy = 5
|
|
||||||
self.Legend.dx = 5
|
|
||||||
self.Legend.deltay = 5
|
|
||||||
self.Legend.alignment ='right'
|
|
||||||
self._add(self,0,name='preview',validate=None,desc=None)
|
|
||||||
|
|
||||||
if __name__=="__main__": #NORUNTESTS
|
|
||||||
FilledRadarChart().save(formats=['pdf'],outDir=None,fnRoot='filled_radar')
|
|