Border collapsing implementation for tables

David Hyatt hyatt at apple.com
Sat Oct 18 01:01:24 CEST 2003


Index: khtml/css/cssparser.cpp
===================================================================
RCS file: /local/home/cvs/Labyrinth/WebCore/khtml/css/cssparser.cpp,v
retrieving revision 1.42
diff -u -p -r1.42 khtml/css/cssparser.cpp
--- khtml/css/cssparser.cpp	2003/10/01 23:27:11	1.42
+++ khtml/css/cssparser.cpp	2003/10/16 09:35:15
@@ -565,7 +565,7 @@ bool CSSParser::parseValue( int propId, 
     case CSS_PROP_BORDER_RIGHT_STYLE:   //   Defined as:    none | hidden | dotted | dashed |
     case CSS_PROP_BORDER_BOTTOM_STYLE:  //   solid | double | groove | ridge | inset | outset
     case CSS_PROP_BORDER_LEFT_STYLE:    ////
-	if (id >= CSS_VAL_NONE && id <= CSS_VAL_RIDGE)
+	if (id >= CSS_VAL_NONE && id <= CSS_VAL_DOUBLE)
 	    valid_primitive = true;
 	break;
 
Index: khtml/css/cssstyleselector.cpp
===================================================================
RCS file: /local/home/cvs/Labyrinth/WebCore/khtml/css/cssstyleselector.cpp,v
retrieving revision 1.97
diff -u -p -r1.97 khtml/css/cssstyleselector.cpp
--- khtml/css/cssstyleselector.cpp	2003/10/14 19:47:25	1.97
+++ khtml/css/cssstyleselector.cpp	2003/10/16 08:58:55
@@ -1762,7 +1762,21 @@ void CSSStyleSelector::applyRule( int id
     }
 
     case CSS_PROP_EMPTY_CELLS:
+    {
+        if (value->cssValueType() == CSSValue::CSS_INHERIT)
+        {
+            if(!parentNode) return;
+            style->setEmptyCells(parentStyle->emptyCells());
+            break;
+        }
+        if (!primitiveValue) break;
+        int id = primitiveValue->getIdent();
+        if (id == CSS_VAL_SHOW)
+            style->setEmptyCells(SHOW);
+        else if (id == CSS_VAL_HIDE)
+            style->setEmptyCells(HIDE);
         break;
+    }
     case CSS_PROP_FLOAT:
     {
         if(value->cssValueType() == CSSValue::CSS_INHERIT)

Index: khtml/css/cssvalues.in
===================================================================
RCS file: /local/home/cvs/Labyrinth/WebCore/khtml/css/cssvalues.in,v
retrieving revision 1.10
diff -u -p -r1.10 khtml/css/cssvalues.in
--- khtml/css/cssvalues.in	2003/10/01 23:27:11	1.10
+++ khtml/css/cssvalues.in	2003/10/16 09:35:22
@@ -17,14 +17,14 @@ inherit
 # CSS_PROP_BORDER_LEFT_STYLE
 none
 hidden
-dotted
-dashed
-double
-solid
-outset
 inset
 groove
 ridge
+outset
+dotted
+dashed
+solid
+double
 #
 # CSS_PROP_FONT:
 #

Index: khtml/rendering/bidi.cpp
===================================================================
RCS file: /local/home/cvs/Labyrinth/WebCore/khtml/rendering/bidi.cpp,v
retrieving revision 1.76
diff -u -p -r1.76 khtml/rendering/bidi.cpp
--- khtml/rendering/bidi.cpp	2003/10/07 15:49:30	1.76
+++ khtml/rendering/bidi.cpp	2003/10/16 08:58:58
@@ -1282,8 +1282,8 @@ void RenderBlock::layoutInlineChildren(b
 #if BIDI_DEBUG > 1 || defined( DEBUG_LINEBREAKS )
     kdDebug(6041) << " ------- bidi start " << this << " -------" << endl;
 #endif
-    int toAdd = style()->borderBottomWidth();
-    m_height = style()->borderTopWidth();
+    int toAdd = borderBottom();
+    m_height = borderTop();
 
     m_height += paddingTop();
     toAdd += paddingBottom();

Index: khtml/rendering/render_form.cpp
===================================================================
RCS file: /local/home/cvs/Labyrinth/WebCore/khtml/rendering/render_form.cpp,v
retrieving revision 1.65
diff -u -p -r1.65 khtml/rendering/render_form.cpp
--- khtml/rendering/render_form.cpp	2003/10/10 22:03:13	1.65
+++ khtml/rendering/render_form.cpp	2003/10/16 09:35:31
@@ -680,15 +680,15 @@ void RenderFieldset::paintBorderMinusLeg
 
     if(render_t) {
         drawBorder(p, _tx, _ty, _tx + lx, _ty +  style->borderTopWidth(), BSTop, tc, style->color(), ts,
-                   (render_l && ls<=DOUBLE?style->borderLeftWidth():0), 0);
+                   (render_l && (ls == DOTTED || ls == DASHED || ls == DOUBLE)?style->borderLeftWidth():0), 0);
         drawBorder(p, _tx+lx+lw, _ty, _tx + w, _ty +  style->borderTopWidth(), BSTop, tc, style->color(), ts,
-                   0, (render_r && rs<=DOUBLE?style->borderRightWidth():0));
+                   0, (render_r && (rs == DOTTED || rs == DASHED || rs == DOUBLE)?style->borderRightWidth():0));
     }
 
     if(render_b)
         drawBorder(p, _tx, _ty + h - style->borderBottomWidth(), _tx + w, _ty + h, BSBottom, bc, style->color(), bs,
-                   (render_l && ls<=DOUBLE?style->borderLeftWidth():0),
-                   (render_r && rs<=DOUBLE?style->borderRightWidth():0));
+                   (render_l && (ls == DOTTED || ls == DASHED || ls == DOUBLE)?style->borderLeftWidth():0),
+                   (render_r && (rs == DOTTED || rs == DASHED || rs == DOUBLE)?style->borderRightWidth():0));
 
     if(render_l)
     {
@@ -696,12 +696,12 @@ void RenderFieldset::paintBorderMinusLeg
 
         bool ignore_top =
             (tc == lc) &&
-            (ls <= OUTSET) &&
+            (ls >= OUTSET) &&
             (ts == DOTTED || ts == DASHED || ts == SOLID || ts == OUTSET);
 
         bool ignore_bottom =
             (bc == lc) &&
-            (ls <= OUTSET) &&
+            (ls >= OUTSET) &&
             (bs == DOTTED || bs == DASHED || bs == SOLID || bs == INSET);
 
         drawBorder(p, _tx, _ty, _tx + style->borderLeftWidth(), _ty + h, BSLeft, lc, style->color(), ls,
@@ -715,12 +715,12 @@ void RenderFieldset::paintBorderMinusLeg
 
         bool ignore_top =
             (tc == rc) &&
-            (rs <= SOLID || rs == INSET) &&
+            (rs >= DOTTED || rs == INSET) &&
             (ts == DOTTED || ts == DASHED || ts == SOLID || ts == OUTSET);
 
         bool ignore_bottom =
             (bc == rc) &&
-            (rs <= SOLID || rs == INSET) &&
+            (rs >= DOTTED || rs == INSET) &&
             (bs == DOTTED || bs == DASHED || bs == SOLID || bs == INSET);
 
         drawBorder(p, _tx + w - style->borderRightWidth(), _ty, _tx + w, _ty + h, BSRight, rc, style->color(), rs,
Index: khtml/rendering/render_object.cpp
===================================================================
RCS file: /local/home/cvs/Labyrinth/WebCore/khtml/rendering/render_object.cpp,v
retrieving revision 1.104
diff -u -p -r1.104 khtml/rendering/render_object.cpp
--- khtml/rendering/render_object.cpp	2003/10/10 22:03:14	1.104
+++ khtml/rendering/render_object.cpp	2003/10/16 09:35:33
@@ -811,12 +811,12 @@ void RenderObject::paintBorder(QPainter 
     if(render_t) {
         bool ignore_left =
             (tc == lc) && (tt == lt) &&
-            (ts <= OUTSET) &&
+            (ts >= OUTSET) &&
             (ls == DOTTED || ls == DASHED || ls == SOLID || ls == OUTSET);
 
         bool ignore_right =
             (tc == rc) && (tt == rt) &&
-            (ts <= OUTSET) &&
+            (ts >= OUTSET) &&
             (rs == DOTTED || rs == DASHED || rs == SOLID || rs == INSET);
         
         drawBorder(p, _tx, _ty, _tx + w, _ty +  style->borderTopWidth(), BSTop, tc, style->color(), ts,
@@ -827,12 +827,12 @@ void RenderObject::paintBorder(QPainter 
     if(render_b) {
         bool ignore_left =
         (bc == lc) && (bt == lt) &&
-        (bs <= OUTSET) &&
+        (bs >= OUTSET) &&
         (ls == DOTTED || ls == DASHED || ls == SOLID || ls == OUTSET);
 
         bool ignore_right =
             (bc == rc) && (bt == rt) &&
-            (bs <= OUTSET) &&
+            (bs >= OUTSET) &&
             (rs == DOTTED || rs == DASHED || rs == SOLID || rs == INSET);
         
         drawBorder(p, _tx, _ty + h - style->borderBottomWidth(), _tx + w, _ty + h, BSBottom, bc, style->color(), bs,
@@ -844,12 +844,12 @@ void RenderObject::paintBorder(QPainter 
     {
 	bool ignore_top =
 	  (tc == lc) && (tt == lt) &&
-	  (ls <= OUTSET) &&
+	  (ls >= OUTSET) &&
 	  (ts == DOTTED || ts == DASHED || ts == SOLID || ts == OUTSET);
 
 	bool ignore_bottom =
 	  (bc == lc) && (bt == lt) &&
-	  (ls <= OUTSET) &&
+	  (ls >= OUTSET) &&
 	  (bs == DOTTED || bs == DASHED || bs == SOLID || bs == INSET);
 
         drawBorder(p, _tx, _ty, _tx + style->borderLeftWidth(), _ty + h, BSLeft, lc, style->color(), ls,
@@ -861,12 +861,12 @@ void RenderObject::paintBorder(QPainter 
     {
 	bool ignore_top =
 	  (tc == rc) && (tt == rt) &&
-	  (rs <= SOLID || rs == INSET) &&
+	  (rs >= DOTTED || rs == INSET) &&
 	  (ts == DOTTED || ts == DASHED || ts == SOLID || ts == OUTSET);
 
 	bool ignore_bottom =
 	  (bc == rc) && (bt == rt) &&
-	  (rs <= SOLID || rs == INSET) &&
+	  (rs >= DOTTED || rs == INSET) &&
 	  (bs == DOTTED || bs == DASHED || bs == SOLID || bs == INSET);
 
         drawBorder(p, _tx + w - style->borderRightWidth(), _ty, _tx + w, _ty + h, BSRight, rc, style->color(), rs,
@@ -1791,6 +1791,12 @@ void RenderObject::updateWidgetPositions
 }
 #endif
 
+void RenderObject::collectBorders(QPtrList<CollapsedBorderValue>& borderStyles)
+{
+    for (RenderObject* curr = firstChild(); curr; curr = curr->nextSibling())
+        curr->collectBorders(borderStyles);
+}
+    
 QChar RenderObject::backslashAsCurrencySymbol() const
 {
 #if !APPLE_CHANGES

Index: khtml/rendering/render_object.h
===================================================================
RCS file: /local/home/cvs/Labyrinth/WebCore/khtml/rendering/render_object.h,v
retrieving revision 1.85
diff -u -p -r1.85 khtml/rendering/render_object.h
--- khtml/rendering/render_object.h	2003/10/10 22:03:14	1.85
+++ khtml/rendering/render_object.h	2003/10/16 08:59:06
@@ -71,7 +71,8 @@ typedef enum {
     PaintActionChildBackgrounds,
     PaintActionFloat,
     PaintActionForeground,
-    PaintActionSelection
+    PaintActionSelection,
+    PaintActionCollapsedTableBorders
 } PaintAction;
 
 typedef enum {
@@ -100,6 +101,7 @@ namespace khtml {
     class RenderLayer;
     class InlineBox;
     class InlineFlowBox;
+    class CollapsedBorderValue;
 
 /**
  * Base Class for all rendering tree objects.
@@ -577,6 +579,9 @@ public:
                     int adjbw1, int adjbw2, bool invalidisInvert = false);
 
     virtual void setTable(RenderTable*) {};
+
+    // Used by collapsed border tables.
+    virtual void collectBorders(QPtrList<CollapsedBorderValue>& borderStyles);
 
     // Repaint the entire object.  Called when, e.g., the color of a border changes, or when a border
     // style changes.

Index: khtml/rendering/render_style.h
===================================================================
RCS file: /local/home/cvs/Labyrinth/WebCore/khtml/rendering/render_style.h,v
retrieving revision 1.31
diff -u -p -r1.31 khtml/rendering/render_style.h
--- khtml/rendering/render_style.h	2003/10/10 23:22:10	1.31
+++ khtml/rendering/render_style.h	2003/10/16 08:59:09
@@ -184,10 +184,10 @@ enum EFloat {
 //------------------------------------------------
 // Border attributes. Not inherited.
 
-
+// These have been defined in the order of their precedence for border-collapsing. Do
+// not change this order!
 enum EBorderStyle {
-    BNONE, BHIDDEN, DOTTED, DASHED, DOUBLE, SOLID,
-    OUTSET, INSET, GROOVE, RIDGE
+    BNONE, BHIDDEN, INSET, GROOVE, RIDGE, OUTSET, DOTTED, DASHED, SOLID, DOUBLE
 };
 
@@ -222,6 +222,30 @@ public:
 
 };
 
+enum EBorderPrecedence { BOFF, BTABLE, BCOLGROUP, BCOL, BROWGROUP, BROW, BCELL };
+
+struct CollapsedBorderValue
+{
+    CollapsedBorderValue() :border(0), precedence(BOFF) {}
+    CollapsedBorderValue(const BorderValue* b, EBorderPrecedence p) :border(b), precedence(p) {}
+    
+    int width() const { return border ? border->width : 0; }
+    EBorderStyle style() const { return border ? border->style : BHIDDEN; }
+    bool exists() const { return border; }
+    QColor color() const { return border ? border->color : QColor(); }
+    bool isTransparent() const { return border ? border->transparent : true; }
+    
+    bool operator==(const CollapsedBorderValue& o) const
+    {
+        if (!border) return !o.border;
+        if (!o.border) return false;
+        return *border == *o.border && precedence == o.precedence;
+    }
+    
+    const BorderValue* border;
+    EBorderPrecedence precedence;    
+};
+
 class BorderData : public Shared<BorderData>
 {
 public:
 
@@ -799,6 +824,11 @@ public:
     Length  	minHeight() const { return box->min_height; }
     Length  	maxHeight() const { return box->max_height; }
 
+    const BorderValue& borderLeft() const { return surround->border.left; }
+    const BorderValue& borderRight() const { return surround->border.right; }
+    const BorderValue& borderTop() const { return surround->border.top; }
+    const BorderValue& borderBottom() const { return surround->border.bottom; }
+    
 
Index: khtml/rendering/render_table.cpp
===================================================================
RCS file: /local/home/cvs/Labyrinth/WebCore/khtml/rendering/render_table.cpp,v
retrieving revision 1.78
diff -u -p -r1.78 khtml/rendering/render_table.cpp
--- khtml/rendering/render_table.cpp	2003/10/10 23:22:10	1.78
+++ khtml/rendering/render_table.cpp	2003/10/16 08:59:11
@@ -55,11 +55,13 @@ RenderTable::RenderTable(DOM::NodeImpl* 
     tCaption = 0;
     head = foot = firstBody = 0;
     tableLayout = 0;
-
+    m_currentBorder = 0;
+    

@@ -105,6 +108,10 @@ void RenderTable::setStyle(RenderStyle *
 	} else
 	    tableLayout = new AutoTableLayout(this);
     }
+    
+    // The table never paints its border if it collapses.  It lets the cells do the painting.
+    if (collapseBorders())
+        setShouldPaintBackgroundOrBorder(false);
 }
 
 void RenderTable::addChild(RenderObject *child, RenderObject *beforeChild)
 
@@ -417,9 +434,8 @@ void RenderTable::paint( QPainter *p, in
 #endif
 
     if ((paintAction == PaintActionElementBackground || paintAction == PaintActionChildBackground)
-        && style()->visibility() == VISIBLE) {
+        && shouldPaintBackgroundOrBorder() && style()->visibility() == VISIBLE)
         paintBoxDecorations(p, _x, _y, _w, _h, _tx, _ty);
-    }
 
     // We're done.  We don't bother painting any children.
     if (paintAction == PaintActionElementBackground)
@@ -435,6 +451,24 @@ void RenderTable::paint( QPainter *p, in
 	child = child->nextSibling();
     }
 
+    if (collapseBorders() && 
+        (paintAction == PaintActionElementBackground || paintAction == PaintActionChildBackground)
+        && style()->visibility() == VISIBLE) {
+        // Collect all the unique border styles that we want to paint in a sorted list.  Once we
+        // have all the styles sorted, we then do individual passes, painting each style of border
+        // from lowest precedence to highest precedence.
+        QPtrList<CollapsedBorderValue> borderStyles;
+        borderStyles.setAutoDelete(true);
+        collectBorders(borderStyles);
+        for (uint i = 0; i < borderStyles.count(); i++) {
+            m_currentBorder = borderStyles.at(i);
+            for (child = firstChild(); child; child = child->nextSibling()) {
+                if (child->isTableSection())
+                    child->paint(p, _x, _y, _w, _h, _tx, _ty, PaintActionCollapsedTableBorders);
+            }
+        }
+    }
+        
 #ifdef BOX_DEBUG
     outlineBox(p, _tx, _ty, "blue");
 #endif
@@ -641,30 +675,70 @@ RenderObject* RenderTable::removeChildNo
     return RenderContainer::removeChildNode( child );
 }
 
-#if APPLE_CHANGES
-RenderTableCell* RenderTable::cellAbove(RenderTableCell* cell) const
+int RenderTable::borderLeft() const
 {
+    if (collapseBorders()) {
+        // FIXME: For strict mode, returning 0 is correct, since the table border half spills into the margin,
+        // but I'm working to get this changed.  For now, follow the spec.
+        return 0;
+    }
+    return RenderBlock::borderLeft();
+}
+    
+int RenderTable::borderRight() const
+{
+    if (collapseBorders()) {
+        // FIXME: For strict mode, returning 0 is correct, since the table border half spills into the margin,
+        // but I'm working to get this changed.  For now, follow the spec.
+        return 0;
+    }
+    return RenderBlock::borderRight();
+}
+
+int RenderTable::borderTop() const
+{
+    if (collapseBorders()) {
+        // FIXME: For strict mode, returning 0 is correct, since the table border half spills into the margin,
+        // but I'm working to get this changed.  For now, follow the spec.
+        return 0;
+    }
+    return RenderBlock::borderTop();
+}
+
+int RenderTable::borderBottom() const
+{
+    if (collapseBorders()) {
+        // FIXME: For strict mode, returning 0 is correct, since the table border half spills into the margin,
+        // but I'm working to get this changed.  For now, follow the spec.
+        return 0;
+    }
+    return RenderBlock::borderBottom();
+}
+
+RenderTableCell* RenderTable::cellAbove(const RenderTableCell* cell) const
+{
     // Find the section and row to look in
     int r = cell->row();
-    RenderTableSection *section;
-    int rAbove = 0;
+    RenderTableSection* section = 0;
+    int rAbove = -1;
     if (r > 0) {
         // cell is not in the first row, so use the above row in its own section
         section = cell->section();
         rAbove = r-1;
     } else {
         // cell is at top of a section, use last row in previous section
-        RenderObject *prevSection = cell->section()->previousSibling();
-        while (prevSection && !prevSection->isTableSection()) {
-            prevSection = prevSection->previousSibling();
-        }
-        section = static_cast<RenderTableSection *>(prevSection);
-        if (section) {
-            rAbove = section->numRows()-1;
+        for (RenderObject *prevSection = cell->section()->previousSibling();
+             prevSection && rAbove < 0;
+             prevSection = prevSection->previousSibling()) {
+            if (prevSection->isTableSection()) {
+                section = static_cast<RenderTableSection *>(prevSection);
+                if (section->numRows() > 0)
+                    rAbove = section->numRows()-1;
+            }
         }
     }
 
-    // Look up the cell in the section's grid, which required effective col index
+    // Look up the cell in the section's grid, which requires effective col index
     if (section && rAbove >= 0) {
         int effCol = colToEffCol(cell->col());
         RenderTableCell* aboveCell;
@@ -678,8 +752,70 @@ RenderTableCell* RenderTable::cellAbove(
         return 0;
     }
 }
-#endif
 
+RenderTableCell* RenderTable::cellBelow(const RenderTableCell* cell) const
+{
+    // Find the section and row to look in
+    int r = cell->row() + cell->rowSpan() - 1;
+    RenderTableSection* section = 0;
+    int rBelow = -1;
+    if (r < cell->section()->numRows()-1) {
+        // The cell is not in the last row, so use the next row in the section.
+        section = cell->section();
+        rBelow= r+1;
+    } else {
+        // The cell is at the bottom of a section. Use the first row in the next section.
+        for (RenderObject* nextSection = cell->section()->nextSibling();
+             nextSection && rBelow < 0;
+             nextSection = nextSection->nextSibling()) 
+        {
+            if (nextSection->isTableSection()) {
+                section = static_cast<RenderTableSection *>(nextSection);
+                if (section->numRows() > 0)
+                    rBelow = 0;
+            }
+        }
+    }
+    
+    // Look up the cell in the section's grid, which requires effective col index
+    if (section && rBelow >= 0) {
+        int effCol = colToEffCol(cell->col());
+        RenderTableCell* belowCell;
+        // If we hit a colspan back up to a real cell.
+        do {
+            belowCell = section->cellAt(rBelow, effCol);
+            effCol--;
+        } while (belowCell == (RenderTableCell *)-1 && effCol >=0);
+        return (belowCell == (RenderTableCell *)-1) ? 0 : belowCell;
+    } else {
+        return 0;
+    }    
+}
+
+RenderTableCell* RenderTable::cellLeft(const RenderTableCell* cell) const
+{
+    RenderTableSection* section = cell->section();
+    int effCol = colToEffCol(cell->col());
+    if (effCol == 0)
+        return 0;
+    
+    // If we hit a colspan back up to a real cell.
+    RenderTableCell* prevCell;
+    do {
+        prevCell = section->cellAt(cell->row(), effCol-1);
+        effCol--;
+    } while (prevCell == (RenderTableCell *)-1 && effCol >=0);
+    return (prevCell == (RenderTableCell *)-1) ? 0 : prevCell;
+}
+
+RenderTableCell* RenderTable::cellRight(const RenderTableCell* cell) const
+{
+    int effCol = colToEffCol(cell->col());
+    if (effCol == numEffCols()-1)
+        return 0;
+    return cell->section()->cellAt(cell->row(), effCol+1);
+}
+
 #ifndef NDEBUG
 void RenderTable::dump(QTextStream *stream, QString ind) const
 {

@@ -1568,6 +1705,313 @@ void RenderTableCell::setStyle( RenderSt
     }
 }
 
+// The following rules apply for resolving conflicts and figuring out which border
+// to use.
+// (1) Borders with the 'border-style' of 'hidden' take precedence over all other conflicting 
+// borders. Any border with this value suppresses all borders at this location.
+// (2) Borders with a style of 'none' have the lowest priority. Only if the border properties of all 
+// the elements meeting at this edge are 'none' will the border be omitted (but note that 'none' is 
+// the default value for the border style.)
+// (3) If none of the styles are 'hidden' and at least one of them is not 'none', then narrow borders 
+// are discarded in favor of wider ones. If several have the same 'border-width' then styles are preferred 
+// in this order: 'double', 'solid', 'dashed', 'dotted', 'ridge', 'outset', 'groove', and the lowest: 'inset'.
+// (4) If border styles differ only in color, then a style set on a cell wins over one on a row, 
+// which wins over a row group, column, column group and, lastly, table. It is undefined which color 
+// is used when two elements of the same type disagree.
+static const CollapsedBorderValue compareBorders(const CollapsedBorderValue& border1, 
+                                                 const CollapsedBorderValue& border2)
+{
+    // Sanity check the values passed in.  If either is null, return the other.
+    if (!border2.exists()) return border1;
+    if (!border1.exists()) return border2;
+    
+    // Rule #1 above.
+    if (border1.style() == BHIDDEN || border2.style() == BHIDDEN)
+        return CollapsedBorderValue(); // No border should exist at this location.
+    
+    // Rule #2 above.  A style of 'none' has lowest priority and always loses to any other border.
+    if (border2.style() == BNONE) return border1;
+    if (border1.style() == BNONE) return border2;
+    
+    // The first part of rule #3 above. Wider borders win.
+    if (border1.width() != border2.width())
+        return border1.width() > border2.width() ? border1 : border2;
+    
+    // The borders have equal width.  Sort by border style.
+    if (border1.style() != border2.style())
+        return border1.style() > border2.style() ? border1 : border2;
+    
+    // The border have the same width and style.  Rely on precedence (cell over row over row group, etc.)
+    return border1.precedence >= border2.precedence ? border1 : border2;
+}
+
+CollapsedBorderValue RenderTableCell::collapsedLeftBorder() const
+{
+    // For border left, we need to check, in order of precedence:
+    // (1) Our left border.
+    CollapsedBorderValue result(&style()->borderLeft(), BCELL);
+    
+    // (2) The previous cell's right border.
+    RenderTableCell* prevCell = table()->cellLeft(this);
+    if (prevCell) {
+        result = compareBorders(result, CollapsedBorderValue(&prevCell->style()->borderRight(), BCELL));
+        if (!result.exists()) return result;
+    }
+    else if (col() == 0) {
+        // (3) Our row's left border.
+        result = compareBorders(result, CollapsedBorderValue(&parent()->style()->borderLeft(), BROW));
+        if (!result.exists()) return result;
+        
+        // (4) Our row group's left border.
+        result = compareBorders(result, CollapsedBorderValue(&section()->style()->borderLeft(), BROWGROUP));
+        if (!result.exists()) return result;
+    }
+    
+    // (5) Our column's left border.
+    RenderTableCol* colElt = table()->colElement(col());
+    if (colElt) {
+        result = compareBorders(result, CollapsedBorderValue(&colElt->style()->borderLeft(), BCOL));
+        if (!result.exists()) return result;
+    }
+    
+    // (6) The previous column's right border.
+    if (col() > 0) {
+        colElt = table()->colElement(col()-1);
+        if (colElt) {
+            result = compareBorders(result, CollapsedBorderValue(&colElt->style()->borderRight(), BCOL));
+            if (!result.exists()) return result;
+        }
+    }
+    
+    if (col() == 0) {
+        // (7) The table's left border.
+        result = compareBorders(result, CollapsedBorderValue(&table()->style()->borderLeft(), BTABLE));
+        if (!result.exists()) return result;
+    }
+    
+    return result;
+}
+
+CollapsedBorderValue RenderTableCell::collapsedRightBorder() const
+{
+    RenderTable* tableElt = table();
+    bool inLastColumn = false;
+    int effCol = tableElt->colToEffCol(col());
+    if (effCol == tableElt->numEffCols()-1)
+        inLastColumn = true;
+    
+    // For border right, we need to check, in order of precedence:
+    // (1) Our right border.
+    CollapsedBorderValue result = CollapsedBorderValue(&style()->borderRight(), BCELL);
+    
+    // (2) The next cell's left border.
+    if (!inLastColumn) {
+        RenderTableCell* nextCell = tableElt->cellRight(this);
+        if (nextCell) {
+            result = compareBorders(result, CollapsedBorderValue(&nextCell->style()->borderLeft(), BCELL));
+            if (!result.exists()) return result;
+        }
+    }
+    else {
+        // (3) Our row's right border.
+        result = compareBorders(result, CollapsedBorderValue(&parent()->style()->borderRight(), BROW));
+        if (!result.exists()) return result;
+        
+        // (4) Our row group's right border.
+        result = compareBorders(result, CollapsedBorderValue(&section()->style()->borderRight(), BROWGROUP));
+        if (!result.exists()) return result;
+    }
+    
+    // (5) Our column's right border.
+    RenderTableCol* colElt = table()->colElement(col());
+    if (colElt) {
+        result = compareBorders(result, CollapsedBorderValue(&colElt->style()->borderRight(), BCOL));
+        if (!result.exists()) return result;
+    }
+    
+    // (6) The next column's left border.
+    if (!inLastColumn) {
+        colElt = tableElt->colElement(col()+1);
+        if (colElt) {
+            result = compareBorders(result, CollapsedBorderValue(&colElt->style()->borderLeft(), BCOL));
+            if (!result.exists()) return result;
+        }
+    }
+    else {
+        // (7) The table's right border.
+        result = compareBorders(result, CollapsedBorderValue(&tableElt->style()->borderRight(), BTABLE));
+        if (!result.exists()) return result;
+    }
+    
+    return result;
+}
+
+CollapsedBorderValue RenderTableCell::collapsedTopBorder() const
+{
+    // For border top, we need to check, in order of precedence:
+    // (1) Our top border.
+    CollapsedBorderValue result = CollapsedBorderValue(&style()->borderTop(), BCELL);
+    
+    RenderTableCell* prevCell = table()->cellAbove(this);
+    if (prevCell) {
+        // (2) A previous cell's bottom border.
+        result = compareBorders(result, CollapsedBorderValue(&prevCell->style()->borderBottom(), BCELL));
+        if (!result.exists()) return result;
+    }
+    
+    // (3) Our row's top border.
+    result = compareBorders(result, CollapsedBorderValue(&parent()->style()->borderTop(), BROW));
+    if (!result.exists()) return result;
+    
+    // (4) The previous row's bottom border.
+    if (prevCell) {
+        RenderObject* prevRow = 0;
+        if (prevCell->section() == section())
+            prevRow = parent()->previousSibling();
+        else
+            prevRow = prevCell->section()->lastChild();
+    
+        if (prevRow) {
+            result = compareBorders(result, CollapsedBorderValue(&prevRow->style()->borderBottom(), BROW));
+            if (!result.exists()) return result;
+        }
+    }
+    
+    // Now check row groups.
+    RenderObject* currSection = parent()->parent();
+    if (row() == 0) {
+        // (5) Our row group's top border.
+        result = compareBorders(result, CollapsedBorderValue(&currSection->style()->borderTop(), BROWGROUP));
+        if (!result.exists()) return result;
+        
+        // (6) Previous row group's bottom border.
+        for (currSection = currSection->previousSibling(); currSection;
+             currSection = currSection->previousSibling()) {
+            if (currSection->isTableSection()) {
+                RenderTableSection* section = static_cast<RenderTableSection*>(currSection);
+                result = compareBorders(result, CollapsedBorderValue(&section->style()->borderBottom(), BROWGROUP));
+                if (!result.exists()) return result;
+            }
+        }
+    }
+    
+    if (!currSection) {
+        // (8) Our column's top border.
+        RenderTableCol* colElt = table()->colElement(col());
+        if (colElt) {
+            result = compareBorders(result, CollapsedBorderValue(&colElt->style()->borderTop(), BCOL));
+            if (!result.exists()) return result;
+        }
+        
+        // (9) The table's top border.
+        result = compareBorders(result, CollapsedBorderValue(&table()->style()->borderTop(), BTABLE));
+        if (!result.exists()) return result;
+    }
+    
+    return result;
+}
+
+CollapsedBorderValue RenderTableCell::collapsedBottomBorder() const
+{
+    // For border top, we need to check, in order of precedence:
+    // (1) Our bottom border.
+    CollapsedBorderValue result = CollapsedBorderValue(&style()->borderBottom(), BCELL);
+    
+    RenderTableCell* nextCell = table()->cellBelow(this);
+    if (nextCell) {
+        // (2) A following cell's top border.
+        result = compareBorders(result, CollapsedBorderValue(&nextCell->style()->borderTop(), BCELL));
+        if (!result.exists()) return result;
+    }
+    
+    // (3) Our row's bottom border. (FIXME: Deal with rowspan!)
+    result = compareBorders(result, CollapsedBorderValue(&parent()->style()->borderBottom(), BROW));
+    if (!result.exists()) return result;
+    
+    // (4) The next row's top border.
+    if (nextCell) {
+        result = compareBorders(result, CollapsedBorderValue(&nextCell->parent()->style()->borderTop(), BROW));
+        if (!result.exists()) return result;
+    }
+    
+    // Now check row groups.
+    RenderObject* currSection = parent()->parent();
+    if (row()+rowSpan() >= static_cast<RenderTableSection*>(currSection)->numRows()) {
+        // (5) Our row group's bottom border.
+        result = compareBorders(result, CollapsedBorderValue(&currSection->style()->borderBottom(), BROWGROUP));
+        if (!result.exists()) return result;
+        
+        // (6) Following row group's top border.
+        for (currSection = currSection->nextSibling(); currSection;
+             currSection = currSection->nextSibling()) {
+            if (currSection->isTableSection()) {
+                RenderTableSection* section = static_cast<RenderTableSection*>(currSection);
+                result = compareBorders(result, CollapsedBorderValue(&section->style()->borderTop(), BROWGROUP));
+                if (!result.exists()) return result;
+            }
+        }
+    }
+    
+    if (!currSection) {
+        // (8) Our column's bottom border.
+        RenderTableCol* colElt = table()->colElement(col());
+        if (colElt) {
+            result = compareBorders(result, CollapsedBorderValue(&colElt->style()->borderBottom(), BCOL));
+            if (!result.exists()) return result;
+        }
+        
+        // (9) The table's bottom border.
+        result = compareBorders(result, CollapsedBorderValue(&table()->style()->borderBottom(), BTABLE));
+        if (!result.exists()) return result;
+    }
+    
+    return result;    
+}
+
+int RenderTableCell::borderLeft() const
+{
+    if (table()->collapseBorders()) {
+        CollapsedBorderValue border = collapsedLeftBorder();
+        if (border.exists())
+            return int(border.width()/2.0+0.5); // Give the extra pixel to top and left.
+        return 0;
+    }
+    return RenderBlock::borderLeft();
+}
+    
+int RenderTableCell::borderRight() const
+{
+    if (table()->collapseBorders()) {
+        CollapsedBorderValue border = collapsedRightBorder();
+        if (border.exists())
+            return border.width()/2;
+        return 0;
+    }
+    return RenderBlock::borderRight();
+}
+
+int RenderTableCell::borderTop() const
+{
+    if (table()->collapseBorders()) {
+        CollapsedBorderValue border = collapsedTopBorder();
+        if (border.exists())
+            return int(border.width()/2.0+0.5); // Give the extra pixel to top and left.
+        return 0;
+    }
+    return RenderBlock::borderTop();
+}
+
+int RenderTableCell::borderBottom() const
+{
+    if (table()->collapseBorders()) {
+        CollapsedBorderValue border = collapsedBottomBorder();
+        if (border.exists())
+            return border.width()/2;
+        return 0;
+    }
+    return RenderBlock::borderBottom();
+}
+
 #ifdef BOX_DEBUG
 #include <qpainter.h>
 
@@ -1603,10 +2047,168 @@ void RenderTableCell::paint(QPainter *p,
 #endif
 }
 
+static EBorderStyle collapsedBorderStyle(EBorderStyle style)
+{
+    if (style == OUTSET)
+        style = GROOVE;
+    else if (style == INSET)
+        style = RIDGE;
+    return style;
+}
+
+struct CollapsedBorder {
+    CollapsedBorder(){}
+    
+    CollapsedBorderValue border;
+    RenderObject::BorderSide side;
+    bool shouldPaint;
+    int x1;
+    int y1;
+    int x2;
+    int y2;
+    EBorderStyle style;
+};
+
+class CollapsedBorders
+{
+public:
+    CollapsedBorders(int i) :count(0) {}
+    
+    void addBorder(const CollapsedBorderValue& b, RenderObject::BorderSide s, bool paint, 
+                   int _x1, int _y1, int _x2, int _y2,
+                   EBorderStyle _style)
+    {
+        if (b.exists() && paint) {
+            borders[count].border = b;
+            borders[count].side = s;
+            borders[count].shouldPaint = paint;
+            borders[count].x1 = _x1;
+            borders[count].x2 = _x2;
+            borders[count].y1 = _y1;
+            borders[count].y2 = _y2;
+            borders[count].style = _style;
+            count++;
+        }
+    }
+
+    CollapsedBorder* nextBorder() {
+        for (int i = 0; i < count; i++) {
+            if (borders[i].border.exists() && borders[i].shouldPaint) {
+                borders[i].shouldPaint = false;
+                return &borders[i];
+            }
+        }
+        
+        return 0;
+    }
+    
+    CollapsedBorder borders[4];
+    int count;
+};
+
+static void addBorderStyle(QPtrList<CollapsedBorderValue>& borderStyles, CollapsedBorderValue borderValue)
+{
+    if (!borderValue.exists())
+        return;
+    
+    uint count = borderStyles.count();
+    if (count == 0)
+        borderStyles.append(new CollapsedBorderValue(borderValue));
+    else {
+        for (uint i = 0; i < count; i++) {
+            CollapsedBorderValue* b = borderStyles.at(i);
+            if (*b == borderValue)
+                return;
+            CollapsedBorderValue result = compareBorders(*b, borderValue);
+            if (result == *b) {
+                borderStyles.insert(i, new CollapsedBorderValue(borderValue));
+                return;
+            }
+        }
+        
+        borderStyles.append(new CollapsedBorderValue(borderValue));
+    }
+}
+
+void RenderTableCell::collectBorders(QPtrList<CollapsedBorderValue>& borderStyles)
+{
+    addBorderStyle(borderStyles, collapsedLeftBorder());
+    addBorderStyle(borderStyles, collapsedRightBorder());
+    addBorderStyle(borderStyles, collapsedTopBorder());
+    addBorderStyle(borderStyles, collapsedBottomBorder());
+}
+
+void RenderTableCell::paintCollapsedBorder(QPainter* p, int _tx, int _ty, int w, int h)
+{
+    if (!table()->currentBorderStyle())
+        return;
+    
+    CollapsedBorderValue leftVal = collapsedLeftBorder();
+    CollapsedBorderValue rightVal = collapsedRightBorder();
+    CollapsedBorderValue topVal = collapsedTopBorder();
+    CollapsedBorderValue bottomVal = collapsedBottomBorder();
+     
+    // Adjust our x/y/width/height so that we paint the collapsed borders at the correct location.
+    int topWidth = topVal.width();
+    int bottomWidth = bottomVal.width();
+    int leftWidth = leftVal.width();
+    int rightWidth = rightVal.width();
+    
+    _tx -= leftWidth/2;
+    _ty -= topWidth/2;
+    w += leftWidth/2 + int(rightWidth/2.0+0.5);
+    h += topWidth/2 + int(bottomWidth/2.0+0.5);
+    
+    bool tt = topVal.isTransparent();
+    bool bt = bottomVal.isTransparent();
+    bool rt = rightVal.isTransparent();
+    bool lt = leftVal.isTransparent();
+    
+    EBorderStyle ts = collapsedBorderStyle(topVal.style());
+    EBorderStyle bs = collapsedBorderStyle(bottomVal.style());
+    EBorderStyle ls = collapsedBorderStyle(leftVal.style());
+    EBorderStyle rs = collapsedBorderStyle(rightVal.style());
+    
+    bool render_t = ts > BHIDDEN && !tt;
+    bool render_l = ls > BHIDDEN && !lt;
+    bool render_r = rs > BHIDDEN && !rt;
+    bool render_b = bs > BHIDDEN && !bt;
+
+    // We never paint diagonals at the joins.  We simply let the border with the highest
+    // precedence paint on top of borders with lower precedence.  
+    CollapsedBorders borders(4);
+    borders.addBorder(topVal, BSTop, render_t, _tx, _ty, _tx + w, _ty + topWidth, ts);
+    borders.addBorder(bottomVal, BSBottom, render_b, _tx, _ty + h - bottomWidth, _tx + w, _ty + h, bs);
+    borders.addBorder(leftVal, BSLeft, render_l, _tx, _ty, _tx + leftWidth, _ty + h, ls);
+    borders.addBorder(rightVal, BSRight, render_r, _tx + w - rightWidth, _ty, _tx + w, _ty + h, rs);
+    
+    for (CollapsedBorder* border = borders.nextBorder(); border; border = borders.nextBorder()) {
+        if (border->border == *table()->currentBorderStyle())
+            drawBorder(p, border->x1, border->y1, border->x2, border->y2, border->side, 
+                       border->border.color(), style()->color(), border->style, 0, 0);
+    }
+}
+
+void RenderTableCell::paintObject(QPainter* p, int _x, int _y, int _w, int _h,
+                                  int _tx, int _ty, PaintAction paintAction)
+{
+    if (paintAction == PaintActionCollapsedTableBorders && style()->visibility() == VISIBLE) {
+        int w = width();
+        int h = height() + borderTopExtra() + borderBottomExtra();
+        _ty -= borderTopExtra();
+        paintCollapsedBorder(p, _tx, _ty, w, h);
+    }
+    else
+        RenderBlock::paintObject(p, _x, _y, _w, _h, _tx, _ty, paintAction);
+}
 
 void RenderTableCell::paintBoxDecorations(QPainter *p,int, int _y,
-                                       int, int _h, int _tx, int _ty)
+                                          int, int _h, int _tx, int _ty)
 {
+    RenderTable* tableElt = table();
+    if (!tableElt->collapseBorders() && style()->emptyCells() == HIDE && !firstChild())
+        return;
+    
     int w = width();
     int h = height() + borderTopExtra() + borderBottomExtra();
     _ty -= borderTopExtra();
@@ -1657,7 +2259,7 @@ void RenderTableCell::paintBoxDecoration
     if ( bg || c.isValid() )
 	paintBackground(p, c, bg, my, mh, _tx, _ty, w, h);
 
-    if(style()->hasBorder())
+    if (style()->hasBorder() && !tableElt->collapseBorders())
         paintBorder(p, _tx, _ty, w, h, style());
 }
 
Index: khtml/rendering/render_table.h
===================================================================
RCS file: /local/home/cvs/Labyrinth/WebCore/khtml/rendering/render_table.h,v
retrieving revision 1.28
diff -u -p -r1.28 khtml/rendering/render_table.h
--- khtml/rendering/render_table.h	2003/10/10 23:22:10	1.28
+++ khtml/rendering/render_table.h	2003/10/16 08:59:12
@@ -84,10 +84,15 @@ public:

+    int borderLeft() const;
+    int borderRight() const;
+    int borderTop() const;
+    int borderBottom() const;
+    
     Rules getRules() const { return rules; }
 
     const QColor &bgColor() const { return style()->backgroundColor(); }

@@ -160,10 +165,14 @@ public:
     void setNeedSectionRecalc() { needSectionRecalc = true; }
 
     virtual RenderObject* removeChildNode(RenderObject* child);
-#if APPLE_CHANGES
-    virtual RenderTableCell* cellAbove(RenderTableCell* cell) const;
-#endif
 
+    RenderTableCell* cellAbove(const RenderTableCell* cell) const;
+    RenderTableCell* cellBelow(const RenderTableCell* cell) const;
+    RenderTableCell* cellLeft(const RenderTableCell* cell) const;
+    RenderTableCell* cellRight(const RenderTableCell* cell) const;
+ 
+    CollapsedBorderValue* currentBorderStyle() { return m_currentBorder; }
+    
 protected:
 
     void recalcSections();
@@ -178,13 +187,17 @@ protected:
 
     TableLayout *tableLayout;
 
+    CollapsedBorderValue* m_currentBorder;
+    
     Frame frame                 : 4;
     Rules rules                 : 4;

@@ -326,6 +339,17 @@ public:
     virtual void setWidth( int width );
     virtual void setStyle( RenderStyle *style );
 
+    int borderLeft() const;
+    int borderRight() const;
+    int borderTop() const;
+    int borderBottom() const;
+
+    CollapsedBorderValue collapsedLeftBorder() const;
+    CollapsedBorderValue collapsedRightBorder() const;
+    CollapsedBorderValue collapsedTopBorder() const;
+    CollapsedBorderValue collapsedBottomBorder() const;
+    virtual void collectBorders(QPtrList<CollapsedBorderValue>& borderStyles);
+
     virtual void updateFromElement();
 
     virtual void layout();
@@ -339,6 +363,8 @@ public:
     virtual void paint( QPainter* p, int x, int y,
                         int w, int h, int tx, int ty, PaintAction paintAction);
 
+    void paintCollapsedBorder(QPainter* p, int x, int y, int w, int h);
+    
     virtual void close();
 
     // lie position to outside observers
@@ -359,6 +385,9 @@ public:
     virtual void dump(QTextStream *stream, QString ind = "") const;
 #endif
 
+    virtual void paintObject(QPainter *, int x, int y, int w, int h,
+                             int tx, int ty, PaintAction paintAction);
+    
 protected:
     virtual void paintBoxDecorations(QPainter *p,int _x, int _y,
                                      int _w, int _h, int _tx, int _ty);
-------------- next part --------------


dave


More information about the Khtml-devel mailing list