odoo/bin/reportlab/test/test_platypus_general.py

582 lines
24 KiB
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/test/test_platypus_general.py
__version__=''' $Id: test_platypus_general.py 2619 2005-06-24 14:49:15Z rgbecker $ '''
#tests and documents Page Layout API
__doc__="""This is not obvious so here's a brief explanation. This module is both
the test script and user guide for layout. Each page has two frames on it:
one for commentary, and one for demonstration objects which may be drawn in
various esoteric ways. The two functions getCommentary() and getExamples()
return the 'story' for each. The run() function gets the stories, then
builds a special "document model" in which the frames are added to each page
and drawn into.
"""
import string, copy, sys, os
from reportlab.test import unittest
from reportlab.test.utils import makeSuiteForClasses, outputfile, printLocation
from reportlab.pdfgen import canvas
from reportlab import platypus
from reportlab.platypus import BaseDocTemplate, PageTemplate, Flowable, FrameBreak
from reportlab.platypus import Paragraph, Preformatted
from reportlab.lib.units import inch, cm
from reportlab.lib.styles import PropertySet, getSampleStyleSheet, ParagraphStyle
from reportlab.lib import colors
from reportlab.rl_config import defaultPageSize
from reportlab.lib.utils import haveImages, _RL_DIR, rl_isfile, open_for_read
if haveImages:
_GIF = os.path.join(_RL_DIR,'test','pythonpowered.gif')
if not rl_isfile(_GIF): _GIF = None
else:
_GIF = None
_JPG = os.path.join(_RL_DIR,'docs','images','lj8100.jpg')
if not rl_isfile(_JPG): _JPG = None
def getFurl(fn):
furl = fn.replace(os.sep,'/')
if sys.platform=='win32' and furl[1]==':': furl = furl[0]+'|'+furl[2:]
if furl[0]!='/': furl = '/'+furl
return 'file://'+furl
PAGE_HEIGHT = defaultPageSize[1]
#################################################################
#
# first some drawing utilities
#
#
################################################################
BASEFONT = ('Times-Roman', 10)
def framePage(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-Italic',12)
canvas.drawRightString(523, PAGE_HEIGHT - 56, "Platypus User Guide and Test Script")
canvas.setFont('Times-Roman',12)
canvas.drawString(4 * inch, 0.75 * inch,
"Page %d" % canvas.getPageNumber())
canvas.restoreState()
def getParagraphs(textBlock):
"""Within the script, it is useful to whack out a page in triple
quotes containing separate paragraphs. This breaks one into its
constituent paragraphs, using blank lines as the delimiter."""
lines = string.split(textBlock, '\n')
paras = []
currentPara = []
for line in lines:
if len(string.strip(line)) == 0:
#blank, add it
if currentPara <> []:
paras.append(string.join(currentPara, '\n'))
currentPara = []
else:
currentPara.append(line)
#...and the last one
if currentPara <> []:
paras.append(string.join(currentPara, '\n'))
return paras
def getCommentary():
"""Returns the story for the commentary - all the paragraphs."""
styleSheet = getSampleStyleSheet()
story = []
story.append(Paragraph("""
PLATYPUS User Guide and Test Script
""", styleSheet['Heading1']))
spam = """
Welcome to PLATYPUS!
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.
This document is both the user guide &amp; the output of the test script.
In other words, a script used platypus to create the document you are now
reading, and the fact that you are reading it proves that it works. Or
rather, that it worked for this script anyway. It is a first release!
Platypus is built 'on top of' PDFgen, the Python library for creating PDF
documents. To learn about PDFgen, read the document testpdfgen.pdf.
"""
for text in getParagraphs(spam):
story.append(Paragraph(text, styleSheet['BodyText']))
story.append(Paragraph("""
What concepts does PLATYPUS deal with?
""", styleSheet['Heading2']))
story.append(Paragraph("""
The central concepts in PLATYPUS are Flowable Objects, Frames, Flow
Management, Styles and Style Sheets, Paragraphs and Tables. This is
best explained in contrast to PDFgen, the layer underneath PLATYPUS.
PDFgen is a graphics library, and has primitive commans to draw lines
and strings. There is nothing in it to manage the flow of text down
the page. PLATYPUS works at the conceptual level fo a desktop publishing
package; you can write programs which deal intelligently with graphic
objects and fit them onto the page.
""", styleSheet['BodyText']))
story.append(Paragraph("""
How is this document organized?
""", styleSheet['Heading2']))
story.append(Paragraph("""
Since this is a test script, we'll just note how it is organized.
the top of each page contains commentary. The bottom half contains
example drawings and graphic elements to whicht he commentary will
relate. Down below, you can see the outline of a text frame, and
various bits and pieces within it. We'll explain how they work
on the next page.
""", styleSheet['BodyText']))
story.append(FrameBreak())
#######################################################################
# Commentary Page 2
#######################################################################
story.append(Paragraph("""
Flowable Objects
""", styleSheet['Heading2']))
spam = """
The first and most fundamental concept is that of a 'Flowable Object'.
In PDFgen, you draw stuff by calling methods of the canvas to set up
the colors, fonts and line styles, and draw the graphics primitives.
If you set the pen color to blue, everything you draw after will be
blue until you change it again. And you have to handle all of the X-Y
coordinates yourself.
A 'Flowable object' is exactly what it says. It knows how to draw itself
on the canvas, and the way it does so is totally independent of what
you drew before or after. Furthermore, it draws itself at the location
on the page you specify.
The most fundamental Flowable Objects in most documents are likely to be
paragraphs, tables, diagrams/charts and images - but there is no
restriction. You can write your own easily, and I hope that people
will start to contribute them. PINGO users - we provide a "PINGO flowable" object to let
you insert platform-independent graphics into the flow of a document.
When you write a flowable object, you inherit from Flowable and
must implement two methods. object.wrap(availWidth, availHeight) will be called by other parts of
the system, and tells you how much space you have. You should return
how much space you are going to use. For a fixed-size object, this
is trivial, but it is critical - PLATYPUS needs to figure out if things
will fit on the page before drawing them. For other objects such as paragraphs,
the height is obviously determined by the available width.
The second method is object.draw(). Here, you do whatever you want.
The Flowable base class sets things up so that you have an origin of
(0,0) for your drawing, and everything will fit nicely if you got the
height and width right. It also saves and restores the graphics state
around your calls, so you don;t have to reset all the properties you
changed.
Programs which actually draw a Flowable don't
call draw() this directly - they call object.drawOn(canvas, x, y).
So you can write code in your own coordinate system, and things
can be drawn anywhere on the page (possibly even scaled or rotated).
"""
for text in getParagraphs(spam):
story.append(Paragraph(text, styleSheet['BodyText']))
story.append(FrameBreak())
#######################################################################
# Commentary Page 3
#######################################################################
story.append(Paragraph("""
Available Flowable Objects
""", styleSheet['Heading2']))
story.append(Paragraph("""
Platypus comes with a basic set of flowable objects. Here we list their
class names and tell you what they do:
""", styleSheet['BodyText']))
#we can use the bullet feature to do a definition list
story.append(Paragraph("""
<para color=green bcolor=red bg=pink>This is a contrived object to give an example of a Flowable -
just a fixed-size box with an X through it and a centred string.</para>""",
styleSheet['Definition'],
bulletText='XBox ' #hack - spot the extra space after
))
story.append(Paragraph("""
This is the basic unit of a document. Paragraphs can be finely
tuned and offer a host of properties through their associated
ParagraphStyle.""",
styleSheet['Definition'],
bulletText='Paragraph ' #hack - spot the extra space after
))
story.append(Paragraph("""
This is used for printing code and other preformatted text.
There is no wrapping, and line breaks are taken where they occur.
Many paragraph style properties do not apply. You may supply
an optional 'dedent' parameter to trim a number of characters
off the front of each line.""",
styleSheet['Definition'],
bulletText='Preformatted ' #hack - spot the extra space after
))
story.append(Paragraph("""
This is a straight wrapper around an external image file. By default
the image will be drawn at a scale of one pixel equals one point, and
centred in the frame. You may supply an optional width and height.""",
styleSheet['Definition'],
bulletText='Image ' #hack - spot the extra space after
))
story.append(Paragraph("""
This is a table drawing class; it is intended to be simpler
than a full HTML table model yet be able to draw attractive output,
and behave intelligently when the numbers of rows and columns vary.
Still need to add the cell properties (shading, alignment, font etc.)""",
styleSheet['Definition'],
bulletText='Table ' #hack - spot the extra space after
))
story.append(Paragraph("""
This is a 'null object' which merely takes up space on the page.
Use it when you want some extra padding betweene elements.""",
styleSheet['Definition'],
bulletText='Spacer ' #hack - spot the extra space after
))
story.append(Paragraph("""
A FrameBreak causes the document to call its handle_frameEnd method.""",
styleSheet['Definition'],
bulletText='FrameBreak ' #hack - spot the extra space after
))
story.append(Paragraph("""
This is in progress, but a macro is basically a chunk of Python code to
be evaluated when it is drawn. It could do lots of neat things.""",
styleSheet['Definition'],
bulletText='Macro ' #hack - spot the extra space after
))
story.append(FrameBreak())
story.append(Paragraph(
"The next example uses a custom font",
styleSheet['Italic']))
def code(txt,story=story,styleSheet=styleSheet):
story.append(Preformatted(txt,styleSheet['Code']))
code('''import reportlab.rl_config
reportlab.rl_config.warnOnMissingFontGlyphs = 0
from reportlab.pdfbase import pdfmetrics
fontDir = os.path.join(os.path.dirname(reportlab.__file__),'fonts')
face = pdfmetrics.EmbeddedType1Face(os.path.join(fontDir,'LeERC___.AFM'),
os.path.join(fontDir,'LeERC___.PFB'))
faceName = face.name # should be 'LettErrorRobot-Chrome'
pdfmetrics.registerTypeFace(face)
font = pdfmetrics.Font(faceName, faceName, 'WinAnsiEncoding')
pdfmetrics.registerFont(font)
# put it inside a paragraph.
story.append(Paragraph(
"""This is an ordinary paragraph, which happens to contain
text in an embedded font:
<font name="LettErrorRobot-Chrome">LettErrorRobot-Chrome</font>.
Now for the real challenge...""", styleSheet['Normal']))
styRobot = ParagraphStyle('Robot', styleSheet['Normal'])
styRobot.fontSize = 16
styRobot.leading = 20
styRobot.fontName = 'LettErrorRobot-Chrome'
story.append(Paragraph(
"This whole paragraph is 16-point Letterror-Robot-Chrome.",
styRobot))''')
story.append(FrameBreak())
if _GIF:
story.append(Paragraph("""We can use images via the file name""", styleSheet['BodyText']))
code(''' story.append(platypus.Image('%s'))'''%_GIF)
code(''' story.append(platypus.Image('%s'.encode('utf8')))''' % _GIF)
story.append(Paragraph("""They can also be used with a file URI or from an open python file!""", styleSheet['BodyText']))
code(''' story.append(platypus.Image('%s'))'''% getFurl(_GIF))
code(''' story.append(platypus.Image(open_for_read('%s','b')))''' % _GIF)
story.append(FrameBreak())
story.append(Paragraph("""Images can even be obtained from the internet.""", styleSheet['BodyText']))
code(''' img = platypus.Image('http://www.reportlab.com/rsrc/encryption.gif')
story.append(img)''')
story.append(FrameBreak())
if _JPG:
story.append(Paragraph("""JPEGs are a native PDF image format. They should be available even if PIL cannot be used.""", styleSheet['BodyText']))
story.append(FrameBreak())
return story
def getExamples():
"""Returns all the example flowable objects"""
styleSheet = getSampleStyleSheet()
story = []
#make a style with indents and spacing
sty = ParagraphStyle('obvious', None)
sty.leftIndent = 18
sty.rightIndent = 18
sty.firstLineIndent = 18
sty.spaceBefore = 6
sty.spaceAfter = 6
story.append(Paragraph("""Now for some demo stuff - we need some on this page,
even before we explain the concepts fully""", styleSheet['BodyText']))
p = Paragraph("""
Platypus is all about fitting objects into frames on the page. You
are looking at a fairly simple Platypus paragraph in Debug mode.
It has some gridlines drawn around it to show the left and right indents,
and the space before and after, all of which are attributes set in
the style sheet. To be specific, this paragraph has left and
right indents of 18 points, a first line indent of 36 points,
and 6 points of space before and after itself. A paragraph
object fills the width of the enclosing frame, as you would expect.""", sty)
p.debug = 1 #show me the borders
story.append(p)
story.append(Paragraph("""Same but with justification 1.5 extra leading and green text.""", styleSheet['BodyText']))
p = Paragraph("""
<para align=justify leading=+1.5 fg=green><font color=red>Platypus</font> is all about fitting objects into frames on the page. You
are looking at a fairly simple Platypus paragraph in Debug mode.
It has some gridlines drawn around it to show the left and right indents,
and the space before and after, all of which are attributes set in
the style sheet. To be specific, this paragraph has left and
right indents of 18 points, a first line indent of 36 points,
and 6 points of space before and after itself. A paragraph
object fills the width of the enclosing frame, as you would expect.</para>""", sty)
p.debug = 1 #show me the borders
story.append(p)
story.append(platypus.XBox(4*inch, 0.75*inch,
'This is a box with a fixed size'))
story.append(Paragraph("""
All of this is being drawn within a text frame which was defined
on the page. This frame is in 'debug' mode so you can see the border,
and also see the margins which it reserves. A frame does not have
to have margins, but they have been set to 6 points each to create
a little space around the contents.
""", styleSheet['BodyText']))
story.append(FrameBreak())
#######################################################################
# Examples Page 2
#######################################################################
story.append(Paragraph("""
Here's the base class for Flowable...
""", styleSheet['Italic']))
code = '''class Flowable:
"""Abstract base class for things to be drawn. Key concepts:
1. It knows its size
2. It draws in its own coordinate system (this requires the
base API to provide a translate() function.
"""
def __init__(self):
self.width = 0
self.height = 0
self.wrapped = 0
def drawOn(self, canvas, x, y):
"Tell it to draw itself on the canvas. Do not override"
self.canv = canvas
self.canv.saveState()
self.canv.translate(x, y)
self.draw() #this is the bit you overload
self.canv.restoreState()
del self.canv
def wrap(self, availWidth, availHeight):
"""This will be called by the enclosing frame before objects
are asked their size, drawn or whatever. It returns the
size actually used."""
return (self.width, self.height)
'''
story.append(Preformatted(code, styleSheet['Code'], dedent=4))
story.append(FrameBreak())
#######################################################################
# Examples Page 3
#######################################################################
story.append(Paragraph(
"Here are some examples of the remaining objects above.",
styleSheet['Italic']))
story.append(Paragraph("This is a bullet point", styleSheet['Bullet'], bulletText='O'))
story.append(Paragraph("Another bullet point", styleSheet['Bullet'], bulletText='O'))
story.append(Paragraph("""Here is a Table, which takes all kinds of formatting options...""",
styleSheet['Italic']))
story.append(platypus.Spacer(0, 12))
g = platypus.Table(
(('','North','South','East','West'),
('Quarter 1',100,200,300,400),
('Quarter 2',100,200,300,400),
('Total',200,400,600,800)),
(72,36,36,36,36),
(24, 16,16,18)
)
style = platypus.TableStyle([('ALIGN', (1,1), (-1,-1), 'RIGHT'),
('ALIGN', (0,0), (-1,0), 'CENTRE'),
('GRID', (0,0), (-1,-1), 0.25, colors.black),
('LINEBELOW', (0,0), (-1,0), 2, colors.black),
('LINEBELOW',(1,-1), (-1, -1), 2, (0.5, 0.5, 0.5)),
('TEXTCOLOR', (0,1), (0,-1), colors.black),
('BACKGROUND', (0,0), (-1,0), (0,0.7,0.7))
])
g.setStyle(style)
story.append(g)
story.append(FrameBreak())
#######################################################################
# Examples Page 4 - custom fonts
#######################################################################
# custom font with LettError-Robot font
import reportlab.rl_config
reportlab.rl_config.warnOnMissingFontGlyphs = 0
from reportlab.pdfbase import pdfmetrics
fontDir = os.path.join(os.path.dirname(reportlab.__file__),'fonts')
face = pdfmetrics.EmbeddedType1Face(os.path.join(fontDir,'LeERC___.AFM'),os.path.join(fontDir,'LeERC___.PFB'))
faceName = face.name # should be 'LettErrorRobot-Chrome'
pdfmetrics.registerTypeFace(face)
font = pdfmetrics.Font(faceName, faceName, 'WinAnsiEncoding')
pdfmetrics.registerFont(font)
# put it inside a paragraph.
story.append(Paragraph(
"""This is an ordinary paragraph, which happens to contain
text in an embedded font:
<font name="LettErrorRobot-Chrome">LettErrorRobot-Chrome</font>.
Now for the real challenge...""", styleSheet['Normal']))
styRobot = ParagraphStyle('Robot', styleSheet['Normal'])
styRobot.fontSize = 16
styRobot.leading = 20
styRobot.fontName = 'LettErrorRobot-Chrome'
story.append(Paragraph(
"This whole paragraph is 16-point Letterror-Robot-Chrome.",
styRobot))
story.append(FrameBreak())
if _GIF:
story.append(Paragraph("Here is an Image flowable obtained from a string filename.",styleSheet['Italic']))
story.append(platypus.Image(_GIF))
story.append(Paragraph( "Here is an Image flowable obtained from a utf8 filename.", styleSheet['Italic']))
story.append(platypus.Image(_GIF.encode('utf8')))
story.append(Paragraph("Here is an Image flowable obtained from a string file url.",styleSheet['Italic']))
story.append(platypus.Image(getFurl(_GIF)))
story.append(Paragraph("Here is an Image flowable obtained from an open file.",styleSheet['Italic']))
story.append(platypus.Image(open_for_read(_GIF,'b')))
story.append(FrameBreak())
try:
img = platypus.Image('http://www.reportlab.com/rsrc/encryption.gif')
story.append(Paragraph("Here is an Image flowable obtained from a string http url.",styleSheet['Italic']))
story.append(img)
except:
story.append(Paragraph("The image could not be obtained from a string http url.",styleSheet['Italic']))
story.append(FrameBreak())
if _JPG:
img = platypus.Image(_JPG)
story.append(Paragraph("Here is an JPEG Image flowable obtained from a filename.",styleSheet['Italic']))
story.append(img)
story.append(Paragraph("Here is an JPEG Image flowable obtained from an open file.",styleSheet['Italic']))
img = platypus.Image(open_for_read(_JPG,'b'))
story.append(img)
story.append(FrameBreak())
return story
class AndyTemplate(BaseDocTemplate):
_invalidInitArgs = ('pageTemplates',)
def __init__(self, filename, **kw):
frame1 = platypus.Frame(inch, 5.6*inch, 6*inch, 5.2*inch,id='F1')
frame2 = platypus.Frame(inch, inch, 6*inch, 4.5*inch, showBoundary=1,id='F2')
self.allowSplitting = 0
apply(BaseDocTemplate.__init__,(self,filename),kw)
self.addPageTemplates(PageTemplate('normal',[frame1,frame2],framePage))
def fillFrame(self,flowables):
f = self.frame
while len(flowables)>0 and f is self.frame:
self.handle_flowable(flowables)
def build(self, flowables1, flowables2):
assert filter(lambda x: not isinstance(x,Flowable), flowables1)==[], "flowables1 argument error"
assert filter(lambda x: not isinstance(x,Flowable), flowables2)==[], "flowables2 argument error"
self._startBuild()
while (len(flowables1) > 0 or len(flowables1) > 0):
self.clean_hanging()
self.fillFrame(flowables1)
self.fillFrame(flowables2)
self._endBuild()
def showProgress(pageNo):
print 'CALLBACK SAYS: page %d' % pageNo
def run():
doc = AndyTemplate(outputfile('test_platypus_general.pdf'))
#doc.setPageCallBack(showProgress)
commentary = getCommentary()
examples = getExamples()
doc.build(commentary,examples)
class PlatypusTestCase(unittest.TestCase):
"Make documents with lots of Platypus features"
def test0(self):
"Make a platypus document"
run()
def makeSuite():
return makeSuiteForClasses(PlatypusTestCase)
#noruntests
if __name__ == "__main__":
if '-debug' in sys.argv:
run()
else:
unittest.TextTestRunner().run(makeSuite())
printLocation()