diff options
author | Vincent Hennebert <vhennebert@apache.org> | 2007-12-13 16:10:32 +0000 |
---|---|---|
committer | Vincent Hennebert <vhennebert@apache.org> | 2007-12-13 16:10:32 +0000 |
commit | 709bda3d6c3bc23050e6bf69d16b32ed6b35b541 (patch) | |
tree | 429aae5baa401dc11337b6e9e2975847ff76d3a4 /src/java | |
parent | 5ea761b17a5cbdcc2ac36f6c841d7a22d8bdd974 (diff) | |
download | xmlgraphics-fop-709bda3d6c3bc23050e6bf69d16b32ed6b35b541.tar.gz xmlgraphics-fop-709bda3d6c3bc23050e6bf69d16b32ed6b35b541.zip |
Implemented the resolution of collapsing borders in the FO tree, for every situation (normal, cell at the top of a page, cell broken), taking conditionality, headers and footers into account.
git-svn-id: https://svn.apache.org/repos/asf/xmlgraphics/fop/trunk@603945 13f79535-47bb-0310-9956-ffa450edef68
Diffstat (limited to 'src/java')
12 files changed, 822 insertions, 140 deletions
diff --git a/src/java/org/apache/fop/fo/FOPropertyMapping.java b/src/java/org/apache/fop/fo/FOPropertyMapping.java index e0acfca7e..5d1a6f31a 100644 --- a/src/java/org/apache/fop/fo/FOPropertyMapping.java +++ b/src/java/org/apache/fop/fo/FOPropertyMapping.java @@ -1141,7 +1141,7 @@ public final class FOPropertyMapping implements Constants { m.useGeneric(genericSpace); corr = new SpacePropertyMaker(m); corr.setCorresponding(PR_MARGIN_TOP, PR_MARGIN_TOP, PR_MARGIN_RIGHT); - corr.setUseParent(true); + corr.setUseParent(false); corr.setRelative(true); addPropertyMaker("space-before", m); diff --git a/src/java/org/apache/fop/fo/flow/table/BorderSpecification.java b/src/java/org/apache/fop/fo/flow/table/BorderSpecification.java index 214f9be07..ce6e1b802 100644 --- a/src/java/org/apache/fop/fo/flow/table/BorderSpecification.java +++ b/src/java/org/apache/fop/fo/flow/table/BorderSpecification.java @@ -20,6 +20,7 @@ package org.apache.fop.fo.flow.table; import org.apache.fop.fo.Constants; +import org.apache.fop.fo.properties.CommonBorderPaddingBackground; import org.apache.fop.fo.properties.CommonBorderPaddingBackground.BorderInfo; /** @@ -28,6 +29,8 @@ import org.apache.fop.fo.properties.CommonBorderPaddingBackground.BorderInfo; */ public/*TODO*/ class BorderSpecification { + private static BorderSpecification defaultBorder; + private BorderInfo borderInfo; private int holder; @@ -43,6 +46,14 @@ public/*TODO*/ class BorderSpecification { this.holder = holder; } + static synchronized BorderSpecification getDefaultBorder() { + if (defaultBorder == null) { + defaultBorder = new BorderSpecification(CommonBorderPaddingBackground + .getDefaultBorderInfo(), Constants.FO_TABLE_CELL); + } + return defaultBorder; + } + /** * Returns this border's informations. * @@ -66,6 +77,17 @@ public/*TODO*/ class BorderSpecification { /** {@inheritDoc} */ public String toString() { - return "{" + borderInfo + ", " + holder + "}"; + String holderName = ""; + switch (holder) { + case Constants.FO_TABLE: holderName = "table"; break; + case Constants.FO_TABLE_COLUMN: holderName = "table-column"; break; + case Constants.FO_TABLE_HEADER: holderName = "table-header"; break; + case Constants.FO_TABLE_FOOTER: holderName = "table-footer"; break; + case Constants.FO_TABLE_BODY: holderName = "table-body"; break; + case Constants.FO_TABLE_ROW: holderName = "table-row"; break; + case Constants.FO_TABLE_CELL: holderName = "table-cell"; break; + default: assert false; + } + return "{" + borderInfo + ", " + holderName + "}"; } } diff --git a/src/java/org/apache/fop/fo/flow/table/CollapsingBorderResolver.java b/src/java/org/apache/fop/fo/flow/table/CollapsingBorderResolver.java index c4f7f9a78..f92f7bb30 100644 --- a/src/java/org/apache/fop/fo/flow/table/CollapsingBorderResolver.java +++ b/src/java/org/apache/fop/fo/flow/table/CollapsingBorderResolver.java @@ -19,6 +19,7 @@ package org.apache.fop.fo.flow.table; +import java.util.ArrayList; import java.util.Iterator; import java.util.List; @@ -31,138 +32,366 @@ class CollapsingBorderResolver implements BorderResolver { private Table table; - private List previousRow; - /** - * The flow of rows is interrupted by the table-footer. Save the header's last row (if - * any) for resolution between it and the body's first row. + * The previously registered row, either in the header or the body(-ies), but not in + * the footer (handled separately). */ - private List previousRowSave; - - private TableBody currentTablePart; + private List/*<GridUnit>*/ previousRow; private boolean firstInTable; - private boolean firstInPart; + private List/*<GridUnit>*/ footerFirstRow; - private List footerFirstRow; + /** The last currently registered footer row. */ + private List/*<GridUnit>*/ footerLastRow; - private List footerLastRow; + private Resolver delegate; - private boolean inFooter; + private Resolver resolverInFooter; - CollapsingBorderResolver(Table table) { - this.table = table; - firstInTable = true; - } + private List/*<ConditionalBorder>*/ leadingBorders; - /** {@inheritDoc} */ - public void endRow(List row, TableCellContainer container) { - // 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(); + private List/*<ConditionalBorder>*/ trailingBorders; + + /** + * 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 TableBody tablePart; + + protected boolean firstInPart; + + /** + * 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 withLeadingTrailing + * @param withNonLeadingTrailing + * @param withRest + */ + void resolveBordersFirstRowInTable(List/*<GridUnit>*/ row, boolean withLeadingTrailing, + boolean withNonLeadingTrailing, 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, withLeadingTrailing, + withNonLeadingTrailing, 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) { - gu.resolveBorder(CommonBorderPaddingBackground.BEFORE, tableRow); - } - if (gu.isLastGridUnitRowSpan()) { - gu.resolveBorder(CommonBorderPaddingBackground.AFTER, tableRow); + GridUnit beforeGU = (GridUnit) rowBefore.get(i); + gu.resolveBorder(beforeGU, CommonBorderPaddingBackground.BEFORE); } } } - if (inFooter) { - if (footerFirstRow == null) { - footerFirstRow = row; + + /** Integrates the border-after of the part. */ + void resolveBordersLastRowInPart(List/*<GridUnit>*/ row, boolean withLeadingTrailing, + boolean withNonLeadingTrailing, boolean withRest) { + for (int i = 0; i < row.size(); i++) { + ((GridUnit) row.get(i)).integrateBorderSegment(CommonBorderPaddingBackground.AFTER, + tablePart, withLeadingTrailing, withNonLeadingTrailing, withRest); } - footerLastRow = row; - } else if (firstInTable) { - // Resolve border-before for the first row in the table + } + + /** + * 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 withLeadingTrailing + * @param withNonLeadingTrailing + * @param withRest + */ + void resolveBordersLastRowInTable(List/*<GridUnit>*/ row, boolean withLeadingTrailing, + boolean withNonLeadingTrailing, boolean withRest) { for (int i = 0; i < row.size(); i++) { TableColumn column = table.getColumn(i); - ((GridUnit) row.get(i)).resolveBorder(CommonBorderPaddingBackground.BEFORE, column); + ((GridUnit) row.get(i)).integrateBorderSegment(CommonBorderPaddingBackground.AFTER, + column, withLeadingTrailing, withNonLeadingTrailing, withRest); } - firstInTable = false; } - if (firstInPart) { - // Resolve border-before for the first row in the part - for (int i = 0; i < row.size(); i++) { - ((GridUnit) row.get(i)).resolveBorder(CommonBorderPaddingBackground.BEFORE, - currentTablePart); + + /** + * 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, + true, false, true); } - firstInPart = false; } - if (previousRow != null) { - // Resolve after/before borders between rows - for (int i = 0; i < row.size(); i++) { + + /** + * 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); - if (gu.getRowSpanIndex() == 0) { - GridUnit beforeGU = (GridUnit) previousRow.get(i); - gu.resolveBorder(beforeGU, CommonBorderPaddingBackground.BEFORE); - } + ConditionalBorder border = (ConditionalBorder) trailingBorders.get(i); + gu.integrateCompetingBorder(CommonBorderPaddingBackground.AFTER, border, + true, false, true); } } - // Resolve start/end borders in the row - Iterator guIter = row.iterator(); - GridUnit gu = (GridUnit) guIter.next(); - gu.resolveBorder(CommonBorderPaddingBackground.START, container); - while (guIter.hasNext()) { - GridUnit guEnd = (GridUnit) guIter.next(); - if (gu.isLastGridUnitColSpan()) { - gu.resolveBorder(guEnd, CommonBorderPaddingBackground.END); + + void startPart(TableBody part) { + tablePart = part; + firstInPart = true; + } + + /** + * 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) { + // 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(); + if (gu.getRowSpanIndex() == 0) { + gu.integrateBorderSegment(CommonBorderPaddingBackground.BEFORE, tableRow, + true, true, true); + } + if (gu.isLastGridUnitRowSpan()) { + gu.integrateBorderSegment(CommonBorderPaddingBackground.AFTER, tableRow, + true, true, true); + } + } + } + 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(); + gu.integrateBorderSegment(CommonBorderPaddingBackground.START, container); + while (guIter.hasNext()) { + GridUnit guEnd = (GridUnit) guIter.next(); + if (gu.isLastGridUnitColSpan()) { + gu.resolveBorder(guEnd, CommonBorderPaddingBackground.END); + } + gu = guEnd; } - gu = guEnd; + gu.integrateBorderSegment(CommonBorderPaddingBackground.END, container); } - gu.resolveBorder(CommonBorderPaddingBackground.END, container); - previousRow = row; + void endPart() { + resolveBordersLastRowInPart(previousRow, true, true, true); + } + + abstract void endTable(); } - /** {@inheritDoc} */ - public void startPart(TableBody part) { - firstInPart = true; - currentTablePart = part; - if (part.isTableFooter()) { - inFooter = true; - previousRowSave = previousRow; - previousRow = null; + 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 nonLeadingTrailing 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 = null; + borderBefore.rest = borderBefore.nonLeadingTrailing; + } + resolveBordersFirstRowInTable(row, false, true, true); + } + previousRow = row; } - } - /** {@inheritDoc} */ - public void endPart(TableBody part) { - // Resolve border-after for the last row in the part - for (int i = 0; i < previousRow.size(); i++) { - ((GridUnit) previousRow.get(i)) - .resolveBorder(CommonBorderPaddingBackground.AFTER, part); + 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 nonLeadingTrailing + * 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.nonLeadingTrailing; + borderAfter.rest = borderAfter.nonLeadingTrailing; + leadingBorders.add(borderAfter); + } } - if (inFooter) { - inFooter = false; - previousRow = previousRowSave; + + void endTable() { + throw new IllegalStateException(); } } - /** {@inheritDoc} */ - public void endTable() { - if (footerFirstRow != null) { + 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.nonLeadingTrailing; + borderBefore.rest = borderBefore.nonLeadingTrailing; + trailingBorders.add(borderBefore); + } + } + + void endTable() { // Resolve after/before border between the last row of table-body and the // first row of table-footer - for (int i = 0; i < footerFirstRow.size(); i++) { - GridUnit gu = (GridUnit) footerFirstRow.get(i); - GridUnit beforeGU = (GridUnit) previousRow.get(i); - gu.resolveBorder(beforeGU, CommonBorderPaddingBackground.BEFORE); + 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 = null; + borderAfter.rest = borderAfter.nonLeadingTrailing; } + resolveBordersLastRowInTable(footerLastRow, false, true, true); } - List lastRow; - if (footerLastRow != null) { - lastRow = footerLastRow; - } else { - lastRow = previousRow; + } + + private class ResolverInBody extends Resolver { + + 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; } - // Resolve border-after for the last row of the table - for (int i = 0; i < lastRow.size(); i++) { - TableColumn column = table.getColumn(i); - ((GridUnit) lastRow.get(i)).resolveBorder(CommonBorderPaddingBackground.AFTER, column); + + void endTable() { + if (resolverInFooter != null) { + resolverInFooter.endTable(); + } else { + // Trailing and rest borders already resolved with integrateTrailingBorders + resolveBordersLastRowInTable(previousRow, false, true, false); + } } } + + CollapsingBorderResolver(Table table) { + this.table = table; + firstInTable = true; + } + + /** {@inheritDoc} */ + public void endRow(List/*<GridUnit>*/ row, TableCellContainer container) { + delegate.endRow(row, container); + } + + /** {@inheritDoc} */ + public void startPart(TableBody part) { + if (part.isTableHeader()) { + delegate = new ResolverInHeader(); + } else { + if (leadingBorders == null) { + // No header, leading borders determined by the table + leadingBorders = new ArrayList(table.getNumberOfColumns()); + for (Iterator colIter = table.getColumns().iterator(); colIter.hasNext();) { + // See endRow method in ResolverInHeader for an explanation of the hack + ConditionalBorder border = ((TableColumn) colIter.next()).borderBefore; + border.leadingTrailing = border.rest; + leadingBorders.add(border); + } + } + if (part.isTableFooter()) { + resolverInFooter = new ResolverInFooter(); + delegate = resolverInFooter; + } else { + if (trailingBorders == null) { + // No footer, trailing borders determined by the table + trailingBorders = new ArrayList(table.getNumberOfColumns()); + for (Iterator colIter = table.getColumns().iterator(); colIter.hasNext();) { + // See endRow method in ResolverInHeader for an explanation of the hack + ConditionalBorder border = ((TableColumn) colIter.next()).borderAfter; + border.leadingTrailing = border.rest; + trailingBorders.add(border); + } + } + delegate = new ResolverInBody(); + } + } + delegate.startPart(part); + } + + /** {@inheritDoc} */ + public void endPart(TableBody part) { + delegate.endPart(); + } + + /** {@inheritDoc} */ + public void endTable() { + delegate.endTable(); + delegate = null; + } } diff --git a/src/java/org/apache/fop/fo/flow/table/ConditionalBorder.java b/src/java/org/apache/fop/fo/flow/table/ConditionalBorder.java new file mode 100644 index 000000000..e03e11a15 --- /dev/null +++ b/src/java/org/apache/fop/fo/flow/table/ConditionalBorder.java @@ -0,0 +1,204 @@ +/* + * 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 org.apache.fop.layoutmgr.table.CollapsingBorderModel; + +/** + * A class that holds the three possible values for a border-before/after on a table-cell, + * in the collapsing model. These three values are (for border-before, similar for + * border-after): + * <ul> + * <li>non-leading: common case, when a cell follows the cell before on a same page;</li> + * <li>leading: when the table is broken and the cell appears at the top of a page, in + * which case its border must be resolved with the header (or the top of the table) + * instead of with the previous cell;</li> + * <li>rest: when a cell is broken over several pages; same as leading but with + * conditionality taken into account.</li> + * </ul> + */ +public class ConditionalBorder { + + /** Special case: the cell is at the top or the bottom of the page. */ + BorderSpecification leadingTrailing; + + /** Normal case, no break. */ + BorderSpecification nonLeadingTrailing; + + /** Special case: break inside the cell. */ + BorderSpecification rest; + + /** The model used to resolve borders. */ + private CollapsingBorderModel collapsingBorderModel; + + private ConditionalBorder(BorderSpecification leadingTrailing, + BorderSpecification nonLeadingTrailing, BorderSpecification rest, + CollapsingBorderModel collapsingBorderModel) { + this.leadingTrailing = leadingTrailing; + this.nonLeadingTrailing = nonLeadingTrailing; + this.rest = rest; + this.collapsingBorderModel = collapsingBorderModel; + } + + /** + * Creates a new conditional border. + * + * @param borderSpecification the border specification to take as a basis + * @param collapsingBorderModel the model that will be used to resolved borders + */ + ConditionalBorder(BorderSpecification borderSpecification, + CollapsingBorderModel collapsingBorderModel) { + leadingTrailing = borderSpecification; + nonLeadingTrailing = leadingTrailing; + if (borderSpecification.getBorderInfo().getWidth().isDiscard()) { + rest = BorderSpecification.getDefaultBorder(); + } else { + rest = leadingTrailing; + } + this.collapsingBorderModel = collapsingBorderModel; + } + + /** + * Resolves and updates the relevant parts of this border as well as the given one. + * + * @param competitor + * @param withLeadingTrailing + * @param withNonLeadingTrailing + * @param withRest + */ + void resolve(ConditionalBorder competitor, boolean withLeadingTrailing, + boolean withNonLeadingTrailing, boolean withRest) { + if (withLeadingTrailing) { + BorderSpecification resolvedBorder = collapsingBorderModel.determineWinner( + leadingTrailing, competitor.leadingTrailing); + if (resolvedBorder != null) { + leadingTrailing = resolvedBorder; + competitor.leadingTrailing = resolvedBorder; + } + } + if (withNonLeadingTrailing) { + BorderSpecification resolvedBorder = collapsingBorderModel.determineWinner( + nonLeadingTrailing, competitor.nonLeadingTrailing); + if (resolvedBorder != null) { + nonLeadingTrailing = resolvedBorder; + competitor.nonLeadingTrailing = resolvedBorder; + } + } + if (withRest) { + BorderSpecification resolvedBorder = collapsingBorderModel.determineWinner(rest, + competitor.rest); + if (resolvedBorder != null) { + rest = resolvedBorder; + competitor.rest = resolvedBorder; + } + } + } + + /** + * Integrates the given segment in this border. Unlike for + * {@link #integrateSegment(ConditionalBorder, boolean, boolean, boolean)}, this + * method nicely handles the case where the CollapsingBorderModel returns null, by + * keeping the components to their old values. + * + * @param competitor + * @param withLeadingTrailing + * @param withNonLeadingTrailing + * @param withRest + */ + void integrateCompetingSegment(ConditionalBorder competitor, boolean withLeadingTrailing, + boolean withNonLeadingTrailing, boolean withRest) { + if (withLeadingTrailing) { + BorderSpecification resolvedBorder = collapsingBorderModel.determineWinner( + leadingTrailing, competitor.leadingTrailing); + if (resolvedBorder != null) { + leadingTrailing = resolvedBorder; + } + } + if (withNonLeadingTrailing) { + BorderSpecification resolvedBorder = collapsingBorderModel.determineWinner( + nonLeadingTrailing, competitor.nonLeadingTrailing); + if (resolvedBorder != null) { + nonLeadingTrailing = resolvedBorder; + } + } + if (withRest) { + BorderSpecification resolvedBorder = collapsingBorderModel.determineWinner(rest, + competitor.rest); + if (resolvedBorder != null) { + rest = resolvedBorder; + } + } + } + + /** + * Updates this border after taking into account the given segment. The + * CollapsingBorderModel is not expected to return null. + * + * @param segment + * @param withLeadingTrailing + * @param withNonLeadingTrailing + * @param withRest + */ + void integrateSegment(ConditionalBorder segment, boolean withLeadingTrailing, + boolean withNonLeadingTrailing, boolean withRest) { + if (withLeadingTrailing) { + leadingTrailing = collapsingBorderModel.determineWinner(leadingTrailing, + segment.leadingTrailing); + assert leadingTrailing != null; + } + if (withNonLeadingTrailing) { + nonLeadingTrailing = collapsingBorderModel.determineWinner(nonLeadingTrailing, + segment.nonLeadingTrailing); + assert nonLeadingTrailing != null; + } + if (withRest) { + rest = collapsingBorderModel.determineWinner(rest, segment.rest); + assert rest != null; + } + } + + /** + * Returns a shallow copy of this border. + * + * @return a copy of this border + */ + ConditionalBorder copy() { + return new ConditionalBorder(leadingTrailing, nonLeadingTrailing, rest, + collapsingBorderModel); + } + + /** {@inheritDoc} */ + public String toString() { + return "{non-leading: " + nonLeadingTrailing + ", leading: " + leadingTrailing + ", rest: " + + rest + "}"; + } + + /** + * Returns a default border specification. + * + * @param collapsingBorderModel the model that will be used to resolve borders + * @return a border with style 'none' for all of the three components + */ + static ConditionalBorder getDefaultBorder(CollapsingBorderModel collapsingBorderModel) { + BorderSpecification defaultBorderSpec = BorderSpecification.getDefaultBorder(); + return new ConditionalBorder(defaultBorderSpec, defaultBorderSpec, defaultBorderSpec, + collapsingBorderModel); + } +} diff --git a/src/java/org/apache/fop/fo/flow/table/EmptyGridUnit.java b/src/java/org/apache/fop/fo/flow/table/EmptyGridUnit.java index be487931b..7a8b0be62 100644 --- a/src/java/org/apache/fop/fo/flow/table/EmptyGridUnit.java +++ b/src/java/org/apache/fop/fo/flow/table/EmptyGridUnit.java @@ -19,8 +19,6 @@ package org.apache.fop.fo.flow.table; -import org.apache.fop.fo.Constants; -import org.apache.fop.fo.properties.CommonBorderPaddingBackground; /** * GridUnit subclass for empty grid units. @@ -40,10 +38,11 @@ public class EmptyGridUnit extends GridUnit { } /** {@inheritDoc} */ - protected void setBorder(int side) { - resolvedBorders[side] = new BorderSpecification( - CommonBorderPaddingBackground.getDefaultBorderInfo(), - Constants.FO_TABLE_CELL); + protected void setBordersFromCell() { + borderBefore = ConditionalBorder.getDefaultBorder(collapsingBorderModel); + borderAfter = ConditionalBorder.getDefaultBorder(collapsingBorderModel); + borderStart = BorderSpecification.getDefaultBorder(); + borderEnd = BorderSpecification.getDefaultBorder(); } /** {@inheritDoc} */ diff --git a/src/java/org/apache/fop/fo/flow/table/GridUnit.java b/src/java/org/apache/fop/fo/flow/table/GridUnit.java index f7c7672e3..d6d622d57 100644 --- a/src/java/org/apache/fop/fo/flow/table/GridUnit.java +++ b/src/java/org/apache/fop/fo/flow/table/GridUnit.java @@ -90,9 +90,12 @@ public class GridUnit { /** flags for the grid unit */ private byte flags = 0; - protected BorderSpecification[] resolvedBorders; + ConditionalBorder borderBefore; + ConditionalBorder borderAfter; + BorderSpecification borderStart; + BorderSpecification borderEnd; - private CollapsingBorderModel collapsingBorderModel; + protected CollapsingBorderModel collapsingBorderModel; /** * Creates a new grid unit. @@ -156,26 +159,27 @@ public class GridUnit { if (table.isSeparateBorderModel()) { assignBorderForSeparateBorderModel(); } else { - resolvedBorders = new BorderSpecification[4]; collapsingBorderModel = CollapsingBorderModel.getBorderModelFor(table .getBorderCollapse()); - if (rowSpanIndex == 0) { - setBorder(CommonBorderPaddingBackground.BEFORE); - } - if (isLastGridUnitRowSpan()) { - setBorder(CommonBorderPaddingBackground.AFTER); - } - if (colSpanIndex == 0) { - setBorder(CommonBorderPaddingBackground.START); - } - if (isLastGridUnitColSpan()) { - setBorder(CommonBorderPaddingBackground.END); - } + setBordersFromCell(); } } - protected void setBorder(int side) { - resolvedBorders[side] = cell.resolvedBorders[side]; + protected void setBordersFromCell() { + borderBefore = cell.borderBefore.copy(); + if (rowSpanIndex > 0) { + borderBefore.nonLeadingTrailing = null; + } + borderAfter = cell.borderAfter.copy(); + if (!isLastGridUnitRowSpan()) { + borderAfter.nonLeadingTrailing = null; + } + if (colSpanIndex == 0) { + borderStart = cell.borderStart; + } + if (isLastGridUnitColSpan()) { + borderEnd = cell.borderEnd; + } } public TableCell getCell() { @@ -301,8 +305,30 @@ public class GridUnit { } private void setBorderInfo(int side) { - if (resolvedBorders[side] != null) { - effectiveBorders.setBorderInfo(resolvedBorders[side].getBorderInfo(), side); + switch (side) { + case CommonBorderPaddingBackground.BEFORE: + if (borderBefore.nonLeadingTrailing/*TODO*/ != null) { + effectiveBorders.setBorderInfo(borderBefore.nonLeadingTrailing.getBorderInfo(), + side); + } + break; + case CommonBorderPaddingBackground.AFTER: + if (borderAfter.nonLeadingTrailing/*TODO*/ != null) { + effectiveBorders.setBorderInfo(borderAfter.nonLeadingTrailing.getBorderInfo(), + side); + } + break; + case CommonBorderPaddingBackground.START: + if (borderStart != null) { + effectiveBorders.setBorderInfo(borderStart.getBorderInfo(), side); + } + break; + case CommonBorderPaddingBackground.END: + if (borderEnd != null) { + effectiveBorders.setBorderInfo(borderEnd.getBorderInfo(), side); + } + break; + default: assert false; } } @@ -332,26 +358,96 @@ public class GridUnit { * CommonBorderPaddingBackground.BEFORE|AFTER|START|END) */ void resolveBorder(GridUnit other, int side) { - BorderSpecification resolvedBorder = collapsingBorderModel.determineWinner( - resolvedBorders[side], other.resolvedBorders[CollapsingBorderModel - .getOtherSide(side)]); - if (resolvedBorder != null) { - this.resolvedBorders[side] = resolvedBorder; - other.resolvedBorders[CollapsingBorderModel.getOtherSide(side)] = resolvedBorder; + switch (side) { + case CommonBorderPaddingBackground.BEFORE: + borderBefore.resolve(other.borderAfter, false, true, false); + break; + case CommonBorderPaddingBackground.AFTER: + borderAfter.resolve(other.borderBefore, false, true, false); + break; + case CommonBorderPaddingBackground.START: + BorderSpecification resolvedBorder = collapsingBorderModel.determineWinner( + borderStart, other.borderEnd); + if (resolvedBorder != null) { + this.borderStart = resolvedBorder; + other.borderEnd = resolvedBorder; + } + break; + case CommonBorderPaddingBackground.END: + resolvedBorder = collapsingBorderModel.determineWinner( + borderEnd, other.borderStart); + if (resolvedBorder != null) { + this.borderEnd = resolvedBorder; + other.borderStart = resolvedBorder; + } + break; + default: assert false; } } /** - * Resolves the border on the given side of this grid unit, comparing it against the - * same border of the given parent element. + * For the given side, integrates in the conflict resolution the border segment of the + * given parent element. * - * @param side the side to resolve (one of + * @param side the side to consider (either CommonBorderPaddingBackground.BEFORE or + * AFTER) + * @param parent a table element whose corresponding border coincides on the given + * side + */ + void integrateBorderSegment(int side, TableFObj parent, boolean withLeadingTrailing, + boolean withNonLeadingTrailing, boolean withRest) { + switch (side) { + case CommonBorderPaddingBackground.BEFORE: + borderBefore.integrateSegment(parent.borderBefore, withLeadingTrailing, + withNonLeadingTrailing, withRest); + break; + case CommonBorderPaddingBackground.AFTER: + borderAfter.integrateSegment(parent.borderAfter, withLeadingTrailing, + withNonLeadingTrailing, withRest); + break; + default: assert false; + } + } + + /** + * For the given side, integrates in the conflict resolution the border segment of the + * given parent element. + * + * @param side the side to consider (one of * CommonBorderPaddingBackground.BEFORE|AFTER|START|END) - * @param parent the parent element holding a competing border + * @param parent a table element whose corresponding border coincides on the given side */ - void resolveBorder(int side, TableFObj parent) { - resolvedBorders[side] = collapsingBorderModel.determineWinner(resolvedBorders[side], - parent.resolvedBorders[side]); + void integrateBorderSegment(int side, TableFObj parent) { + switch (side) { + case CommonBorderPaddingBackground.BEFORE: + case CommonBorderPaddingBackground.AFTER: + integrateBorderSegment(side, parent, true, true, true); + break; + case CommonBorderPaddingBackground.START: + borderStart = collapsingBorderModel.determineWinner(borderStart, + parent.borderStart); + break; + case CommonBorderPaddingBackground.END: + borderEnd = collapsingBorderModel.determineWinner(borderEnd, + parent.borderEnd); + break; + default: assert false; + } + } + + void integrateCompetingBorder(int side, ConditionalBorder competitor, + boolean withLeadingTrailing, boolean withNonLeadingTrailing, boolean withRest) { + switch (side) { + case CommonBorderPaddingBackground.BEFORE: + borderBefore.integrateCompetingSegment(competitor, withLeadingTrailing, + withNonLeadingTrailing, withRest); + break; + case CommonBorderPaddingBackground.AFTER: + borderAfter.integrateCompetingSegment(competitor, withLeadingTrailing, + withNonLeadingTrailing, withRest); + break; + default: assert false; + } } /** diff --git a/src/java/org/apache/fop/fo/flow/table/TableBody.java b/src/java/org/apache/fop/fo/flow/table/TableBody.java index 6b12271fc..c1453bdfa 100644 --- a/src/java/org/apache/fop/fo/flow/table/TableBody.java +++ b/src/java/org/apache/fop/fo/flow/table/TableBody.java @@ -261,6 +261,10 @@ public class TableBody extends TableCellContainer { return FO_TABLE_BODY; } + protected boolean isTableHeader() { + return false; + } + protected boolean isTableFooter() { return false; } diff --git a/src/java/org/apache/fop/fo/flow/table/TableFObj.java b/src/java/org/apache/fop/fo/flow/table/TableFObj.java index 236556a05..4b90ccac3 100644 --- a/src/java/org/apache/fop/fo/flow/table/TableFObj.java +++ b/src/java/org/apache/fop/fo/flow/table/TableFObj.java @@ -42,7 +42,10 @@ public abstract class TableFObj extends FObj { private Numeric borderEndPrecedence; private Numeric borderStartPrecedence; - BorderSpecification[] resolvedBorders = new BorderSpecification[4]; // TODO + ConditionalBorder borderBefore; + ConditionalBorder borderAfter; + BorderSpecification borderStart; + BorderSpecification borderEnd; CollapsingBorderModel collapsingBorderModel; @@ -200,7 +203,6 @@ public abstract class TableFObj extends FObj { if (!inMarker() && !table.isSeparateBorderModel()) { collapsingBorderModel = CollapsingBorderModel.getBorderModelFor(table .getBorderCollapse()); - resolvedBorders = new BorderSpecification[4]; setCollapsedBorders(); } } @@ -226,8 +228,23 @@ public abstract class TableFObj extends FObj { * @param side one of CommonBorderPaddingBackground.BEFORE|AFTER|START|END */ protected void createBorder(int side) { - resolvedBorders[side] = new BorderSpecification(getCommonBorderPaddingBackground() - .getBorderInfo(side), getNameId()); + BorderSpecification borderSpec = new BorderSpecification( + getCommonBorderPaddingBackground().getBorderInfo(side), getNameId()); + switch (side) { + case CommonBorderPaddingBackground.BEFORE: + borderBefore = new ConditionalBorder(borderSpec, collapsingBorderModel); + break; + case CommonBorderPaddingBackground.AFTER: + borderAfter = new ConditionalBorder(borderSpec, collapsingBorderModel); + break; + case CommonBorderPaddingBackground.START: + borderStart = borderSpec; + break; + case CommonBorderPaddingBackground.END: + borderEnd = borderSpec; + break; + default: assert false; + } } /** @@ -240,7 +257,22 @@ public abstract class TableFObj extends FObj { */ protected void createBorder(int side, TableFObj competitor) { createBorder(side); - resolvedBorders[side] = collapsingBorderModel.determineWinner(resolvedBorders[side], - competitor.resolvedBorders[side]); + switch (side) { + case CommonBorderPaddingBackground.BEFORE: + borderBefore.integrateSegment(competitor.borderBefore, true, true, true); + break; + case CommonBorderPaddingBackground.AFTER: + borderAfter.integrateSegment(competitor.borderAfter, true, true, true); + break; + case CommonBorderPaddingBackground.START: + borderStart = collapsingBorderModel.determineWinner(borderStart, + competitor.borderStart); + break; + case CommonBorderPaddingBackground.END: + borderEnd = collapsingBorderModel.determineWinner(borderEnd, + competitor.borderEnd); + break; + default: assert false; + } } } diff --git a/src/java/org/apache/fop/fo/flow/table/TableHeader.java b/src/java/org/apache/fop/fo/flow/table/TableHeader.java index 01ada2aa7..e248a0f7e 100644 --- a/src/java/org/apache/fop/fo/flow/table/TableHeader.java +++ b/src/java/org/apache/fop/fo/flow/table/TableHeader.java @@ -66,4 +66,9 @@ public class TableHeader extends TableBody { public int getNameId() { return FO_TABLE_HEADER; } + + /** {@inheritDoc} */ + protected boolean isTableHeader() { + return true; + } } diff --git a/src/java/org/apache/fop/fo/properties/CommonBorderPaddingBackground.java b/src/java/org/apache/fop/fo/properties/CommonBorderPaddingBackground.java index 6ea08f19c..c5a4950f4 100755 --- a/src/java/org/apache/fop/fo/properties/CommonBorderPaddingBackground.java +++ b/src/java/org/apache/fop/fo/properties/CommonBorderPaddingBackground.java @@ -131,14 +131,68 @@ public class CommonBorderPaddingBackground { private static BorderInfo defaultBorderInfo; /** + * A conditional length of value 0. Returned by the + * {@link CommonBorderPaddingBackground#getBorderInfo(int)} method when the + * corresponding border isn't specified, to avoid to callers painful checks for null. + */ + private static class ConditionalNullLength extends CondLengthProperty { + + /** {@inheritDoc} */ + public Property getComponent(int cmpId) { + throw new UnsupportedOperationException(); + } + + /** {@inheritDoc} */ + public Property getConditionality() { + throw new UnsupportedOperationException(); + } + + /** {@inheritDoc} */ + public Length getLength() { + throw new UnsupportedOperationException(); + } + + /** {@inheritDoc} */ + public Property getLengthComponent() { + throw new UnsupportedOperationException(); + } + + /** {@inheritDoc} */ + public int getLengthValue() { + return 0; + } + + /** {@inheritDoc} */ + public int getLengthValue(PercentBaseContext context) { + return 0; + } + + /** {@inheritDoc} */ + public boolean isDiscard() { + return true; + } + + /** {@inheritDoc} */ + public void setComponent(int cmpId, Property cmpnValue, boolean isDefault) { + throw new UnsupportedOperationException(); + } + + /** {@inheritDoc} */ + public String toString() { + return "CondLength[0mpt, discard]"; + } + } + + /** * Returns a default BorderInfo of style none. * * @return a BorderInfo instance with style set to {@link Constants#EN_NONE} */ public static synchronized BorderInfo getDefaultBorderInfo() { if (defaultBorderInfo == null) { - /* It is enough to set color and width to null, as they should never be consulted */ - defaultBorderInfo = new BorderInfo(Constants.EN_NONE, null, null); + /* It is enough to set color to null, as it should never be consulted */ + defaultBorderInfo = new BorderInfo(Constants.EN_NONE, + new ConditionalNullLength(), null); } return defaultBorderInfo; } diff --git a/src/java/org/apache/fop/layoutmgr/table/CollapsingBorderModel.java b/src/java/org/apache/fop/layoutmgr/table/CollapsingBorderModel.java index a0bff3b5e..c3df74800 100644 --- a/src/java/org/apache/fop/layoutmgr/table/CollapsingBorderModel.java +++ b/src/java/org/apache/fop/layoutmgr/table/CollapsingBorderModel.java @@ -181,9 +181,24 @@ public abstract class CollapsingBorderModel { * * @param border1 a border specification * @param border2 another border specification + * @param discard true if the .conditionality component of the border width must be + * taken into account * @return the winning border, null if the two borders are equivalent */ public abstract BorderSpecification determineWinner(BorderSpecification border1, + BorderSpecification border2, boolean discard); + + /** + * Returns the border which wins the border conflict resolution. Same as + * {@link #determineWinner(BorderSpecification, BorderSpecification, boolean) + * determineWinner(border1, border2, false)}. + * + * @param border1 a border specification + * @param border2 another border specification + * @return the winning border, null if the two borders are equivalent + * @see determineWinner + */ + public abstract BorderSpecification determineWinner(BorderSpecification border1, BorderSpecification border2); } diff --git a/src/java/org/apache/fop/layoutmgr/table/CollapsingBorderModelEyeCatching.java b/src/java/org/apache/fop/layoutmgr/table/CollapsingBorderModelEyeCatching.java index 5f979c986..c1a9380a3 100644 --- a/src/java/org/apache/fop/layoutmgr/table/CollapsingBorderModelEyeCatching.java +++ b/src/java/org/apache/fop/layoutmgr/table/CollapsingBorderModelEyeCatching.java @@ -21,6 +21,7 @@ package org.apache.fop.layoutmgr.table; import org.apache.fop.fo.Constants; import org.apache.fop.fo.flow.table.BorderSpecification; +import org.apache.fop.fo.properties.CommonBorderPaddingBackground; import org.apache.fop.fo.properties.CommonBorderPaddingBackground.BorderInfo; /** @@ -32,6 +33,27 @@ public class CollapsingBorderModelEyeCatching extends CollapsingBorderModel { /** {@inheritDoc} */ public BorderSpecification determineWinner(BorderSpecification border1, + BorderSpecification border2, boolean discard) { + BorderInfo bi1 = border1.getBorderInfo(); + BorderInfo bi2 = border2.getBorderInfo(); + if (discard) { + if (bi1.getWidth().isDiscard()) { + if (bi2.getWidth().isDiscard()) { + return new BorderSpecification( + CommonBorderPaddingBackground.getDefaultBorderInfo(), 0/*TODO*/); + } else { + return border2; + } + } else if (bi2.getWidth().isDiscard()) { + return border1; + } + } + // Otherwise, fall back to the default resolution algorithm + return determineWinner(border1, border2); + } + + /** {@inheritDoc} */ + public BorderSpecification determineWinner(BorderSpecification border1, BorderSpecification border2) { BorderInfo bi1 = border1.getBorderInfo(); BorderInfo bi2 = border2.getBorderInfo(); |