git-svn-id: https://svn.apache.org/repos/asf/xmlgraphics/fop/trunk@603945 13f79535-47bb-0310-9956-ffa450edef68tags/fop-0_95beta
@@ -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); | |||
@@ -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 + "}"; | |||
} | |||
} |
@@ -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; | |||
} | |||
} |
@@ -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); | |||
} | |||
} |
@@ -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} */ |
@@ -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; | |||
} | |||
} | |||
/** |
@@ -261,6 +261,10 @@ public class TableBody extends TableCellContainer { | |||
return FO_TABLE_BODY; | |||
} | |||
protected boolean isTableHeader() { | |||
return false; | |||
} | |||
protected boolean isTableFooter() { | |||
return false; | |||
} |
@@ -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; | |||
} | |||
} | |||
} |
@@ -66,4 +66,9 @@ public class TableHeader extends TableBody { | |||
public int getNameId() { | |||
return FO_TABLE_HEADER; | |||
} | |||
/** {@inheritDoc} */ | |||
protected boolean isTableHeader() { | |||
return true; | |||
} | |||
} |
@@ -130,6 +130,59 @@ 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. | |||
* | |||
@@ -137,8 +190,9 @@ public class CommonBorderPaddingBackground { | |||
*/ | |||
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; | |||
} |
@@ -181,8 +181,23 @@ 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); | |||
@@ -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; | |||
/** | |||
@@ -30,6 +31,27 @@ import org.apache.fop.fo.properties.CommonBorderPaddingBackground.BorderInfo; | |||
*/ | |||
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) { |