Re: Patch to support tables with oversize cells

Robin Becker <[email protected]> Wed, 20 Apr 2022 10:04:10 +0100
Newsgroups gmane.comp.python.reportlab.user
Message-ID <[email protected]>
Hi Lennart,

I did some investigation of the problems I saw in that last example (see attached new version of the script useData==2).

It seems that the real problem is that because of the spanned cell in column 0 row 0 that in _splitRows @ 1565

n=self._getFirstPossibleSplitRowPosition(availHeight)

returns a value of 0 for the first possible split row position.

Then at line 1633 we get

n=0 usedHeights=0 cellvalues=['A\nB\nC\nD', 'BBBBB', 'C', 'D', 'E']
curRowHeight=18 minSplit=15.0 maxSplit=15.0
minSplit + maxSplit > curRowHeight=True
minSplit > (availHeight - usedHeights)=False

I assume that because of the spanned rows we are just using the first (n=0) row to work out the minSplit/maxSplit and 
then the failure follows from the first test.

1) It's not obvious what n should be in the splitInRow row span case.
2) It seems the splitInRow case requires us to consider far more than the height of one row.
-- 
Robin Becker
lennart-example.py (text/x-python, 5.7 KB)
def main():
    from reportlab.lib import colors
    from reportlab.lib.units import inch
    from reportlab.platypus import Paragraph, Table, SimpleDocTemplate, Spacer, HRFlowable
    from reportlab.lib.styles import getSampleStyleSheet
    import os, sys, getopt
    try:
        opts, args = getopt.getopt(sys.argv[1:], '', 'splitByRow= splitInRow= split= useData= pdfn='.split())
    except getopt.GetoptError as err:
        # print help information and exit:
        print(err)
        print(f'Usage {os.path.basename(sys.argv[0])} [--splitByRow=num] [--splitInRow=num] [--split=num] [--useData=] --pdfn=')
        sys.exit(2)
    else:
        splitByRow = 1
        splitInRow = 0
        split = 36
        pdfn = None
        useData = 0
        for k, v in opts:
            if k=='--splitByRow':
                splitByRow = int(v)
            elif k=='--splitInRow':
                splitInRow = int(v)
            elif k=='--split':
                split = float(v)
                if int(split)==split: split=int(split)
            elif k=='--useData':
                useData = int(v)
            elif k=='--pdfn':
                pdfn =  v
            else:
                raise ValueError(f'Unknown option name {k}={v}')

    class IndicatorTable(Table):
        def draw(self):
            Table.draw(self)
            c = self.canv
            x = self._width
            y = self._height - split
            c.setStrokeColor(colors.toColor('red'))
            c.setLineWidth(0.5)
            c.setDash([2,2])
            c.line(0,y,x+13,y)

    story = []
    storyAdd = story.append
    styleSheet = getSampleStyleSheet()
    btStyle = styleSheet['BodyText']
    def makeTable(klass=Table):
        if useData==0:
            data=  [['00', '01', '02', '03', '04'],
                    ['10', '11', '12', '13', '14'],
                    ['20', '21', '22', '23', '24'],
                    ['30', '31', '32', '33', '34']]
            data=  [['00\n\naa', '01', '02', '03', '04'],
                    ['10', '11\nbb', '12', '13', '14'],
                    ['20', '21', '22\ncc', '23', '24'],
                    ['30', '31', '32', '33\ndd', '34']]
            sty = [
                    ('GRID',(0,0),(-1,-1),0.5,colors.grey),
                    ('GRID',(1,1),(-2,-2),1,colors.green),
                    ('BOX',(0,0),(1,-1),2,colors.red),
                    ('BOX',(0,0),(-1,-1),2,colors.black),
                    ('LINEABOVE',(1,2),(-2,2),1,colors.blue),
                    ('LINEBEFORE',(2,1),(2,-2),1,colors.pink),
                    ('BACKGROUND', (0, 0), (0, 1), colors.pink),
                    ('BACKGROUND', (1, 1), (1, 2), colors.lavender),
                    ('BACKGROUND', (2, 2), (2, 3), colors.orange),
                    ('TEXTCOLOR',(0,-1),(-2,-1),colors.green),
                    ]
        elif useData in (1,2):
            if useData==1:
                rows01 = [['A', 'BBBBB', 'C', 'D', 'E'],
                        ['', '01', '02', '03', '04']]
            else:
                rows01 = [['A\nB\nC\nD', 'BBBBB', 'C', 'D', 'E'],
                        ['', '01\n\naa', '02\n\nbb', '03\n\ncc', '04\n\ndd']]
            data=  rows01 + [
                    ['10\n11', ],
                    ['20', '21', '22', '23', '24'],
                    ['30', '31', '32', '33', '34']]
            sty = [
                    ('ALIGN',(0,0),(-1,-1),'CENTER'),
                    ('VALIGN',(0,0),(-1,-1),'TOP'),
                    ('GRID',(0,0),(-1,-1),1,colors.green),
                    ('BOX',(0,0),(-1,-1),2,colors.red),

                    #span 'BBBB' across middle 3 cells in top row
                    ('SPAN',(1,0),(3,0)),
                    #now color the first cell in this range only,
                    #i.e. the one we want to have spanned.  Hopefuly
                    #the range of 3 will come out khaki.
                    ('BACKGROUND',(1,0),(1,0),colors.khaki),

                    #span row 3 across all columns
                    ('SPAN',(0,2),(-1,2)),

                    #span 'AAA' down first two rows
                    ('SPAN',(0,0), (0, 1)),
                    ('BACKGROUND',(0,0),(0,0),colors.cyan),
                    ('TEXTCOLOR', (0,'splitfirst'), (-1,'splitfirst'), colors.cyan),
                    ('TEXTCOLOR', (0,'splitlast'), (-1,'splitlast'), colors.red),
                    ('BACKGROUND', (0,'splitlast'), (-1,'splitlast'), colors.pink),
                    ('LINEBELOW', (0,'splitlast'), (-1,'splitlast'), 1, colors.grey,'butt'),
                   ]
        else:
            raise ValueError(f'useData should be in [0,1] not {useData}')
        return klass(data,
                    style=sty,
                    splitInRow=splitInRow,
                    splitByRow=splitByRow,
                    )
    storyAdd(Paragraph("Illustrating splits: nosplit", btStyle))
    storyAdd(makeTable(klass=IndicatorTable))
    storyAdd(Spacer(0,6))
    def addSplitTable(size=30):
        t = makeTable()
        S = t.split(4*inch,size)
        if not S:
            storyAdd(Paragraph(f"<span color=red>Illustrating splits failed</span>: split(4in,{size}) {splitByRow=} {splitInRow=}", btStyle))
            storyAdd(Spacer(0,6))
            #print('!!!!! Failed')
        else:
            #print('##### OK')
            storyAdd(Paragraph(f"Illustrating splits: split(4in,{size}) {splitByRow=} {splitInRow=}", btStyle))
            storyAdd(Spacer(0,6))
            for s in S:
                storyAdd(s)
                storyAdd(Spacer(0,6))
    addSplitTable(split)

    pdfn = pdfn or f'{os.path.basename(os.path.splitext(sys.argv[0])[0])}-{splitByRow!s}-{splitInRow!s}-{split!s}.pdf'
    doc = SimpleDocTemplate(pdfn, showBoundary=0)
    doc.build(story)
    print(f'Saved {pdfn}')

if __name__=='__main__':
    main()