[FIX] Conference title overflows abstract booklet
[cds-indico.git] / indico / MaKaC / PDFinterface / base.py
blobb6594f65a8dd70b82d23657b085f2e0af1ace116
1 # -*- coding: utf-8 -*-
2 ##
3 ##
4 ## This file is part of CDS Indico.
5 ## Copyright (C) 2002, 2003, 2004, 2005, 2006, 2007 CERN.
6 ##
7 ## CDS Indico is free software; you can redistribute it and/or
8 ## modify it under the terms of the GNU General Public License as
9 ## published by the Free Software Foundation; either version 2 of the
10 ## License, or (at your option) any later version.
12 ## CDS Indico is distributed in the hope that it will be useful, but
13 ## WITHOUT ANY WARRANTY; without even the implied warranty of
14 ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 ## General Public License for more details.
17 ## You should have received a copy of the GNU General Public License
18 ## along with CDS Indico; if not, write to the Free Software Foundation, Inc.,
19 ## 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
21 import os, math
22 import xml.sax.saxutils as saxutils
23 from HTMLParser import HTMLParser
24 from reportlab.platypus import SimpleDocTemplate, PageTemplate, Table
25 from reportlab.platypus.tableofcontents import TableOfContents
26 from reportlab.lib.styles import ParagraphStyle
27 from reportlab.rl_config import defaultPageSize
28 from reportlab.lib.units import inch, cm
29 from reportlab.lib.enums import TA_CENTER, TA_RIGHT, TA_LEFT
30 from reportlab import platypus
31 from reportlab.pdfgen.canvas import Canvas
32 from reportlab.platypus.frames import Frame
33 from reportlab.lib.pagesizes import landscape, A4, LETTER, A0, A1, A2, A3, A5
34 from reportlab.pdfbase import pdfmetrics
35 from reportlab.pdfbase.ttfonts import TTFont
36 from MaKaC.i18n import _
37 from MaKaC.common.utils import isStringHTML
39 # PIL is the library used by reportlab to work with images.
40 # If it isn't available, we must NOT put images in the PDF.
41 # Then before add an image, we must check the HAVE_PIL global variable
42 try :
43 from PIL import Image as PILImage
44 HAVE_PIL = True
45 except ImportError, e:
46 HAVE_PIL = False
48 ratio = math.sqrt(math.sqrt(2.0))
50 class PDFSizes:
52 def __init__(self):
53 self.PDFpagesizes = {'Letter' : LETTER,
54 'A0' : A3,
55 'A1' : A3,
56 'A2' : A3,
57 'A3' : A3,
58 'A4' : A4,
59 'A5' : A5
62 self.PDFfontsizes = [_("xxx-small"), _("xx-small"), _("x-small"), _("smaller"), _("small"), _("normal"), _("large"), _("larger")]
64 class PDFHTMLParser(HTMLParser):
65 _removedTags = ["a", "font"]
67 def __init__(self):
68 HTMLParser.__init__(self)
69 self.text = []
71 def parse(self, s):
72 "Parse the given string 's'."
73 self.feed(s)
74 self.close()
75 return "".join(self.text)
77 def handle_data(self, data):
78 self.text.append( saxutils.escape(data) )
80 def filterAttrs(self, attrs):
81 filteredAttrs = []
82 for x, y in attrs:
83 if x not in ["target"]:
84 filteredAttrs.append((x,y))
85 return filteredAttrs
87 def handle_entityref(self, name):
88 self.text.append( "&%s;"%name )
90 def handle_starttag(self, tag, attrs):
91 if tag == "br":
92 self.text.append( "<br/>" )
93 elif tag in self._removedTags:
94 return
95 else:
96 self.text.append( "<%s%s>" % (tag, " ".join([ ' %s="%s"' % (x,y) for x,y in self.filterAttrs(attrs)])) )
98 def handle_startendtag(self, tag, attrs):
99 self.text.append( "<%s%s/>" % (tag, " ".join([ ' %s="%s"' % (x,y) for x,y in self.filterAttrs(attrs)])) )
101 def handle_endtag(self, tag):
102 if tag in self._removedTags:
103 return
104 self.text.append( "</%s>" % tag )
106 def escape(text):
107 if text is None:
108 text = ""
109 try:
110 text = PDFHTMLParser().parse(text)
111 if not isStringHTML(text):
112 text = text.replace("\r\n"," <br/>")
113 text = text.replace("\n"," <br/>")
114 text = text.replace("\r"," <br/>")
115 return text
116 except Exception:
117 return saxutils.escape(text)
119 def modifiedFontSize(fontsize, lowerNormalHigher):
121 if lowerNormalHigher == _("normal"):
122 return fontsize
123 elif lowerNormalHigher == _("small"):
124 return fontsize / ratio
125 elif lowerNormalHigher == _("large"):
126 return fontsize * ratio
127 elif lowerNormalHigher == _("smaller"):
128 return (fontsize / ratio) / ratio
129 elif lowerNormalHigher == _("x-small"):
130 return ((fontsize / ratio) / ratio) / ratio
131 elif lowerNormalHigher == _("xx-small"):
132 return (((fontsize / ratio) / ratio) / ratio) / ratio
133 elif lowerNormalHigher == _("xxx-small"):
134 return ((((fontsize / ratio) / ratio) / ratio) / ratio) / ratio
135 elif lowerNormalHigher == _("larger"):
136 return fontsize * ratio * ratio
137 else:
138 return fontsize
140 alreadyRegistered = False
142 def setTTFonts():
143 global alreadyRegistered
144 if not alreadyRegistered:
145 # Import fonts from indico.extra (separate package)
146 import indico.extra.fonts
148 dir=os.path.split(os.path.abspath(indico.extra.fonts.__file__))[0]
149 pdfmetrics.registerFont(TTFont('Times-Roman', os.path.join(dir,'LiberationSerif-Regular.ttf')))
150 pdfmetrics.registerFont(TTFont('Times-Bold', os.path.join(dir, 'LiberationSerif-Bold.ttf')))
151 pdfmetrics.registerFont(TTFont('Times-Italic', os.path.join(dir,'LiberationSerif-Italic.ttf')))
152 pdfmetrics.registerFont(TTFont('Times-Bold-Italic', os.path.join(dir, 'LiberationSerif-BoldItalic.ttf')))
153 pdfmetrics.registerFont(TTFont('Sans', os.path.join(dir,'LiberationSans-Regular.ttf')))
154 pdfmetrics.registerFont(TTFont('Sans-Bold', os.path.join(dir, 'LiberationSans-Bold.ttf')))
155 pdfmetrics.registerFont(TTFont('Sans-Italic', os.path.join(dir,'LiberationSans-Italic.ttf')))
156 pdfmetrics.registerFont(TTFont('Sans-Bold-Italic', os.path.join(dir, 'LiberationSans-BoldItalic.ttf')))
157 pdfmetrics.registerFont(TTFont('Courier', os.path.join(dir, 'LiberationMono-Regular.ttf')))
158 pdfmetrics.registerFont(TTFont('Courier-Bold', os.path.join(dir, 'LiberationMono-Bold.ttf')))
159 pdfmetrics.registerFont(TTFont('Courier-Italic', os.path.join(dir, 'LiberationMono-Italic.ttf')))
160 pdfmetrics.registerFont(TTFont('Courier-Bold-Italic', os.path.join(dir, 'LiberationMono-BoldItalic.ttf')))
161 pdfmetrics.registerFont(TTFont('LinuxLibertine', os.path.join(dir, 'LinLibertine_Re-4.4.1.ttf')))
162 pdfmetrics.registerFont(TTFont('LinuxLibertine-Bold', os.path.join(dir, 'LinLibertine_Bd-4.1.0.ttf')))
163 pdfmetrics.registerFont(TTFont('LinuxLibertine-Italic', os.path.join(dir, 'LinLibertine_It-4.0.6.ttf')))
164 pdfmetrics.registerFont(TTFont('LinuxLibertine-Bold-Italic', os.path.join(dir, 'LinLibertine_BI-4.0.5.ttf')))
165 pdfmetrics.registerFont(TTFont('Kochi-Mincho', os.path.join(dir, 'kochi-mincho-subst.ttf')))
166 pdfmetrics.registerFont(TTFont('Kochi-Gothic', os.path.join(dir, 'kochi-gothic-subst.ttf')))
167 #pdfmetrics.registerFont(TTFont('Uming-CN', os.path.join(dir, 'uming.ttc')))
168 alreadyRegistered = True
170 class Paragraph(platypus.Paragraph):
172 add a part attribute for drawing the name of the current part on the laterPages function
174 def __init__(self, test, style, part="", bulletText=None, frags=None, caseSensitive=1):
175 platypus.Paragraph.__init__(self, test, style, bulletText, frags, caseSensitive)
176 self._part = part
178 def setPart(self, part):
179 self._part = part
181 def getPart(self):
182 return self._part
184 class SimpleParagraph(platypus.Flowable):
185 """ Simple and fast paragraph.
187 WARNING! This paragraph cannot break the line and doesn't have almost any formatting methods.
188 It's used only to increase PDF performance in places where normal paragraph is not needed.
190 def __init__(self, text, fontSize = 10, indent = 0, spaceAfter = 2):
191 platypus.Flowable.__init__(self)
192 self.text = text
193 self.height = fontSize + spaceAfter
194 self.fontSize = fontSize
195 self.spaceAfter = spaceAfter
196 self.indent = indent
198 def __repr__(self):
199 return ""
201 def draw(self):
202 #centre the text
203 self.canv.setFont('Times-Roman',self.fontSize)
204 self.canv.drawString(self.indent, self.spaceAfter, self.text)
206 class TableOfContentsEntry(Paragraph):
208 Class used to create table of contents entry with its number.
209 Much faster than table of table of contents from platypus lib
211 def __init__(self, test, pageNumber,style, part="", bulletText=None, frags=None, caseSensitive=1):
212 Paragraph.__init__(self, test, style, part, bulletText, frags, caseSensitive)
213 self._part = part
214 self._pageNumber = pageNumber
216 def _drawDots(self):
218 Draws row of dots from the end of the abstract title to the page number.
220 try:
221 freeSpace = int(self.blPara.lines[-1][0])
222 except AttributeError:
223 # Sometimes we get an ABag instead of a tuple.. in this case we use the extraSpace attribute
224 # as it seems to contain just what we need.
225 freeSpace = int(self.blPara.lines[-1].extraSpace)
226 while( freeSpace > 10 ):
227 dot = self.beginText(self.width + 10 - freeSpace, self.style.leading - self.style.fontSize)
228 dot.textLine(".")
229 self.canv.drawText(dot)
230 freeSpace -= 3
232 def draw(self):
233 platypus.Paragraph.draw(self)
234 tx = self.beginText(self.width + 10, self.style.leading - self.style.fontSize)
235 tx.setFont(self.style.fontName, self.style.fontSize, 0)
236 tx.textLine(str(self._pageNumber))
237 self.canv.drawText(tx)
238 self._drawDots()
240 class Spacer(platypus.Spacer):
241 def __init__(self, width, height, part=""):
242 platypus.Spacer.__init__(self, width, height)
243 self._part = part
245 def setPart(self, part):
246 self._part = part
248 def getPart(self):
249 return self._part
251 class Image(platypus.Image):
252 def __init__(self, filename, part="", width=None, height=None, kind='direct', mask="auto", lazy=1):
253 platypus.Image.__init__(self, filename, width=None, height=None, kind='direct', mask="auto", lazy=1)
254 self._part = part
256 def setPart(self, part):
257 self._part = part
259 def getPart(self):
260 return self._part
263 class PageBreak(platypus.PageBreak):
264 def __init__(self, part=""):
265 self._part = part
267 def setPart(self, part):
268 self._part = part
270 def getPart(self):
271 return self._part
273 class Preformatted(platypus.Preformatted):
274 def __init__(self, text, style, part="", bulletText = None, dedent=0):
275 platypus.Preformatted.__init__(self, text, style, bulletText = None, dedent=0)
276 self._part = part
278 def setPart(self, part):
279 self._part = part
281 def getPart(self):
282 return self._part
285 class FileDummy:
286 def __init__(self):
287 self._data = ""
288 self.name = "fileDummy"
290 def write(self, data):
291 self._data += data
293 def getData(self):
294 return self._data
296 def close(self):
297 pass
299 class CanvasA0(Canvas):
300 def __init__(self,filename,
301 pagesize=None,
302 bottomup = 1,
303 pageCompression=None,
304 encoding = None,
305 invariant = None,
306 verbosity=0):
308 Canvas.__init__(self, filename, pagesize=pagesize, bottomup=bottomup, pageCompression=pageCompression,
309 encoding=encoding, invariant=invariant, verbosity=verbosity)
310 self.scale(4.0, 4.0)
311 self.setPageSize(A0)
313 class CanvasA1(Canvas):
314 def __init__(self,filename,
315 pagesize=None,
316 bottomup = 1,
317 pageCompression=None,
318 encoding = None,
319 invariant = None,
320 verbosity=0):
322 Canvas.__init__(self, filename, pagesize=pagesize, bottomup=bottomup, pageCompression=pageCompression,
323 encoding=encoding, invariant=invariant, verbosity=verbosity)
324 self.scale(2.0 * math.sqrt(2.0), 2.0 * math.sqrt(2.0))
325 self.setPageSize(A1)
327 class CanvasA2(Canvas):
328 def __init__(self,filename,
329 pagesize=None,
330 bottomup = 1,
331 pageCompression=None,
332 encoding = None,
333 invariant = None,
334 verbosity=0):
336 Canvas.__init__(self, filename, pagesize=pagesize, bottomup=bottomup, pageCompression=pageCompression,
337 encoding=encoding, invariant=invariant, verbosity=verbosity)
338 self.scale(2.0, 2.0)
339 self.setPageSize(A2)
341 class CanvasA3(Canvas):
342 def __init__(self,filename,
343 pagesize=None,
344 bottomup = 1,
345 pageCompression=None,
346 encoding = None,
347 invariant = None,
348 verbosity=0):
350 Canvas.__init__(self, filename, pagesize=pagesize, bottomup=bottomup, pageCompression=pageCompression,
351 encoding=encoding, invariant=invariant, verbosity=verbosity)
352 self.scale(math.sqrt(2.0), math.sqrt(2.0))
353 self.setPageSize(A3)
355 class CanvasA5(Canvas):
356 def __init__(self,filename,
357 pagesize=None,
358 bottomup = 1,
359 pageCompression=None,
360 encoding = None,
361 invariant = None,
362 verbosity=0):
364 Canvas.__init__(self, filename, pagesize=pagesize, bottomup=bottomup, pageCompression=pageCompression,
365 encoding=encoding, invariant=invariant, verbosity=verbosity)
366 self.scale(1.0 / math.sqrt(2.0), 1.0 / math.sqrt(2.0))
367 self.setPageSize(A5)
369 pagesizeNameToCanvas = {'A4': Canvas,
370 'A0': CanvasA0,
371 'A1': CanvasA1,
372 'A2': CanvasA2,
373 'A3': CanvasA3,
374 'A5': CanvasA5,
375 'Letter': Canvas,
378 class PDFBase:
380 def __init__(self, doc=None, story=None, pagesize = 'A4', printLandscape=False):
382 if doc:
383 self._doc = doc
384 else:
385 #create a new document
386 #As the constructor of SimpleDocTemplate can take only a filename or a file object,
387 #to keep the PDF data not in a file, we use a dummy file object which save the data in a string
388 self._fileDummy = FileDummy()
389 if printLandscape:
390 self._doc = SimpleDocTemplate(self._fileDummy, pagesize = landscape(PDFSizes().PDFpagesizes[pagesize]))
391 else:
392 self._doc = SimpleDocTemplate(self._fileDummy, pagesize = PDFSizes().PDFpagesizes[pagesize])
394 if story is not None:
395 self._story = story
396 else:
397 #create a new story with a spacer which take all the first page
398 #then the first page is only drawing by the firstPage method
399 self._story = [PageBreak()]
401 if printLandscape:
402 self._PAGE_HEIGHT = landscape(PDFSizes().PDFpagesizes[pagesize])[1]
403 self._PAGE_WIDTH = landscape(PDFSizes().PDFpagesizes[pagesize])[0]
404 else:
405 self._PAGE_HEIGHT = PDFSizes().PDFpagesizes[pagesize][1]
406 self._PAGE_WIDTH = PDFSizes().PDFpagesizes[pagesize][0]
408 self._canv = Canvas
409 setTTFonts()
411 def firstPage(self, c, doc):
412 """set the first page of the document
413 This function is call by doc.build method for the first page
415 pass
418 def laterPages(self, c, doc):
419 """set the layout of the page after the first
420 This function is call by doc.build method one each page after the first
422 pass
425 def getBody(self, story=None):
426 if not story:
427 story = self._story
428 """add the content to the story
430 pass
433 def getPDFBin(self):
434 #build the pdf in the fileDummy
435 self.getBody()
436 self._doc.build(self._story, onFirstPage=self.firstPage, onLaterPages=self.laterPages)
437 #return the data from the fileDummy
438 return self._fileDummy.getData()
440 def _drawWrappedString(self, c, text, font='Times-Bold', size=30, color=(0,0,0), \
441 align="center", width=None, height=None, measurement=cm, lineSpacing=1, maximumWidth=None ):
442 if maximumWidth is None:
443 maximumWidth = self._PAGE_WIDTH-1*cm
444 if width is None:
445 width=self._PAGE_WIDTH/2.0
446 if height is None:
447 height=self._PAGE_HEIGHT-10*measurement
448 draw = c.drawCentredString
449 if align == "right":
450 draw = c.drawRightString
451 elif align == "left":
452 draw = c.drawString
453 c.setFont(font, size)
454 c.setFillColorRGB(*color)
455 titleWords = text.split()
456 line=""
457 for word in titleWords:
458 lineAux = "%s %s"%(line, word)
459 lsize = c.stringWidth(lineAux, font, size)
460 if lsize < maximumWidth:
461 line = lineAux
462 else:
463 draw(width,height, escape(line))
464 height -= lineSpacing*measurement
465 line = word
466 if line.strip() != "":
467 draw(width, height, escape(line))
468 return height
470 def _drawLogo(self, c, drawTitle = True):
471 if HAVE_PIL:
472 logo = self._conf.getLogo()
473 imagePath = ""
474 if logo:
475 imagePath = logo.getFilePath()
476 if imagePath:
477 img = PILImage.open(imagePath)
478 width, height = img.size
479 if width > self._PAGE_WIDTH:
480 ratio = float(height)/width
481 width = self._PAGE_WIDTH
482 height = self._PAGE_WIDTH * ratio
483 img = img.resize((width, height))
484 startHeight = self._PAGE_HEIGHT
485 if drawTitle:
486 startHeight = self._drawWrappedString(c, escape(self._conf.getTitle()), height=self._PAGE_HEIGHT - inch)
487 height = 0
488 c.drawInlineImage(img, self._PAGE_WIDTH/2.0 - width/2, startHeight - 1.5 * inch - height)
489 return True
490 return False
494 def _doNothing(canvas, doc):
495 "Dummy callback for onPage"
496 pass
498 class DocTemplateWithTOC(SimpleDocTemplate):
500 def __init__(self, indexedFlowable, filename, firstPageNumber = 1, **kw ):
501 """toc is the TableOfContents object
502 indexedFlowale is a dictionnary with flowables as key and a dictionnary as value.
503 the sub-dictionnary have two key:
504 text: the text which will br print in the table
505 level: the level of the entry( modifying the indentation and the police
508 self._toc = []
509 self._tocStory = []
510 self._indexedFlowable = indexedFlowable
511 self._filename = filename
512 self._part = ""
513 self._firstPageNumber = firstPageNumber
514 SimpleDocTemplate.__init__(self, filename, **kw)
515 setTTFonts()
517 def afterFlowable(self, flowable):
518 if flowable in self._indexedFlowable:
519 self._toc.append((self._indexedFlowable[flowable]["level"],self._indexedFlowable[flowable]["text"], self.page + self._firstPageNumber - 1))
520 try:
521 if flowable.getPart() != "":
522 self._part = flowable.getPart()
523 except:
524 pass
526 def handle_documentBegin(self):
527 self._part = ""
528 SimpleDocTemplate.handle_documentBegin(self)
530 def _prepareTOC(self):
531 headerStyle = ParagraphStyle({})
532 headerStyle.fontName = "Times-Bold"
533 headerStyle.fontSize = modifiedFontSize(18, 18)
534 headerStyle.leading = modifiedFontSize(22, 22)
535 headerStyle.alignment = TA_CENTER
536 entryStyle = ParagraphStyle({})
537 entryStyle.spaceBefore = 8
538 self._tocStory.append(PageBreak())
539 self._tocStory.append(Spacer(inch, 1*cm))
540 self._tocStory.append(Paragraph( _("Table of contents"), headerStyle))
541 self._tocStory.append(Spacer(inch, 2*cm))
542 for entry in self._toc:
543 self._tocStory.append(TableOfContentsEntry("<para leftIndent=%s" % ((entry[0] - 1) * 50) + ">" + entry[1] + "</para>", str(entry[2]),entryStyle))
544 #self._tocStory.append(SimpleParagraph(entry[1]))
545 self._tocStory.append(PageBreak())
547 def multiBuild(self, story, filename=None, canvasMaker=Canvas, maxPasses=10, onFirstPage=_doNothing, onLaterPages=_doNothing):
548 self._calc() #in case we changed margins sizes etc
549 frameT = Frame(self.leftMargin, self.bottomMargin, self.width, self.height, id='normal')
550 self.addPageTemplates([PageTemplate(id='Later',frames=frameT, onPageEnd=onLaterPages,pagesize=self.pagesize)])
551 if onLaterPages is _doNothing and hasattr(self,'onLaterPages'):
552 self.pageTemplates[0].beforeDrawPage = self.onLaterPages
553 SimpleDocTemplate.multiBuild(self, story, maxPasses, canvasmaker=canvasMaker)
554 self._prepareTOC()
555 contentFile = self.filename
556 self.filename = FileDummy()
557 self.pageTemplates = []
558 self.addPageTemplates([PageTemplate(id='First',frames=frameT, onPage=onFirstPage,pagesize=self.pagesize)])
559 if onFirstPage is _doNothing and hasattr(self,'onFirstPage'):
560 self.pageTemplates[0].beforeDrawPage = self.onFirstPage
561 SimpleDocTemplate.multiBuild(self, self._tocStory, maxPasses, canvasmaker=canvasMaker)
562 self.mergePDFs(self.filename, contentFile)
564 def mergePDFs(self, pdf1, pdf2):
565 from pyPdf import PdfFileWriter, PdfFileReader
566 import cStringIO
567 outputStream = cStringIO.StringIO()
568 pdf1Stream = cStringIO.StringIO()
569 pdf2Stream = cStringIO.StringIO()
570 pdf1Stream.write(pdf1.getData())
571 pdf2Stream.write(pdf2.getData())
572 output = PdfFileWriter()
573 background_pages = PdfFileReader(pdf1Stream)
574 foreground_pages = PdfFileReader(pdf2Stream)
575 for page in background_pages.pages:
576 output.addPage(page)
577 for page in foreground_pages.pages:
578 output.addPage(page)
579 output.write(outputStream)
580 pdf2._data = outputStream.getvalue()
581 outputStream.close()
583 def getCurrentPart(self):
584 return self._part
587 class PDFWithTOC(PDFBase):
589 create a PDF with a Table of Contents
593 def __init__(self, story=None, pagesize = 'A4', fontsize = 'normal', firstPageNumber = 1 ):
596 self._fontsize = fontsize
597 #self._indexedFlowable = [] #indexedFlowable
598 #self._toc = TableOfContents()
599 self._story=story
600 if story is None:
601 self._story = []
602 self._story.append( Spacer(inch, 0*cm) ) #without this blank spacer first abstract isn't displayed. why?
604 #self._toc = TableOfContents()
605 #self._processTOCPage()
606 self._indexedFlowable = {}
607 self._fileDummy = FileDummy()
609 self._doc = DocTemplateWithTOC(self._indexedFlowable, self._fileDummy, firstPageNumber = firstPageNumber, pagesize=PDFSizes().PDFpagesizes[pagesize])
611 self._PAGE_HEIGHT = PDFSizes().PDFpagesizes[pagesize][1]
612 self._PAGE_WIDTH = PDFSizes().PDFpagesizes[pagesize][0]
614 setTTFonts()
617 def _processTOCPage(self):
618 """ Generates page with table of contents.
620 Not used, because table of contents is generated automatically inside DocTemplateWithTOC class
622 style1 = ParagraphStyle({})
623 style1.fontName = "Times-Bold"
624 style1.fontSize = modifiedFontSize(18, self._fontsize)
625 style1.leading = modifiedFontSize(22, self._fontsize)
626 style1.alignment = TA_CENTER
627 p = Paragraph( _("Table of contents"), style1)
628 self._story.append(Spacer(inch, 1*cm))
629 self._story.append(p)
630 self._story.append(Spacer(inch, 2*cm))
631 self._story.append(self._toc)
632 self._story.append(PageBreak())
634 def getBody(self, story=None):
635 """add the content to the story
636 When you want to put a paragraph p in the toc, add it to the self._indexedFlowable as this:
637 self._indexedFlowable[p] = {"text":"my title", "level":1}
639 if not story:
640 story = self._story
641 pass
643 def getPDFBin(self):
644 self.getBody()
645 self._doc.multiBuild( self._story, onFirstPage=self.firstPage, onLaterPages=self.laterPages)
646 return self._fileDummy.getData()