1 /*
2  * Licensed to the Apache Software Foundation (ASF) under one or more
3  * contributor license agreements.  See the NOTICE file distributed with
4  * this work for additional information regarding copyright ownership.
5  * The ASF licenses this file to You under the Apache License, Version 2.0
6  * (the "License"); you may not use this file except in compliance with
7  * the License.  You may obtain a copy of the License at
8  *
9  *      http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */
17 
18 /* $Id: ActiveCell.java 1610839 2014-07-15 20:25:58Z vhennebert $ */
19 
20 package org.apache.fop.layoutmgr.table;
21 
22 import java.util.ArrayList;
23 import java.util.LinkedList;
24 import java.util.List;
25 import java.util.ListIterator;
26 
27 import org.apache.commons.logging.Log;
28 import org.apache.commons.logging.LogFactory;
29 
30 import org.apache.fop.fo.Constants;
31 import org.apache.fop.fo.flow.table.ConditionalBorder;
32 import org.apache.fop.fo.flow.table.EffRow;
33 import org.apache.fop.fo.flow.table.PrimaryGridUnit;
34 import org.apache.fop.fo.properties.CommonBorderPaddingBackground;
35 import org.apache.fop.layoutmgr.ElementListUtils;
36 import org.apache.fop.layoutmgr.Keep;
37 import org.apache.fop.layoutmgr.KnuthBlockBox;
38 import org.apache.fop.layoutmgr.KnuthBox;
39 import org.apache.fop.layoutmgr.KnuthElement;
40 import org.apache.fop.layoutmgr.KnuthPenalty;
41 import org.apache.fop.traits.MinOptMax;
42 
43 /**
44  * A cell playing in the construction of steps for a row-group.
45  */
46 class ActiveCell {
47 
48     private static Log log = LogFactory.getLog(ActiveCell.class);
49 
50     private PrimaryGridUnit pgu;
51     /** Knuth elements for this active cell. */
52     private List elementList;
53     /** Iterator over the Knuth element list. */
54     private ListIterator knuthIter;
55     /** Number of the row where the row-span ends, zero-based. */
56     private int endRowIndex;
57     /** Length of the Knuth elements not yet included in the steps. */
58     private int remainingLength;
59     /** Total length of this cell's content plus the lengths of the previous rows. */
60     private int totalLength;
61     /** Length of the Knuth elements already included in the steps. */
62     private int includedLength;
63 
64     private int paddingBeforeNormal;
65     private int paddingBeforeLeading;
66     private int paddingAfterNormal;
67     private int paddingAfterTrailing;
68 
69     private int bpBeforeNormal;
70     private int bpBeforeLeading;
71     private int bpAfterNormal;
72     private int bpAfterTrailing;
73 
74     /** True if the next CellPart that will be created will be the last one for this cell. */
75     private boolean lastCellPart;
76 
77     private Keep keepWithNext;
78 
79     private int spanIndex;
80 
81     private Step previousStep;
82     private Step nextStep;
83     /**
84      * The step following nextStep. Computing it early allows to calculate
85      * {@link Step#condBeforeContentLength}, thus to easily determine the remaining
86      * length. That also helps for {@link #increaseCurrentStep(int)}.
87      */
88     private Step afterNextStep;
89 
90     /**
91      * Auxiliary class to store all the informations related to a breaking step.
92      */
93     private static class Step {
94         /** Index, in the list of Knuth elements, of the element starting this step. */
95         private int start;
96         /** Index, in the list of Knuth elements, of the element ending this step. */
97         private int end;
98         /** Length of the Knuth elements up to this step. */
99         private int contentLength;
100         /** Total length up to this step, including paddings and borders. */
101         private int totalLength;
102         /** Length of the penalty ending this step, if any. */
103         private int penaltyLength;
104         /** Value of the penalty ending this step, 0 if the step does not end on a penalty. */
105         private int penaltyValue;
106         /** List of footnotes for this step. */
107         private List footnoteList;
108         /**
109          * One of {@link Constants#EN_AUTO}, {@link Constants#EN_COLUMN},
110          * {@link Constants#EN_PAGE}, {@link Constants#EN_EVEN_PAGE},
111          * {@link Constants#EN_ODD_PAGE}. Set to auto if the break isn't at a penalty
112          * element.
113          */
114         private int breakClass;
115         /**
116          * Length of the optional content at the beginning of the step. That is, content
117          * that will not appear if this step starts a new page.
118          */
119         private int condBeforeContentLength;
120 
Step(int contentLength)121         Step(int contentLength) {
122             this.contentLength = contentLength;
123             this.end = -1;
124         }
125 
Step(Step other)126         Step(Step other) {
127             set(other);
128         }
129 
set(Step other)130         void set(Step other) {
131             this.start         = other.start;
132             this.end           = other.end;
133             this.contentLength = other.contentLength;
134             this.totalLength   = other.totalLength;
135             this.penaltyLength = other.penaltyLength;
136             this.penaltyValue  = other.penaltyValue;
137             if (other.footnoteList != null) {
138                 if (this.footnoteList == null) {
139                     this.footnoteList = new ArrayList();
140                 }
141                 this.footnoteList.addAll(other.footnoteList);
142             }
143             this.condBeforeContentLength = other.condBeforeContentLength;
144             this.breakClass    = other.breakClass;
145         }
146 
147         /** {@inheritDoc} */
toString()148         public String toString() {
149             return "Step: start=" + start + " end=" + end + " length=" + totalLength;
150         }
151     }
152 
153     // TODO to be removed along with the RowPainter#computeContentLength method
154     /** See {@link ActiveCell#handleExplicitHeight(MinOptMax, MinOptMax)}. */
155     private static class FillerPenalty extends KnuthPenalty {
156 
157         private int contentLength;
158 
FillerPenalty(KnuthPenalty p, int length)159         FillerPenalty(KnuthPenalty p, int length) {
160             super(length, p.getPenalty(), p.isPenaltyFlagged(), p.getBreakClass(),
161                     p.getPosition(), p.isAuxiliary());
162             contentLength = p.getWidth();
163         }
164 
FillerPenalty(int length)165         FillerPenalty(int length) {
166             super(length, 0, false, null, true);
167             contentLength = 0;
168         }
169     }
170 
171     /** See {@link ActiveCell#handleExplicitHeight(MinOptMax, MinOptMax)}. */
172     private static class FillerBox extends KnuthBox {
FillerBox(int length)173         FillerBox(int length) {
174             super(length, null, true);
175         }
176     }
177 
178     /**
179      * Returns the actual length of the content represented by the given element. In the
180      * case where this element is used as a filler to match a row's fixed height, the
181      * value returned by the getW() method will be higher than the actual content.
182      *
183      * @param el an element
184      * @return the actual content length corresponding to the element
185      */
getElementContentLength(KnuthElement el)186     static int getElementContentLength(KnuthElement el) {
187         if (el instanceof FillerPenalty) {
188             return ((FillerPenalty) el).contentLength;
189         } else if (el instanceof FillerBox) {
190             return 0;
191         } else {
192             return el.getWidth();
193         }
194     }
195 
ActiveCell(PrimaryGridUnit pgu, EffRow row, int rowIndex, int previousRowsLength, TableLayoutManager tableLM)196     ActiveCell(PrimaryGridUnit pgu, EffRow row, int rowIndex, int previousRowsLength,
197             TableLayoutManager tableLM) {
198         this.pgu = pgu;
199         CommonBorderPaddingBackground bordersPaddings = pgu.getCell()
200                 .getCommonBorderPaddingBackground();
201         TableCellLayoutManager cellLM = pgu.getCellLM();
202         paddingBeforeNormal = bordersPaddings.getPaddingBefore(false, cellLM);
203         paddingBeforeLeading = bordersPaddings.getPaddingBefore(true, cellLM);
204         paddingAfterNormal = bordersPaddings.getPaddingAfter(false, cellLM);
205         paddingAfterTrailing = bordersPaddings.getPaddingAfter(true, cellLM);
206         bpBeforeNormal = paddingBeforeNormal
207                 + pgu.getBeforeBorderWidth(0, ConditionalBorder.NORMAL);
208         bpBeforeLeading = paddingBeforeLeading
209                 + pgu.getBeforeBorderWidth(0, ConditionalBorder.REST);
210         bpAfterNormal = paddingAfterNormal + pgu.getAfterBorderWidth(ConditionalBorder.NORMAL);
211         bpAfterTrailing = paddingAfterTrailing + pgu.getAfterBorderWidth(0, ConditionalBorder.REST);
212         elementList = pgu.getElements();
213         handleExplicitHeight(pgu.getCell().getBlockProgressionDimension().toMinOptMax(tableLM),
214                 row.getExplicitHeight());
215         knuthIter = elementList.listIterator();
216         includedLength = -1;  // Avoid troubles with cells having content of zero length
217         totalLength = previousRowsLength + ElementListUtils.calcContentLength(elementList);
218         endRowIndex = rowIndex + pgu.getCell().getNumberRowsSpanned() - 1;
219         keepWithNext = Keep.KEEP_AUTO;
220         remainingLength = totalLength - previousRowsLength;
221 
222         afterNextStep = new Step(previousRowsLength);
223         previousStep = new Step(afterNextStep);
224         gotoNextLegalBreak();
225         nextStep = new Step(afterNextStep);
226         if (afterNextStep.end < elementList.size() - 1) {
227             gotoNextLegalBreak();
228         }
229     }
230 
231     /**
232      * Modifies the cell's element list by putting filler elements, so that the cell's or
233      * row's explicit height is always reached.
234      *
235      * TODO this will work properly only for the first break. Then the limitation
236      * explained on http://wiki.apache.org/xmlgraphics-fop/TableLayout/KnownProblems
237      * occurs. The list of elements needs to be re-adjusted after each break.
238      */
handleExplicitHeight(MinOptMax cellBPD, MinOptMax rowBPD)239     private void handleExplicitHeight(MinOptMax cellBPD, MinOptMax rowBPD) {
240         int minBPD = Math.max(cellBPD.getMin(), rowBPD.getMin());
241         if (minBPD > 0) {
242             ListIterator iter = elementList.listIterator();
243             int cumulateLength = 0;
244             boolean prevIsBox = false;
245             while (iter.hasNext() && cumulateLength < minBPD) {
246                 KnuthElement el = (KnuthElement) iter.next();
247                 if (el.isBox()) {
248                     prevIsBox = true;
249                     cumulateLength += el.getWidth();
250                 } else if (el.isGlue()) {
251                     if (prevIsBox) {
252                         elementList.add(iter.nextIndex() - 1,
253                                 new FillerPenalty(minBPD - cumulateLength));
254                     }
255                     prevIsBox = false;
256                     cumulateLength += el.getWidth();
257                 } else {
258                     prevIsBox = false;
259                     if (cumulateLength + el.getWidth() < minBPD) {
260                         iter.set(new FillerPenalty((KnuthPenalty) el, minBPD - cumulateLength));
261                     }
262                 }
263             }
264         }
265         int optBPD = Math.max(minBPD, Math.max(cellBPD.getOpt(), rowBPD.getOpt()));
266         if (pgu.getContentLength() < optBPD) {
267             elementList.add(new FillerBox(optBPD - pgu.getContentLength()));
268         }
269     }
270 
getPrimaryGridUnit()271     PrimaryGridUnit getPrimaryGridUnit() {
272         return pgu;
273     }
274 
275     /**
276      * Returns true if this cell ends on the given row.
277      *
278      * @param rowIndex index of a row in the row-group, zero-based
279      * @return true if this cell ends on the given row
280      */
endsOnRow(int rowIndex)281     boolean endsOnRow(int rowIndex) {
282         return rowIndex == endRowIndex;
283     }
284 
285     /**
286      * Returns the length of this cell's content not yet included in the steps, plus the
287      * cell's borders and paddings if applicable.
288      *
289      * @return the remaining length, zero if the cell is finished
290      */
getRemainingLength()291     int getRemainingLength() {
292         if (includedInLastStep() && (nextStep.end == elementList.size() - 1)) {
293             // The cell is finished
294             return 0;
295         } else {
296             return bpBeforeLeading + remainingLength + bpAfterNormal;
297         }
298     }
299 
gotoNextLegalBreak()300     private void gotoNextLegalBreak() {
301         afterNextStep.penaltyLength = 0;
302         afterNextStep.penaltyValue = 0;
303         afterNextStep.condBeforeContentLength = 0;
304         afterNextStep.breakClass = Constants.EN_AUTO;
305         if (afterNextStep.footnoteList != null) {
306             afterNextStep.footnoteList.clear();
307         }
308         boolean breakFound = false;
309         boolean prevIsBox = false;
310         boolean boxFound = false;
311         while (!breakFound && knuthIter.hasNext()) {
312             KnuthElement el = (KnuthElement) knuthIter.next();
313             if (el.isPenalty()) {
314                 prevIsBox = false;
315                 if (el.getPenalty() < KnuthElement.INFINITE
316                         || ((KnuthPenalty) el).getBreakClass() == Constants.EN_PAGE) {
317                     // TODO too much is being done in that test, only to handle
318                     // keep.within-column properly.
319 
320                     // First legal break point
321                     breakFound = true;
322                     KnuthPenalty p = (KnuthPenalty) el;
323                     afterNextStep.penaltyLength = p.getWidth();
324                     afterNextStep.penaltyValue = p.getPenalty();
325                     if (p.isForcedBreak()) {
326                         afterNextStep.breakClass = p.getBreakClass();
327                     }
328                 }
329             } else if (el.isGlue()) {
330                 if (prevIsBox) {
331                     // Second legal break point
332                     breakFound = true;
333                 } else {
334                     afterNextStep.contentLength += el.getWidth();
335                     if (!boxFound) {
336                         afterNextStep.condBeforeContentLength += el.getWidth();
337                     }
338                 }
339                 prevIsBox = false;
340             } else {
341                 if (el instanceof KnuthBlockBox && ((KnuthBlockBox) el).hasAnchors()) {
342                     if (afterNextStep.footnoteList == null) {
343                         afterNextStep.footnoteList = new LinkedList();
344                     }
345                     afterNextStep.footnoteList.addAll(((KnuthBlockBox) el).getFootnoteBodyLMs());
346                 }
347                 prevIsBox = true;
348                 boxFound = true;
349                 afterNextStep.contentLength += el.getWidth();
350             }
351         }
352         afterNextStep.end = knuthIter.nextIndex() - 1;
353         afterNextStep.totalLength = bpBeforeNormal
354                 + afterNextStep.contentLength + afterNextStep.penaltyLength
355                 + bpAfterTrailing;
356     }
357 
358     /**
359      * Returns the minimal step that is needed for this cell to contribute some content.
360      *
361      * @return the step for this cell's first legal break
362      */
getFirstStep()363     int getFirstStep() {
364         log.debug(this + ": min first step = " + nextStep.totalLength);
365         return nextStep.totalLength;
366     }
367 
368     /**
369      * Returns the last step for this cell. This includes the normal border- and
370      * padding-before, the whole content, the normal padding-after, and the
371      * <em>trailing</em> after border. Indeed, if the normal border is taken instead,
372      * and appears to be smaller than the trailing one, the last step may be smaller than
373      * the current step (see TableStepper#considerRowLastStep). This will produce a wrong
374      * infinite penalty, plus the cell's content won't be taken into account since the
375      * final step will be smaller than the current one (see {@link #signalNextStep(int)}).
376      * This actually means that the content will be swallowed.
377      *
378      * @return the length of last step
379      */
getLastStep()380     int getLastStep() {
381         assert nextStep.end == elementList.size() - 1;
382         assert nextStep.contentLength == totalLength && nextStep.penaltyLength == 0;
383         int lastStep = bpBeforeNormal + totalLength + paddingAfterNormal
384                 + pgu.getAfterBorderWidth(ConditionalBorder.LEADING_TRAILING);
385         log.debug(this + ": last step = " + lastStep);
386         return lastStep;
387     }
388 
389     /**
390      * Increases the next step up to the given limit.
391      *
392      * @param limit the length up to which the next step is allowed to increase
393      * @see #signalRowFirstStep(int)
394      * @see #signalRowLastStep(int)
395      */
increaseCurrentStep(int limit)396     private void increaseCurrentStep(int limit) {
397         if (nextStep.end < elementList.size() - 1) {
398             while (afterNextStep.totalLength <= limit && nextStep.breakClass == Constants.EN_AUTO) {
399                 int condBeforeContentLength = nextStep.condBeforeContentLength;
400                 nextStep.set(afterNextStep);
401                 nextStep.condBeforeContentLength = condBeforeContentLength;
402                 if (afterNextStep.end >= elementList.size() - 1) {
403                     break;
404                 }
405                 gotoNextLegalBreak();
406             }
407         }
408     }
409 
410     /**
411      * Gets the selected first step for the current row. If this cell's first step is
412      * smaller, then it may be able to add some more of its content, since there will be
413      * no break before the given step anyway.
414      *
415      * @param firstStep the current row's first step
416      */
signalRowFirstStep(int firstStep)417     void signalRowFirstStep(int firstStep) {
418         increaseCurrentStep(firstStep);
419         if (log.isTraceEnabled()) {
420             log.trace(this + ": first step increased to " + nextStep.totalLength);
421         }
422     }
423 
424     /** See {@link #signalRowFirstStep(int)}. */
signalRowLastStep(int lastStep)425     void signalRowLastStep(int lastStep) {
426         increaseCurrentStep(lastStep);
427         if (log.isTraceEnabled()) {
428             log.trace(this + ": next step increased to " + nextStep.totalLength);
429         }
430     }
431 
432     /**
433      * Returns the total length up to the next legal break, not yet included in the steps.
434      *
435      * @return the total length up to the next legal break (-1 signals no further step)
436      */
getNextStep()437     int getNextStep() {
438         if (includedInLastStep()) {
439             previousStep.set(nextStep);
440             if (nextStep.end >= elementList.size() - 1) {
441                 nextStep.start = elementList.size();
442                 return -1;
443             } else {
444                 nextStep.set(afterNextStep);
445                 nextStep.start = previousStep.end + 1;
446                 afterNextStep.start = nextStep.start;
447                 if (afterNextStep.end < elementList.size() - 1) {
448                     gotoNextLegalBreak();
449                 }
450             }
451         }
452         return nextStep.totalLength;
453     }
454 
includedInLastStep()455     private boolean includedInLastStep() {
456         return includedLength == nextStep.contentLength;
457     }
458 
459     /**
460      * Signals the length of the chosen next step, so that this cell determines whether
461      * its own step may be included or not.
462      *
463      * @param minStep length of the chosen next step
464      * @return the break class of the step, if any. One of {@link Constants#EN_AUTO},
465      * {@link Constants#EN_COLUMN}, {@link Constants#EN_PAGE},
466      * {@link Constants#EN_EVEN_PAGE}, {@link Constants#EN_ODD_PAGE}. EN_AUTO if this
467      * cell's step is not included in the next step.
468      */
signalNextStep(int minStep)469     int signalNextStep(int minStep) {
470         if (nextStep.totalLength <= minStep) {
471             includedLength = nextStep.contentLength;
472             remainingLength = totalLength - includedLength - afterNextStep.condBeforeContentLength;
473             return nextStep.breakClass;
474         } else {
475             return Constants.EN_AUTO;
476         }
477     }
478 
479     /**
480      * Receives indication that the next row is about to start, and that (collapse)
481      * borders must be updated accordingly.
482      */
nextRowStarts()483     void nextRowStarts() {
484         spanIndex++;
485         // Subtract the old value of bpAfterTrailing...
486         nextStep.totalLength -= bpAfterTrailing;
487         afterNextStep.totalLength -= bpAfterTrailing;
488 
489         bpAfterTrailing = paddingAfterTrailing
490                 + pgu.getAfterBorderWidth(spanIndex, ConditionalBorder.REST);
491 
492         // ... and add the new one
493         nextStep.totalLength += bpAfterTrailing;
494         afterNextStep.totalLength += bpAfterTrailing;
495         // TODO if the new after border is greater than the previous one the next step may
496         // increase further than the row's first step, which can lead to wrong output in
497         // some cases
498     }
499 
500     /**
501      * Receives indication that the current row is ending, and that (collapse) borders
502      * must be updated accordingly.
503      *
504      * @param rowIndex the index of the ending row
505      */
endRow(int rowIndex)506     void endRow(int rowIndex) {
507         if (endsOnRow(rowIndex)) {
508             // Subtract the old value of bpAfterTrailing...
509             nextStep.totalLength -= bpAfterTrailing;
510             bpAfterTrailing = paddingAfterNormal
511                     + pgu.getAfterBorderWidth(ConditionalBorder.LEADING_TRAILING);
512             // ... and add the new one
513             nextStep.totalLength += bpAfterTrailing;
514             lastCellPart = true;
515         } else {
516             bpBeforeLeading = paddingBeforeLeading
517                     + pgu.getBeforeBorderWidth(spanIndex + 1, ConditionalBorder.REST);
518         }
519     }
520 
521     /**
522      * Returns true if this cell would be finished after the given step. That is, it would
523      * be included in the step and the end of its content would be reached.
524      *
525      * @param step the next step
526      * @return true if this cell finishes at the given step
527      */
finishes(int step)528     boolean finishes(int step) {
529         return nextStep.totalLength <= step && (nextStep.end == elementList.size() - 1);
530     }
531 
532     /**
533      * Creates and returns a CellPart instance for the content of this cell which
534      * is included in the next step.
535      *
536      * @return a CellPart instance
537      */
createCellPart()538     CellPart createCellPart() {
539         if (nextStep.end + 1 == elementList.size()) {
540             keepWithNext = pgu.getKeepWithNext();
541             // TODO if keep-with-next is set on the row, must every cell of the row
542             // contribute some content from children blocks?
543             // see http://mail-archives.apache.org/mod_mbox/xmlgraphics-fop-dev/200802.mbox/
544             // %3c47BDA379.4050606@anyware-tech.com%3e
545             // Assuming no, but if yes the following code should enable this behaviour
546 //            if (pgu.getRow() != null && pgu.getRow().mustKeepWithNext()) {
547 //                keepWithNextSignal = true; //to be converted to integer strengths
548 //            }
549         }
550         int bpBeforeFirst;
551         if (nextStep.start == 0) {
552             bpBeforeFirst = pgu.getBeforeBorderWidth(0, ConditionalBorder.LEADING_TRAILING)
553                     + paddingBeforeNormal;
554         } else {
555             bpBeforeFirst = bpBeforeLeading;
556         }
557         int length = nextStep.contentLength - nextStep.condBeforeContentLength
558                 - previousStep.contentLength;
559         if (!includedInLastStep() || nextStep.start == elementList.size()) {
560             return new CellPart(pgu, nextStep.start, previousStep.end, lastCellPart,
561                     0, 0, previousStep.penaltyLength,
562                     bpBeforeNormal, bpBeforeFirst, bpAfterNormal, bpAfterTrailing);
563         } else {
564             return new CellPart(pgu, nextStep.start, nextStep.end, lastCellPart,
565                     nextStep.condBeforeContentLength, length, nextStep.penaltyLength,
566                     bpBeforeNormal, bpBeforeFirst, bpAfterNormal, bpAfterTrailing);
567         }
568     }
569 
570     /**
571      * Adds the footnotes (if any) that are part of the next step, if this cell
572      * contributes content to the next step.
573      *
574      * @param footnoteList the list to which this cell must add its footnotes
575      */
addFootnotes(List footnoteList)576     void addFootnotes(List footnoteList) {
577         if (includedInLastStep() && nextStep.footnoteList != null) {
578             footnoteList.addAll(nextStep.footnoteList);
579             nextStep.footnoteList.clear();
580         }
581     }
582 
getKeepWithNext()583     Keep getKeepWithNext() {
584         return keepWithNext;
585     }
586 
getPenaltyValue()587     int getPenaltyValue() {
588         if (includedInLastStep()) {
589             return nextStep.penaltyValue;
590         } else {
591             return previousStep.penaltyValue;
592         }
593     }
594 
595     /** {@inheritDoc} */
toString()596     public String toString() {
597         return "Cell " + (pgu.getRowIndex() + 1) + "." + (pgu.getColIndex() + 1);
598     }
599 }
600