| /* |
| * 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.ArrayList; |
| import java.util.LinkedList; |
| import java.util.List; |
| import java.util.ListIterator; |
| |
| 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.ConditionalBorder; |
| import org.apache.fop.fo.flow.table.EffRow; |
| import org.apache.fop.fo.flow.table.PrimaryGridUnit; |
| import org.apache.fop.fo.properties.CommonBorderPaddingBackground; |
| import org.apache.fop.layoutmgr.ElementListUtils; |
| import org.apache.fop.layoutmgr.Keep; |
| import org.apache.fop.layoutmgr.KnuthBlockBox; |
| import org.apache.fop.layoutmgr.KnuthBox; |
| import org.apache.fop.layoutmgr.KnuthElement; |
| import org.apache.fop.layoutmgr.KnuthPenalty; |
| import org.apache.fop.traits.MinOptMax; |
| |
| /** |
| * A cell playing in the construction of steps for a row-group. |
| */ |
| class ActiveCell { |
| |
| private static Log log = LogFactory.getLog(ActiveCell.class); |
| |
| private PrimaryGridUnit pgu; |
| /** Knuth elements for this active cell. */ |
| private List elementList; |
| /** Iterator over the Knuth element list. */ |
| private ListIterator knuthIter; |
| /** Number of the row where the row-span ends, zero-based. */ |
| private int endRowIndex; |
| /** Length of the Knuth elements not yet included in the steps. */ |
| private int remainingLength; |
| /** Total length of this cell's content plus the lengths of the previous rows. */ |
| private int totalLength; |
| /** Length of the Knuth elements already included in the steps. */ |
| private int includedLength; |
| |
| private int paddingBeforeNormal; |
| private int paddingBeforeLeading; |
| private int paddingAfterNormal; |
| private int paddingAfterTrailing; |
| |
| private int bpBeforeNormal; |
| private int bpBeforeLeading; |
| private int bpAfterNormal; |
| private int bpAfterTrailing; |
| |
| /** True if the next CellPart that will be created will be the last one for this cell. */ |
| private boolean lastCellPart; |
| |
| private Keep keepWithNext; |
| |
| private int spanIndex; |
| |
| private Step previousStep; |
| private Step nextStep; |
| /** |
| * The step following nextStep. Computing it early allows to calculate |
| * {@link Step#condBeforeContentLength}, thus to easily determine the remaining |
| * length. That also helps for {@link #increaseCurrentStep(int)}. |
| */ |
| private Step afterNextStep; |
| |
| /** |
| * Auxiliary class to store all the informations related to a breaking step. |
| */ |
| private static class Step { |
| /** Index, in the list of Knuth elements, of the element starting this step. */ |
| private int start; |
| /** Index, in the list of Knuth elements, of the element ending this step. */ |
| private int end; |
| /** Length of the Knuth elements up to this step. */ |
| private int contentLength; |
| /** Total length up to this step, including paddings and borders. */ |
| private int totalLength; |
| /** Length of the penalty ending this step, if any. */ |
| private int penaltyLength; |
| /** Value of the penalty ending this step, 0 if the step does not end on a penalty. */ |
| private int penaltyValue; |
| /** List of footnotes for this step. */ |
| private List footnoteList; |
| /** |
| * One of {@link Constants#EN_AUTO}, {@link Constants#EN_COLUMN}, |
| * {@link Constants#EN_PAGE}, {@link Constants#EN_EVEN_PAGE}, |
| * {@link Constants#EN_ODD_PAGE}. Set to auto if the break isn't at a penalty |
| * element. |
| */ |
| private int breakClass; |
| /** |
| * Length of the optional content at the beginning of the step. That is, content |
| * that will not appear if this step starts a new page. |
| */ |
| private int condBeforeContentLength; |
| |
| Step(int contentLength) { |
| this.contentLength = contentLength; |
| this.end = -1; |
| } |
| |
| Step(Step other) { |
| set(other); |
| } |
| |
| void set(Step other) { |
| this.start = other.start; |
| this.end = other.end; |
| this.contentLength = other.contentLength; |
| this.totalLength = other.totalLength; |
| this.penaltyLength = other.penaltyLength; |
| this.penaltyValue = other.penaltyValue; |
| if (other.footnoteList != null) { |
| if (this.footnoteList == null) { |
| this.footnoteList = new ArrayList(); |
| } |
| this.footnoteList.addAll(other.footnoteList); |
| } |
| this.condBeforeContentLength = other.condBeforeContentLength; |
| this.breakClass = other.breakClass; |
| } |
| |
| /** {@inheritDoc} */ |
| public String toString() { |
| return "Step: start=" + start + " end=" + end + " length=" + totalLength; |
| } |
| } |
| |
| // TODO to be removed along with the RowPainter#computeContentLength method |
| /** See {@link ActiveCell#handleExplicitHeight(MinOptMax, MinOptMax)}. */ |
| private static class FillerPenalty extends KnuthPenalty { |
| |
| private int contentLength; |
| |
| FillerPenalty(KnuthPenalty p, int length) { |
| super(length, p.getPenalty(), p.isPenaltyFlagged(), p.getBreakClass(), |
| p.getPosition(), p.isAuxiliary()); |
| contentLength = p.getWidth(); |
| } |
| |
| FillerPenalty(int length) { |
| super(length, 0, false, null, true); |
| contentLength = 0; |
| } |
| } |
| |
| /** See {@link ActiveCell#handleExplicitHeight(MinOptMax, MinOptMax)}. */ |
| private static class FillerBox extends KnuthBox { |
| FillerBox(int length) { |
| super(length, null, true); |
| } |
| } |
| |
| /** |
| * Returns the actual length of the content represented by the given element. In the |
| * case where this element is used as a filler to match a row's fixed height, the |
| * value returned by the getW() method will be higher than the actual content. |
| * |
| * @param el an element |
| * @return the actual content length corresponding to the element |
| */ |
| static int getElementContentLength(KnuthElement el) { |
| if (el instanceof FillerPenalty) { |
| return ((FillerPenalty) el).contentLength; |
| } else if (el instanceof FillerBox) { |
| return 0; |
| } else { |
| return el.getWidth(); |
| } |
| } |
| |
| ActiveCell(PrimaryGridUnit pgu, EffRow row, int rowIndex, int previousRowsLength, |
| TableLayoutManager tableLM) { |
| this.pgu = pgu; |
| CommonBorderPaddingBackground bordersPaddings = pgu.getCell() |
| .getCommonBorderPaddingBackground(); |
| TableCellLayoutManager cellLM = pgu.getCellLM(); |
| paddingBeforeNormal = bordersPaddings.getPaddingBefore(false, cellLM); |
| paddingBeforeLeading = bordersPaddings.getPaddingBefore(true, cellLM); |
| paddingAfterNormal = bordersPaddings.getPaddingAfter(false, cellLM); |
| paddingAfterTrailing = bordersPaddings.getPaddingAfter(true, cellLM); |
| bpBeforeNormal = paddingBeforeNormal |
| + pgu.getBeforeBorderWidth(0, ConditionalBorder.NORMAL); |
| bpBeforeLeading = paddingBeforeLeading |
| + pgu.getBeforeBorderWidth(0, ConditionalBorder.REST); |
| bpAfterNormal = paddingAfterNormal + pgu.getAfterBorderWidth(ConditionalBorder.NORMAL); |
| bpAfterTrailing = paddingAfterTrailing + pgu.getAfterBorderWidth(0, ConditionalBorder.REST); |
| elementList = pgu.getElements(); |
| handleExplicitHeight(pgu.getCell().getBlockProgressionDimension().toMinOptMax(tableLM), |
| row.getExplicitHeight()); |
| knuthIter = elementList.listIterator(); |
| includedLength = -1; // Avoid troubles with cells having content of zero length |
| totalLength = previousRowsLength + ElementListUtils.calcContentLength(elementList); |
| endRowIndex = rowIndex + pgu.getCell().getNumberRowsSpanned() - 1; |
| keepWithNext = Keep.KEEP_AUTO; |
| remainingLength = totalLength - previousRowsLength; |
| |
| afterNextStep = new Step(previousRowsLength); |
| previousStep = new Step(afterNextStep); |
| gotoNextLegalBreak(); |
| nextStep = new Step(afterNextStep); |
| if (afterNextStep.end < elementList.size() - 1) { |
| gotoNextLegalBreak(); |
| } |
| } |
| |
| /** |
| * Modifies the cell's element list by putting filler elements, so that the cell's or |
| * row's explicit height is always reached. |
| * |
| * TODO this will work properly only for the first break. Then the limitation |
| * explained on http://wiki.apache.org/xmlgraphics-fop/TableLayout/KnownProblems |
| * occurs. The list of elements needs to be re-adjusted after each break. |
| */ |
| private void handleExplicitHeight(MinOptMax cellBPD, MinOptMax rowBPD) { |
| int minBPD = Math.max(cellBPD.getMin(), rowBPD.getMin()); |
| if (minBPD > 0) { |
| int cumulateLength = 0; |
| boolean prevIsBox = false; |
| for (int i = 0; i < elementList.size() && cumulateLength < minBPD; i++) { |
| KnuthElement el = (KnuthElement) elementList.get(i); |
| if (el.isBox()) { |
| prevIsBox = true; |
| cumulateLength += el.getWidth(); |
| } else if (el.isGlue()) { |
| if (prevIsBox) { |
| elementList.add(i, new FillerPenalty(minBPD - cumulateLength)); |
| } |
| prevIsBox = false; |
| cumulateLength += el.getWidth(); |
| } else { |
| prevIsBox = false; |
| if (cumulateLength + el.getWidth() < minBPD) { |
| elementList.set(i, new FillerPenalty((KnuthPenalty) el, minBPD - cumulateLength)); |
| } |
| } |
| } |
| } |
| int optBPD = Math.max(minBPD, Math.max(cellBPD.getOpt(), rowBPD.getOpt())); |
| if (pgu.getContentLength() < optBPD) { |
| elementList.add(new FillerBox(optBPD - pgu.getContentLength())); |
| } |
| } |
| |
| PrimaryGridUnit getPrimaryGridUnit() { |
| return pgu; |
| } |
| |
| /** |
| * Returns true if this cell ends on the given row. |
| * |
| * @param rowIndex index of a row in the row-group, zero-based |
| * @return true if this cell ends on the given row |
| */ |
| boolean endsOnRow(int rowIndex) { |
| return rowIndex == endRowIndex; |
| } |
| |
| /** |
| * Returns the length of this cell's content not yet included in the steps, plus the |
| * cell's borders and paddings if applicable. |
| * |
| * @return the remaining length, zero if the cell is finished |
| */ |
| int getRemainingLength() { |
| if (includedInLastStep() && (nextStep.end == elementList.size() - 1)) { |
| // The cell is finished |
| return 0; |
| } else { |
| return bpBeforeLeading + remainingLength + bpAfterNormal; |
| } |
| } |
| |
| private void gotoNextLegalBreak() { |
| afterNextStep.penaltyLength = 0; |
| afterNextStep.penaltyValue = 0; |
| afterNextStep.condBeforeContentLength = 0; |
| afterNextStep.breakClass = Constants.EN_AUTO; |
| if (afterNextStep.footnoteList != null) { |
| afterNextStep.footnoteList.clear(); |
| } |
| boolean breakFound = false; |
| boolean prevIsBox = false; |
| boolean boxFound = false; |
| while (!breakFound && knuthIter.hasNext()) { |
| KnuthElement el = (KnuthElement) knuthIter.next(); |
| if (el.isPenalty()) { |
| prevIsBox = false; |
| if (el.getPenalty() < KnuthElement.INFINITE |
| || ((KnuthPenalty) el).getBreakClass() == Constants.EN_PAGE) { |
| // TODO too much is being done in that test, only to handle |
| // keep.within-column properly. |
| |
| // First legal break point |
| breakFound = true; |
| KnuthPenalty p = (KnuthPenalty) el; |
| afterNextStep.penaltyLength = p.getWidth(); |
| afterNextStep.penaltyValue = p.getPenalty(); |
| if (p.isForcedBreak()) { |
| afterNextStep.breakClass = p.getBreakClass(); |
| } |
| } |
| } else if (el.isGlue()) { |
| if (prevIsBox) { |
| // Second legal break point |
| breakFound = true; |
| } else { |
| afterNextStep.contentLength += el.getWidth(); |
| if (!boxFound) { |
| afterNextStep.condBeforeContentLength += el.getWidth(); |
| } |
| } |
| prevIsBox = false; |
| } else { |
| if (el instanceof KnuthBlockBox && ((KnuthBlockBox) el).hasAnchors()) { |
| if (afterNextStep.footnoteList == null) { |
| afterNextStep.footnoteList = new LinkedList(); |
| } |
| afterNextStep.footnoteList.addAll(((KnuthBlockBox) el).getFootnoteBodyLMs()); |
| } |
| prevIsBox = true; |
| boxFound = true; |
| afterNextStep.contentLength += el.getWidth(); |
| } |
| } |
| afterNextStep.end = knuthIter.nextIndex() - 1; |
| afterNextStep.totalLength = bpBeforeNormal |
| + afterNextStep.contentLength + afterNextStep.penaltyLength |
| + bpAfterTrailing; |
| } |
| |
| /** |
| * Returns the minimal step that is needed for this cell to contribute some content. |
| * |
| * @return the step for this cell's first legal break |
| */ |
| int getFirstStep() { |
| log.debug(this + ": min first step = " + nextStep.totalLength); |
| return nextStep.totalLength; |
| } |
| |
| /** |
| * Returns the last step for this cell. This includes the normal border- and |
| * padding-before, the whole content, the normal padding-after, and the |
| * <em>trailing</em> after border. Indeed, if the normal border is taken instead, |
| * and appears to be smaller than the trailing one, the last step may be smaller than |
| * the current step (see TableStepper#considerRowLastStep). This will produce a wrong |
| * infinite penalty, plus the cell's content won't be taken into account since the |
| * final step will be smaller than the current one (see {@link #signalNextStep(int)}). |
| * This actually means that the content will be swallowed. |
| * |
| * @return the length of last step |
| */ |
| int getLastStep() { |
| assert nextStep.end == elementList.size() - 1; |
| assert nextStep.contentLength == totalLength && nextStep.penaltyLength == 0; |
| int lastStep = bpBeforeNormal + totalLength + paddingAfterNormal |
| + pgu.getAfterBorderWidth(ConditionalBorder.LEADING_TRAILING); |
| log.debug(this + ": last step = " + lastStep); |
| return lastStep; |
| } |
| |
| /** |
| * Increases the next step up to the given limit. |
| * |
| * @param limit the length up to which the next step is allowed to increase |
| * @see #signalRowFirstStep(int) |
| * @see #signalRowLastStep(int) |
| */ |
| private void increaseCurrentStep(int limit) { |
| if (nextStep.end < elementList.size() - 1) { |
| while (afterNextStep.totalLength <= limit && nextStep.breakClass == Constants.EN_AUTO) { |
| int condBeforeContentLength = nextStep.condBeforeContentLength; |
| nextStep.set(afterNextStep); |
| nextStep.condBeforeContentLength = condBeforeContentLength; |
| if (afterNextStep.end >= elementList.size() - 1) { |
| break; |
| } |
| gotoNextLegalBreak(); |
| } |
| } |
| } |
| |
| /** |
| * Gets the selected first step for the current row. If this cell's first step is |
| * smaller, then it may be able to add some more of its content, since there will be |
| * no break before the given step anyway. |
| * |
| * @param firstStep the current row's first step |
| */ |
| void signalRowFirstStep(int firstStep) { |
| increaseCurrentStep(firstStep); |
| if (log.isTraceEnabled()) { |
| log.trace(this + ": first step increased to " + nextStep.totalLength); |
| } |
| } |
| |
| /** See {@link #signalRowFirstStep(int)}. */ |
| void signalRowLastStep(int lastStep) { |
| increaseCurrentStep(lastStep); |
| if (log.isTraceEnabled()) { |
| log.trace(this + ": next step increased to " + nextStep.totalLength); |
| } |
| } |
| |
| /** |
| * Returns the total length up to the next legal break, not yet included in the steps. |
| * |
| * @return the total length up to the next legal break (-1 signals no further step) |
| */ |
| int getNextStep() { |
| if (includedInLastStep()) { |
| previousStep.set(nextStep); |
| if (nextStep.end >= elementList.size() - 1) { |
| nextStep.start = elementList.size(); |
| return -1; |
| } else { |
| nextStep.set(afterNextStep); |
| nextStep.start = previousStep.end + 1; |
| afterNextStep.start = nextStep.start; |
| if (afterNextStep.end < elementList.size() - 1) { |
| gotoNextLegalBreak(); |
| } |
| } |
| } |
| return nextStep.totalLength; |
| } |
| |
| private boolean includedInLastStep() { |
| return includedLength == nextStep.contentLength; |
| } |
| |
| /** |
| * Signals the length of the chosen next step, so that this cell determines whether |
| * its own step may be included or not. |
| * |
| * @param minStep length of the chosen next step |
| * @return the break class of the step, if any. One of {@link Constants#EN_AUTO}, |
| * {@link Constants#EN_COLUMN}, {@link Constants#EN_PAGE}, |
| * {@link Constants#EN_EVEN_PAGE}, {@link Constants#EN_ODD_PAGE}. EN_AUTO if this |
| * cell's step is not included in the next step. |
| */ |
| int signalNextStep(int minStep) { |
| if (nextStep.totalLength <= minStep) { |
| includedLength = nextStep.contentLength; |
| remainingLength = totalLength - includedLength - afterNextStep.condBeforeContentLength; |
| return nextStep.breakClass; |
| } else { |
| return Constants.EN_AUTO; |
| } |
| } |
| |
| /** |
| * Receives indication that the next row is about to start, and that (collapse) |
| * borders must be updated accordingly. |
| */ |
| void nextRowStarts() { |
| spanIndex++; |
| // Subtract the old value of bpAfterTrailing... |
| nextStep.totalLength -= bpAfterTrailing; |
| afterNextStep.totalLength -= bpAfterTrailing; |
| |
| bpAfterTrailing = paddingAfterTrailing |
| + pgu.getAfterBorderWidth(spanIndex, ConditionalBorder.REST); |
| |
| // ... and add the new one |
| nextStep.totalLength += bpAfterTrailing; |
| afterNextStep.totalLength += bpAfterTrailing; |
| // TODO if the new after border is greater than the previous one the next step may |
| // increase further than the row's first step, which can lead to wrong output in |
| // some cases |
| } |
| |
| /** |
| * Receives indication that the current row is ending, and that (collapse) borders |
| * must be updated accordingly. |
| * |
| * @param rowIndex the index of the ending row |
| */ |
| void endRow(int rowIndex) { |
| if (endsOnRow(rowIndex)) { |
| // Subtract the old value of bpAfterTrailing... |
| nextStep.totalLength -= bpAfterTrailing; |
| bpAfterTrailing = paddingAfterNormal |
| + pgu.getAfterBorderWidth(ConditionalBorder.LEADING_TRAILING); |
| // ... and add the new one |
| nextStep.totalLength += bpAfterTrailing; |
| lastCellPart = true; |
| } else { |
| bpBeforeLeading = paddingBeforeLeading |
| + pgu.getBeforeBorderWidth(spanIndex + 1, ConditionalBorder.REST); |
| } |
| } |
| |
| /** |
| * Returns true if this cell would be finished after the given step. That is, it would |
| * be included in the step and the end of its content would be reached. |
| * |
| * @param step the next step |
| * @return true if this cell finishes at the given step |
| */ |
| boolean finishes(int step) { |
| return nextStep.totalLength <= step && (nextStep.end == elementList.size() - 1); |
| } |
| |
| /** |
| * Creates and returns a CellPart instance for the content of this cell which |
| * is included in the next step. |
| * |
| * @return a CellPart instance |
| */ |
| CellPart createCellPart() { |
| if (nextStep.end + 1 == elementList.size()) { |
| keepWithNext = pgu.getKeepWithNext(); |
| // TODO if keep-with-next is set on the row, must every cell of the row |
| // contribute some content from children blocks? |
| // see http://mail-archives.apache.org/mod_mbox/xmlgraphics-fop-dev/200802.mbox/ |
| // %3c47BDA379.4050606@anyware-tech.com%3e |
| // Assuming no, but if yes the following code should enable this behaviour |
| // if (pgu.getRow() != null && pgu.getRow().mustKeepWithNext()) { |
| // keepWithNextSignal = true; //to be converted to integer strengths |
| // } |
| } |
| int bpBeforeFirst; |
| if (nextStep.start == 0) { |
| bpBeforeFirst = pgu.getBeforeBorderWidth(0, ConditionalBorder.LEADING_TRAILING) |
| + paddingBeforeNormal; |
| } else { |
| bpBeforeFirst = bpBeforeLeading; |
| } |
| int length = nextStep.contentLength - nextStep.condBeforeContentLength |
| - previousStep.contentLength; |
| if (!includedInLastStep() || nextStep.start == elementList.size()) { |
| return new CellPart(pgu, nextStep.start, previousStep.end, lastCellPart, |
| 0, 0, previousStep.penaltyLength, |
| bpBeforeNormal, bpBeforeFirst, bpAfterNormal, bpAfterTrailing); |
| } else { |
| return new CellPart(pgu, nextStep.start, nextStep.end, lastCellPart, |
| nextStep.condBeforeContentLength, length, nextStep.penaltyLength, |
| bpBeforeNormal, bpBeforeFirst, bpAfterNormal, bpAfterTrailing); |
| } |
| } |
| |
| /** |
| * Adds the footnotes (if any) that are part of the next step, if this cell |
| * contributes content to the next step. |
| * |
| * @param footnoteList the list to which this cell must add its footnotes |
| */ |
| void addFootnotes(List footnoteList) { |
| if (includedInLastStep() && nextStep.footnoteList != null) { |
| footnoteList.addAll(nextStep.footnoteList); |
| nextStep.footnoteList.clear(); |
| } |
| } |
| |
| Keep getKeepWithNext() { |
| return keepWithNext; |
| } |
| |
| int getPenaltyValue() { |
| if (includedInLastStep()) { |
| return nextStep.penaltyValue; |
| } else { |
| return previousStep.penaltyValue; |
| } |
| } |
| |
| /** {@inheritDoc} */ |
| public String toString() { |
| return "Cell " + (pgu.getRowIndex() + 1) + "." + (pgu.getColIndex() + 1); |
| } |
| } |