]> source.dussan.org Git - xmlgraphics-fop.git/commitdiff
Next step: Basic border resolution and proper painting (even though it's following...
authorJeremias Maerki <jeremias@apache.org>
Wed, 4 May 2005 07:18:13 +0000 (07:18 +0000)
committerJeremias Maerki <jeremias@apache.org>
Wed, 4 May 2005 07:18:13 +0000 (07:18 +0000)
Bugfixes in the collapsing border model resolution.
Note: The Knuth elements do not take borders into account, yet, so page breaking is off-target.

git-svn-id: https://svn.apache.org/repos/asf/xmlgraphics/fop/branches/Temp_KnuthStylePageBreaking@198602 13f79535-47bb-0310-9956-ffa450edef68

src/java/org/apache/fop/layoutmgr/MinOptMaxUtil.java
src/java/org/apache/fop/layoutmgr/table/Cell.java
src/java/org/apache/fop/layoutmgr/table/CollapsingBorderModel.java
src/java/org/apache/fop/layoutmgr/table/CollapsingBorderModelEyeCatching.java
src/java/org/apache/fop/layoutmgr/table/EmptyGridUnit.java
src/java/org/apache/fop/layoutmgr/table/GridUnit.java
src/java/org/apache/fop/layoutmgr/table/PrimaryGridUnit.java
src/java/org/apache/fop/layoutmgr/table/TableContentLayoutManager.java
src/java/org/apache/fop/layoutmgr/table/TableRowIterator.java

index 63cec1a7e561bb950f2e38d210d4f6188216f476..319a48b88f2f0913bbc925177cb10bd4e18c3366 100644 (file)
@@ -63,6 +63,19 @@ public class MinOptMaxUtil {
         }
     }
 
+    public static void extendMinimum(MinOptMax mom, int len, boolean optToLen) {
+        System.out.print("before: " + mom);
+        if (mom.min < len) {
+            mom.min = len;
+            mom.opt = Math.max(mom.min, mom.opt);
+            if (optToLen) {
+                mom.opt = Math.min(mom.min, len);
+            }
+            mom.max = Math.max(mom.opt, mom.max);
+        }
+        System.out.println(" - after: " + mom);
+    }
+    
     /**
      * After a calculation on a MinOptMax, this can be called to set opt to
      * a new effective value.
@@ -77,4 +90,21 @@ public class MinOptMaxUtil {
         }
     }
     
+    /**
+     * Converts a LengthRangeProperty to a MinOptMax.
+     * @param prop LengthRangeProperty
+     * @return the requested MinOptMax instance
+     */
+    public static MinOptMax toMinOptMax(LengthRangeProperty prop) {
+        MinOptMax mom = new MinOptMax(
+                (prop.getMinimum().isAuto() 
+                        ? 0 : prop.getMinimum().getLength().getValue()),
+                (prop.getOptimum().isAuto() 
+                        ? 0 : prop.getOptimum().getLength().getValue()),
+                (prop.getMinimum().isAuto() 
+                        ? Integer.MAX_VALUE 
+                        : prop.getMaximum().getLength().getValue()));
+        return mom;
+    }
+    
 }
index c875e350595ef307d3efe179818c383539844553..ac4f44ff3d438e07890c0d82315ddbd92a0ce4f5 100644 (file)
@@ -395,6 +395,16 @@ public class Cell extends BlockStackingLayoutManager implements BlockLevelLayout
         rowHeight = h;
     }
 
+    private int getContentHeight(int rowHeight, GridUnit gu) {
+        int bpd = rowHeight;
+        bpd -= gu.getPrimary().getHalfMaxBorderWidth();
+        CommonBorderPaddingBackground cbpb 
+            = gu.getCell().getCommonBorderPaddingBackground(); 
+        bpd -= cbpb.getPaddingBefore(false);
+        bpd -= cbpb.getPaddingAfter(false);
+        return bpd;
+    }
+    
     /**
      * Add the areas for the break points.
      * The cell contains block stacking layout managers
@@ -421,7 +431,11 @@ public class Cell extends BlockStackingLayoutManager implements BlockLevelLayout
         } else {
             TraitSetter.addBackground(curBlockArea, fobj.getCommonBorderPaddingBackground());
             //TODO Set these booleans right
-            boolean[] outer = new boolean[] {false, false, false, false};
+            boolean[] outer = new boolean[] {
+                    gridUnit.getFlag(GridUnit.FIRST_IN_TABLE), 
+                    gridUnit.getFlag(GridUnit.LAST_IN_TABLE),
+                    gridUnit.getFlag(GridUnit.IN_FIRST_COLUMN),
+                    gridUnit.getFlag(GridUnit.IN_LAST_COLUMN)};
             if (!gridUnit.hasSpanning()) {
                 //Can set the borders directly if there's no span
                 TraitSetter.addCollapsingBorders(curBlockArea, 
@@ -442,9 +456,12 @@ public class Cell extends BlockStackingLayoutManager implements BlockLevelLayout
                         Block block = new Block();
                         block.addTrait(Trait.IS_REFERENCE_AREA, Boolean.TRUE);
                         block.setPositioning(Block.ABSOLUTE);
-                        //block.setBPD(gu.row.getRowHeight());
-                        block.setBPD(rowHeight); //TODO This needs to be fixed for row spanning
-                        //lastRowHeight = gu.row.getRowHeight();
+
+                        int bpd = getContentHeight(rowHeight, gu);
+                        bpd += gridUnit.getHalfMaxBeforeBorderWidth() 
+                                - (gu.getBorders().getBorderBeforeWidth(false) / 2);
+                        block.setBPD(bpd);
+                        //TODO This needs to be fixed for row spanning
                         lastRowHeight = rowHeight;
                         int ipd = gu.getColumn().getColumnWidth().getValue();
                         int borderStartWidth = gu.getBorders().getBorderStartWidth(false) / 2; 
@@ -452,7 +469,12 @@ public class Cell extends BlockStackingLayoutManager implements BlockLevelLayout
                         ipd -= gu.getBorders().getBorderEndWidth(false) / 2;
                         block.setIPD(ipd);
                         block.setXOffset(dx + borderStartWidth);
-                        block.setYOffset(dy);
+                        int halfCollapsingBorderHeight = 0;
+                        if (!fobj.isSeparateBorderModel()) {
+                            halfCollapsingBorderHeight += 
+                                gu.getBorders().getBorderBeforeWidth(false) / 2;
+                        }
+                        block.setYOffset(dy - halfCollapsingBorderHeight);
                         TraitSetter.addCollapsingBorders(block, gu.getBorders(), outer);
                         parentLM.addChildArea(block);
                         dx += gu.getColumn().getColumnWidth().getValue();
@@ -477,35 +499,8 @@ public class Cell extends BlockStackingLayoutManager implements BlockLevelLayout
         }
 
         AreaAdditionUtil.addAreas(parentIter, layoutContext);
-        /*
-        LayoutManager childLM;
-        int iStartPos = 0;
-        LayoutContext lc = new LayoutContext(0);
-        PositionIterator childPosIter;
-        childPosIter = new StackingIter(positionList.listIterator());
-        while ((childLM = childPosIter.getNextChildLM()) != null) {
-            // set last area flag
-            lc.setFlags(LayoutContext.LAST_AREA,
-                    (layoutContext.isLastArea() && childLM == lastLM));
-            lc.setStackLimit(layoutContext.getStackLimit());
-            // Add the line areas to Area
-            childLM.addAreas(childPosIter, lc);
-        }
-        while (parentIter.hasNext()) {
-            LeafPosition lfp = (LeafPosition) parentIter.next();
-            // Add the block areas to Area
-            PositionIterator breakPosIter =
-              new BreakPossPosIter(childBreaks, iStartPos,
-                                   lfp.getLeafPos() + 1);
-            iStartPos = lfp.getLeafPos() + 1;
-            while ((childLM = breakPosIter.getNextChildLM()) != null) {
-                childLM.addAreas(breakPosIter, lc);
-            }
-        }*/
-
         
-        int contentBPD = rowHeight;
-        contentBPD -= borderAndPaddingBPD;
+        int contentBPD = getContentHeight(rowHeight, gridUnit);
         curBlockArea.setBPD(contentBPD);
 
         flush();
@@ -546,8 +541,11 @@ public class Cell extends BlockStackingLayoutManager implements BlockLevelLayout
             }
             int halfCollapsingBorderHeight = 0;
             if (!fobj.isSeparateBorderModel()) {
-                halfCollapsingBorderHeight += 
-                    gridUnit.getBorders().getBorderBeforeWidth(false) / 2;
+                if (gridUnit.hasSpanning()) {
+                    halfCollapsingBorderHeight -= gridUnit.getHalfMaxBeforeBorderWidth();
+                } else {
+                    halfCollapsingBorderHeight += gridUnit.getHalfMaxBeforeBorderWidth();
+                }
             }
             curBlockArea.setXOffset(xoffset + inRowIPDOffset + halfBorderSep + indent);
             curBlockArea.setYOffset(yoffset - halfCollapsingBorderHeight);
@@ -585,7 +583,7 @@ public class Cell extends BlockStackingLayoutManager implements BlockLevelLayout
         }
     }
 
-    /* (non-Javadoc)
+    /**
      * @see org.apache.fop.layoutmgr.BlockLevelLayoutManager#negotiateBPDAdjustment(int, org.apache.fop.layoutmgr.KnuthElement)
      */
     public int negotiateBPDAdjustment(int adj, KnuthElement lastElement) {
index 4d923fecd3d65fbe821cb44fdb79bb27ec48ccfa..c64bfc1b4fd9fd47eef0ca671c5c148021708414 100644 (file)
@@ -28,14 +28,26 @@ import org.apache.fop.fo.properties.CommonBorderPaddingBackground.BorderInfo;
  */
 public abstract class CollapsingBorderModel {
 
+    /** before side */
+    protected static final int BEFORE = CommonBorderPaddingBackground.BEFORE;
+    /** after side */
+    protected static final int AFTER = CommonBorderPaddingBackground.AFTER;
+    /** start side */
+    protected static final int START = CommonBorderPaddingBackground.START;
+    /** end side */
+    protected static final int END = CommonBorderPaddingBackground.END;
+    
+    /** Flag: current grid unit is either start or end of the table. */
+    public static final int VERTICAL_START_END_OF_TABLE = 1;
+    
     /** Indicates that the cell is/starts in the first row being painted on a particular page */
-    public static final int FIRST_ROW_IN_TABLE_PART = 1;
+    //public static final int FIRST_ROW_IN_TABLE_PART = 1;
     /** Indicates that the cell is/ends in the last row being painted on a particular page */
-    public static final int LAST_ROW_IN_TABLE_PART  = 2;
+    //public static final int LAST_ROW_IN_TABLE_PART  = 2;
     /** Indicates that the cell is/starts in the first row of a body/table-header/table-footer */
-    public static final int FIRST_ROW_IN_GROUP      = 4;
+    //public static final int FIRST_ROW_IN_GROUP      = 4;
     /** Indicates that the cell is/end in the last row of a body/table-header/table-footer */
-    public static final int LAST_ROW_IN_GROUP       = 8;
+    //public static final int LAST_ROW_IN_GROUP       = 8;
     
     private static CollapsingBorderModel collapse = null;
     private static CollapsingBorderModel collapseWithPrecedence = null;
index 1fccbc4a43c23997f7b5aad8089925a357eedc95..9c330f6abfe2e7a81eaf144ad7cc74d1a06d4ffa 100644 (file)
@@ -24,7 +24,6 @@ import org.apache.fop.fo.flow.TableBody;
 import org.apache.fop.fo.flow.TableCell;
 import org.apache.fop.fo.flow.TableColumn;
 import org.apache.fop.fo.flow.TableRow;
-import org.apache.fop.fo.properties.CommonBorderPaddingBackground;
 import org.apache.fop.fo.properties.CommonBorderPaddingBackground.BorderInfo;
 
 /**
@@ -34,11 +33,6 @@ import org.apache.fop.fo.properties.CommonBorderPaddingBackground.BorderInfo;
  */
 public class CollapsingBorderModelEyeCatching extends CollapsingBorderModel {
 
-    private static final int BEFORE = CommonBorderPaddingBackground.BEFORE;
-    private static final int AFTER = CommonBorderPaddingBackground.AFTER;
-    private static final int START = CommonBorderPaddingBackground.START;
-    private static final int END = CommonBorderPaddingBackground.END;
-    
     public BorderInfo determineWinner(GridUnit currentGridUnit, 
             GridUnit otherGridUnit, int side, int flags) {
         final boolean vertical = isVerticalRelation(side);
@@ -109,8 +103,9 @@ public class CollapsingBorderModelEyeCatching extends CollapsingBorderModel {
             //row group (=body, table-header or table-footer)
             current[2] = currentBody.getCommonBorderPaddingBackground().getBorderInfo(side);
         }
-        if ((otherSide == BEFORE && otherGridUnit.getFlag(GridUnit.FIRST_IN_BODY))
-                || (otherSide == AFTER && otherGridUnit.getFlag(GridUnit.LAST_IN_BODY))) {
+        if (otherGridUnit != null
+                && ((otherSide == BEFORE && otherGridUnit.getFlag(GridUnit.FIRST_IN_BODY))
+                    || (otherSide == AFTER && otherGridUnit.getFlag(GridUnit.LAST_IN_BODY)))) {
             //row group (=body, table-header or table-footer)
             other[2] = otherBody.getCommonBorderPaddingBackground().getBorderInfo(otherSide);
         }
@@ -126,7 +121,11 @@ public class CollapsingBorderModelEyeCatching extends CollapsingBorderModel {
             other[3] = otherColumn.getCommonBorderPaddingBackground().getBorderInfo(otherSide);
         }
         //TODO current[4] and other[4] for column groups
-        if (otherGridUnit == null) {
+        if (otherGridUnit == null
+            && ((side == BEFORE && (flags & VERTICAL_START_END_OF_TABLE) > 0)
+                    || (side == AFTER && (flags & VERTICAL_START_END_OF_TABLE) > 0)
+                    || (side == START)
+                    || (side == END))) {
             //table
             current[5] = table.getCommonBorderPaddingBackground().getBorderInfo(side);
         }
@@ -142,7 +141,6 @@ public class CollapsingBorderModelEyeCatching extends CollapsingBorderModel {
         
         // *** Rule 2 ***
         if (!doRule2(current, other)) {
-            return null; //paint no border
         }
         
         // *** Rule 3 ***
@@ -167,7 +165,7 @@ public class CollapsingBorderModelEyeCatching extends CollapsingBorderModel {
     }
 
     private BorderInfo doRule1(BorderInfo[] current, BorderInfo[] other) {
-        for (int i = 0; i < current.length - 1; i++) {
+        for (int i = 0; i < current.length; i++) {
             if ((current[i] != null) && (current[i].getStyle() == Constants.EN_HIDDEN)) {
                 return current[i];
             }
@@ -180,7 +178,7 @@ public class CollapsingBorderModelEyeCatching extends CollapsingBorderModel {
     
     private boolean doRule2(BorderInfo[] current, BorderInfo[] other) {
         boolean found = false;
-        for (int i = 0; i < current.length - 1; i++) {
+        for (int i = 0; i < current.length; i++) {
             if ((current[i] != null) && (current[i].getStyle() != Constants.EN_NONE)) {
                 found = true;
                 break;
@@ -196,7 +194,7 @@ public class CollapsingBorderModelEyeCatching extends CollapsingBorderModel {
     private BorderInfo doRule3(BorderInfo[] current, BorderInfo[] other) {
         int width = 0;
         //Find max border width
-        for (int i = 0; i < current.length - 1; i++) {
+        for (int i = 0; i < current.length; i++) {
             if ((current[i] != null) && (current[i].getRetainedWidth() > width)) {
                 width = current[i].getRetainedWidth();
             }
@@ -207,13 +205,12 @@ public class CollapsingBorderModelEyeCatching extends CollapsingBorderModel {
         BorderInfo widest = null;
         int count = 0;
         //See if there's only one with the widest border
-        for (int i = 0; i < current.length - 1; i++) {
+        for (int i = 0; i < current.length; i++) {
             if ((current[i] != null) && (current[i].getRetainedWidth() == width)) {
                 count++;
                 if (widest == null) {
                     widest = current[i];
                 }
-                break;
             } else {
                 current[i] = null; //Discard the narrower ones
             }
@@ -222,7 +219,6 @@ public class CollapsingBorderModelEyeCatching extends CollapsingBorderModel {
                 if (widest == null) {
                     widest = other[i];
                 }
-                break;
             } else {
                 other[i] = null; //Discard the narrower ones
             }
@@ -237,26 +233,24 @@ public class CollapsingBorderModelEyeCatching extends CollapsingBorderModel {
     private BorderInfo doRule4(BorderInfo[] current, BorderInfo[] other) {
         int pref = getPreferenceValue(Constants.EN_INSET); //Lowest preference
         //Find highest preference value
-        for (int i = 0; i < current.length - 1; i++) {
+        for (int i = 0; i < current.length; i++) {
             if (current[i] != null) {
                 int currPref = getPreferenceValue(current[i].getStyle());
                 if (currPref > pref) {
                     pref = currPref;
-                    break;
                 }
             }
             if (other[i] != null) {
                 int currPref = getPreferenceValue(other[i].getStyle());
                 if (currPref > pref) {
                     pref = currPref;
-                    break;
                 }
             }
         }
         BorderInfo preferred = null;
         int count = 0;
         //See if there's only one with the preferred border style
-        for (int i = 0; i < current.length - 1; i++) {
+        for (int i = 0; i < current.length; i++) {
             if (current[i] != null) {
                 int currPref = getPreferenceValue(current[i].getStyle());
                 if (currPref == pref) {
@@ -290,7 +284,7 @@ public class CollapsingBorderModelEyeCatching extends CollapsingBorderModel {
     }
 
     private BorderInfo doRule5(BorderInfo[] current, BorderInfo[] other) {
-        for (int i = 0; i < current.length - 1; i++) {
+        for (int i = 0; i < current.length; i++) {
             if (current[i] != null) {
                 return current[i];
             }
index 8197dd576662e6eda3f1012e23ac546a28c99eb8..8d17420d8e92c03d290bd4426b1f4afb529ef499 100644 (file)
@@ -19,7 +19,6 @@
 package org.apache.fop.layoutmgr.table;
 
 import org.apache.fop.fo.flow.TableBody;
-import org.apache.fop.fo.flow.TableCell;
 import org.apache.fop.fo.flow.TableColumn;
 import org.apache.fop.fo.flow.TableRow;
 
@@ -35,12 +34,11 @@ public class EmptyGridUnit extends GridUnit {
      * @param row Optional table-row instance
      * @param column table-column instance
      * @param body table-body the grid unit belongs to
-     * @param startCol 
-     * @param colSpanIndex
+     * @param startCol column index 
      */
     public EmptyGridUnit(TableRow row, TableColumn column, TableBody body, 
             int startCol) {
-        super(null, column, startCol, 0);
+        super(null, null, column, startCol, 0);
         this.row = row;
         this.body = body;
     }
index cc9ed9b36a18f7aeea46adffed74e9e2eb5d7a00..abec8c8c6078dc8827caafbb7e231f480c441639 100644 (file)
@@ -45,6 +45,8 @@ public class GridUnit {
     /** Indicates that the grid unit is in the last row (context: table). */
     public static final int LAST_IN_TABLE = 5;
     
+    /** Primary grid unit */
+    private PrimaryGridUnit primary;
     /** Table cell which occupies this grid unit */
     private TableCell cell;
     /** Table column that this grid unit belongs to */
@@ -63,6 +65,15 @@ public class GridUnit {
     
     
     public GridUnit(TableCell cell, TableColumn column, int startCol, int colSpanIndex) {
+        this(null, cell, column, startCol, colSpanIndex);
+    }
+    
+    public GridUnit(PrimaryGridUnit primary, TableColumn column, int startCol, int colSpanIndex) {
+        this(primary, primary.getCell(), column, startCol, colSpanIndex);
+    }
+    
+    protected GridUnit(PrimaryGridUnit primary, TableCell cell, TableColumn column, int startCol, int colSpanIndex) {
+        this.primary = primary;
         this.cell = cell;
         this.column = column;
         this.startCol = startCol;
@@ -101,6 +112,13 @@ public class GridUnit {
         return (Table)node;
     }
     
+    /**
+     * @return the primary grid unit if this is a spanned grid unit
+     */
+    public PrimaryGridUnit getPrimary() {
+        return (isPrimary() ? (PrimaryGridUnit)this : this.primary);
+    }
+
     public boolean isPrimary() {
         return false;
     }
@@ -131,6 +149,13 @@ public class GridUnit {
         }
     }
     
+    /**
+     * @return the index of the grid unit inside a cell in row direction
+     */
+    public int getRowSpanIndex() {
+        return this.rowSpanIndex;
+    }
+    
     /**
      * Returns a BorderInfo instance for a side of the currently applicable cell before border
      * resolution (i.e. the value from the FO). A return value of null indicates an empty cell.
@@ -176,6 +201,16 @@ public class GridUnit {
      * @param side the side to resolve (one of CommonBorderPaddingBackground.BEFORE|AFTER|START|END)
      */
     public void resolveBorder(GridUnit other, int side) {
+        resolveBorder(other, side, 0);
+    }
+    
+    /**
+     * Resolve collapsing borders for the given cell. Used in case of the collapsing border model.
+     * @param other neighbouring grid unit if any
+     * @param side the side to resolve (one of CommonBorderPaddingBackground.BEFORE|AFTER|START|END)
+     * @param resFlags flags for the border resolution
+     */
+    public void resolveBorder(GridUnit other, int side, int resFlags) {
         CollapsingBorderModel borderModel = CollapsingBorderModel.getBorderModelFor(
                 getTable().getBorderCollapse());
         if (this.effBorders == null) {
@@ -183,7 +218,7 @@ public class GridUnit {
         }
         this.effBorders.setBorderInfo(
                 borderModel.determineWinner(this, other, 
-                        side, 0), side);
+                        side, resFlags), side);
     }
     
     public boolean getFlag(int which) {
@@ -206,7 +241,7 @@ public class GridUnit {
             return null;
         } else {
             //cloning the current GridUnit with adjustments
-            GridUnit gu = new GridUnit(getCell(), getColumn(), startCol, colSpanIndex);
+            GridUnit gu = new GridUnit(getPrimary(), getColumn(), startCol, colSpanIndex);
             gu.rowSpanIndex = rowSpanIndex + 1;
             return gu;
         }
index c9c481289e539798ac64970222a0677b4d5c2311..aff3be337f45469e381e937a48dd3fe5aa8a366d 100644 (file)
@@ -23,6 +23,7 @@ import java.util.List;
 
 import org.apache.fop.fo.flow.TableCell;
 import org.apache.fop.fo.flow.TableColumn;
+import org.apache.fop.fo.properties.LengthRangeProperty;
 
 /**
  * This class represents a primary grid unit of a spanned cell.
@@ -37,6 +38,8 @@ public class PrimaryGridUnit extends GridUnit {
     private int startRow;
     /** Links to the spanned grid units. (List of GridUnit arrays, one array represents a row) */ 
     private List rows;
+    /** The calculated size of the cell's content. (cached value) */
+    private int contentLength = -1;
     
     public PrimaryGridUnit(TableCell cell, TableColumn column, int startCol, int startRow) {
         super(cell, column, startCol, 0);
@@ -62,6 +65,94 @@ public class PrimaryGridUnit extends GridUnit {
         return this.elements;
     }
     
+    /** 
+     * @return Returns the half the maximum before border width of this cell.
+     */
+    public int getHalfMaxBeforeBorderWidth() {
+        int value = 0;
+        if (getRows() != null) {
+            int before = 0;
+            //first row for before borders
+            GridUnit[] row = (GridUnit[])getRows().get(0);
+            for (int i = 0; i < row.length; i++) {
+                if (row[i].hasBorders()) {
+                    before = Math.max(before, 
+                            row[i].getBorders().getBorderBeforeWidth(false));
+                }
+            }
+            value += before / 2;
+        } else {
+            if (hasBorders()) {
+                value += getBorders().getBorderBeforeWidth(false) / 2;
+            }
+        }
+        return value;
+    }
+    
+    /** 
+     * @return Returns the sum of half the maximum before and after border 
+     * widths of this cell.
+     */
+    public int getHalfMaxBorderWidth() {
+        int value = getHalfMaxBeforeBorderWidth();
+        if (getRows() != null) {
+            //Last row for after borders
+            int after = 0;
+            GridUnit[] row = (GridUnit[])getRows().get(getRows().size() - 1);
+            for (int i = 0; i < row.length; i++) {
+                if (row[i].hasBorders()) {
+                    after = Math.max(after, row[i].getBorders().getBorderAfterWidth(false));
+                }
+            }
+            value += after / 2;
+        } else {
+            if (hasBorders()) {
+                value += getBorders().getBorderAfterWidth(false) / 2;
+            }
+        }
+        return value;
+    }
+    
+    /** @param value The length of the cell content to remember. */
+    public void setContentLength(int value) {
+        this.contentLength = value;
+    }
+    
+    /** @return Returns the length of the cell content. */
+    public int getContentLength() {
+        return contentLength;
+    }
+
+    /** 
+     * @return Returns the length of the cell content after the bpd/height attributes on cell
+     * and row have been taken into account.
+     */
+    public int getEffectiveContentLength() {
+        int value = getContentLength();
+        if (!getCell().getBlockProgressionDimension().getMinimum().isAuto()) {
+            value = Math.max(value, 
+                    getCell().getBlockProgressionDimension().getMinimum().getLength().getValue());
+        }
+        if (getRow() != null 
+                && !getRow().getBlockProgressionDimension().getMinimum().isAuto()) {
+            value = Math.max(value, 
+                    getRow().getBlockProgressionDimension().getMinimum().getLength().getValue());
+        }
+        return value;
+    }
+    
+    /** @return true if cell/row has an explicit BPD/height */
+    public boolean hasBPD() {
+        if (!getCell().getBlockProgressionDimension().getOptimum().isAuto()) {
+            return true;
+        }
+        if (getRow() != null 
+                && !getRow().getBlockProgressionDimension().getOptimum().isAuto()) {
+            return true;
+        }
+        return false;
+    }
+
     public List getRows() {
         return this.rows;
     }
index b5df25ab42e7e078d7f65a8c26cc506e011cdee1..843aaa9ef1646d749dc420777153147b4a2bb4e6 100644 (file)
@@ -30,6 +30,7 @@ import org.apache.fop.area.Block;
 import org.apache.fop.area.Trait;
 import org.apache.fop.fo.flow.Table;
 import org.apache.fop.fo.flow.TableRow;
+import org.apache.fop.fo.properties.CommonBorderPaddingBackground;
 import org.apache.fop.fo.properties.LengthRangeProperty;
 import org.apache.fop.layoutmgr.KnuthBox;
 import org.apache.fop.layoutmgr.KnuthElement;
@@ -37,6 +38,7 @@ import org.apache.fop.layoutmgr.KnuthPenalty;
 import org.apache.fop.layoutmgr.KnuthPossPosIter;
 import org.apache.fop.layoutmgr.LayoutContext;
 import org.apache.fop.layoutmgr.LayoutManager;
+import org.apache.fop.layoutmgr.MinOptMaxUtil;
 import org.apache.fop.layoutmgr.Position;
 import org.apache.fop.layoutmgr.PositionIterator;
 import org.apache.fop.layoutmgr.TraitSetter;
@@ -62,6 +64,10 @@ public class TableContentLayoutManager {
     private int startXOffset;
     private int usedBPD;
     
+    /**
+     * Main constructor
+     * @param parent Parent layout manager
+     */
     public TableContentLayoutManager(TableLayoutManager parent) {
         this.tableLM = parent;
         Table table = getTableLM().getTable();
@@ -76,10 +82,16 @@ public class TableContentLayoutManager {
         }
     }
     
+    /**
+     * @return the table layout manager
+     */
     public TableLayoutManager getTableLM() {
         return this.tableLM;
     }
     
+    /**
+     * @return the column setup of this table
+     */
     public ColumnSetup getColumns() {
         return getTableLM().getColumns();
     }
@@ -169,101 +181,292 @@ public class TableContentLayoutManager {
      * @param iter TableRowIterator instance to fetch rows from
      * @param context Active LayoutContext
      * @param alignment alignment indicator
+     * @param isHeaderFooter true if currently handling headers/footers
      * @return An element list
      */
     private LinkedList getKnuthElementsForRowIterator(TableRowIterator iter, 
-            LayoutContext context, int alignment, boolean disableHeaderFooter) {
+            LayoutContext context, int alignment, boolean isHeaderFooter) {
         LinkedList returnList = new LinkedList();
         TableRowIterator.EffRow[] rowGroup = null;
-        TableRowIterator.EffRow row = null;
         while ((rowGroup = iter.getNextRowGroup()) != null) {
-            for (int rgi = 0; rgi < rowGroup.length; rgi++) {
-                row = rowGroup[rgi];
-                List pgus = new java.util.ArrayList();
-                TableRow tableRow = null;
-                int maxCellHeight = 0;
-                for (int j = 0; j < row.getGridUnits().size(); j++) {
-                    GridUnit gu = (GridUnit)row.getGridUnits().get(j);
-                    if (gu.isPrimary() && !gu.isEmpty()) {
-                        PrimaryGridUnit primary = (PrimaryGridUnit)gu;
-                        primary.getCellLM().setParent(tableLM);
+            resolveNormalBeforeAfterBordersForRowGroup(rowGroup, iter);
+            createElementsForRowGroup(context, alignment, isHeaderFooter, 
+                        returnList, rowGroup);
+        }
+        
+        //Remove last penalty
+        KnuthElement last = (KnuthElement)returnList.getLast();
+        if (last.isPenalty() && last.getW() == 0 && last.getP() == 0) {
+            returnList.removeLast();
+        }
+        return returnList;
+    }
 
-                        //Calculate width of cell
-                        int spanWidth = 0;
-                        for (int i = primary.getStartCol(); 
-                                i < primary.getStartCol() + primary.getCell().getNumberColumnsSpanned();
-                                i++) {
-                            spanWidth += getTableLM().getColumns().getColumn(i + 1)
-                                .getColumnWidth().getValue();
-                        }
-                        log.info("spanWidth=" + spanWidth);
-                        LayoutContext childLC = new LayoutContext(0);
-                        childLC.setStackLimit(context.getStackLimit()); //necessary?
-                        childLC.setRefIPD(spanWidth);
+    /**
+     * Resolves normal borders for a row group.
+     * @param iter Table row iterator to operate on
+     */
+    private void resolveNormalBeforeAfterBordersForRowGroup(TableRowIterator.EffRow[] rowGroup, 
+            TableRowIterator iter) {
+        for (int rgi = 0; rgi < rowGroup.length; rgi++) {
+            TableRowIterator.EffRow row = rowGroup[rgi];
+            TableRowIterator.EffRow prev = iter.getCachedRow(row.getIndex() - 1);
+            TableRowIterator.EffRow next = iter.getCachedRow(row.getIndex() + 1);
+            if (next == null) {
+                //It wasn't read, yet, or we are at the last row
+                next = iter.getNextRow();
+                iter.backToPreviewRow();
+            }
+            if ((prev == null) && (iter == this.trIter) && (this.headerIter != null)) {
+                prev = this.headerIter.getLastRow();
+            }
+            if ((prev == null) && (iter == this.headerIter)) {
+                prev = this.trIter.getFirstRow();
+            }
+            if ((next == null) && (iter == this.trIter) && (this.footerIter != null)) {
+                next = this.footerIter.getFirstRow();
+            }
+            if ((next == null) && (iter == this.footerIter)) {
+                //TODO This could be bad for memory consumption because it already causes the
+                //whole body iterator to be prefetched!
+                prev = this.trIter.getLastRow();
+            }
+            log.debug(prev + " - " + row + " - " + next);
+            
+            //Determine the grid units necessary for getting all the borders right
+            int guCount = row.getGridUnits().size();
+            if (prev != null) {
+                guCount = Math.max(guCount, prev.getGridUnits().size());
+            }
+            if (next != null) {
+                guCount = Math.max(guCount, next.getGridUnits().size());
+            }
+            GridUnit gu = (GridUnit)row.getGridUnits().get(0);
+            //Create empty grid units to hold resolved borders of neighbouring cells
+            //TODO maybe this needs to be done differently (and sooner)
+            for (int i = 0; i < guCount - row.getGridUnits().size(); i++) {
+                //TODO This block in untested!
+                int pos = row.getGridUnits().size() + i;
+                row.getGridUnits().add(new EmptyGridUnit(gu.getRow(), 
+                        this.tableLM.getColumns().getColumn(pos + 1), gu.getBody(), 
+                        pos));
+            }
+            
+            //Now resolve normal borders
+            if (getTableLM().getTable().isSeparateBorderModel()) {
+                //nop, borders are already assigned at this point
+            } else {
+                for (int i = 0; i < row.getGridUnits().size(); i++) {
+                    gu = (GridUnit)row.getGridUnits().get(i);
+                    GridUnit other;
+                    int flags = 0;
+                    if (prev != null && i < prev.getGridUnits().size()) {
+                        other = (GridUnit)prev.getGridUnits().get(i);
+                    } else {
+                        other = null;
+                    }
+                    if ((iter == this.trIter)
+                            && gu.getFlag(GridUnit.FIRST_IN_TABLE)
+                            && (this.headerIter == null)) {
+                        flags |= CollapsingBorderModel.VERTICAL_START_END_OF_TABLE;
+                    }
+                    if ((iter == this.headerIter)
+                            && gu.getFlag(GridUnit.FIRST_IN_TABLE)) {
+                        flags |= CollapsingBorderModel.VERTICAL_START_END_OF_TABLE;
+                    }
+                    gu.resolveBorder(other, 
+                            CommonBorderPaddingBackground.BEFORE, flags);
+                    
+                    flags = 0;
+                    if (next != null && i < next.getGridUnits().size()) {
+                        other = (GridUnit)next.getGridUnits().get(i);
+                    } else {
+                        other = null;
+                    }
+                    if ((iter == this.trIter)
+                            && gu.getFlag(GridUnit.LAST_IN_TABLE)
+                            && (this.footerIter == null)) {
+                        flags |= CollapsingBorderModel.VERTICAL_START_END_OF_TABLE;
+                    }
+                    if ((iter == this.footerIter)
+                            && gu.getFlag(GridUnit.LAST_IN_TABLE)) {
+                        flags |= CollapsingBorderModel.VERTICAL_START_END_OF_TABLE;
+                    }
+                    gu.resolveBorder(other, 
+                            CommonBorderPaddingBackground.AFTER, flags);
+                }
+
+            }
+        }
+    }
+
+    /**
+     * Creates Knuth elements for a row group (see TableRowIterator.getNextRowGroup()).
+     * @param context Active LayoutContext
+     * @param alignment alignment indicator
+     * @param isHeaderFooter true if currently processing headers/footers
+     * @param returnList List to received the generated elements
+     * @param rowGroup row group to process
+     */
+    private void createElementsForRowGroup(LayoutContext context, int alignment, 
+            boolean isHeaderFooter, LinkedList returnList, 
+            TableRowIterator.EffRow[] rowGroup) {
+        MinOptMax[] rowHeights = new MinOptMax[rowGroup.length];
+        TableRowIterator.EffRow row;
+        List pgus = new java.util.ArrayList(); //holds a list of a row's primary grid units
+        for (int rgi = 0; rgi < rowGroup.length; rgi++) {
+            row = rowGroup[rgi];
+            rowHeights[rgi] = new MinOptMax(0, 0, Integer.MAX_VALUE);
+            
+            pgus.clear();
+            TableRow tableRow = null;
+            int minContentHeight = 0;
+            int maxCellHeight = 0;
+            for (int j = 0; j < row.getGridUnits().size(); j++) {
+                GridUnit gu = (GridUnit)row.getGridUnits().get(j);
+                if (gu.isPrimary() && !gu.isEmpty()) {
+                    PrimaryGridUnit primary = (PrimaryGridUnit)gu;
+                    primary.getCellLM().setParent(tableLM);
+
+                    //Determine the table-row if any
+                    if (tableRow == null) {
+                        tableRow = primary.getRow();
                         
-                        LinkedList elems = primary.getCellLM().getNextKnuthElements(childLC, alignment);
-                        primary.setElements(elems);
-                        log.debug("Elements: " + elems);
-                        int len = calcCellHeightFromContents(elems);
-                        pgus.add(primary);
-                        maxCellHeight = Math.max(maxCellHeight, len);
-                        if (len > row.getHeight().opt) {
-                            row.setHeight(new MinOptMax(len));
+                        //Check for bpd on row, see CSS21, 17.5.3 Table height algorithms
+                        LengthRangeProperty bpd = tableRow.getBlockProgressionDimension();
+                        if (!bpd.getMinimum().isAuto()) {
+                            minContentHeight = Math.max(minContentHeight, 
+                                    bpd.getMinimum().getLength().getValue());
                         }
+                    }
+
+                    //Calculate width of cell
+                    int spanWidth = 0;
+                    for (int i = primary.getStartCol(); 
+                            i < primary.getStartCol() + primary.getCell().getNumberColumnsSpanned();
+                            i++) {
+                        spanWidth += getTableLM().getColumns().getColumn(i + 1)
+                            .getColumnWidth().getValue();
+                    }
+                    log.info("spanWidth=" + spanWidth);
+                    LayoutContext childLC = new LayoutContext(0);
+                    childLC.setStackLimit(context.getStackLimit()); //necessary?
+                    childLC.setRefIPD(spanWidth);
+                    
+                    //Get the element list for the cell contents
+                    LinkedList elems = primary.getCellLM().getNextKnuthElements(childLC, alignment);
+                    primary.setElements(elems);
+                    log.debug("Elements: " + elems);
+                    
+                    //Calculate height of cell contents
+                    primary.setContentLength(calcCellHeightFromContents(elems));
+                    maxCellHeight = Math.max(maxCellHeight, primary.getContentLength());
+
+                    //Calculate height of row, see CSS21, 17.5.3 Table height algorithms
+                    if (gu.isLastGridUnitRowSpan()) {
+                        int effCellContentHeight = minContentHeight;
                         LengthRangeProperty bpd = primary.getCell().getBlockProgressionDimension();
-                        if (!bpd.getOptimum().isAuto()) {
-                            if (bpd.getOptimum().getLength().getValue() > row.getHeight().opt) {
-                                row.setHeight(new MinOptMax(bpd.getOptimum().getLength().getValue()));
-                            }
+                        if (!bpd.getMinimum().isAuto()) {
+                            effCellContentHeight = Math.max(effCellContentHeight,
+                                    bpd.getMinimum().getLength().getValue());
                         }
-                        if (tableRow == null) {
-                            tableRow = primary.getRow();
+                        effCellContentHeight = Math.max(effCellContentHeight, 
+                                primary.getContentLength());
+                        int halfMaxBorderWidths = primary.getHalfMaxBorderWidth();
+                        int padding = 0;
+                        CommonBorderPaddingBackground cbpb 
+                            = primary.getCell().getCommonBorderPaddingBackground(); 
+                        padding += cbpb.getPaddingBefore(false);
+                        padding += cbpb.getPaddingAfter(false);
+                        int effRowHeight = effCellContentHeight + padding + halfMaxBorderWidths;
+                        if (effRowHeight > rowHeights[rgi].min) {
+                            //This is the new height of the (grid) row
+                            MinOptMaxUtil.extendMinimum(rowHeights[rgi], effRowHeight, false);
+                            row.setHeight(rowHeights[rgi]);
                         }
                     }
+                    
+                    pgus.add(primary);
                 }
-                
-                if (tableRow != null) {
-                    LengthRangeProperty bpd = tableRow.getBlockProgressionDimension();
-                    if (!bpd.getOptimum().isAuto()) {
-                        if (bpd.getOptimum().getLength().getValue() > row.getHeight().opt) {
-                            row.setHeight(new MinOptMax(bpd.getOptimum().getLength().getValue()));
-                        }
-                    }
-                }
-                log.debug(row);
-                
-                PrimaryGridUnit[] pguArray = new PrimaryGridUnit[pgus.size()];
-                pguArray = (PrimaryGridUnit[])pgus.toArray(pguArray);
-                LinkedList returnedList = getCombinedKnuthElementsForRow(pguArray, row, 
-                        disableHeaderFooter);
-                if (returnedList != null) {
-                    returnList.addAll(returnedList);
-                }
+            }
+            
+            log.debug(row);
+            
+            PrimaryGridUnit[] pguArray = new PrimaryGridUnit[pgus.size()];
+            pguArray = (PrimaryGridUnit[])pgus.toArray(pguArray);
+            LinkedList returnedList = getCombinedKnuthElementsForRow(pguArray, row, 
+                    isHeaderFooter);
+            if (returnedList != null) {
+                returnList.addAll(returnedList);
+            }
 
-                if (row.getHeight().opt > maxCellHeight) {
-                    int space = row.getHeight().opt - maxCellHeight;
-                    KnuthPenalty penalty = (KnuthPenalty)returnList.removeLast();
-                    //Insert dummy box before penalty
-                    returnList.add(new KnuthBox(space, new Position(getTableLM()), false));
-                    returnList.add(penalty);
+            /* not necessary anymore
+            if (row.getHeight().opt > maxCellHeight) {
+                int space = row.getHeight().opt - maxCellHeight;
+                KnuthPenalty penalty = (KnuthPenalty)returnList.removeLast();
+                //Insert dummy box before penalty
+                returnList.add(new KnuthBox(space, new Position(getTableLM()), false));
+                returnList.add(penalty);
+            }*/
+            
+            //Calculate row height in row groups with spans
+            /*
+            if (tableRow != null) {
+                LengthRangeProperty bpd = tableRow.getBlockProgressionDimension();
+                if (bpd.getOptimum().isAuto()) {
+                    rowHeights[rgi] = new MinOptMax(0, 0, Integer.MAX_VALUE);
+                } else {
+                    rowHeights[rgi] = MinOptMaxUtil.toMinOptMax(bpd);
                 }
-            }
-        }
-        
-        //Remove last penalty
-        KnuthElement last = (KnuthElement)returnList.getLast();
-        if (last.isPenalty() && last.getW() == 0 && last.getP() == 0) {
-            returnList.removeLast();
+            } else {
+                rowHeights[rgi] = new MinOptMax(0, 0, Integer.MAX_VALUE);
+            }*/
+            /*
+            for (int j = 0; j < row.getGridUnits().size(); j++) {
+                GridUnit gu = (GridUnit)row.getGridUnits().get(j);
+                if (gu.isLastGridUnitRowSpan() && !gu.isEmpty()) {
+                    log.debug(rgi + " - " + gu);
+                    MinOptMax effCellHeight; 
+                    LengthRangeProperty bpd = gu.getCell().getBlockProgressionDimension();
+                    if (bpd.getOptimum().isAuto()) {
+                        effCellHeight = new MinOptMax(0, 0, Integer.MAX_VALUE);
+                    } else {
+                        effCellHeight = MinOptMaxUtil.toMinOptMax(bpd);
+                    }
+                    int contentLen = gu.getPrimary().getContentLength();
+                    if (getTableLM().getTable().isSeparateBorderModel()) {
+                        //contentLen += before and after borders of that cell plus half the BPD border-separation 
+                    } else {
+                        //contentLen += half of before and after borders for that cell
+                    }
+                    for (int previous = 0; previous < gu.getRowSpanIndex(); previous++) {
+                        contentLen -= rowHeights[rgi - previous - 1].opt;
+                    }
+                    log.debug("->" + contentLen);
+                    if (contentLen > effCellHeight.min) {
+                        MinOptMaxUtil.extendMinimum(effCellHeight, contentLen, true);
+                    }
+                    if (effCellHeight.min > rowHeights[rgi].min) {
+                        MinOptMaxUtil.extendMinimum(rowHeights[rgi], effCellHeight.min, false);
+                    }
+                }
+            }*/
         }
-        return returnList;
     }
 
     private LinkedList getCombinedKnuthElementsForRow(PrimaryGridUnit[] pguArray, 
-            TableRowIterator.EffRow row, boolean disableHeaderFooter) {
+            TableRowIterator.EffRow row, boolean isHeaderFooter) {
         List[] elementLists = new List[pguArray.length];
         for (int i = 0; i < pguArray.length; i++) {
-            //Copy elements to array lists to improve element access performance
-            elementLists[i] = new java.util.ArrayList(pguArray[i].getElements());
+            if (pguArray[i].hasBPD()) {
+                List list = new java.util.ArrayList(1);
+                list.add(new KnuthBoxCellWithBPD(
+                        pguArray[i].getEffectiveContentLength(), pguArray[i]));
+                elementLists[i] = list;
+            } else {
+                //Copy elements (LinkedList) to array lists to improve element access performance
+                elementLists[i] = new java.util.ArrayList(pguArray[i].getElements());
+            }
         }
         int[] index = new int[pguArray.length];
         int[] start = new int[pguArray.length];
@@ -274,7 +477,7 @@ public class TableContentLayoutManager {
         
         int totalHeight = 0;
         for (int i = 0; i < pguArray.length; i++) {
-            fullWidths[i] = calcCellHeightFromContents(pguArray[i].getElements());
+            fullWidths[i] = pguArray[i].getContentLength();
             totalHeight = Math.max(totalHeight, fullWidths[i]);
         }
         int laststep = 0;
@@ -294,7 +497,14 @@ public class TableContentLayoutManager {
             List gridUnitParts = new java.util.ArrayList(pguArray.length);
             for (int i = 0; i < pguArray.length; i++) {
                 if (end[i] >= start[i]) {
-                    gridUnitParts.add(new GridUnitPart(pguArray[i], start[i], end[i]));
+                    if (start[i] == 0 && end[i] == 0 
+                            && elementLists[i].size() == 1
+                            && elementLists[i].get(0) instanceof KnuthBoxCellWithBPD) {
+                        gridUnitParts.add(new GridUnitPart(pguArray[i], 
+                                0, pguArray[i].getElements().size() - 1));
+                    } else {
+                        gridUnitParts.add(new GridUnitPart(pguArray[i], start[i], end[i]));
+                    }
                 }
             }
             
@@ -303,7 +513,7 @@ public class TableContentLayoutManager {
                     gridUnitParts, row);
             returnList.add(new KnuthBox(boxLen, tcpos, false));
             TableHFPenaltyPosition penaltyPos = new TableHFPenaltyPosition(getTableLM());
-            if (!disableHeaderFooter) {
+            if (!isHeaderFooter) {
                 if (!getTableLM().getTable().omitHeaderAtBreak()) {
                     penaltyLen += this.headerNetHeight;
                     penaltyPos.headerElements = this.headerList;
@@ -547,9 +757,8 @@ public class TableContentLayoutManager {
         private void handleTableContentPosition(TableContentPosition tcpos) {
             rowFO = null;
             if (lastRow != tcpos.row && lastRow != null) {
-                //yoffset += lastRow.getHeight().opt;
-                yoffset += lastRowHeight;
-                this.accumulatedBPD += lastRowHeight;
+                yoffset += lastRow.getHeight().opt;
+                this.accumulatedBPD += lastRow.getHeight().opt;
             }
             lastRow = tcpos.row;
             Iterator partIter = tcpos.gridUnitParts.iterator();
@@ -573,7 +782,7 @@ public class TableContentLayoutManager {
             
             //Calculate the height of the row
             int maxLen = addAreasAndFlushRow(false);
-            lastRowHeight = maxLen;
+            lastRowHeight = tcpos.row.getHeight().opt;
         }
         
         private int addAreasAndFlushRow(boolean finalFlush) {
@@ -588,7 +797,7 @@ public class TableContentLayoutManager {
                     partLength[i] = len;
                     log.debug("len of part: " + len);
                     maxLen = Math.max(maxLen, len);
-                    maxLen = Math.max(maxLen, getExplicitCellHeight(gridUnits[i]));
+                    //maxLen = Math.max(maxLen, getExplicitCellHeight(gridUnits[i]));
                 }
             }
             
@@ -602,7 +811,8 @@ public class TableContentLayoutManager {
                                 + start[i] + "-" + end[i]);
                     }
                     addAreasForCell(gridUnits[i], start[i], end[i], 
-                            layoutContext, lastRow, yoffset, partLength[i], maxLen);
+                            layoutContext, lastRow, yoffset, 
+                            partLength[i], lastRow.getHeight().opt);
                     gridUnits[i] = null;
                     start[i] = 0;
                     end[i] = 0;
@@ -615,7 +825,8 @@ public class TableContentLayoutManager {
         }
 
     }
-    
+
+    /*
     private int getExplicitCellHeight(PrimaryGridUnit pgu) {
         int len = 0;
         if (!pgu.getCell().getBlockProgressionDimension().getOptimum().isAuto()) {
@@ -628,7 +839,7 @@ public class TableContentLayoutManager {
                     .getOptimum().getLength().getValue());
         }
         return len;
-    }
+    }*/
     
     private void addAreasForCell(PrimaryGridUnit gu, int start, int end, 
             LayoutContext layoutContext, TableRowIterator.EffRow row, 
@@ -772,5 +983,15 @@ public class TableContentLayoutManager {
             return sb.toString();
         }
     }
+    
+    private class KnuthBoxCellWithBPD extends KnuthBox {
+        
+        private PrimaryGridUnit pgu;
+        
+        public KnuthBoxCellWithBPD(int w, PrimaryGridUnit pgu) {
+            super(w, null, true);
+            this.pgu = pgu;
+        }
+}
 
 }
index 728190820153b661cad9b8542583d415506b34d4..ccbe2f2ab638437bc5655d5828a627c18154342d 100644 (file)
@@ -150,11 +150,35 @@ public class TableRowIterator {
         }
     }
     
+    public void backToPreviewRow() {
+        currentIndex--;
+    }
+    
+    public EffRow getFirstRow() {
+        if (rows.size() == 0) {
+            prefetchNext();
+        }
+        return getCachedRow(0);
+    }
+    
+    public EffRow getLastRow() {
+        while (prefetchNext()) {
+            //nop
+        }
+        return getCachedRow(rows.size() - 1);
+    }
+    
     public EffRow getCachedRow(int index) {
-        return (EffRow)rows.get(index);
+        if (index < 0 || index >= rows.size()) {
+            return null;
+        } else {
+            return (EffRow)rows.get(index);
+        }
     }
     
     private boolean prefetchNext() {
+        boolean firstInTable = false;
+        boolean firstInBody = false;
         if (childInBodyIterator != null) {
             if (!childInBodyIterator.hasNext()) {
                 //force skip on to next body
@@ -164,8 +188,18 @@ public class TableRowIterator {
         if (childInBodyIterator == null) {
             if (bodyIterator.hasNext()) {
                 childInBodyIterator = ((TableBody)bodyIterator.next()).getChildNodes();
+                if (rows.size() == 0) {
+                    firstInTable = true;
+                }
+                firstInBody = true;
             } else {
                 //no more rows
+                if (rows.size() > 0) {
+                    getCachedRow(rows.size() - 1).setFlagForAllGridUnits(
+                            GridUnit.LAST_IN_BODY, true);
+                    getCachedRow(rows.size() - 1).setFlagForAllGridUnits(
+                            GridUnit.LAST_IN_TABLE, true);
+                }
                 return false;
             }
         }
@@ -198,6 +232,12 @@ public class TableRowIterator {
             throw new IllegalStateException("Illegal class found: " + node.getClass().getName());
         }
         EffRow gridUnits = buildGridRow(this.currentRow);
+        if (firstInBody) {
+            gridUnits.setFlagForAllGridUnits(GridUnit.FIRST_IN_BODY, true);
+        }
+        if (firstInTable) {
+            gridUnits.setFlagForAllGridUnits(GridUnit.FIRST_IN_TABLE, true);
+        }
         log.debug(gridUnits);
         rows.add(gridUnits);
         return true;
@@ -277,7 +317,7 @@ public class TableRowIterator {
                 horzSpan[0] = gu;
                 for (int j = 1; j < cell.getNumberColumnsSpanned(); j++) {
                     colnum++;
-                    GridUnit guSpan = new GridUnit(cell, columns.getColumn(colnum), colnum - 1, j);
+                    GridUnit guSpan = new GridUnit(gu, columns.getColumn(colnum), colnum - 1, j);
                     if (safelyGetListItem(gridUnits, colnum - 1) != null) {
                         log.error("Overlapping cell at position " + colnum);
                         //TODO throw layout exception
@@ -409,6 +449,14 @@ public class TableRowIterator {
             return gridUnits;
         }
         
+        public void setFlagForAllGridUnits(int flag, boolean value) {
+            Iterator iter = gridUnits.iterator();
+            while (iter.hasNext()) {
+                GridUnit gu = (GridUnit)iter.next();
+                gu.setFlag(flag, value);
+            }
+        }
+
         /** @see java.lang.Object#toString() */
         public String toString() {
             StringBuffer sb = new StringBuffer("EffRow {");