1#Copyright ReportLab Europe Ltd. 2000-2017 2#see license.txt for license details 3#history https://hg.reportlab.com/hg-public/reportlab/log/tip/src/reportlab/platypus/doctemplate.py 4__all__ = ( 5 'ActionFlowable', 6 'BaseDocTemplate', 7 'CurrentFrameFlowable', 8 'FrameActionFlowable', 9 'FrameBreak', 10 'Indenter', 11 'IndexingFlowable', 12 'LayoutError', 13 'LCActionFlowable', 14 'NextFrameFlowable', 15 'NextPageTemplate', 16 'NotAtTopPageBreak', 17 'NullActionFlowable', 18 'PageAccumulator', 19 'PageBegin', 20 'PageTemplate', 21 'SimpleDocTemplate', 22 ) 23__version__='3.5.20' 24 25__doc__=""" 26This module contains the core structure of platypus. 27 28rlatypus constructs documents. Document styles are determined by DocumentTemplates. 29 30Each DocumentTemplate contains one or more PageTemplates which defines the look of the 31pages of the document. 32 33Each PageTemplate has a procedure for drawing the "non-flowing" part of the page 34(for example the header, footer, page number, fixed logo graphic, watermark, etcetera) and 35a set of Frames which enclose the flowing part of the page (for example the paragraphs, 36tables, or non-fixed diagrams of the text). 37 38A document is built when a DocumentTemplate is fed a sequence of Flowables. 39The action of the build consumes the flowables in order and places them onto 40frames on pages as space allows. When a frame runs out of space the next frame 41of the page is used. If no frame remains a new page is created. A new page 42can also be created if a page break is forced. 43 44The special invisible flowable NextPageTemplate can be used to specify 45the page template for the next page (which by default is the one being used 46for the current frame). 47""" 48 49from reportlab.platypus.flowables import * 50from reportlab.platypus.flowables import _ContainerSpace 51from reportlab.lib.units import inch 52from reportlab.platypus.paragraph import Paragraph 53from reportlab.platypus.frames import Frame 54from reportlab.rl_config import defaultPageSize, verbose 55import reportlab.lib.sequencer 56from reportlab.pdfgen import canvas 57from reportlab.lib.utils import isSeq, encode_label, decode_label, annotateException, strTypes 58from reportlab import ascii 59 60try: 61 set 62except NameError: 63 from sets import Set as set 64 65import sys 66import logging 67logger = logging.getLogger("reportlab.platypus") 68 69class LayoutError(Exception): 70 pass 71 72def _fSizeString(f): 73 #used to get size during error messages 74 w=getattr(f,'width',None) 75 if w is None: 76 w=getattr(f,'_width',None) 77 78 h=getattr(f,'height',None) 79 if h is None: 80 h=getattr(f,'_height',None) 81 #tables in particular may have some nasty large culprit 82 if hasattr(f, '_culprit'): 83 c = ', %s, ' % f._culprit() 84 else: 85 c = '' 86 if w is not None or h is not None: 87 if w is None: w='???' 88 if h is None: h='???' 89 return '(%s x %s)%s' % (w,h,c) 90 return '' 91 92def _doNothing(canvas, doc): 93 "Dummy callback for onPage" 94 pass 95 96class PTCycle(list): 97 def __new__(cls,*args,**kwds): 98 self = list.__new__(cls,*args,**kwds) 99 self._restart = 0 100 self._idx = 0 101 return self 102 103 @property 104 def next_value(self): 105 v = self[self._idx] 106 self._idx += 1 107 if self._idx>=len(self): 108 self._idx = self._restart 109 return v 110 111 @property 112 def peek(self): 113 return self[self._idx] 114 115class IndexingFlowable(Flowable): 116 """Abstract interface definition for flowables which might 117 hold references to other pages or themselves be targets 118 of cross-references. XRefStart, XRefDest, Table of Contents, 119 Indexes etc.""" 120 def isIndexing(self): 121 return 1 122 123 def isSatisfied(self): 124 return 1 125 126 def notify(self, kind, stuff): 127 """This will be called by the framework wherever 'stuff' happens. 128 'kind' will be a value that can be used to decide whether to 129 pay attention or not.""" 130 pass 131 132 def beforeBuild(self): 133 """Called by multiBuild before it starts; use this to clear 134 old contents""" 135 pass 136 137 def afterBuild(self): 138 """Called after build ends but before isSatisfied""" 139 pass 140 141class ActionFlowable(Flowable): 142 '''This Flowable is never drawn, it can be used for data driven controls 143 For example to change a page template (from one column to two, for example) 144 use NextPageTemplate which creates an ActionFlowable. 145 ''' 146 def __init__(self,action=()): 147 #must call super init to ensure it has a width and height (of zero), 148 #as in some cases the packer might get called on it... 149 Flowable.__init__(self) 150 if not isSeq(action): 151 action = (action,) 152 self.action = tuple(action) 153 154 def apply(self,doc): 155 ''' 156 This is called by the doc.build processing to allow the instance to 157 implement its behaviour 158 ''' 159 action = self.action[0] 160 args = tuple(self.action[1:]) 161 arn = 'handle_'+action 162 if arn=="handle_nextPageTemplate" and args[0]=='main': 163 pass 164 try: 165 getattr(doc,arn)(*args) 166 except AttributeError as aerr: 167 if aerr.args[0]==arn: 168 raise NotImplementedError("Can't handle ActionFlowable(%s)" % action) 169 else: 170 raise 171 except: 172 annotateException("\nhandle_%s args=%s"%(action,ascii(args))) 173 174 def __call__(self): 175 return self 176 177 def identity(self, maxLen=None): 178 return "ActionFlowable: %s%s" % (str(self.action),self._frameName()) 179 180class NullActionFlowable(ActionFlowable): 181 '''an ActionFlowable that does nothing''' 182 def apply(self,doc): 183 pass 184 185class LCActionFlowable(ActionFlowable): 186 locChanger = 1 #we cause a frame or page change 187 188 def wrap(self, availWidth, availHeight): 189 '''Should never be called.''' 190 raise NotImplementedError('%s.wrap should never be called' % self.__class__.__name__) 191 192 def draw(self): 193 '''Should never be called.''' 194 raise NotImplementedError('%s.draw should never be called' % self.__class__.__name__) 195 196class NextFrameFlowable(ActionFlowable): 197 locChanger = 1 #we cause a frame or page change 198 def __init__(self,ix,resume=0): 199 ActionFlowable.__init__(self,('nextFrame',ix,resume)) 200 201class CurrentFrameFlowable(LCActionFlowable): 202 def __init__(self,ix,resume=0): 203 ActionFlowable.__init__(self,('currentFrame',ix,resume)) 204 205class NullActionFlowable(ActionFlowable): 206 def apply(self,doc): 207 pass 208 209class _FrameBreak(LCActionFlowable): 210 ''' 211 A special ActionFlowable that allows setting doc._nextFrameIndex 212 213 eg story.append(FrameBreak('mySpecialFrame')) 214 ''' 215 def __call__(self,ix=None,resume=0): 216 r = self.__class__(self.action+(resume,)) 217 r._ix = ix 218 return r 219 220 def apply(self,doc): 221 if getattr(self,'_ix',None): 222 doc.handle_nextFrame(self._ix) 223 ActionFlowable.apply(self,doc) 224 225FrameBreak = _FrameBreak('frameEnd') 226PageBegin = LCActionFlowable('pageBegin') 227 228def _evalMeasurement(n): 229 if isinstance(n,str): 230 from reportlab.platypus.paraparser import _num 231 n = _num(n) 232 if isSeq(n): n = n[1] 233 return n 234 235class FrameActionFlowable(Flowable): 236 _fixedWidth = _fixedHeight = 1 237 def __init__(self,*arg,**kw): 238 raise NotImplementedError('%s.__init__ should never be called for abstract Class'%self.__class__.__name__) 239 240 def frameAction(self,frame): 241 raise NotImplementedError('%s.frameAction should never be called for abstract Class'%self.__class__.__name__) 242 243class Indenter(FrameActionFlowable): 244 """Increases or decreases left and right margins of frame. 245 246 This allows one to have a 'context-sensitive' indentation 247 and makes nested lists way easier. 248 """ 249 _ZEROSIZE=True 250 width=0 251 height=0 252 def __init__(self, left=0, right=0): 253 self.left = _evalMeasurement(left) 254 self.right = _evalMeasurement(right) 255 256 def frameAction(self, frame): 257 frame._leftExtraIndent += self.left 258 frame._rightExtraIndent += self.right 259 260class NotAtTopPageBreak(FrameActionFlowable): 261 locChanger = 1 #we cause a frame or page change 262 def __init__(self,nextTemplate=None): 263 self.nextTemplate = nextTemplate 264 265 def frameAction(self,frame): 266 if not frame._atTop: 267 frame.add_generated_content(PageBreak(nextTemplate=self.nextTemplate)) 268 269class NextPageTemplate(ActionFlowable): 270 locChanger = 1 #we cause a frame or page change 271 """When you get to the next page, use the template specified (change to two column, for example) """ 272 def __init__(self,pt): 273 ActionFlowable.__init__(self,('nextPageTemplate',pt)) 274 275class PageTemplate: 276 """ 277 essentially a list of Frames and an onPage routine to call at the start 278 of a page when this is selected. onPageEnd gets called at the end. 279 derived classes can also implement beforeDrawPage and afterDrawPage if they want 280 """ 281 def __init__(self,id=None,frames=[],onPage=_doNothing, onPageEnd=_doNothing, 282 pagesize=None, autoNextPageTemplate=None, 283 cropBox=None, 284 artBox=None, 285 trimBox=None, 286 bleedBox=None, 287 ): 288 frames = frames or [] 289 if not isSeq(frames): frames = [frames] 290 assert [x for x in frames if not isinstance(x,Frame)]==[], "frames argument error" 291 self.id = id 292 self.frames = frames 293 self.onPage = onPage 294 self.onPageEnd = onPageEnd 295 self.pagesize = pagesize 296 self.autoNextPageTemplate = autoNextPageTemplate 297 self.cropBox = cropBox 298 self.artBox = artBox 299 self.trimBox = trimBox 300 self.bleedBox = bleedBox 301 302 def beforeDrawPage(self,canv,doc): 303 """Override this if you want additional functionality or prefer 304 a class based page routine. Called before any flowables for 305 this page are processed.""" 306 pass 307 308 def checkPageSize(self,canv,doc): 309 """This gets called by the template framework 310 If canv size != template size then the canv size is set to 311 the template size or if that's not available to the 312 doc size. 313 """ 314 #### NEVER EVER EVER COMPARE FLOATS FOR EQUALITY 315 #RGB converting pagesizes to ints means we are accurate to one point 316 #RGB I suggest we should be aiming a little better 317 cp = None 318 dp = None 319 sp = None 320 if canv._pagesize: cp = list(map(int, canv._pagesize)) 321 if self.pagesize: sp = list(map(int, self.pagesize)) 322 if doc.pagesize: dp = list(map(int, doc.pagesize)) 323 if cp!=sp: 324 if sp: 325 canv.setPageSize(self.pagesize) 326 elif cp!=dp: 327 canv.setPageSize(doc.pagesize) 328 for box in 'crop','art','trim','bleed': 329 size = getattr(self,box+'Box',None) 330 if size: 331 canv.setCropBox(size,name=box) 332 333 def afterDrawPage(self, canv, doc): 334 """This is called after the last flowable for the page has 335 been processed. You might use this if the page header or 336 footer needed knowledge of what flowables were drawn on 337 this page.""" 338 pass 339 340def _addGeneratedContent(flowables,frame): 341 S = getattr(frame,'_generated_content',None) 342 if S: 343 flowables[0:0] = S 344 del frame._generated_content 345 346class onDrawStr(str): 347 def __new__(cls,value,onDraw,label,kind=None): 348 self = str.__new__(cls,value) 349 self.onDraw = onDraw 350 self.kind = kind 351 self.label = label 352 return self 353 354class PageAccumulator: 355 '''gadget to accumulate information in a page 356 and then allow it to be interrogated at the end 357 of the page''' 358 _count = 0 359 def __init__(self,name=None): 360 if name is None: 361 name = self.__class__.__name__+str(self.__class__._count) 362 self.__class__._count += 1 363 self.name = name 364 self.data = [] 365 366 def reset(self): 367 self.data[:] = [] 368 369 def add(self,*args): 370 self.data.append(args) 371 372 def onDrawText(self,*args): 373 return '<onDraw name="%s" label="%s" />' % (self.name,encode_label(args)) 374 375 def __call__(self,canv,kind,label): 376 self.add(*decode_label(label)) 377 378 def attachToPageTemplate(self,pt): 379 if pt.onPage: 380 def onPage(canv,doc,oop=pt.onPage): 381 self.onPage(canv,doc) 382 oop(canv,doc) 383 else: 384 def onPage(canv,doc): 385 self.onPage(canv,doc) 386 pt.onPage = onPage 387 if pt.onPageEnd: 388 def onPageEnd(canv,doc,oop=pt.onPageEnd): 389 self.onPageEnd(canv,doc) 390 oop(canv,doc) 391 else: 392 def onPageEnd(canv,doc): 393 self.onPageEnd(canv,doc) 394 pt.onPageEnd = onPageEnd 395 396 def onPage(self,canv,doc): 397 '''this will be called at the start of the page''' 398 setattr(canv,self.name,self) #push ourselves onto the canvas 399 self.reset() 400 401 def onPageEnd(self,canv,doc): 402 '''this will be called at the end of a page''' 403 self.pageEndAction(canv,doc) 404 try: 405 delattr(canv,self.name) 406 except: 407 pass 408 self.reset() 409 410 def pageEndAction(self,canv,doc): 411 '''this should be overridden to do something useful''' 412 pass 413 414 def onDrawStr(self,value,*args): 415 return onDrawStr(value,self,encode_label(args)) 416 417def _ktAllow(f): 418 '''return true if allowed in containers like KeepTogether''' 419 return not (isinstance(f,(_ContainerSpace,DocIf,DocWhile)) or getattr(f,'locChanger',False)) 420 421class BaseDocTemplate: 422 """ 423 First attempt at defining a document template class. 424 425 The basic idea is simple. 426 427 1) The document has a list of data associated with it 428 this data should derive from flowables. We'll have 429 special classes like PageBreak, FrameBreak to do things 430 like forcing a page end etc. 431 432 2) The document has one or more page templates. 433 434 3) Each page template has one or more frames. 435 436 4) The document class provides base methods for handling the 437 story events and some reasonable methods for getting the 438 story flowables into the frames. 439 440 5) The document instances can override the base handler routines. 441 442 Most of the methods for this class are not called directly by the user, 443 but in some advanced usages they may need to be overridden via subclassing. 444 445 EXCEPTION: doctemplate.build(...) must be called for most reasonable uses 446 since it builds a document using the page template. 447 448 Each document template builds exactly one document into a file specified 449 by the filename argument on initialization. 450 451 Possible keyword arguments for the initialization: 452 453 - pageTemplates: A list of templates. Must be nonempty. Names 454 assigned to the templates are used for referring to them so no two used 455 templates should have the same name. For example you might want one template 456 for a title page, one for a section first page, one for a first page of 457 a chapter and two more for the interior of a chapter on odd and even pages. 458 If this argument is omitted then at least one pageTemplate should be provided 459 using the addPageTemplates method before the document is built. 460 - pageSize: a 2-tuple or a size constant from reportlab/lib/pagesizes.pu. 461 Used by the SimpleDocTemplate subclass which does NOT accept a list of 462 pageTemplates but makes one for you; ignored when using pageTemplates. 463 464 - showBoundary: if set draw a box around the frame boundaries. 465 - leftMargin: 466 - rightMargin: 467 - topMargin: 468 - bottomMargin: Margin sizes in points (default 1 inch). These margins may be 469 overridden by the pageTemplates. They are primarily of interest for the 470 SimpleDocumentTemplate subclass. 471 472 - allowSplitting: If set flowables (eg, paragraphs) may be split across frames or pages 473 (default: 1) 474 - title: Internal title for document (does not automatically display on any page) 475 - author: Internal author for document (does not automatically display on any page) 476 """ 477 _initArgs = { 'pagesize':defaultPageSize, 478 'pageTemplates':[], 479 'showBoundary':0, 480 'leftMargin':inch, 481 'rightMargin':inch, 482 'topMargin':inch, 483 'bottomMargin':inch, 484 'allowSplitting':1, 485 'title':None, 486 'author':None, 487 'subject':None, 488 'creator':None, 489 'producer':None, 490 'keywords':[], 491 'invariant':None, 492 'pageCompression':None, 493 '_pageBreakQuick':1, 494 'rotation':0, 495 '_debug':0, 496 'encrypt': None, 497 'cropMarks': None, 498 'enforceColorSpace': None, 499 'displayDocTitle': None, 500 'lang': None, 501 'initialFontName': None, 502 'initialFontSize': None, 503 'initialLeading': None, 504 'cropBox': None, 505 'artBox': None, 506 'trimBox': None, 507 'bleedBox': None, 508 'keepTogetherClass': KeepTogether, 509 'hideToolbar': None, 510 'hideMenubar': None, 511 'hideWindowUI': None, 512 'fitWindow': None, 513 'centerWindow': None, 514 'nonFullScreenPageMode': None, 515 'direction': None, 516 'viewArea': None, 517 'viewClip': None, 518 'printArea': None, 519 'printClip': None, 520 'printScaling': None, 521 'duplex': None, 522 } 523 _invalidInitArgs = () 524 _firstPageTemplateIndex = 0 525 526 def __init__(self, filename, **kw): 527 """create a document template bound to a filename (see class documentation for keyword arguments)""" 528 self.filename = filename 529 self._nameSpace = dict(doc=self) 530 self._lifetimes = {} 531 532 for k in self._initArgs.keys(): 533 if k not in kw: 534 v = self._initArgs[k] 535 else: 536 if k in self._invalidInitArgs: 537 raise ValueError("Invalid argument %s" % k) 538 v = kw[k] 539 setattr(self,k,v) 540 541 p = self.pageTemplates 542 self.pageTemplates = [] 543 self.addPageTemplates(p) 544 545 # facility to assist multi-build and cross-referencing. 546 # various hooks can put things into here - key is what 547 # you want, value is a page number. This can then be 548 # passed to indexing flowables. 549 self._pageRefs = {} 550 self._indexingFlowables = [] 551 552 #callback facility for progress monitoring 553 self._onPage = None 554 self._onProgress = None 555 self._flowableCount = 0 # so we know how far to go 556 557 #infinite loop detection if we start doing lots of empty pages 558 self._curPageFlowableCount = 0 559 self._emptyPages = 0 560 self._emptyPagesAllowed = 10 561 562 #context sensitive margins - set by story, not from outside 563 self._leftExtraIndent = 0.0 564 self._rightExtraIndent = 0.0 565 self._topFlowables = [] 566 self._pageTopFlowables = [] 567 self._frameBGs = [] 568 569 self._calc() 570 self.afterInit() 571 572 def _calc(self): 573 self._rightMargin = self.pagesize[0] - self.rightMargin 574 self._topMargin = self.pagesize[1] - self.topMargin 575 self.width = self._rightMargin - self.leftMargin 576 self.height = self._topMargin - self.bottomMargin 577 578 def setPageCallBack(self, func): 579 'Simple progress monitor - func(pageNo) called on each new page' 580 self._onPage = func 581 582 def setProgressCallBack(self, func): 583 '''Cleverer progress monitor - func(typ, value) called regularly''' 584 self._onProgress = func 585 586 def clean_hanging(self): 587 'handle internal postponed actions' 588 while len(self._hanging): 589 self.handle_flowable(self._hanging) 590 591 def addPageTemplates(self,pageTemplates): 592 'add one or a sequence of pageTemplates' 593 if not isSeq(pageTemplates): 594 pageTemplates = [pageTemplates] 595 #this test below fails due to inconsistent imports! 596 #assert filter(lambda x: not isinstance(x,PageTemplate), pageTemplates)==[], "pageTemplates argument error" 597 for t in pageTemplates: 598 self.pageTemplates.append(t) 599 600 def handle_documentBegin(self): 601 '''implement actions at beginning of document''' 602 self._hanging = [PageBegin] 603 if isinstance(self._firstPageTemplateIndex,list): 604 self.handle_nextPageTemplate(self._firstPageTemplateIndex) 605 self._setPageTemplate() 606 else: 607 self.pageTemplate = self.pageTemplates[self._firstPageTemplateIndex] 608 self.page = 0 609 self.beforeDocument() 610 611 def handle_pageBegin(self): 612 """Perform actions required at beginning of page. 613 shouldn't normally be called directly""" 614 self.page += 1 615 if self._debug: logger.debug("beginning page %d" % self.page) 616 self.pageTemplate.beforeDrawPage(self.canv,self) 617 self.pageTemplate.checkPageSize(self.canv,self) 618 self.pageTemplate.onPage(self.canv,self) 619 for f in self.pageTemplate.frames: f._reset() 620 self.beforePage() 621 #keep a count of flowables added to this page. zero indicates bad stuff 622 self._curPageFlowableCount = 0 623 if hasattr(self,'_nextFrameIndex'): 624 del self._nextFrameIndex 625 self.frame = self.pageTemplate.frames[0] 626 self.frame._debug = self._debug 627 self.handle_frameBegin(pageTopFlowables=self._pageTopFlowables) 628 629 def _setPageTemplate(self): 630 if hasattr(self,'_nextPageTemplateCycle'): 631 #they are cycling through pages'; we keep the index 632 self.pageTemplate = self._nextPageTemplateCycle.next_value 633 elif hasattr(self,'_nextPageTemplateIndex'): 634 self.pageTemplate = self.pageTemplates[self._nextPageTemplateIndex] 635 del self._nextPageTemplateIndex 636 elif self.pageTemplate.autoNextPageTemplate: 637 self.handle_nextPageTemplate(self.pageTemplate.autoNextPageTemplate) 638 self.pageTemplate = self.pageTemplates[self._nextPageTemplateIndex] 639 640 def _samePT(self,npt): 641 if isSeq(npt): 642 return getattr(self,'_nextPageTemplateCycle',[]) 643 if isinstance(npt,strTypes): 644 return npt == (self.pageTemplates[self._nextPageTemplateIndex].id if hasattr(self,'_nextPageTemplateIndex') else self.pageTemplate.id) 645 if isinstance(npt,int) and 0<=npt<len(self.pageTemplates): 646 if hasattr(self,'_nextPageTemplateIndex'): 647 return npt==self._nextPageTemplateIndex 648 return npt==self.pageTemplates.find(self.pageTemplate) 649 650 def handle_pageEnd(self): 651 ''' show the current page 652 check the next page template 653 hang a page begin 654 ''' 655 self._removeVars(('page','frame')) 656 self._leftExtraIndent = self.frame._leftExtraIndent 657 self._rightExtraIndent = self.frame._rightExtraIndent 658 self._frameBGs = self.frame._frameBGs 659 #detect infinite loops... 660 if self._curPageFlowableCount == 0: 661 self._emptyPages += 1 662 else: 663 self._emptyPages = 0 664 if self._emptyPages >= self._emptyPagesAllowed: 665 if 1: 666 ident = "More than %d pages generated without content - halting layout. Likely that a flowable is too large for any frame." % self._emptyPagesAllowed 667 #leave to keep apart from the raise 668 raise LayoutError(ident) 669 else: 670 pass #attempt to restore to good state 671 else: 672 if self._onProgress: 673 self._onProgress('PAGE', self.canv.getPageNumber()) 674 self.pageTemplate.afterDrawPage(self.canv, self) 675 self.pageTemplate.onPageEnd(self.canv, self) 676 self.afterPage() 677 if self._debug: logger.debug("ending page %d" % self.page) 678 self.canv.setPageRotation(getattr(self.pageTemplate,'rotation',self.rotation)) 679 self.canv.showPage() 680 self._setPageTemplate() 681 if self._emptyPages==0: 682 pass #store good state here 683 self._hanging.append(PageBegin) 684 685 def handle_pageBreak(self,slow=None): 686 '''some might choose not to end all the frames''' 687 if self._pageBreakQuick and not slow: 688 self.handle_pageEnd() 689 else: 690 n = len(self._hanging) 691 while len(self._hanging)==n: 692 self.handle_frameEnd() 693 694 def handle_frameBegin(self,resume=0,pageTopFlowables=None): 695 '''What to do at the beginning of a frame''' 696 f = self.frame 697 if f._atTop: 698 boundary = self.frame.showBoundary or self.showBoundary 699 if boundary: 700 self.frame.drawBoundary(self.canv,boundary) 701 f._leftExtraIndent = self._leftExtraIndent 702 f._rightExtraIndent = self._rightExtraIndent 703 f._frameBGs = self._frameBGs 704 if pageTopFlowables: 705 self._hanging.extend(pageTopFlowables) 706 if self._topFlowables: 707 self._hanging.extend(self._topFlowables) 708 709 def handle_frameEnd(self,resume=0): 710 ''' Handles the semantics of the end of a frame. This includes the selection of 711 the next frame or if this is the last frame then invoke pageEnd. 712 ''' 713 self._removeVars(('frame',)) 714 self._leftExtraIndent = self.frame._leftExtraIndent 715 self._rightExtraIndent = self.frame._rightExtraIndent 716 self._frameBGs = self.frame._frameBGs 717 718 if hasattr(self,'_nextFrameIndex'): 719 self.frame = self.pageTemplate.frames[self._nextFrameIndex] 720 self.frame._debug = self._debug 721 del self._nextFrameIndex 722 self.handle_frameBegin(resume) 723 else: 724 f = self.frame 725 if hasattr(f,'lastFrame') or f is self.pageTemplate.frames[-1]: 726 self.handle_pageEnd() 727 self.frame = None 728 else: 729 self.frame = self.pageTemplate.frames[self.pageTemplate.frames.index(f) + 1] 730 self.frame._debug = self._debug 731 self.handle_frameBegin() 732 733 def handle_nextPageTemplate(self,pt): 734 '''On endPage change to the page template with name or index pt''' 735 if isinstance(pt,strTypes): 736 if hasattr(self, '_nextPageTemplateCycle'): del self._nextPageTemplateCycle 737 for t in self.pageTemplates: 738 if t.id == pt: 739 self._nextPageTemplateIndex = self.pageTemplates.index(t) 740 return 741 raise ValueError("can't find template('%s')"%pt) 742 elif isinstance(pt,int): 743 if hasattr(self, '_nextPageTemplateCycle'): del self._nextPageTemplateCycle 744 self._nextPageTemplateIndex = pt 745 elif isSeq(pt): 746 #used for alternating left/right pages 747 #collect the refs to the template objects, complain if any are bad 748 c = PTCycle() 749 for ptn in pt: 750 found = 0 751 if ptn=='*': #special case name used to short circuit the iteration 752 c._restart = len(c) 753 continue 754 for t in self.pageTemplates: 755 if t.id == ptn: 756 c.append(t) 757 found = 1 758 if not found: 759 raise ValueError("Cannot find page template called %s" % ptn) 760 if not c: 761 raise ValueError("No valid page templates in cycle") 762 elif c._restart>len(c): 763 raise ValueError("Invalid cycle restart position") 764 765 #ensure we start on the first one 766 self._nextPageTemplateCycle = c 767 else: 768 raise TypeError("argument pt should be string or integer or list") 769 770 def _peekNextPageTemplate(self,pt): 771 if isinstance(pt,strTypes): 772 for t in self.pageTemplates: 773 if t.id == pt: 774 return t 775 raise ValueError("can't find template('%s')"%pt) 776 elif isinstance(pt,int): 777 self.pageTemplates[pt] 778 elif isSeq(pt): 779 #used for alternating left/right pages 780 #collect the refs to the template objects, complain if any are bad 781 c = PTCycle() 782 for ptn in pt: 783 found = 0 784 if ptn=='*': #special case name used to short circuit the iteration 785 c._restart = len(c) 786 continue 787 for t in self.pageTemplates: 788 if t.id == ptn: 789 c.append(t) 790 found = 1 791 if not found: 792 raise ValueError("Cannot find page template called %s" % ptn) 793 if not c: 794 raise ValueError("No valid page templates in cycle") 795 elif c._restart>len(c): 796 raise ValueError("Invalid cycle restart position") 797 return c.peek 798 else: 799 raise TypeError("argument pt should be string or integer or list") 800 801 def _peekNextFrame(self): 802 '''intended to be used by extreme flowables''' 803 if hasattr(self,'_nextFrameIndex'): 804 return self.pageTemplate.frames[self._nextFrameIndex] 805 f = self.frame 806 if hasattr(f,'lastFrame') or f is self.pageTemplate.frames[-1]: 807 if hasattr(self,'_nextPageTemplateCycle'): 808 #they are cycling through pages'; we keep the index 809 pageTemplate = self._nextPageTemplateCycle.peek 810 elif hasattr(self,'_nextPageTemplateIndex'): 811 pageTemplate = self.pageTemplates[self._nextPageTemplateIndex] 812 elif self.pageTemplate.autoNextPageTemplate: 813 pageTemplate = self._peekNextPageTemplate(self.pageTemplate.autoNextPageTemplate) 814 else: 815 pageTemplate = self.pageTemplate 816 return pageTemplate.frames[0] 817 else: 818 return self.pageTemplate.frames[self.pageTemplate.frames.index(f) + 1] 819 820 def handle_nextFrame(self,fx,resume=0): 821 '''On endFrame change to the frame with name or index fx''' 822 if isinstance(fx,strTypes): 823 for f in self.pageTemplate.frames: 824 if f.id == fx: 825 self._nextFrameIndex = self.pageTemplate.frames.index(f) 826 return 827 raise ValueError("can't find frame('%s') in %r(%s) which has frames %r"%(fx,self.pageTemplate,self.pageTemplate.id,[(f,f.id) for f in self.pageTemplate.frames])) 828 elif isinstance(fx,int): 829 self._nextFrameIndex = fx 830 else: 831 raise TypeError("argument fx should be string or integer") 832 833 def handle_currentFrame(self,fx,resume=0): 834 '''change to the frame with name or index fx''' 835 self.handle_nextFrame(fx,resume) 836 self.handle_frameEnd(resume) 837 838 def handle_breakBefore(self, flowables): 839 '''preprocessing step to allow pageBreakBefore and frameBreakBefore attributes''' 840 first = flowables[0] 841 # if we insert a page break before, we'll process that, see it again, 842 # and go in an infinite loop. So we need to set a flag on the object 843 # saying 'skip me'. This should be unset on the next pass 844 if hasattr(first, '_skipMeNextTime'): 845 delattr(first, '_skipMeNextTime') 846 return 847 # this could all be made much quicker by putting the attributes 848 # in to the flowables with a defult value of 0 849 if hasattr(first,'pageBreakBefore') and first.pageBreakBefore == 1: 850 first._skipMeNextTime = 1 851 first.insert(0, PageBreak()) 852 return 853 if hasattr(first,'style') and hasattr(first.style, 'pageBreakBefore') and first.style.pageBreakBefore == 1: 854 first._skipMeNextTime = 1 855 flowables.insert(0, PageBreak()) 856 return 857 if hasattr(first,'frameBreakBefore') and first.frameBreakBefore == 1: 858 first._skipMeNextTime = 1 859 flowables.insert(0, FrameBreak()) 860 return 861 if hasattr(first,'style') and hasattr(first.style, 'frameBreakBefore') and first.style.frameBreakBefore == 1: 862 first._skipMeNextTime = 1 863 flowables.insert(0, FrameBreak()) 864 return 865 866 def handle_keepWithNext(self, flowables): 867 "implements keepWithNext" 868 i = 0 869 n = len(flowables) 870 while i<n and flowables[i].getKeepWithNext() and _ktAllow(flowables[i]): i += 1 871 if i: 872 if i<n and _ktAllow(flowables[i]): i += 1 873 K = self.keepTogetherClass(flowables[:i]) 874 mbe = getattr(self,'_multiBuildEdits',None) 875 if mbe: 876 for f in K._content[:-1]: 877 if hasattr(f,'keepWithNext'): 878 mbe((setattr,f,'keepWithNext',f.keepWithNext)) 879 else: 880 mbe((delattr,f,'keepWithNext')) #must get it from a style 881 f.__dict__['keepWithNext'] = 0 882 else: 883 for f in K._content[:-1]: 884 f.__dict__['keepWithNext'] = 0 885 del flowables[:i] 886 flowables.insert(0,K) 887 888 def _fIdent(self,f,maxLen=None,frame=None): 889 if frame: f._frame = frame 890 try: 891 return f.identity(maxLen) 892 finally: 893 if frame: del f._frame 894 895 def handle_flowable(self,flowables): 896 '''try to handle one flowable from the front of list flowables.''' 897 898 #allow document a chance to look at, modify or ignore 899 #the object(s) about to be processed 900 self.filterFlowables(flowables) 901 902 f = flowables[0] 903 if f: 904 self.handle_breakBefore(flowables) 905 self.handle_keepWithNext(flowables) 906 f = flowables[0] 907 del flowables[0] 908 if f is None: 909 return 910 911 if isinstance(f,PageBreak): 912 npt = f.nextTemplate 913 if npt and not self._samePT(npt): 914 npt=NextPageTemplate(npt) 915 npt.apply(self) 916 self.afterFlowable(npt) 917 if isinstance(f,SlowPageBreak): 918 self.handle_pageBreak(slow=1) 919 else: 920 self.handle_pageBreak() 921 self.afterFlowable(f) 922 elif isinstance(f,ActionFlowable): 923 f.apply(self) 924 self.afterFlowable(f) 925 else: 926 frame = self.frame 927 canv = self.canv 928 #try to fit it then draw it 929 if frame.add(f, canv, trySplit=self.allowSplitting): 930 if not isinstance(f,FrameActionFlowable): 931 self._curPageFlowableCount += 1 932 self.afterFlowable(f) 933 _addGeneratedContent(flowables,frame) 934 else: 935 if self.allowSplitting: 936 # see if this is a splittable thing 937 S = frame.split(f,canv) 938 n = len(S) 939 else: 940 n = 0 941 if n: 942 if not isinstance(S[0],(PageBreak,SlowPageBreak,ActionFlowable,DDIndenter)): 943 if not frame.add(S[0], canv, trySplit=0): 944 ident = "Splitting error(n==%d) on page %d in\n%s\nS[0]=%s" % (n,self.page,self._fIdent(f,60,frame),self._fIdent(S[0],60,frame)) 945 #leave to keep apart from the raise 946 raise LayoutError(ident) 947 self._curPageFlowableCount += 1 948 self.afterFlowable(S[0]) 949 flowables[0:0] = S[1:] # put rest of splitted flowables back on the list 950 _addGeneratedContent(flowables,frame) 951 else: 952 flowables[0:0] = S # put split flowables back on the list 953 else: 954 if hasattr(f,'_postponed'): 955 ident = "Flowable %s%s too large on page %d in frame %r%s of template %r" % \ 956 (self._fIdent(f,60,frame),_fSizeString(f),self.page, self.frame.id, 957 self.frame._aSpaceString(), self.pageTemplate.id) 958 #leave to keep apart from the raise 959 raise LayoutError(ident) 960 # this ought to be cleared when they are finally drawn! 961 f._postponed = 1 962 mbe = getattr(self,'_multiBuildEdits',None) 963 if mbe: 964 mbe((delattr,f,'_postponed')) 965 flowables.insert(0,f) # put the flowable back 966 self.handle_frameEnd() 967 968 #these are provided so that deriving classes can refer to them 969 _handle_documentBegin = handle_documentBegin 970 _handle_pageBegin = handle_pageBegin 971 _handle_pageEnd = handle_pageEnd 972 _handle_frameBegin = handle_frameBegin 973 _handle_frameEnd = handle_frameEnd 974 _handle_flowable = handle_flowable 975 _handle_nextPageTemplate = handle_nextPageTemplate 976 _handle_currentFrame = handle_currentFrame 977 _handle_nextFrame = handle_nextFrame 978 979 def _makeCanvas(self, filename=None, canvasmaker=canvas.Canvas): 980 '''make and return a sample canvas. As suggested by 981 Chris Jerdonek cjerdonek @ bitbucket this allows testing of stringWidths 982 etc. 983 984 *NB* only the canvases created in self._startBuild will actually be used 985 in the build process. 986 ''' 987 #each distinct pass gets a sequencer 988 self.seq = reportlab.lib.sequencer.Sequencer() 989 canv = canvasmaker(filename or self.filename, 990 pagesize=self.pagesize, 991 invariant=self.invariant, 992 pageCompression=self.pageCompression, 993 enforceColorSpace=self.enforceColorSpace, 994 initialFontName = self.initialFontName, 995 initialFontSize = self.initialFontSize, 996 initialLeading = self.initialLeading, 997 cropBox = self.cropBox, 998 artBox = self.artBox, 999 trimBox = self.trimBox, 1000 bleedBox = self.bleedBox, 1001 lang = self.lang, 1002 ) 1003 1004 getattr(canv,'setEncrypt',lambda x: None)(self.encrypt) 1005 1006 canv._cropMarks = self.cropMarks 1007 canv.setAuthor(self.author) 1008 canv.setTitle(self.title) 1009 canv.setSubject(self.subject) 1010 canv.setCreator(self.creator) 1011 canv.setProducer(self.producer) 1012 canv.setKeywords(self.keywords) 1013 from reportlab.pdfbase.pdfdoc import ( 1014 ViewerPreferencesPDFDictionary as VPD, checkPDFBoolean as cPDFB, 1015 ) 1016 for k,vf in VPD.validate.items(): 1017 v = getattr(self,k[0].lower()+k[1:],None) 1018 if v is not None: 1019 if vf is cPDFB: 1020 v = ['false','true'][v] #convert to pdf form of boolean 1021 canv.setViewerPreference(k,v) 1022 1023 if self._onPage: 1024 canv.setPageCallBack(self._onPage) 1025 return canv 1026 1027 def _startBuild(self, filename=None, canvasmaker=canvas.Canvas): 1028 self._calc() 1029 self.canv = self._makeCanvas(filename=filename,canvasmaker=canvasmaker) 1030 self.handle_documentBegin() 1031 1032 def _endBuild(self): 1033 self._removeVars(('build','page','frame')) 1034 if self._hanging!=[] and self._hanging[-1] is PageBegin: 1035 del self._hanging[-1] 1036 self.clean_hanging() 1037 else: 1038 self.clean_hanging() 1039 self.handle_pageBreak() 1040 1041 if getattr(self,'_doSave',1): self.canv.save() 1042 if self._onPage: self.canv.setPageCallBack(None) 1043 1044 def build(self, flowables, filename=None, canvasmaker=canvas.Canvas): 1045 """Build the document from a list of flowables. 1046 If the filename argument is provided then that filename is used 1047 rather than the one provided upon initialization. 1048 If the canvasmaker argument is provided then it will be used 1049 instead of the default. For example a slideshow might use 1050 an alternate canvas which places 6 slides on a page (by 1051 doing translations, scalings and redefining the page break 1052 operations). 1053 """ 1054 #assert filter(lambda x: not isinstance(x,Flowable), flowables)==[], "flowables argument error" 1055 flowableCount = len(flowables) 1056 if self._onProgress: 1057 self._onProgress('STARTED',0) 1058 self._onProgress('SIZE_EST', len(flowables)) 1059 self._startBuild(filename,canvasmaker) 1060 1061 #pagecatcher can drag in information from embedded PDFs and we want ours 1062 #to take priority, so cache and reapply our own info dictionary after the build. 1063 canv = self.canv 1064 self._savedInfo = canv._doc.info 1065 handled = 0 1066 1067 try: 1068 canv._doctemplate = self 1069 while len(flowables): 1070 if self._hanging and self._hanging[-1] is PageBegin and isinstance(flowables[0],PageBreakIfNotEmpty): 1071 npt = flowables[0].nextTemplate 1072 if npt and not self._samePT(npt): 1073 npt=NextPageTemplate(npt) 1074 npt.apply(self) 1075 self._setPageTemplate() 1076 del flowables[0] 1077 self.clean_hanging() 1078 try: 1079 first = flowables[0] 1080 self.handle_flowable(flowables) 1081 handled += 1 1082 except: 1083 #if it has trace info, add it to the traceback message. 1084 if hasattr(first, '_traceInfo') and first._traceInfo: 1085 exc = sys.exc_info()[1] 1086 args = list(exc.args) 1087 tr = first._traceInfo 1088 args[0] += '\n(srcFile %s, line %d char %d to line %d char %d)' % ( 1089 tr.srcFile, 1090 tr.startLineNo, 1091 tr.startLinePos, 1092 tr.endLineNo, 1093 tr.endLinePos 1094 ) 1095 exc.args = tuple(args) 1096 raise 1097 if self._onProgress: 1098 self._onProgress('PROGRESS',flowableCount - len(flowables)) 1099 finally: 1100 del canv._doctemplate 1101 1102 1103 #reapply pagecatcher info 1104 canv._doc.info = self._savedInfo 1105 1106 self._endBuild() 1107 if self._onProgress: 1108 self._onProgress('FINISHED',0) 1109 1110 def _allSatisfied(self): 1111 """Called by multi-build - are all cross-references resolved?""" 1112 allHappy = 1 1113 for f in self._indexingFlowables: 1114 if not f.isSatisfied(): 1115 allHappy = 0 1116 break 1117 return allHappy 1118 1119 def notify(self, kind, stuff): 1120 """Forward to any listeners""" 1121 for l in self._indexingFlowables: 1122 _canv = getattr(l,'_canv',self) 1123 try: 1124 if _canv==self: 1125 l._canv = self.canv 1126 l.notify(kind, stuff) 1127 finally: 1128 if _canv==self: 1129 del l._canv 1130 1131 def pageRef(self, label): 1132 """hook to register a page number""" 1133 if verbose: print("pageRef called with label '%s' on page %d" % ( 1134 label, self.page)) 1135 self._pageRefs[label] = self.page 1136 1137 def multiBuild(self, story, 1138 maxPasses = 10, 1139 **buildKwds 1140 ): 1141 """Makes multiple passes until all indexing flowables 1142 are happy. 1143 1144 Returns number of passes""" 1145 self._indexingFlowables = [] 1146 #scan the story and keep a copy 1147 for thing in story: 1148 if thing.isIndexing(): 1149 self._indexingFlowables.append(thing) 1150 1151 #better fix for filename is a 'file' problem 1152 self._doSave = 0 1153 passes = 0 1154 mbe = [] 1155 self._multiBuildEdits = mbe.append 1156 while 1: 1157 passes += 1 1158 if self._onProgress: 1159 self._onProgress('PASS', passes) 1160 if verbose: sys.stdout.write('building pass '+str(passes) + '...') 1161 1162 for fl in self._indexingFlowables: 1163 fl.beforeBuild() 1164 1165 # work with a copy of the story, since it is consumed 1166 tempStory = story[:] 1167 self.build(tempStory, **buildKwds) 1168 #self.notify('debug',None) 1169 1170 for fl in self._indexingFlowables: 1171 fl.afterBuild() 1172 1173 happy = self._allSatisfied() 1174 1175 if happy: 1176 self._doSave = 0 1177 self.canv.save() 1178 break 1179 if passes > maxPasses: 1180 raise IndexError("Index entries not resolved after %d passes" % maxPasses) 1181 1182 #work through any edits 1183 while mbe: 1184 e = mbe.pop(0) 1185 e[0](*e[1:]) 1186 1187 del self._multiBuildEdits 1188 if verbose: print('saved') 1189 return passes 1190 1191 #these are pure virtuals override in derived classes 1192 #NB these get called at suitable places by the base class 1193 #so if you derive and override the handle_xxx methods 1194 #it's up to you to ensure that they maintain the needed consistency 1195 def afterInit(self): 1196 """This is called after initialisation of the base class.""" 1197 pass 1198 1199 def beforeDocument(self): 1200 """This is called before any processing is 1201 done on the document.""" 1202 pass 1203 1204 def beforePage(self): 1205 """This is called at the beginning of page 1206 processing, and immediately before the 1207 beforeDrawPage method of the current page 1208 template.""" 1209 pass 1210 1211 def afterPage(self): 1212 """This is called after page processing, and 1213 immediately after the afterDrawPage method 1214 of the current page template.""" 1215 pass 1216 1217 def filterFlowables(self,flowables): 1218 '''called to filter flowables at the start of the main handle_flowable method. 1219 Upon return if flowables[0] has been set to None it is discarded and the main 1220 method returns. 1221 ''' 1222 pass 1223 1224 def afterFlowable(self, flowable): 1225 '''called after a flowable has been rendered''' 1226 pass 1227 1228 _allowedLifetimes = 'page','frame','build','forever' 1229 def docAssign(self,var,expr,lifetime): 1230 if not isinstance(expr,strTypes): expr=str(expr) 1231 expr=expr.strip() 1232 var=var.strip() 1233 self.docExec('%s=(%s)'%(var.strip(),expr.strip()),lifetime) 1234 1235 def docExec(self,stmt,lifetime): 1236 stmt=stmt.strip() 1237 NS=self._nameSpace 1238 K0=list(NS.keys()) 1239 try: 1240 if lifetime not in self._allowedLifetimes: 1241 raise ValueError('bad lifetime %r not in %r'%(lifetime,self._allowedLifetimes)) 1242 exec(stmt, NS) 1243 except: 1244 K1 = [k for k in NS if k not in K0] #the added keys we need to delete 1245 for k in K1: 1246 del NS[k] 1247 annotateException('\ndocExec %s lifetime=%r failed!\n' % (stmt,lifetime)) 1248 self._addVars([k for k in NS.keys() if k not in K0],lifetime) 1249 1250 def _addVars(self,vars,lifetime): 1251 '''add namespace variables to lifetimes lists''' 1252 LT=self._lifetimes 1253 for var in vars: 1254 for v in LT.values(): 1255 if var in v: 1256 v.remove(var) 1257 LT.setdefault(lifetime,set([])).add(var) 1258 1259 def _removeVars(self,lifetimes): 1260 '''remove namespace variables for with lifetime in lifetimes''' 1261 LT=self._lifetimes 1262 NS=self._nameSpace 1263 for lifetime in lifetimes: 1264 for k in LT.setdefault(lifetime,[]): 1265 try: 1266 del NS[k] 1267 except KeyError: 1268 pass 1269 del LT[lifetime] 1270 1271 def docEval(self,expr): 1272 try: 1273 return eval(expr.strip(),{},self._nameSpace) 1274 except: 1275 annotateException('\ndocEval %s failed!\n' % expr) 1276 1277class SimpleDocTemplate(BaseDocTemplate): 1278 """A special case document template that will handle many simple documents. 1279 See documentation for BaseDocTemplate. No pageTemplates are required 1280 for this special case. A page templates are inferred from the 1281 margin information and the onFirstPage, onLaterPages arguments to the build method. 1282 1283 A document which has all pages with the same look except for the first 1284 page may can be built using this special approach. 1285 """ 1286 _invalidInitArgs = ('pageTemplates',) 1287 1288 def handle_pageBegin(self): 1289 '''override base method to add a change of page template after the firstpage. 1290 ''' 1291 self._handle_pageBegin() 1292 self._handle_nextPageTemplate('Later') 1293 1294 def build(self,flowables,onFirstPage=_doNothing, onLaterPages=_doNothing, canvasmaker=canvas.Canvas): 1295 """build the document using the flowables. Annotate the first page using the onFirstPage 1296 function and later pages using the onLaterPages function. The onXXX pages should follow 1297 the signature 1298 1299 def myOnFirstPage(canvas, document): 1300 # do annotations and modify the document 1301 ... 1302 1303 The functions can do things like draw logos, page numbers, 1304 footers, etcetera. They can use external variables to vary 1305 the look (for example providing page numbering or section names). 1306 """ 1307 self._calc() #in case we changed margins sizes etc 1308 frameT = Frame(self.leftMargin, self.bottomMargin, self.width, self.height, id='normal') 1309 self.addPageTemplates([PageTemplate(id='First',frames=frameT, onPage=onFirstPage,pagesize=self.pagesize), 1310 PageTemplate(id='Later',frames=frameT, onPage=onLaterPages,pagesize=self.pagesize)]) 1311 if onFirstPage is _doNothing and hasattr(self,'onFirstPage'): 1312 self.pageTemplates[0].beforeDrawPage = self.onFirstPage 1313 if onLaterPages is _doNothing and hasattr(self,'onLaterPages'): 1314 self.pageTemplates[1].beforeDrawPage = self.onLaterPages 1315 BaseDocTemplate.build(self,flowables, canvasmaker=canvasmaker) 1316 1317def progressCB(typ, value): 1318 """Example prototype for progress monitoring. 1319 1320 This aims to provide info about what is going on 1321 during a big job. It should enable, for example, a reasonably 1322 smooth progress bar to be drawn. We design the argument 1323 signature to be predictable and conducive to programming in 1324 other (type safe) languages. If set, this will be called 1325 repeatedly with pairs of values. The first is a string 1326 indicating the type of call; the second is a numeric value. 1327 1328 typ 'STARTING', value = 0 1329 typ 'SIZE_EST', value = numeric estimate of job size 1330 typ 'PASS', value = number of this rendering pass 1331 typ 'PROGRESS', value = number between 0 and SIZE_EST 1332 typ 'PAGE', value = page number of page 1333 type 'FINISHED', value = 0 1334 1335 The sequence is 1336 STARTING - always called once 1337 SIZE_EST - always called once 1338 PROGRESS - called often 1339 PAGE - called often when page is emitted 1340 FINISHED - called when really, really finished 1341 1342 some juggling is needed to accurately estimate numbers of 1343 pages in pageDrawing mode. 1344 1345 NOTE: the SIZE_EST is a guess. It is possible that the 1346 PROGRESS value may slightly exceed it, or may even step 1347 back a little on rare occasions. The only way to be 1348 really accurate would be to do two passes, and I don't 1349 want to take that performance hit. 1350 """ 1351 print('PROGRESS MONITOR: %-10s %d' % (typ, value)) 1352 1353if __name__ == '__main__': 1354 from reportlab.lib.styles import _baseFontName, _baseFontNameB 1355 def myFirstPage(canvas, doc): 1356 from reportlab.lib.colors import red 1357 PAGE_HEIGHT = canvas._pagesize[1] 1358 canvas.saveState() 1359 canvas.setStrokeColor(red) 1360 canvas.setLineWidth(5) 1361 canvas.line(66,72,66,PAGE_HEIGHT-72) 1362 canvas.setFont(_baseFontNameB,24) 1363 canvas.drawString(108, PAGE_HEIGHT-108, "TABLE OF CONTENTS DEMO") 1364 canvas.setFont(_baseFontName,12) 1365 canvas.drawString(4 * inch, 0.75 * inch, "First Page") 1366 canvas.restoreState() 1367 1368 def myLaterPages(canvas, doc): 1369 from reportlab.lib.colors import red 1370 PAGE_HEIGHT = canvas._pagesize[1] 1371 canvas.saveState() 1372 canvas.setStrokeColor(red) 1373 canvas.setLineWidth(5) 1374 canvas.line(66,72,66,PAGE_HEIGHT-72) 1375 canvas.setFont(_baseFontName,12) 1376 canvas.drawString(4 * inch, 0.75 * inch, "Page %d" % doc.page) 1377 canvas.restoreState() 1378 1379 def run(): 1380 objects_to_draw = [] 1381 from reportlab.lib.styles import ParagraphStyle 1382 #from paragraph import Paragraph 1383 from reportlab.platypus.doctemplate import SimpleDocTemplate 1384 1385 #need a style 1386 normal = ParagraphStyle('normal') 1387 normal.firstLineIndent = 18 1388 normal.spaceBefore = 6 1389 from reportlab.lib.randomtext import randomText 1390 import random 1391 for i in range(15): 1392 height = 0.5 + (2*random.random()) 1393 box = XBox(6 * inch, height * inch, 'Box Number %d' % i) 1394 objects_to_draw.append(box) 1395 para = Paragraph(randomText(), normal) 1396 objects_to_draw.append(para) 1397 1398 SimpleDocTemplate('doctemplate.pdf').build(objects_to_draw, 1399 onFirstPage=myFirstPage,onLaterPages=myLaterPages) 1400 1401 run() 1402