--- a/reportlab/platypus/doctemplate.py Thu Sep 22 16:53:18 2005 +0000
+++ b/reportlab/platypus/doctemplate.py Tue Sep 27 13:26:12 2005 +0000
@@ -29,10 +29,12 @@
"""
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
@@ -153,21 +155,26 @@
if type(n) is type(()): n = n[1]
return n
-class Indenter(ActionFlowable):
+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 apply(self, doc):
- doc.frame._leftExtraIndent = doc.frame._leftExtraIndent + self.left
- doc.frame._rightExtraIndent = doc.frame._rightExtraIndent + self.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) """
--- a/reportlab/platypus/flowables.py Thu Sep 22 16:53:18 2005 +0000
+++ b/reportlab/platypus/flowables.py Tue Sep 27 13:26:12 2005 +0000
@@ -29,13 +29,14 @@
from copy import deepcopy
from types import ListType, TupleType, StringType
-from reportlab.pdfgen import canvas
-from reportlab.lib.units import inch
from reportlab.lib.colors import red, gray, lightgrey
+from reportlab.lib.utils import fp_str
from reportlab.pdfbase import pdfutils
-from reportlab.rl_config import defaultPageSize, _FUZZ
-PAGE_HEIGHT = defaultPageSize[1]
+from reportlab.rl_config import _FUZZ, overlapAttachedSpace
+__all__=('TraceInfo','Flowable','XBox','Preformatted','Image','Spacer','PageBreak','SlowPageBreak',
+ 'CondPageBreak','KeepTogether','Macro','CallerMacro','ParagraphAndImage',
+ 'FailOnWrap','HRFlowable','PTOContainer','FrameFlowable')
class TraceInfo:
@@ -424,26 +425,28 @@
return (availWidth, availHeight)
return (0, 0)
-def _listWrapOn(F,availWidth,canv,mergeSpace=1):
+def _listWrapOn(F,availWidth,canv,mergeSpace=1,obj=None):
'''return max width, required height for a list of flowables F'''
W = 0
H = 0
pS = 0
- n = len(F)
- nm1 = n - 1
- for i in xrange(n):
- f = F[i]
+ atTop = 1
+ for f in F:
w,h = f.wrapOn(canv,availWidth,0xfffffff)
+ if w<=_FUZZ or h<=_FUZZ: continue
W = max(W,w)
- H = H+h
- if i:
+ H += h
+ if not atTop:
h = f.getSpaceBefore()
if mergeSpace: H += max(h-pS,0)
else: H += h
- if i!=nm1:
- pS = f.getSpaceAfter()
- H += pS
- return W, H
+ else:
+ if obj is not None: obj._spaceBefore = f.getSpaceBefore()
+ atTop = 0
+ pS = f.getSpaceAfter()
+ H += pS
+ if obj is not None: obj._spaceAfter = pS
+ return W, H-pS
def _flowableSublist(V):
"if it isn't a list or tuple, wrap it in a list"
@@ -611,7 +614,36 @@
self.trailer = _flowableSublist(trailer)
self.header = _flowableSublist(header)
-class PTOContainer(Flowable):
+class _Container: #Abstract some common container like behaviour
+ def getSpaceBefore(self):
+ for c in self._content:
+ if not hasattr(c,'frameAction'):
+ return c.getSpaceBefore()
+ return 0
+
+ def getSpaceAfter(self):
+ for c in reversed(self._content):
+ if not hasattr(c,'frameAction'):
+ return c.getSpaceAfter()
+ return 0
+
+ def drawOn(self, canv, x, y, _sW=0,scale=1.0):
+ '''we simulate being added to a frame'''
+ pS = 0
+ aW = scale*(self.width+_sW)
+ C = self._content
+ y += self.height*scale
+ for c in C:
+ w, h = c.wrapOn(canv,aW,0xfffffff)
+ if w<_FUZZ or h<_FUZZ: continue
+ if c is not C[0]: h += max(c.getSpaceBefore()-pS,0)
+ y -= h
+ c.drawOn(canv,x,y,_sW=aW-w)
+ if c is not C[-1]:
+ pS = c.getSpaceAfter()
+ y -= pS
+
+class PTOContainer(_Container,Flowable):
'''PTOContainer(contentList,trailerList,headerList)
A container for flowables decorated with trailer & header lists.
@@ -632,12 +664,6 @@
self.width, self.height = _listWrapOn(self._content,availWidth,self.canv)
return self.width,self.height
- def getSpaceBefore(self):
- return self._content[0].getSpaceBefore()
-
- def getSpaceAfter(self):
- return self._content[-1].getSpaceAfter()
-
def split(self, availWidth, availHeight):
canv = self.canv
C = self._content
@@ -685,17 +711,120 @@
R2 = Hdr + C[i:]
return R1 + [PTOContainer(R2,deepcopy(I.trailer),deepcopy(I.header))]
+#utility functions used by FrameFlowable
+def _hmodel(s0,s1,h0,h1):
+ # calculate the parameters in the model
+ # h = a/s**2 + b/s
+ a11 = 1./s0**2
+ a12 = 1./s0
+ a21 = 1./s1**2
+ a22 = 1./s1
+ det = a11*a22-a12*a21
+ b11 = a22/det
+ b12 = -a12/det
+ b21 = -a21/det
+ b22 = a11/det
+ a = b11*h0+b12*h1
+ b = b21*h0+b22*h1
+ return a,b
+
+def _qsolve(h,(a,b)):
+ '''solve the model v = a/s**2 + b/s for an s which gives us v==h'''
+ t = 0.5*b/a
+ from math import sqrt
+ f = -h/a
+ r = t*t-f
+ if r<0: return None
+ r = sqrt(r)
+ if t>=0:
+ s1 = -t - r
+ else:
+ s1 = -t + r
+ s2 = f/s1
+ return max(1./s1, 1./s2)
+
+class FrameFlowable(_Container,Flowable):
+ def __init__(self, maxWidth, maxHeight, content=[], mergeSpace=None, mode=0, id=None):
+ '''mode describes the action to take when overflowing
+ 0 raise an error in the normal way
+ 1 ignore ie just draw it and report maxWidth, maxHeight
+ 2 shrinkToFit
+ '''
+ self.id = id
+ self.maxWidth = maxWidth
+ self.maxHeight = maxHeight
+ self.mode = mode
+ assert mode in (0,1,2), '%s invalid mode value %s' % (self.identity(),mode)
+ if mergeSpace is None: mergeSpace = overlapAttachedSpace
+ self.mergespace = mergeSpace
+ self._content = content
+
+ def _getAvailableWidth(self):
+ return self.maxWidth - self._leftExtraIndent - self._rightExtraIndent
+
+ def identity(self, maxLen=None):
+ return "<%s at %d%s> size=%sx%s" % (self.__class__.__name__, id(self), self.id and ' id="%s"'%self.id, fp_str(self.maxWidth),fp_str(self.maxHeight))
+
+ def wrap(self,availWidth,availHeight):
+ mode = self.mode
+ maxWidth = float(self.maxWidth)
+ maxHeight = float(self.maxHeight)
+ W, H = _listWrapOn(self._content,availWidth,self.canv)
+ if mode==0 or (W<=maxWidth and H<=maxHeight):
+ self.width = W #we take what we get
+ self.height = H
+ elif mode==1: #we lie
+ self.width = min(maxWidth,W)-_FUZZ
+ self.height = min(maxHeight,H)-_FUZZ
+ else:
+ def func(x):
+ W, H = _listWrapOn(self._content,x*availWidth,self.canv)
+ W /= x
+ H /= x
+ return W, H
+ W0 = W
+ H0 = H
+ s0 = 1
+ if W>maxWidth:
+ #squeeze out the excess width
+ s1 = W/maxWidth
+ W, H = func(s1)
+ if H<=maxHeight:
+ self.width = W
+ self.height = H
+ self._scale = s1
+ return W,H
+ s0 = s1
+ H0 = H
+ W0 = W
+ s1 = H/maxHeight
+ W, H = func(s1)
+ self.width = W
+ self.height = H
+ self._scale = s1
+ if H<min(0.95*maxHeight,maxHeight-10):
+ #the standard case W should be OK, H is short we want
+ #to find the smallest s with H<=maxHeight
+ H1 = H
+ for f in 0, 0.01, 0.05, 0.10, 0.15:
+ #apply the quadratic model
+ s = _qsolve(maxHeight*(1-f),_hmodel(s0,s1,H0,H1))
+ W, H = func(s)
+ if H<=maxHeight:
+ self.width = W
+ self.height = H
+ self._scale = s
+ break
+
+ return self.width, self.height
+
def drawOn(self, canv, x, y, _sW=0):
- '''we simulate being added to a frame'''
- pS = 0
- aW = self.width+_sW
- C = self._content
- y += self.height
- for c in C:
- w, h = c.wrapOn(canv,aW,0xfffffff)
- if c is not C[0]: h += max(c.getSpaceBefore()-pS,0)
- y -= h
- c.drawOn(canv,x,y,_sW=aW-w)
- if c is not C[-1]:
- pS = c.getSpaceAfter()
- y -= pS
+ scale = getattr(self,'_scale',1.0)
+ if scale!=1.0:
+ canv.saveState()
+ canv.translate(x,y)
+ x=y=0
+ canv.scale(1.0/scale, 1.0/scale)
+ _Container.drawOn(self, canv, x, y, _sW=_sW, scale=scale)
+ if scale!=1.0:
+ canv.restoreState()
--- a/reportlab/platypus/frames.py Thu Sep 22 16:53:18 2005 +0000
+++ b/reportlab/platypus/frames.py Tue Sep 27 13:26:12 2005 +0000
@@ -63,12 +63,6 @@
self.__dict__['_rightPadding'] = rightPadding
self.__dict__['_topPadding'] = topPadding
- # 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
-
# if we want a boundary to be shown
self.showBoundary = showBoundary
@@ -104,6 +98,12 @@
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
@@ -113,6 +113,10 @@
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
--- a/reportlab/test/test_platypus_pto.py Thu Sep 22 16:53:18 2005 +0000
+++ b/reportlab/test/test_platypus_pto.py Tue Sep 27 13:26:12 2005 +0000
@@ -8,7 +8,7 @@
from reportlab.test import unittest
from reportlab.test.utils import makeSuiteForClasses, outputfile, printLocation
-from reportlab.platypus.flowables import Flowable, PTOContainer
+from reportlab.platypus.flowables import Flowable, PTOContainer, FrameFlowable
from reportlab.lib.units import cm
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.colors import toColor, black
@@ -28,7 +28,72 @@
canvas.drawString(10*cm, cm, str(pageNumber))
canvas.restoreState()
-def _breakingTestCase(self):
+def _showDoc(fn,story):
+ pageTemplate = PageTemplate('normal', [Frame(72, 440, 170, 284, id='F1'),
+ Frame(326, 440, 170, 284, id='F2'),
+ Frame(72, 72, 170, 284, id='F3'),
+ Frame(326, 72, 170, 284, id='F4'),
+ ], myMainPageFrame)
+ doc = BaseDocTemplate(outputfile(fn),
+ pageTemplates = pageTemplate,
+ showBoundary = 1,
+ )
+ doc.multiBuild(story)
+
+text2 ='''We have already seen that the natural general principle that will
+subsume this case cannot be arbitrary in the requirement that branching
+is not tolerated within the dominance scope of a complex symbol.
+Notice, incidentally, that the speaker-hearer's linguistic intuition is
+to be regarded as the strong generative capacity of the theory. A
+consequence of the approach just outlined is that the descriptive power
+of the base component does not affect the structure of the levels of
+acceptability from fairly high (e.g. (99a)) to virtual gibberish (e.g.
+(98d)). By combining adjunctions and certain deformations, a
+descriptively adequate grammar cannot be arbitrary in the strong
+generative capacity of the theory.'''
+
+text1='''
+On our assumptions, a descriptively adequate grammar delimits the strong
+generative capacity of the theory. For one thing, the fundamental error
+of regarding functional notions as categorial is to be regarded as a
+corpus of utterance tokens upon which conformity has been defined by the
+paired utterance test. A majority of informed linguistic specialists
+agree that the appearance of parasitic gaps in domains relatively
+inaccessible to ordinary extraction is necessary to impose an
+interpretation on the requirement that branching is not tolerated within
+the dominance scope of a complex symbol. It may be, then, that the
+speaker-hearer's linguistic intuition appears to correlate rather
+closely with the ultimate standard that determines the accuracy of any
+proposed grammar. Analogously, the notion of level of grammaticalness
+may remedy and, at the same time, eliminate a general convention
+regarding the forms of the grammar.'''
+
+text0 = '''To characterize a linguistic level L,
+this selectionally introduced contextual
+feature delimits the requirement that
+branching is not tolerated within the
+dominance scope of a complex
+symbol. Notice, incidentally, that the
+notion of level of grammaticalness
+does not affect the structure of the
+levels of acceptability from fairly high
+(e.g. (99a)) to virtual gibberish (e.g.
+(98d)). Suppose, for instance, that a
+subset of English sentences interesting
+on quite independent grounds appears
+to correlate rather closely with an
+important distinction in language use.
+Presumably, this analysis of a
+formative as a pair of sets of features is
+not quite equivalent to the system of
+base rules exclusive of the lexicon. We
+have already seen that the appearance
+of parasitic gaps in domains relatively
+inaccessible to ordinary extraction
+does not readily tolerate the strong
+generative capacity of the theory.'''
+
+def _ptoTestCase(self):
"This makes one long multi-page paragraph."
# Build story.
@@ -52,57 +117,6 @@
if type(content) not in (type([]),type(())): content = [content]
story.append(PTOContainer([Paragraph(blurb,H1)]+list(content),trailer,header))
- text2 ='''We have already seen that the natural general principle that will
-subsume this case cannot be arbitrary in the requirement that branching
-is not tolerated within the dominance scope of a complex symbol.
-Notice, incidentally, that the speaker-hearer's linguistic intuition is
-to be regarded as the strong generative capacity of the theory. A
-consequence of the approach just outlined is that the descriptive power
-of the base component does not affect the structure of the levels of
-acceptability from fairly high (e.g. (99a)) to virtual gibberish (e.g.
-(98d)). By combining adjunctions and certain deformations, a
-descriptively adequate grammar cannot be arbitrary in the strong
-generative capacity of the theory.'''
- text1='''
-On our assumptions, a descriptively adequate grammar delimits the strong
-generative capacity of the theory. For one thing, the fundamental error
-of regarding functional notions as categorial is to be regarded as a
-corpus of utterance tokens upon which conformity has been defined by the
-paired utterance test. A majority of informed linguistic specialists
-agree that the appearance of parasitic gaps in domains relatively
-inaccessible to ordinary extraction is necessary to impose an
-interpretation on the requirement that branching is not tolerated within
-the dominance scope of a complex symbol. It may be, then, that the
-speaker-hearer's linguistic intuition appears to correlate rather
-closely with the ultimate standard that determines the accuracy of any
-proposed grammar. Analogously, the notion of level of grammaticalness
-may remedy and, at the same time, eliminate a general convention
-regarding the forms of the grammar.'''
-
- text0 = '''To characterize a linguistic level L,
-this selectionally introduced contextual
-feature delimits the requirement that
-branching is not tolerated within the
-dominance scope of a complex
-symbol. Notice, incidentally, that the
-notion of level of grammaticalness
-does not affect the structure of the
-levels of acceptability from fairly high
-(e.g. (99a)) to virtual gibberish (e.g.
-(98d)). Suppose, for instance, that a
-subset of English sentences interesting
-on quite independent grounds appears
-to correlate rather closely with an
-important distinction in language use.
-Presumably, this analysis of a
-formative as a pair of sets of features is
-not quite equivalent to the system of
-base rules exclusive of the lexicon. We
-have already seen that the appearance
-of parasitic gaps in domains relatively
-inaccessible to ordinary extraction
-does not readily tolerate the strong
-generative capacity of the theory.'''
t0 = [ColorParagraph('blue','Please turn over', pto )]
h0 = [ColorParagraph('blue','continued from previous page', pto )]
t1 = [ColorParagraph('red','Please turn over(inner)', pto )]
@@ -134,30 +148,38 @@
ptoblob('A long PTO',[Paragraph(text0+' '+text1,bt)],t0,h0)
fbreak()
ptoblob('2 PTO (inner split)',[ColorParagraph('pink',text0,bt),PTOContainer([ColorParagraph(black,'Inner Starts',H1),ColorParagraph('yellow',text2,bt),ColorParagraph('black','Inner Ends',H1)],t1,h1),ColorParagraph('magenta',text1,bt)],t0,h0)
+ _showDoc('test_platypus_pto.pdf',story)
- pageTemplate = PageTemplate('normal', [Frame(2.5*cm, 15.5*cm, 6*cm, 10*cm, id='F1'),
- Frame(11.5*cm, 15.5*cm, 6*cm, 10*cm, id='F2'),
- Frame(2.5*cm, 2.5*cm, 6*cm, 10*cm, id='F3'),
- Frame(11.5*cm, 2.5*cm, 6*cm, 10*cm, id='F4'),
- ], myMainPageFrame)
- doc = BaseDocTemplate(outputfile('test_platypus_pto.pdf'),
- pageTemplates = pageTemplate,
- showBoundary = 1,
- )
- doc.multiBuild(story)
+def _frameFlowableTestCase(self):
+ story = []
+ def fbreak(story=story):
+ story.append(FrameBreak())
+ styleSheet = getSampleStyleSheet()
+ H1 = styleSheet['Heading1']
+ H1.pageBreakBefore = 0
+ H1.keepWithNext = 0
+ bt = styleSheet['BodyText']
+ story.append(FrameFlowable(170-12,284-12,[Paragraph(text0,bt)],mode=2))
+ fbreak()
+ story.append(FrameFlowable(170-12,284-12,[Paragraph(text0,bt),Paragraph(text1,bt)],mode=2))
+ fbreak()
+ story.append(FrameFlowable(170-12,284-12,[Paragraph(text0,bt),Paragraph(text1,bt),Paragraph(text2,bt)],mode=2))
+ _showDoc('test_platypus_frameflowable.pdf',story)
-class BreakingTestCase(unittest.TestCase):
+class TestCases(unittest.TestCase):
"Test multi-page splitting of paragraphs (eyeball-test)."
def test0(self):
- _breakingTestCase(self)
+ _ptoTestCase(self)
+ def test1(self):
+ _frameFlowableTestCase(self)
def makeSuite():
- return makeSuiteForClasses(BreakingTestCase)
+ return makeSuiteForClasses(TestCases)
#noruntests
if __name__ == "__main__": #NORUNTESTS
if 'debug' in sys.argv:
- _test0(None)
+ _frameFlowableTestCase(None)
else:
unittest.TextTestRunner().run(makeSuite())
printLocation()