123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513 |
- /*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
- /* $Id$ */
-
- package org.apache.fop.layoutmgr.table;
-
- import java.util.Iterator;
- import java.util.LinkedList;
- import java.util.List;
-
- import org.apache.commons.logging.Log;
- import org.apache.commons.logging.LogFactory;
-
- import org.apache.fop.fo.Constants;
- import org.apache.fop.fo.flow.table.EffRow;
- import org.apache.fop.fo.flow.table.GridUnit;
- import org.apache.fop.fo.flow.table.PrimaryGridUnit;
- import org.apache.fop.layoutmgr.BreakElement;
- import org.apache.fop.layoutmgr.Keep;
- import org.apache.fop.layoutmgr.KnuthBlockBox;
- import org.apache.fop.layoutmgr.KnuthBox;
- import org.apache.fop.layoutmgr.KnuthGlue;
- import org.apache.fop.layoutmgr.KnuthPenalty;
- import org.apache.fop.layoutmgr.LayoutContext;
- import org.apache.fop.layoutmgr.Position;
- import org.apache.fop.util.BreakUtil;
-
- /**
- * This class processes row groups to create combined element lists for tables.
- */
- public class TableStepper {
-
- /** Logger **/
- private static Log log = LogFactory.getLog(TableStepper.class);
-
- private TableContentLayoutManager tclm;
-
- private EffRow[] rowGroup;
- /** Number of columns in the row group. */
- private int columnCount;
- private int totalHeight;
- private int previousRowsLength;
- private int activeRowIndex;
-
- private boolean rowFinished;
-
- /** Cells spanning the current row. */
- private List activeCells = new LinkedList();
-
- /** Cells that will start the next row. */
- private List nextActiveCells = new LinkedList();
-
- /**
- * True if the next row is being delayed, that is, if cells spanning the current and
- * the next row have steps smaller than the next row's first step. In this case the
- * next row may be extended to offer additional break possibilities.
- */
- private boolean delayingNextRow;
-
- /**
- * The first step for a row. This is the minimal step necessary to include some
- * content from all the cells starting the row.
- */
- private int rowFirstStep;
-
- /**
- * Flag used to produce an infinite penalty if the height of the current row is
- * smaller than the first step for that row (may happen with row-spanning cells).
- *
- * @see #considerRowLastStep(int)
- */
- private boolean rowHeightSmallerThanFirstStep;
-
- /**
- * The class of the next break. One of {@link Constants#EN_AUTO},
- * {@link Constants#EN_COLUMN}, {@link Constants#EN_PAGE},
- * {@link Constants#EN_EVEN_PAGE}, {@link Constants#EN_ODD_PAGE}. Defaults to
- * EN_AUTO.
- */
- private int nextBreakClass;
-
- /**
- * Main constructor
- * @param tclm The parent TableContentLayoutManager
- */
- public TableStepper(TableContentLayoutManager tclm) {
- this.tclm = tclm;
- this.columnCount = tclm.getTableLM().getTable().getNumberOfColumns();
- }
-
- /**
- * Initializes the fields of this instance to handle a new row group.
- *
- * @param rows the new row group to handle
- */
- private void setup(EffRow[] rows) {
- rowGroup = rows;
- previousRowsLength = 0;
- activeRowIndex = 0;
- activeCells.clear();
- nextActiveCells.clear();
- delayingNextRow = false;
- rowFirstStep = 0;
- rowHeightSmallerThanFirstStep = false;
- }
-
- private void calcTotalHeight() {
- totalHeight = 0;
- for (EffRow aRowGroup : rowGroup) {
- totalHeight += aRowGroup.getHeight().getOpt();
- }
- if (log.isDebugEnabled()) {
- log.debug("totalHeight=" + totalHeight);
- }
- }
-
- private int getMaxRemainingHeight() {
- int maxW = 0;
- for (Object activeCell1 : activeCells) {
- ActiveCell activeCell = (ActiveCell) activeCell1;
- int remain = activeCell.getRemainingLength();
- PrimaryGridUnit pgu = activeCell.getPrimaryGridUnit();
- for (int i = activeRowIndex + 1; i < pgu.getRowIndex() - rowGroup[0].getIndex()
- + pgu.getCell().getNumberRowsSpanned(); i++) {
- remain -= rowGroup[i].getHeight().getOpt();
- }
- maxW = Math.max(maxW, remain);
- }
- for (int i = activeRowIndex + 1; i < rowGroup.length; i++) {
- maxW += rowGroup[i].getHeight().getOpt();
- }
- return maxW;
- }
-
- /**
- * Creates ActiveCell instances for cells starting on the row at the given index.
- *
- * @param activeCellList the list that will hold the active cells
- * @param rowIndex the index of the row from which cells must be activated
- */
- private void activateCells(List activeCellList, int rowIndex) {
- EffRow row = rowGroup[rowIndex];
- for (int i = 0; i < columnCount; i++) {
- GridUnit gu = row.getGridUnit(i);
- if (!gu.isEmpty() && gu.isPrimary()) {
- assert (gu instanceof PrimaryGridUnit);
- activeCellList.add(new ActiveCell((PrimaryGridUnit) gu, row, rowIndex,
- previousRowsLength, getTableLM()));
- }
- }
- }
-
- /**
- * Creates the combined element list for a row group.
- * @param context Active LayoutContext
- * @param rows the row group
- * @param bodyType Indicates what type of body is processed (body, header or footer)
- * @return the combined element list
- */
- public LinkedList getCombinedKnuthElementsForRowGroup(LayoutContext context, EffRow[] rows,
- int bodyType) {
- setup(rows);
- activateCells(activeCells, 0);
- calcTotalHeight();
-
- int cumulateLength = 0; // Length of the content accumulated before the break
- TableContentPosition lastTCPos = null;
- LinkedList returnList = new LinkedList();
- int laststep = 0;
- int step = getFirstStep();
- do {
- int maxRemainingHeight = getMaxRemainingHeight();
- int penaltyOrGlueLen = step + maxRemainingHeight - totalHeight;
- int boxLen = step - cumulateLength - Math.max(0, penaltyOrGlueLen)/* penalty, if any */;
- cumulateLength += boxLen + Math.max(0, -penaltyOrGlueLen)/* the glue, if any */;
-
- if (log.isDebugEnabled()) {
- log.debug("Next step: " + step + " (+" + (step - laststep) + ")");
- log.debug(" max remaining height: " + maxRemainingHeight);
- if (penaltyOrGlueLen >= 0) {
- log.debug(" box = " + boxLen + " penalty = " + penaltyOrGlueLen);
- } else {
- log.debug(" box = " + boxLen + " glue = " + (-penaltyOrGlueLen));
- }
- }
-
- LinkedList footnoteList = new LinkedList();
- //Put all involved grid units into a list
- List cellParts = new java.util.ArrayList(activeCells.size());
- for (Object activeCell2 : activeCells) {
- ActiveCell activeCell = (ActiveCell) activeCell2;
- CellPart part = activeCell.createCellPart();
- cellParts.add(part);
- activeCell.addFootnotes(footnoteList);
- }
-
- //Create elements for step
- TableContentPosition tcpos = new TableContentPosition(getTableLM(),
- cellParts, rowGroup[activeRowIndex]);
- if (delayingNextRow) {
- tcpos.setNewPageRow(rowGroup[activeRowIndex + 1]);
- }
- if (returnList.size() == 0) {
- tcpos.setFlag(TableContentPosition.FIRST_IN_ROWGROUP, true);
- }
- lastTCPos = tcpos;
-
- // TODO TableStepper should remain as footnote-agnostic as possible
- if (footnoteList.isEmpty()) {
- returnList.add(new KnuthBox(boxLen, tcpos, false));
- } else {
- returnList.add(new KnuthBlockBox(boxLen, footnoteList, tcpos, false));
- }
-
- int effPenaltyLen = Math.max(0, penaltyOrGlueLen);
- TableHFPenaltyPosition penaltyPos = new TableHFPenaltyPosition(getTableLM());
- if (bodyType == TableRowIterator.BODY) {
- if (!getTableLM().getTable().omitHeaderAtBreak()) {
- effPenaltyLen += tclm.getHeaderNetHeight();
- penaltyPos.headerElements = tclm.getHeaderElements();
- }
- if (!getTableLM().getTable().omitFooterAtBreak()) {
- effPenaltyLen += tclm.getFooterNetHeight();
- penaltyPos.footerElements = tclm.getFooterElements();
- }
- }
-
- Keep keep = getTableLM().getKeepTogether();
- int stepPenalty = 0;
- for (Object activeCell1 : activeCells) {
- ActiveCell activeCell = (ActiveCell) activeCell1;
- keep = keep.compare(activeCell.getKeepWithNext());
- stepPenalty = Math.max(stepPenalty, activeCell.getPenaltyValue());
- }
- if (!rowFinished) {
- keep = keep.compare(rowGroup[activeRowIndex].getKeepTogether());
- } else if (activeRowIndex < rowGroup.length - 1) {
- keep = keep.compare(rowGroup[activeRowIndex].getKeepWithNext());
- keep = keep.compare(rowGroup[activeRowIndex + 1].getKeepWithPrevious());
- nextBreakClass = BreakUtil.compareBreakClasses(nextBreakClass,
- rowGroup[activeRowIndex].getBreakAfter());
- nextBreakClass = BreakUtil.compareBreakClasses(nextBreakClass,
- rowGroup[activeRowIndex + 1].getBreakBefore());
- }
- int p = keep.getPenalty();
- if (rowHeightSmallerThanFirstStep) {
- rowHeightSmallerThanFirstStep = false;
- p = KnuthPenalty.INFINITE;
- }
- p = Math.max(p, stepPenalty);
- int breakClass = keep.getContext();
- if (nextBreakClass != Constants.EN_AUTO) {
- log.trace("Forced break encountered");
- p = -KnuthPenalty.INFINITE; //Overrides any keeps (see 4.8 in XSL 1.0)
- breakClass = nextBreakClass;
- }
- returnList.add(new BreakElement(penaltyPos, effPenaltyLen, p, breakClass, context));
-
- laststep = step;
- step = getNextStep();
-
- if (penaltyOrGlueLen < 0) {
- if (step < 0) {
- returnList.add(new KnuthGlue(0, -penaltyOrGlueLen, 0, new Position(null), true));
- } else {
- returnList.add(new KnuthGlue(-penaltyOrGlueLen, 0, 0, new Position(null), true));
- }
- }
- } while (step >= 0);
- assert !returnList.isEmpty();
- lastTCPos.setFlag(TableContentPosition.LAST_IN_ROWGROUP, true);
- return returnList;
- }
-
- /**
- * Returns the first step for the current row group.
- *
- * @return the first step for the current row group
- */
- private int getFirstStep() {
- computeRowFirstStep(activeCells);
- signalRowFirstStep();
- int minStep = considerRowLastStep(rowFirstStep);
- signalNextStep(minStep);
- return minStep;
- }
-
- /**
- * Returns the next break possibility.
- *
- * @return the next step
- */
- private int getNextStep() {
- if (rowFinished) {
- if (activeRowIndex == rowGroup.length - 1) {
- // The row group is finished, no next step
- return -1;
- }
- rowFinished = false;
- removeCellsEndingOnCurrentRow();
- log.trace("Delaying next row");
- delayingNextRow = true;
- }
- if (delayingNextRow) {
- int minStep = computeMinStep();
- if (minStep < 0 || minStep >= rowFirstStep
- || minStep > rowGroup[activeRowIndex].getExplicitHeight().getMax()) {
- if (log.isTraceEnabled()) {
- log.trace("Step = " + minStep);
- }
- delayingNextRow = false;
- minStep = rowFirstStep;
- switchToNextRow();
- signalRowFirstStep();
- minStep = considerRowLastStep(minStep);
- }
- signalNextStep(minStep);
- return minStep;
- } else {
- int minStep = computeMinStep();
- minStep = considerRowLastStep(minStep);
- signalNextStep(minStep);
- return minStep;
- }
- }
-
- /**
- * Computes the minimal necessary step to make the next row fit. That is, so such as
- * cell on the next row can contribute some content.
- *
- * @param cells the cells occupying the next row (may include cells starting on
- * previous rows and spanning over this one)
- */
- private void computeRowFirstStep(List cells) {
- for (Object cell : cells) {
- ActiveCell activeCell = (ActiveCell) cell;
- rowFirstStep = Math.max(rowFirstStep, activeCell.getFirstStep());
- }
- }
-
- /**
- * Computes the next minimal step.
- *
- * @return the minimal step from the active cells, < 0 if there is no such step
- */
- private int computeMinStep() {
- int minStep = Integer.MAX_VALUE;
- boolean stepFound = false;
- for (Object activeCell1 : activeCells) {
- ActiveCell activeCell = (ActiveCell) activeCell1;
- int nextStep = activeCell.getNextStep();
- if (nextStep >= 0) {
- stepFound = true;
- minStep = Math.min(minStep, nextStep);
- }
- }
- if (stepFound) {
- return minStep;
- } else {
- return -1;
- }
- }
-
- /**
- * Signals the first step to the active cells, to allow them to add more content to
- * the step if possible.
- *
- * @see ActiveCell#signalRowFirstStep(int)
- */
- private void signalRowFirstStep() {
- for (Object activeCell1 : activeCells) {
- ActiveCell activeCell = (ActiveCell) activeCell1;
- activeCell.signalRowFirstStep(rowFirstStep);
- }
- }
-
- /**
- * Signals the next selected step to the active cells.
- *
- * @param step the next step
- */
- private void signalNextStep(int step) {
- nextBreakClass = Constants.EN_AUTO;
- for (Object activeCell1 : activeCells) {
- ActiveCell activeCell = (ActiveCell) activeCell1;
- nextBreakClass = BreakUtil.compareBreakClasses(nextBreakClass,
- activeCell.signalNextStep(step));
- }
- }
-
- /**
- * Determines if the given step will finish the current row, and if so switch to the
- * last step for this row.
- * <p>If the row is finished then the after borders for the cell may change (their
- * conditionalities no longer apply for the cells ending on the current row). Thus the
- * final step may grow with respect to the given one.</p>
- * <p>In more rare occasions, the given step may correspond to the first step of a
- * row-spanning cell, and may be greater than the height of the current row (consider,
- * for example, an unbreakable cell spanning three rows). In such a case the returned
- * step will correspond to the row height and a flag will be set to produce an
- * infinite penalty for this step. This will prevent the breaking algorithm from
- * choosing this break, but still allow to create the appropriate TableContentPosition
- * for the cells ending on the current row.</p>
- *
- * @param step the next step
- * @return the updated step if any
- */
- private int considerRowLastStep(int step) {
- rowFinished = true;
- for (Object activeCell3 : activeCells) {
- ActiveCell activeCell = (ActiveCell) activeCell3;
- if (activeCell.endsOnRow(activeRowIndex)) {
- if (!activeCell.finishes(step)) {
- rowFinished = false;
- }
- }
- }
- if (rowFinished) {
- if (log.isTraceEnabled()) {
- log.trace("Step = " + step);
- log.trace("Row finished, computing last step");
- }
- int maxStep = 0;
- for (Object activeCell2 : activeCells) {
- ActiveCell activeCell = (ActiveCell) activeCell2;
- if (activeCell.endsOnRow(activeRowIndex)) {
- maxStep = Math.max(maxStep, activeCell.getLastStep());
- }
- }
- if (log.isTraceEnabled()) {
- log.trace("Max step: " + maxStep);
- }
- for (Object activeCell1 : activeCells) {
- ActiveCell activeCell = (ActiveCell) activeCell1;
- activeCell.endRow(activeRowIndex);
- if (!activeCell.endsOnRow(activeRowIndex)) {
- activeCell.signalRowLastStep(maxStep);
- }
- }
- if (maxStep < step) {
- log.trace("Row height smaller than first step, produced penalty will be infinite");
- rowHeightSmallerThanFirstStep = true;
- }
- step = maxStep;
- prepareNextRow();
- }
- return step;
- }
-
- /**
- * Pre-activates the cells that will start the next row, and computes the first step
- * for that row.
- */
- private void prepareNextRow() {
- if (activeRowIndex < rowGroup.length - 1) {
- previousRowsLength += rowGroup[activeRowIndex].getHeight().getOpt();
- activateCells(nextActiveCells, activeRowIndex + 1);
- if (log.isTraceEnabled()) {
- log.trace("Computing first step for row " + (activeRowIndex + 2));
- }
- computeRowFirstStep(nextActiveCells);
- if (log.isTraceEnabled()) {
- log.trace("Next first step = " + rowFirstStep);
- }
- }
- }
-
- private void removeCellsEndingOnCurrentRow() {
- for (Iterator iter = activeCells.iterator(); iter.hasNext();) {
- ActiveCell activeCell = (ActiveCell) iter.next();
- if (activeCell.endsOnRow(activeRowIndex)) {
- iter.remove();
- }
- }
- }
-
- /**
- * Actually switches to the next row, increasing activeRowIndex and transferring to
- * activeCells the cells starting on the next row.
- */
- private void switchToNextRow() {
- activeRowIndex++;
- if (log.isTraceEnabled()) {
- log.trace("Switching to row " + (activeRowIndex + 1));
- }
- for (Object activeCell1 : activeCells) {
- ActiveCell activeCell = (ActiveCell) activeCell1;
- activeCell.nextRowStarts();
- }
- activeCells.addAll(nextActiveCells);
- nextActiveCells.clear();
- }
-
- /** @return the table layout manager */
- private TableLayoutManager getTableLM() {
- return this.tclm.getTableLM();
- }
- }
|