Package org.apache.fop.layoutmgr.table

Source Code of org.apache.fop.layoutmgr.table.TableContentLayoutManager$TableHeaderFooterPosition

/*
* Copyright 2005 The Apache Software Foundation.
*
* Licensed 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: TableContentLayoutManager.java 357061 2005-12-15 19:17:01Z jeremias $ */

package org.apache.fop.layoutmgr.table;

import java.util.Arrays;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.fop.area.Block;
import org.apache.fop.area.Trait;
import org.apache.fop.datatypes.PercentBaseContext;
import org.apache.fop.fo.Constants;
import org.apache.fop.fo.FONode;
import org.apache.fop.fo.FObj;
import org.apache.fop.fo.flow.Table;
import org.apache.fop.fo.flow.TableBody;
import org.apache.fop.fo.flow.TableRow;
import org.apache.fop.fo.properties.CommonBorderPaddingBackground;
import org.apache.fop.fo.properties.LengthRangeProperty;
import org.apache.fop.layoutmgr.BreakElement;
import org.apache.fop.layoutmgr.ElementListObserver;
import org.apache.fop.layoutmgr.ElementListUtils;
import org.apache.fop.layoutmgr.KnuthBox;
import org.apache.fop.layoutmgr.KnuthElement;
import org.apache.fop.layoutmgr.KnuthPenalty;
import org.apache.fop.layoutmgr.KnuthPossPosIter;
import org.apache.fop.layoutmgr.LayoutContext;
import org.apache.fop.layoutmgr.LayoutManager;
import org.apache.fop.layoutmgr.ListElement;
import org.apache.fop.layoutmgr.MinOptMaxUtil;
import org.apache.fop.layoutmgr.Position;
import org.apache.fop.layoutmgr.PositionIterator;
import org.apache.fop.layoutmgr.SpaceResolver;
import org.apache.fop.layoutmgr.TraitSetter;
import org.apache.fop.layoutmgr.SpaceResolver.SpaceHandlingBreakPosition;
import org.apache.fop.traits.MinOptMax;

/**
* Layout manager for table contents, particularly managing the creation of combined element lists.
*/
public class TableContentLayoutManager implements PercentBaseContext {

    /** Logger **/
    private static Log log = LogFactory.getLog(TableContentLayoutManager.class);

    private TableLayoutManager tableLM;
    private TableRowIterator trIter;
    private TableRowIterator headerIter;
    private TableRowIterator footerIter;
    private LinkedList headerList;
    private LinkedList footerList;
    private int headerNetHeight = 0;
    private int footerNetHeight = 0;
    private boolean firstBreakBeforeServed = false;

    private int startXOffset;
    private int usedBPD;
   
    /**
     * Main constructor
     * @param parent Parent layout manager
     */
    public TableContentLayoutManager(TableLayoutManager parent) {
        this.tableLM = parent;
        Table table = getTableLM().getTable();
        this.trIter = new TableRowIterator(table, getTableLM().getColumns(), TableRowIterator.BODY);
        if (table.getTableHeader() != null) {
            headerIter = new TableRowIterator(table,
                    getTableLM().getColumns(), TableRowIterator.HEADER);
        }
        if (table.getTableFooter() != null) {
            footerIter = new TableRowIterator(table,
                    getTableLM().getColumns(), TableRowIterator.FOOTER);
        }
    }
   
    /**
     * @return the table layout manager
     */
    public TableLayoutManager getTableLM() {
        return this.tableLM;
    }
   
    /** @return true if the table uses the separate border model. */
    private boolean isSeparateBorderModel() {
        return getTableLM().getTable().isSeparateBorderModel();
    }
   
    /**
     * @return the column setup of this table
     */
    public ColumnSetup getColumns() {
        return getTableLM().getColumns();
    }
   
    /** @return the net header height */
    protected int getHeaderNetHeight() {
        return this.headerNetHeight;
    }

    /** @return the net footer height */
    protected int getFooterNetHeight() {
        return this.headerNetHeight;
    }

    /** @return the header element list */
    protected LinkedList getHeaderElements() {
        return this.headerList;
    }

    /** @return the footer element list */
    protected LinkedList getFooterElements() {
        return this.footerList;
    }

    /** @see org.apache.fop.layoutmgr.LayoutManager */
    public LinkedList getNextKnuthElements(LayoutContext context, int alignment) {
        log.debug("==> Columns: " + getTableLM().getColumns());
        KnuthBox headerAsFirst = null;
        KnuthBox headerAsSecondToLast = null;
        KnuthBox footerAsLast = null;
        if (headerIter != null && headerList == null) {
            this.headerList = getKnuthElementsForRowIterator(
                    headerIter, context, alignment, TableRowIterator.HEADER);
            ElementListUtils.removeLegalBreaks(this.headerList);
            this.headerNetHeight = ElementListUtils.calcContentLength(this.headerList);
            if (log.isDebugEnabled()) {
                log.debug("==> Header: " + headerNetHeight + " - " + this.headerList);
            }
            TableHeaderFooterPosition pos = new TableHeaderFooterPosition(
                    getTableLM(), true, this.headerList);
            KnuthBox box = new KnuthBox(headerNetHeight, pos, false);
            if (getTableLM().getTable().omitHeaderAtBreak()) {
                //We can simply add the table header at the beginning of the whole list
                headerAsFirst = box;
            } else {
                headerAsSecondToLast = box;
            }
        }
        if (footerIter != null && footerList == null) {
            this.footerList = getKnuthElementsForRowIterator(
                    footerIter, context, alignment, TableRowIterator.FOOTER);
            ElementListUtils.removeLegalBreaks(this.footerList);
            this.footerNetHeight = ElementListUtils.calcContentLength(this.footerList);
            if (log.isDebugEnabled()) {
                log.debug("==> Footer: " + footerNetHeight + " - " + this.footerList);
            }
            if (true /*getTableLM().getTable().omitFooterAtBreak()*/) {
                //We can simply add the table header at the end of the whole list
                TableHeaderFooterPosition pos = new TableHeaderFooterPosition(
                        getTableLM(), false, this.footerList);
                KnuthBox box = new KnuthBox(footerNetHeight, pos, false);
                footerAsLast = box;
            }
        }
        LinkedList returnList = getKnuthElementsForRowIterator(
                trIter, context, alignment, TableRowIterator.BODY);
        if (headerAsFirst != null) {
            returnList.add(0, headerAsFirst);
        } else if (headerAsSecondToLast != null) {
            returnList.add(headerAsSecondToLast);
        }
        if (footerAsLast != null) {
            returnList.add(footerAsLast);
        }
        return returnList;
    }
   
    /**
     * Creates Knuth elements by iterating over a TableRowIterator.
     * @param iter TableRowIterator instance to fetch rows from
     * @param context Active LayoutContext
     * @param alignment alignment indicator
     * @param bodyType Indicates what kind of body is being processed (BODY, HEADER or FOOTER)
     * @return An element list
     */
    private LinkedList getKnuthElementsForRowIterator(TableRowIterator iter,
            LayoutContext context, int alignment, int bodyType) {
        LinkedList returnList = new LinkedList();
        EffRow[] rowGroup = null;
        while ((rowGroup = iter.getNextRowGroup()) != null) {
            //Check for break-before on the table-row at the start of the row group
            TableRow rowFO = rowGroup[0].getTableRow();
            if (rowFO != null && rowFO.getBreakBefore() != Constants.EN_AUTO) {
                log.info("break-before found");
                if (returnList.size() > 0) {
                    ListElement last = (ListElement)returnList.getLast();
                    if (last.isPenalty()) {
                        KnuthPenalty pen = (KnuthPenalty)last;
                        pen.setP(-KnuthPenalty.INFINITE);
                        pen.setBreakClass(rowFO.getBreakBefore());
                    } else if (last instanceof BreakElement) {
                        BreakElement breakPoss = (BreakElement)last;
                        breakPoss.setPenaltyValue(-KnuthPenalty.INFINITE);
                        breakPoss.setBreakClass(rowFO.getBreakBefore());
                    }
                } else {
                    if (!firstBreakBeforeServed) {
                        //returnList.add(new KnuthPenalty(0, -KnuthPenalty.INFINITE,
                        //        false, rowFO.getBreakBefore(), new Position(getTableLM()), true));
                        returnList.add(new BreakElement(new Position(getTableLM()),
                                0, -KnuthPenalty.INFINITE, rowFO.getBreakBefore(), context));
                        iter.backToPreviousRow();
                        firstBreakBeforeServed = true;
                        break;
                    }
                }
            }
            firstBreakBeforeServed = true;
           
            //Border resolution
            if (!isSeparateBorderModel()) {
                resolveNormalBeforeAfterBordersForRowGroup(rowGroup, iter);
            }
           
            //Element list creation
            createElementsForRowGroup(context, alignment, bodyType,
                        returnList, rowGroup);
           
            //Handle keeps
            if (context.isKeepWithNextPending()) {
                log.debug("child LM (row group) signals pending keep-with-next");
            }
            if (context.isKeepWithPreviousPending()) {
                log.debug("child LM (row group) signals pending keep-with-previous");
                if (returnList.size() > 0) {
                    //Modify last penalty
                    ListElement last = (ListElement)returnList.getLast();
                    if (last.isPenalty()) {
                        BreakElement breakPoss = (BreakElement)last;
                        //Only honor keep if there's no forced break
                        if (!breakPoss.isForcedBreak()) {
                            breakPoss.setPenaltyValue(KnuthPenalty.INFINITE);
                        }
                    }
                }
            }
           
            //Check for break-after on the table-row at the end of the row group
            rowFO = rowGroup[rowGroup.length - 1].getTableRow();
            if (rowFO != null && rowFO.getBreakAfter() != Constants.EN_AUTO) {
                log.info("break-after found");
                if (returnList.size() > 0) {
                    ListElement last = (ListElement)returnList.getLast();
                    if (last instanceof KnuthPenalty) {
                        KnuthPenalty pen = (KnuthPenalty)last;
                        pen.setP(-KnuthPenalty.INFINITE);
                        pen.setBreakClass(rowFO.getBreakAfter());
                    } else if (last instanceof BreakElement) {
                        BreakElement breakPoss = (BreakElement)last;
                        breakPoss.setPenaltyValue(-KnuthPenalty.INFINITE);
                        breakPoss.setBreakClass(rowFO.getBreakAfter());
                    }
                }
            }
        }
       
        if (returnList.size() > 0) {
            //Remove last penalty
            ListElement last = (ListElement)returnList.getLast();
            if (last.isPenalty() || last instanceof BreakElement) {
                if (!last.isForcedBreak()) {
                    //Only remove if we don't signal a forced break
                    returnList.removeLast();
                }
            }
        }
        return returnList;
    }

    /**
     * Resolves normal borders for a row group.
     * @param iter Table row iterator to operate on
     */
    private void resolveNormalBeforeAfterBordersForRowGroup(EffRow[] rowGroup,
            TableRowIterator iter) {
        for (int rgi = 0; rgi < rowGroup.length; rgi++) {
            EffRow row = rowGroup[rgi];
            EffRow prev = iter.getCachedRow(row.getIndex() - 1);
            EffRow next = iter.getCachedRow(row.getIndex() + 1);
            if (next == null) {
                //It wasn't read, yet, or we are at the last row
                next = iter.getNextRow();
                iter.backToPreviousRow();
            }
            if ((prev == null) && (iter == this.trIter) && (this.headerIter != null)) {
                prev = this.headerIter.getLastRow();
            }
            if ((next == null) && (iter == this.headerIter)) {
                next = this.trIter.getFirstRow();
            }
            if ((next == null) && (iter == this.trIter) && (this.footerIter != null)) {
                next = this.footerIter.getFirstRow();
            }
            if ((prev == null) && (iter == this.footerIter)) {
                //TODO This could be bad for memory consumption because it already causes the
                //whole body iterator to be prefetched!
                prev = this.trIter.getLastRow();
            }
            log.debug(prev + " - " + row + " - " + next);
           
            //Determine the grid units necessary for getting all the borders right
            int guCount = row.getGridUnits().size();
            if (prev != null) {
                guCount = Math.max(guCount, prev.getGridUnits().size());
            }
            if (next != null) {
                guCount = Math.max(guCount, next.getGridUnits().size());
            }
            GridUnit gu = row.getGridUnit(0);
            //Create empty grid units to hold resolved borders of neighbouring cells
            //TODO maybe this needs to be done differently (and sooner)
            for (int i = 0; i < guCount - row.getGridUnits().size(); i++) {
                //TODO This block is untested!
                int pos = row.getGridUnits().size() + i;
                row.getGridUnits().add(new EmptyGridUnit(gu.getRow(),
                        this.tableLM.getColumns().getColumn(pos + 1), gu.getBody(),
                        pos));
            }
           
            //Now resolve normal borders
            if (getTableLM().getTable().isSeparateBorderModel()) {
                //nop, borders are already assigned at this point
            } else {
                for (int i = 0; i < row.getGridUnits().size(); i++) {
                    gu = row.getGridUnit(i);
                    GridUnit other;
                    int flags = 0;
                    if (prev != null && i < prev.getGridUnits().size()) {
                        other = prev.getGridUnit(i);
                    } else {
                        other = null;
                    }
                    if (other == null
                            || other.isEmpty()
                            || gu.isEmpty()
                            || gu.getPrimary() != other.getPrimary()) {
                        if ((iter == this.trIter)
                                && gu.getFlag(GridUnit.FIRST_IN_TABLE)
                                && (this.headerIter == null)) {
                            flags |= CollapsingBorderModel.VERTICAL_START_END_OF_TABLE;
                        }
                        if ((iter == this.headerIter)
                                && gu.getFlag(GridUnit.FIRST_IN_TABLE)) {
                            flags |= CollapsingBorderModel.VERTICAL_START_END_OF_TABLE;
                        }
                        gu.resolveBorder(other,
                                CommonBorderPaddingBackground.BEFORE, flags);
                    }
                   
                    flags = 0;
                    if (next != null && i < next.getGridUnits().size()) {
                        other = next.getGridUnit(i);
                    } else {
                        other = null;
                    }
                    if (other == null
                            || other.isEmpty()
                            || gu.isEmpty()
                            || gu.getPrimary() != other.getPrimary()) {
                        if ((iter == this.trIter)
                                && gu.getFlag(GridUnit.LAST_IN_TABLE)
                                && (this.footerIter == null)) {
                            flags |= CollapsingBorderModel.VERTICAL_START_END_OF_TABLE;
                        }
                        if ((iter == this.footerIter)
                                && gu.getFlag(GridUnit.LAST_IN_TABLE)) {
                            flags |= CollapsingBorderModel.VERTICAL_START_END_OF_TABLE;
                        }
                        gu.resolveBorder(other,
                                CommonBorderPaddingBackground.AFTER, flags);
                    }
                }

            }
        }
    }

    /**
     * Creates Knuth elements for a row group (see TableRowIterator.getNextRowGroup()).
     * @param context Active LayoutContext
     * @param alignment alignment indicator
     * @param bodyType Indicates what kind of body is being processed (BODY, HEADER or FOOTER)
     * @param returnList List to received the generated elements
     * @param rowGroup row group to process
     */
    private void createElementsForRowGroup(LayoutContext context, int alignment,
            int bodyType, LinkedList returnList,
            EffRow[] rowGroup) {
        log.debug("Handling row group with " + rowGroup.length + " rows...");
        MinOptMax[] rowHeights = new MinOptMax[rowGroup.length];
        MinOptMax[] explicitRowHeights = new MinOptMax[rowGroup.length];
        EffRow row;
        int maxColumnCount = 0;
        List pgus = new java.util.ArrayList(); //holds a list of a row's primary grid units
        for (int rgi = 0; rgi < rowGroup.length; rgi++) {
            row = rowGroup[rgi];
            rowHeights[rgi] = new MinOptMax(0, 0, Integer.MAX_VALUE);
            explicitRowHeights[rgi] = new MinOptMax(0, 0, Integer.MAX_VALUE);
           
            pgus.clear();
            TableRow tableRow = null;
            int minContentHeight = 0;
            int maxCellHeight = 0;
            int effRowContentHeight = 0;
            for (int j = 0; j < row.getGridUnits().size(); j++) {
                maxColumnCount = Math.max(maxColumnCount, row.getGridUnits().size());
                GridUnit gu = row.getGridUnit(j);
                if ((gu.isPrimary() || (gu.getColSpanIndex() == 0 && gu.isLastGridUnitRowSpan()))
                        && !gu.isEmpty()) {
                    PrimaryGridUnit primary = gu.getPrimary();
                   
                    if (gu.isPrimary()) {
                        primary.getCellLM().setParent(getTableLM());
                    
                        //Determine the table-row if any
                        if (tableRow == null && primary.getRow() != null) {
                            tableRow = primary.getRow();
                           
                            //Check for bpd on row, see CSS21, 17.5.3 Table height algorithms
                            LengthRangeProperty bpd = tableRow.getBlockProgressionDimension();
                            if (!bpd.getMinimum(getTableLM()).isAuto()) {
                                minContentHeight = Math.max(
                                        minContentHeight,
                                        bpd.getMinimum(
                                                getTableLM()).getLength().getValue(getTableLM()));
                            }
                            MinOptMaxUtil.restrict(explicitRowHeights[rgi], bpd, getTableLM());
                           
                        }

                        //Calculate width of cell
                        int spanWidth = 0;
                        for (int i = primary.getStartCol();
                                i < primary.getStartCol()
                                        + primary.getCell().getNumberColumnsSpanned();
                                i++) {
                            if (getTableLM().getColumns().getColumn(i + 1) != null) {
                                spanWidth += getTableLM().getColumns().getColumn(i + 1)
                                    .getColumnWidth().getValue(getTableLM());
                            }
                        }
                        LayoutContext childLC = new LayoutContext(0);
                        childLC.setStackLimit(context.getStackLimit()); //necessary?
                        childLC.setRefIPD(spanWidth);
                       
                        //Get the element list for the cell contents
                        LinkedList elems = primary.getCellLM().getNextKnuthElements(
                                                childLC, alignment);
                        //Temporary? Multiple calls in case of break conditions.
                        //TODO Revisit when table layout is restartable
                        while (!primary.getCellLM().isFinished()) {
                            LinkedList additionalElems = primary.getCellLM().getNextKnuthElements(
                                    childLC, alignment);
                            elems.addAll(additionalElems);
                        }
                        ElementListObserver.observe(elems, "table-cell", primary.getCell().getId());

                        if ((elems.size() > 0)
                                && ((KnuthElement)elems.getLast()).isForcedBreak()) {
                            // a descendant of this block has break-after
                            log.debug("Descendant of table-cell signals break: "
                                    + primary.getCellLM().isFinished());
                        }
                       
                        primary.setElements(elems);
                       
                        if (childLC.isKeepWithNextPending()) {
                            log.debug("child LM signals pending keep-with-next");
                            primary.setFlag(GridUnit.KEEP_WITH_NEXT_PENDING, true);
                        }
                        if (childLC.isKeepWithPreviousPending()) {
                            log.debug("child LM signals pending keep-with-previous");
                            primary.setFlag(GridUnit.KEEP_WITH_PREVIOUS_PENDING, true);
                        }
                    }

                   
                    //Calculate height of cell contents
                    primary.setContentLength(ElementListUtils.calcContentLength(
                            primary.getElements()));
                    maxCellHeight = Math.max(maxCellHeight, primary.getContentLength());

                    //Calculate height of row, see CSS21, 17.5.3 Table height algorithms
                    if (gu.isLastGridUnitRowSpan()) {
                        int effCellContentHeight = minContentHeight;
                        LengthRangeProperty bpd = primary.getCell().getBlockProgressionDimension();
                        if (!bpd.getMinimum(getTableLM()).isAuto()) {
                            effCellContentHeight = Math.max(
                                effCellContentHeight,
                                bpd.getMinimum(getTableLM()).getLength().getValue(getTableLM()));
                        }
                        if (!bpd.getOptimum(getTableLM()).isAuto()) {
                            effCellContentHeight = Math.max(
                                effCellContentHeight,
                                bpd.getOptimum(getTableLM()).getLength().getValue(getTableLM()));
                        }
                        if (gu.getRowSpanIndex() == 0) {
                            //TODO ATM only non-row-spanned cells are taken for this
                            MinOptMaxUtil.restrict(explicitRowHeights[rgi], bpd, tableLM);
                        }
                        effCellContentHeight = Math.max(effCellContentHeight,
                                primary.getContentLength());
                       
                        int borderWidths;
                        if (isSeparateBorderModel()) {
                            borderWidths = primary.getBorders().getBorderBeforeWidth(false)
                                    + primary.getBorders().getBorderAfterWidth(false);
                        } else {
                            borderWidths = primary.getHalfMaxBorderWidth();
                        }
                        int padding = 0;
                        effRowContentHeight = Math.max(effRowContentHeight,
                                effCellContentHeight);
                        CommonBorderPaddingBackground cbpb
                            = primary.getCell().getCommonBorderPaddingBackground();
                        padding += cbpb.getPaddingBefore(false, primary.getCellLM());
                        padding += cbpb.getPaddingAfter(false, primary.getCellLM());
                        int effRowHeight = effCellContentHeight
                                + padding + borderWidths
                                + 2 * getTableLM().getHalfBorderSeparationBPD();
                        for (int previous = 0; previous < gu.getRowSpanIndex(); previous++) {
                            effRowHeight -= rowHeights[rgi - previous - 1].opt;
                        }
                        if (effRowHeight > rowHeights[rgi].min) {
                            //This is the new height of the (grid) row
                            MinOptMaxUtil.extendMinimum(rowHeights[rgi], effRowHeight, false);
                        }
                    }
                   
                    if (gu.isPrimary()) {
                        pgus.add(primary);
                    }
                }
            }

            row.setHeight(rowHeights[rgi]);
            row.setExplicitHeight(explicitRowHeights[rgi]);
            if (effRowContentHeight > row.getExplicitHeight().max) {
                log.warn(FONode.decorateWithContextInfo(
                        "The contents of row " + (row.getIndex() + 1)
                        + " are taller than they should be (there is a"
                        + " block-progression-dimension or height constraint on the indicated row)."
                        + " Due to its contents the row grows"
                        + " to " + effRowContentHeight + " millipoints, but the row shouldn't get"
                        + " any taller than " + row.getExplicitHeight() + " millipoints.",
                        row.getTableRow()));
            }
        }
        if (log.isDebugEnabled()) {
            log.debug("rowGroup:");
            for (int i = 0; i < rowHeights.length; i++) {
                log.debug("  height=" + rowHeights[i] + " explicit=" + explicitRowHeights[i]);
            }
        }
        //TODO It may make sense to reuse the stepper since it allocates quite some space
        TableStepper stepper = new TableStepper(this);
        LinkedList returnedList = stepper.getCombinedKnuthElementsForRowGroup(
                context, rowGroup, maxColumnCount, bodyType);
        if (returnedList != null) {
            returnList.addAll(returnedList);
        }
       
    }

    /**
     * Retuns the X offset of the given grid unit.
     * @param gu the grid unit
     * @return the requested X offset
     */
    protected int getXOffsetOfGridUnit(GridUnit gu) {
        int col = gu.getStartCol();
        return startXOffset + getTableLM().getColumns().getXOffset(col + 1, getTableLM());
    }
   
    /**
     * Adds the areas generated my this layout manager to the area tree.
     * @param parentIter the position iterator
     * @param layoutContext the layout context for adding areas
     */
    public void addAreas(PositionIterator parentIter, LayoutContext layoutContext) {
        this.usedBPD = 0;
        RowPainter painter = new RowPainter(layoutContext);

        List positions = new java.util.ArrayList();
        List headerElements = null;
        List footerElements = null;
        Position firstPos = null;
        Position lastPos = null;
        Position lastCheckPos = null;
        while (parentIter.hasNext()) {
            Position pos = (Position)parentIter.next();
            if (pos instanceof SpaceHandlingBreakPosition) {
                //This position has only been needed before addAreas was called, now we need the
                //original one created by the layout manager.
                pos = ((SpaceHandlingBreakPosition)pos).getOriginalBreakPosition();
            }
            if (pos == null) {
                continue;
            }
            if (firstPos == null) {
                firstPos = pos;
            }
            lastPos = pos;
            if (pos.getIndex() >= 0) {
                lastCheckPos = pos;
            }
            if (pos instanceof TableHeaderFooterPosition) {
                TableHeaderFooterPosition thfpos = (TableHeaderFooterPosition)pos;
                //these positions need to be unpacked
                if (thfpos.header) {
                    //Positions for header will be added first
                    headerElements = thfpos.nestedElements;
                } else {
                    //Positions for footers are simply added at the end
                    footerElements = thfpos.nestedElements;
                }
            } else if (pos instanceof TableHFPenaltyPosition) {
                //ignore for now, see special handling below if break is at a penalty
                //Only if the last position in this part/page us such a position it will be used
            } else {
                //leave order as is for the rest
                positions.add(pos);
            }
        }
        if (lastPos instanceof TableHFPenaltyPosition) {
            TableHFPenaltyPosition penaltyPos = (TableHFPenaltyPosition)lastPos;
            log.debug("Break at penalty!");
            if (penaltyPos.headerElements != null) {
                //Header positions for the penalty position are in the last element and need to
                //be handled first before all other TableContentPositions
                headerElements = penaltyPos.headerElements;
            }
            if (penaltyPos.footerElements != null) {
                footerElements = penaltyPos.footerElements;
            }
        }

        Map markers = getTableLM().getTable().getMarkers();
        if (markers != null) {
            getTableLM().getCurrentPV().addMarkers(markers,
                    true, getTableLM().isFirst(firstPos), getTableLM().isLast(lastCheckPos));
        }
       
        if (headerElements != null) {
            //header positions for the last part are the second-to-last element and need to
            //be handled first before all other TableContentPositions
            PositionIterator nestedIter = new KnuthPossPosIter(headerElements);
            iterateAndPaintPositions(nestedIter, painter);
            painter.addAreasAndFlushRow(true);
        }
       
        //Iterate over all steps
        Iterator posIter = positions.iterator();
        iterateAndPaintPositions(posIter, painter);
        painter.addAreasAndFlushRow(true);

        if (footerElements != null) {
            //Positions for footers are simply added at the end
            PositionIterator nestedIter = new KnuthPossPosIter(footerElements);
            iterateAndPaintPositions(nestedIter, painter);
            painter.addAreasAndFlushRow(true);
        }
       
        painter.notifyEndOfSequence();
        this.usedBPD += painter.getAccumulatedBPD();

        if (markers != null) {
            getTableLM().getCurrentPV().addMarkers(markers,
                    false, getTableLM().isFirst(firstPos), getTableLM().isLast(lastCheckPos));
        }
    }
   
    private void iterateAndPaintPositions(Iterator iterator, RowPainter painter) {
        List lst = new java.util.ArrayList();
        boolean firstPos = false;
        boolean lastPos = false;
        TableBody body = null;
        while (iterator.hasNext()) {
            Position pos = (Position)iterator.next();
            if (pos instanceof TableContentPosition) {
                TableContentPosition tcpos = (TableContentPosition)pos;
                lst.add(tcpos);
                GridUnitPart part = (GridUnitPart)tcpos.gridUnitParts.get(0);
                if (body == null) {
                    body = part.pgu.getBody();
                }
                if (tcpos.getFlag(TableContentPosition.FIRST_IN_ROWGROUP)
                        && tcpos.row.getFlag(EffRow.FIRST_IN_BODY)) {
                    firstPos = true;

                }
                if (tcpos.getFlag(TableContentPosition.LAST_IN_ROWGROUP)
                        && tcpos.row.getFlag(EffRow.LAST_IN_BODY)) {
                    lastPos = true;
                    getTableLM().getCurrentPV().addMarkers(body.getMarkers(),
                            true, firstPos, lastPos);
                    int size = lst.size();
                    for (int i = 0; i < size; i++) {
                        painter.handleTableContentPosition((TableContentPosition)lst.get(i));
                    }
                    getTableLM().getCurrentPV().addMarkers(body.getMarkers(),
                            false, firstPos, lastPos);
                    //reset
                    firstPos = false;
                    lastPos = false;
                    body = null;
                    lst.clear();
                }
            } else {
                if (log.isDebugEnabled()) {
                    log.debug("Ignoring position: " + pos);
                }
            }
        }
        if (body != null) {
            getTableLM().getCurrentPV().addMarkers(body.getMarkers(),
                    true, firstPos, lastPos);
            int size = lst.size();
            for (int i = 0; i < size; i++) {
                painter.handleTableContentPosition((TableContentPosition)lst.get(i));
            }
            getTableLM().getCurrentPV().addMarkers(body.getMarkers(),
                    false, firstPos, lastPos);
        }
    }
  
    private class RowPainter {
       
        private TableRow rowFO = null;
        private int colCount = getColumns().getColumnCount();
        private int yoffset = 0;
        private int accumulatedBPD = 0;
        private EffRow lastRow = null;
        private LayoutContext layoutContext;
        private int lastRowHeight = 0;
        private int[] firstRow = new int[3];
        private Map[] rowOffsets = new Map[] {new java.util.HashMap(),
                new java.util.HashMap(), new java.util.HashMap()};

        //These three variables are our buffer to recombine the individual steps into cells
        private PrimaryGridUnit[] gridUnits = new PrimaryGridUnit[colCount];
        private int[] start = new int[colCount];
        private int[] end = new int[colCount];
        private int[] partLength = new int[colCount];
       
        public RowPainter(LayoutContext layoutContext) {
            this.layoutContext = layoutContext;
            Arrays.fill(firstRow, -1);
            Arrays.fill(end, -1);
        }
       
        public int getAccumulatedBPD() {
            return this.accumulatedBPD;
        }
       
        public void notifyEndOfSequence() {
            this.accumulatedBPD += lastRowHeight; //for last row
        }
       
        public void handleTableContentPosition(TableContentPosition tcpos) {
            if (lastRow != tcpos.row && lastRow != null) {
                addAreasAndFlushRow(false);
                yoffset += lastRowHeight;
                this.accumulatedBPD += lastRowHeight;
            }
            if (log.isDebugEnabled()) {
                log.debug("===handleTableContentPosition(" + tcpos);
            }
            rowFO = tcpos.row.getTableRow();
            lastRow = tcpos.row;
            Iterator partIter = tcpos.gridUnitParts.iterator();
            //Iterate over all grid units in the current step
            while (partIter.hasNext()) {
                GridUnitPart gup = (GridUnitPart)partIter.next();
                if (log.isDebugEnabled()) {
                    log.debug(">" + gup);
                }
                int colIndex = gup.pgu.getStartCol();
                if (gridUnits[colIndex] != gup.pgu) {
                    if (gridUnits[colIndex] != null) {
                        log.warn("Replacing GU in slot " + colIndex
                                + ". Some content may not be painted.");
                    }
                    gridUnits[colIndex] = gup.pgu;
                    start[colIndex] = gup.start;
                    end[colIndex] = gup.end;
                } else {
                    if (gup.end < end[colIndex]) {
                        throw new IllegalStateException("Internal Error: stepper problem");
                    }
                    end[colIndex] = gup.end;
                }
            }
        }
       
        public int addAreasAndFlushRow(boolean forcedFlush) {
            int actualRowHeight = 0;
            int readyCount = 0;
           
            int bt = lastRow.getBodyType();
            if (log.isDebugEnabled()) {
                log.debug("Remembering yoffset for row " + lastRow.getIndex() + ": " + yoffset);
            }
            rowOffsets[bt].put(new Integer(lastRow.getIndex()), new Integer(yoffset));

            for (int i = 0; i < gridUnits.length; i++) {
                if ((gridUnits[i] != null)
                        && (forcedFlush || (end[i] == gridUnits[i].getElements().size() - 1))) {
                    if (log.isTraceEnabled()) {
                        log.trace("getting len for " + i + " "
                                + start[i] + "-" + end[i]);
                    }
                    readyCount++;
                    int len = ElementListUtils.calcContentLength(
                            gridUnits[i].getElements(), start[i], end[i]);
                    partLength[i] = len;
                    if (log.isTraceEnabled()) {
                        log.trace("len of part: " + len);
                    }

                    if (start[i] == 0) {
                        LengthRangeProperty bpd = gridUnits[i].getCell()
                                .getBlockProgressionDimension();
                        if (!bpd.getMinimum(getTableLM()).isAuto()) {
                            int min = bpd.getMinimum(getTableLM())
                                        .getLength().getValue(getTableLM());
                            if (min > 0) {
                                len = Math.max(len, bpd.getMinimum(getTableLM())
                                        .getLength().getValue(getTableLM()));
                            }
                        }
                        if (!bpd.getOptimum(getTableLM()).isAuto()) {
                            int opt = bpd.getOptimum(getTableLM())
                                        .getLength().getValue(getTableLM());
                            if (opt > 0) {
                                len = Math.max(len, opt);
                            }
                        }
                        if (gridUnits[i].getRow() != null) {
                            bpd = gridUnits[i].getRow().getBlockProgressionDimension();
                            if (!bpd.getMinimum(getTableLM()).isAuto()) {
                                int min = bpd.getMinimum(getTableLM()).getLength()
                                            .getValue(getTableLM());
                                if (min > 0) {
                                    len = Math.max(len, min);
                                }
                            }
                        }
                    }
                   
                    // Add the padding if any
                    len += gridUnits[i].getBorders()
                                    .getPaddingBefore(false, gridUnits[i].getCellLM());
                    len += gridUnits[i].getBorders()
                                    .getPaddingAfter(false, gridUnits[i].getCellLM());

                    //Now add the borders to the contentLength
                    if (isSeparateBorderModel()) {
                        len += gridUnits[i].getBorders().getBorderBeforeWidth(false);
                        len += gridUnits[i].getBorders().getBorderAfterWidth(false);
                    }
                    int startRow = Math.max(gridUnits[i].getStartRow(), firstRow[bt]);
                    Integer storedOffset = (Integer)rowOffsets[bt].get(new Integer(startRow));
                    int effYOffset;
                    if (storedOffset != null) {
                        effYOffset = storedOffset.intValue();
                    } else {
                        effYOffset = yoffset;
                    }
                    len -= yoffset - effYOffset;
                    actualRowHeight = Math.max(actualRowHeight, len);
                }
            }
            if (readyCount == 0) {
                return 0;
            }
            actualRowHeight += 2 * getTableLM().getHalfBorderSeparationBPD();
            lastRowHeight = actualRowHeight;
           
            //Add areas for row
            addRowBackgroundArea(rowFO, actualRowHeight, layoutContext.getRefIPD(), yoffset);
            for (int i = 0; i < gridUnits.length; i++) {
                GridUnit currentGU = lastRow.safelyGetGridUnit(i);
                if ((gridUnits[i] != null)
                        && (forcedFlush || ((end[i] == gridUnits[i].getElements().size() - 1))
                                && (currentGU == null || currentGU.isLastGridUnitRowSpan()))
                    || (gridUnits[i] == null && currentGU != null)) {
                    //the last line in the "if" above is to avoid a premature end of an
                    //row-spanned cell because no GridUnitParts are generated after a cell is
                    //finished with its content. currentGU can be null if there's no grid unit
                    //at this place in the current row (empty cell and no borders to process)
                    if (log.isDebugEnabled()) {
                        log.debug((forcedFlush ? "FORCED " : "") + "flushing..." + i + " "
                                + start[i] + "-" + end[i]);
                    }
                    PrimaryGridUnit gu = gridUnits[i];
                    if (gu == null
                            && !currentGU.isEmpty()
                            && currentGU.getColSpanIndex() == 0
                            && currentGU.isLastGridUnitColSpan()
                            && (forcedFlush || currentGU.isLastGridUnitRowSpan())) {
                        gu = currentGU.getPrimary();
                    }
                    if (gu != null) {
                        addAreasForCell(gu, start[i], end[i],
                                lastRow, 
                                partLength[i], actualRowHeight);
                        gridUnits[i] = null;
                        start[i] = 0;
                        end[i] = -1;
                        partLength[i] = 0;
                    }
                }
            }
            return actualRowHeight;
        }

        private void addAreasForCell(PrimaryGridUnit pgu, int startPos, int endPos,
                EffRow row, int contentHeight, int rowHeight) {
            int bt = row.getBodyType();
            if (firstRow[bt] < 0) {
                firstRow[bt] = row.getIndex();
            }
            //Determine the first row in this sequence
            int startRow = Math.max(pgu.getStartRow(), firstRow[bt]);
            //Determine y offset for the cell
            Integer offset = (Integer)rowOffsets[bt].get(new Integer(startRow));
            while (offset == null) {
                startRow--;
                offset = (Integer)rowOffsets[bt].get(new Integer(startRow));
            }
            int effYOffset = offset.intValue();
            int effCellHeight = rowHeight;
            effCellHeight += yoffset - effYOffset;
            if (log.isDebugEnabled()) {
                log.debug("Creating area for cell:");
                log.debug("  current row: " + row.getIndex());
                log.debug("  start row: " + pgu.getStartRow() + " " + yoffset + " " + effYOffset);
                log.debug("  contentHeight: " + contentHeight + " rowHeight=" + rowHeight
                        + " effCellHeight=" + effCellHeight);
            }
            TableCellLayoutManager cellLM = pgu.getCellLM();
            cellLM.setXOffset(getXOffsetOfGridUnit(pgu));
            cellLM.setYOffset(effYOffset);
            cellLM.setContentHeight(contentHeight);
            cellLM.setRowHeight(effCellHeight);
            //cellLM.setRowHeight(row.getHeight().opt);
            int prevBreak = ElementListUtils.determinePreviousBreak(pgu.getElements(), startPos);
            if (endPos >= 0) {
                SpaceResolver.performConditionalsNotification(pgu.getElements(),
                        startPos, endPos, prevBreak);
            }
            cellLM.addAreas(new KnuthPossPosIter(pgu.getElements(),
                    startPos, endPos + 1), layoutContext);
        }
       
    }

    /**
     * Get the area for a row for background.
     * @param row the table-row object or null
     * @return the row area or null if there's no background to paint
     */
    public Block getRowArea(TableRow row) {
        if (row == null || !row.getCommonBorderPaddingBackground().hasBackground()) {
            return null;
        } else {
            Block block = new Block();
            block.addTrait(Trait.IS_REFERENCE_AREA, Boolean.TRUE);
            block.setPositioning(Block.ABSOLUTE);
            return block;
        }
    }

    /**
     * Adds the area for the row background if any.
     * @param row row for which to generate the background
     * @param bpd block-progression-dimension of the row
     * @param ipd inline-progression-dimension of the row
     * @param yoffset Y offset at which to paint
     */
    public void addRowBackgroundArea(TableRow row, int bpd, int ipd, int yoffset) {
        //Add row background if any
        Block rowBackground = getRowArea(row);
        if (rowBackground != null) {
            rowBackground.setBPD(bpd);
            rowBackground.setIPD(ipd);
            rowBackground.setXOffset(this.startXOffset);
            rowBackground.setYOffset(yoffset);
            getTableLM().addChildArea(rowBackground);
            TraitSetter.addBackground(rowBackground,
                    row.getCommonBorderPaddingBackground(), getTableLM());
        }
    }
   
   
    /**
     * Sets the overall starting x-offset. Used for proper placement of cells.
     * @param startXOffset starting x-offset (table's start-indent)
     */
    public void setStartXOffset(int startXOffset) {
        this.startXOffset = startXOffset;
    }

    /**
     * @return the amount of block-progression-dimension used by the content
     */
    public int getUsedBPD() {
        return this.usedBPD;
    }
   
    /**
     * Represents a non-dividable part of a grid unit. Used by the table stepper.
     */
    protected static class GridUnitPart {
       
        /** Primary grid unit */
        protected PrimaryGridUnit pgu;
        /** Index of the starting element of this part */
        protected int start;
        /** Index of the ending element of this part */
        protected int end;
       
        /**
         * Creates a new GridUnitPart.
         * @param pgu Primary grid unit
         * @param start starting element
         * @param end ending element
         */
        protected GridUnitPart(PrimaryGridUnit pgu, int start, int end) {
            this.pgu = pgu;
            this.start = start;
            this.end = end;
        }
       
        /** @return true if this part is the first part of a cell */
        public boolean isFirstPart() {
            return (start == 0);
        }
       
        /** @return true if this part is the last part of a cell */
        public boolean isLastPart() {
            return (end >= 0 && end == pgu.getElements().size() - 1);
        }
       
        /** @see java.lang.Object#toString() */
        public String toString() {
            StringBuffer sb = new StringBuffer("Part: ");
            sb.append(start).append("-").append(end);
            sb.append(" [").append(isFirstPart() ? "F" : "-").append(isLastPart() ? "L" : "-");
            sb.append("] ").append(pgu);
            return sb.toString();
        }
       
    }
   
    /**
     * This class represents a Position specific to this layout manager. Used for normal content
     * cases.
     */
    public static class TableContentPosition extends Position {

        /** The position is the first of the row group. */
        public static final int FIRST_IN_ROWGROUP = 1;
        /** The position is the last of the row group. */
        public static final int LAST_IN_ROWGROUP = 2;
       
        /** the list of GridUnitParts making up this position */
        protected List gridUnitParts;
        /** effective row this position belongs to */
        protected EffRow row;
        /** flags for the position */
        protected int flags;
       
        /**
         * Creates a new TableContentPosition.
         * @param lm applicable layout manager
         * @param gridUnitParts the list of GridUnitPart instances
         * @param row effective row this position belongs to
         */
        protected TableContentPosition(LayoutManager lm, List gridUnitParts,
                EffRow row) {
            super(lm);
            this.gridUnitParts = gridUnitParts;
            this.row = row;
        }
       
        /**
         * Returns a flag for this GridUnit.
         * @param which the requested flag
         * @return the value of the flag
         */
        public boolean getFlag(int which) {
            return (flags & (1 << which)) != 0;
        }
       
        /**
         * Sets a flag on a GridUnit.
         * @param which the flag to set
         * @param value the new value for the flag
         */
        public void setFlag(int which, boolean value) {
            if (value) {
                flags |= (1 << which); //set flag
            } else {
                flags &= ~(1 << which); //clear flag
            }
        }
       
        /** @see java.lang.Object#toString() */
        public String toString() {
            StringBuffer sb = new StringBuffer("TableContentPosition:");
            sb.append(getIndex());
            sb.append("[");
            sb.append(row.getIndex()).append("/");
            sb.append(getFlag(FIRST_IN_ROWGROUP) ? "F" : "-");
            sb.append(getFlag(LAST_IN_ROWGROUP) ? "L" : "-").append("]");
            sb.append("(");
            sb.append(gridUnitParts);
            sb.append(")");
            return sb.toString();
        }
    }
   
    /**
     * This class represents a Position specific to this layout manager. Used for table
     * headers and footers at the beginning and end of a table.
     */
    public static class TableHeaderFooterPosition extends Position {

        /** True indicates a position for a header, false for a footer. */
        protected boolean header;
        /** Element list representing the header/footer */
        protected List nestedElements;
       
        /**
         * Creates a new TableHeaderFooterPosition.
         * @param lm applicable layout manager
         * @param header True indicates a position for a header, false for a footer.
         * @param nestedElements Element list representing the header/footer
         */
        protected TableHeaderFooterPosition(LayoutManager lm,
                boolean header, List nestedElements) {
            super(lm);
            this.header = header;
            this.nestedElements = nestedElements;
        }
       
        /** @see java.lang.Object#toString() */
        public String toString() {
            StringBuffer sb = new StringBuffer("Table");
            sb.append(header ? "Header" : "Footer");
            sb.append("Position:");
            sb.append(getIndex()).append("(");
            sb.append(nestedElements);
            sb.append(")");
            return sb.toString();
        }
    }

    /**
    * This class represents a Position specific to this layout manager. Used for table
    * headers and footers at breaks.
    */
    public static class TableHFPenaltyPosition extends Position {
       
        /** Element list for the header */
        protected List headerElements;
        /** Element list for the footer */
        protected List footerElements;
       
        /**
         * Creates a new TableHFPenaltyPosition
         * @param lm applicable layout manager
         */
        protected TableHFPenaltyPosition(LayoutManager lm) {
            super(lm);
        }
       
        /** @see java.lang.Object#toString() */
        public String toString() {
            StringBuffer sb = new StringBuffer("TableHFPenaltyPosition:");
            sb.append(getIndex()).append("(");
            sb.append("header:");
            sb.append(headerElements);
            sb.append(", footer:");
            sb.append(footerElements);
            sb.append(")");
            return sb.toString();
        }
    }
   
    // --------- Property Resolution related functions --------- //
   
    /**
     * @see org.apache.fop.datatypes.PercentBaseContext#getBaseLength(int, FObj)
     */
    public int getBaseLength(int lengthBase, FObj fobj) {
        return tableLM.getBaseLength(lengthBase, fobj);
    }

}
TOP

Related Classes of org.apache.fop.layoutmgr.table.TableContentLayoutManager$TableHeaderFooterPosition

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.