All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.fop.fo.flow.table.CollapsingBorderResolver Maven / Gradle / Ivy

Go to download

Apache FOP (Formatting Objects Processor) is the world's first print formatter driven by XSL formatting objects (XSL-FO) and the world's first output independent formatter. It is a Java application that reads a formatting object (FO) tree and renders the resulting pages to a specified output. Output formats currently supported include PDF, PCL, PS, AFP, TIFF, PNG, SVG, XML (area tree representation), Print, AWT and TXT. The primary output target is PDF.

There is a newer version: 2.9
Show newest version
/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/* $Id: CollapsingBorderResolver.java 1762060 2016-09-23 12:57:46Z ssteiner $ */

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 previousRow;

    private boolean firstInTable;

    private List footerFirstRow;

    /** The last currently registered footer row. */
    private List 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 leadingBorders;

    private List trailingBorders;

    /* TODO Temporary hack for resolved borders in header */
    /* Currently the normal border is always used. */
    private List headerLastRow;
    /* 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 row, boolean withNormal,
                boolean withLeadingTrailing, boolean withRest) {
            assert firstInTable;
            for (int i = 0; i < row.size(); i++) {
                TableColumn column = table.getColumn(i);
                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 rowBefore, List rowAfter) {
            assert rowBefore != null && rowAfter != null;
            for (int i = 0; i < rowAfter.size(); i++) {
                GridUnit gu = rowAfter.get(i);
                if (gu.getRowSpanIndex() == 0) {
                    GridUnit beforeGU = rowBefore.get(i);
                    gu.resolveBorder(beforeGU, CommonBorderPaddingBackground.BEFORE);
                }
            }
        }

        /** Integrates the border-after of the part. */
        void resolveBordersLastRowInPart(List row, boolean withNormal,
                boolean withLeadingTrailing, boolean withRest) {
            for (Object aRow : row) {
                ((GridUnit) aRow).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 row, boolean withNormal,
                boolean withLeadingTrailing, boolean withRest) {
            for (int i = 0; i < row.size(); i++) {
                TableColumn column = table.getColumn(i);
                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 row) {
            for (int i = 0; i < table.getNumberOfColumns(); i++) {
                GridUnit gu = row.get(i);
                ConditionalBorder border = 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 row) {
            for (int i = 0; i < table.getNumberOfColumns(); i++) {
                GridUnit gu = row.get(i);
                ConditionalBorder border = 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.
         * 
    *
  • Integrates the border-before/after of the containing table-row if any;
  • *
  • Integrates the border-before of the containing part, if first row;
  • *
  • Resolves border-start/end between grid units.
  • *
* * @param row the row being finished * @param container the containing element */ void endRow(List 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 (Object aRow : row) { GridUnit gu = (GridUnit) aRow; 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 (Object aRow : row) { ((GridUnit) aRow).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 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 (Object aRow : row) { ConditionalBorder borderBefore = ((GridUnit) aRow).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 (Object aPreviousRow : previousRow) { ConditionalBorder borderAfter = ((GridUnit) aPreviousRow).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 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 (Object aFooterFirstRow : footerFirstRow) { ConditionalBorder borderBefore = ((GridUnit) aFooterFirstRow).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 (Object aFooterLastRow : footerLastRow) { ConditionalBorder borderAfter = ((GridUnit) aFooterLastRow).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 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 (Object aRow : row) { GridUnit gu = (GridUnit) aRow; 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 (Object aPreviousRow : previousRow) { GridUnit gu = (GridUnit) aPreviousRow; 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 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 (Object o : table.getColumns()) { ConditionalBorder border = ((TableColumn) o).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 (Object o : table.getColumns()) { ConditionalBorder border = ((TableColumn) o).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 (Object aHeaderLastRow : headerLastRow) { GridUnit gu = (GridUnit) aHeaderLastRow; gu.borderAfter.leadingTrailing = gu.borderAfter.normal; } } if (footerLastRow != null) { for (Object aFooterLastRow : footerLastRow) { GridUnit gu = (GridUnit) aFooterLastRow; gu.borderAfter.leadingTrailing = gu.borderAfter.normal; } } /* End of temporary hack */ } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy