/* * 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; import java.util.BitSet; import java.util.Iterator; import java.util.List; import org.apache.fop.apps.FOPException; import org.apache.fop.datatypes.Numeric; import org.apache.fop.datatypes.ValidationPercentBaseContext; import org.apache.fop.fo.Constants; import org.apache.fop.fo.FONode; import org.apache.fop.fo.FObj; import org.apache.fop.fo.PropertyList; import org.apache.fop.fo.ValidationException; import org.apache.fop.fo.expr.PropertyException; import org.apache.fop.fo.properties.CommonBorderPaddingBackground; import org.apache.fop.fo.properties.NumberProperty; import org.apache.fop.fo.properties.Property; import org.apache.fop.fo.properties.PropertyMaker; /** * Superclass for table-related FOs */ public abstract class TableFObj extends FObj { private Numeric borderAfterPrecedence; private Numeric borderBeforePrecedence; private Numeric borderEndPrecedence; private Numeric borderStartPrecedence; /** * Used for determining initial values for column-numbers * in case of row-spanning cells * (for clarity) * */ protected static class PendingSpan { /** * member variable holding the number of rows left */ protected int rowsLeft; /** * Constructor * * @param rows number of rows spanned */ public PendingSpan(int rows) { rowsLeft = rows; } } /** * Main constructor * * @param parent the parent node */ public TableFObj(FONode parent) { super(parent); } /** * @see FObj#bind(PropertyList) */ public void bind(PropertyList pList) throws FOPException { borderAfterPrecedence = pList.get(PR_BORDER_AFTER_PRECEDENCE).getNumeric(); borderBeforePrecedence = pList.get(PR_BORDER_BEFORE_PRECEDENCE).getNumeric(); borderEndPrecedence = pList.get(PR_BORDER_END_PRECEDENCE).getNumeric(); borderStartPrecedence = pList.get(PR_BORDER_START_PRECEDENCE).getNumeric(); //Complain if table has separate border-model and fo is not a table or cell //see: Rec 6.7.4, 6.7.6 - 6.7.9 if (getNameId() != FO_TABLE && getNameId() != FO_TABLE_CELL && getTable().isSeparateBorderModel() && getCommonBorderPaddingBackground().hasBorderInfo()) { attributeWarning("In the separate border model (border-collapse=\"separate\")" + ", borders are not applicable to " + getName() + ", but a non-zero value for border was found."); } if (getNameId() != FO_TABLE //Separate check for fo:table in Table.java && getNameId() != FO_TABLE_CELL && getCommonBorderPaddingBackground().hasPadding( ValidationPercentBaseContext .getPseudoContextForValidationPurposes())) { attributeWarning( "padding-* properties are not applicable to " + getName() + ", but a non-zero value for padding was found."); } } /** * @see org.apache.fop.fo.FONode#addChildNode(FONode) */ protected void addChildNode(FONode child) throws FOPException { if (!inMarker() && child.getNameId() == FO_TABLE_CELL) { /* update current column index for the table-body/table-row */ updateColumnIndex((TableCell) child); } super.addChildNode(child); } private void updateColumnIndex(TableCell cell) throws ValidationException { int rowSpan = cell.getNumberRowsSpanned(); int colSpan = cell.getNumberColumnsSpanned(); int columnIndex = getCurrentColumnIndex(); int i = -1; while (++i < colSpan) { if (isColumnNumberUsed(columnIndex + i)) { /* if column-number is already in use by another cell * in the current row => error! */ StringBuffer errorMessage = new StringBuffer(); errorMessage.append("fo:table-cell overlaps in column ") .append(columnIndex + i); if (locator.getLineNumber() != -1) { errorMessage.append(" (line #") .append(locator.getLineNumber()).append(", column #") .append(locator.getColumnNumber()).append(")"); } throw new ValidationException(errorMessage.toString()); } } if (getNameId() == FO_TABLE_ROW) { TableRow row = (TableRow) this; for (i = colSpan; --i >= 0;) { row.pendingSpans.add(null); } /* if the current cell spans more than one row, * update pending span list for the next row */ if (rowSpan > 1) { for (i = colSpan; --i >= 0;) { row.pendingSpans.set(columnIndex - 1 + i, new PendingSpan(rowSpan)); } } } else { TableBody body = (TableBody) this; /* if body.firstRow is still true, and : * a) the cell starts a row, * b) there was a previous cell * c) that previous cell didn't explicitly end the previous row * => set firstRow flag to false */ if (body.firstRow && cell.startsRow()) { if (!body.previousCellEndedRow()) { body.firstRow = false; } } /* pendingSpans not initialized for the first row... */ if (body.firstRow) { for (i = colSpan; --i >= 0;) { body.pendingSpans.add(null); } } /* if the current cell spans more than one row, * update pending span list for the next row */ if (rowSpan > 1) { for (i = colSpan; --i >= 0;) { body.pendingSpans.set(columnIndex - 1 + i, new PendingSpan(rowSpan)); } } } /* flag column indices used by this cell, * take into account that possibly not all column-numbers * are used by columns in the parent table (if any), * so a cell spanning three columns, might actually * take up more than three columnIndices... */ int startIndex = columnIndex - 1; int endIndex = startIndex + colSpan; if (getTable().columns != null) { List cols = getTable().columns; int tmpIndex = endIndex; for (i = startIndex; i <= tmpIndex; ++i) { if (i < cols.size() && cols.get(i) == null) { endIndex++; } } } flagColumnIndices(startIndex, endIndex); if (getNameId() != FO_TABLE_ROW && cell.endsRow()) { ((TableBody) this).firstRow = false; ((TableBody) this).resetColumnIndex(); } } /** * * @param side the side for which to return the border precedence * @return the "border-precedence" value for the given side */ public Numeric getBorderPrecedence(int side) { switch (side) { case CommonBorderPaddingBackground.BEFORE: return borderBeforePrecedence; case CommonBorderPaddingBackground.AFTER: return borderAfterPrecedence; case CommonBorderPaddingBackground.START: return borderStartPrecedence; case CommonBorderPaddingBackground.END: return borderEndPrecedence; default: return null; } } /** * Returns the current column index of the given TableFObj * (overridden for Table, TableBody, TableRow) * * @return the next column number to use */ protected int getCurrentColumnIndex() { return 0; } /** * Sets the current column index of the given TableFObj * used when a value for column-number is explicitly * specified on the child FO (TableCell or TableColumn) * (overridden for Table, TableBody, TableRow) * * @param newIndex new value for column index */ protected void setCurrentColumnIndex(int newIndex) { //do nothing by default } /** * Checks if a certain column-number is already occupied * (overridden for Table, TableBody, TableRow) * * @param colNr the column-number to check * @return true if column-number is already in use */ public boolean isColumnNumberUsed(int colNr) { return false; } /** * Convenience method to returns a reference * to the base Table instance * * @return the base table instance * */ public Table getTable() { if (this.getNameId() == FO_TABLE) { //node is a Table //=> return itself return (Table) this; } else { //any other Table-node //=> recursive call to parent.getTable() return ((TableFObj) parent).getTable(); } } /** * @return the Common Border, Padding, and Background Properties. */ public abstract CommonBorderPaddingBackground getCommonBorderPaddingBackground(); /** * Flags column indices from start to end, * and updates the current column index. * Overridden for Table, TableBody, TableRow * @param start start index * @param end end index */ protected void flagColumnIndices(int start, int end) { //nop } /** * PropertyMaker subclass for the column-number property * */ public static class ColumnNumberPropertyMaker extends NumberProperty.Maker { /** * Constructor * @param propId the id of the property for which the maker should * be created */ public ColumnNumberPropertyMaker(int propId) { super(propId); } /** * @see PropertyMaker#make(PropertyList) */ public Property make(PropertyList propertyList) throws PropertyException { FObj fo = propertyList.getFObj(); if (fo.getNameId() == Constants.FO_TABLE_CELL || fo.getNameId() == Constants.FO_TABLE_COLUMN) { if (fo.getNameId() == Constants.FO_TABLE_CELL && fo.getParent().getNameId() != Constants.FO_TABLE_ROW && (propertyList.get(Constants.PR_STARTS_ROW).getEnum() == Constants.EN_TRUE)) { TableBody parent = (TableBody) fo.getParent(); if (!parent.previousCellEndedRow()) { parent.resetColumnIndex(); } } return new NumberProperty(((TableFObj) fo.getParent()) .getCurrentColumnIndex()); } else { throw new PropertyException( "column-number property is only allowed" + " on fo:table-cell or fo:table-column, not on " + fo.getName()); } } /** * Check the value of the column-number property. * Return the parent's column index (initial value) in case * of a negative or zero value * * @see org.apache.fop.fo.properties.PropertyMaker#get( * int, PropertyList, boolean, boolean) */ public Property get(int subpropId, PropertyList propertyList, boolean tryInherit, boolean tryDefault) throws PropertyException { Property p = super.get(0, propertyList, tryInherit, tryDefault); TableFObj fo = (TableFObj) propertyList.getFObj(); TableFObj parent = (TableFObj) propertyList.getParentFObj(); if (p != null) { int columnIndex = p.getNumeric().getValue(); if (columnIndex <= 0) { fo.getLogger().warn("Specified negative or zero value for " + "column-number on " + fo.getName() + ": " + columnIndex + " forced to " + parent.getCurrentColumnIndex()); return new NumberProperty(parent.getCurrentColumnIndex()); } double tmpIndex = p.getNumeric().getNumericValue(); if (tmpIndex - columnIndex > 0.0) { columnIndex = (int) Math.round(tmpIndex); fo.getLogger().warn("Rounding specified column-number of " + tmpIndex + " to " + columnIndex); return new NumberProperty(columnIndex); } /* if column-number was explicitly specified, force the * parent's current column index to the specified value, * so that the updated index will be the correct initial * value for the next cell/column (see Rec 7.26.8) */ if (propertyList.getExplicit(Constants.PR_COLUMN_NUMBER) != null) { parent.setCurrentColumnIndex(p.getNumeric().getValue()); } } return p; } } }