RL2
bzr revid: pinky-1da086d6ffc903bddd20216fdc81b76f38293992
This commit is contained in:
parent
87b2a6bebc
commit
6488c0b0da
|
@ -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
|
|
@ -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()
|
|
@ -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
|
@ -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
|
@ -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>β<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>τ</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<B>C&D"E'F''')
|
||||||
|
check_text('''A< B> C& D" E' 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.''')
|
|
@ -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)
|
||||||
|
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -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, '&', '&')
|
||||||
|
s = string.replace(s, '<', '<')
|
||||||
|
s = string.replace(s, '>', '>')
|
||||||
|
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 &| & | 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)
|
Loading…
Reference in New Issue