diff options
Diffstat (limited to 'bin/reportlab/platypus/tableofcontents.py')
| -rw-r--r-- | bin/reportlab/platypus/tableofcontents.py | 329 |
1 files changed, 329 insertions, 0 deletions
diff --git a/bin/reportlab/platypus/tableofcontents.py b/bin/reportlab/platypus/tableofcontents.py new file mode 100644 index 00000000000..d7f18393d89 --- /dev/null +++ b/bin/reportlab/platypus/tableofcontents.py @@ -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$ ''' +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) + + |
