KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > fop > layoutmgr > table > TableStepper


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: TableStepper.java 453900 2006-10-07 13:25:32Z jeremias $ */
19
20 package org.apache.fop.layoutmgr.table;
21
22 import java.util.Arrays JavaDoc;
23 import java.util.LinkedList JavaDoc;
24 import java.util.List JavaDoc;
25
26 import org.apache.commons.logging.Log;
27 import org.apache.commons.logging.LogFactory;
28 import org.apache.fop.fo.Constants;
29 import org.apache.fop.fo.FONode;
30 import org.apache.fop.fo.flow.TableRow;
31 import org.apache.fop.layoutmgr.BreakElement;
32 import org.apache.fop.layoutmgr.ElementListUtils;
33 import org.apache.fop.layoutmgr.KnuthBox;
34 import org.apache.fop.layoutmgr.KnuthElement;
35 import org.apache.fop.layoutmgr.KnuthPenalty;
36 import org.apache.fop.layoutmgr.LayoutContext;
37 import org.apache.fop.layoutmgr.table.TableContentLayoutManager.GridUnitPart;
38 import org.apache.fop.layoutmgr.table.TableContentLayoutManager.TableContentPosition;
39 import org.apache.fop.layoutmgr.table.TableContentLayoutManager.TableHFPenaltyPosition;
40
41 /**
42  * This class processes row groups to create combined element lists for tables.
43  */

44 public class TableStepper {
45
46     /** Logger **/
47     private static Log log = LogFactory.getLog(TableStepper.class);
48
49     private TableContentLayoutManager tclm;
50     
51     private EffRow[] rowGroup;
52     private int totalHeight;
53     private int activeRow;
54     private List JavaDoc[] elementLists;
55     private int[] startRow;
56     private int[] start;
57     private int[] end;
58     private int[] widths;
59     private int[] baseWidth;
60     private int[] borderBefore;
61     private int[] paddingBefore;
62     private int[] borderAfter;
63     private int[] paddingAfter;
64     private boolean rowBacktrackForLastStep;
65     private boolean skippedStep;
66     private boolean[] keepWithNextSignals;
67     private boolean[] forcedBreaks;
68     private int lastMaxPenalty;
69     
70     /**
71      * Main constructor
72      * @param tclm The parent TableContentLayoutManager
73      */

74     public TableStepper(TableContentLayoutManager tclm) {
75         this.tclm = tclm;
76     }
77     
78     private void setup(int columnCount) {
79         this.activeRow = 0;
80         elementLists = new List JavaDoc[columnCount];
81         startRow = new int[columnCount];
82         start = new int[columnCount];
83         end = new int[columnCount];
84         widths = new int[columnCount];
85         baseWidth = new int[columnCount];
86         borderBefore = new int[columnCount];
87         paddingBefore = new int[columnCount];
88         borderAfter = new int[columnCount];
89         paddingAfter = new int[columnCount];
90         keepWithNextSignals = new boolean[columnCount];
91         forcedBreaks = new boolean[columnCount];
92         Arrays.fill(end, -1);
93     }
94     
95     private void clearBreakCondition() {
96         Arrays.fill(forcedBreaks, false);
97     }
98     
99     private boolean isBreakCondition() {
100         for (int i = 0; i < forcedBreaks.length; i++) {
101             if (forcedBreaks[i]) {
102                 return true;
103             }
104         }
105         return false;
106     }
107     
108     private EffRow getActiveRow() {
109         return rowGroup[activeRow];
110     }
111     
112     private GridUnit getActiveGridUnit(int column) {
113         return getActiveRow().safelyGetGridUnit(column);
114     }
115     
116     private PrimaryGridUnit getActivePrimaryGridUnit(int column) {
117         GridUnit gu = getActiveGridUnit(column);
118         if (gu == null) {
119             return null;
120         } else {
121             return gu.getPrimary();
122         }
123     }
124     
125     private void calcTotalHeight() {
126         totalHeight = 0;
127         for (int i = 0; i < rowGroup.length; i++) {
128             totalHeight += rowGroup[i].getHeight().opt;
129         }
130         log.debug("totalHeight=" + totalHeight);
131     }
132     
133     private int getMaxRemainingHeight() {
134         int maxW = 0;
135         if (!rowBacktrackForLastStep) {
136             for (int i = 0; i < widths.length; i++) {
137                 if (elementLists[i] == null) {
138                     continue;
139                 }
140                 if (end[i] == elementLists[i].size() - 1) {
141                     continue;
142                 }
143                 GridUnit gu = getActiveGridUnit(i);
144                 if (!gu.isLastGridUnitRowSpan()) {
145                     continue;
146                 }
147                 int len = widths[i];
148                 if (len > 0) {
149                     len += 2 * getTableLM().getHalfBorderSeparationBPD();
150                     len += borderBefore[i] + borderAfter[i];
151                     len += paddingBefore[i] + paddingAfter[i];
152                 }
153                 int nominalHeight = rowGroup[activeRow].getHeight().opt;
154                 for (int r = 0; r < gu.getRowSpanIndex(); r++) {
155                     nominalHeight += rowGroup[activeRow - r - 1].getHeight().opt;
156                 }
157                 if (len == nominalHeight) {
158                     //row is filled
159
maxW = 0;
160                     break;
161                 }
162                 maxW = Math.max(maxW, nominalHeight - len);
163             }
164         }
165         for (int i = activeRow + 1; i < rowGroup.length; i++) {
166             maxW += rowGroup[i].getHeight().opt;
167         }
168         //log.debug("maxRemainingHeight=" + maxW);
169
return maxW;
170     }
171
172     private void setupElementList(int column) {
173         GridUnit gu = getActiveGridUnit(column);
174         EffRow row = getActiveRow();
175         if (gu == null || gu.isEmpty()) {
176             elementLists[column] = null;
177             start[column] = 0;
178             end[column] = -1;
179             widths[column] = 0;
180             startRow[column] = activeRow;
181             keepWithNextSignals[column] = false;
182             forcedBreaks[column] = false;
183         } else if (gu.isPrimary()) {
184             PrimaryGridUnit pgu = (PrimaryGridUnit)gu;
185             boolean makeBoxForWholeRow = false;
186             if (row.getExplicitHeight().min > 0) {
187                 boolean contentsSmaller = ElementListUtils.removeLegalBreaks(
188                         pgu.getElements(), row.getExplicitHeight());
189                 if (contentsSmaller) {
190                     makeBoxForWholeRow = true;
191                 }
192             }
193             if (pgu.isLastGridUnitRowSpan() && pgu.getRow() != null) {
194                 makeBoxForWholeRow |= pgu.getRow().mustKeepTogether();
195                 makeBoxForWholeRow |= pgu.getTable().mustKeepTogether();
196             }
197             if (makeBoxForWholeRow) {
198                 List JavaDoc list = new java.util.ArrayList JavaDoc(1);
199                 int height = row.getExplicitHeight().opt;
200                 if (height == 0) {
201                     height = row.getHeight().opt;
202                 }
203                 list.add(new KnuthBoxCellWithBPD(height, pgu));
204                 elementLists[column] = list;
205             } else {
206                 //Copy elements (LinkedList) to array lists to improve
207
//element access performance
208
elementLists[column] = new java.util.ArrayList JavaDoc(pgu.getElements());
209             }
210             if (isSeparateBorderModel()) {
211                 borderBefore[column] = pgu.getBorders().getBorderBeforeWidth(false);
212             } else {
213                 borderBefore[column] = pgu.getBorders().getBorderBeforeWidth(false) / 2;
214             }
215             paddingBefore[column] = pgu.getBorders().getPaddingBefore(false, pgu.getCellLM());
216             paddingAfter[column] = pgu.getBorders().getPaddingAfter(false, pgu.getCellLM());
217             start[column] = 0;
218             end[column] = -1;
219             widths[column] = 0;
220             startRow[column] = activeRow;
221             keepWithNextSignals[column] = false;
222             forcedBreaks[column] = false;
223         }
224     }
225     
226     private void initializeElementLists() {
227         for (int i = 0; i < start.length; i++) {
228             setupElementList(i);
229         }
230     }
231
232     /**
233      * Creates the combined element list for a row group.
234      * @param context Active LayoutContext
235      * @param rowGroup the row group
236      * @param maxColumnCount the maximum number of columns to expect
237      * @param bodyType Indicates what type of body is processed (boder, header or footer)
238      * @return the combined element list
239      */

240     public LinkedList JavaDoc getCombinedKnuthElementsForRowGroup(
241             LayoutContext context,
242             EffRow[] rowGroup, int maxColumnCount, int bodyType) {
243         this.rowGroup = rowGroup;
244         setup(maxColumnCount);
245         initializeElementLists();
246         calcTotalHeight();
247         
248         boolean signalKeepWithNext = false;
249         int laststep = 0;
250         int step;
251         int addedBoxLen = 0;
252         TableContentPosition lastTCPos = null;
253         LinkedList JavaDoc returnList = new LinkedList JavaDoc();
254         while ((step = getNextStep(laststep)) >= 0) {
255             int normalRow = activeRow;
256             if (rowBacktrackForLastStep) {
257                 //Even though we've already switched to the next row, we have to
258
//calculate as if we were still on the previous row
259
activeRow--;
260             }
261             int increase = step - laststep;
262             int penaltyLen = step + getMaxRemainingHeight() - totalHeight;
263             int boxLen = step - addedBoxLen - penaltyLen;
264             addedBoxLen += boxLen;
265
266             //Put all involved grid units into a list
267
List JavaDoc gridUnitParts = new java.util.ArrayList JavaDoc(maxColumnCount);
268             for (int i = 0; i < start.length; i++) {
269                 if (end[i] >= start[i]) {
270                     PrimaryGridUnit pgu = rowGroup[startRow[i]].getGridUnit(i).getPrimary();
271                     if (start[i] == 0 && end[i] == 0
272                             && elementLists[i].size() == 1
273                             && elementLists[i].get(0) instanceof KnuthBoxCellWithBPD) {
274                         //Special case: Cell with fixed BPD
275
gridUnitParts.add(new GridUnitPart(pgu,
276                                 0, pgu.getElements().size() - 1));
277                     } else {
278                         gridUnitParts.add(new GridUnitPart(pgu, start[i], end[i]));
279                     }
280                     if (end[i] + 1 == elementLists[i].size()) {
281                         if (pgu.getFlag(GridUnit.KEEP_WITH_NEXT_PENDING)) {
282                             log.debug("PGU has pending keep-with-next");
283                             keepWithNextSignals[i] = true;
284                         }
285                         if (pgu.getRow() != null && pgu.getRow().mustKeepWithNext()) {
286                             log.debug("table-row causes keep-with-next");
287                             keepWithNextSignals[i] = true;
288                         }
289                     }
290                     if (start[i] == 0 && end[i] >= 0) {
291                         if (pgu.getFlag(GridUnit.KEEP_WITH_PREVIOUS_PENDING)) {
292                             log.debug("PGU has pending keep-with-previous");
293                             if (returnList.size() == 0) {
294                                 context.setFlags(LayoutContext.KEEP_WITH_PREVIOUS_PENDING);
295                             }
296                         }
297                         if (pgu.getRow() != null && pgu.getRow().mustKeepWithPrevious()) {
298                             log.debug("table-row causes keep-with-previous");
299                             if (returnList.size() == 0) {
300                                 context.setFlags(LayoutContext.KEEP_WITH_PREVIOUS_PENDING);
301                             }
302                         }
303                     }
304                 }
305             }
306             //log.debug(">>> guPARTS: " + gridUnitParts);
307

308             //Create elements for step
309
int effPenaltyLen = penaltyLen;
310             TableContentPosition tcpos = new TableContentPosition(getTableLM(),
311                     gridUnitParts, rowGroup[normalRow]);
312             if (returnList.size() == 0) {
313                 tcpos.setFlag(TableContentPosition.FIRST_IN_ROWGROUP, true);
314             }
315             lastTCPos = tcpos;
316             if (log.isDebugEnabled()) {
317                 log.debug(" - backtrack=" + rowBacktrackForLastStep
318                         + " - row=" + activeRow + " - " + tcpos);
319             }
320             returnList.add(new KnuthBox(boxLen, tcpos, false));
321             TableHFPenaltyPosition penaltyPos = new TableHFPenaltyPosition(getTableLM());
322             if (bodyType == TableRowIterator.BODY) {
323                 if (!getTableLM().getTable().omitHeaderAtBreak()) {
324                     effPenaltyLen += tclm.getHeaderNetHeight();
325                     penaltyPos.headerElements = tclm.getHeaderElements();
326                 }
327                 if (!getTableLM().getTable().omitFooterAtBreak()) {
328                     effPenaltyLen += tclm.getFooterNetHeight();
329                     penaltyPos.footerElements = tclm.getFooterElements();
330                 }
331             }
332             
333             //Handle a penalty length coming from nested content
334
//Example: nested table with header/footer
335
if (this.lastMaxPenalty != 0) {
336                 penaltyPos.nestedPenaltyLength = this.lastMaxPenalty;
337                 if (log.isDebugEnabled()) {
338                     log.debug("Additional penalty length from table-cell break: "
339                             + this.lastMaxPenalty);
340                 }
341             }
342             effPenaltyLen += this.lastMaxPenalty;
343             
344             int p = 0;
345             boolean allCellsHaveContributed = true;
346             signalKeepWithNext = false;
347             for (int i = 0; i < start.length; i++) {
348                 if (start[i] == 0 && end[i] < 0 && elementLists[i] != null) {
349                     allCellsHaveContributed = false;
350                 }
351                 signalKeepWithNext |= keepWithNextSignals[i];
352             }
353             if (!allCellsHaveContributed) {
354                 //Not all cells have contributed to a newly started row. The penalty here is
355
//used to avoid breaks resulting in badly broken tables.
356
//See also: http://marc.theaimsgroup.com/?t=112248999600005&r=1&w=2
357
p = 900; //KnuthPenalty.INFINITE; //TODO Arbitrary value. Please refine.
358
}
359             if (signalKeepWithNext || getTableLM().mustKeepTogether()) {
360                 p = KnuthPenalty.INFINITE;
361             }
362             if (skippedStep) {
363                 p = KnuthPenalty.INFINITE;
364                 //Need to avoid breaking because borders and/or paddding from other columns would
365
//not fit in the available space (see getNextStep())
366
}
367             if (isBreakCondition()) {
368                 if (skippedStep) {
369                     log.error("This is a conflict situation. The output may be wrong."
370                             + " Please send your FO file to fop-dev@xmlgraphics.apache.org!");
371                 }
372                 p = -KnuthPenalty.INFINITE; //Overrides any keeps (see 4.8 in XSL 1.0)
373
clearBreakCondition();
374             }
375             returnList.add(new BreakElement(penaltyPos, effPenaltyLen, p, -1, context));
376
377             if (log.isDebugEnabled()) {
378                 log.debug("step=" + step + " (+" + increase + ")"
379                         + " box=" + boxLen
380                         + " penalty=" + penaltyLen
381                         + " effPenalty=" + effPenaltyLen);
382             }
383             
384             laststep = step;
385             if (rowBacktrackForLastStep) {
386                 //If row was set to previous, restore now
387
activeRow++;
388             }
389         }
390         if (signalKeepWithNext) {
391             //Last step signalled a keep-with-next. Since the last penalty will be removed,
392
//we have to signal the still pending last keep-with-next using the LayoutContext.
393
context.setFlags(LayoutContext.KEEP_WITH_NEXT_PENDING);
394         }
395         if (isBreakCondition()) {
396             ((BreakElement)returnList.getLast()).setPenaltyValue(-KnuthPenalty.INFINITE);
397         }
398         if (lastTCPos != null) {
399             lastTCPos.setFlag(TableContentPosition.LAST_IN_ROWGROUP, true);
400         }
401         return returnList;
402     }
403     
404     private int getNextStep(int lastStep) {
405         this.lastMaxPenalty = 0;
406         //Check for forced break conditions
407
/*
408         if (isBreakCondition()) {
409             return -1;
410         }*/

411         
412         int[] backupWidths = new int[start.length];
413         System.arraycopy(widths, 0, backupWidths, 0, backupWidths.length);
414
415         //set starting points
416
int rowPendingIndicator = 0;
417         for (int i = 0; i < start.length; i++) {
418             if (elementLists[i] == null) {
419                 continue;
420             }
421             if (end[i] < elementLists[i].size()) {
422                 start[i] = end[i] + 1;
423                 if (end[i] + 1 < elementLists[i].size()
424                         && getActiveGridUnit(i).isLastGridUnitRowSpan()) {
425                     rowPendingIndicator++;
426                 }
427             } else {
428                 start[i] = -1; //end of list reached
429
end[i] = -1;
430             }
431         }
432
433         if (rowPendingIndicator == 0) {
434             if (activeRow < rowGroup.length - 1) {
435                 TableRow rowFO = getActiveRow().getTableRow();
436                 if (rowFO != null && rowFO.getBreakAfter() != Constants.EN_AUTO) {
437                     log.warn(FONode.decorateWithContextInfo(
438                             "break-after ignored on table-row because of row spanning "
439                             + "in progress (See XSL 1.0, 7.19.1)", rowFO));
440                 }
441                 activeRow++;
442                 if (log.isDebugEnabled()) {
443                     log.debug("===> new row: " + activeRow);
444                 }
445                 initializeElementLists();
446                 for (int i = 0; i < backupWidths.length; i++) {
447                     if (end[i] < 0) {
448                         backupWidths[i] = 0;
449                     }
450                 }
451                 rowFO = getActiveRow().getTableRow();
452                 if (rowFO != null && rowFO.getBreakBefore() != Constants.EN_AUTO) {
453                     log.warn(FONode.decorateWithContextInfo(
454                             "break-before ignored on table-row because of row spanning "
455                             + "in progress (See XSL 1.0, 7.19.2)", rowFO));
456                 }
457             }
458         }
459
460         //Get next possible sequence for each cell
461
int seqCount = 0;
462         for (int i = 0; i < start.length; i++) {
463             if (elementLists[i] == null) {
464                 continue;
465             }
466             while (end[i] + 1 < elementLists[i].size()) {
467                 end[i]++;
468                 KnuthElement el = (KnuthElement)elementLists[i].get(end[i]);
469                 if (el.isPenalty()) {
470                     this.lastMaxPenalty = Math.max(this.lastMaxPenalty, el.getW());
471                     if (el.getP() <= -KnuthElement.INFINITE) {
472                         log.debug("FORCED break encountered!");
473                         forcedBreaks[i] = true;
474                         break;
475                     } else if (el.getP() < KnuthElement.INFINITE) {
476                         //First legal break point
477
break;
478                     }
479                 } else if (el.isGlue()) {
480                     if (end[i] > 0) {
481                         KnuthElement prev = (KnuthElement)elementLists[i].get(end[i] - 1);
482                         if (prev.isBox()) {
483                             //Second legal break point
484
break;
485                         }
486                     }
487                     widths[i] += el.getW();
488                 } else {
489                     widths[i] += el.getW();
490                 }
491             }
492             if (end[i] < start[i]) {
493                 widths[i] = backupWidths[i];
494             } else {
495                 seqCount++;
496             }
497             //log.debug("part " + start[i] + "-" + end[i] + " " + widths[i]);
498
if (end[i] + 1 >= elementLists[i].size()) {
499                 //element list for this cell is finished
500
if (isSeparateBorderModel()) {
501                     borderAfter[i] = getActivePrimaryGridUnit(i)
502                             .getBorders().getBorderAfterWidth(false);
503                 } else {
504                     borderAfter[i] = getActivePrimaryGridUnit(i).getHalfMaxAfterBorderWidth();
505                 }
506             } else {
507                 //element list for this cell is not finished
508
if (isSeparateBorderModel()) {
509                     borderAfter[i] = getActivePrimaryGridUnit(i)
510                             .getBorders().getBorderAfterWidth(false);
511                 } else {
512                     //TODO fix me!
513
borderAfter[i] = getActivePrimaryGridUnit(i).getHalfMaxAfterBorderWidth();
514                 }
515             }
516             if (log.isTraceEnabled()) {
517                 log.trace("borders before=" + borderBefore[i] + " after=" + borderAfter[i]);
518                 log.trace("padding before=" + paddingBefore[i] + " after=" + paddingAfter[i]);
519             }
520         }
521         if (seqCount == 0) {
522             return -1;
523         }
524
525         //Determine smallest possible step
526
int minStep = Integer.MAX_VALUE;
527         StringBuffer JavaDoc sb = new StringBuffer JavaDoc();
528         for (int i = 0; i < widths.length; i++) {
529             baseWidth[i] = 0;
530             for (int prevRow = 0; prevRow < startRow[i]; prevRow++) {
531                 baseWidth[i] += rowGroup[prevRow].getHeight().opt;
532             }
533             baseWidth[i] += 2 * getTableLM().getHalfBorderSeparationBPD();
534             baseWidth[i] += borderBefore[i] + borderAfter[i];
535             baseWidth[i] += paddingBefore[i] + paddingAfter[i];
536             if (end[i] >= start[i]) {
537                 int len = baseWidth[i] + widths[i];
538                 sb.append(len + " ");
539                 minStep = Math.min(len, minStep);
540             }
541         }
542         if (log.isDebugEnabled()) {
543             log.debug("candidate steps: " + sb + " lastStep=" + lastStep);
544         }
545
546         //Check for constellations that would result in overlapping borders
547
/*
548         for (int i = 0; i < widths.length; i++) {
549             
550         }*/

551         
552         //Reset bigger-than-minimum sequences
553
//See http://people.apache.org/~jeremias/fop/NextStepAlgoNotes.pdf
554
rowBacktrackForLastStep = false;
555         skippedStep = false;
556         for (int i = 0; i < widths.length; i++) {
557             int len = baseWidth[i] + widths[i];
558             if (len > minStep) {
559                 widths[i] = backupWidths[i];
560                 end[i] = start[i] - 1;
561                 if (baseWidth[i] + widths[i] > minStep) {
562                     log.debug("minStep vs. border/padding increase conflict:");
563                     if (activeRow == 0) {
564                         log.debug(" First row. Skip this step.");
565                         skippedStep = true;
566                     } else {
567                         log.debug(" row-span situation: backtracking to last row");
568                         //Stay on the previous row for another step because borders and padding on
569
//columns may make their contribution to the step bigger than the addition
570
//of the next element for this step would make the step to grow.
571
rowBacktrackForLastStep = true;
572                     }
573                 }
574             }
575         }
576         if (log.isDebugEnabled()) {
577             /*StringBuffer*/ sb = new StringBuffer JavaDoc();
578             for (int i = 0; i < widths.length; i++) {
579                 if (end[i] >= start[i]) {
580                     sb.append(i + ": " + start[i] + "-" + end[i] + "(" + widths[i] + "), ");
581                 } else {
582                     sb.append(i + ": skip, ");
583                 }
584             }
585             log.debug(sb.toString());
586         }
587
588         return minStep;
589     }
590     
591     
592     /** @return true if the table uses the separate border model. */
593     private boolean isSeparateBorderModel() {
594         return getTableLM().getTable().isSeparateBorderModel();
595     }
596
597     /** @return the table layout manager */
598     private TableLayoutManager getTableLM() {
599         return this.tclm.getTableLM();
600     }
601
602     private class KnuthBoxCellWithBPD extends KnuthBox {
603         
604         private PrimaryGridUnit pgu;
605         
606         public KnuthBoxCellWithBPD(int w, PrimaryGridUnit pgu) {
607             super(w, null, true);
608             this.pgu = pgu;
609         }
610     }
611     
612 }
613
Popular Tags