add preliminary MultiCol implementation; version --> 3.5.20
authorrobin <robin@reportlab.com>
Thu, 25 Apr 2019 09:29:41 +0100
changeset 4502 86c084dcb3a2
parent 4501 06b7befd4add
child 4503 a2ecb7962d30
add preliminary MultiCol implementation; version --> 3.5.20
src/reportlab/__init__.py
src/reportlab/platypus/__init__.py
src/reportlab/platypus/doctemplate.py
src/reportlab/platypus/multicol.py
src/reportlab/platypus/paragraph.py
src/reportlab/platypus/paraparser.py
src/reportlab/platypus/tables.py
src/reportlab/platypus/xpreformatted.py
tests/test_platypus_lists.py
--- a/src/reportlab/__init__.py	Thu Apr 25 09:28:47 2019 +0100
+++ b/src/reportlab/__init__.py	Thu Apr 25 09:29:41 2019 +0100
@@ -1,9 +1,9 @@
 #Copyright ReportLab Europe Ltd. 2000-2018
 #see license.txt for license details
 __doc__="""The Reportlab PDF generation library."""
-Version = "3.5.19"
+Version = "3.5.20"
 __version__=Version
-__date__='20190415'
+__date__='20190425'
 
 import sys, os
 
--- a/src/reportlab/platypus/__init__.py	Thu Apr 25 09:28:47 2019 +0100
+++ b/src/reportlab/platypus/__init__.py	Thu Apr 25 09:29:41 2019 +0100
@@ -1,15 +1,14 @@
 #Copyright ReportLab Europe Ltd. 2000-2017
 #see license.txt for license details
 #history https://bitbucket.org/rptlab/reportlab/history-node/tip/src/reportlab/platypus/__init__.py
-__version__='3.3.0'
+__version__='3.5.20'
 __doc__='''Page Layout and Typography Using Scripts" - higher-level framework for flowing documents'''
 
 from .flowables import *
 from .frames import *
-from reportlab.platypus.paragraph import Paragraph, cleanBlockQuotedText, ParaLines
-from reportlab.platypus.paraparser import ParaFrag
-from reportlab.platypus.tables import Table, TableStyle, CellStyle, LongTable
-from reportlab.platypus.doctemplate import BaseDocTemplate, NextPageTemplate, PageTemplate, ActionFlowable, \
-                        SimpleDocTemplate, FrameBreak, PageBegin, Indenter, NotAtTopPageBreak, \
-                        NullActionFlowable, IndexingFlowable
-from reportlab.platypus.xpreformatted import XPreformatted
+from .multicol import *
+from .paragraph import *
+from .paraparser import *
+from .tables import *
+from .doctemplate import *
+from .xpreformatted import *
--- a/src/reportlab/platypus/doctemplate.py	Thu Apr 25 09:28:47 2019 +0100
+++ b/src/reportlab/platypus/doctemplate.py	Thu Apr 25 09:29:41 2019 +0100
@@ -1,8 +1,26 @@
 #Copyright ReportLab Europe Ltd. 2000-2017
 #see license.txt for license details
 #history https://bitbucket.org/rptlab/reportlab/history-node/tip/src/reportlab/platypus/doctemplate.py
-
-__version__='3.4.2'
+__all__ = (
+        'ActionFlowable',
+        'BaseDocTemplate',
+        'CurrentFrameFlowable',
+        'FrameActionFlowable',
+        'FrameBreak',
+        'Indenter',
+        'IndexingFlowable',
+        'LayoutError',
+        'LCActionFlowable',
+        'NextFrameFlowable',
+        'NextPageTemplate',
+        'NotAtTopPageBreak',
+        'NullActionFlowable',
+        'PageAccumulator',
+        'PageBegin',
+        'PageTemplate',
+        'SimpleDocTemplate',
+        )
+__version__='3.5.20'
 
 __doc__="""
 This module contains the core structure of platypus.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/reportlab/platypus/multicol.py	Thu Apr 25 09:29:41 2019 +0100
@@ -0,0 +1,111 @@
+__all__ = '''MultiCol'''.split()
+from reportlab.lib.utils import strTypes
+from .flowables import Flowable, _Container, _FindSplitterMixin, _listWrapOn
+
+class MultiCol(_Container,_FindSplitterMixin,Flowable):
+	def __init__(self,contents,widths, minHeightNeeded=36, spaceBefore=None, spaceAfter=None):
+		if len(contents)!=len(widths):
+			raise ValueError('%r len(contents)=%d not the same as len(widths)=%d' % (self,len(contents),len(widths)))
+		self.contents = contents
+		self.widths = widths
+		self.minHeightNeeded = minHeightNeeded
+		self._spaceBefore = spaceBefore
+		self._spaceAfter = spaceAfter
+		self._naW = None
+
+	def nWidths(self,aW):
+		if aW==self._naW: return self._nW
+		nW = [].append
+		widths = self.widths
+		s = 0.0
+		for i,w in enumerate(widths):
+			if isinstance(w,strTypes):
+				w=w.strip()
+				pc = w.endswith('%')
+				if pc: w=w[:-1]
+				try:
+					w = float(w)
+				except:
+					raise ValueError('%s: nWidths failed with value %r' % (self,widths[i]))
+				if pc: w = w*0.01*aW
+			elif not isinstance(w,(float,int)):
+				raise ValueError('%s: nWidths failed with value %r' % (self,widths[i]))
+
+			s += w
+			nW(w)
+
+		self._naW = aW
+		s = aW / s
+		self._nW = [w*s for w in nW.__self__]
+		return self._nW
+
+	def wrap(self,aW,aH):
+		widths = self.nWidths(aW)
+		w = h = 0.0
+		canv = self.canv
+		h = 0
+		for faW,F in zip(widths,self.contents):
+			if not F:
+				fW = faW
+				fH = 0
+			else:
+				fW,fH = _listWrapOn(F,faW,canv)
+			h = max(h,fH)
+			w += fW
+		self.width = w
+		self.height = h
+		return w, h
+
+	def split(self,aW,aH):
+		if aH<self.minHeightNeeded:
+			return []
+		widths = self.nWidths(aW)
+		S = [[],[]]
+		canv = self.canv
+		for faW,F in zip(widths,self.contents):
+			if not F:
+				fW = faW
+				fH0 = 0
+				S0 = []
+				S1 = []
+			else:
+				fW,fH0,S0,S1 = self._findSplit(canv,faW,aH,content=F,paraFix=False)
+				if S0 is F: return [] #we failed to find a split
+			S[0].append(S0)
+			S[1].append(S1)
+
+		return	[
+				MultiCol(S[0],
+					self.widths,
+					minHeightNeeded=self.minHeightNeeded,
+					spaceBefore=self._spaceBefore,
+					spaceAfter=self._spaceAfter),
+				MultiCol(S[1],
+					self.widths,
+					minHeightNeeded=self.minHeightNeeded,
+					spaceBefore=self._spaceBefore,
+					spaceAfter=self._spaceAfter),
+				]
+
+	def getSpaceAfter(self):
+		m = self._spaceAfter
+		if m is None:
+			m = 0
+			for F in self.contents:
+				m = max(m,_Container.getSpaceAfter(self,F))
+		return m
+
+	def getSpaceBefore(self):
+		m = self._spaceBefore
+		if m is None:
+			m = 0
+			for F in self.contents:
+				m = max(m,_Container.getSpaceBefore(self,F))
+		return m
+
+	def drawOn(self, canv, x, y, _sW=0):
+		widths = self._nW
+		xOffs = 0
+		for faW,F in zip(widths,self.contents):
+			_Container.drawOn(self, canv, x+xOffs, y, content=F, aW=faW)
+			xOffs += faW
--- a/src/reportlab/platypus/paragraph.py	Thu Apr 25 09:28:47 2019 +0100
+++ b/src/reportlab/platypus/paragraph.py	Thu Apr 25 09:29:41 2019 +0100
@@ -1,7 +1,13 @@
 #Copyright ReportLab Europe Ltd. 2000-2017
 #see license.txt for license details
 #history https://bitbucket.org/rptlab/reportlab/history-node/tip/src/reportlab/platypus/paragraph.py
-__version__='3.3.0'
+__all__=(
+        'Paragraph',
+        'cleanBlockQuotedText',
+        'ParaLines',
+        'FragLine',
+        )
+__version__='3.5.20'
 __doc__='''The standard paragraph implementation'''
 from string import whitespace
 from operator import truth
--- a/src/reportlab/platypus/paraparser.py	Thu Apr 25 09:28:47 2019 +0100
+++ b/src/reportlab/platypus/paraparser.py	Thu Apr 25 09:29:41 2019 +0100
@@ -1,7 +1,8 @@
 #Copyright ReportLab Europe Ltd. 2000-2017
 #see license.txt for license details
 #history https://bitbucket.org/rptlab/reportlab/history-node/tip/src/reportlab/platypus/paraparser.py
-__version__='3.3.0'
+__all__ = ('ParaFrag', 'ParaParser')
+__version__='3.5.20'
 __doc__='''The parser used to process markup within paragraphs'''
 import string
 import re
--- a/src/reportlab/platypus/tables.py	Thu Apr 25 09:28:47 2019 +0100
+++ b/src/reportlab/platypus/tables.py	Thu Apr 25 09:29:41 2019 +0100
@@ -1,7 +1,13 @@
 #Copyright ReportLab Europe Ltd. 2000-2017
 #see license.txt for license details
 #history https://bitbucket.org/rptlab/reportlab/history-node/tip/src/reportlab/platypus/tables.py
-__version__='3.3.0'
+__all__= (
+        'Table',
+        'TableStyle',
+        'CellStyle',
+        'LongTable',
+        )
+__version__='3.5.20'
 
 __doc__="""
 Tables are created by passing the constructor a tuple of column widths, a tuple of row heights and the data in
--- a/src/reportlab/platypus/xpreformatted.py	Thu Apr 25 09:28:47 2019 +0100
+++ b/src/reportlab/platypus/xpreformatted.py	Thu Apr 25 09:29:41 2019 +0100
@@ -1,7 +1,11 @@
 #Copyright ReportLab Europe Ltd. 2000-2017
 #see license.txt for license details
 #history https://bitbucket.org/rptlab/reportlab/history-node/tip/src/reportlab/platypus/xpreformatted.py
-__version__='3.3.0'
+__all__ = (
+            'XPreformatted',
+            'PythonPreformatted',
+            )
+__version__='3.5.20'
 __doc__='''A 'rich preformatted text' widget allowing internal markup'''
 from reportlab.lib import PyFontify
 from reportlab.platypus.paragraph import Paragraph, cleanBlockQuotedText, _handleBulletWidth, \
--- a/tests/test_platypus_lists.py	Thu Apr 25 09:28:47 2019 +0100
+++ b/tests/test_platypus_lists.py	Thu Apr 25 09:29:41 2019 +0100
@@ -1,12 +1,16 @@
 from random import randint
+from xml.sax.saxutils import escape as xmlEscape
+from reportlab import xrange
 from reportlab.lib.testutils import setOutDir,makeSuiteForClasses, outputfile, printLocation
 setOutDir(__name__)
 import os,unittest
-from reportlab.platypus import Spacer, SimpleDocTemplate, Table, TableStyle, ListFlowable, ListItem, Paragraph, PageBreak
+from reportlab.platypus import Spacer, SimpleDocTemplate, Table, TableStyle, ListFlowable, ListItem, \
+        Paragraph, PageBreak, DDIndenter, MultiCol
 from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
 from reportlab.lib.units import inch, cm
 from reportlab.lib.utils import simpleSplit
 from reportlab.lib import colors
+from reportlab.lib import randomtext
 
 TEXTS=[
 '''We have already seen that the notion of level of grammaticalness is,
@@ -57,15 +61,16 @@
 
     def test1(self):
         styleSheet = getSampleStyleSheet()
-        doc = SimpleDocTemplate(outputfile('test_platypus_lists1.pdf'))
+        doc = SimpleDocTemplate(outputfile('test_platypus_lists1.pdf'),showBoundary=True)
         story=[]
         sty = [ ('GRID',(0,0),(-1,-1),1,colors.green),
             ('BOX',(0,0),(-1,-1),2,colors.red),
             ]
         normal = styleSheet['BodyText']
+        bold = normal.clone('bold',fontName='Helvetica-Bold')
         lpSty = normal.clone('lpSty',spaceAfter=18)
-        data = [[str(i+1), Paragraph("xx "* (i%10), styleSheet["BodyText"]), Paragraph(("blah "*(i%40)), normal)] for i in range(5)]
-        data1 = [[str(i+1), Paragraph(["zz ","yy "][i]*(i+3), styleSheet["BodyText"]), Paragraph(("duh  "*(i+3)), normal)] for i in range(2)]
+        data = [[str(i+1), Paragraph("xx "* (i%10), styleSheet["BodyText"]), Paragraph(("blah "*(i%40)), normal)] for i in xrange(5)]
+        data1 = [[str(i+1), Paragraph(["zz ","yy "][i]*(i+3), styleSheet["BodyText"]), Paragraph(("duh  "*(i+3)), normal)] for i in xrange(2)]
         OL = ListFlowable(
             [
             Paragraph("A table with 5 rows", lpSty),
@@ -157,7 +162,45 @@
                     ),
             )
         
-        
+        story.append(PageBreak())
+        story.append(Paragraph("DDIndenter", style=normal))
+        story.append(Paragraph("Coffee",style=bold))
+        story.append(DDIndenter(Paragraph("Black hot drink",style=normal),leftIndent=36))
+        story.append(Paragraph("Milk",style=bold))
+        story.append(DDIndenter(Paragraph("White cold drink",style=normal),leftIndent=36))
+        story.append(Paragraph("Whiskey",style=bold))
+        story.append(DDIndenter(Paragraph("A nice alcoholic drink",style=normal),leftIndent=36))
+        story.append(PageBreak())
+        story.append(Paragraph("MultiCol", style=normal))
+        RT = 'STARTUP COMPUTERS BLAH BUZZWORD STARTREK PRINTING PYTHON CHOMSKY CHOMSKY'.split()
+        for i in xrange(5):
+            topic = RT[randint(0,len(RT)-1)]
+            np = randint(2,6)
+            story.append(
+                    MultiCol([
+                        [Paragraph('Column %d' % (i+1,),style=bold)],
+                        [],
+                        [Paragraph(xmlEscape(randomtext.randomText(topic,randint(1,7))),style=normal) for j in xrange(np)]
+                        ],
+                        widths=['20%',3,'80%'],
+                        )
+                    )
+
+        story.append(PageBreak())
+        story.append(Paragraph("MultiCol 2", style=normal))
+        for i in xrange(5):
+            topic = RT[randint(0,len(RT)-1)]
+            np = randint(2,6)
+            story.append(
+                    MultiCol([
+                        ([Paragraph('Column %d' % (i+1,),style=bold)]+
+                        [Paragraph(xmlEscape(randomtext.randomText(topic,randint(1,7))),style=normal) for j in xrange(np)]),
+                        [],
+                        [Paragraph(xmlEscape(randomtext.randomText(topic,randint(1,7))),style=normal) for j in xrange(np)]
+                        ],
+                        widths=['50%',5,'50%'],
+                        )
+                    )
         
         doc.build(story)