The purpose of the stepping algorithm is to find potential breaks inside a row group; that is, a set of all the rows spanned over by one or more table cells:
- A row group
One issue of this stepping algorithm is to determine when the current row ends, and when we can switch to the next one. In fact, determining when a row ends is easy: it is when all of the cells ending on the row have contributed all of their contents. The only thing that must be taken into account is that the conditionality of their after borders no longer applies, since the row has ended. This may increase the next step and leave space for the spanning cells to contribute more content.
- The first row is now finished…
- … and taking the after border into account leaves room for adding one more line of the spanning cell
But this is not all: the next row can actually start only once the next step is big enough to encompass the before borders of the cells starting on the row, plus at least a piece of content for each of those cells.
As long as this condition is not met, the next row can’t start and we must stay on the current row. This mechanism was formerly known (in the code and documentation) as backtracking: after switching to the next row, the code was activating the cells starting on this row, computing those cells’ first steps and reverting to the previous row if necessary.
The removal of the 900-penalty mechanism (that is, put a break penalty of 900 to avoid breaking at places where not all cells starting on a row can contribute content) was an opportunity to improve this mechanism, and take advantage of the fact that the next row can’t be started yet to put more content from spanning cells on the current row. The next row is actually delayed, and we will call that row-delaying mechanism.
- If we directly jump to the next row’s first step, we miss an opportunity to break after line 4
- This opportunity can be enabled by delaying row 2, and extending row 1
The idea is quite simple:
- once the minimal next step is computed, determine if the current row is finished;
- if it is, update the minimal step by adding the widths of the after paddings and borders of the cells ending on the current row, if any. The step may change if some paddings or borders were conditional, in which case the conditionality no longer applies; this is an opportunity to add more content from the cells that span over the next row;
- the row-delaying phase now starts. The first minimal step necessary to include some content of all the cells starting on the next row is computed. While this amount is not reached we can step over the row-spanning cells, creating additional break opportunities.
- once the minimal first step is reached, we can finally switch to the next row and continue as normal.
In practice we must take into account additional elements:
- a maximal height allowed for the current row: we can’t extend it over that maximal amount;
- the Position elements that must be created for each step; this is slightly tricky since the Positions created for the cells finishing on the current row must not be taken into account if the corresponding step actually appears on the following page;
- the cells spanning over the next row may finish “early” i.e., have all of their content fitting on the current row. In such a case there’s no possibility (and need) for extending the current row.
All the details are to be found in the o.a.f.layoutmgr.table.TableStepper and o.a.f.layoutmgr.table.ActiveCell classes.
There’s a fundamental limitation in the stepping algorithm: apart from forced breaks all the generated penalties have the same value of 0. There is no means to privilege one over another one. This is not related to the row-delaying mechanism but can be seen when that latter is triggered.
Look below at the two different ways the example can be rendered:
Obviously the second is preferable to the first one, but since both of the corresponding penalties have the same value the breaking algorithm will not try to privilege one over the other. The computation of demerits like it is used for other elements doesn’t apply here, since the stepping algorithm produces no glue. And even if it would the glues that it would generate would probably not correspond to the individual glues of the table cells. The only variable we can play with is the value of the penalty, which would somehow be related to the amount of whitespace generated by each step. But it must carefully be computed to not overflow the value of the infinite penalty, and be progressive enough, etc. Not speaking of TableLayout/KnownProblems...