Java tutorial
/* * 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: TableContentLayoutManager.java 1328515 2012-04-20 21:26:43Z gadams $ */ package org.apache.fop.layoutmgr.table; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.ListIterator; import java.util.Map; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.fop.datatypes.PercentBaseContext; import org.apache.fop.fo.Constants; import org.apache.fop.fo.FObj; import org.apache.fop.fo.flow.table.EffRow; import org.apache.fop.fo.flow.table.PrimaryGridUnit; import org.apache.fop.fo.flow.table.Table; import org.apache.fop.fo.flow.table.TablePart; import org.apache.fop.layoutmgr.BreakElement; import org.apache.fop.layoutmgr.ElementListUtils; import org.apache.fop.layoutmgr.Keep; import org.apache.fop.layoutmgr.KnuthBox; import org.apache.fop.layoutmgr.KnuthElement; import org.apache.fop.layoutmgr.KnuthGlue; import org.apache.fop.layoutmgr.KnuthPossPosIter; import org.apache.fop.layoutmgr.LayoutContext; import org.apache.fop.layoutmgr.ListElement; import org.apache.fop.layoutmgr.Position; import org.apache.fop.layoutmgr.PositionIterator; import org.apache.fop.layoutmgr.SpaceResolver.SpaceHandlingBreakPosition; import org.apache.fop.util.BreakUtil; /** * Layout manager for table contents, particularly managing the creation of combined element lists. */ public class TableContentLayoutManager implements PercentBaseContext { /** Logger **/ private static final Log LOG = LogFactory.getLog(TableContentLayoutManager.class); private TableLayoutManager tableLM; private TableRowIterator bodyIter; private TableRowIterator headerIter; private TableRowIterator footerIter; private LinkedList headerList; private LinkedList footerList; private int headerNetHeight = 0; private int footerNetHeight = 0; private int startXOffset; private int usedBPD; private TableStepper stepper; /** * Main constructor * @param parent Parent layout manager */ TableContentLayoutManager(TableLayoutManager parent) { this.tableLM = parent; Table table = getTableLM().getTable(); this.bodyIter = new TableRowIterator(table, TableRowIterator.BODY); if (table.getTableHeader() != null) { headerIter = new TableRowIterator(table, TableRowIterator.HEADER); } if (table.getTableFooter() != null) { footerIter = new TableRowIterator(table, TableRowIterator.FOOTER); } stepper = new TableStepper(this); } /** * @return the table layout manager */ TableLayoutManager getTableLM() { return this.tableLM; } /** @return true if the table uses the separate border model. */ boolean isSeparateBorderModel() { return getTableLM().getTable().isSeparateBorderModel(); } /** * @return the column setup of this table */ 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.footerNetHeight; } /** @return the header element list */ protected LinkedList getHeaderElements() { return this.headerList; } /** @return the footer element list */ protected LinkedList getFooterElements() { return this.footerList; } /** * Get a sequence of KnuthElements representing the content * of the node assigned to the LM. * * @param context the LayoutContext used to store layout information * @param alignment the desired text alignment * @return the list of KnuthElements * @see org.apache.fop.layoutmgr.LayoutManager#getNextKnuthElements(LayoutContext, int) */ public List getNextKnuthElements(LayoutContext context, int alignment) { if (LOG.isDebugEnabled()) { 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); 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 start //of the whole list headerAsFirst = box; } else { headerAsSecondToLast = box; } } if (footerIter != null && footerList == null) { this.footerList = getKnuthElementsForRowIterator(footerIter, context, alignment, TableRowIterator.FOOTER); this.footerNetHeight = ElementListUtils.calcContentLength(this.footerList); if (LOG.isDebugEnabled()) { LOG.debug("==> Footer: " + footerNetHeight + " - " + this.footerList); } //We can simply add the table footer 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(bodyIter, context, alignment, TableRowIterator.BODY); if (headerAsFirst != null) { int insertionPoint = 0; if (returnList.size() > 0 && ((ListElement) returnList.getFirst()).isForcedBreak()) { insertionPoint++; } returnList.add(insertionPoint, headerAsFirst); } else if (headerAsSecondToLast != null) { int insertionPoint = returnList.size(); if (returnList.size() > 0 && ((ListElement) returnList.getLast()).isForcedBreak()) { insertionPoint--; } returnList.add(insertionPoint, headerAsSecondToLast); } if (footerAsLast != null) { int insertionPoint = returnList.size(); if (returnList.size() > 0 && ((ListElement) returnList.getLast()).isForcedBreak()) { insertionPoint--; } returnList.add(insertionPoint, 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 = iter.getNextRowGroup(); // TODO homogenize the handling of keeps and breaks context.clearKeepsPending(); context.setBreakBefore(Constants.EN_AUTO); context.setBreakAfter(Constants.EN_AUTO); Keep keepWithPrevious = Keep.KEEP_AUTO; int breakBefore = Constants.EN_AUTO; if (rowGroup != null) { RowGroupLayoutManager rowGroupLM = new RowGroupLayoutManager(getTableLM(), rowGroup, stepper); List nextRowGroupElems = rowGroupLM.getNextKnuthElements(context, alignment, bodyType); keepWithPrevious = keepWithPrevious.compare(context.getKeepWithPreviousPending()); breakBefore = context.getBreakBefore(); int breakBetween = context.getBreakAfter(); returnList.addAll(nextRowGroupElems); while ((rowGroup = iter.getNextRowGroup()) != null) { rowGroupLM = new RowGroupLayoutManager(getTableLM(), rowGroup, stepper); //Note previous pending keep-with-next and clear the strength //(as the layout context is reused) Keep keepWithNextPending = context.getKeepWithNextPending(); context.clearKeepWithNextPending(); //Get elements for next row group nextRowGroupElems = rowGroupLM.getNextKnuthElements(context, alignment, bodyType); /* * The last break element produced by TableStepper (for the previous row * group) may be used to represent the break between the two row groups. * Its penalty value and break class must just be overridden by the * characteristics of the keep or break between the two. * * However, we mustn't forget that if the after border of the last row of * the row group is thicker in the normal case than in the trailing case, * an additional glue will be appended to the element list. So we may have * to go two steps backwards in the list. */ //Determine keep constraints Keep keep = keepWithNextPending.compare(context.getKeepWithPreviousPending()); context.clearKeepWithPreviousPending(); keep = keep.compare(getTableLM().getKeepTogether()); int penaltyValue = keep.getPenalty(); int breakClass = keep.getContext(); breakBetween = BreakUtil.compareBreakClasses(breakBetween, context.getBreakBefore()); if (breakBetween != Constants.EN_AUTO) { penaltyValue = -KnuthElement.INFINITE; breakClass = breakBetween; } BreakElement breakElement; ListIterator elemIter = returnList.listIterator(returnList.size()); ListElement elem = (ListElement) elemIter.previous(); if (elem instanceof KnuthGlue) { breakElement = (BreakElement) elemIter.previous(); } else { breakElement = (BreakElement) elem; } breakElement.setPenaltyValue(penaltyValue); breakElement.setBreakClass(breakClass); returnList.addAll(nextRowGroupElems); breakBetween = context.getBreakAfter(); } } /* * The last break produced for the last row-group of this table part must be * removed, because the breaking after the table will be handled by TableLM. * Unless the element list ends with a glue, which must be kept to accurately * represent the content. In such a case the break is simply disabled by setting * its penalty to infinite. */ ListIterator elemIter = returnList.listIterator(returnList.size()); ListElement elem = (ListElement) elemIter.previous(); if (elem instanceof KnuthGlue) { BreakElement breakElement = (BreakElement) elemIter.previous(); breakElement.setPenaltyValue(KnuthElement.INFINITE); } else { elemIter.remove(); } context.updateKeepWithPreviousPending(keepWithPrevious); context.setBreakBefore(breakBefore); //fox:widow-content-limit int widowContentLimit = getTableLM().getTable().getWidowContentLimit().getValue(); if (widowContentLimit != 0 && bodyType == TableRowIterator.BODY) { ElementListUtils.removeLegalBreaks(returnList, widowContentLimit); } //fox:orphan-content-limit int orphanContentLimit = getTableLM().getTable().getOrphanContentLimit().getValue(); if (orphanContentLimit != 0 && bodyType == TableRowIterator.BODY) { ElementListUtils.removeLegalBreaksFromEnd(returnList, orphanContentLimit); } return returnList; } /** * Returns the X offset of the given grid unit. * @param gu the grid unit * @return the requested X offset */ protected int getXOffsetOfGridUnit(PrimaryGridUnit gu) { return getXOffsetOfGridUnit(gu.getColIndex(), gu.getCell().getNumberColumnsSpanned()); } /** * Returns the X offset of the grid unit in the given column. * @param colIndex the column index (zero-based) * @param nrColSpan number columns spanned * @return the requested X offset */ protected int getXOffsetOfGridUnit(int colIndex, int nrColSpan) { return startXOffset + getTableLM().getColumns().getXOffset(colIndex + 1, nrColSpan, getTableLM()); } /** * Adds the areas generated by this layout manager to the area tree. * @param parentIter the position iterator * @param layoutContext the layout context for adding areas */ void addAreas(PositionIterator parentIter, LayoutContext layoutContext) { this.usedBPD = 0; RowPainter painter = new RowPainter(this, layoutContext); List tablePositions = 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 if (pos instanceof TableContentPosition) { tablePositions.add(pos); } else { if (LOG.isDebugEnabled()) { LOG.debug("Ignoring position: " + 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 addHeaderFooterAreas(headerElements, tableLM.getTable().getTableHeader(), painter, false); } if (tablePositions.isEmpty()) { // TODO make sure this actually never happens LOG.error("tablePositions empty." + " Please send your FO file to fop-users@xmlgraphics.apache.org"); } else { // Here we are sure that posIter iterates only over TableContentPosition instances addBodyAreas(tablePositions.iterator(), painter, footerElements == null); } if (footerElements != null) { //Positions for footers are simply added at the end addHeaderFooterAreas(footerElements, tableLM.getTable().getTableFooter(), painter, true); } this.usedBPD += painter.getAccumulatedBPD(); if (markers != null) { getTableLM().getCurrentPV().addMarkers(markers, false, getTableLM().isFirst(firstPos), getTableLM().isLast(lastCheckPos)); } } private void addHeaderFooterAreas(List elements, TablePart part, RowPainter painter, boolean lastOnPage) { List lst = new java.util.ArrayList(elements.size()); for (Iterator iter = new KnuthPossPosIter(elements); iter.hasNext();) { Position pos = (Position) iter.next(); /* * Unlike for the body the Positions associated to the glues generated by * TableStepper haven't been removed yet. */ if (pos instanceof TableContentPosition) { lst.add((TableContentPosition) pos); } } addTablePartAreas(lst, painter, part, true, true, true, lastOnPage); } /** * Iterates over the positions corresponding to the table's body (which may contain * several table-body elements!) and adds the corresponding areas. * * @param iterator iterator over TableContentPosition elements. Those positions * correspond to the elements of the body present on the current page * @param painter * @param lastOnPage true if the table has no footer (then the last line of the table * that will be present on the page belongs to the body) */ private void addBodyAreas(Iterator iterator, RowPainter painter, boolean lastOnPage) { painter.startBody(); List lst = new java.util.ArrayList(); TableContentPosition pos = (TableContentPosition) iterator.next(); boolean isFirstPos = pos.getFlag(TableContentPosition.FIRST_IN_ROWGROUP) && pos.getRow().getFlag(EffRow.FIRST_IN_PART); TablePart part = pos.getTablePart(); lst.add(pos); while (iterator.hasNext()) { pos = (TableContentPosition) iterator.next(); if (pos.getTablePart() != part) { addTablePartAreas(lst, painter, part, isFirstPos, true, false, false); isFirstPos = true; lst.clear(); part = pos.getTablePart(); } lst.add(pos); } boolean isLastPos = pos.getFlag(TableContentPosition.LAST_IN_ROWGROUP) && pos.getRow().getFlag(EffRow.LAST_IN_PART); addTablePartAreas(lst, painter, part, isFirstPos, isLastPos, true, lastOnPage); painter.endBody(); } /** * Adds the areas corresponding to a single fo:table-header/footer/body element. */ private void addTablePartAreas(List positions, RowPainter painter, TablePart body, boolean isFirstPos, boolean isLastPos, boolean lastInBody, boolean lastOnPage) { getTableLM().getCurrentPV().addMarkers(body.getMarkers(), true, isFirstPos, isLastPos); painter.startTablePart(body); for (Iterator iter = positions.iterator(); iter.hasNext();) { painter.handleTableContentPosition((TableContentPosition) iter.next()); } getTableLM().getCurrentPV().addMarkers(body.getMarkers(), false, isFirstPos, isLastPos); painter.endTablePart(lastInBody, lastOnPage); } /** * Sets the overall starting x-offset. Used for proper placement of cells. * @param startXOffset starting x-offset (table's start-indent) */ void setStartXOffset(int startXOffset) { this.startXOffset = startXOffset; } /** * @return the amount of block-progression-dimension used by the content */ int getUsedBPD() { return this.usedBPD; } // --------- Property Resolution related functions --------- // /** * {@inheritDoc} */ public int getBaseLength(int lengthBase, FObj fobj) { return tableLM.getBaseLength(lengthBase, fobj); } }