123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472 |
- /*
- * 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 */
- }
- }
|