bzr revid: pinky-1da086d6ffc903bddd20216fdc81b76f38293992
This commit is contained in:
pinky 2007-01-07 23:35:16 +00:00
parent 87b2a6bebc
commit 6488c0b0da
11 changed files with 9000 additions and 0 deletions

View File

@ -0,0 +1,15 @@
#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/platypus/__init__.py
__version__=''' $Id: __init__.py 2775 2006-02-15 12:17:24Z rgbecker $ '''
__doc__=''
from reportlab.platypus.flowables import Flowable, Image, Macro, PageBreak, Preformatted, Spacer, XBox, \
CondPageBreak, KeepTogether, TraceInfo, FailOnWrap, FailOnDraw, PTOContainer, \
KeepInFrame, ParagraphAndImage, ImageAndFlowables
from reportlab.platypus.paragraph import Paragraph, cleanBlockQuotedText, ParaLines
from reportlab.platypus.paraparser import ParaFrag
from reportlab.platypus.tables import Table, TableStyle, CellStyle, LongTable
from reportlab.platypus.frames import Frame
from reportlab.platypus.doctemplate import BaseDocTemplate, NextPageTemplate, PageTemplate, ActionFlowable, \
SimpleDocTemplate, FrameBreak, PageBegin, Indenter
from xpreformatted import XPreformatted

View File

@ -0,0 +1,953 @@
#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/platypus/doctemplate.py
__version__=''' $Id: doctemplate.py 2852 2006-05-08 15:04:15Z rgbecker $ '''
__doc__="""
This module contains the core structure of platypus.
Platypus constructs documents. Document styles are determined by DocumentTemplates.
Each DocumentTemplate contains one or more PageTemplates which defines the look of the
pages of the document.
Each PageTemplate has a procedure for drawing the "non-flowing" part of the page
(for example the header, footer, page number, fixed logo graphic, watermark, etcetera) and
a set of Frames which enclose the flowing part of the page (for example the paragraphs,
tables, or non-fixed diagrams of the text).
A document is built when a DocumentTemplate is fed a sequence of Flowables.
The action of the build consumes the flowables in order and places them onto
frames on pages as space allows. When a frame runs out of space the next frame
of the page is used. If no frame remains a new page is created. A new page
can also be created if a page break is forced.
The special invisible flowable NextPageTemplate can be used to specify
the page template for the next page (which by default is the one being used
for the current frame).
"""
from reportlab.platypus.flowables import *
from reportlab.lib.units import inch
from reportlab.platypus.paragraph import Paragraph
from reportlab.platypus.frames import Frame
from reportlab.rl_config import defaultPageSize, verbose
import reportlab.lib.sequencer
from reportlab.pdfgen import canvas
from types import *
import sys
import logging
logger = logging.getLogger("reportlab.platypus")
class LayoutError(Exception):
pass
def _doNothing(canvas, doc):
"Dummy callback for onPage"
pass
class IndexingFlowable(Flowable):
"""Abstract interface definition for flowables which might
hold references to other pages or themselves be targets
of cross-references. XRefStart, XRefDest, Table of Contents,
Indexes etc."""
def isIndexing(self):
return 1
def isSatisfied(self):
return 1
def notify(self, kind, stuff):
"""This will be called by the framework wherever 'stuff' happens.
'kind' will be a value that can be used to decide whether to
pay attention or not."""
pass
def beforeBuild(self):
"""Called by multiBuild before it starts; use this to clear
old contents"""
pass
def afterBuild(self):
"""Called after build ends but before isSatisfied"""
pass
class ActionFlowable(Flowable):
'''This Flowable is never drawn, it can be used for data driven controls
For example to change a page template (from one column to two, for example)
use NextPageTemplate which creates an ActionFlowable.
'''
def __init__(self,action=()):
#must call super init to ensure it has a width and height (of zero),
#as in some cases the packer might get called on it...
Flowable.__init__(self)
if type(action) not in (ListType, TupleType):
action = (action,)
self.action = tuple(action)
def apply(self,doc):
'''
This is called by the doc.build processing to allow the instance to
implement its behaviour
'''
action = self.action[0]
args = tuple(self.action[1:])
arn = 'handle_'+action
try:
apply(getattr(doc,arn), args)
except AttributeError, aerr:
if aerr.args[0]==arn:
raise NotImplementedError, "Can't handle ActionFlowable(%s)" % action
else:
raise
except "bogus":
t, v, unused = sys.exc_info()
raise t, "%s\n handle_%s args=%s"%(v,action,args)
def __call__(self):
return self
def identity(self, maxLen=None):
return "ActionFlowable: %s%s" % (str(self.action),self._frameName())
class LCActionFlowable(ActionFlowable):
locChanger = 1 #we cause a frame or page change
def wrap(self, availWidth, availHeight):
'''Should never be called.'''
raise NotImplementedError
def draw(self):
'''Should never be called.'''
raise NotImplementedError
class NextFrameFlowable(ActionFlowable):
def __init__(self,ix,resume=0):
ActionFlowable.__init__(self,('nextFrame',ix,resume))
class CurrentFrameFlowable(LCActionFlowable):
def __init__(self,ix,resume=0):
ActionFlowable.__init__(self,('currentFrame',ix,resume))
class NullActionFlowable(ActionFlowable):
def apply(self):
pass
class _FrameBreak(LCActionFlowable):
'''
A special ActionFlowable that allows setting doc._nextFrameIndex
eg story.append(FrameBreak('mySpecialFrame'))
'''
def __call__(self,ix=None,resume=0):
r = self.__class__(self.action+(resume,))
r._ix = ix
return r
def apply(self,doc):
if getattr(self,'_ix',None):
doc.handle_nextFrame(self._ix)
ActionFlowable.apply(self,doc)
FrameBreak = _FrameBreak('frameEnd')
PageBegin = LCActionFlowable('pageBegin')
def _evalMeasurement(n):
if type(n) is type(''):
from paraparser import _num
n = _num(n)
if type(n) is type(()): n = n[1]
return n
class FrameActionFlowable(Flowable):
def __init__(self,*arg,**kw):
raise NotImplementedError('Abstract Class')
def frameAction(self,frame):
raise NotImplementedError('Abstract Class')
class Indenter(FrameActionFlowable):
"""Increases or decreases left and right margins of frame.
This allows one to have a 'context-sensitive' indentation
and makes nested lists way easier.
"""
def __init__(self, left=0, right=0):
self.left = _evalMeasurement(left)
self.right = _evalMeasurement(right)
def frameAction(self, frame):
frame._leftExtraIndent += self.left
frame._rightExtraIndent += self.right
class NextPageTemplate(ActionFlowable):
"""When you get to the next page, use the template specified (change to two column, for example) """
def __init__(self,pt):
ActionFlowable.__init__(self,('nextPageTemplate',pt))
class PageTemplate:
"""
essentially a list of Frames and an onPage routine to call at the start
of a page when this is selected. onPageEnd gets called at the end.
derived classes can also implement beforeDrawPage and afterDrawPage if they want
"""
def __init__(self,id=None,frames=[],onPage=_doNothing, onPageEnd=_doNothing,
pagesize=None):
if type(frames) not in (ListType,TupleType): frames = [frames]
assert filter(lambda x: not isinstance(x,Frame), frames)==[], "frames argument error"
self.id = id
self.frames = frames
self.onPage = onPage
self.onPageEnd = onPageEnd
self.pagesize = pagesize
def beforeDrawPage(self,canv,doc):
"""Override this if you want additional functionality or prefer
a class based page routine. Called before any flowables for
this page are processed."""
pass
def checkPageSize(self,canv,doc):
'''This gets called by the template framework
If canv size != template size then the canv size is set to
the template size or if that's not available to the
doc size.
'''
#### NEVER EVER EVER COMPARE FLOATS FOR EQUALITY
#RGB converting pagesizes to ints means we are accurate to one point
#RGB I suggest we should be aiming a little better
cp = None
dp = None
sp = None
if canv._pagesize: cp = map(int, canv._pagesize)
if self.pagesize: sp = map(int, self.pagesize)
if doc.pagesize: dp = map(int, doc.pagesize)
if cp!=sp:
if sp:
canv.setPageSize(self.pagesize)
elif cp!=dp:
canv.setPageSize(doc.pagesize)
def afterDrawPage(self, canv, doc):
"""This is called after the last flowable for the page has
been processed. You might use this if the page header or
footer needed knowledge of what flowables were drawn on
this page."""
pass
class BaseDocTemplate:
"""
First attempt at defining a document template class.
The basic idea is simple.
0) The document has a list of data associated with it
this data should derive from flowables. We'll have
special classes like PageBreak, FrameBreak to do things
like forcing a page end etc.
1) The document has one or more page templates.
2) Each page template has one or more frames.
3) The document class provides base methods for handling the
story events and some reasonable methods for getting the
story flowables into the frames.
4) The document instances can override the base handler routines.
Most of the methods for this class are not called directly by the user,
but in some advanced usages they may need to be overridden via subclassing.
EXCEPTION: doctemplate.build(...) must be called for most reasonable uses
since it builds a document using the page template.
Each document template builds exactly one document into a file specified
by the filename argument on initialization.
Possible keyword arguments for the initialization:
pageTemplates: A list of templates. Must be nonempty. Names
assigned to the templates are used for referring to them so no two used
templates should have the same name. For example you might want one template
for a title page, one for a section first page, one for a first page of
a chapter and two more for the interior of a chapter on odd and even pages.
If this argument is omitted then at least one pageTemplate should be provided
using the addPageTemplates method before the document is built.
pageSize: a 2-tuple or a size constant from reportlab/lib/pagesizes.pu.
Used by the SimpleDocTemplate subclass which does NOT accept a list of
pageTemplates but makes one for you; ignored when using pageTemplates.
showBoundary: if set draw a box around the frame boundaries.
leftMargin:
rightMargin:
topMargin:
bottomMargin: Margin sizes in points (default 1 inch)
These margins may be overridden by the pageTemplates. They are primarily of interest
for the SimpleDocumentTemplate subclass.
allowSplitting: If set flowables (eg, paragraphs) may be split across frames or pages
(default: 1)
title: Internal title for document (does not automatically display on any page)
author: Internal author for document (does not automatically display on any page)
"""
_initArgs = { 'pagesize':defaultPageSize,
'pageTemplates':[],
'showBoundary':0,
'leftMargin':inch,
'rightMargin':inch,
'topMargin':inch,
'bottomMargin':inch,
'allowSplitting':1,
'title':None,
'author':None,
'invariant':None,
'pageCompression':None,
'_pageBreakQuick':1,
'rotation':0,
'_debug':0}
_invalidInitArgs = ()
_firstPageTemplateIndex = 0
def __init__(self, filename, **kw):
"""create a document template bound to a filename (see class documentation for keyword arguments)"""
self.filename = filename
for k in self._initArgs.keys():
if not kw.has_key(k):
v = self._initArgs[k]
else:
if k in self._invalidInitArgs:
raise ValueError, "Invalid argument %s" % k
v = kw[k]
setattr(self,k,v)
p = self.pageTemplates
self.pageTemplates = []
self.addPageTemplates(p)
# facility to assist multi-build and cross-referencing.
# various hooks can put things into here - key is what
# you want, value is a page number. This can then be
# passed to indexing flowables.
self._pageRefs = {}
self._indexingFlowables = []
#callback facility for progress monitoring
self._onPage = None
self._onProgress = None
self._flowableCount = 0 # so we know how far to go
#infinite loop detection if we start doing lots of empty pages
self._curPageFlowableCount = 0
self._emptyPages = 0
self._emptyPagesAllowed = 10
#context sensitive margins - set by story, not from outside
self._leftExtraIndent = 0.0
self._rightExtraIndent = 0.0
self._calc()
self.afterInit()
def _calc(self):
self._rightMargin = self.pagesize[0] - self.rightMargin
self._topMargin = self.pagesize[1] - self.topMargin
self.width = self._rightMargin - self.leftMargin
self.height = self._topMargin - self.bottomMargin
def setPageCallBack(self, func):
'Simple progress monitor - func(pageNo) called on each new page'
self._onPage = func
def setProgressCallBack(self, func):
'''Cleverer progress monitor - func(typ, value) called regularly'''
self._onProgress = func
def clean_hanging(self):
'handle internal postponed actions'
while len(self._hanging):
self.handle_flowable(self._hanging)
def addPageTemplates(self,pageTemplates):
'add one or a sequence of pageTemplates'
if type(pageTemplates) not in (ListType,TupleType):
pageTemplates = [pageTemplates]
#this test below fails due to inconsistent imports!
#assert filter(lambda x: not isinstance(x,PageTemplate), pageTemplates)==[], "pageTemplates argument error"
for t in pageTemplates:
self.pageTemplates.append(t)
def handle_documentBegin(self):
'''implement actions at beginning of document'''
self._hanging = [PageBegin]
self.pageTemplate = self.pageTemplates[self._firstPageTemplateIndex]
self.page = 0
self.beforeDocument()
def handle_pageBegin(self):
'''Perform actions required at beginning of page.
shouldn't normally be called directly'''
self.page += 1
if self._debug: logger.debug("beginning page %d" % self.page)
self.pageTemplate.beforeDrawPage(self.canv,self)
self.pageTemplate.checkPageSize(self.canv,self)
self.pageTemplate.onPage(self.canv,self)
for f in self.pageTemplate.frames: f._reset()
self.beforePage()
#keep a count of flowables added to this page. zero indicates bad stuff
self._curPageFlowableCount = 0
if hasattr(self,'_nextFrameIndex'):
del self._nextFrameIndex
self.frame = self.pageTemplate.frames[0]
self.frame._debug = self._debug
self.handle_frameBegin()
def handle_pageEnd(self):
''' show the current page
check the next page template
hang a page begin
'''
#detect infinite loops...
if self._curPageFlowableCount == 0:
self._emptyPages += 1
else:
self._emptyPages = 0
if self._emptyPages >= self._emptyPagesAllowed:
if 1:
ident = "More than %d pages generated without content - halting layout. Likely that a flowable is too large for any frame." % self._emptyPagesAllowed
#leave to keep apart from the raise
raise LayoutError(ident)
else:
pass #attempt to restore to good state
else:
if self._onProgress:
self._onProgress('PAGE', self.canv.getPageNumber())
self.pageTemplate.afterDrawPage(self.canv, self)
self.pageTemplate.onPageEnd(self.canv, self)
self.afterPage()
if self._debug: logger.debug("ending page %d" % self.page)
self.canv.setPageRotation(getattr(self.pageTemplate,'rotation',self.rotation))
self.canv.showPage()
if hasattr(self,'_nextPageTemplateCycle'):
#they are cycling through pages'; we keep the index
cyc = self._nextPageTemplateCycle
idx = self._nextPageTemplateIndex
self.pageTemplate = cyc[idx] #which is one of the ones in the list anyway
#bump up by 1
self._nextPageTemplateIndex = (idx + 1) % len(cyc)
elif hasattr(self,'_nextPageTemplateIndex'):
self.pageTemplate = self.pageTemplates[self._nextPageTemplateIndex]
del self._nextPageTemplateIndex
if self._emptyPages==0:
pass #store good state here
self._hanging.append(PageBegin)
def handle_pageBreak(self,slow=None):
'''some might choose not to end all the frames'''
if self._pageBreakQuick and not slow:
self.handle_pageEnd()
else:
n = len(self._hanging)
while len(self._hanging)==n:
self.handle_frameEnd()
def handle_frameBegin(self,resume=0):
'''What to do at the beginning of a frame'''
f = self.frame
if f._atTop:
if self.showBoundary or self.frame.showBoundary:
self.frame.drawBoundary(self.canv)
f._leftExtraIndent = self._leftExtraIndent
f._rightExtraIndent = self._rightExtraIndent
def handle_frameEnd(self,resume=0):
''' Handles the semantics of the end of a frame. This includes the selection of
the next frame or if this is the last frame then invoke pageEnd.
'''
self._leftExtraIndent = self.frame._leftExtraIndent
self._rightExtraIndent = self.frame._rightExtraIndent
if hasattr(self,'_nextFrameIndex'):
self.frame = self.pageTemplate.frames[self._nextFrameIndex]
self.frame._debug = self._debug
del self._nextFrameIndex
self.handle_frameBegin(resume)
elif hasattr(self.frame,'lastFrame') or self.frame is self.pageTemplate.frames[-1]:
self.handle_pageEnd()
self.frame = None
else:
f = self.frame
self.frame = self.pageTemplate.frames[self.pageTemplate.frames.index(f) + 1]
self.frame._debug = self._debug
self.handle_frameBegin()
def handle_nextPageTemplate(self,pt):
'''On endPage change to the page template with name or index pt'''
if type(pt) is StringType:
if hasattr(self, '_nextPageTemplateCycle'): del self._nextPageTemplateCycle
for t in self.pageTemplates:
if t.id == pt:
self._nextPageTemplateIndex = self.pageTemplates.index(t)
return
raise ValueError, "can't find template('%s')"%pt
elif type(pt) is IntType:
if hasattr(self, '_nextPageTemplateCycle'): del self._nextPageTemplateCycle
self._nextPageTemplateIndex = pt
elif type(pt) in (ListType, TupleType):
#used for alternating left/right pages
#collect the refs to the template objects, complain if any are bad
cycle = []
for templateName in pt:
found = 0
for t in self.pageTemplates:
if t.id == templateName:
cycle.append(t)
found = 1
if not found:
raise ValueError("Cannot find page template called %s" % templateName)
#double-check all of them are there...
first = cycle[0]
#ensure we start on the first one
self._nextPageTemplateCycle = cycle
self._nextPageTemplateIndex = 0 #indexes into the cycle
else:
raise TypeError, "argument pt should be string or integer or list"
def handle_nextFrame(self,fx,resume=0):
'''On endFrame change to the frame with name or index fx'''
if type(fx) is StringType:
for f in self.pageTemplate.frames:
if f.id == fx:
self._nextFrameIndex = self.pageTemplate.frames.index(f)
return
raise ValueError, "can't find frame('%s')"%fx
elif type(fx) is IntType:
self._nextFrameIndex = fx
else:
raise TypeError, "argument fx should be string or integer"
def handle_currentFrame(self,fx,resume=0):
'''change to the frame with name or index fx'''
self.handle_nextFrame(fx,resume)
self.handle_frameEnd(resume)
def handle_breakBefore(self, flowables):
'''preprocessing step to allow pageBreakBefore and frameBreakBefore attributes'''
first = flowables[0]
# if we insert a page break before, we'll process that, see it again,
# and go in an infinite loop. So we need to set a flag on the object
# saying 'skip me'. This should be unset on the next pass
if hasattr(first, '_skipMeNextTime'):
delattr(first, '_skipMeNextTime')
return
# this could all be made much quicker by putting the attributes
# in to the flowables with a defult value of 0
if hasattr(first,'pageBreakBefore') and first.pageBreakBefore == 1:
first._skipMeNextTime = 1
first.insert(0, PageBreak())
return
if hasattr(first,'style') and hasattr(first.style, 'pageBreakBefore') and first.style.pageBreakBefore == 1:
first._skipMeNextTime = 1
flowables.insert(0, PageBreak())
return
if hasattr(first,'frameBreakBefore') and first.frameBreakBefore == 1:
first._skipMeNextTime = 1
flowables.insert(0, FrameBreak())
return
if hasattr(first,'style') and hasattr(first.style, 'frameBreakBefore') and first.style.frameBreakBefore == 1:
first._skipMeNextTime = 1
flowables.insert(0, FrameBreak())
return
def handle_keepWithNext(self, flowables):
"implements keepWithNext"
i = 0
n = len(flowables)
while i<n and flowables[i].getKeepWithNext(): i += 1
if i:
if not getattr(flowables[i],'locChanger',None): i += 1
K = KeepTogether(flowables[:i])
for f in K._content:
f.keepWithNext = 0
del flowables[:i]
flowables.insert(0,K)
def _fIdent(self,f,maxLen=None,frame=None):
if frame: f._frame = frame
try:
return f.identity(maxLen)
finally:
if frame: del f._frame
def handle_flowable(self,flowables):
'''try to handle one flowable from the front of list flowables.'''
#allow document a chance to look at, modify or ignore
#the object(s) about to be processed
self.filterFlowables(flowables)
self.handle_breakBefore(flowables)
self.handle_keepWithNext(flowables)
f = flowables[0]
del flowables[0]
if f is None:
return
if isinstance(f,PageBreak):
if isinstance(f,SlowPageBreak):
self.handle_pageBreak(slow=1)
else:
self.handle_pageBreak()
self.afterFlowable(f)
elif isinstance(f,ActionFlowable):
f.apply(self)
self.afterFlowable(f)
else:
#try to fit it then draw it
if self.frame.add(f, self.canv, trySplit=self.allowSplitting):
self._curPageFlowableCount += 1
self.afterFlowable(f)
else:
if self.allowSplitting:
# see if this is a splittable thing
S = self.frame.split(f,self.canv)
n = len(S)
else:
n = 0
if n:
if not isinstance(S[0],(PageBreak,SlowPageBreak,ActionFlowable)):
if self.frame.add(S[0], self.canv, trySplit=0):
self._curPageFlowableCount += 1
self.afterFlowable(S[0])
else:
ident = "Splitting error(n==%d) on page %d in\n%s" % (n,self.page,self._fIdent(f,30,self.frame))
#leave to keep apart from the raise
raise LayoutError(ident)
del S[0]
for i,f in enumerate(S):
flowables.insert(i,f) # put split flowables back on the list
else:
if hasattr(f,'_postponed'):
ident = "Flowable %s too large on page %d" % (self._fIdent(f,30,self.frame), self.page)
#leave to keep apart from the raise
raise LayoutError(ident)
# this ought to be cleared when they are finally drawn!
f._postponed = 1
flowables.insert(0,f) # put the flowable back
self.handle_frameEnd()
#these are provided so that deriving classes can refer to them
_handle_documentBegin = handle_documentBegin
_handle_pageBegin = handle_pageBegin
_handle_pageEnd = handle_pageEnd
_handle_frameBegin = handle_frameBegin
_handle_frameEnd = handle_frameEnd
_handle_flowable = handle_flowable
_handle_nextPageTemplate = handle_nextPageTemplate
_handle_currentFrame = handle_currentFrame
_handle_nextFrame = handle_nextFrame
def _startBuild(self, filename=None, canvasmaker=canvas.Canvas):
self._calc()
self.canv = canvasmaker(filename or self.filename,
pagesize=self.pagesize,
invariant=self.invariant,
pageCompression=self.pageCompression)
if self._onPage:
self.canv.setPageCallBack(self._onPage)
self.handle_documentBegin()
def _endBuild(self):
if self._hanging!=[] and self._hanging[-1] is PageBegin:
del self._hanging[-1]
self.clean_hanging()
else:
self.clean_hanging()
self.handle_pageBreak()
if getattr(self,'_doSave',1): self.canv.save()
if self._onPage: self.canv.setPageCallBack(None)
def build(self, flowables, filename=None, canvasmaker=canvas.Canvas):
"""Build the document from a list of flowables.
If the filename argument is provided then that filename is used
rather than the one provided upon initialization.
If the canvasmaker argument is provided then it will be used
instead of the default. For example a slideshow might use
an alternate canvas which places 6 slides on a page (by
doing translations, scalings and redefining the page break
operations).
"""
#assert filter(lambda x: not isinstance(x,Flowable), flowables)==[], "flowables argument error"
flowableCount = len(flowables)
if self._onProgress:
self._onProgress('STARTED',0)
self._onProgress('SIZE_EST', len(flowables))
self._startBuild(filename,canvasmaker)
while len(flowables):
self.clean_hanging()
try:
first = flowables[0]
self.handle_flowable(flowables)
except:
#if it has trace info, add it to the traceback message.
if hasattr(first, '_traceInfo') and first._traceInfo:
exc = sys.exc_info()[1]
args = list(exc.args)
tr = first._traceInfo
args[0] += '\n(srcFile %s, line %d char %d to line %d char %d)' % (
tr.srcFile,
tr.startLineNo,
tr.startLinePos,
tr.endLineNo,
tr.endLinePos
)
exc.args = tuple(args)
raise
if self._onProgress:
self._onProgress('PROGRESS',flowableCount - len(flowables))
self._endBuild()
if self._onProgress:
self._onProgress('FINISHED',0)
def _allSatisfied(self):
"""Called by multi-build - are all cross-references resolved?"""
allHappy = 1
for f in self._indexingFlowables:
if not f.isSatisfied():
allHappy = 0
break
return allHappy
def notify(self, kind, stuff):
""""Forward to any listeners"""
for l in self._indexingFlowables:
l.notify(kind, stuff)
def pageRef(self, label):
"""hook to register a page number"""
if verbose: print "pageRef called with label '%s' on page %d" % (
label, self.page)
self._pageRefs[label] = self.page
def multiBuild(self, story,
filename=None,
canvasmaker=canvas.Canvas,
maxPasses = 10):
"""Makes multiple passes until all indexing flowables
are happy."""
self._indexingFlowables = []
#scan the story and keep a copy
for thing in story:
if thing.isIndexing():
self._indexingFlowables.append(thing)
#better fix for filename is a 'file' problem
self._doSave = 0
passes = 0
while 1:
passes += 1
if self._onProgress:
self._onProgress('PASS', passes)
if verbose: print 'building pass '+str(passes) + '...',
for fl in self._indexingFlowables:
fl.beforeBuild()
# work with a copy of the story, since it is consumed
tempStory = story[:]
self.build(tempStory, filename, canvasmaker)
#self.notify('debug',None)
#clean up so multi-build does not go wrong - the frame
#packer might have tacked an attribute onto some flowables
for elem in story:
if hasattr(elem, '_postponed'):
del elem._postponed
for fl in self._indexingFlowables:
fl.afterBuild()
happy = self._allSatisfied()
if happy:
self._doSave = 0
self.canv.save()
break
if passes > maxPasses:
raise IndexError, "Index entries not resolved after %d passes" % maxPasses
if verbose: print 'saved'
#these are pure virtuals override in derived classes
#NB these get called at suitable places by the base class
#so if you derive and override the handle_xxx methods
#it's up to you to ensure that they maintain the needed consistency
def afterInit(self):
"""This is called after initialisation of the base class."""
pass
def beforeDocument(self):
"""This is called before any processing is
done on the document."""
pass
def beforePage(self):
"""This is called at the beginning of page
processing, and immediately before the
beforeDrawPage method of the current page
template."""
pass
def afterPage(self):
"""This is called after page processing, and
immediately after the afterDrawPage method
of the current page template."""
pass
def filterFlowables(self,flowables):
'''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.
'''
pass
def afterFlowable(self, flowable):
'''called after a flowable has been rendered'''
pass
class SimpleDocTemplate(BaseDocTemplate):
"""A special case document template that will handle many simple documents.
See documentation for BaseDocTemplate. No pageTemplates are required
for this special case. A page templates are inferred from the
margin information and the onFirstPage, onLaterPages arguments to the build method.
A document which has all pages with the same look except for the first
page may can be built using this special approach.
"""
_invalidInitArgs = ('pageTemplates',)
def handle_pageBegin(self):
'''override base method to add a change of page template after the firstpage.
'''
self._handle_pageBegin()
self._handle_nextPageTemplate('Later')
def build(self,flowables,onFirstPage=_doNothing, onLaterPages=_doNothing, canvasmaker=canvas.Canvas):
"""build the document using the flowables. Annotate the first page using the onFirstPage
function and later pages using the onLaterPages function. The onXXX pages should follow
the signature
def myOnFirstPage(canvas, document):
# do annotations and modify the document
...
The functions can do things like draw logos, page numbers,
footers, etcetera. They can use external variables to vary
the look (for example providing page numbering or section names).
"""
self._calc() #in case we changed margins sizes etc
frameT = Frame(self.leftMargin, self.bottomMargin, self.width, self.height, id='normal')
self.addPageTemplates([PageTemplate(id='First',frames=frameT, onPage=onFirstPage,pagesize=self.pagesize),
PageTemplate(id='Later',frames=frameT, onPage=onLaterPages,pagesize=self.pagesize)])
if onFirstPage is _doNothing and hasattr(self,'onFirstPage'):
self.pageTemplates[0].beforeDrawPage = self.onFirstPage
if onLaterPages is _doNothing and hasattr(self,'onLaterPages'):
self.pageTemplates[1].beforeDrawPage = self.onLaterPages
BaseDocTemplate.build(self,flowables, canvasmaker=canvasmaker)
def progressCB(typ, value):
"""Example prototype for progress monitoring.
This aims to provide info about what is going on
during a big job. It should enable, for example, a reasonably
smooth progress bar to be drawn. We design the argument
signature to be predictable and conducive to programming in
other (type safe) languages. If set, this will be called
repeatedly with pairs of values. The first is a string
indicating the type of call; the second is a numeric value.
typ 'STARTING', value = 0
typ 'SIZE_EST', value = numeric estimate of job size
typ 'PASS', value = number of this rendering pass
typ 'PROGRESS', value = number between 0 and SIZE_EST
typ 'PAGE', value = page number of page
type 'FINISHED', value = 0
The sequence is
STARTING - always called once
SIZE_EST - always called once
PROGRESS - called often
PAGE - called often when page is emitted
FINISHED - called when really, really finished
some juggling is needed to accurately estimate numbers of
pages in pageDrawing mode.
NOTE: the SIZE_EST is a guess. It is possible that the
PROGRESS value may slightly exceed it, or may even step
back a little on rare occasions. The only way to be
really accurate would be to do two passes, and I don't
want to take that performance hit.
"""
print 'PROGRESS MONITOR: %-10s %d' % (typ, value)
if __name__ == '__main__':
def myFirstPage(canvas, doc):
canvas.saveState()
canvas.setStrokeColor(red)
canvas.setLineWidth(5)
canvas.line(66,72,66,PAGE_HEIGHT-72)
canvas.setFont('Times-Bold',24)
canvas.drawString(108, PAGE_HEIGHT-108, "TABLE OF CONTENTS DEMO")
canvas.setFont('Times-Roman',12)
canvas.drawString(4 * inch, 0.75 * inch, "First Page")
canvas.restoreState()
def myLaterPages(canvas, doc):
canvas.saveState()
canvas.setStrokeColor(red)
canvas.setLineWidth(5)
canvas.line(66,72,66,PAGE_HEIGHT-72)
canvas.setFont('Times-Roman',12)
canvas.drawString(4 * inch, 0.75 * inch, "Page %d" % doc.page)
canvas.restoreState()
def run():
objects_to_draw = []
from reportlab.lib.styles import ParagraphStyle
#from paragraph import Paragraph
from doctemplate import SimpleDocTemplate
#need a style
normal = ParagraphStyle('normal')
normal.firstLineIndent = 18
normal.spaceBefore = 6
from reportlab.lib.randomtext import randomText
import random
for i in range(15):
height = 0.5 + (2*random.random())
box = XBox(6 * inch, height * inch, 'Box Number %d' % i)
objects_to_draw.append(box)
para = Paragraph(randomText(), normal)
objects_to_draw.append(para)
SimpleDocTemplate('doctemplate.pdf').build(objects_to_draw,
onFirstPage=myFirstPage,onLaterPages=myLaterPages)
run()

View File

@ -0,0 +1,372 @@
#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/platypus/figures.py
"""This includes some demos of platypus for use in the API proposal"""
__version__=''' $Id: figures.py 2385 2004-06-17 15:26:05Z rgbecker $ '''
import os
from reportlab.lib import colors
from reportlab.pdfgen.canvas import Canvas
from reportlab.lib.styles import ParagraphStyle
from reportlab.lib.utils import recursiveImport
from reportlab.platypus import Frame
from reportlab.platypus import Flowable
from reportlab.platypus import Paragraph
from reportlab.lib.units import inch
from reportlab.lib.enums import TA_LEFT, TA_RIGHT, TA_CENTER
from reportlab.lib.validators import isColor
from reportlab.lib.colors import toColor
captionStyle = ParagraphStyle('Caption', fontName='Times-Italic', fontSize=10, alignment=TA_CENTER)
class Figure(Flowable):
def __init__(self, width, height, caption="",
captionFont="Times-Italic", captionSize=12,
background=None,
captionTextColor=toColor('black'),
captionBackColor=None,
border=1):
Flowable.__init__(self)
self.width = width
self.figureHeight = height
self.caption = caption
self.captionFont = captionFont
self.captionSize = captionSize
self.captionTextColor = captionTextColor
self.captionBackColor = captionBackColor
self._captionData = None
self.captionHeight = 0 # work out later
self.background = background
self.border = border
self.spaceBefore = 12
self.spaceAfter = 12
def _getCaptionPara(self):
caption = self.caption
captionFont = self.captionFont
captionSize = self.captionSize
captionTextColor = self.captionTextColor
captionBackColor = self.captionBackColor
if self._captionData!=(caption,captionFont,captionSize,captionTextColor,captionBackColor):
self._captionData = (caption,captionFont,captionSize,captionTextColor,captionBackColor)
self.captionStyle = ParagraphStyle(
'Caption',
fontName=captionFont,
fontSize=captionSize,
leading=1.2*captionSize,
textColor = captionTextColor,
backColor = captionBackColor,
spaceBefore=captionSize * 0.5,
alignment=TA_CENTER)
#must build paragraph now to get sequencing in synch with rest of story
self.captionPara = Paragraph(self.caption, self.captionStyle)
def wrap(self, availWidth, availHeight):
# try to get the caption aligned
if self.caption:
self._getCaptionPara()
(w, h) = self.captionPara.wrap(self.width, availHeight - self.figureHeight)
self.captionHeight = h
self.height = self.captionHeight + self.figureHeight
if w>self.width: self.width = w
else:
self.height = self.figureHeight
self.dx = 0.5 * (availWidth - self.width)
return (self.width, self.height)
def draw(self):
self.canv.translate(self.dx, 0)
if self.caption:
self._getCaptionPara()
self.drawCaption()
self.canv.translate(0, self.captionHeight)
if self.background:
self.drawBackground()
if self.border:
self.drawBorder()
self.drawFigure()
def drawBorder(self):
self.canv.rect(0, 0, self.width, self.figureHeight)
def _doBackground(self, color):
self.canv.saveState()
self.canv.setFillColor(self.background)
self.canv.rect(0, 0, self.width, self.figureHeight, fill=1)
self.canv.restoreState()
def drawBackground(self):
"""For use when using a figure on a differently coloured background.
Allows you to specify a colour to be used as a background for the figure."""
if isColor(self.background):
self._doBackground(self.background)
else:
try:
c = toColor(self.background)
self._doBackground(c)
except:
pass
def drawCaption(self):
self.captionPara.drawOn(self.canv, 0, 0)
def drawFigure(self):
pass
def drawPage(canvas,x, y, width, height):
#draws something which looks like a page
pth = canvas.beginPath()
corner = 0.05*width
# shaded backdrop offset a little
canvas.setFillColorRGB(0.5,0.5,0.5)
canvas.rect(x + corner, y - corner, width, height, stroke=0, fill=1)
#'sheet of paper' in light yellow
canvas.setFillColorRGB(1,1,0.9)
canvas.setLineWidth(0)
canvas.rect(x, y, width, height, stroke=1, fill=1)
#reset
canvas.setFillColorRGB(0,0,0)
canvas.setStrokeColorRGB(0,0,0)
class PageFigure(Figure):
"""Shows a blank page in a frame, and draws on that. Used in
illustrations of how PLATYPUS works."""
def __init__(self, background=None):
Figure.__init__(self, 3*inch, 3*inch)
self.caption = 'Figure 1 - a blank page'
self.captionStyle = captionStyle
self.background = background
def drawVirtualPage(self):
pass
def drawFigure(self):
drawPage(self.canv, 0.625*inch, 0.25*inch, 1.75*inch, 2.5*inch)
self.canv.translate(0.625*inch, 0.25*inch)
self.canv.scale(1.75/8.27, 2.5/11.69)
self.drawVirtualPage()
class PlatPropFigure1(PageFigure):
"""This shows a page with a frame on it"""
def __init__(self):
PageFigure.__init__(self)
self.caption = "Figure 1 - a page with a simple frame"
def drawVirtualPage(self):
demo1(self.canv)
class FlexFigure(Figure):
"""Base for a figure class with a caption. Can grow or shrink in proportion"""
def __init__(self, width, height, caption, background=None):
Figure.__init__(self, width, height, caption,
captionFont="Helvetica-Oblique", captionSize=8,
background=None)
self.shrinkToFit = 1 #if set and wrap is too tight, shrinks
self.growToFit = 1 #if set and wrap is too tight, shrinks
self.scaleFactor = None
self.captionStyle = ParagraphStyle(
'Caption',
fontName='Times', #'Helvetica-Oblique',
fontSize=4, #8,
spaceBefore=9, #3,
alignment=TA_CENTER
)
self._scaleFactor = None
self.background = background
def _scale(self,availWidth,availHeight):
"Rescale to fit according to the rules, but only once"
if self._scaleFactor is None or self.width>availWidth or self.height>availHeight:
w, h = Figure.wrap(self, availWidth, availHeight)
captionHeight = h - self.figureHeight
if self.scaleFactor is None:
#scale factor None means auto
self._scaleFactor = min(availWidth/self.width,(availHeight-captionHeight)/self.figureHeight)
else: #they provided a factor
self._scaleFactor = self.scaleFactor
if self._scaleFactor<1 and self.shrinkToFit:
self.width = self.width * self._scaleFactor - 0.0001
self.figureHeight = self.figureHeight * self._scaleFactor
elif self._scaleFactor>1 and self.growToFit:
self.width = self.width*self._scaleFactor - 0.0001
self.figureHeight = self.figureHeight * self._scaleFactor
def wrap(self, availWidth, availHeight):
self._scale(availWidth,availHeight)
return Figure.wrap(self, availWidth, availHeight)
def split(self, availWidth, availHeight):
self._scale(availWidth,availHeight)
return Figure.split(self, availWidth, availHeight)
class ImageFigure(FlexFigure):
"""Image with a caption below it"""
def __init__(self, filename, caption, background=None):
assert os.path.isfile(filename), 'image file %s not found' % filename
from reportlab.lib.utils import ImageReader
w, h = ImageReader(filename).getSize()
self.filename = filename
FlexFigure.__init__(self, w, h, caption, background)
def drawFigure(self):
self.canv.drawInlineImage(self.filename,
0, 0,self.width, self.figureHeight)
class DrawingFigure(FlexFigure):
"""Drawing with a caption below it. Clunky, scaling fails."""
def __init__(self, modulename, classname, caption, baseDir=None, background=None):
module = recursiveImport(modulename, baseDir)
klass = getattr(module, classname)
self.drawing = klass()
FlexFigure.__init__(self,
self.drawing.width,
self.drawing.height,
caption,
background)
self.growToFit = 1
def drawFigure(self):
self.canv.scale(self._scaleFactor, self._scaleFactor)
self.drawing.drawOn(self.canv, 0, 0)
try:
from rlextra.pageCatcher.pageCatcher import restoreForms, storeForms, storeFormsInMemory, restoreFormsInMemory
_hasPageCatcher = 1
except ImportError:
_hasPageCatcher = 0
if _hasPageCatcher:
####################################################################
#
# PageCatcher plugins
# These let you use our PageCatcher product to add figures
# to other documents easily.
####################################################################
class PageCatcherCachingMixIn:
"Helper functions to cache pages for figures"
def getFormName(self, pdfFileName, pageNo):
#naming scheme works within a directory only
dirname, filename = os.path.split(pdfFileName)
root, ext = os.path.splitext(filename)
return '%s_page%d' % (root, pageNo)
def needsProcessing(self, pdfFileName, pageNo):
"returns 1 if no forms or form is older"
formName = self.getFormName(pdfFileName, pageNo)
if os.path.exists(formName + '.frm'):
formModTime = os.stat(formName + '.frm')[8]
pdfModTime = os.stat(pdfFileName)[8]
return (pdfModTime > formModTime)
else:
return 1
def processPDF(self, pdfFileName, pageNo):
formName = self.getFormName(pdfFileName, pageNo)
storeForms(pdfFileName, formName + '.frm',
prefix= formName + '_',
pagenumbers=[pageNo])
#print 'stored %s.frm' % formName
return formName + '.frm'
class cachePageCatcherFigureNonA4(FlexFigure, PageCatcherCachingMixIn):
"""PageCatcher page with a caption below it. Size to be supplied."""
# This should merge with PageFigure into one class that reuses
# form information to determine the page orientation...
def __init__(self, filename, pageNo, caption, width, height, background=None):
self.dirname, self.filename = os.path.split(filename)
if self.dirname == '':
self.dirname = os.curdir
self.pageNo = pageNo
self.formName = self.getFormName(self.filename, self.pageNo) + '_' + str(pageNo)
FlexFigure.__init__(self, width, height, caption, background)
def drawFigure(self):
self.canv.saveState()
if not self.canv.hasForm(self.formName):
restorePath = self.dirname + os.sep + self.filename
#does the form file exist? if not, generate it.
formFileName = self.getFormName(restorePath, self.pageNo) + '.frm'
if self.needsProcessing(restorePath, self.pageNo):
#print 'preprocessing PDF %s page %s' % (restorePath, self.pageNo)
self.processPDF(restorePath, self.pageNo)
names = restoreForms(formFileName, self.canv)
self.canv.scale(self._scaleFactor, self._scaleFactor)
self.canv.doForm(self.formName)
self.canv.restoreState()
class cachePageCatcherFigure(cachePageCatcherFigureNonA4):
"""PageCatcher page with a caption below it. Presumes A4, Portrait.
This needs our commercial PageCatcher product, or you'll get a blank."""
def __init__(self, filename, pageNo, caption, width=595, height=842, background=None):
cachePageCatcherFigureNonA4.__init__(self, filename, pageNo, caption, width, height, background=background)
class PageCatcherFigureNonA4(FlexFigure):
"""PageCatcher page with a caption below it. Size to be supplied."""
# This should merge with PageFigure into one class that reuses
# form information to determine the page orientation...
_cache = {}
def __init__(self, filename, pageNo, caption, width, height, background=None, caching=None):
fn = self.filename = filename
self.pageNo = pageNo
fn = fn.replace(os.sep,'_').replace('/','_').replace('\\','_').replace('-','_').replace(':','_')
self.prefix = fn.replace('.','_')+'_'+str(pageNo)+'_'
self.formName = self.prefix + str(pageNo)
self.caching = caching
FlexFigure.__init__(self, width, height, caption, background)
def drawFigure(self):
if not self.canv.hasForm(self.formName):
if self.filename in self._cache:
f,data = self._cache[self.filename]
else:
f = open(self.filename,'rb')
pdf = f.read()
f.close()
f, data = storeFormsInMemory(pdf, pagenumbers=[self.pageNo], prefix=self.prefix)
if self.caching=='memory':
self._cache[self.filename] = f, data
f = restoreFormsInMemory(data, self.canv)
self.canv.saveState()
self.canv.scale(self._scaleFactor, self._scaleFactor)
self.canv.doForm(self.formName)
self.canv.restoreState()
class PageCatcherFigure(PageCatcherFigureNonA4):
"""PageCatcher page with a caption below it. Presumes A4, Portrait.
This needs our commercial PageCatcher product, or you'll get a blank."""
def __init__(self, filename, pageNo, caption, width=595, height=842, background=None, caching=None):
PageCatcherFigureNonA4.__init__(self, filename, pageNo, caption, width, height, background=background, caching=caching)
def demo1(canvas):
frame = Frame(
2*inch, # x
4*inch, # y at bottom
4*inch, # width
5*inch, # height
showBoundary = 1 # helps us see what's going on
)
bodyStyle = ParagraphStyle('Body', fontName='Times-Roman', fontSize=24, leading=28, spaceBefore=6)
para1 = Paragraph('Spam spam spam spam. ' * 5, bodyStyle)
para2 = Paragraph('Eggs eggs eggs. ' * 5, bodyStyle)
mydata = [para1, para2]
#this does the packing and drawing. The frame will consume
#items from the front of the list as it prints them
frame.addFromList(mydata,canvas)
def test1():
c = Canvas('figures.pdf')
f = Frame(inch, inch, 6*inch, 9*inch, showBoundary=1)
v = PlatPropFigure1()
v.captionTextColor = toColor('blue')
v.captionBackColor = toColor('lightyellow')
f.addFromList([v],c)
c.save()
if __name__ == '__main__':
test1()

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,206 @@
#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/platypus/frames.py
__version__=''' $Id: frames.py 2852 2006-05-08 15:04:15Z rgbecker $ '''
__doc__="""
"""
import logging
logger = logging.getLogger('reportlab.platypus')
_geomAttr=('x1', 'y1', 'width', 'height', 'leftPadding', 'bottomPadding', 'rightPadding', 'topPadding')
from reportlab import rl_config
_FUZZ=rl_config._FUZZ
class Frame:
'''
A Frame is a piece of space in a document that is filled by the
"flowables" in the story. For example in a book like document most
pages have the text paragraphs in one or two frames. For generality
a page might have several frames (for example for 3 column text or
for text that wraps around a graphic).
After creation a Frame is not usually manipulated directly by the
applications program -- it is used internally by the platypus modules.
Here is a diagramatid abstraction for the definitional part of a Frame
width x2,y2
+---------------------------------+
| l top padding r | h
| e +-------------------------+ i | e
| f | | g | i
| t | | h | g
| | | t | h
| p | | | t
| a | | p |
| d | | a |
| | | d |
| +-------------------------+ |
| bottom padding |
+---------------------------------+
(x1,y1) <-- lower left corner
NOTE!! Frames are stateful objects. No single frame should be used in
two documents at the same time (especially in the presence of multithreading.
'''
def __init__(self, x1, y1, width,height, leftPadding=6, bottomPadding=6,
rightPadding=6, topPadding=6, id=None, showBoundary=0,
overlapAttachedSpace=None,_debug=None):
self.id = id
self._debug = _debug
#these say where it goes on the page
self.__dict__['_x1'] = x1
self.__dict__['_y1'] = y1
self.__dict__['_width'] = width
self.__dict__['_height'] = height
#these create some padding.
self.__dict__['_leftPadding'] = leftPadding
self.__dict__['_bottomPadding'] = bottomPadding
self.__dict__['_rightPadding'] = rightPadding
self.__dict__['_topPadding'] = topPadding
# if we want a boundary to be shown
self.showBoundary = showBoundary
if overlapAttachedSpace is None: overlapAttachedSpace = rl_config.overlapAttachedSpace
self._oASpace = overlapAttachedSpace
self._geom()
self._reset()
def __getattr__(self,a):
if a in _geomAttr: return self.__dict__['_'+a]
raise AttributeError, a
def __setattr__(self,a,v):
if a in _geomAttr:
self.__dict__['_'+a] = v
self._geom()
else:
self.__dict__[a] = v
def _geom(self):
self._x2 = self._x1 + self._width
self._y2 = self._y1 + self._height
#efficiency
self._y1p = self._y1 + self._bottomPadding
#work out the available space
self._aW = self._x2 - self._x1 - self._leftPadding - self._rightPadding
self._aH = self._y2 - self._y1p - self._topPadding
def _reset(self):
#drawing starts at top left
self._x = self._x1 + self._leftPadding
self._y = self._y2 - self._topPadding
self._atTop = 1
self._prevASpace = 0
# these two should NOT be set on a frame.
# they are used when Indenter flowables want
# to adjust edges e.g. to do nested lists
self._leftExtraIndent = 0.0
self._rightExtraIndent = 0.0
def _getAvailableWidth(self):
return self._aW - self._leftExtraIndent - self._rightExtraIndent
def _add(self, flowable, canv, trySplit=0):
""" Draws the flowable at the current position.
Returns 1 if successful, 0 if it would not fit.
Raises a LayoutError if the object is too wide,
or if it is too high for a totally empty frame,
to avoid infinite loops"""
if getattr(flowable,'frameAction',None):
flowable.frameAction(self)
return 1
y = self._y
p = self._y1p
s = 0
aW = self._getAvailableWidth()
if not self._atTop:
s =flowable.getSpaceBefore()
if self._oASpace:
s = max(s-self._prevASpace,0)
h = y - p - s
if h>0:
flowable._frame = self
flowable.canv = canv #so they can use stringWidth etc
w, h = flowable.wrap(aW, h)
del flowable.canv, flowable._frame
else:
return 0
h += s
y -= h
if y < p-_FUZZ:
if not rl_config.allowTableBoundsErrors and ((h>self._aH or w>aW) and not trySplit):
raise "LayoutError", "Flowable %s (%sx%s points) too large for frame (%sx%s points)." % (
flowable.__class__, w,h, aW,self._aH)
return 0
else:
#now we can draw it, and update the current point.
flowable._frame = self
flowable.drawOn(canv, self._x + self._leftExtraIndent, y, _sW=aW-w)
if self._debug: logger.debug('drew %s' % flowable.identity())
del flowable._frame
s = flowable.getSpaceAfter()
y -= s
if self._oASpace: self._prevASpace = s
if y!=self._y: self._atTop = 0
self._y = y
return 1
add = _add
def split(self,flowable,canv):
'''Ask the flowable to split using up the available space.'''
y = self._y
p = self._y1p
s = 0
if not self._atTop: s = flowable.getSpaceBefore()
flowable.canv = canv #some flowables might need this
r = flowable.split(self._aW, y-p-s)
del flowable.canv
return r
def drawBoundary(self,canv):
"draw the frame boundary as a rectangle (primarily for debugging)."
from reportlab.lib.colors import Color, CMYKColor, toColor
sb = self.showBoundary
isColor = type(sb) in (type(''),type(()),type([])) or isinstance(sb,Color)
if isColor:
sb = toColor(sb,self)
if sb is self: isColor = 0
else:
canv.saveState()
canv.setStrokeColor(sb)
canv.rect(
self._x1,
self._y1,
self._x2 - self._x1,
self._y2 - self._y1
)
if isColor: canv.restoreState()
def addFromList(self, drawlist, canv):
"""Consumes objects from the front of the list until the
frame is full. If it cannot fit one object, raises
an exception."""
if self._debug: logger.debug("enter Frame.addFromlist() for frame %s" % self.id)
if self.showBoundary:
self.drawBoundary(canv)
while len(drawlist) > 0:
head = drawlist[0]
if self.add(head,canv,trySplit=0):
del drawlist[0]
else:
#leave it in the list for later
break

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,898 @@
#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/platypus/paraparser.py
__version__=''' $Id: paraparser.py 2853 2006-05-10 12:56:39Z rgbecker $ '''
import string
import re
from types import TupleType, UnicodeType, StringType
import sys
import os
import copy
import reportlab.lib.sequencer
from reportlab.lib.abag import ABag
from reportlab.lib import xmllib
from reportlab.lib.colors import toColor, white, black, red, Color
from reportlab.lib.fonts import tt2ps, ps2tt
from reportlab.lib.enums import TA_LEFT, TA_RIGHT, TA_CENTER, TA_JUSTIFY
from reportlab.lib.units import inch,mm,cm,pica
_re_para = re.compile(r'^\s*<\s*para(?:\s+|>|/>)')
sizeDelta = 2 # amount to reduce font size by for super and sub script
subFraction = 0.5 # fraction of font size that a sub script should be lowered
superFraction = 0.5 # fraction of font size that a super script should be raised
def _num(s, unit=1):
"""Convert a string like '10cm' to an int or float (in points).
The default unit is point, but optionally you can use other
default units like mm.
"""
if s[-2:]=='cm':
unit=cm
s = s[:-2]
if s[-2:]=='in':
unit=inch
s = s[:-2]
if s[-2:]=='pt':
unit=1
s = s[:-2]
if s[-1:]=='i':
unit=inch
s = s[:-1]
if s[-2:]=='mm':
unit=mm
s = s[:-2]
if s[-4:]=='pica':
unit=pica
s = s[:-4]
if s[0] in ['+','-']:
try:
return ('relative',int(s)*unit)
except ValueError:
return ('relative',float(s)*unit)
else:
try:
return int(s)*unit
except ValueError:
return float(s)*unit
def _align(s):
s = string.lower(s)
if s=='left': return TA_LEFT
elif s=='right': return TA_RIGHT
elif s=='justify': return TA_JUSTIFY
elif s in ('centre','center'): return TA_CENTER
else: raise ValueError
_paraAttrMap = {'font': ('fontName', None),
'face': ('fontName', None),
'fontsize': ('fontSize', _num),
'size': ('fontSize', _num),
'leading': ('leading', _num),
'lindent': ('leftIndent', _num),
'rindent': ('rightIndent', _num),
'findent': ('firstLineIndent', _num),
'align': ('alignment', _align),
'spaceb': ('spaceBefore', _num),
'spacea': ('spaceAfter', _num),
'bfont': ('bulletFontName', None),
'bfontsize': ('bulletFontSize',_num),
'bindent': ('bulletIndent',_num),
'bcolor': ('bulletColor',toColor),
'color':('textColor',toColor),
'backcolor':('backColor',toColor),
'bgcolor':('backColor',toColor),
'bg':('backColor',toColor),
'fg': ('textColor',toColor),
}
_bulletAttrMap = {
'font': ('bulletFontName', None),
'face': ('bulletFontName', None),
'size': ('bulletFontSize',_num),
'fontsize': ('bulletFontSize',_num),
'indent': ('bulletIndent',_num),
'color': ('bulletColor',toColor),
'fg': ('bulletColor',toColor),
}
#things which are valid font attributes
_fontAttrMap = {'size': ('fontSize', _num),
'face': ('fontName', None),
'name': ('fontName', None),
'fg': ('textColor', toColor),
'color':('textColor', toColor),
'backcolor':('backColor',toColor),
'bgcolor':('backColor',toColor),
}
#things which are valid font attributes
_linkAttrMap = {'size': ('fontSize', _num),
'face': ('fontName', None),
'name': ('fontName', None),
'fg': ('textColor', toColor),
'color':('textColor', toColor),
'backcolor':('backColor',toColor),
'bgcolor':('backColor',toColor),
'dest': ('link', None),
'destination': ('link', None),
'target': ('link', None),
'href': ('link', None),
}
def _addAttributeNames(m):
K = m.keys()
for k in K:
n = m[k][0]
if not m.has_key(n): m[n] = m[k]
n = string.lower(n)
if not m.has_key(n): m[n] = m[k]
_addAttributeNames(_paraAttrMap)
_addAttributeNames(_fontAttrMap)
_addAttributeNames(_bulletAttrMap)
def _applyAttributes(obj, attr):
for k, v in attr.items():
if type(v) is TupleType and v[0]=='relative':
#AR 20/5/2000 - remove 1.5.2-ism
#v = v[1]+getattr(obj,k,0)
if hasattr(obj, k):
v = v[1]+getattr(obj,k)
else:
v = v[1]
setattr(obj,k,v)
#Named character entities intended to be supported from the special font
#with additions suggested by Christoph Zwerschke who also suggested the
#numeric entity names that follow.
greeks = {
'pound': '\xc2\xa3',
'nbsp': '\xc2\xa0',
'alefsym': '\xe2\x84\xb5',
'Alpha': '\xce\x91',
'alpha': '\xce\xb1',
'and': '\xe2\x88\xa7',
'ang': '\xe2\x88\xa0',
'asymp': '\xe2\x89\x88',
'Beta': '\xce\x92',
'beta': '\xce\xb2',
'bull': '\xe2\x80\xa2',
'cap': '\xe2\x88\xa9',
'Chi': '\xce\xa7',
'chi': '\xcf\x87',
'clubs': '\xe2\x99\xa3',
'cong': '\xe2\x89\x85',
'cup': '\xe2\x88\xaa',
'darr': '\xe2\x86\x93',
'dArr': '\xe2\x87\x93',
'delta': '\xce\xb4',
'Delta': '\xe2\x88\x86',
'diams': '\xe2\x99\xa6',
'empty': '\xe2\x88\x85',
'Epsilon': '\xce\x95',
'epsilon': '\xce\xb5',
'epsiv': '\xce\xb5',
'equiv': '\xe2\x89\xa1',
'Eta': '\xce\x97',
'eta': '\xce\xb7',
'euro': '\xe2\x82\xac',
'exist': '\xe2\x88\x83',
'forall': '\xe2\x88\x80',
'frasl': '\xe2\x81\x84',
'Gamma': '\xce\x93',
'gamma': '\xce\xb3',
'ge': '\xe2\x89\xa5',
'harr': '\xe2\x86\x94',
'hArr': '\xe2\x87\x94',
'hearts': '\xe2\x99\xa5',
'hellip': '\xe2\x80\xa6',
'image': '\xe2\x84\x91',
'infin': '\xe2\x88\x9e',
'int': '\xe2\x88\xab',
'Iota': '\xce\x99',
'iota': '\xce\xb9',
'isin': '\xe2\x88\x88',
'Kappa': '\xce\x9a',
'kappa': '\xce\xba',
'Lambda': '\xce\x9b',
'lambda': '\xce\xbb',
'lang': '\xe2\x8c\xa9',
'larr': '\xe2\x86\x90',
'lArr': '\xe2\x87\x90',
'lceil': '\xef\xa3\xae',
'le': '\xe2\x89\xa4',
'lfloor': '\xef\xa3\xb0',
'lowast': '\xe2\x88\x97',
'loz': '\xe2\x97\x8a',
'minus': '\xe2\x88\x92',
'mu': '\xc2\xb5',
'Mu': '\xce\x9c',
'nabla': '\xe2\x88\x87',
'ne': '\xe2\x89\xa0',
'ni': '\xe2\x88\x8b',
'notin': '\xe2\x88\x89',
'nsub': '\xe2\x8a\x84',
'Nu': '\xce\x9d',
'nu': '\xce\xbd',
'oline': '\xef\xa3\xa5',
'omega': '\xcf\x89',
'Omega': '\xe2\x84\xa6',
'Omicron': '\xce\x9f',
'omicron': '\xce\xbf',
'oplus': '\xe2\x8a\x95',
'or': '\xe2\x88\xa8',
'otimes': '\xe2\x8a\x97',
'part': '\xe2\x88\x82',
'perp': '\xe2\x8a\xa5',
'Phi': '\xce\xa6',
'phi': '\xcf\x95',
'phis': '\xcf\x86',
'Pi': '\xce\xa0',
'pi': '\xcf\x80',
'piv': '\xcf\x96',
'prime': '\xe2\x80\xb2',
'prod': '\xe2\x88\x8f',
'prop': '\xe2\x88\x9d',
'Psi': '\xce\xa8',
'psi': '\xcf\x88',
'radic': '\xe2\x88\x9a',
'rang': '\xe2\x8c\xaa',
'rarr': '\xe2\x86\x92',
'rArr': '\xe2\x87\x92',
'rceil': '\xef\xa3\xb9',
'real': '\xe2\x84\x9c',
'rfloor': '\xef\xa3\xbb',
'Rho': '\xce\xa1',
'rho': '\xcf\x81',
'sdot': '\xe2\x8b\x85',
'Sigma': '\xce\xa3',
'sigma': '\xcf\x83',
'sigmaf': '\xcf\x82',
'sigmav': '\xcf\x82',
'sim': '\xe2\x88\xbc',
'spades': '\xe2\x99\xa0',
'sub': '\xe2\x8a\x82',
'sube': '\xe2\x8a\x86',
'sum': '\xe2\x88\x91',
'sup': '\xe2\x8a\x83',
'supe': '\xe2\x8a\x87',
'Tau': '\xce\xa4',
'tau': '\xcf\x84',
'there4': '\xe2\x88\xb4',
'Theta': '\xce\x98',
'theta': '\xce\xb8',
'thetasym': '\xcf\x91',
'thetav': '\xcf\x91',
'trade': '\xef\xa3\xaa',
'uarr': '\xe2\x86\x91',
'uArr': '\xe2\x87\x91',
'upsih': '\xcf\x92',
'Upsilon': '\xce\xa5',
'upsilon': '\xcf\x85',
'weierp': '\xe2\x84\x98',
'Xi': '\xce\x9e',
'xi': '\xce\xbe',
'Zeta': '\xce\x96',
'zeta': '\xce\xb6',
}
#------------------------------------------------------------------------
class ParaFrag(ABag):
"""class ParaFrag contains the intermediate representation of string
segments as they are being parsed by the XMLParser.
fontname, fontSize, rise, textColor, cbDefn
"""
_greek2Utf8=None
def _greekConvert(data):
global _greek2Utf8
if not _greek2Utf8:
from reportlab.pdfbase.rl_codecs import RL_Codecs
import codecs
dm = decoding_map = codecs.make_identity_dict(xrange(32,256))
for k in xrange(0,32):
dm[k] = None
dm.update(RL_Codecs._RL_Codecs__rl_codecs_data['symbol'][0])
_greek2Utf8 = {}
for k,v in dm.iteritems():
if not v:
u = '\0'
else:
u = unichr(v).encode('utf8')
_greek2Utf8[chr(k)] = u
return ''.join(map(_greek2Utf8.__getitem__,data))
#------------------------------------------------------------------
# !!! NOTE !!! THIS TEXT IS NOW REPLICATED IN PARAGRAPH.PY !!!
# The ParaFormatter will be able to format the following
# tags:
# < /b > - bold
# < /i > - italics
# < u > < /u > - underline
# < super > < /super > - superscript
# < sup > < /sup > - superscript
# < sub > < /sub > - subscript
# <font name=fontfamily/fontname color=colorname size=float>
# < bullet > </bullet> - bullet text (at head of para only)
# <onDraw name=callable label="a label">
# <unichar name="unicode character name"/>
# <unichar value="unicode code point"/>
# <greek> - </greek>
#
# The whole may be surrounded by <para> </para> tags
#
# It will also be able to handle any MathML specified Greek characters.
#------------------------------------------------------------------
class ParaParser(xmllib.XMLParser):
#----------------------------------------------------------
# First we will define all of the xml tag handler functions.
#
# start_<tag>(attributes)
# end_<tag>()
#
# While parsing the xml ParaFormatter will call these
# functions to handle the string formatting tags.
# At the start of each tag the corresponding field will
# be set to 1 and at the end tag the corresponding field will
# be set to 0. Then when handle_data is called the options
# for that data will be aparent by the current settings.
#----------------------------------------------------------
def __getattr__( self, attrName ):
"""This way we can handle <TAG> the same way as <tag> (ignoring case)."""
if attrName!=attrName.lower() and attrName!="caseSensitive" and not self.caseSensitive and \
(attrName.startswith("start_") or attrName.startswith("end_")):
return getattr(self,attrName.lower())
raise AttributeError, attrName
#### bold
def start_b( self, attributes ):
self._push(bold=1)
def end_b( self ):
self._pop(bold=1)
def start_strong( self, attributes ):
self._push(bold=1)
def end_strong( self ):
self._pop(bold=1)
#### italics
def start_i( self, attributes ):
self._push(italic=1)
def end_i( self ):
self._pop(italic=1)
def start_em( self, attributes ):
self._push(italic=1)
def end_em( self ):
self._pop(italic=1)
#### underline
def start_u( self, attributes ):
self._push(underline=1)
def end_u( self ):
self._pop(underline=1)
#### link
def start_link(self, attributes):
self._push(**self.getAttributes(attributes,_linkAttrMap))
def end_link(self):
frag = self._stack[-1]
del self._stack[-1]
assert frag.link!=None
#### super script
def start_super( self, attributes ):
self._push(super=1)
def end_super( self ):
self._pop(super=1)
start_sup = start_super
end_sup = end_super
#### sub script
def start_sub( self, attributes ):
self._push(sub=1)
def end_sub( self ):
self._pop(sub=1)
#### greek script
#### add symbol encoding
def handle_charref(self, name):
try:
if name[0]=='x':
n = int(name[1:],16)
else:
n = int(name)
except ValueError:
self.unknown_charref(name)
return
self.handle_data(unichr(n).encode('utf8'))
def handle_entityref(self,name):
if greeks.has_key(name):
self.handle_data(greeks[name])
else:
xmllib.XMLParser.handle_entityref(self,name)
def syntax_error(self,lineno,message):
self._syntax_error(message)
def _syntax_error(self,message):
if message[:10]=="attribute " and message[-17:]==" value not quoted": return
self.errors.append(message)
def start_greek(self, attr):
self._push(greek=1)
def end_greek(self):
self._pop(greek=1)
def start_unichar(self, attr):
if attr.has_key('name'):
if attr.has_key('code'):
self._syntax_error('<unichar/> invalid with both name and code attributes')
try:
v = unicodedata.lookup(attr['name']).encode('utf8')
except KeyError:
self._syntax_error('<unichar/> invalid name attribute\n"%s"' % name)
v = '\0'
elif attr.has_key('code'):
try:
v = unichr(int(eval(attr['code']))).encode('utf8')
except:
self._syntax_error('<unichar/> invalid code attribute %s' % attr['code'])
v = '\0'
else:
v = None
if attr:
self._syntax_error('<unichar/> invalid attribute %s' % attr.keys()[0])
if v is not None:
self.handle_data(v)
self._push(_selfClosingTag='unichar')
def end_unichar(self):
self._pop()
def start_font(self,attr):
self._push(**self.getAttributes(attr,_fontAttrMap))
def end_font(self):
self._pop()
def _initial_frag(self,attr,attrMap,bullet=0):
style = self._style
if attr!={}:
style = copy.deepcopy(style)
_applyAttributes(style,self.getAttributes(attr,attrMap))
self._style = style
# initialize semantic values
frag = ParaFrag()
frag.sub = 0
frag.super = 0
frag.rise = 0
frag.underline = 0
frag.greek = 0
frag.link = None
if bullet:
frag.fontName, frag.bold, frag.italic = ps2tt(style.bulletFontName)
frag.fontSize = style.bulletFontSize
frag.textColor = hasattr(style,'bulletColor') and style.bulletColor or style.textColor
else:
frag.fontName, frag.bold, frag.italic = ps2tt(style.fontName)
frag.fontSize = style.fontSize
frag.textColor = style.textColor
return frag
def start_para(self,attr):
self._stack = [self._initial_frag(attr,_paraAttrMap)]
def end_para(self):
self._pop()
def start_bullet(self,attr):
if hasattr(self,'bFragList'):
self._syntax_error('only one <bullet> tag allowed')
self.bFragList = []
frag = self._initial_frag(attr,_bulletAttrMap,1)
frag.isBullet = 1
self._stack.append(frag)
def end_bullet(self):
self._pop()
#---------------------------------------------------------------
def start_seqdefault(self, attr):
try:
default = attr['id']
except KeyError:
default = None
self._seq.setDefaultCounter(default)
def end_seqdefault(self):
pass
def start_seqreset(self, attr):
try:
id = attr['id']
except KeyError:
id = None
try:
base = int(attr['base'])
except:
base=0
self._seq.reset(id, base)
def end_seqreset(self):
pass
def start_seqchain(self, attr):
try:
order = attr['order']
except KeyError:
order = ''
order = order.split()
seq = self._seq
for p,c in zip(order[:-1],order[1:]):
seq.chain(p, c)
end_seqchain = end_seqreset
def start_seqformat(self, attr):
try:
id = attr['id']
except KeyError:
id = None
try:
value = attr['value']
except KeyError:
value = '1'
self._seq.setFormat(id,value)
end_seqformat = end_seqreset
# AR hacking in aliases to allow the proper casing for RML.
# the above ones should be deprecated over time. 2001-03-22
start_seqDefault = start_seqdefault
end_seqDefault = end_seqdefault
start_seqReset = start_seqreset
end_seqReset = end_seqreset
start_seqChain = start_seqchain
end_seqChain = end_seqchain
start_seqFormat = start_seqformat
end_seqFormat = end_seqformat
def start_seq(self, attr):
#if it has a template, use that; otherwise try for id;
#otherwise take default sequence
if attr.has_key('template'):
templ = attr['template']
self.handle_data(templ % self._seq)
return
elif attr.has_key('id'):
id = attr['id']
else:
id = None
output = self._seq.nextf(id)
self.handle_data(output)
def end_seq(self):
pass
def start_onDraw(self,attr):
defn = ABag()
if attr.has_key('name'): defn.name = attr['name']
else: self._syntax_error('<onDraw> needs at least a name attribute')
if attr.has_key('label'): defn.label = attr['label']
defn.kind='onDraw'
self._push(cbDefn=defn)
self.handle_data('')
self._pop()
#---------------------------------------------------------------
def _push(self,**attr):
frag = copy.copy(self._stack[-1])
_applyAttributes(frag,attr)
self._stack.append(frag)
def _pop(self,**kw):
frag = self._stack[-1]
del self._stack[-1]
for k, v in kw.items():
assert getattr(frag,k)==v
return frag
def getAttributes(self,attr,attrMap):
A = {}
for k, v in attr.items():
if not self.caseSensitive:
k = string.lower(k)
if k in attrMap.keys():
j = attrMap[k]
func = j[1]
try:
A[j[0]] = (func is None) and v or func(v)
except:
self._syntax_error('%s: invalid value %s'%(k,v))
else:
self._syntax_error('invalid attribute name %s'%k)
return A
#----------------------------------------------------------------
def __init__(self,verbose=0):
self.caseSensitive = 0
xmllib.XMLParser.__init__(self,verbose=verbose)
def _iReset(self):
self.fragList = []
if hasattr(self, 'bFragList'): delattr(self,'bFragList')
def _reset(self, style):
'''reset the parser'''
xmllib.XMLParser.reset(self)
# initialize list of string segments to empty
self.errors = []
self._style = style
self._iReset()
#----------------------------------------------------------------
def handle_data(self,data):
"Creates an intermediate representation of string segments."
frag = copy.copy(self._stack[-1])
if hasattr(frag,'cbDefn'):
if data!='': syntax_error('Only <onDraw> tag allowed')
elif hasattr(frag,'_selfClosingTag'):
if data!='': syntax_error('No content allowed in %s tag' % frag._selfClosingTag)
return
else:
# if sub and super are both on they will cancel each other out
if frag.sub == 1 and frag.super == 1:
frag.sub = 0
frag.super = 0
if frag.sub:
frag.rise = -frag.fontSize*subFraction
frag.fontSize = max(frag.fontSize-sizeDelta,3)
elif frag.super:
frag.rise = frag.fontSize*superFraction
frag.fontSize = max(frag.fontSize-sizeDelta,3)
if frag.greek:
frag.fontName = 'symbol'
data = _greekConvert(data)
# bold, italic, and underline
x = frag.fontName = tt2ps(frag.fontName,frag.bold,frag.italic)
#save our data
frag.text = data
if hasattr(frag,'isBullet'):
delattr(frag,'isBullet')
self.bFragList.append(frag)
else:
self.fragList.append(frag)
def handle_cdata(self,data):
self.handle_data(data)
def _setup_for_parse(self,style):
self._seq = reportlab.lib.sequencer.getSequencer()
self._reset(style) # reinitialise the parser
def parse(self, text, style):
"""Given a formatted string will return a list of
ParaFrag objects with their calculated widths.
If errors occur None will be returned and the
self.errors holds a list of the error messages.
"""
# AR 20040612 - when we feed Unicode strings in, sgmlop
# tries to coerce to ASCII. Must intercept, coerce to
# any 8-bit encoding which defines most of 256 points,
# and revert at end. Yuk. Preliminary step prior to
# removal of parser altogether.
enc = self._enc = 'cp1252' #our legacy default
self._UNI = type(text) is UnicodeType
if self._UNI:
text = text.encode(enc)
self._setup_for_parse(style)
# the xmlparser requires that all text be surrounded by xml
# tags, therefore we must throw some unused flags around the
# given string
if not(len(text)>=6 and text[0]=='<' and _re_para.match(text)):
text = "<para>"+text+"</para>"
self.feed(text)
self.close() # force parsing to complete
return self._complete_parse()
def _complete_parse(self):
del self._seq
style = self._style
del self._style
if len(self.errors)==0:
fragList = self.fragList
bFragList = hasattr(self,'bFragList') and self.bFragList or None
self._iReset()
else:
fragList = bFragList = None
if self._UNI:
#reconvert to unicode
if fragList:
for frag in fragList:
frag.text = unicode(frag.text, self._enc)
if bFragList:
for frag in bFragList:
frag.text = unicode(frag.text, self._enc)
return style, fragList, bFragList
def _tt_parse(self,tt):
tag = tt[0]
try:
start = getattr(self,'start_'+tag)
end = getattr(self,'end_'+tag)
except AttributeError:
raise ValueError('Invalid tag "%s"' % tag)
start(tt[1] or {})
C = tt[2]
if C:
M = self._tt_handlers
for c in C:
M[type(c) is TupleType](c)
end()
def tt_parse(self,tt,style):
'''parse from tupletree form'''
self._setup_for_parse(style)
self._tt_handlers = self.handle_data,self._tt_parse
self._tt_parse(tt)
return self._complete_parse()
if __name__=='__main__':
from reportlab.platypus import cleanBlockQuotedText
_parser=ParaParser()
def check_text(text,p=_parser):
print '##########'
text = cleanBlockQuotedText(text)
l,rv,bv = p.parse(text,style)
if rv is None:
for l in _parser.errors:
print l
else:
print 'ParaStyle', l.fontName,l.fontSize,l.textColor
for l in rv:
print l.fontName,l.fontSize,l.textColor,l.bold, l.rise, '|%s|'%l.text[:25],
if hasattr(l,'cbDefn'):
print 'cbDefn',l.cbDefn.name,l.cbDefn.label,l.cbDefn.kind
else: print
style=ParaFrag()
style.fontName='Times-Roman'
style.fontSize = 12
style.textColor = black
style.bulletFontName = black
style.bulletFontName='Times-Roman'
style.bulletFontSize=12
text='''
<b><i><greek>a</greek>D</i></b>&beta;<unichr value="0x394"/>
<font name="helvetica" size="15" color=green>
Tell me, O muse, of that ingenious hero who travelled far and wide
after</font> he had sacked the famous town of Troy. 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
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<super>1</super> may know them.
'''
check_text(text)
check_text('<para> </para>')
check_text('<para font="times-bold" size=24 leading=28.8 spaceAfter=72>ReportLab -- Reporting for the Internet Age</para>')
check_text('''
<font color=red>&tau;</font>Tell me, O muse, of that ingenious hero who travelled far and wide
after he had sacked the famous town of Troy. 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
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.''')
check_text('''
Telemachus took this speech as of good omen and rose at once, for
he was bursting with what he had to say. He stood in the middle of
the assembly and the good herald Pisenor brought him his staff. Then,
turning to Aegyptius, "Sir," said he, "it is I, as you will shortly
learn, who have convened you, for it is I who am the most aggrieved.
I have not got wind of any host approaching about which I would warn
you, nor is there any matter of public moment on which I would speak.
My grieveance is purely personal, and turns on two great misfortunes
which have fallen upon my house. The first of these is the loss of
my excellent father, who was chief among all you here present, and
was like a father to every one of you; the second is much more serious,
and ere long will be the utter ruin of my estate. The sons of all
the chief men among you are pestering my mother to marry them against
her will. They are afraid to go to her father Icarius, asking him
to choose the one he likes best, and to provide marriage gifts for
his daughter, but day by day they keep hanging about my father's house,
sacrificing our oxen, sheep, and fat goats for their banquets, and
never giving so much as a thought to the quantity of wine they drink.
No estate can stand such recklessness; we have now no Ulysses to ward
off harm from our doors, and I cannot hold my own against them. I
shall never all my days be as good a man as he was, still I would
indeed defend myself if I had power to do so, for I cannot stand such
treatment any longer; my house is being disgraced and ruined. Have
respect, therefore, to your own consciences and to public opinion.
Fear, too, the wrath of heaven, lest the gods should be displeased
and turn upon you. I pray you by Jove and Themis, who is the beginning
and the end of councils, [do not] hold back, my friends, and leave
me singlehanded- unless it be that my brave father Ulysses did some
wrong to the Achaeans which you would now avenge on me, by aiding
and abetting these suitors. Moreover, if I am to be eaten out of house
and home at all, I had rather you did the eating yourselves, for I
could then take action against you to some purpose, and serve you
with notices from house to house till I got paid in full, whereas
now I have no remedy."''')
check_text('''
But as the sun was rising from the fair sea into the firmament of
heaven to shed light on mortals and immortals, they reached Pylos
the city of Neleus. Now the people of Pylos were gathered on the sea
shore to offer sacrifice of black bulls to Neptune lord of the Earthquake.
There were nine guilds with five hundred men in each, and there were
nine bulls to each guild. As they were eating the inward meats and
burning the thigh bones [on the embers] in the name of Neptune, Telemachus
and his crew arrived, furled their sails, brought their ship to anchor,
and went ashore. ''')
check_text('''
So the neighbours and kinsmen of Menelaus were feasting and making
merry in his house. There was a bard also to sing to them and play
his lyre, while two tumblers went about performing in the midst of
them when the man struck up with his tune.]''')
check_text('''
"When we had passed the [Wandering] rocks, with Scylla and terrible
Charybdis, we reached the noble island of the sun-god, where were
the goodly cattle and sheep belonging to the sun Hyperion. While still
at sea in my ship I could bear the cattle lowing as they came home
to the yards, and the sheep bleating. Then I remembered what the blind
Theban prophet Teiresias had told me, and how carefully Aeaean Circe
had warned me to shun the island of the blessed sun-god. So being
much troubled I said to the men, 'My men, I know you are hard pressed,
but listen while I tell you the prophecy that Teiresias made me, and
how carefully Aeaean Circe warned me to shun the island of the blessed
sun-god, for it was here, she said, that our worst danger would lie.
Head the ship, therefore, away from the island.''')
check_text('''A&lt;B&gt;C&amp;D&quot;E&apos;F''')
check_text('''A&lt; B&gt; C&amp; D&quot; E&apos; F''')
check_text('''<![CDATA[<>&'"]]>''')
check_text('''<bullet face=courier size=14 color=green>+</bullet>
There was a bard also to sing to them and play
his lyre, while two tumblers went about performing in the midst of
them when the man struck up with his tune.]''')
check_text('''<onDraw name="myFunc" label="aaa bbb">A paragraph''')
check_text('''<para><onDraw name="myFunc" label="aaa bbb">B paragraph</para>''')
# HVB, 30.05.2003: Test for new features
_parser.caseSensitive=0
check_text('''Here comes <FONT FACE="Helvetica" SIZE="14pt">Helvetica 14</FONT> with <STRONG>strong</STRONG> <EM>emphasis</EM>.''')
check_text('''Here comes <font face="Helvetica" size="14pt">Helvetica 14</font> with <Strong>strong</Strong> <em>emphasis</em>.''')
check_text('''Here comes <font face="Courier" size="3cm">Courier 3cm</font> and normal again.''')

View File

@ -0,0 +1,329 @@
#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/platypus/tableofcontents.py
"""
This module defines a single TableOfContents() class that can be used to
create automatically a table of tontents for Platypus documents like
this:
story = []
toc = TableOfContents()
story.append(toc)
# some heading paragraphs here...
doc = MyTemplate(path)
doc.multiBuild(story)
The data needed to create the table is a list of (level, text, pageNum)
triplets, plus some paragraph styles for each level of the table itself.
The triplets will usually be created in a document template's method
like afterFlowable(), making notification calls using the notify()
method with appropriate data like this:
(level, text, pageNum) = ...
self.notify('TOCEntry', (level, text, pageNum))
As the table of contents need at least two passes over the Platypus
story which is why the moultiBuild0() method must be called.
The level<NUMBER>ParaStyle variables are the paragraph styles used
to format the entries in the table of contents. Their indentation
is calculated like this: each entry starts at a multiple of some
constant named delta. If one entry spans more than one line, all
lines after the first are indented by the same constant named
epsilon.
"""
__version__=''' $Id: tableofcontents.py 2385 2004-06-17 15:26:05Z rgbecker $ '''
import string
from reportlab.lib import enums
from reportlab.lib.units import cm
from reportlab.lib.styles import ParagraphStyle
from reportlab.platypus.paragraph import Paragraph
from reportlab.platypus.doctemplate import IndexingFlowable
from reportlab.platypus.tables import TableStyle, Table
# Default paragraph styles for tables of contents.
# (This could also be generated automatically or even
# on-demand if it is not known how many levels the
# TOC will finally need to display...)
delta = 1*cm
epsilon = 0.5*cm
levelZeroParaStyle = \
ParagraphStyle(name='LevelZero',
fontName='Times-Roman',
fontSize=10,
leading=11,
firstLineIndent = -epsilon,
leftIndent = 0*delta + epsilon)
levelOneParaStyle = \
ParagraphStyle(name='LevelOne',
parent = levelZeroParaStyle,
leading=11,
firstLineIndent = -epsilon,
leftIndent = 1*delta + epsilon)
levelTwoParaStyle = \
ParagraphStyle(name='LevelTwo',
parent = levelOneParaStyle,
leading=11,
firstLineIndent = -epsilon,
leftIndent = 2*delta + epsilon)
levelThreeParaStyle = \
ParagraphStyle(name='LevelThree',
parent = levelTwoParaStyle,
leading=11,
firstLineIndent = -epsilon,
leftIndent = 3*delta + epsilon)
levelFourParaStyle = \
ParagraphStyle(name='LevelFour',
parent = levelTwoParaStyle,
leading=11,
firstLineIndent = -epsilon,
leftIndent = 4*delta + epsilon)
defaultTableStyle = \
TableStyle([('VALIGN', (0,0), (-1,-1), 'TOP')])
class TableOfContents(IndexingFlowable):
"""This creates a formatted table of contents.
It presumes a correct block of data is passed in.
The data block contains a list of (level, text, pageNumber)
triplets. You can supply a paragraph style for each level
(starting at zero).
"""
def __init__(self):
self.entries = []
self.rightColumnWidth = 72
self.levelStyles = [levelZeroParaStyle,
levelOneParaStyle,
levelTwoParaStyle,
levelThreeParaStyle,
levelFourParaStyle]
self.tableStyle = defaultTableStyle
self._table = None
self._entries = []
self._lastEntries = []
def beforeBuild(self):
# keep track of the last run
self._lastEntries = self._entries[:]
self.clearEntries()
def isIndexing(self):
return 1
def isSatisfied(self):
return (self._entries == self._lastEntries)
def notify(self, kind, stuff):
"""The notification hook called to register all kinds of events.
Here we are interested in 'TOCEntry' events only.
"""
if kind == 'TOCEntry':
(level, text, pageNum) = stuff
self.addEntry(level, text, pageNum)
def clearEntries(self):
self._entries = []
def addEntry(self, level, text, pageNum):
"""Adds one entry to the table of contents.
This allows incremental buildup by a doctemplate.
Requires that enough styles are defined."""
assert type(level) == type(1), "Level must be an integer"
assert level < len(self.levelStyles), \
"Table of contents must have a style defined " \
"for paragraph level %d before you add an entry" % level
self._entries.append((level, text, pageNum))
def addEntries(self, listOfEntries):
"""Bulk creation of entries in the table of contents.
If you knew the titles but not the page numbers, you could
supply them to get sensible output on the first run."""
for (level, text, pageNum) in listOfEntries:
self.addEntry(level, text, pageNum)
def wrap(self, availWidth, availHeight):
"All table properties should be known by now."
widths = (availWidth - self.rightColumnWidth,
self.rightColumnWidth)
# makes an internal table which does all the work.
# we draw the LAST RUN's entries! If there are
# none, we make some dummy data to keep the table
# from complaining
if len(self._lastEntries) == 0:
_tempEntries = [(0,'Placeholder for table of contents',0)]
else:
_tempEntries = self._lastEntries
tableData = []
for (level, text, pageNum) in _tempEntries:
leftColStyle = self.levelStyles[level]
#right col style is right aligned
rightColStyle = ParagraphStyle(name='leftColLevel%d' % level,
parent=leftColStyle,
leftIndent=0,
alignment=enums.TA_RIGHT)
leftPara = Paragraph(text, leftColStyle)
rightPara = Paragraph(str(pageNum), rightColStyle)
tableData.append([leftPara, rightPara])
self._table = Table(tableData, colWidths=widths,
style=self.tableStyle)
self.width, self.height = self._table.wrapOn(self.canv,availWidth, availHeight)
return (self.width, self.height)
def split(self, availWidth, availHeight):
"""At this stage we do not care about splitting the entries,
we will just return a list of platypus tables. Presumably the
calling app has a pointer to the original TableOfContents object;
Platypus just sees tables.
"""
return self._table.splitOn(self.canv,availWidth, availHeight)
def drawOn(self, canvas, x, y, _sW=0):
"""Don't do this at home! The standard calls for implementing
draw(); we are hooking this in order to delegate ALL the drawing
work to the embedded table object.
"""
self._table.drawOn(canvas, x, y, _sW)
class SimpleIndex(IndexingFlowable):
"""This creates a very simple index.
Entries have a string key, and appear with a page number on
the right. Prototype for more sophisticated multi-level index."""
def __init__(self):
#keep stuff in a dictionary while building
self._entries = {}
self._lastEntries = {}
self._table = None
self.textStyle = ParagraphStyle(name='index',
fontName='Times-Roman',
fontSize=12)
def isIndexing(self):
return 1
def isSatisfied(self):
return (self._entries == self._lastEntries)
def beforeBuild(self):
# keep track of the last run
self._lastEntries = self._entries.copy()
self.clearEntries()
def clearEntries(self):
self._entries = {}
def notify(self, kind, stuff):
"""The notification hook called to register all kinds of events.
Here we are interested in 'IndexEntry' events only.
"""
if kind == 'IndexEntry':
(text, pageNum) = stuff
self.addEntry(text, pageNum)
def addEntry(self, text, pageNum):
"""Allows incremental buildup"""
if self._entries.has_key(text):
self._entries[text].append(str(pageNum))
else:
self._entries[text] = [pageNum]
def split(self, availWidth, availHeight):
"""At this stage we do not care about splitting the entries,
we will just return a list of platypus tables. Presumably the
calling app has a pointer to the original TableOfContents object;
Platypus just sees tables.
"""
return self._table.splitOn(self.canv,availWidth, availHeight)
def wrap(self, availWidth, availHeight):
"All table properties should be known by now."
# makes an internal table which does all the work.
# we draw the LAST RUN's entries! If there are
# none, we make some dummy data to keep the table
# from complaining
if len(self._lastEntries) == 0:
_tempEntries = [('Placeholder for index',[0,1,2])]
else:
_tempEntries = self._lastEntries.items()
_tempEntries.sort()
tableData = []
for (text, pageNumbers) in _tempEntries:
#right col style is right aligned
allText = text + ': ' + string.join(map(str, pageNumbers), ', ')
para = Paragraph(allText, self.textStyle)
tableData.append([para])
self._table = Table(tableData, colWidths=[availWidth])
self.width, self.height = self._table.wrapOn(self.canv,availWidth, availHeight)
return (self.width, self.height)
def drawOn(self, canvas, x, y, _sW=0):
"""Don't do this at home! The standard calls for implementing
draw(); we are hooking this in order to delegate ALL the drawing
work to the embedded table object.
"""
self._table.drawOn(canvas, x, y, _sW)
class ReferenceText(IndexingFlowable):
"""Fakery to illustrate how a reference would work if we could
put it in a paragraph."""
def __init__(self, textPattern, targetKey):
self.textPattern = textPattern
self.target = targetKey
self.paraStyle = ParagraphStyle('tmp')
self._lastPageNum = None
self._pageNum = -999
self._para = None
def beforeBuild(self):
self._lastPageNum = self._pageNum
def notify(self, kind, stuff):
if kind == 'Target':
(key, pageNum) = stuff
if key == self.target:
self._pageNum = pageNum
def wrap(self, availWidth, availHeight):
text = self.textPattern % self._lastPageNum
self._para = Paragraph(text, self.paraStyle)
return self._para.wrap(availWidth, availHeight)
def drawOn(self, canvas, x, y, _sW=0):
self._para.drawOn(canvas, x, y, _sW)

1384
bin/reportlab/platypus/tables.py Executable file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,316 @@
#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/platypus/xpreformatted.py
__version__=''' $Id: xpreformatted.py 2426 2004-09-02 11:52:56Z rgbecker $ '''
import string
from types import StringType, ListType
from reportlab.lib import PyFontify
from paragraph import Paragraph, cleanBlockQuotedText, _handleBulletWidth, \
ParaLines, _getFragWords, stringWidth, _sameFrag
from flowables import _dedenter
def _getFragLines(frags):
lines = []
cline = []
W = frags[:]
while W != []:
w = W[0]
t = w.text
del W[0]
i = string.find(t,'\n')
if i>=0:
tleft = t[i+1:]
cline.append(w.clone(text=t[:i]))
lines.append(cline)
cline = []
if tleft!='':
W.insert(0,w.clone(text=tleft))
else:
cline.append(w)
if cline!=[]:
lines.append(cline)
return lines
def _split_blPara(blPara,start,stop):
f = blPara.clone()
for a in ('lines', 'text'):
if hasattr(f,a): delattr(f,a)
f.lines = blPara.lines[start:stop]
return [f]
# Will be removed shortly.
def _countSpaces(text):
return string.count(text, ' ')
## i = 0
## s = 0
## while 1:
## j = string.find(text,' ',i)
## if j<0: return s
## s = s + 1
## i = j + 1
def _getFragWord(frags):
''' given a fragment list return a list of lists
[size, spaces, (f00,w00), ..., (f0n,w0n)]
each pair f,w represents a style and some string
'''
W = []
n = 0
s = 0
for f in frags:
text = f.text[:]
W.append((f,text))
n = n + stringWidth(text, f.fontName, f.fontSize)
#s = s + _countSpaces(text)
s = s + string.count(text, ' ') # much faster for many blanks
#del f.text # we can't do this until we sort out splitting
# of paragraphs
return n, s, W
class XPreformatted(Paragraph):
def __init__(self, text, style, bulletText = None, frags=None, caseSensitive=1, dedent=0):
self.caseSensitive = caseSensitive
cleaner = lambda text, dedent=dedent: string.join(_dedenter(text or '',dedent),'\n')
self._setup(text, style, bulletText, frags, cleaner)
def breakLines(self, width):
"""
Returns a broken line structure. There are two cases
A) For the simple case of a single formatting input fragment the output is
A fragment specifier with
kind = 0
fontName, fontSize, leading, textColor
lines= A list of lines
Each line has two items.
1) unused width in points
2) a list of words
B) When there is more than one input formatting fragment the out put is
A fragment specifier with
kind = 1
lines= A list of fragments each having fields
extraspace (needed for justified)
fontSize
words=word list
each word is itself a fragment with
various settings
This structure can be used to easily draw paragraphs with the various alignments.
You can supply either a single width or a list of widths; the latter will have its
last item repeated until necessary. A 2-element list is useful when there is a
different first line indent; a longer list could be created to facilitate custom wraps
around irregular objects."""
if type(width) <> ListType: maxWidths = [width]
else: maxWidths = width
lines = []
lineno = 0
maxWidth = maxWidths[lineno]
style = self.style
fFontSize = float(style.fontSize)
requiredWidth = 0
#for bullets, work out width and ensure we wrap the right amount onto line one
_handleBulletWidth(self.bulletText,style,maxWidths)
self.height = 0
frags = self.frags
nFrags= len(frags)
if nFrags==1:
f = frags[0]
if hasattr(f,'text'):
fontSize = f.fontSize
fontName = f.fontName
kind = 0
L=string.split(f.text, '\n')
for l in L:
currentWidth = stringWidth(l,fontName,fontSize)
requiredWidth = max(currentWidth,requiredWidth)
extraSpace = maxWidth-currentWidth
lines.append((extraSpace,string.split(l,' '),currentWidth))
lineno = lineno+1
maxWidth = lineno<len(maxWidths) and maxWidths[lineno] or maxWidths[-1]
else:
kind = f.kind
lines = f.lines
for L in lines:
if kind==0:
currentWidth = L[2]
else:
currentWidth = L.currentWidth
requiredWidth = max(currentWidth,requiredWidth)
self.width = max(self.width,requiredWidth)
return f.clone(kind=kind, lines=lines)
elif nFrags<=0:
return ParaLines(kind=0, fontSize=style.fontSize, fontName=style.fontName,
textColor=style.textColor, lines=[])
else:
for L in _getFragLines(frags):
maxSize = 0
currentWidth, n, w = _getFragWord(L)
f = w[0][0]
maxSize = max(maxSize,f.fontSize)
words = [f.clone()]
words[-1].text = w[0][1]
for i in w[1:]:
f = i[0].clone()
f.text=i[1]
words.append(f)
maxSize = max(maxSize,f.fontSize)
lineno = lineno+1
maxWidth = lineno<len(maxWidths) and maxWidths[lineno] or maxWidths[-1]
requiredWidth = max(currentWidth,requiredWidth)
extraSpace = maxWidth - currentWidth
lines.append(ParaLines(extraSpace=extraSpace,wordCount=n, words=words, fontSize=maxSize, currentWidth=currentWidth))
self.width = max(self.width,requiredWidth)
return ParaLines(kind=1, lines=lines)
return lines
# we need this her to get the right splitter
def _get_split_blParaFunc(self):
return _split_blPara
class PythonPreformatted(XPreformatted):
"""Used for syntax-colored Python code, otherwise like XPreformatted.
"""
formats = {
'rest' : ('', ''),
'comment' : ('<font color="green">', '</font>'),
'keyword' : ('<font color="blue"><b>', '</b></font>'),
'parameter' : ('<font color="black">', '</font>'),
'identifier' : ('<font color="red">', '</font>'),
'string' : ('<font color="gray">', '</font>') }
def __init__(self, text, style, bulletText = None, dedent=0, frags=None):
if text:
text = self.fontify(self.escapeHtml(text))
apply(XPreformatted.__init__,
(self, text, style),
{'bulletText':bulletText, 'dedent':dedent, 'frags':frags})
def escapeHtml(self, text):
s = string.replace(text, '&', '&amp;')
s = string.replace(s, '<', '&lt;')
s = string.replace(s, '>', '&gt;')
return s
def fontify(self, code):
"Return a fontified version of some Python code."
if code[0] == '\n':
code = code[1:]
tags = PyFontify.fontify(code)
fontifiedCode = ''
pos = 0
for k, i, j, dummy in tags:
fontifiedCode = fontifiedCode + code[pos:i]
s, e = self.formats[k]
fontifiedCode = fontifiedCode + s + code[i:j] + e
pos = j
fontifiedCode = fontifiedCode + code[pos:]
return fontifiedCode
if __name__=='__main__': #NORUNTESTS
def dumpXPreformattedLines(P):
print '\n############dumpXPreforemattedLines(%s)' % str(P)
lines = P.blPara.lines
n =len(lines)
for l in range(n):
line = lines[l]
words = line.words
nwords = len(words)
print 'line%d: %d(%d)\n ' % (l,nwords,line.wordCount),
for w in range(nwords):
print "%d:'%s'"%(w,words[w].text),
print
def dumpXPreformattedFrags(P):
print '\n############dumpXPreforemattedFrags(%s)' % str(P)
frags = P.frags
n =len(frags)
for l in range(n):
print "frag%d: '%s'" % (l, frags[l].text)
l = 0
for L in _getFragLines(frags):
n=0
for W in _getFragWords(L):
print "frag%d.%d: size=%d" % (l, n, W[0]),
n = n + 1
for w in W[1:]:
print "'%s'" % w[1],
print
l = l + 1
def try_it(text,style,dedent,aW,aH):
P=XPreformatted(text,style,dedent=dedent)
dumpXPreformattedFrags(P)
w,h = P.wrap(aW, aH)
dumpXPreformattedLines(P)
S = P.split(aW,aH)
dumpXPreformattedLines(P)
for s in S:
s.wrap(aW,aH)
dumpXPreformattedLines(s)
aH = 500
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
styleSheet = getSampleStyleSheet()
B = styleSheet['BodyText']
DTstyle = ParagraphStyle("discussiontext", parent=B)
DTstyle.fontName= 'Helvetica'
for (text,dedent,style, aW, aH, active) in [('''
The <font name=courier color=green>CMYK</font> or subtractive
method follows the way a printer
mixes three pigments (cyan, magenta, and yellow) to form colors.
Because mixing chemicals is more difficult than combining light there
is a fourth parameter for darkness. For example a chemical
combination of the <font name=courier color=green>CMY</font> pigments generally never makes a perfect
black -- instead producing a muddy color -- so, to get black printers
don't use the <font name=courier color=green>CMY</font> pigments but use a direct black ink. Because
<font name=courier color=green>CMYK</font> maps more directly to the way printer hardware works it may
be the case that &amp;| &amp; | colors specified in <font name=courier color=green>CMYK</font> will provide better fidelity
and better control when printed.
''',0,DTstyle, 456.0, 42.8, 0),
('''
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.
''',3, DTstyle, 456.0, 42.8, 0),
("""\
<font color=blue>class </font><font color=red>FastXMLParser</font>:
# Nonsense method
def nonsense(self):
self.foo = 'bar'
""",0, styleSheet['Code'], 456.0, 4.8, 1),
]:
if active: try_it(text,style,dedent,aW,aH)