org.apache.fop.layoutmgr.table.TableContentLayoutManager Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of org.apache.fop Show documentation
Show all versions of org.apache.fop Show documentation
The core maven build properties
The newest version!
/*
* 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 1872773 2020-01-14 12:07:58Z ssteiner $ */
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.Marker;
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.TableBody;
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.FootenoteUtil;
import org.apache.fop.layoutmgr.FootnoteBodyLayoutManager;
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.KnuthGlue;
import org.apache.fop.layoutmgr.KnuthPossPosIter;
import org.apache.fop.layoutmgr.LayoutContext;
import org.apache.fop.layoutmgr.ListElement;
import org.apache.fop.layoutmgr.PageBreaker;
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;
private int footerNetHeight;
private int startXOffset;
private int usedBPD;
private TableStepper stepper;
private boolean headerIsBeingRepeated;
private boolean atLeastOnce;
/**
* 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;
LinkedList returnList = new LinkedList();
int headerFootnoteBPD = 0;
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);
List footnoteList = FootenoteUtil.getFootnotes(headerList);
KnuthBox box = (footnoteList.isEmpty() || !getTableLM().getTable().omitHeaderAtBreak())
? new KnuthBox(headerNetHeight, pos, false)
: new KnuthBlockBox(headerNetHeight, footnoteList, pos, false);
if (getTableLM().getTable().omitHeaderAtBreak()) {
//We can simply add the table header at the start
//of the whole list
headerAsFirst = box;
} else {
if (!footnoteList.isEmpty()) {
List> footnotes = PageBreaker.getFootnoteKnuthElements(
getTableLM().getPSLM().getFlowLayoutManager(), context, footnoteList);
getTableLM().setHeaderFootnotes(footnotes);
headerFootnoteBPD = getFootnotesBPD(footnotes);
returnList.add(new KnuthBlockBox(-headerFootnoteBPD, footnoteList,
new Position(getTableLM()), true));
headerNetHeight += headerFootnoteBPD;
}
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);
List footnoteList = FootenoteUtil.getFootnotes(footerList);
footerAsLast = footnoteList.isEmpty()
? new KnuthBox(footerNetHeight, pos, false)
: new KnuthBlockBox(footerNetHeight, footnoteList, pos, false);
if (!(getTableLM().getTable().omitFooterAtBreak() || footnoteList.isEmpty())) {
List> footnotes = PageBreaker.getFootnoteKnuthElements(
getTableLM().getPSLM().getFlowLayoutManager(), context, footnoteList);
getTableLM().setFooterFootnotes(footnotes);
footerNetHeight += getFootnotesBPD(footnotes);
}
}
returnList.addAll(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);
}
if (headerFootnoteBPD != 0) {
returnList.add(new KnuthBox(headerFootnoteBPD, new Position(getTableLM()), true));
}
return returnList;
}
private int getFootnotesBPD(List> footnotes) {
int bpd = 0;
for (List footnote : footnotes) {
bpd += ElementListUtils.calcContentLength(footnote);
}
return bpd;
}
/**
* 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.
*/
if (!returnList.isEmpty()) {
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 = 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);
}
}
}
boolean treatFooterAsArtifact = layoutContext.treatAsArtifact();
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;
treatFooterAsArtifact = true;
}
}
// there may be table fragment markers stored; clear them since we are starting a new fragment
tableLM.clearTableFragmentMarkers();
// note: markers at table level are to be retrieved by the page, not by the table itself
Map markers = getTableLM().getTable().getMarkers();
if (markers != null) {
getTableLM().getCurrentPV().registerMarkers(markers,
true, getTableLM().isFirst(firstPos), getTableLM().isLast(lastCheckPos));
}
if (headerElements != null) {
boolean ancestorTreatAsArtifact = layoutContext.treatAsArtifact();
if (headerIsBeingRepeated) {
layoutContext.setTreatAsArtifact(true);
if (!getTableLM().getHeaderFootnotes().isEmpty()) {
getTableLM().getPSLM().addTableHeaderFootnotes(getTableLM().getHeaderFootnotes());
}
}
//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 (!ancestorTreatAsArtifact) {
headerIsBeingRepeated = true;
}
layoutContext.setTreatAsArtifact(ancestorTreatAsArtifact);
}
if (tablePositions.isEmpty()) {
// TODO make sure this actually never happens
LOG.error("tablePositions empty."
+ " Please send your FO file to [email protected]");
} else {
// Here we are sure that posIter iterates only over TableContentPosition instances
addBodyAreas(tablePositions.iterator(), painter, footerElements == null);
}
// if there are TCLMs saved because they have a RetrieveTableMarker, we repeat the header areas now;
// this can also be done after the areas for the footer are added but should be the same as here
tableLM.setRepeateHeader(atLeastOnce);
tableLM.repeatAddAreasForSavedTableHeaderTableCellLayoutManagers();
atLeastOnce = true;
if (footerElements != null && !footerElements.isEmpty()) {
boolean ancestorTreatAsArtifact = layoutContext.treatAsArtifact();
layoutContext.setTreatAsArtifact(treatFooterAsArtifact);
//Positions for footers are simply added at the end
addHeaderFooterAreas(footerElements, tableLM.getTable().getTableFooter(), painter, true);
if (lastPos instanceof TableHFPenaltyPosition && !tableLM.getFooterFootnotes().isEmpty()) {
tableLM.getPSLM().addTableFooterFootnotes(getTableLM().getFooterFootnotes());
}
layoutContext.setTreatAsArtifact(ancestorTreatAsArtifact);
}
this.usedBPD += painter.getAccumulatedBPD();
if (markers != null) {
getTableLM().getCurrentPV().registerMarkers(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().registerMarkers(body.getMarkers(),
true, isFirstPos, isLastPos);
if (body instanceof TableBody) {
getTableLM().registerMarkers(body.getMarkers(), true, isFirstPos, isLastPos);
}
painter.startTablePart(body);
for (Object position : positions) {
painter.handleTableContentPosition((TableContentPosition) position);
}
getTableLM().getCurrentPV().registerMarkers(body.getMarkers(),
false, isFirstPos, isLastPos);
if (body instanceof TableBody) {
getTableLM().registerMarkers(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);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy