blob: 783c2ff8fc1806ad43d8b7a6fa82cb94f13fafea [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/* $Id$ */
package org.apache.fop.fo.flow.table;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.apache.fop.fo.properties.CommonBorderPaddingBackground;
import org.apache.fop.layoutmgr.table.CollapsingBorderModel;
/**
* A class that implements the border-collapsing model.
*/
class CollapsingBorderResolver implements BorderResolver {
private Table table;
private CollapsingBorderModel collapsingBorderModel;
/**
* The previously registered row, either in the header or the body(-ies), but not in
* the footer (handled separately).
*/
private List/*<GridUnit>*/ previousRow;
private boolean firstInTable;
private List/*<GridUnit>*/ footerFirstRow;
/** The last currently registered footer row. */
private List/*<GridUnit>*/ footerLastRow;
private Resolver delegate;
// Re-use the same ResolverInBody for every table-body
// Important to properly handle firstInBody!!
private Resolver resolverInBody = new ResolverInBody();
private Resolver resolverInFooter;
private List/*<ConditionalBorder>*/ leadingBorders;
private List/*<ConditionalBorder>*/ trailingBorders;
/* TODO Temporary hack for resolved borders in header */
/* Currently the normal border is always used. */
private List/*<GridUnit>*/ headerLastRow = null;
/* End of temporary hack */
/**
* Base class for delegate resolvers. Implementation of the State design pattern: the
* treatment differs slightly whether we are in the table's header, footer or body. To
* avoid complicated if statements, specialised delegate resolvers will be used
* instead.
*/
private abstract class Resolver {
protected TablePart tablePart;
protected boolean firstInPart;
private BorderSpecification borderStartTableAndBody;
private BorderSpecification borderEndTableAndBody;
/**
* Integrates border-before specified on the table and its column.
*
* @param row the first row of the table (in the header, or in the body if the
* table has no header)
* @param withNormal
* @param withLeadingTrailing
* @param withRest
*/
void resolveBordersFirstRowInTable(List/*<GridUnit>*/ row, boolean withNormal,
boolean withLeadingTrailing, boolean withRest) {
assert firstInTable;
for (int i = 0; i < row.size(); i++) {
TableColumn column = table.getColumn(i);
((GridUnit) row.get(i)).integrateBorderSegment(
CommonBorderPaddingBackground.BEFORE, column, withNormal,
withLeadingTrailing, withRest);
}
firstInTable = false;
}
/**
* Resolves border-after for the first row, border-before for the second one.
*
* @param rowBefore
* @param rowAfter
*/
void resolveBordersBetweenRows(List/*<GridUnit>*/ rowBefore, List/*<GridUnit>*/ rowAfter) {
assert rowBefore != null && rowAfter != null;
for (int i = 0; i < rowAfter.size(); i++) {
GridUnit gu = (GridUnit) rowAfter.get(i);
if (gu.getRowSpanIndex() == 0) {
GridUnit beforeGU = (GridUnit) rowBefore.get(i);
gu.resolveBorder(beforeGU, CommonBorderPaddingBackground.BEFORE);
}
}
}
/** Integrates the border-after of the part. */
void resolveBordersLastRowInPart(List/*<GridUnit>*/ row, boolean withNormal,
boolean withLeadingTrailing, boolean withRest) {
for (int i = 0; i < row.size(); i++) {
((GridUnit) row.get(i)).integrateBorderSegment(CommonBorderPaddingBackground.AFTER,
tablePart, withNormal, withLeadingTrailing, withRest);
}
}
/**
* Integrates border-after specified on the table and its columns.
*
* @param row the last row of the footer, or of the last body if the table has no
* footer
* @param withNormal
* @param withLeadingTrailing
* @param withRest
*/
void resolveBordersLastRowInTable(List/*<GridUnit>*/ row, boolean withNormal,
boolean withLeadingTrailing, boolean withRest) {
for (int i = 0; i < row.size(); i++) {
TableColumn column = table.getColumn(i);
((GridUnit) row.get(i)).integrateBorderSegment(CommonBorderPaddingBackground.AFTER,
column, withNormal, withLeadingTrailing, withRest);
}
}
/**
* Integrates either border-before specified on the table and its columns if the
* table has no header, or border-after specified on the cells of the header's
* last row. For the case the grid unit are at the top of a page.
*
* @param row
*/
void integrateLeadingBorders(List/*<GridUnit>*/ row) {
for (int i = 0; i < table.getNumberOfColumns(); i++) {
GridUnit gu = (GridUnit) row.get(i);
ConditionalBorder border = (ConditionalBorder) leadingBorders.get(i);
gu.integrateCompetingBorder(CommonBorderPaddingBackground.BEFORE, border,
false, true, true);
}
}
/**
* Integrates either border-after specified on the table and its columns if the
* table has no footer, or border-before specified on the cells of the footer's
* first row. For the case the grid unit are at the bottom of a page.
*
* @param row
*/
void integrateTrailingBorders(List/*<GridUnit>*/ row) {
for (int i = 0; i < table.getNumberOfColumns(); i++) {
GridUnit gu = (GridUnit) row.get(i);
ConditionalBorder border = (ConditionalBorder) trailingBorders.get(i);
gu.integrateCompetingBorder(CommonBorderPaddingBackground.AFTER, border,
false, true, true);
}
}
void startPart(TablePart part) {
tablePart = part;
firstInPart = true;
borderStartTableAndBody = collapsingBorderModel.determineWinner(table.borderStart,
tablePart.borderStart);
borderEndTableAndBody = collapsingBorderModel.determineWinner(table.borderEnd,
tablePart.borderEnd);
}
/**
* Resolves the applicable borders for the given row.
* <ul>
* <li>Integrates the border-before/after of the containing table-row if any;</li>
* <li>Integrates the border-before of the containing part, if first row;</li>
* <li>Resolves border-start/end between grid units.</li>
* </ul>
*
* @param row the row being finished
* @param container the containing element
*/
void endRow(List/*<GridUnit>*/ row, TableCellContainer container) {
BorderSpecification borderStart = borderStartTableAndBody;
BorderSpecification borderEnd = borderEndTableAndBody;
// Resolve before- and after-borders for the table-row
if (container instanceof TableRow) {
TableRow tableRow = (TableRow) container;
for (Iterator iter = row.iterator(); iter.hasNext();) {
GridUnit gu = (GridUnit) iter.next();
boolean first = (gu.getRowSpanIndex() == 0);
boolean last = gu.isLastGridUnitRowSpan();
gu.integrateBorderSegment(CommonBorderPaddingBackground.BEFORE, tableRow,
first, first, true);
gu.integrateBorderSegment(CommonBorderPaddingBackground.AFTER, tableRow,
last, last, true);
}
borderStart = collapsingBorderModel.determineWinner(borderStart,
tableRow.borderStart);
borderEnd = collapsingBorderModel.determineWinner(borderEnd,
tableRow.borderEnd);
}
if (firstInPart) {
// Integrate the border-before of the part
for (int i = 0; i < row.size(); i++) {
((GridUnit) row.get(i)).integrateBorderSegment(
CommonBorderPaddingBackground.BEFORE, tablePart, true, true, true);
}
firstInPart = false;
}
// Resolve start/end borders in the row
Iterator guIter = row.iterator();
GridUnit gu = (GridUnit) guIter.next();
Iterator colIter = table.getColumns().iterator();
TableColumn col = (TableColumn) colIter.next();
gu.integrateBorderSegment(CommonBorderPaddingBackground.START, col);
gu.integrateBorderSegment(CommonBorderPaddingBackground.START, borderStart);
while (guIter.hasNext()) {
GridUnit nextGU = (GridUnit) guIter.next();
TableColumn nextCol = (TableColumn) colIter.next();
if (gu.isLastGridUnitColSpan()) {
gu.integrateBorderSegment(CommonBorderPaddingBackground.END, col);
nextGU.integrateBorderSegment(CommonBorderPaddingBackground.START, nextCol);
gu.resolveBorder(nextGU, CommonBorderPaddingBackground.END);
}
gu = nextGU;
col = nextCol;
}
gu.integrateBorderSegment(CommonBorderPaddingBackground.END, col);
gu.integrateBorderSegment(CommonBorderPaddingBackground.END, borderEnd);
}
void endPart() {
resolveBordersLastRowInPart(previousRow, true, true, true);
}
abstract void endTable();
}
private class ResolverInHeader extends Resolver {
void endRow(List/*<GridUnit>*/ row, TableCellContainer container) {
super.endRow(row, container);
if (previousRow != null) {
resolveBordersBetweenRows(previousRow, row);
} else {
/*
* This is a bit hacky...
* The two only sensible values for border-before on the header's first row are:
* - at the beginning of the table (normal case)
* - if the header is repeated after each page break
* To represent those values we (ab)use the normal and the rest fields of
* ConditionalBorder. But strictly speaking this is not their purposes.
*/
for (Iterator guIter = row.iterator(); guIter.hasNext();) {
ConditionalBorder borderBefore = ((GridUnit) guIter.next()).borderBefore;
borderBefore.leadingTrailing = borderBefore.normal;
borderBefore.rest = borderBefore.normal;
}
resolveBordersFirstRowInTable(row, true, false, true);
}
previousRow = row;
}
void endPart() {
super.endPart();
leadingBorders = new ArrayList(table.getNumberOfColumns());
/*
* Another hack...
* The border-after of a header is always the same. Leading and rest don't
* apply to cells in the header since they are never broken. To ease
* resolution we override the (normally unused) leadingTrailing and rest
* fields of ConditionalBorder with the only sensible normal field. That way
* grid units from the body will always resolve against the same, normal
* header border.
*/
for (Iterator guIter = previousRow.iterator(); guIter.hasNext();) {
ConditionalBorder borderAfter = ((GridUnit) guIter.next()).borderAfter;
borderAfter.leadingTrailing = borderAfter.normal;
borderAfter.rest = borderAfter.normal;
leadingBorders.add(borderAfter);
}
/* TODO Temporary hack for resolved borders in header */
headerLastRow = previousRow;
/* End of temporary hack */
}
void endTable() {
throw new IllegalStateException();
}
}
private class ResolverInFooter extends Resolver {
void endRow(List/*<GridUnit>*/ row, TableCellContainer container) {
super.endRow(row, container);
if (footerFirstRow == null) {
footerFirstRow = row;
} else {
// There is a previous row
resolveBordersBetweenRows(footerLastRow, row);
}
footerLastRow = row;
}
void endPart() {
resolveBordersLastRowInPart(footerLastRow, true, true, true);
trailingBorders = new ArrayList(table.getNumberOfColumns());
// See same method in ResolverInHeader for an explanation of the hack
for (Iterator guIter = footerFirstRow.iterator(); guIter.hasNext();) {
ConditionalBorder borderBefore = ((GridUnit) guIter.next()).borderBefore;
borderBefore.leadingTrailing = borderBefore.normal;
borderBefore.rest = borderBefore.normal;
trailingBorders.add(borderBefore);
}
}
void endTable() {
// Resolve after/before border between the last row of table-body and the
// first row of table-footer
resolveBordersBetweenRows(previousRow, footerFirstRow);
// See endRow method in ResolverInHeader for an explanation of the hack
for (Iterator guIter = footerLastRow.iterator(); guIter.hasNext();) {
ConditionalBorder borderAfter = ((GridUnit) guIter.next()).borderAfter;
borderAfter.leadingTrailing = borderAfter.normal;
borderAfter.rest = borderAfter.normal;
}
resolveBordersLastRowInTable(footerLastRow, true, false, true);
}
}
private class ResolverInBody extends Resolver {
private boolean firstInBody = true;
void endRow(List/*<GridUnit>*/ row, TableCellContainer container) {
super.endRow(row, container);
if (firstInTable) {
resolveBordersFirstRowInTable(row, true, true, true);
} else {
// Either there is a header, and then previousRow is set to the header's last row,
// or this is not the first row in the body, and previousRow is not null
resolveBordersBetweenRows(previousRow, row);
integrateLeadingBorders(row);
}
integrateTrailingBorders(row);
previousRow = row;
if (firstInBody) {
firstInBody = false;
for (Iterator iter = row.iterator(); iter.hasNext();) {
GridUnit gu = (GridUnit) iter.next();
gu.borderBefore.leadingTrailing = gu.borderBefore.normal;
}
}
}
void endTable() {
if (resolverInFooter != null) {
resolverInFooter.endTable();
} else {
// Trailing and rest borders already resolved with integrateTrailingBorders
resolveBordersLastRowInTable(previousRow, true, false, false);
}
for (Iterator iter = previousRow.iterator(); iter.hasNext();) {
GridUnit gu = (GridUnit) iter.next();
gu.borderAfter.leadingTrailing = gu.borderAfter.normal;
}
}
}
CollapsingBorderResolver(Table table) {
this.table = table;
collapsingBorderModel = CollapsingBorderModel.getBorderModelFor(table.getBorderCollapse());
firstInTable = true;
// Resolve before and after borders between the table and each table-column
int index = 0;
do {
TableColumn col = table.getColumn(index);
// See endRow method in ResolverInHeader for an explanation of the hack
col.borderBefore.integrateSegment(table.borderBefore, true, false, true);
col.borderBefore.leadingTrailing = col.borderBefore.rest;
col.borderAfter.integrateSegment(table.borderAfter, true, false, true);
col.borderAfter.leadingTrailing = col.borderAfter.rest;
/*
* TODO The border resolution must be done only once for each table column,
* even if it's repeated; otherwise, re-resolving against the table's borders
* will lead to null border specifications.
*
* Eventually table columns should probably be cloned instead.
*/
index += col.getNumberColumnsRepeated();
} while (index < table.getNumberOfColumns());
}
/** {@inheritDoc} */
public void endRow(List/*<GridUnit>*/ row, TableCellContainer container) {
delegate.endRow(row, container);
}
/** {@inheritDoc} */
public void startPart(TablePart part) {
if (part instanceof TableHeader) {
delegate = new ResolverInHeader();
} else {
if (leadingBorders == null || table.omitHeaderAtBreak()) {
// No header, leading borders determined by the table
leadingBorders = new ArrayList(table.getNumberOfColumns());
for (Iterator colIter = table.getColumns().iterator(); colIter.hasNext();) {
ConditionalBorder border = ((TableColumn) colIter.next()).borderBefore;
leadingBorders.add(border);
}
}
if (part instanceof TableFooter) {
resolverInFooter = new ResolverInFooter();
delegate = resolverInFooter;
} else {
if (trailingBorders == null || table.omitFooterAtBreak()) {
// No footer, trailing borders determined by the table
trailingBorders = new ArrayList(table.getNumberOfColumns());
for (Iterator colIter = table.getColumns().iterator(); colIter.hasNext();) {
ConditionalBorder border = ((TableColumn) colIter.next()).borderAfter;
trailingBorders.add(border);
}
}
delegate = resolverInBody;
}
}
delegate.startPart(part);
}
/** {@inheritDoc} */
public void endPart() {
delegate.endPart();
}
/** {@inheritDoc} */
public void endTable() {
delegate.endTable();
delegate = null;
/* TODO Temporary hack for resolved borders in header */
if (headerLastRow != null) {
for (Iterator iter = headerLastRow.iterator(); iter.hasNext();) {
GridUnit gu = (GridUnit) iter.next();
gu.borderAfter.leadingTrailing = gu.borderAfter.normal;
}
}
if (footerLastRow != null) {
for (Iterator iter = footerLastRow.iterator(); iter.hasNext();) {
GridUnit gu = (GridUnit) iter.next();
gu.borderAfter.leadingTrailing = gu.borderAfter.normal;
}
}
/* End of temporary hack */
}
}