Hi all,

Caution, long post ;-)

I’ve been thinking about the handling of keeps and breaks in tables for 
a while, and it seems to me that improvements could be done in that 
whole area. I’ll use break-before as an example but what I’ll be saying 
applies to break-after and keeps as well.

Currently, for every object to which the break-before property applies, 
the value of the property is checked at the beginning of the 
getNextKnuthElements method and, if it corresponds to a hard-break, 
a Knuth penalty with -inf value is produced. This has several 
shortcomings:
- this leads to much code duplication;
- if, say, an fo:block has another fo:block as a child and both have 
  break-before, two penalties will be generated instead of one (although 
  I’m suddenly not so sure of that, more later)
- this makes it difficult to improve breaks to distinguish columns from 
  pages, and keeps to take integer values into account.

In fact, we don’t so much care about the penalty element itself as 
whether there /should/ be a break between elements or not. I mean, the 
LMs which actually care are those which concatenate the elements: 
fo:flow, fo:block, fo:block-container, fo:table-cell, etc. And they all 
do it the same way:
    iterate over the children LMs
    for each LM:
        if there is a following LM, then:
            if the current LM has break-after or the following LM has 
            break-before, then
                generate a Knuth forced break

So the main question is to know whether there is a break before a LM. 
And here that’s quite simple, there are only a few shared behaviours: 
indeed there is a break-before on an element if:
- the break-before property is set on the element itself or the first of 
  its children:
  fo:block, fo:block-container, fo:list-block
- it is set on the element itself or any of its children:
  fo:table-row, fo:list-item
- it is set on the first of its children:
  fo:table-caption, fo:table-cell, fo:list-item-body, 
  fo:list-item-label, fo:wrapper
- special:
  - fo:table: on itself or first table-body
  - fo:table-body: on the first fo:table-row child if any; otherwise on 
    any of fo:table-cell children making the first row
- not applicable:
  fo:table-column, fo:table-header/footer, fo:float, fo:footnote-body

So there would just be a couple of methods to write, for each behaviour. 
We could (for the moment) define a dedicated class with static methods, 
which would be called by each LM (some methods are imaginary, but you 
get the idea):

    public class KeepsAndBreaksHandler {

        public static boolean breakBeforeSelfOrFirstChild(LM lm) {
            return lm.getProperty(break-before).isForced() ||
                    lm.getFirstChild().getProperty(break-before).isForced();
        }

        public static boolean breakBeforeSelfOrAnyChild(LM lm) {
            ...
        }

        public static boolean breakBeforeFirstChild(LM lm) {
            ...
        }
        ...
    }

The method mustKeepWithPrevious would become:
- for BlockLM:
    return KeepsAndBreaksHandler.breakBeforeSelfOrFirstChild(this)
- for TableCellLM:
    return KeepsAndBreaksHandler.breakBeforeFirstChild(this)
etc.

The static method wouldn’t even need to be made synchronized, as the 
class is stateless. Later on we could replace this class with 
a Singleton object that would be passed to the LMs at their creation, 
and we would have some kind of “KeepsAndBreaksHandlerFactory”. But for 
now a simple class with static methods would be sufficient.

We could also put in this class the code for producing the penalties 
elements:
    KnuthElement getElementForBreakBetween(LM firstLM, LM secondLM) {
        if (firstLM.hasBreakAfter() || secondLM().hasBreakBefore()) {
            return new KnuthPenalty(...);
        } else if (firstLM.mustKeepWithNext() || 
secondLM.mustKeepWithPrevious()) {
            return new KnuthForbiddenBreak()
        }
    }
or something like that.

The benefit would be that the whole handling of breaks can be found in 
just one place, instead of being spread among all the LMs. This is 
easier to correct bugs; this is easier to implement new features (column 
vs page break, integer keeps...); this simplifies the 
getNextKnuthElements methods of the LMs. And so on...


WDYT?
Vincent

Reply via email to