diff --git a/mozilla/layout/html/table/src/nsTableBorderCollapser.cpp b/mozilla/layout/html/table/src/nsTableBorderCollapser.cpp new file mode 100644 index 00000000000..7d9319ab0c8 --- /dev/null +++ b/mozilla/layout/html/table/src/nsTableBorderCollapser.cpp @@ -0,0 +1,872 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * The contents of this file are subject to the Netscape Public License + * Version 1.0 (the "NPL"); you may not use this file except in + * compliance with the NPL. You may obtain a copy of the NPL at + * http://www.mozilla.org/NPL/ + * + * Software distributed under the NPL is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the NPL + * for the specific language governing rights and limitations under the + * NPL. + * + * The Initial Developer of this code under the NPL is Netscape + * Communications Corporation. Portions created by Netscape are + * Copyright (C) 1998 Netscape Communications Corporation. All Rights + * Reserved. + */ + +#include "nsIPresContext.h" +#include "nsTableBorderCollapser.h" +#include "nsTableFrame.h" +#include "nsTableCellFrame.h" +#include "nsTableColFrame.h" +#include "nsCellMap.h" +#include "cellData.h" + +nsTableBorderCollapser::nsTableBorderCollapser(nsTableFrame& aTableFrame) +:mTableFrame(aTableFrame) +{ +} + +nsTableBorderCollapser::~nsTableBorderCollapser() +{ + for (PRInt32 sideX = 0; sideX < 4; sideX++) { + nsBorderEdge* border = (nsBorderEdge *)(mBorderEdges.mEdges[sideX].ElementAt(0)); + while (border) { + delete border; + mBorderEdges.mEdges[sideX].RemoveElementAt(0); + border = (nsBorderEdge *)(mBorderEdges.mEdges[sideX].ElementAt(0)); + } + } +} + +void nsTableBorderCollapser::SetBorderEdgeLength(PRUint8 aSide, + PRInt32 aIndex, + nscoord aLength) +{ + nsBorderEdge* border = (nsBorderEdge *)(mBorderEdges.mEdges[aSide].ElementAt(aIndex)); + if (border) { + border->mLength = aLength; + } +} + +void nsTableBorderCollapser::DidComputeHorizontalBorders(nsIPresContext& aPresContext, + PRInt32 aStartRowIndex, + PRInt32 aEndRowIndex) +{ + // XXX: for now, this only does table edges. May need to do interior edges also? Probably not. + PRInt32 lastRowIndex = mTableFrame.GetRowCount() - 1; + PRInt32 lastColIndex = mTableFrame.GetColCount() - 1; + if (0 == aStartRowIndex) { + nsTableCellFrame* cellFrame = mTableFrame.GetCellInfoAt(0, 0); + nsRect rowRect(0,0,0,0); + if (cellFrame) { + nsIFrame* rowFrame; + cellFrame->GetParent(&rowFrame); + rowFrame->GetRect(rowRect); + nsBorderEdge* leftBorder = (nsBorderEdge *)(mBorderEdges.mEdges[NS_SIDE_LEFT].ElementAt(0)); + nsBorderEdge* rightBorder = (nsBorderEdge *)(mBorderEdges.mEdges[NS_SIDE_RIGHT].ElementAt(0)); + nsBorderEdge* topBorder = (nsBorderEdge *)(mBorderEdges.mEdges[NS_SIDE_TOP].ElementAt(0)); + if (leftBorder) + leftBorder->mLength = rowRect.height + topBorder->mWidth; + if (topBorder) + topBorder = (nsBorderEdge *)(mBorderEdges.mEdges[NS_SIDE_TOP].ElementAt(lastColIndex)); + if (rightBorder) + rightBorder->mLength = rowRect.height + topBorder->mWidth; + } + } + + if (lastRowIndex <= aEndRowIndex) { + nsTableCellFrame* cellFrame = mTableFrame.GetCellInfoAt(lastRowIndex, 0); + nsRect rowRect(0,0,0,0); + if (cellFrame) { + nsIFrame* rowFrame; + cellFrame->GetParent(&rowFrame); + rowFrame->GetRect(rowRect); + nsBorderEdge* leftBorder = (nsBorderEdge *)(mBorderEdges.mEdges[NS_SIDE_LEFT].ElementAt(lastRowIndex)); + nsBorderEdge* rightBorder = (nsBorderEdge *)(mBorderEdges.mEdges[NS_SIDE_RIGHT].ElementAt(lastRowIndex)); + nsBorderEdge* bottomBorder = (nsBorderEdge *)(mBorderEdges.mEdges[NS_SIDE_BOTTOM].ElementAt(0)); + if (leftBorder) + leftBorder->mLength = rowRect.height + bottomBorder->mWidth; + if (bottomBorder) + bottomBorder = (nsBorderEdge *)(mBorderEdges.mEdges[NS_SIDE_BOTTOM].ElementAt(lastColIndex)); + if (rightBorder) + rightBorder->mLength = rowRect.height + bottomBorder->mWidth; + } + } + + for (PRInt32 borderX = NS_SIDE_TOP; borderX <= NS_SIDE_LEFT; borderX++) { + if (!mBorderEdges.mEdges[borderX].ElementAt(0)) { + mBorderEdges.mEdges[borderX].AppendElement(new nsBorderEdge()); + } + } +} + + + // For every row between aStartRowIndex and aEndRowIndex (-1 == the end of the table), + // walk across every edge and compute the border at that edge. + // We compute each edge only once, arbitrarily choosing to compute right and bottom edges, + // except for exterior cells that share a left or top edge with the table itself. + // Distribute half the computed border to the appropriate adjacent objects + // (always a cell frame or the table frame.) In the case of odd width, + // the object on the right/bottom gets the extra portion + +/* compute the top and bottom collapsed borders between aStartRowIndex and aEndRowIndex, inclusive */ +void nsTableBorderCollapser::ComputeHorizontalBorders(nsIPresContext& aPresContext, + PRInt32 aStartRowIndex, + PRInt32 aEndRowIndex) +{ + PRInt32 colCount = mTableFrame.GetColCount(); + PRInt32 rowCount = mTableFrame.GetRowCount(); + if (aStartRowIndex >= rowCount) { + return; // we don't have the requested row yet + } + + for (PRInt32 rowIndex = aStartRowIndex; (rowIndex < rowCount) && (rowIndex <= aEndRowIndex); rowIndex++) { + for (PRInt32 colIndex = 0; colIndex < colCount; colIndex++) { + if (0 == rowIndex) { // table is top neighbor + ComputeTopBorderForEdgeAt(aPresContext, rowIndex, colIndex); + } + ComputeBottomBorderForEdgeAt(aPresContext, rowIndex, colIndex); + } + } +} + +/* compute the left and right collapsed borders between aStartRowIndex and aEndRowIndex, inclusive */ +void nsTableBorderCollapser::ComputeVerticalBorders(nsIPresContext& aPresContext, + PRInt32 aStartRowIndex, + PRInt32 aEndRowIndex) +{ + //nsCellMap* cellMap = mTableFrame.GetCellMap(); + //if (nsnull == cellMap) + // return; // no info yet, so nothing useful to do + + mTableFrame.CacheColFrames(aPresContext); // XXX why here? + + // compute all the collapsing border values for the entire table + // XXX: we have to make this more incremental! + + PRInt32 colCount = mTableFrame.GetColCount(); + PRInt32 rowCount = mTableFrame.GetRowCount(); + PRInt32 endRowIndex = aEndRowIndex; + if (-1 == endRowIndex) + endRowIndex = rowCount-1; + if (aStartRowIndex >= rowCount) { + return; // we don't have the requested row yet + } + + for (PRInt32 rowX = aStartRowIndex; (rowX < rowCount) && (rowX <= endRowIndex); rowX++) { + for (PRInt32 colX = 0; colX < colCount; colX++) { + if (0 == colX) { // table is left neighbor + ComputeLeftBorderForEdgeAt(aPresContext, rowX, colX); + } + ComputeRightBorderForEdgeAt(aPresContext, rowX, colX); + } + } +} + +void nsTableBorderCollapser::ComputeLeftBorderForEdgeAt(nsIPresContext& aPresContext, + PRInt32 aRowIndex, + PRInt32 aColIndex) +{ + PRInt32 numSegments = mBorderEdges.mEdges[NS_SIDE_LEFT].Count(); + while (numSegments <= aRowIndex) { + nsBorderEdge* borderToAdd = new nsBorderEdge(); + mBorderEdges.mEdges[NS_SIDE_LEFT].AppendElement(borderToAdd); + numSegments++; + } + // "border" is the border segment we are going to set + nsBorderEdge *border = (nsBorderEdge *)(mBorderEdges.mEdges[NS_SIDE_LEFT].ElementAt(aRowIndex)); + if (!border) + return; + + // collect all the incident frames and compute the dominant border + nsVoidArray styles; + // styles are added to the array in the order least dominant -> most dominant + // 1. table + const nsStyleSpacing *spacing; + mTableFrame.GetStyleData(eStyleStruct_Spacing, ((const nsStyleStruct *&)spacing)); + styles.AppendElement((void*)spacing); + // 2. colgroup + nsTableColFrame* colFrame; + mTableFrame.GetColumnFrame(aColIndex, colFrame); + nsIFrame* colGroupFrame; + colFrame->GetParent(&colGroupFrame); + colGroupFrame->GetStyleData(eStyleStruct_Spacing, ((const nsStyleStruct *&)spacing)); + styles.AppendElement((void*)spacing); + // 3. col + colFrame->GetStyleData(eStyleStruct_Spacing, ((const nsStyleStruct *&)spacing)); + styles.AppendElement((void*)spacing); + // 4. rowgroup + nsTableCellFrame* cellFrame = mTableFrame.GetCellInfoAt(aRowIndex, aColIndex); + nsRect rowRect(0,0,0,0); + if (cellFrame) { + nsIFrame* rowFrame; + cellFrame->GetParent(&rowFrame); + rowFrame->GetRect(rowRect); + nsIFrame* rowGroupFrame; + rowFrame->GetParent(&rowGroupFrame); + rowGroupFrame->GetStyleData(eStyleStruct_Spacing, ((const nsStyleStruct *&)spacing)); + styles.AppendElement((void*)spacing); + // 5. row + rowFrame->GetStyleData(eStyleStruct_Spacing, ((const nsStyleStruct *&)spacing)); + styles.AppendElement((void*)spacing); + // 6. cell (need to do something smart for rowspanner with row frame) + cellFrame->GetStyleData(eStyleStruct_Spacing, ((const nsStyleStruct *&)spacing)); + styles.AppendElement((void*)spacing); + } + ComputeBorderSegment(NS_SIDE_LEFT, &styles, *border, PR_FALSE); + // now give half the computed border to the table segment, and half to the cell + // to avoid rounding errors, we convert up to pixels, divide by 2, and + // we give the odd pixel to the table border + float t2p; + aPresContext.GetTwipsToPixels(&t2p); + float p2t; + aPresContext.GetPixelsToTwips(&p2t); + nscoord widthAsPixels = NSToCoordRound((float)(border->mWidth)*t2p); + nscoord widthToAdd = 0; + border->mWidth = widthAsPixels/2; + if ((border->mWidth*2)!=widthAsPixels) + widthToAdd = NSToCoordCeil(p2t); + border->mWidth *= NSToCoordCeil(p2t); + border->mLength = rowRect.height; + border->mInsideNeighbor = cellFrame->mBorderEdges; + // we need to factor in the table's horizontal borders. + // but we can't compute that length here because we don't know how thick top and bottom borders are + // see DidComputeHorizontalCollapsingBorders + if (nsnull!=cellFrame) + { + cellFrame->SetBorderEdge(NS_SIDE_LEFT, aRowIndex, aColIndex, border, 0); // set the left edge of the cell frame + } + border->mWidth += widthToAdd; + mBorderEdges.mMaxBorderWidth.left = PR_MAX(border->mWidth, mBorderEdges.mMaxBorderWidth.left); +} + +void nsTableBorderCollapser::ComputeRightBorderForEdgeAt(nsIPresContext& aPresContext, + PRInt32 aRowIndex, + PRInt32 aColIndex) +{ + nsCellMap* cellMap = mTableFrame.GetCellMap(); + if (!cellMap) { + return; + } + + PRInt32 colCount = mTableFrame.GetColCount(); + PRInt32 numSegments = mBorderEdges.mEdges[NS_SIDE_RIGHT].Count(); + while (numSegments <= aRowIndex) { + nsBorderEdge* borderToAdd = new nsBorderEdge(); + mBorderEdges.mEdges[NS_SIDE_RIGHT].AppendElement(borderToAdd); + numSegments++; + } + // "border" is the border segment we are going to set + nsBorderEdge border; + + // collect all the incident frames and compute the dominant border + nsVoidArray styles; + // styles are added to the array in the order least dominant -> most dominant + // 1. table, only if this cell is in the right-most column and no rowspanning cell is + // to it's right. Otherwise, we remember what cell is the right neighbor + nsTableCellFrame* rightNeighborFrame=nsnull; + + if ((colCount - 1) != aColIndex) { + for (PRInt32 colIndex = aColIndex + 1; colIndex < colCount; colIndex++) { + CellData* cd = cellMap->GetCellAt(aRowIndex, colIndex); + if (cd) { // there's really a cell at (aRowIndex, colIndex) + if (!cd->mOrigCell) { // the cell at (aRowIndex, colIndex) is the result of a span + nsTableCellFrame* cell = nsnull; + if (cd->mRowSpanData) + cell = cd->mRowSpanData->mOrigCell; + else if (cd->mColSpanData) + cell = cd->mColSpanData->mOrigCell; + NS_ASSERTION(cell, "bad cell map state, missing real cell"); + PRInt32 realRowIndex; + cell->GetRowIndex (realRowIndex); + if (realRowIndex!=aRowIndex) { // the span is caused by a rowspan + rightNeighborFrame = cd->mRowSpanData->mOrigCell; + break; + } + } + else { + rightNeighborFrame = cd->mOrigCell; + break; + } + } + } + } + const nsStyleSpacing *spacing; + if (!rightNeighborFrame) { + // if rightNeighborFrame is null, our right neighbor is the table + mTableFrame.GetStyleData(eStyleStruct_Spacing, ((const nsStyleStruct *&)spacing)); + styles.AppendElement((void*)spacing); + } + // 2. colgroup //XXX: need to test if we're really on a colgroup border + nsTableColFrame* colFrame = cellMap->GetColumnFrame(aColIndex); + nsIFrame* colGroupFrame; + colFrame->GetParent(&colGroupFrame); + colGroupFrame->GetStyleData(eStyleStruct_Spacing, ((const nsStyleStruct *&)spacing)); + styles.AppendElement((void*)spacing); + // 3. col + colFrame->GetStyleData(eStyleStruct_Spacing, ((const nsStyleStruct *&)spacing)); + styles.AppendElement((void*)spacing); + // 4. rowgroup + nsTableCellFrame* cellFrame = cellMap->GetCellInfoAt(aRowIndex, aColIndex); + nsRect rowRect(0,0,0,0); + if (cellFrame) { + nsIFrame* rowFrame; + cellFrame->GetParent(&rowFrame); + rowFrame->GetRect(rowRect); + nsIFrame* rowGroupFrame; + rowFrame->GetParent(&rowGroupFrame); + if (!rightNeighborFrame) { + // if rightNeighborFrame is null, our right neighbor is the table so we include the rowgroup and row + rowGroupFrame->GetStyleData(eStyleStruct_Spacing, ((const nsStyleStruct *&)spacing)); + styles.AppendElement((void*)spacing); + // 5. row + rowFrame->GetStyleData(eStyleStruct_Spacing, ((const nsStyleStruct *&)spacing)); + styles.AppendElement((void*)spacing); + } + // 6. cell (need to do something smart for rowspanner with row frame) + cellFrame->GetStyleData(eStyleStruct_Spacing, ((const nsStyleStruct *&)spacing)); + styles.AppendElement((void*)spacing); + } + // 7. left edge of rightNeighborCell, if there is one + if (rightNeighborFrame) { + rightNeighborFrame->GetStyleData(eStyleStruct_Spacing, ((const nsStyleStruct *&)spacing)); + styles.AppendElement((void*)spacing); + } + ComputeBorderSegment(NS_SIDE_RIGHT, &styles, border, (nsnull != rightNeighborFrame)); + // now give half the computed border to each of the two neighbors + // (the 2 cells, or the cell and the table) + // to avoid rounding errors, we convert up to pixels, divide by 2, and + // we give the odd pixel to the right cell border + float t2p; + aPresContext.GetTwipsToPixels(&t2p); + float p2t; + aPresContext.GetPixelsToTwips(&p2t); + nscoord widthAsPixels = NSToCoordRound((float)(border.mWidth)*t2p); + nscoord widthToAdd = 0; + border.mWidth = widthAsPixels/2; + if ((border.mWidth*2) != widthAsPixels) + widthToAdd = NSToCoordCeil(p2t); + border.mWidth *= NSToCoordCeil(p2t); + border.mLength = rowRect.height; + if (cellFrame) { + cellFrame->SetBorderEdge(NS_SIDE_RIGHT, aRowIndex, aColIndex, &border, widthToAdd); + } + if (!rightNeighborFrame) { + nsBorderEdge * tableBorder = (nsBorderEdge *)(mBorderEdges.mEdges[NS_SIDE_RIGHT].ElementAt(aRowIndex)); + *tableBorder = border; + if (cellFrame) { + tableBorder->mInsideNeighbor = cellFrame->mBorderEdges; + } + mBorderEdges.mMaxBorderWidth.right = PR_MAX(border.mWidth, mBorderEdges.mMaxBorderWidth.right); + // since the table is our right neightbor, we need to factor in the table's horizontal borders. + // can't compute that length here because we don't know how thick top and bottom borders are + // see DidComputeHorizontalCollapsingBorders + } + else { + rightNeighborFrame->SetBorderEdge(NS_SIDE_LEFT, aRowIndex, aColIndex, &border, 0); + } +} + +void nsTableBorderCollapser::ComputeTopBorderForEdgeAt(nsIPresContext& aPresContext, + PRInt32 aRowIndex, + PRInt32 aColIndex) +{ + nsCellMap* cellMap = mTableFrame.GetCellMap(); + if (!cellMap) { + return; + } + + PRInt32 numSegments = mBorderEdges.mEdges[NS_SIDE_TOP].Count(); + while (numSegments<=aColIndex) { + nsBorderEdge* borderToAdd = new nsBorderEdge(); + mBorderEdges.mEdges[NS_SIDE_TOP].AppendElement(borderToAdd); + numSegments++; + } + // "border" is the border segment we are going to set + nsBorderEdge *border = (nsBorderEdge *)(mBorderEdges.mEdges[NS_SIDE_TOP].ElementAt(aColIndex)); + if (!border) + return; + + // collect all the incident frames and compute the dominant border + nsVoidArray styles; + // styles are added to the array in the order least dominant -> most dominant + // 1. table + const nsStyleSpacing *spacing; + mTableFrame.GetStyleData(eStyleStruct_Spacing, ((const nsStyleStruct *&)spacing)); + styles.AppendElement((void*)spacing); + // 2. colgroup + nsTableColFrame* colFrame = cellMap->GetColumnFrame(aColIndex); + nsIFrame* colGroupFrame; + colFrame->GetParent(&colGroupFrame); + colGroupFrame->GetStyleData(eStyleStruct_Spacing, ((const nsStyleStruct *&)spacing)); + styles.AppendElement((void*)spacing); + // 3. col + colFrame->GetStyleData(eStyleStruct_Spacing, ((const nsStyleStruct *&)spacing)); + styles.AppendElement((void*)spacing); + // 4. rowgroup + nsTableCellFrame* cellFrame = cellMap->GetCellInfoAt(aRowIndex, aColIndex); + if (cellFrame) { + nsIFrame* rowFrame; + cellFrame->GetParent(&rowFrame); + nsIFrame* rowGroupFrame; + rowFrame->GetParent(&rowGroupFrame); + rowGroupFrame->GetStyleData(eStyleStruct_Spacing, ((const nsStyleStruct *&)spacing)); + styles.AppendElement((void*)spacing); + // 5. row + rowFrame->GetStyleData(eStyleStruct_Spacing, ((const nsStyleStruct *&)spacing)); + styles.AppendElement((void*)spacing); + // 6. cell (need to do something smart for rowspanner with row frame) + cellFrame->GetStyleData(eStyleStruct_Spacing, ((const nsStyleStruct *&)spacing)); + styles.AppendElement((void*)spacing); + } + ComputeBorderSegment(NS_SIDE_TOP, &styles, *border, PR_FALSE); + // now give half the computed border to the table segment, and half to the cell + // to avoid rounding errors, we convert up to pixels, divide by 2, and + // we give the odd pixel to the right border + float t2p; + aPresContext.GetTwipsToPixels(&t2p); + float p2t; + aPresContext.GetPixelsToTwips(&p2t); + nscoord widthAsPixels = NSToCoordRound((float)(border->mWidth)*t2p); + nscoord widthToAdd = 0; + border->mWidth = widthAsPixels/2; + if ((border->mWidth*2) != widthAsPixels) + widthToAdd = NSToCoordCeil(p2t); + border->mWidth *= NSToCoordCeil(p2t); + border->mLength = mTableFrame.GetColumnWidth(aColIndex); + border->mInsideNeighbor = cellFrame->mBorderEdges; + if (0 == aColIndex) { + // if we're the first column, factor in the thickness of the left table border + nsBorderEdge *leftBorder = (nsBorderEdge *)(mBorderEdges.mEdges[NS_SIDE_LEFT].ElementAt(0)); + if (leftBorder) + border->mLength += leftBorder->mWidth; + } + if ((cellMap->GetColCount() - 1) == aColIndex) { + // if we're the last column, factor in the thickness of the right table border + nsBorderEdge* rightBorder = (nsBorderEdge *)(mBorderEdges.mEdges[NS_SIDE_RIGHT].ElementAt(0)); + if (rightBorder) + border->mLength += rightBorder->mWidth; + } + if (cellFrame) { + cellFrame->SetBorderEdge(NS_SIDE_TOP, aRowIndex, aColIndex, border, 0); // set the top edge of the cell frame + } + border->mWidth += widthToAdd; + mBorderEdges.mMaxBorderWidth.top = PR_MAX(border->mWidth, mBorderEdges.mMaxBorderWidth.top); +} + +void nsTableBorderCollapser::ComputeBottomBorderForEdgeAt(nsIPresContext& aPresContext, + PRInt32 aRowIndex, + PRInt32 aColIndex) +{ + nsCellMap* cellMap = mTableFrame.GetCellMap(); + if (!cellMap) { + return; + } + + PRInt32 rowCount = cellMap->GetRowCount(); + PRInt32 numSegments = mBorderEdges.mEdges[NS_SIDE_BOTTOM].Count(); + while (numSegments <= aColIndex) { + nsBorderEdge* borderToAdd = new nsBorderEdge(); + mBorderEdges.mEdges[NS_SIDE_BOTTOM].AppendElement(borderToAdd); + numSegments++; + } + // "border" is the border segment we are going to set + nsBorderEdge border; + + // collect all the incident frames and compute the dominant border + nsVoidArray styles; + // styles are added to the array in the order least dominant -> most dominant + // 1. table, only if this cell is in the bottom-most row and no colspanning cell is + // beneath it. Otherwise, we remember what cell is the bottom neighbor + nsTableCellFrame* bottomNeighborFrame=nsnull; + if ((rowCount-1) != aRowIndex) { + for (PRInt32 rowIndex = aRowIndex + 1; rowIndex < rowCount; rowIndex++) { + CellData* cd = cellMap->GetCellAt(rowIndex, aColIndex); + if (cd) { // there's really a cell at (rowIndex, aColIndex) + if (!cd->mOrigCell) { + // the cell at (rowIndex, aColIndex) is the result of a span + nsTableCellFrame* cell = nsnull; + if (cd->mRowSpanData) // XXX should we check for a row span + cell = cd->mRowSpanData->mOrigCell; + else if (cd->mColSpanData) + cell = cd->mColSpanData->mOrigCell; + NS_ASSERTION( cell, "bad cell map state, missing real cell"); + PRInt32 realColIndex; + cell->GetColIndex (realColIndex); + if (realColIndex!=aColIndex) { // the span is caused by a colspan + bottomNeighborFrame = cd->mColSpanData->mOrigCell; + break; + } + } + else { + bottomNeighborFrame = cd->mOrigCell; + break; + } + } + } + } + const nsStyleSpacing *spacing; + if (!bottomNeighborFrame) { + // if bottomNeighborFrame is null, our bottom neighbor is the table + mTableFrame.GetStyleData(eStyleStruct_Spacing, ((const nsStyleStruct *&)spacing)); + styles.AppendElement((void*)spacing); + + // 2. colgroup // XXX: need to deterine if we're on a colgroup boundary + nsTableColFrame* colFrame = cellMap->GetColumnFrame(aColIndex); + nsIFrame* colGroupFrame; + colFrame->GetParent(&colGroupFrame); + colGroupFrame->GetStyleData(eStyleStruct_Spacing, ((const nsStyleStruct *&)spacing)); + styles.AppendElement((void*)spacing); + // 3. col + colFrame->GetStyleData(eStyleStruct_Spacing, ((const nsStyleStruct *&)spacing)); + styles.AppendElement((void*)spacing); + } + // 4. rowgroup // XXX: use rowgroup only if we're on a table edge + nsTableCellFrame* cellFrame = cellMap->GetCellInfoAt(aRowIndex, aColIndex); + nsRect rowRect(0,0,0,0); + if (cellFrame) { + nsIFrame* rowFrame; + cellFrame->GetParent(&rowFrame); + rowFrame->GetRect(rowRect); + nsIFrame* rowGroupFrame; + rowFrame->GetParent(&rowGroupFrame); + rowGroupFrame->GetStyleData(eStyleStruct_Spacing, ((const nsStyleStruct *&)spacing)); + styles.AppendElement((void*)spacing); + // 5. row + rowFrame->GetStyleData(eStyleStruct_Spacing, ((const nsStyleStruct *&)spacing)); + styles.AppendElement((void*)spacing); + // 6. cell (need to do something smart for rowspanner with row frame) + cellFrame->GetStyleData(eStyleStruct_Spacing, ((const nsStyleStruct *&)spacing)); + styles.AppendElement((void*)spacing); + } + // 7. top edge of bottomNeighborCell, if there is one + if (bottomNeighborFrame) { + bottomNeighborFrame->GetStyleData(eStyleStruct_Spacing, ((const nsStyleStruct *&)spacing)); + styles.AppendElement((void*)spacing); + } + ComputeBorderSegment(NS_SIDE_BOTTOM, &styles, border, (nsnull != bottomNeighborFrame)); + // now give half the computed border to each of the two neighbors + // (the 2 cells, or the cell and the table) + // to avoid rounding errors, we convert up to pixels, divide by 2, and + // we give the odd pixel to the right cell border + float t2p; + aPresContext.GetTwipsToPixels(&t2p); + float p2t; + aPresContext.GetPixelsToTwips(&p2t); + nscoord widthAsPixels = NSToCoordRound((float)(border.mWidth)*t2p); + nscoord widthToAdd = 0; + border.mWidth = widthAsPixels/2; + if ((border.mWidth*2) != widthAsPixels) + widthToAdd = NSToCoordCeil(p2t); + border.mWidth *= NSToCoordCeil(p2t); + border.mLength = mTableFrame.GetColumnWidth(aColIndex); + if (cellFrame) { + cellFrame->SetBorderEdge(NS_SIDE_BOTTOM, aRowIndex, aColIndex, &border, widthToAdd); + } + if (!bottomNeighborFrame) { + nsBorderEdge* tableBorder = (nsBorderEdge *)(mBorderEdges.mEdges[NS_SIDE_BOTTOM].ElementAt(aColIndex)); + *tableBorder = border; + if (cellFrame) { + tableBorder->mInsideNeighbor = cellFrame->mBorderEdges; + } + mBorderEdges.mMaxBorderWidth.bottom = PR_MAX(border.mWidth, mBorderEdges.mMaxBorderWidth.bottom); + // since the table is our bottom neightbor, we need to factor in the table's vertical borders. + PRInt32 lastColIndex = cellMap->GetColCount()-1; + if (0 == aColIndex) { + // if we're the first column, factor in the thickness of the left table border + nsBorderEdge *leftBorder = (nsBorderEdge *)(mBorderEdges.mEdges[NS_SIDE_LEFT].ElementAt(rowCount-1)); + if (leftBorder) + tableBorder->mLength += leftBorder->mWidth; + } + if (lastColIndex == aColIndex) { + // if we're the last column, factor in the thickness of the right table border + nsBorderEdge *rightBorder = (nsBorderEdge *)(mBorderEdges.mEdges[NS_SIDE_RIGHT].ElementAt(rowCount-1)); + if (rightBorder) + tableBorder->mLength += rightBorder->mWidth; + } + } + else + { + bottomNeighborFrame->SetBorderEdge(NS_SIDE_TOP, aRowIndex, aColIndex, &border, 0); + } +} + +nscoord nsTableBorderCollapser::GetWidthForSide(const nsMargin& aBorder, + PRUint8 aSide) +{ + if (NS_SIDE_LEFT == aSide) + return aBorder.left; + else if (NS_SIDE_RIGHT == aSide) + return aBorder.right; + else if (NS_SIDE_TOP == aSide) + return aBorder.top; + else + return aBorder.bottom; +} + +/* Given an Edge, find the opposing edge (top<-->bottom, left<-->right) */ +PRUint8 nsTableBorderCollapser::GetOpposingEdge(PRUint8 aEdge) +{ + PRUint8 result; + switch (aEdge) + { + case NS_SIDE_LEFT: + result = NS_SIDE_RIGHT; break; + case NS_SIDE_RIGHT: + result = NS_SIDE_LEFT; break; + case NS_SIDE_TOP: + result = NS_SIDE_BOTTOM; break; + case NS_SIDE_BOTTOM: + result = NS_SIDE_TOP; break; + default: + result = NS_SIDE_TOP; + } + return result; +} + +/* returns BORDER_PRECEDENT_LOWER if aStyle1 is lower precedent that aStyle2 + * BORDER_PRECEDENT_HIGHER if aStyle1 is higher precedent that aStyle2 + * BORDER_PRECEDENT_EQUAL if aStyle1 and aStyle2 have the same precedence + * (note, this is not necessarily the same as saying aStyle1==aStyle2) + * this is a method on nsTableFrame because other objects might define their + * own border precedence rules. + */ +PRUint8 nsTableBorderCollapser::CompareBorderStyles(PRUint8 aStyle1, + PRUint8 aStyle2) +{ + PRUint8 result=BORDER_PRECEDENT_HIGHER; // if we get illegal types for table borders, HIGHER is the default + if (aStyle1 == aStyle2) + result = BORDER_PRECEDENT_EQUAL; + else if (NS_STYLE_BORDER_STYLE_HIDDEN==aStyle1) + result = BORDER_PRECEDENT_HIGHER; + else if (NS_STYLE_BORDER_STYLE_NONE==aStyle1) + result = BORDER_PRECEDENT_LOWER; + else if (NS_STYLE_BORDER_STYLE_NONE==aStyle2) + result = BORDER_PRECEDENT_HIGHER; + else if (NS_STYLE_BORDER_STYLE_HIDDEN==aStyle2) + result = BORDER_PRECEDENT_LOWER; + else { + switch (aStyle1) { + case NS_STYLE_BORDER_STYLE_BG_INSET: + result = BORDER_PRECEDENT_LOWER; + break; + + case NS_STYLE_BORDER_STYLE_GROOVE: + if (NS_STYLE_BORDER_STYLE_BG_INSET==aStyle2) + result = BORDER_PRECEDENT_HIGHER; + else + result = BORDER_PRECEDENT_LOWER; + break; + + case NS_STYLE_BORDER_STYLE_BG_OUTSET: + if (NS_STYLE_BORDER_STYLE_BG_INSET==aStyle2 || + NS_STYLE_BORDER_STYLE_GROOVE==aStyle2) + result = BORDER_PRECEDENT_HIGHER; + else + result = BORDER_PRECEDENT_LOWER; + break; + + case NS_STYLE_BORDER_STYLE_RIDGE: + if (NS_STYLE_BORDER_STYLE_BG_INSET==aStyle2 || + NS_STYLE_BORDER_STYLE_GROOVE==aStyle2 || + NS_STYLE_BORDER_STYLE_BG_OUTSET==aStyle2) + result = BORDER_PRECEDENT_HIGHER; + else + result = BORDER_PRECEDENT_LOWER; + break; + + case NS_STYLE_BORDER_STYLE_DOTTED: + if (NS_STYLE_BORDER_STYLE_BG_INSET==aStyle2 || + NS_STYLE_BORDER_STYLE_GROOVE==aStyle2 || + NS_STYLE_BORDER_STYLE_BG_OUTSET==aStyle2 || + NS_STYLE_BORDER_STYLE_RIDGE==aStyle2) + result = BORDER_PRECEDENT_HIGHER; + else + result = BORDER_PRECEDENT_LOWER; + break; + + case NS_STYLE_BORDER_STYLE_DASHED: + if (NS_STYLE_BORDER_STYLE_BG_INSET==aStyle2 || + NS_STYLE_BORDER_STYLE_GROOVE==aStyle2 || + NS_STYLE_BORDER_STYLE_BG_OUTSET==aStyle2 || + NS_STYLE_BORDER_STYLE_RIDGE==aStyle2 || + NS_STYLE_BORDER_STYLE_DOTTED==aStyle2) + result = BORDER_PRECEDENT_HIGHER; + else + result = BORDER_PRECEDENT_LOWER; + break; + + case NS_STYLE_BORDER_STYLE_SOLID: + if (NS_STYLE_BORDER_STYLE_BG_INSET==aStyle2 || + NS_STYLE_BORDER_STYLE_GROOVE==aStyle2 || + NS_STYLE_BORDER_STYLE_BG_OUTSET==aStyle2 || + NS_STYLE_BORDER_STYLE_RIDGE==aStyle2 || + NS_STYLE_BORDER_STYLE_DOTTED==aStyle2 || + NS_STYLE_BORDER_STYLE_DASHED==aStyle2) + result = BORDER_PRECEDENT_HIGHER; + else + result = BORDER_PRECEDENT_LOWER; + break; + + case NS_STYLE_BORDER_STYLE_DOUBLE: + result = BORDER_PRECEDENT_LOWER; + break; + } + } + return result; +} + +/* + This method is the CSS2 border conflict resolution algorithm + The spec says to resolve conflicts in this order: + 1. any border with the style HIDDEN wins + 2. the widest border with a style that is not NONE wins + 3. the border styles are ranked in this order, highest to lowest precedence: + double, solid, dashed, dotted, ridge, outset, groove, inset + 4. borders that are of equal width and style (differ only in color) have this precedence: + cell, row, rowgroup, col, colgroup, table + 5. if all border styles are NONE, then that's the computed border style. + This method assumes that the styles were added to aStyles in the reverse precedence order + of their frame type, so that styles that come later in the list win over style + earlier in the list if the tie-breaker gets down to #4. + This method sets the out-param aBorder with the resolved border attributes +*/ +void nsTableBorderCollapser::ComputeBorderSegment(PRUint8 aSide, + nsVoidArray* aStyles, + nsBorderEdge &aBorder, + PRBool aFlipLastSide) +{ + if (aStyles) { + PRInt32 styleCount = aStyles->Count(); + if (0 != styleCount) { + nsVoidArray sameWidthBorders; + nsStyleSpacing * spacing; + nsStyleSpacing * lastSpacing=nsnull; + nsMargin border; + PRInt32 maxWidth=0; + PRUint8 side = aSide; + PRInt32 i; + for (i = 0; i < styleCount; i++) { + spacing = (nsStyleSpacing *)(aStyles->ElementAt(i)); + if (aFlipLastSide && (i == styleCount-1)) { + side = GetOpposingEdge(aSide); + lastSpacing = spacing; + } + if (spacing->GetBorderStyle(side) == NS_STYLE_BORDER_STYLE_HIDDEN) { + aBorder.mStyle=NS_STYLE_BORDER_STYLE_HIDDEN; + aBorder.mWidth=0; + return; + } + else if (spacing->GetBorderStyle(side)!=NS_STYLE_BORDER_STYLE_NONE) { + spacing->GetBorder(border); + nscoord borderWidth = GetWidthForSide(border, side); + if (borderWidth == maxWidth) + sameWidthBorders.AppendElement(spacing); + else if (borderWidth > maxWidth) { + maxWidth = borderWidth; + sameWidthBorders.Clear(); + sameWidthBorders.AppendElement(spacing); + } + } + } + aBorder.mWidth = maxWidth; + // now we've gone through each overlapping border once, and we have a list + // of all styles with the same width. If there's more than one, resolve the + // conflict based on border style + styleCount = sameWidthBorders.Count(); + if (0 == styleCount) { // all borders were of style NONE + aBorder.mWidth=0; + aBorder.mStyle=NS_STYLE_BORDER_STYLE_NONE; + return; + } + else if (1 == styleCount) { // there was just one border of the largest width + spacing = (nsStyleSpacing *)(sameWidthBorders.ElementAt(0)); + side = aSide; + if (spacing == lastSpacing) + side = GetOpposingEdge(aSide); + if (!spacing->GetBorderColor(side, aBorder.mColor)) { + // XXX EEEK handle transparent border color somehow... + } + aBorder.mStyle = spacing->GetBorderStyle(side); + return; + } + else { + nsStyleSpacing* winningStyleBorder; + PRUint8 winningStyle=NS_STYLE_BORDER_STYLE_NONE; + for (i = 0; i < styleCount; i++) { + spacing = (nsStyleSpacing *)(sameWidthBorders.ElementAt(i)); + side = aSide; + if (spacing == lastSpacing) + side = GetOpposingEdge(aSide); + PRUint8 thisStyle = spacing->GetBorderStyle(side); + PRUint8 borderCompare = CompareBorderStyles(thisStyle, winningStyle); + if (BORDER_PRECEDENT_HIGHER == borderCompare) { + winningStyle = thisStyle; + winningStyleBorder = spacing; + } + else if (BORDER_PRECEDENT_EQUAL == borderCompare) { + // we're in lowest-to-highest precedence order, so later border styles win + winningStyleBorder=spacing; + } + } + aBorder.mStyle = winningStyle; + side = aSide; + if (winningStyleBorder == lastSpacing) + side = GetOpposingEdge(aSide); + if (!winningStyleBorder->GetBorderColor(side, aBorder.mColor)) { + // XXX handle transparent border colors somehow + } + } + } + } + +} + +void nsTableBorderCollapser::GetBorder(nsMargin& aBorder) +{ + aBorder = mBorderEdges.mMaxBorderWidth; +} + +void nsTableBorderCollapser::GetBorderAt(PRInt32 aRowIndex, + PRInt32 aColIndex, + nsMargin& aBorder) +{ + nsBorderEdge* border = (nsBorderEdge *)(mBorderEdges.mEdges[NS_SIDE_LEFT].ElementAt(aRowIndex)); + if (border) { + aBorder.left = border->mWidth; + } + border = (nsBorderEdge *)(mBorderEdges.mEdges[NS_SIDE_RIGHT].ElementAt(aRowIndex)); + if (border) { + aBorder.right = border->mWidth; + } + border = (nsBorderEdge *)(mBorderEdges.mEdges[NS_SIDE_TOP].ElementAt(aColIndex)); + if (border) { + aBorder.top = border->mWidth; + } + border = (nsBorderEdge *)(mBorderEdges.mEdges[NS_SIDE_BOTTOM].ElementAt(aColIndex)); + if (border) { + aBorder.bottom = border->mWidth; + } +} + +void nsTableBorderCollapser::GetMaxBorder(PRInt32 aStartRowIndex, + PRInt32 aEndRowIndex, + PRInt32 aStartColIndex, + PRInt32 aEndColIndex, + nsMargin aBorder) +{ + aBorder.top = aBorder.right = aBorder.bottom = aBorder.left = 0; + for (PRInt32 rowX = aStartRowIndex; rowX <= aEndRowIndex; rowX++) { + for (PRInt32 colX = aStartColIndex; colX <= aEndColIndex; colX++) { + nsMargin border; + GetBorderAt(rowX, colX, border); + aBorder.top = PR_MAX(aBorder.top, border.top); + aBorder.right = PR_MAX(aBorder.right, border.right); + aBorder.bottom = PR_MAX(aBorder.bottom, border.bottom); + aBorder.left = PR_MAX(aBorder.left, border.left); + } + } +} diff --git a/mozilla/layout/html/table/src/nsTableBorderCollapser.h b/mozilla/layout/html/table/src/nsTableBorderCollapser.h new file mode 100644 index 00000000000..0c460d9e65a --- /dev/null +++ b/mozilla/layout/html/table/src/nsTableBorderCollapser.h @@ -0,0 +1,158 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * The contents of this file are subject to the Netscape Public License + * Version 1.0 (the "NPL"); you may not use this file except in + * compliance with the NPL. You may obtain a copy of the NPL at + * http://www.mozilla.org/NPL/ + * + * Software distributed under the NPL is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the NPL + * for the specific language governing rights and limitations under the + * NPL. + * + * The Initial Developer of this code under the NPL is Netscape + * Communications Corporation. Portions created by Netscape are + * Copyright (C) 1998 Netscape Communications Corporation. All Rights + * Reserved. + */ +#ifndef nsTableBorderCollapser_h__ +#define nsTableBorderCollapser_h__ + +#include "nsIStyleContext.h" +class nsTableFrame; + +/** + * Class for handling CSS border-style:collapse + */ +class nsTableBorderCollapser +{ +public: + nsTableBorderCollapser(nsTableFrame& aTableFrame); + + ~nsTableBorderCollapser(); + + /** notification that top and bottom borders have been computed */ + void DidComputeHorizontalBorders(nsIPresContext& aPresContext, + PRInt32 aStartRowIndex, + PRInt32 aEndRowIndex); + + /** compute the left and right collapsed borders between aStartRowIndex and aEndRowIndex, inclusive */ + void ComputeVerticalBorders(nsIPresContext& aPresContext, + PRInt32 aStartRowIndex, + PRInt32 aEndRowIndex); + + /** compute the top and bottom collapsed borders between aStartRowIndex and aEndRowIndex, inclusive */ + void ComputeHorizontalBorders(nsIPresContext& aPresContext, + PRInt32 aStartRowIndex, + PRInt32 aEndRowIndex); + + /** compute the left borders for the table objects intersecting at (aRowIndex, aColIndex) */ + void ComputeLeftBorderForEdgeAt(nsIPresContext& aPresContext, + PRInt32 aRowIndex, + PRInt32 aColIndex); + + /** compute the right border for the table cell at (aRowIndex, aColIndex) + * and the appropriate border for that cell's right neighbor + * (the left border for a neighboring cell, or the right table edge) + */ + void ComputeRightBorderForEdgeAt(nsIPresContext& aPresContext, + PRInt32 aRowIndex, + PRInt32 aColIndex); + + /** compute the top borders for the table objects intersecting at (aRowIndex, aColIndex) */ + void ComputeTopBorderForEdgeAt(nsIPresContext& aPresContext, + PRInt32 aRowIndex, + PRInt32 aColIndex); + + /** compute the bottom border for the table cell at (aRowIndex, aColIndex) + * and the appropriate border for that cell's bottom neighbor + * (the top border for a neighboring cell, or the bottom table edge) + */ + void ComputeBottomBorderForEdgeAt(nsIPresContext& aPresContext, + PRInt32 aRowIndex, + PRInt32 aColIndex); + + /** at the time we initially compute collapsing borders, we don't yet have the + * column widths. So we set them as a post-process of the column balancing algorithm. + */ + void SetHorizontalEdgeLengths(); + + /** @return the identifier representing the edge opposite from aEdge (left-right, top-bottom) */ + PRUint8 GetOpposingEdge(PRUint8 aEdge); + + /** @return the computed width for aSide of aBorder */ + nscoord GetWidthForSide(const nsMargin& aBorder, + PRUint8 aSide); + + /** returns BORDER_PRECEDENT_LOWER if aStyle1 is lower precedent that aStyle2 + * BORDER_PRECEDENT_HIGHER if aStyle1 is higher precedent that aStyle2 + * BORDER_PRECEDENT_EQUAL if aStyle1 and aStyle2 have the same precedence + * (note, this is not necessarily the same as saying aStyle1==aStyle2) + * according to the CSS-2 collapsing borders for tables precedent rules. + */ + PRUint8 CompareBorderStyles(PRUint8 aStyle1, + PRUint8 aStyle2); + + /** helper to set the length of an edge for aSide border of this table frame */ + void SetBorderEdgeLength(PRUint8 aSide, + PRInt32 aIndex, + nscoord aLength); + + /** Compute the style, width, and color of an edge in a collapsed-border table. + * This method is the CSS2 border conflict resolution algorithm + * The spec says to resolve conflicts in this order:
+ * 1. any border with the style HIDDEN wins
+ * 2. the widest border with a style that is not NONE wins
+ * 3. the border styles are ranked in this order, highest to lowest precedence:
+ * double, solid, dashed, dotted, ridge, outset, groove, inset
+ * 4. borders that are of equal width and style (differ only in color) have this precedence:
+ * cell, row, rowgroup, col, colgroup, table
+ * 5. if all border styles are NONE, then that's the computed border style.
+ * This method assumes that the styles were added to aStyles in the reverse precedence order + * of their frame type, so that styles that come later in the list win over style + * earlier in the list if the tie-breaker gets down to #4. + * This method sets the out-param aBorder with the resolved border attributes + * + * @param aSide the side that is being compared + * @param aStyles the resolved styles of the table objects intersecting at aSide + * styles must be added to this list in reverse precedence order + * @param aBorder [OUT] the border edge that we're computing. Results of the computation + * are stored in aBorder: style, color, and width. + * @param aFlipLastSide an indication of what the bordering object is: another cell, or the table itself. + */ + void ComputeBorderSegment(PRUint8 aSide, + nsVoidArray* aStyles, + nsBorderEdge& aBorder, + PRBool aFlipLastSide); + + void GetBorder(nsMargin& aBorder); + + void GetBorderAt(PRInt32 aRowIndex, + PRInt32 aColIndex, + nsMargin& aBorder); + + void GetMaxBorder(PRInt32 aStartRowIndex, + PRInt32 aEndRowIndex, + PRInt32 aStartColIndex, + PRInt32 aEndColIndex, + nsMargin aBorder); + + nsBorderEdges* GetEdges(); + +protected: + nsBorderEdges mBorderEdges; + nsTableFrame& mTableFrame; +}; + +inline nsBorderEdges* nsTableBorderCollapser::GetEdges() +{ + return &mBorderEdges; +} + +#endif + + + + + +