1
#Copyright ReportLab Europe Ltd. 2000-2004
2
#see license.txt for license details
3
#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/platypus/tableofcontents.py
5
This module defines a single TableOfContents() class that can be used to
6
create automatically a table of tontents for Platypus documents like
10
toc = TableOfContents()
12
# some heading paragraphs here...
13
doc = MyTemplate(path)
16
The data needed to create the table is a list of (level, text, pageNum)
17
triplets, plus some paragraph styles for each level of the table itself.
18
The triplets will usually be created in a document template's method
19
like afterFlowable(), making notification calls using the notify()
20
method with appropriate data like this:
22
(level, text, pageNum) = ...
23
self.notify('TOCEntry', (level, text, pageNum))
25
As the table of contents need at least two passes over the Platypus
26
story which is why the moultiBuild0() method must be called.
28
The level<NUMBER>ParaStyle variables are the paragraph styles used
29
to format the entries in the table of contents. Their indentation
30
is calculated like this: each entry starts at a multiple of some
31
constant named delta. If one entry spans more than one line, all
32
lines after the first are indented by the same constant named
35
__version__=''' $Id$ '''
38
from reportlab.lib import enums
39
from reportlab.lib.units import cm
40
from reportlab.lib.styles import ParagraphStyle
41
from reportlab.platypus.paragraph import Paragraph
42
from reportlab.platypus.doctemplate import IndexingFlowable
43
from reportlab.platypus.tables import TableStyle, Table
46
# Default paragraph styles for tables of contents.
47
# (This could also be generated automatically or even
48
# on-demand if it is not known how many levels the
49
# TOC will finally need to display...)
54
levelZeroParaStyle = \
55
ParagraphStyle(name='LevelZero',
56
fontName='Times-Roman',
59
firstLineIndent = -epsilon,
60
leftIndent = 0*delta + epsilon)
63
ParagraphStyle(name='LevelOne',
64
parent = levelZeroParaStyle,
66
firstLineIndent = -epsilon,
67
leftIndent = 1*delta + epsilon)
70
ParagraphStyle(name='LevelTwo',
71
parent = levelOneParaStyle,
73
firstLineIndent = -epsilon,
74
leftIndent = 2*delta + epsilon)
76
levelThreeParaStyle = \
77
ParagraphStyle(name='LevelThree',
78
parent = levelTwoParaStyle,
80
firstLineIndent = -epsilon,
81
leftIndent = 3*delta + epsilon)
83
levelFourParaStyle = \
84
ParagraphStyle(name='LevelFour',
85
parent = levelTwoParaStyle,
87
firstLineIndent = -epsilon,
88
leftIndent = 4*delta + epsilon)
91
TableStyle([('VALIGN', (0,0), (-1,-1), 'TOP')])
94
class TableOfContents(IndexingFlowable):
95
"""This creates a formatted table of contents.
97
It presumes a correct block of data is passed in.
98
The data block contains a list of (level, text, pageNumber)
99
triplets. You can supply a paragraph style for each level
105
self.rightColumnWidth = 72
106
self.levelStyles = [levelZeroParaStyle,
111
self.tableStyle = defaultTableStyle
114
self._lastEntries = []
117
def beforeBuild(self):
118
# keep track of the last run
119
self._lastEntries = self._entries[:]
123
def isIndexing(self):
127
def isSatisfied(self):
128
return (self._entries == self._lastEntries)
130
def notify(self, kind, stuff):
131
"""The notification hook called to register all kinds of events.
133
Here we are interested in 'TOCEntry' events only.
135
if kind == 'TOCEntry':
136
(level, text, pageNum) = stuff
137
self.addEntry(level, text, pageNum)
140
def clearEntries(self):
144
def addEntry(self, level, text, pageNum):
145
"""Adds one entry to the table of contents.
147
This allows incremental buildup by a doctemplate.
148
Requires that enough styles are defined."""
150
assert type(level) == type(1), "Level must be an integer"
151
assert level < len(self.levelStyles), \
152
"Table of contents must have a style defined " \
153
"for paragraph level %d before you add an entry" % level
155
self._entries.append((level, text, pageNum))
158
def addEntries(self, listOfEntries):
159
"""Bulk creation of entries in the table of contents.
161
If you knew the titles but not the page numbers, you could
162
supply them to get sensible output on the first run."""
164
for (level, text, pageNum) in listOfEntries:
165
self.addEntry(level, text, pageNum)
168
def wrap(self, availWidth, availHeight):
169
"All table properties should be known by now."
171
widths = (availWidth - self.rightColumnWidth,
172
self.rightColumnWidth)
174
# makes an internal table which does all the work.
175
# we draw the LAST RUN's entries! If there are
176
# none, we make some dummy data to keep the table
178
if len(self._lastEntries) == 0:
179
_tempEntries = [(0,'Placeholder for table of contents',0)]
181
_tempEntries = self._lastEntries
184
for (level, text, pageNum) in _tempEntries:
185
leftColStyle = self.levelStyles[level]
186
#right col style is right aligned
187
rightColStyle = ParagraphStyle(name='leftColLevel%d' % level,
190
alignment=enums.TA_RIGHT)
191
leftPara = Paragraph(text, leftColStyle)
192
rightPara = Paragraph(str(pageNum), rightColStyle)
193
tableData.append([leftPara, rightPara])
195
self._table = Table(tableData, colWidths=widths,
196
style=self.tableStyle)
198
self.width, self.height = self._table.wrapOn(self.canv,availWidth, availHeight)
199
return (self.width, self.height)
202
def split(self, availWidth, availHeight):
203
"""At this stage we do not care about splitting the entries,
204
we will just return a list of platypus tables. Presumably the
205
calling app has a pointer to the original TableOfContents object;
206
Platypus just sees tables.
208
return self._table.splitOn(self.canv,availWidth, availHeight)
211
def drawOn(self, canvas, x, y, _sW=0):
212
"""Don't do this at home! The standard calls for implementing
213
draw(); we are hooking this in order to delegate ALL the drawing
214
work to the embedded table object.
216
self._table.drawOn(canvas, x, y, _sW)
219
class SimpleIndex(IndexingFlowable):
220
"""This creates a very simple index.
222
Entries have a string key, and appear with a page number on
223
the right. Prototype for more sophisticated multi-level index."""
225
#keep stuff in a dictionary while building
227
self._lastEntries = {}
229
self.textStyle = ParagraphStyle(name='index',
230
fontName='Times-Roman',
232
def isIndexing(self):
235
def isSatisfied(self):
236
return (self._entries == self._lastEntries)
238
def beforeBuild(self):
239
# keep track of the last run
240
self._lastEntries = self._entries.copy()
243
def clearEntries(self):
246
def notify(self, kind, stuff):
247
"""The notification hook called to register all kinds of events.
249
Here we are interested in 'IndexEntry' events only.
251
if kind == 'IndexEntry':
252
(text, pageNum) = stuff
253
self.addEntry(text, pageNum)
255
def addEntry(self, text, pageNum):
256
"""Allows incremental buildup"""
257
if self._entries.has_key(text):
258
self._entries[text].append(str(pageNum))
260
self._entries[text] = [pageNum]
262
def split(self, availWidth, availHeight):
263
"""At this stage we do not care about splitting the entries,
264
we will just return a list of platypus tables. Presumably the
265
calling app has a pointer to the original TableOfContents object;
266
Platypus just sees tables.
268
return self._table.splitOn(self.canv,availWidth, availHeight)
270
def wrap(self, availWidth, availHeight):
271
"All table properties should be known by now."
272
# makes an internal table which does all the work.
273
# we draw the LAST RUN's entries! If there are
274
# none, we make some dummy data to keep the table
276
if len(self._lastEntries) == 0:
277
_tempEntries = [('Placeholder for index',[0,1,2])]
279
_tempEntries = self._lastEntries.items()
283
for (text, pageNumbers) in _tempEntries:
284
#right col style is right aligned
285
allText = text + ': ' + string.join(map(str, pageNumbers), ', ')
286
para = Paragraph(allText, self.textStyle)
287
tableData.append([para])
289
self._table = Table(tableData, colWidths=[availWidth])
291
self.width, self.height = self._table.wrapOn(self.canv,availWidth, availHeight)
292
return (self.width, self.height)
294
def drawOn(self, canvas, x, y, _sW=0):
295
"""Don't do this at home! The standard calls for implementing
296
draw(); we are hooking this in order to delegate ALL the drawing
297
work to the embedded table object.
299
self._table.drawOn(canvas, x, y, _sW)
301
class ReferenceText(IndexingFlowable):
302
"""Fakery to illustrate how a reference would work if we could
303
put it in a paragraph."""
304
def __init__(self, textPattern, targetKey):
305
self.textPattern = textPattern
306
self.target = targetKey
307
self.paraStyle = ParagraphStyle('tmp')
308
self._lastPageNum = None
312
def beforeBuild(self):
313
self._lastPageNum = self._pageNum
315
def notify(self, kind, stuff):
317
(key, pageNum) = stuff
318
if key == self.target:
319
self._pageNum = pageNum
321
def wrap(self, availWidth, availHeight):
322
text = self.textPattern % self._lastPageNum
323
self._para = Paragraph(text, self.paraStyle)
324
return self._para.wrap(availWidth, availHeight)
326
def drawOn(self, canvas, x, y, _sW=0):
327
self._para.drawOn(canvas, x, y, _sW)