jdk/src/share/classes/javax/swing/text/html/TableView.java
changeset 2 90ce3da70b43
child 1287 a04aca99c77a
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/share/classes/javax/swing/text/html/TableView.java	Sat Dec 01 00:00:00 2007 +0000
@@ -0,0 +1,1801 @@
+/*
+ * Copyright 1998-2006 Sun Microsystems, Inc.  All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+package javax.swing.text.html;
+
+import java.awt.*;
+import java.util.BitSet;
+import java.util.Vector;
+import java.util.Arrays;
+import javax.swing.SizeRequirements;
+import javax.swing.event.DocumentEvent;
+
+import javax.swing.text.*;
+
+/**
+ * HTML table view.
+ *
+ * @author  Timothy Prinzing
+ * @see     View
+ */
+/*public*/ class TableView extends BoxView implements ViewFactory {
+
+    /**
+     * Constructs a TableView for the given element.
+     *
+     * @param elem the element that this view is responsible for
+     */
+    public TableView(Element elem) {
+        super(elem, View.Y_AXIS);
+        rows = new Vector();
+        gridValid = false;
+        captionIndex = -1;
+        totalColumnRequirements = new SizeRequirements();
+    }
+
+    /**
+     * Creates a new table row.
+     *
+     * @param elem an element
+     * @return the row
+     */
+    protected RowView createTableRow(Element elem) {
+        // PENDING(prinz) need to add support for some of the other
+        // elements, but for now just ignore anything that is not
+        // a TR.
+        Object o = elem.getAttributes().getAttribute(StyleConstants.NameAttribute);
+        if (o == HTML.Tag.TR) {
+            return new RowView(elem);
+        }
+        return null;
+    }
+
+    /**
+     * The number of columns in the table.
+     */
+    public int getColumnCount() {
+        return columnSpans.length;
+    }
+
+    /**
+     * Fetches the span (width) of the given column.
+     * This is used by the nested cells to query the
+     * sizes of grid locations outside of themselves.
+     */
+    public int getColumnSpan(int col) {
+        if (col < columnSpans.length) {
+            return columnSpans[col];
+        }
+        return 0;
+    }
+
+    /**
+     * The number of rows in the table.
+     */
+    public int getRowCount() {
+        return rows.size();
+    }
+
+    /**
+     * Fetch the span of multiple rows.  This includes
+     * the border area.
+     */
+    public int getMultiRowSpan(int row0, int row1) {
+        RowView rv0 = getRow(row0);
+        RowView rv1 = getRow(row1);
+        if ((rv0 != null) && (rv1 != null)) {
+            int index0 = rv0.viewIndex;
+            int index1 = rv1.viewIndex;
+            int span = getOffset(Y_AXIS, index1) - getOffset(Y_AXIS, index0) +
+                getSpan(Y_AXIS, index1);
+            return span;
+        }
+        return 0;
+    }
+
+    /**
+     * Fetches the span (height) of the given row.
+     */
+    public int getRowSpan(int row) {
+        RowView rv = getRow(row);
+        if (rv != null) {
+            return getSpan(Y_AXIS, rv.viewIndex);
+        }
+        return 0;
+    }
+
+    RowView getRow(int row) {
+        if (row < rows.size()) {
+            return (RowView) rows.elementAt(row);
+        }
+        return null;
+    }
+
+    protected View getViewAtPoint(int x, int y, Rectangle alloc) {
+        int n = getViewCount();
+        View v = null;
+        Rectangle allocation = new Rectangle();
+        for (int i = 0; i < n; i++) {
+            allocation.setBounds(alloc);
+            childAllocation(i, allocation);
+            v = getView(i);
+            if (v instanceof RowView) {
+                v = ((RowView)v).findViewAtPoint(x, y, allocation);
+                if (v != null) {
+                    alloc.setBounds(allocation);
+                    return v;
+                }
+            }
+        }
+        return super.getViewAtPoint(x, y, alloc);
+    }
+
+    /**
+     * Determines the number of columns occupied by
+     * the table cell represented by given element.
+     */
+    protected int getColumnsOccupied(View v) {
+        AttributeSet a = v.getElement().getAttributes();
+
+        if (a.isDefined(HTML.Attribute.COLSPAN)) {
+            String s = (String) a.getAttribute(HTML.Attribute.COLSPAN);
+            if (s != null) {
+                try {
+                    return Integer.parseInt(s);
+                } catch (NumberFormatException nfe) {
+                    // fall through to one column
+                }
+            }
+        }
+
+        return 1;
+    }
+
+    /**
+     * Determines the number of rows occupied by
+     * the table cell represented by given element.
+     */
+    protected int getRowsOccupied(View v) {
+        AttributeSet a = v.getElement().getAttributes();
+
+        if (a.isDefined(HTML.Attribute.ROWSPAN)) {
+            String s = (String) a.getAttribute(HTML.Attribute.ROWSPAN);
+            if (s != null) {
+                try {
+                    return Integer.parseInt(s);
+                } catch (NumberFormatException nfe) {
+                    // fall through to one row
+                }
+            }
+        }
+
+        return 1;
+    }
+
+    protected void invalidateGrid() {
+        gridValid = false;
+    }
+
+    protected StyleSheet getStyleSheet() {
+        HTMLDocument doc = (HTMLDocument) getDocument();
+        return doc.getStyleSheet();
+    }
+
+    /**
+     * Update the insets, which contain the caption if there
+     * is a caption.
+     */
+    void updateInsets() {
+        short top = (short) painter.getInset(TOP, this);
+        short bottom = (short) painter.getInset(BOTTOM, this);
+        if (captionIndex != -1) {
+            View caption = getView(captionIndex);
+            short h = (short) caption.getPreferredSpan(Y_AXIS);
+            AttributeSet a = caption.getAttributes();
+            Object align = a.getAttribute(CSS.Attribute.CAPTION_SIDE);
+            if ((align != null) && (align.equals("bottom"))) {
+                bottom += h;
+            } else {
+                top += h;
+            }
+        }
+        setInsets(top, (short) painter.getInset(LEFT, this),
+                  bottom, (short) painter.getInset(RIGHT, this));
+    }
+
+    /**
+     * Update any cached values that come from attributes.
+     */
+    protected void setPropertiesFromAttributes() {
+        StyleSheet sheet = getStyleSheet();
+        attr = sheet.getViewAttributes(this);
+        painter = sheet.getBoxPainter(attr);
+        if (attr != null) {
+            setInsets((short) painter.getInset(TOP, this),
+                      (short) painter.getInset(LEFT, this),
+                          (short) painter.getInset(BOTTOM, this),
+                      (short) painter.getInset(RIGHT, this));
+
+            CSS.LengthValue lv = (CSS.LengthValue)
+                attr.getAttribute(CSS.Attribute.BORDER_SPACING);
+            if (lv != null) {
+                cellSpacing = (int) lv.getValue();
+            } else {
+                cellSpacing = 0;
+            }
+            lv = (CSS.LengthValue)
+                    attr.getAttribute(CSS.Attribute.BORDER_TOP_WIDTH);
+            if (lv != null) {
+                    borderWidth = (int) lv.getValue();
+            } else {
+                    borderWidth = 0;
+            }
+
+                }
+    }
+
+    /**
+     * Fill in the grid locations that are placeholders
+     * for multi-column, multi-row, and missing grid
+     * locations.
+     */
+    void updateGrid() {
+        if (! gridValid) {
+            relativeCells = false;
+            multiRowCells = false;
+
+            // determine which views are table rows and clear out
+            // grid points marked filled.
+            captionIndex = -1;
+            rows.removeAllElements();
+            int n = getViewCount();
+            for (int i = 0; i < n; i++) {
+                View v = getView(i);
+                if (v instanceof RowView) {
+                    rows.addElement(v);
+                    RowView rv = (RowView) v;
+                    rv.clearFilledColumns();
+                    rv.rowIndex = rows.size() - 1;
+                    rv.viewIndex = i;
+                } else {
+                    Object o = v.getElement().getAttributes().getAttribute(StyleConstants.NameAttribute);
+                    if (o instanceof HTML.Tag) {
+                        HTML.Tag kind = (HTML.Tag) o;
+                        if (kind == HTML.Tag.CAPTION) {
+                            captionIndex = i;
+                        }
+                    }
+                }
+            }
+
+            int maxColumns = 0;
+            int nrows = rows.size();
+            for (int row = 0; row < nrows; row++) {
+                RowView rv = getRow(row);
+                int col = 0;
+                for (int cell = 0; cell < rv.getViewCount(); cell++, col++) {
+                    View cv = rv.getView(cell);
+                    if (! relativeCells) {
+                        AttributeSet a = cv.getAttributes();
+                        CSS.LengthValue lv = (CSS.LengthValue)
+                            a.getAttribute(CSS.Attribute.WIDTH);
+                        if ((lv != null) && (lv.isPercentage())) {
+                            relativeCells = true;
+                        }
+                    }
+                    // advance to a free column
+                    for (; rv.isFilled(col); col++);
+                    int rowSpan = getRowsOccupied(cv);
+                    if (rowSpan > 1) {
+                        multiRowCells = true;
+                    }
+                    int colSpan = getColumnsOccupied(cv);
+                    if ((colSpan > 1) || (rowSpan > 1)) {
+                        // fill in the overflow entries for this cell
+                        int rowLimit = row + rowSpan;
+                        int colLimit = col + colSpan;
+                        for (int i = row; i < rowLimit; i++) {
+                            for (int j = col; j < colLimit; j++) {
+                                if (i != row || j != col) {
+                                    addFill(i, j);
+                                }
+                            }
+                        }
+                        if (colSpan > 1) {
+                            col += colSpan - 1;
+                        }
+                    }
+                }
+                maxColumns = Math.max(maxColumns, col);
+            }
+
+            // setup the column layout/requirements
+            columnSpans = new int[maxColumns];
+            columnOffsets = new int[maxColumns];
+            columnRequirements = new SizeRequirements[maxColumns];
+            for (int i = 0; i < maxColumns; i++) {
+                columnRequirements[i] = new SizeRequirements();
+                columnRequirements[i].maximum = Integer.MAX_VALUE;
+            }
+            gridValid = true;
+        }
+    }
+
+    /**
+     * Mark a grid location as filled in for a cells overflow.
+     */
+    void addFill(int row, int col) {
+        RowView rv = getRow(row);
+        if (rv != null) {
+            rv.fillColumn(col);
+        }
+    }
+
+    /**
+     * Layout the columns to fit within the given target span.
+     *
+     * @param targetSpan the given span for total of all the table
+     *  columns
+     * @param reqs the requirements desired for each column.  This
+     *  is the column maximum of the cells minimum, preferred, and
+     *  maximum requested span
+     * @param spans the return value of how much to allocated to
+     *  each column
+     * @param offsets the return value of the offset from the
+     *  origin for each column
+     * @return the offset from the origin and the span for each column
+     *  in the offsets and spans parameters
+     */
+    protected void layoutColumns(int targetSpan, int[] offsets, int[] spans,
+                                 SizeRequirements[] reqs) {
+        //clean offsets and spans
+        Arrays.fill(offsets, 0);
+        Arrays.fill(spans, 0);
+        colIterator.setLayoutArrays(offsets, spans, targetSpan);
+        CSS.calculateTiledLayout(colIterator, targetSpan);
+    }
+
+    /**
+     * Calculate the requirements for each column.  The calculation
+     * is done as two passes over the table.  The table cells that
+     * occupy a single column are scanned first to determine the
+     * maximum of minimum, preferred, and maximum spans along the
+     * give axis.  Table cells that span multiple columns are excluded
+     * from the first pass.  A second pass is made to determine if
+     * the cells that span multiple columns are satisfied.  If the
+     * column requirements are not satisified, the needs of the
+     * multi-column cell is mixed into the existing column requirements.
+     * The calculation of the multi-column distribution is based upon
+     * the proportions of the existing column requirements and taking
+     * into consideration any constraining maximums.
+     */
+    void calculateColumnRequirements(int axis) {
+        // clean columnRequirements
+        for (SizeRequirements req : columnRequirements) {
+            req.minimum = 0;
+            req.preferred = 0;
+            req.maximum = Integer.MAX_VALUE;
+        }
+        Container host = getContainer();
+        if (host != null) {
+            if (host instanceof JTextComponent) {
+                skipComments = !((JTextComponent)host).isEditable();
+            } else {
+                skipComments = true;
+            }
+        }
+        // pass 1 - single column cells
+        boolean hasMultiColumn = false;
+        int nrows = getRowCount();
+        for (int i = 0; i < nrows; i++) {
+            RowView row = getRow(i);
+            int col = 0;
+            int ncells = row.getViewCount();
+            for (int cell = 0; cell < ncells; cell++) {
+                View cv = row.getView(cell);
+                if (skipComments && !(cv instanceof CellView)) {
+                    continue;
+                }
+                for (; row.isFilled(col); col++); // advance to a free column
+                int rowSpan = getRowsOccupied(cv);
+                int colSpan = getColumnsOccupied(cv);
+                if (colSpan == 1) {
+                    checkSingleColumnCell(axis, col, cv);
+                } else {
+                    hasMultiColumn = true;
+                    col += colSpan - 1;
+                }
+                col++;
+            }
+        }
+
+        // pass 2 - multi-column cells
+        if (hasMultiColumn) {
+            for (int i = 0; i < nrows; i++) {
+                RowView row = getRow(i);
+                int col = 0;
+                int ncells = row.getViewCount();
+                for (int cell = 0; cell < ncells; cell++) {
+                    View cv = row.getView(cell);
+                    if (skipComments && !(cv instanceof CellView)) {
+                        continue;
+                    }
+                    for (; row.isFilled(col); col++); // advance to a free column
+                    int colSpan = getColumnsOccupied(cv);
+                    if (colSpan > 1) {
+                        checkMultiColumnCell(axis, col, colSpan, cv);
+                        col += colSpan - 1;
+                    }
+                    col++;
+                }
+            }
+        }
+    }
+
+    /**
+     * check the requirements of a table cell that spans a single column.
+     */
+    void checkSingleColumnCell(int axis, int col, View v) {
+        SizeRequirements req = columnRequirements[col];
+        req.minimum = Math.max((int) v.getMinimumSpan(axis), req.minimum);
+        req.preferred = Math.max((int) v.getPreferredSpan(axis), req.preferred);
+    }
+
+    /**
+     * check the requirements of a table cell that spans multiple
+     * columns.
+     */
+    void checkMultiColumnCell(int axis, int col, int ncols, View v) {
+        // calculate the totals
+        long min = 0;
+        long pref = 0;
+        long max = 0;
+        for (int i = 0; i < ncols; i++) {
+            SizeRequirements req = columnRequirements[col + i];
+            min += req.minimum;
+            pref += req.preferred;
+            max += req.maximum;
+        }
+
+        // check if the minimum size needs adjustment.
+        int cmin = (int) v.getMinimumSpan(axis);
+        if (cmin > min) {
+            /*
+             * the columns that this cell spans need adjustment to fit
+             * this table cell.... calculate the adjustments.
+             */
+            SizeRequirements[] reqs = new SizeRequirements[ncols];
+            for (int i = 0; i < ncols; i++) {
+                reqs[i] = columnRequirements[col + i];
+            }
+            int[] spans = new int[ncols];
+            int[] offsets = new int[ncols];
+            SizeRequirements.calculateTiledPositions(cmin, null, reqs,
+                                                     offsets, spans);
+            // apply the adjustments
+            for (int i = 0; i < ncols; i++) {
+                SizeRequirements req = reqs[i];
+                req.minimum = Math.max(spans[i], req.minimum);
+                req.preferred = Math.max(req.minimum, req.preferred);
+                req.maximum = Math.max(req.preferred, req.maximum);
+            }
+        }
+
+        // check if the preferred size needs adjustment.
+        int cpref = (int) v.getPreferredSpan(axis);
+        if (cpref > pref) {
+            /*
+             * the columns that this cell spans need adjustment to fit
+             * this table cell.... calculate the adjustments.
+             */
+            SizeRequirements[] reqs = new SizeRequirements[ncols];
+            for (int i = 0; i < ncols; i++) {
+                reqs[i] = columnRequirements[col + i];
+            }
+            int[] spans = new int[ncols];
+            int[] offsets = new int[ncols];
+            SizeRequirements.calculateTiledPositions(cpref, null, reqs,
+                                                     offsets, spans);
+            // apply the adjustments
+            for (int i = 0; i < ncols; i++) {
+                SizeRequirements req = reqs[i];
+                req.preferred = Math.max(spans[i], req.preferred);
+                req.maximum = Math.max(req.preferred, req.maximum);
+            }
+        }
+
+    }
+
+    // --- BoxView methods -----------------------------------------
+
+    /**
+     * Calculate the requirements for the minor axis.  This is called by
+     * the superclass whenever the requirements need to be updated (i.e.
+     * a preferenceChanged was messaged through this view).
+     * <p>
+     * This is implemented to calculate the requirements as the sum of the
+     * requirements of the columns and then adjust it if the
+     * CSS width or height attribute is specified and applicable to
+     * the axis.
+     */
+    protected SizeRequirements calculateMinorAxisRequirements(int axis, SizeRequirements r) {
+        updateGrid();
+
+        // calculate column requirements for each column
+        calculateColumnRequirements(axis);
+
+
+        // the requirements are the sum of the columns.
+        if (r == null) {
+            r = new SizeRequirements();
+        }
+        long min = 0;
+        long pref = 0;
+        int n = columnRequirements.length;
+        for (int i = 0; i < n; i++) {
+            SizeRequirements req = columnRequirements[i];
+            min += req.minimum;
+            pref += req.preferred;
+        }
+        int adjust = (n + 1) * cellSpacing + 2 * borderWidth;
+        min += adjust;
+        pref += adjust;
+        r.minimum = (int) min;
+        r.preferred = (int) pref;
+        r.maximum = (int) pref;
+
+
+        AttributeSet attr = getAttributes();
+        CSS.LengthValue cssWidth = (CSS.LengthValue)attr.getAttribute(
+                                                    CSS.Attribute.WIDTH);
+
+        if (BlockView.spanSetFromAttributes(axis, r, cssWidth, null)) {
+            if (r.minimum < (int)min) {
+                // The user has requested a smaller size than is needed to
+                // show the table, override it.
+                r.maximum = r.minimum = r.preferred = (int) min;
+            }
+        }
+        totalColumnRequirements.minimum = r.minimum;
+        totalColumnRequirements.preferred = r.preferred;
+        totalColumnRequirements.maximum = r.maximum;
+
+        // set the alignment
+        Object o = attr.getAttribute(CSS.Attribute.TEXT_ALIGN);
+        if (o != null) {
+            // set horizontal alignment
+            String ta = o.toString();
+            if (ta.equals("left")) {
+                r.alignment = 0;
+            } else if (ta.equals("center")) {
+                r.alignment = 0.5f;
+            } else if (ta.equals("right")) {
+                r.alignment = 1;
+            } else {
+                r.alignment = 0;
+            }
+        } else {
+            r.alignment = 0;
+        }
+
+        return r;
+    }
+
+    /**
+     * Calculate the requirements for the major axis.  This is called by
+     * the superclass whenever the requirements need to be updated (i.e.
+     * a preferenceChanged was messaged through this view).
+     * <p>
+     * This is implemented to provide the superclass behavior adjusted for
+     * multi-row table cells.
+     */
+    protected SizeRequirements calculateMajorAxisRequirements(int axis, SizeRequirements r) {
+        updateInsets();
+        rowIterator.updateAdjustments();
+        r = CSS.calculateTiledRequirements(rowIterator, r);
+        r.maximum = r.preferred;
+        return r;
+    }
+
+    /**
+     * Perform layout for the minor axis of the box (i.e. the
+     * axis orthoginal to the axis that it represents).  The results
+     * of the layout should be placed in the given arrays which represent
+     * the allocations to the children along the minor axis.  This
+     * is called by the superclass whenever the layout needs to be
+     * updated along the minor axis.
+     * <p>
+     * This is implemented to call the
+     * <a href="#layoutColumns">layoutColumns</a> method, and then
+     * forward to the superclass to actually carry out the layout
+     * of the tables rows.
+     *
+     * @param targetSpan the total span given to the view, which
+     *  whould be used to layout the children
+     * @param axis the axis being layed out
+     * @param offsets the offsets from the origin of the view for
+     *  each of the child views.  This is a return value and is
+     *  filled in by the implementation of this method
+     * @param spans the span of each child view;  this is a return
+     *  value and is filled in by the implementation of this method
+     * @return the offset and span for each child view in the
+     *  offsets and spans parameters
+     */
+    protected void layoutMinorAxis(int targetSpan, int axis, int[] offsets, int[] spans) {
+        // make grid is properly represented
+        updateGrid();
+
+        // all of the row layouts are invalid, so mark them that way
+        int n = getRowCount();
+        for (int i = 0; i < n; i++) {
+            RowView row = getRow(i);
+            row.layoutChanged(axis);
+        }
+
+        // calculate column spans
+        layoutColumns(targetSpan, columnOffsets, columnSpans, columnRequirements);
+
+        // continue normal layout
+        super.layoutMinorAxis(targetSpan, axis, offsets, spans);
+    }
+
+
+    /**
+     * Perform layout for the major axis of the box (i.e. the
+     * axis that it represents).  The results
+     * of the layout should be placed in the given arrays which represent
+     * the allocations to the children along the minor axis.  This
+     * is called by the superclass whenever the layout needs to be
+     * updated along the minor axis.
+     * <p>
+     * This method is where the layout of the table rows within the
+     * table takes place.  This method is implemented to call the use
+     * the RowIterator and the CSS collapsing tile to layout
+     * with border spacing and border collapsing capabilities.
+     *
+     * @param targetSpan the total span given to the view, which
+     *  whould be used to layout the children
+     * @param axis the axis being layed out
+     * @param offsets the offsets from the origin of the view for
+     *  each of the child views; this is a return value and is
+     *  filled in by the implementation of this method
+     * @param spans the span of each child view; this is a return
+     *  value and is filled in by the implementation of this method
+     * @return the offset and span for each child view in the
+     *  offsets and spans parameters
+     */
+    protected void layoutMajorAxis(int targetSpan, int axis, int[] offsets, int[] spans) {
+        rowIterator.setLayoutArrays(offsets, spans);
+        CSS.calculateTiledLayout(rowIterator, targetSpan);
+
+        if (captionIndex != -1) {
+            // place the caption
+            View caption = getView(captionIndex);
+            int h = (int) caption.getPreferredSpan(Y_AXIS);
+            spans[captionIndex] = h;
+            short boxBottom = (short) painter.getInset(BOTTOM, this);
+            if (boxBottom != getBottomInset()) {
+                offsets[captionIndex] = targetSpan + boxBottom;
+            } else {
+                offsets[captionIndex] = - getTopInset();
+            }
+        }
+    }
+
+    /**
+     * Fetches the child view that represents the given position in
+     * the model.  This is implemented to walk through the children
+     * looking for a range that contains the given position.  In this
+     * view the children do not necessarily have a one to one mapping
+     * with the child elements.
+     *
+     * @param pos  the search position >= 0
+     * @param a  the allocation to the table on entry, and the
+     *   allocation of the view containing the position on exit
+     * @return  the view representing the given position, or
+     *   null if there isn't one
+     */
+    protected View getViewAtPosition(int pos, Rectangle a) {
+        int n = getViewCount();
+        for (int i = 0; i < n; i++) {
+            View v = getView(i);
+            int p0 = v.getStartOffset();
+            int p1 = v.getEndOffset();
+            if ((pos >= p0) && (pos < p1)) {
+                // it's in this view.
+                if (a != null) {
+                    childAllocation(i, a);
+                }
+                return v;
+            }
+        }
+        if (pos == getEndOffset()) {
+            View v = getView(n - 1);
+            if (a != null) {
+                this.childAllocation(n - 1, a);
+            }
+            return v;
+        }
+        return null;
+    }
+
+    // --- View methods ---------------------------------------------
+
+    /**
+     * Fetches the attributes to use when rendering.  This is
+     * implemented to multiplex the attributes specified in the
+     * model with a StyleSheet.
+     */
+    public AttributeSet getAttributes() {
+        if (attr == null) {
+            StyleSheet sheet = getStyleSheet();
+            attr = sheet.getViewAttributes(this);
+        }
+        return attr;
+    }
+
+    /**
+     * Renders using the given rendering surface and area on that
+     * surface.  This is implemented to delegate to the css box
+     * painter to paint the border and background prior to the
+     * interior.  The superclass culls rendering the children
+     * that don't directly intersect the clip and the row may
+     * have cells hanging from a row above in it.  The table
+     * does not use the superclass rendering behavior and instead
+     * paints all of the rows and lets the rows cull those
+     * cells not intersecting the clip region.
+     *
+     * @param g the rendering surface to use
+     * @param allocation the allocated region to render into
+     * @see View#paint
+     */
+    public void paint(Graphics g, Shape allocation) {
+        // paint the border
+        Rectangle a = allocation.getBounds();
+        setSize(a.width, a.height);
+        if (captionIndex != -1) {
+            // adjust the border for the caption
+            short top = (short) painter.getInset(TOP, this);
+            short bottom = (short) painter.getInset(BOTTOM, this);
+            if (top != getTopInset()) {
+                int h = getTopInset() - top;
+                a.y += h;
+                a.height -= h;
+            } else {
+                a.height -= getBottomInset() - bottom;
+            }
+        }
+        painter.paint(g, a.x, a.y, a.width, a.height, this);
+        // paint interior
+        int n = getViewCount();
+        for (int i = 0; i < n; i++) {
+            View v = getView(i);
+            v.paint(g, getChildAllocation(i, allocation));
+        }
+        //super.paint(g, a);
+    }
+
+    /**
+     * Establishes the parent view for this view.  This is
+     * guaranteed to be called before any other methods if the
+     * parent view is functioning properly.
+     * <p>
+     * This is implemented
+     * to forward to the superclass as well as call the
+     * <a href="#setPropertiesFromAttributes">setPropertiesFromAttributes</a>
+     * method to set the paragraph properties from the css
+     * attributes.  The call is made at this time to ensure
+     * the ability to resolve upward through the parents
+     * view attributes.
+     *
+     * @param parent the new parent, or null if the view is
+     *  being removed from a parent it was previously added
+     *  to
+     */
+    public void setParent(View parent) {
+        super.setParent(parent);
+        if (parent != null) {
+            setPropertiesFromAttributes();
+        }
+    }
+
+    /**
+     * Fetches the ViewFactory implementation that is feeding
+     * the view hierarchy.
+     * This replaces the ViewFactory with an implementation that
+     * calls through to the createTableRow and createTableCell
+     * methods.   If the element given to the factory isn't a
+     * table row or cell, the request is delegated to the factory
+     * produced by the superclass behavior.
+     *
+     * @return the factory, null if none
+     */
+    public ViewFactory getViewFactory() {
+        return this;
+    }
+
+    /**
+     * Gives notification that something was inserted into
+     * the document in a location that this view is responsible for.
+     * This replaces the ViewFactory with an implementation that
+     * calls through to the createTableRow and createTableCell
+     * methods.   If the element given to the factory isn't a
+     * table row or cell, the request is delegated to the factory
+     * passed as an argument.
+     *
+     * @param e the change information from the associated document
+     * @param a the current allocation of the view
+     * @param f the factory to use to rebuild if the view has children
+     * @see View#insertUpdate
+     */
+    public void insertUpdate(DocumentEvent e, Shape a, ViewFactory f) {
+        super.insertUpdate(e, a, this);
+    }
+
+    /**
+     * Gives notification that something was removed from the document
+     * in a location that this view is responsible for.
+     * This replaces the ViewFactory with an implementation that
+     * calls through to the createTableRow and createTableCell
+     * methods.   If the element given to the factory isn't a
+     * table row or cell, the request is delegated to the factory
+     * passed as an argument.
+     *
+     * @param e the change information from the associated document
+     * @param a the current allocation of the view
+     * @param f the factory to use to rebuild if the view has children
+     * @see View#removeUpdate
+     */
+    public void removeUpdate(DocumentEvent e, Shape a, ViewFactory f) {
+        super.removeUpdate(e, a, this);
+    }
+
+    /**
+     * Gives notification from the document that attributes were changed
+     * in a location that this view is responsible for.
+     * This replaces the ViewFactory with an implementation that
+     * calls through to the createTableRow and createTableCell
+     * methods.   If the element given to the factory isn't a
+     * table row or cell, the request is delegated to the factory
+     * passed as an argument.
+     *
+     * @param e the change information from the associated document
+     * @param a the current allocation of the view
+     * @param f the factory to use to rebuild if the view has children
+     * @see View#changedUpdate
+     */
+    public void changedUpdate(DocumentEvent e, Shape a, ViewFactory f) {
+        super.changedUpdate(e, a, this);
+    }
+
+    protected void forwardUpdate(DocumentEvent.ElementChange ec,
+                                 DocumentEvent e, Shape a, ViewFactory f) {
+        super.forwardUpdate(ec, e, a, f);
+        // A change in any of the table cells usually effects the whole table,
+        // so redraw it all!
+        if (a != null) {
+            Component c = getContainer();
+            if (c != null) {
+                Rectangle alloc = (a instanceof Rectangle) ? (Rectangle)a :
+                                   a.getBounds();
+                c.repaint(alloc.x, alloc.y, alloc.width, alloc.height);
+            }
+        }
+    }
+
+    /**
+     * Change the child views.  This is implemented to
+     * provide the superclass behavior and invalidate the
+     * grid so that rows and columns will be recalculated.
+     */
+    public void replace(int offset, int length, View[] views) {
+        super.replace(offset, length, views);
+        invalidateGrid();
+    }
+
+    // --- ViewFactory methods ------------------------------------------
+
+    /**
+     * The table itself acts as a factory for the various
+     * views that actually represent pieces of the table.
+     * All other factory activity is delegated to the factory
+     * returned by the parent of the table.
+     */
+    public View create(Element elem) {
+        Object o = elem.getAttributes().getAttribute(StyleConstants.NameAttribute);
+        if (o instanceof HTML.Tag) {
+            HTML.Tag kind = (HTML.Tag) o;
+            if (kind == HTML.Tag.TR) {
+                return createTableRow(elem);
+            } else if ((kind == HTML.Tag.TD) || (kind == HTML.Tag.TH)) {
+                return new CellView(elem);
+            } else if (kind == HTML.Tag.CAPTION) {
+                return new javax.swing.text.html.ParagraphView(elem);
+            }
+        }
+        // default is to delegate to the normal factory
+        View p = getParent();
+        if (p != null) {
+            ViewFactory f = p.getViewFactory();
+            if (f != null) {
+                return f.create(elem);
+            }
+        }
+        return null;
+    }
+
+    // ---- variables ----------------------------------------------------
+
+    private AttributeSet attr;
+    private StyleSheet.BoxPainter painter;
+
+    private int cellSpacing;
+    private int borderWidth;
+
+    /**
+     * The index of the caption view if there is a caption.
+     * This has a value of -1 if there is no caption.  The
+     * caption lives in the inset area of the table, and is
+     * updated with each time the grid is recalculated.
+     */
+    private int captionIndex;
+
+    /**
+     * Do any of the table cells contain a relative size
+     * specification?  This is updated with each call to
+     * updateGrid().  If this is true, the ColumnIterator
+     * will do extra work to calculate relative cell
+     * specifications.
+     */
+    private boolean relativeCells;
+
+    /**
+     * Do any of the table cells span multiple rows?  If
+     * true, the RowRequirementIterator will do additional
+     * work to adjust the requirements of rows spanned by
+     * a single table cell.  This is updated with each call to
+     * updateGrid().
+     */
+    private boolean multiRowCells;
+
+    int[] columnSpans;
+    int[] columnOffsets;
+    /**
+     * SizeRequirements for all the columns.
+     */
+    SizeRequirements totalColumnRequirements;
+    SizeRequirements[] columnRequirements;
+
+    RowIterator rowIterator = new RowIterator();
+    ColumnIterator colIterator = new ColumnIterator();
+
+    Vector rows;
+
+    // whether to display comments inside table or not.
+    boolean skipComments = false;
+
+    boolean gridValid;
+    static final private BitSet EMPTY = new BitSet();
+
+    class ColumnIterator implements CSS.LayoutIterator {
+
+        /**
+         * Disable percentage adjustments which should only apply
+         * when calculating layout, not requirements.
+         */
+        void disablePercentages() {
+            percentages = null;
+        }
+
+        /**
+         * Update percentage adjustments if they are needed.
+         */
+        private void updatePercentagesAndAdjustmentWeights(int span) {
+            adjustmentWeights = new int[columnRequirements.length];
+            for (int i = 0; i < columnRequirements.length; i++) {
+                adjustmentWeights[i] = 0;
+            }
+            if (relativeCells) {
+                percentages = new int[columnRequirements.length];
+            } else {
+                percentages = null;
+            }
+            int nrows = getRowCount();
+            for (int rowIndex = 0; rowIndex < nrows; rowIndex++) {
+                RowView row = getRow(rowIndex);
+                int col = 0;
+                int ncells = row.getViewCount();
+                for (int cell = 0; cell < ncells; cell++, col++) {
+                    View cv = row.getView(cell);
+                    for (; row.isFilled(col); col++); // advance to a free column
+                    int rowSpan = getRowsOccupied(cv);
+                    int colSpan = getColumnsOccupied(cv);
+                    AttributeSet a = cv.getAttributes();
+                    CSS.LengthValue lv = (CSS.LengthValue)
+                        a.getAttribute(CSS.Attribute.WIDTH);
+                    if ( lv != null ) {
+                        int len = (int) (lv.getValue(span) / colSpan + 0.5f);
+                        for (int i = 0; i < colSpan; i++) {
+                            if (lv.isPercentage()) {
+                                // add a percentage requirement
+                                percentages[col+i] = Math.max(percentages[col+i], len);
+                                adjustmentWeights[col + i] = Math.max(adjustmentWeights[col + i], WorstAdjustmentWeight);
+                            } else {
+                                adjustmentWeights[col + i] = Math.max(adjustmentWeights[col + i], WorstAdjustmentWeight - 1);
+                            }
+                        }
+                    }
+                    col += colSpan - 1;
+                }
+            }
+        }
+
+        /**
+         * Set the layout arrays to use for holding layout results
+         */
+        public void setLayoutArrays(int offsets[], int spans[], int targetSpan) {
+            this.offsets = offsets;
+            this.spans = spans;
+            updatePercentagesAndAdjustmentWeights(targetSpan);
+        }
+
+        // --- RequirementIterator methods -------------------
+
+        public int getCount() {
+            return columnRequirements.length;
+        }
+
+        public void setIndex(int i) {
+            col = i;
+        }
+
+        public void setOffset(int offs) {
+            offsets[col] = offs;
+        }
+
+        public int getOffset() {
+            return offsets[col];
+        }
+
+        public void setSpan(int span) {
+            spans[col] = span;
+        }
+
+        public int getSpan() {
+            return spans[col];
+        }
+
+        public float getMinimumSpan(float parentSpan) {
+            // do not care for percentages, since min span can't
+            // be less than columnRequirements[col].minimum,
+            // but can be less than percentage value.
+            return columnRequirements[col].minimum;
+        }
+
+        public float getPreferredSpan(float parentSpan) {
+            if ((percentages != null) && (percentages[col] != 0)) {
+                return Math.max(percentages[col], columnRequirements[col].minimum);
+            }
+            return columnRequirements[col].preferred;
+        }
+
+        public float getMaximumSpan(float parentSpan) {
+            return columnRequirements[col].maximum;
+        }
+
+        public float getBorderWidth() {
+            return borderWidth;
+        }
+
+
+        public float getLeadingCollapseSpan() {
+            return cellSpacing;
+        }
+
+        public float getTrailingCollapseSpan() {
+            return cellSpacing;
+        }
+
+        public int getAdjustmentWeight() {
+            return adjustmentWeights[col];
+        }
+
+        /**
+         * Current column index
+         */
+        private int col;
+
+        /**
+         * percentage values (may be null since there
+         * might not be any).
+         */
+        private int[] percentages;
+
+        private int[] adjustmentWeights;
+
+        private int[] offsets;
+        private int[] spans;
+    }
+
+    class RowIterator implements CSS.LayoutIterator {
+
+        RowIterator() {
+        }
+
+        void updateAdjustments() {
+            int axis = Y_AXIS;
+            if (multiRowCells) {
+                // adjust requirements of multi-row cells
+                int n = getRowCount();
+                adjustments = new int[n];
+                for (int i = 0; i < n; i++) {
+                    RowView rv = getRow(i);
+                    if (rv.multiRowCells == true) {
+                        int ncells = rv.getViewCount();
+                        for (int j = 0; j < ncells; j++) {
+                            View v = rv.getView(j);
+                            int nrows = getRowsOccupied(v);
+                            if (nrows > 1) {
+                                int spanNeeded = (int) v.getPreferredSpan(axis);
+                                adjustMultiRowSpan(spanNeeded, nrows, i);
+                            }
+                        }
+                    }
+                }
+            } else {
+                adjustments = null;
+            }
+        }
+
+        /**
+         * Fixup preferences to accomodate a multi-row table cell
+         * if not already covered by existing preferences.  This is
+         * a no-op if not all of the rows needed (to do this check/fixup)
+         * have arrived yet.
+         */
+        void adjustMultiRowSpan(int spanNeeded, int nrows, int rowIndex) {
+            if ((rowIndex + nrows) > getCount()) {
+                // rows are missing (could be a bad rowspan specification)
+                // or not all the rows have arrived.  Do the best we can with
+                // the current set of rows.
+                nrows = getCount() - rowIndex;
+                if (nrows < 1) {
+                    return;
+                }
+            }
+            int span = 0;
+            for (int i = 0; i < nrows; i++) {
+                RowView rv = getRow(rowIndex + i);
+                span += rv.getPreferredSpan(Y_AXIS);
+            }
+            if (spanNeeded > span) {
+                int adjust = (spanNeeded - span);
+                int rowAdjust = adjust / nrows;
+                int firstAdjust = rowAdjust + (adjust - (rowAdjust * nrows));
+                RowView rv = getRow(rowIndex);
+                adjustments[rowIndex] = Math.max(adjustments[rowIndex],
+                                                 firstAdjust);
+                for (int i = 1; i < nrows; i++) {
+                    adjustments[rowIndex + i] = Math.max(
+                        adjustments[rowIndex + i], rowAdjust);
+                }
+            }
+        }
+
+        void setLayoutArrays(int[] offsets, int[] spans) {
+            this.offsets = offsets;
+            this.spans = spans;
+        }
+
+        // --- RequirementIterator methods -------------------
+
+        public void setOffset(int offs) {
+            RowView rv = getRow(row);
+            if (rv != null) {
+                offsets[rv.viewIndex] = offs;
+            }
+        }
+
+        public int getOffset() {
+            RowView rv = getRow(row);
+            if (rv != null) {
+                return offsets[rv.viewIndex];
+            }
+            return 0;
+        }
+
+        public void setSpan(int span) {
+            RowView rv = getRow(row);
+            if (rv != null) {
+                spans[rv.viewIndex] = span;
+            }
+        }
+
+        public int getSpan() {
+            RowView rv = getRow(row);
+            if (rv != null) {
+                return spans[rv.viewIndex];
+            }
+            return 0;
+        }
+
+        public int getCount() {
+            return rows.size();
+        }
+
+        public void setIndex(int i) {
+            row = i;
+        }
+
+        public float getMinimumSpan(float parentSpan) {
+            return getPreferredSpan(parentSpan);
+        }
+
+        public float getPreferredSpan(float parentSpan) {
+            RowView rv = getRow(row);
+            if (rv != null) {
+                int adjust = (adjustments != null) ? adjustments[row] : 0;
+                return rv.getPreferredSpan(TableView.this.getAxis()) + adjust;
+            }
+            return 0;
+        }
+
+        public float getMaximumSpan(float parentSpan) {
+            return getPreferredSpan(parentSpan);
+        }
+
+        public float getBorderWidth() {
+            return borderWidth;
+        }
+
+        public float getLeadingCollapseSpan() {
+            return cellSpacing;
+        }
+
+        public float getTrailingCollapseSpan() {
+            return cellSpacing;
+        }
+
+        public int getAdjustmentWeight() {
+            return 0;
+        }
+
+        /**
+         * Current row index
+         */
+        private int row;
+
+        /**
+         * Adjustments to the row requirements to handle multi-row
+         * table cells.
+         */
+        private int[] adjustments;
+
+        private int[] offsets;
+        private int[] spans;
+    }
+
+    /**
+     * View of a row in a row-centric table.
+     */
+    public class RowView extends BoxView {
+
+        /**
+         * Constructs a TableView for the given element.
+         *
+         * @param elem the element that this view is responsible for
+         */
+        public RowView(Element elem) {
+            super(elem, View.X_AXIS);
+            fillColumns = new BitSet();
+            RowView.this.setPropertiesFromAttributes();
+        }
+
+        void clearFilledColumns() {
+            fillColumns.and(EMPTY);
+        }
+
+        void fillColumn(int col) {
+            fillColumns.set(col);
+        }
+
+        boolean isFilled(int col) {
+            return fillColumns.get(col);
+        }
+
+        /**
+         * The number of columns present in this row.
+         */
+        int getColumnCount() {
+            int nfill = 0;
+            int n = fillColumns.size();
+            for (int i = 0; i < n; i++) {
+                if (fillColumns.get(i)) {
+                    nfill ++;
+                }
+            }
+            return getViewCount() + nfill;
+        }
+
+        /**
+         * Fetches the attributes to use when rendering.  This is
+         * implemented to multiplex the attributes specified in the
+         * model with a StyleSheet.
+         */
+        public AttributeSet getAttributes() {
+            return attr;
+        }
+
+        View findViewAtPoint(int x, int y, Rectangle alloc) {
+            int n = getViewCount();
+            for (int i = 0; i < n; i++) {
+                if (getChildAllocation(i, alloc).contains(x, y)) {
+                    childAllocation(i, alloc);
+                    return getView(i);
+                }
+            }
+            return null;
+        }
+
+        protected StyleSheet getStyleSheet() {
+            HTMLDocument doc = (HTMLDocument) getDocument();
+            return doc.getStyleSheet();
+        }
+
+        /**
+         * This is called by a child to indicate its
+         * preferred span has changed.  This is implemented to
+         * execute the superclass behavior and well as try to
+         * determine if a row with a multi-row cell hangs across
+         * this row.  If a multi-row cell covers this row it also
+         * needs to propagate a preferenceChanged so that it will
+         * recalculate the multi-row cell.
+         *
+         * @param child the child view
+         * @param width true if the width preference should change
+         * @param height true if the height preference should change
+         */
+        public void preferenceChanged(View child, boolean width, boolean height) {
+            super.preferenceChanged(child, width, height);
+            if (TableView.this.multiRowCells && height) {
+                for (int i = rowIndex  - 1; i >= 0; i--) {
+                    RowView rv = TableView.this.getRow(i);
+                    if (rv.multiRowCells) {
+                        rv.preferenceChanged(null, false, true);
+                        break;
+                    }
+                }
+            }
+        }
+
+        // The major axis requirements for a row are dictated by the column
+        // requirements. These methods use the value calculated by
+        // TableView.
+        protected SizeRequirements calculateMajorAxisRequirements(int axis, SizeRequirements r) {
+            SizeRequirements req = new SizeRequirements();
+            req.minimum = totalColumnRequirements.minimum;
+            req.maximum = totalColumnRequirements.maximum;
+            req.preferred = totalColumnRequirements.preferred;
+            req.alignment = 0f;
+            return req;
+        }
+
+        public float getMinimumSpan(int axis) {
+            float value;
+
+            if (axis == View.X_AXIS) {
+                value = totalColumnRequirements.minimum + getLeftInset() +
+                        getRightInset();
+            }
+            else {
+                value = super.getMinimumSpan(axis);
+            }
+            return value;
+        }
+
+        public float getMaximumSpan(int axis) {
+            float value;
+
+            if (axis == View.X_AXIS) {
+                // We're flexible.
+                value = (float)Integer.MAX_VALUE;
+            }
+            else {
+                value = super.getMaximumSpan(axis);
+            }
+            return value;
+        }
+
+        public float getPreferredSpan(int axis) {
+            float value;
+
+            if (axis == View.X_AXIS) {
+                value = totalColumnRequirements.preferred + getLeftInset() +
+                        getRightInset();
+            }
+            else {
+                value = super.getPreferredSpan(axis);
+            }
+            return value;
+        }
+
+        public void changedUpdate(DocumentEvent e, Shape a, ViewFactory f) {
+            super.changedUpdate(e, a, f);
+            int pos = e.getOffset();
+            if (pos <= getStartOffset() && (pos + e.getLength()) >=
+                getEndOffset()) {
+                RowView.this.setPropertiesFromAttributes();
+            }
+        }
+
+        /**
+         * Renders using the given rendering surface and area on that
+         * surface.  This is implemented to delegate to the css box
+         * painter to paint the border and background prior to the
+         * interior.
+         *
+         * @param g the rendering surface to use
+         * @param allocation the allocated region to render into
+         * @see View#paint
+         */
+        public void paint(Graphics g, Shape allocation) {
+            Rectangle a = (Rectangle) allocation;
+            painter.paint(g, a.x, a.y, a.width, a.height, this);
+            super.paint(g, a);
+        }
+
+        /**
+         * Change the child views.  This is implemented to
+         * provide the superclass behavior and invalidate the
+         * grid so that rows and columns will be recalculated.
+         */
+        public void replace(int offset, int length, View[] views) {
+            super.replace(offset, length, views);
+            invalidateGrid();
+        }
+
+        /**
+         * Calculate the height requirements of the table row.  The
+         * requirements of multi-row cells are not considered for this
+         * calculation.  The table itself will check and adjust the row
+         * requirements for all the rows that have multi-row cells spanning
+         * them.  This method updates the multi-row flag that indicates that
+         * this row and rows below need additional consideration.
+         */
+        protected SizeRequirements calculateMinorAxisRequirements(int axis, SizeRequirements r) {
+//          return super.calculateMinorAxisRequirements(axis, r);
+            long min = 0;
+            long pref = 0;
+            long max = 0;
+            multiRowCells = false;
+            int n = getViewCount();
+            for (int i = 0; i < n; i++) {
+                View v = getView(i);
+                if (getRowsOccupied(v) > 1) {
+                    multiRowCells = true;
+                    max = Math.max((int) v.getMaximumSpan(axis), max);
+                } else {
+                    min = Math.max((int) v.getMinimumSpan(axis), min);
+                    pref = Math.max((int) v.getPreferredSpan(axis), pref);
+                    max = Math.max((int) v.getMaximumSpan(axis), max);
+                }
+            }
+
+            if (r == null) {
+                r = new SizeRequirements();
+                r.alignment = 0.5f;
+            }
+            r.preferred = (int) pref;
+            r.minimum = (int) min;
+            r.maximum = (int) max;
+            return r;
+        }
+
+        /**
+         * Perform layout for the major axis of the box (i.e. the
+         * axis that it represents).  The results of the layout should
+         * be placed in the given arrays which represent the allocations
+         * to the children along the major axis.
+         * <p>
+         * This is re-implemented to give each child the span of the column
+         * width for the table, and to give cells that span multiple columns
+         * the multi-column span.
+         *
+         * @param targetSpan the total span given to the view, which
+         *  whould be used to layout the children
+         * @param axis the axis being layed out
+         * @param offsets the offsets from the origin of the view for
+         *  each of the child views; this is a return value and is
+         *  filled in by the implementation of this method
+         * @param spans the span of each child view; this is a return
+         *  value and is filled in by the implementation of this method
+         * @return the offset and span for each child view in the
+         *  offsets and spans parameters
+         */
+        protected void layoutMajorAxis(int targetSpan, int axis, int[] offsets, int[] spans) {
+            int col = 0;
+            int ncells = getViewCount();
+            for (int cell = 0; cell < ncells; cell++) {
+                View cv = getView(cell);
+                if (skipComments && !(cv instanceof CellView)) {
+                    continue;
+                }
+                for (; isFilled(col); col++); // advance to a free column
+                int colSpan = getColumnsOccupied(cv);
+                spans[cell] = columnSpans[col];
+                offsets[cell] = columnOffsets[col];
+                if (colSpan > 1) {
+                    int n = columnSpans.length;
+                    for (int j = 1; j < colSpan; j++) {
+                        // Because the table may be only partially formed, some
+                        // of the columns may not yet exist.  Therefore we check
+                        // the bounds.
+                        if ((col+j) < n) {
+                            spans[cell] += columnSpans[col+j];
+                            spans[cell] += cellSpacing;
+                        }
+                    }
+                    col += colSpan - 1;
+                }
+                col++;
+            }
+        }
+
+        /**
+         * Perform layout for the minor axis of the box (i.e. the
+         * axis orthoginal to the axis that it represents).  The results
+         * of the layout should be placed in the given arrays which represent
+         * the allocations to the children along the minor axis.  This
+         * is called by the superclass whenever the layout needs to be
+         * updated along the minor axis.
+         * <p>
+         * This is implemented to delegate to the superclass, then adjust
+         * the span for any cell that spans multiple rows.
+         *
+         * @param targetSpan the total span given to the view, which
+         *  whould be used to layout the children
+         * @param axis the axis being layed out
+         * @param offsets the offsets from the origin of the view for
+         *  each of the child views; this is a return value and is
+         *  filled in by the implementation of this method
+         * @param spans the span of each child view; this is a return
+         *  value and is filled in by the implementation of this method
+         * @return the offset and span for each child view in the
+         *  offsets and spans parameters
+         */
+        protected void layoutMinorAxis(int targetSpan, int axis, int[] offsets, int[] spans) {
+            super.layoutMinorAxis(targetSpan, axis, offsets, spans);
+            int col = 0;
+            int ncells = getViewCount();
+            for (int cell = 0; cell < ncells; cell++, col++) {
+                View cv = getView(cell);
+                for (; isFilled(col); col++); // advance to a free column
+                int colSpan = getColumnsOccupied(cv);
+                int rowSpan = getRowsOccupied(cv);
+                if (rowSpan > 1) {
+
+                    int row0 = rowIndex;
+                    int row1 = Math.min(rowIndex + rowSpan - 1, getRowCount()-1);
+                    spans[cell] = getMultiRowSpan(row0, row1);
+                }
+                if (colSpan > 1) {
+                    col += colSpan - 1;
+                }
+            }
+        }
+
+        /**
+         * Determines the resizability of the view along the
+         * given axis.  A value of 0 or less is not resizable.
+         *
+         * @param axis may be either View.X_AXIS or View.Y_AXIS
+         * @return the resize weight
+         * @exception IllegalArgumentException for an invalid axis
+         */
+        public int getResizeWeight(int axis) {
+            return 1;
+        }
+
+        /**
+         * Fetches the child view that represents the given position in
+         * the model.  This is implemented to walk through the children
+         * looking for a range that contains the given position.  In this
+         * view the children do not necessarily have a one to one mapping
+         * with the child elements.
+         *
+         * @param pos  the search position >= 0
+         * @param a  the allocation to the table on entry, and the
+         *   allocation of the view containing the position on exit
+         * @return  the view representing the given position, or
+         *   null if there isn't one
+         */
+        protected View getViewAtPosition(int pos, Rectangle a) {
+            int n = getViewCount();
+            for (int i = 0; i < n; i++) {
+                View v = getView(i);
+                int p0 = v.getStartOffset();
+                int p1 = v.getEndOffset();
+                if ((pos >= p0) && (pos < p1)) {
+                    // it's in this view.
+                    if (a != null) {
+                        childAllocation(i, a);
+                    }
+                    return v;
+                }
+            }
+            if (pos == getEndOffset()) {
+                View v = getView(n - 1);
+                if (a != null) {
+                    this.childAllocation(n - 1, a);
+                }
+                return v;
+            }
+            return null;
+        }
+
+        /**
+         * Update any cached values that come from attributes.
+         */
+        void setPropertiesFromAttributes() {
+            StyleSheet sheet = getStyleSheet();
+            attr = sheet.getViewAttributes(this);
+            painter = sheet.getBoxPainter(attr);
+        }
+
+        private StyleSheet.BoxPainter painter;
+        private AttributeSet attr;
+
+        /** columns filled by multi-column or multi-row cells */
+        BitSet fillColumns;
+
+        /**
+         * The row index within the overall grid
+         */
+        int rowIndex;
+
+        /**
+         * The view index (for row index to view index conversion).
+         * This is set by the updateGrid method.
+         */
+        int viewIndex;
+
+        /**
+         * Does this table row have cells that span multiple rows?
+         */
+        boolean multiRowCells;
+
+    }
+
+    /**
+     * Default view of an html table cell.  This needs to be moved
+     * somewhere else.
+     */
+    class CellView extends BlockView {
+
+        /**
+         * Constructs a TableCell for the given element.
+         *
+         * @param elem the element that this view is responsible for
+         */
+        public CellView(Element elem) {
+            super(elem, Y_AXIS);
+        }
+
+        /**
+         * Perform layout for the major axis of the box (i.e. the
+         * axis that it represents).  The results of the layout should
+         * be placed in the given arrays which represent the allocations
+         * to the children along the major axis.  This is called by the
+         * superclass to recalculate the positions of the child views
+         * when the layout might have changed.
+         * <p>
+         * This is implemented to delegate to the superclass to
+         * tile the children.  If the target span is greater than
+         * was needed, the offsets are adjusted to align the children
+         * (i.e. position according to the html valign attribute).
+         *
+         * @param targetSpan the total span given to the view, which
+         *  whould be used to layout the children
+         * @param axis the axis being layed out
+         * @param offsets the offsets from the origin of the view for
+         *  each of the child views; this is a return value and is
+         *  filled in by the implementation of this method
+         * @param spans the span of each child view; this is a return
+         *  value and is filled in by the implementation of this method
+         * @return the offset and span for each child view in the
+         *  offsets and spans parameters
+         */
+        protected void layoutMajorAxis(int targetSpan, int axis, int[] offsets, int[] spans) {
+            super.layoutMajorAxis(targetSpan, axis, offsets, spans);
+            // calculate usage
+            int used = 0;
+            int n = spans.length;
+            for (int i = 0; i < n; i++) {
+                used += spans[i];
+            }
+
+            // calculate adjustments
+            int adjust = 0;
+            if (used < targetSpan) {
+                // PENDING(prinz) change to use the css alignment.
+                String valign = (String) getElement().getAttributes().getAttribute(
+                    HTML.Attribute.VALIGN);
+                if (valign == null) {
+                    AttributeSet rowAttr = getElement().getParentElement().getAttributes();
+                    valign = (String) rowAttr.getAttribute(HTML.Attribute.VALIGN);
+                }
+                if ((valign == null) || valign.equals("middle")) {
+                    adjust = (targetSpan - used) / 2;
+                } else if (valign.equals("bottom")) {
+                    adjust = targetSpan - used;
+                }
+            }
+
+            // make adjustments.
+            if (adjust != 0) {
+                for (int i = 0; i < n; i++) {
+                    offsets[i] += adjust;
+                }
+            }
+        }
+
+        /**
+         * Calculate the requirements needed along the major axis.
+         * This is called by the superclass whenever the requirements
+         * need to be updated (i.e. a preferenceChanged was messaged
+         * through this view).
+         * <p>
+         * This is implemented to delegate to the superclass, but
+         * indicate the maximum size is very large (i.e. the cell
+         * is willing to expend to occupy the full height of the row).
+         *
+         * @param axis the axis being layed out.
+         * @param r the requirements to fill in.  If null, a new one
+         *  should be allocated.
+         */
+        protected SizeRequirements calculateMajorAxisRequirements(int axis,
+                                                                  SizeRequirements r) {
+            SizeRequirements req = super.calculateMajorAxisRequirements(axis, r);
+            req.maximum = Integer.MAX_VALUE;
+            return req;
+        }
+
+        @Override
+        protected SizeRequirements calculateMinorAxisRequirements(int axis, SizeRequirements r) {
+            SizeRequirements rv = super.calculateMinorAxisRequirements(axis, r);
+            //for the cell the minimum should be derived from the child views
+            //the parent behaviour is to use CSS for that
+            int n = getViewCount();
+            int min = 0;
+            for (int i = 0; i < n; i++) {
+                View v = getView(i);
+                min = Math.max((int) v.getMinimumSpan(axis), min);
+            }
+            rv.minimum = Math.min(rv.minimum, min);
+            return rv;
+        }
+    }
+
+
+}