(See TableLayout/KnuthElementsForTables for a full description of the combining algorithm.)
The current combining algorithm is based on the assumption that when a table is broken over two (several) pages, then the sum of its parts’ heights is greater than the unbroken table’s total height. This is mainly due to content in a column that is deferred because it cannot fit on the current page. Hence the use of penalties for representing intermediate steps.
In case of conditional spaces this assumption might actually not be always true. See the following fo snippet (assuming the line height is 15pt):
<fo:table width="100%" table-layout="fixed" border-collapse="separate" border="4pt solid black"> <fo:table-column column-width="proportional-column-width(1)"/> <fo:table-body> <fo:table-row> <fo:table-cell> <fo:block space-after="5pt" space-after.conditionality="retain">Line 1</fo:block> <fo:block space-before="25pt" space-before.conditionality="discard">Line 2</fo:block> </fo:table-cell> </fo:table-row> </fo:table-body> </fo:table>
If the table is unbroken, the two spaces are collapsed into one space of 25pt. The table’s total height is then of 15 + 25 + 15 = 55 points (in the separate-border model borders don’t count in the table’s bpd):
Now if the table is split over two pages, then the conditional space is discarded:
The first part of the broken table has a height of 20 points, and the second of 15 points; that leads to a total of 35 points, which is lower than the height of the unbroken table.
In terms of breaking steps, we have:
totalHeight = 55 1st step: step = 20 maxRemainingHeight = 15 penaltyHeight = 20 + 15 - 55 = -20!!! boxHeight = 20 - 0 - (-20) = 40!!! addedBoxHeight = 40 2nd step: step = 55 maxRemainingHeight = 0 penaltyHeight = 55 + 0 - 55 = 0 boxHeight = 55 - 40 - 0 = 15 addedBoxHeight = 55
Obviously the box/penalty calculation for the first step is wrong. How can we handle that?
We have to broken up the table into a set of boxes whose total heights equal the height of the unbroken table. For each legal break inside the table, we have to create a corresponding box and penalty; the boxes which will be put thereafter will represent the rest of the table, that is the content not included in the break. Their heights much exactly match the remaining content’s height: if they are inferior the content will overflow outside the table; if they are superior there will be superfluous blank space in the table. Those boxes’ heights must actually be equal to maxRemainingHeight.
The heights of the boxes used so far plus maxRemainingHeight must, then, not exceed the unbroken totalHeight. The excess will be put in the penalty. But what if the broken parts are actually smaller than the unbroken table? In such a case there’s nothing to put in the penalty.
It seems natural to keep maxRemainingHeight for the following box; and the box for the current break would be given the height of the broken part. But then the sum of the box heights would be inferior to totalHeight.
The solution is to put the additional length in a glue item. Indeed the glue will be retained if we don’t break at the current point; and if we do break then it will be discarded as per the Knuth algorithm’s rules (every glue and penalty items after the break are discarded until the next box element).
In the algorithm above, penaltyHeight becomes penaltyOrGlueHeight with the following convention: if its value is positive then that means that breaking the table leads to extra height, so we must create a penalty; if it is negative then the broken parts are smaller than the unbroken table, so we must create a glue.
In FOP this algorithm is implemented in org.apache.fop.layoutmgr.table.TableStepper; in practice it is a bit more complicated as we must handle the cumulated height of the previous boxes, the possible penalty values of the inner content, keeps and breaks, Position elements, etc.