blob: 072519e8ac961b993d9f07dd2630dc33ece3f30b [file] [log] [blame]
/*
* Copyright (C) 1997 Martin Jones (mjones@kde.org)
* (C) 1997 Torben Weis (weis@kde.org)
* (C) 1998 Waldo Bastian (bastian@kde.org)
* (C) 1999 Lars Knoll (knoll@kde.org)
* (C) 1999 Antti Koivisto (koivisto@kde.org)
* Copyright (C) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 Apple Inc. All rights reserved.
* Copyright (C) 2006 Alexey Proskuryakov (ap@nypop.com)
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library 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
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "config.h"
#include "RenderTable.h"
#include "AutoTableLayout.h"
#include "CollapsedBorderValue.h"
#include "DeleteButtonController.h"
#include "Document.h"
#include "FixedTableLayout.h"
#include "FrameView.h"
#include "HitTestResult.h"
#include "HTMLNames.h"
#include "RenderLayer.h"
#include "RenderTableCell.h"
#include "RenderTableCol.h"
#include "RenderTableSection.h"
#ifdef ANDROID_LAYOUT
#include "Settings.h"
#endif
#include "RenderView.h"
using namespace std;
namespace WebCore {
using namespace HTMLNames;
RenderTable::RenderTable(Node* node)
: RenderBlock(node)
, m_caption(0)
, m_head(0)
, m_foot(0)
, m_firstBody(0)
, m_currentBorder(0)
, m_hasColElements(false)
, m_needsSectionRecalc(0)
, m_hSpacing(0)
, m_vSpacing(0)
, m_borderStart(0)
, m_borderEnd(0)
{
setChildrenInline(false);
m_columnPos.fill(0, 2);
m_columns.fill(ColumnStruct(), 1);
#ifdef ANDROID_LAYOUT
m_singleColumn = false;
#endif
}
RenderTable::~RenderTable()
{
}
void RenderTable::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle)
{
RenderBlock::styleDidChange(diff, oldStyle);
propagateStyleToAnonymousChildren();
ETableLayout oldTableLayout = oldStyle ? oldStyle->tableLayout() : TAUTO;
// In the collapsed border model, there is no cell spacing.
m_hSpacing = collapseBorders() ? 0 : style()->horizontalBorderSpacing();
m_vSpacing = collapseBorders() ? 0 : style()->verticalBorderSpacing();
m_columnPos[0] = m_hSpacing;
if (!m_tableLayout || style()->tableLayout() != oldTableLayout) {
// According to the CSS2 spec, you only use fixed table layout if an
// explicit width is specified on the table. Auto width implies auto table layout.
if (style()->tableLayout() == TFIXED && !style()->logicalWidth().isAuto())
m_tableLayout.set(new FixedTableLayout(this));
else
m_tableLayout.set(new AutoTableLayout(this));
}
}
static inline void resetSectionPointerIfNotBefore(RenderTableSection*& ptr, RenderObject* before)
{
if (!before || !ptr)
return;
RenderObject* o = before->previousSibling();
while (o && o != ptr)
o = o->previousSibling();
if (!o)
ptr = 0;
}
void RenderTable::addChild(RenderObject* child, RenderObject* beforeChild)
{
// Make sure we don't append things after :after-generated content if we have it.
if (!beforeChild && isAfterContent(lastChild()))
beforeChild = lastChild();
bool wrapInAnonymousSection = !child->isPositioned();
if (child->isRenderBlock() && child->style()->display() == TABLE_CAPTION) {
// First caption wins.
if (beforeChild && m_caption) {
RenderObject* o = beforeChild->previousSibling();
while (o && o != m_caption)
o = o->previousSibling();
if (!o) {
m_caption = 0;
setNeedsSectionRecalc();
}
}
if (!m_caption)
m_caption = toRenderBlock(child);
else
setNeedsSectionRecalc();
wrapInAnonymousSection = false;
} else if (child->isTableCol()) {
m_hasColElements = true;
wrapInAnonymousSection = false;
} else if (child->isTableSection()) {
switch (child->style()->display()) {
case TABLE_HEADER_GROUP:
resetSectionPointerIfNotBefore(m_head, beforeChild);
if (!m_head) {
m_head = toRenderTableSection(child);
} else {
resetSectionPointerIfNotBefore(m_firstBody, beforeChild);
if (!m_firstBody)
m_firstBody = toRenderTableSection(child);
}
wrapInAnonymousSection = false;
break;
case TABLE_FOOTER_GROUP:
resetSectionPointerIfNotBefore(m_foot, beforeChild);
if (!m_foot) {
m_foot = toRenderTableSection(child);
wrapInAnonymousSection = false;
break;
}
// Fall through.
case TABLE_ROW_GROUP:
resetSectionPointerIfNotBefore(m_firstBody, beforeChild);
if (!m_firstBody)
m_firstBody = toRenderTableSection(child);
wrapInAnonymousSection = false;
break;
default:
ASSERT_NOT_REACHED();
}
} else if (child->isTableCell() || child->isTableRow())
wrapInAnonymousSection = true;
else
wrapInAnonymousSection = true;
if (!wrapInAnonymousSection) {
// If the next renderer is actually wrapped in an anonymous table section, we need to go up and find that.
while (beforeChild && beforeChild->parent() != this)
beforeChild = beforeChild->parent();
RenderBox::addChild(child, beforeChild);
return;
}
if (!beforeChild && lastChild() && lastChild()->isTableSection() && lastChild()->isAnonymous()) {
lastChild()->addChild(child);
return;
}
RenderObject* lastBox = beforeChild;
while (lastBox && lastBox->parent()->isAnonymous() && !lastBox->isTableSection() && lastBox->style()->display() != TABLE_CAPTION && lastBox->style()->display() != TABLE_COLUMN_GROUP)
lastBox = lastBox->parent();
if (lastBox && lastBox->isAnonymous() && !isAfterContent(lastBox)) {
if (beforeChild == lastBox)
beforeChild = lastBox->firstChild();
lastBox->addChild(child, beforeChild);
return;
}
if (beforeChild && !beforeChild->isTableSection() && beforeChild->style()->display() != TABLE_CAPTION && beforeChild->style()->display() != TABLE_COLUMN_GROUP)
beforeChild = 0;
RenderTableSection* section = new (renderArena()) RenderTableSection(document() /* anonymous */);
RefPtr<RenderStyle> newStyle = RenderStyle::create();
newStyle->inheritFrom(style());
newStyle->setDisplay(TABLE_ROW_GROUP);
section->setStyle(newStyle.release());
addChild(section, beforeChild);
section->addChild(child);
}
void RenderTable::removeChild(RenderObject* oldChild)
{
RenderBox::removeChild(oldChild);
if (m_caption && oldChild == m_caption && node())
node()->setNeedsStyleRecalc();
setNeedsSectionRecalc();
}
void RenderTable::computeLogicalWidth()
{
#ifdef ANDROID_LAYOUT
if (view()->frameView())
setVisibleWidth(view()->frameView()->textWrapWidth());
#endif
if (isPositioned())
computePositionedLogicalWidth();
RenderBlock* cb = containingBlock();
int availableLogicalWidth = containingBlockLogicalWidthForContent();
bool hasPerpendicularContainingBlock = cb->style()->isHorizontalWritingMode() != style()->isHorizontalWritingMode();
int containerWidthInInlineDirection = hasPerpendicularContainingBlock ? perpendicularContainingBlockLogicalHeight() : availableLogicalWidth;
LengthType logicalWidthType = style()->logicalWidth().type();
if (logicalWidthType > Relative && style()->logicalWidth().isPositive()) {
// Percent or fixed table
setLogicalWidth(style()->logicalWidth().calcMinValue(containerWidthInInlineDirection));
setLogicalWidth(max(minPreferredLogicalWidth(), logicalWidth()));
} else {
// Subtract out any fixed margins from our available width for auto width tables.
int marginTotal = 0;
if (!style()->marginStart().isAuto())
marginTotal += style()->marginStart().calcValue(availableLogicalWidth);
if (!style()->marginEnd().isAuto())
marginTotal += style()->marginEnd().calcValue(availableLogicalWidth);
// Subtract out our margins to get the available content width.
int availableContentLogicalWidth = max(0, containerWidthInInlineDirection - marginTotal);
// Ensure we aren't bigger than our max width or smaller than our min width.
setLogicalWidth(min(availableContentLogicalWidth, maxPreferredLogicalWidth()));
}
setLogicalWidth(max(logicalWidth(), minPreferredLogicalWidth()));
// Finally, with our true width determined, compute our margins for real.
setMarginStart(0);
setMarginEnd(0);
#ifdef ANDROID_LAYOUT
// in SSR mode, we ignore left/right margin for table
if (document()->settings()->layoutAlgorithm() == Settings::kLayoutSSR)
return;
#endif
if (!hasPerpendicularContainingBlock)
computeInlineDirectionMargins(cb, availableLogicalWidth, logicalWidth());
else {
setMarginStart(style()->marginStart().calcMinValue(availableLogicalWidth));
setMarginEnd(style()->marginEnd().calcMinValue(availableLogicalWidth));
}
}
void RenderTable::adjustLogicalHeightForCaption()
{
ASSERT(m_caption);
IntRect captionRect(m_caption->x(), m_caption->y(), m_caption->width(), m_caption->height());
m_caption->setLogicalLocation(m_caption->marginStart(), logicalHeight());
if (!selfNeedsLayout() && m_caption->checkForRepaintDuringLayout())
m_caption->repaintDuringLayoutIfMoved(captionRect);
setLogicalHeight(logicalHeight() + m_caption->logicalHeight() + m_caption->marginBefore() + m_caption->marginAfter());
}
void RenderTable::layout()
{
ASSERT(needsLayout());
if (simplifiedLayout())
return;
recalcSectionsIfNeeded();
LayoutRepainter repainter(*this, checkForRepaintDuringLayout());
LayoutStateMaintainer statePusher(view(), this, IntSize(x(), y()), style()->isFlippedBlocksWritingMode());
setLogicalHeight(0);
m_overflow.clear();
initMaxMarginValues();
#ifdef ANDROID_LAYOUT
bool relayoutChildren = false;
#endif
int oldLogicalWidth = logicalWidth();
computeLogicalWidth();
#ifdef ANDROID_LAYOUT
if (!checkAndSetRelayoutChildren(&relayoutChildren)
&& document()->settings()->layoutAlgorithm() == Settings::kLayoutSSR) {
// if the width of a table is wider than its container width, or it has a nested table,
// we will render it with single column.
int cw = containingBlockLogicalWidthForContent();
bool shouldRenderAsSingleColumn = (width() > cw);
if (!shouldRenderAsSingleColumn) {
RenderObject* child = firstChild();
while (child) {
if (child->isTable()) {
shouldRenderAsSingleColumn = true;
break;
}
child = child->nextInPreOrder();
}
}
if (shouldRenderAsSingleColumn) {
m_singleColumn = true;
if (width() > cw)
setWidth(cw);
if (m_minPreferredLogicalWidth > cw)
m_minPreferredLogicalWidth = cw;
if (m_maxPreferredLogicalWidth > cw)
m_maxPreferredLogicalWidth = cw;
}
}
#endif
if (m_caption && logicalWidth() != oldLogicalWidth)
m_caption->setNeedsLayout(true, false);
// FIXME: The optimisation below doesn't work since the internal table
// layout could have changed. we need to add a flag to the table
// layout that tells us if something has changed in the min max
// calculations to do it correctly.
// if ( oldWidth != width() || columns.size() + 1 != columnPos.size() )
m_tableLayout->layout();
setCellLogicalWidths();
int totalSectionLogicalHeight = 0;
int oldTableLogicalTop = m_caption ? m_caption->logicalHeight() + m_caption->marginBefore() + m_caption->marginAfter() : 0;
bool collapsing = collapseBorders();
for (RenderObject* child = firstChild(); child; child = child->nextSibling()) {
#ifdef ANDROID_LAYOUT
if (relayoutChildren) {
child->setNeedsLayout(true, false);
if (!child->isTableSection()) {
child->layoutIfNeeded();
continue;
}
// fall through
}
#endif
if (child->isTableSection()) {
child->layoutIfNeeded();
RenderTableSection* section = toRenderTableSection(child);
totalSectionLogicalHeight += section->calcRowLogicalHeight();
if (collapsing)
section->recalcOuterBorder();
ASSERT(!section->needsLayout());
} else if (child->isTableCol()) {
child->layoutIfNeeded();
ASSERT(!child->needsLayout());
}
}
// Only lay out one caption, since it's the only one we're going to end up painting.
if (m_caption)
m_caption->layoutIfNeeded();
// If any table section moved vertically, we will just repaint everything from that
// section down (it is quite unlikely that any of the following sections
// did not shift).
bool sectionMoved = false;
int movedSectionLogicalTop = 0;
// FIXME: Collapse caption margin.
if (m_caption && m_caption->style()->captionSide() != CAPBOTTOM) {
adjustLogicalHeightForCaption();
if (logicalHeight() != oldTableLogicalTop) {
sectionMoved = true;
movedSectionLogicalTop = min(logicalHeight(), oldTableLogicalTop);
}
}
int borderAndPaddingBefore = borderBefore() + (collapsing ? 0 : paddingBefore());
int borderAndPaddingAfter = borderAfter() + (collapsing ? 0 : paddingAfter());
setLogicalHeight(logicalHeight() + borderAndPaddingBefore);
if (!isPositioned())
computeLogicalHeight();
Length logicalHeightLength = style()->logicalHeight();
int computedLogicalHeight = 0;
if (logicalHeightLength.isFixed()) {
// Tables size as though CSS height includes border/padding.
computedLogicalHeight = logicalHeightLength.value() - (borderAndPaddingBefore + borderAndPaddingAfter);
} else if (logicalHeightLength.isPercent())
computedLogicalHeight = computePercentageLogicalHeight(logicalHeightLength);
computedLogicalHeight = max(0, computedLogicalHeight);
for (RenderObject* child = firstChild(); child; child = child->nextSibling()) {
if (child->isTableSection())
// FIXME: Distribute extra height between all table body sections instead of giving it all to the first one.
toRenderTableSection(child)->layoutRows(child == m_firstBody ? max(0, computedLogicalHeight - totalSectionLogicalHeight) : 0);
}
if (!m_firstBody && computedLogicalHeight > totalSectionLogicalHeight && !document()->inQuirksMode()) {
// Completely empty tables (with no sections or anything) should at least honor specified height
// in strict mode.
setLogicalHeight(logicalHeight() + computedLogicalHeight);
}
int sectionLogicalLeft = style()->isLeftToRightDirection() ? borderStart() : borderEnd();
if (!collapsing)
sectionLogicalLeft += style()->isLeftToRightDirection() ? paddingStart() : paddingEnd();
// position the table sections
RenderTableSection* section = m_head ? m_head : (m_firstBody ? m_firstBody : m_foot);
while (section) {
if (!sectionMoved && section->logicalTop() != logicalHeight()) {
sectionMoved = true;
movedSectionLogicalTop = min(logicalHeight(), section->logicalTop()) + (style()->isHorizontalWritingMode() ? section->minYVisualOverflow() : section->minXVisualOverflow());
}
section->setLogicalLocation(sectionLogicalLeft, logicalHeight());
setLogicalHeight(logicalHeight() + section->logicalHeight());
section = sectionBelow(section);
}
setLogicalHeight(logicalHeight() + borderAndPaddingAfter);
if (m_caption && m_caption->style()->captionSide() == CAPBOTTOM)
adjustLogicalHeightForCaption();
if (isPositioned())
computeLogicalHeight();
// table can be containing block of positioned elements.
// FIXME: Only pass true if width or height changed.
layoutPositionedObjects(true);
updateLayerTransform();
computeOverflow(clientLogicalBottom());
statePusher.pop();
if (view()->layoutState()->pageLogicalHeight())
setPageLogicalOffset(view()->layoutState()->pageLogicalOffset(logicalTop()));
bool didFullRepaint = repainter.repaintAfterLayout();
// Repaint with our new bounds if they are different from our old bounds.
if (!didFullRepaint && sectionMoved) {
if (style()->isHorizontalWritingMode())
repaintRectangle(IntRect(minXVisualOverflow(), movedSectionLogicalTop, maxXVisualOverflow() - minXVisualOverflow(), maxYVisualOverflow() - movedSectionLogicalTop));
else
repaintRectangle(IntRect(movedSectionLogicalTop, minYVisualOverflow(), maxXVisualOverflow() - movedSectionLogicalTop, maxYVisualOverflow() - minYVisualOverflow()));
}
setNeedsLayout(false);
}
void RenderTable::addOverflowFromChildren()
{
// Add overflow from borders.
// Technically it's odd that we are incorporating the borders into layout overflow, which is only supposed to be about overflow from our
// descendant objects, but since tables don't support overflow:auto, this works out fine.
if (collapseBorders()) {
int rightBorderOverflow = width() + outerBorderRight() - borderRight();
int leftBorderOverflow = borderLeft() - outerBorderLeft();
int bottomBorderOverflow = height() + outerBorderBottom() - borderBottom();
int topBorderOverflow = borderTop() - outerBorderTop();
IntRect borderOverflowRect(leftBorderOverflow, topBorderOverflow, rightBorderOverflow - leftBorderOverflow, bottomBorderOverflow - topBorderOverflow);
if (borderOverflowRect != borderBoxRect()) {
addLayoutOverflow(borderOverflowRect);
addVisualOverflow(borderOverflowRect);
}
}
// Add overflow from our caption.
if (m_caption)
addOverflowFromChild(m_caption);
// Add overflow from our sections.
for (RenderObject* child = firstChild(); child; child = child->nextSibling()) {
if (child->isTableSection()) {
RenderTableSection* section = toRenderTableSection(child);
addOverflowFromChild(section);
}
}
}
void RenderTable::setCellLogicalWidths()
{
for (RenderObject* child = firstChild(); child; child = child->nextSibling()) {
if (child->isTableSection())
toRenderTableSection(child)->setCellLogicalWidths();
}
}
void RenderTable::paint(PaintInfo& paintInfo, int tx, int ty)
{
tx += x();
ty += y();
PaintPhase paintPhase = paintInfo.phase;
if (!isRoot()) {
IntRect overflowBox = visualOverflowRect();
flipForWritingMode(overflowBox);
overflowBox.inflate(maximalOutlineSize(paintInfo.phase));
overflowBox.move(tx, ty);
if (!overflowBox.intersects(paintInfo.rect))
return;
}
bool pushedClip = pushContentsClip(paintInfo, tx, ty);
paintObject(paintInfo, tx, ty);
if (pushedClip)
popContentsClip(paintInfo, paintPhase, tx, ty);
}
void RenderTable::paintObject(PaintInfo& paintInfo, int tx, int ty)
{
PaintPhase paintPhase = paintInfo.phase;
if ((paintPhase == PaintPhaseBlockBackground || paintPhase == PaintPhaseChildBlockBackground) && hasBoxDecorations() && style()->visibility() == VISIBLE)
paintBoxDecorations(paintInfo, tx, ty);
if (paintPhase == PaintPhaseMask) {
paintMask(paintInfo, tx, ty);
return;
}
// We're done. We don't bother painting any children.
if (paintPhase == PaintPhaseBlockBackground)
return;
// We don't paint our own background, but we do let the kids paint their backgrounds.
if (paintPhase == PaintPhaseChildBlockBackgrounds)
paintPhase = PaintPhaseChildBlockBackground;
PaintInfo info(paintInfo);
info.phase = paintPhase;
info.updatePaintingRootForChildren(this);
for (RenderObject* child = firstChild(); child; child = child->nextSibling()) {
if (child->isBox() && !toRenderBox(child)->hasSelfPaintingLayer() && (child->isTableSection() || child == m_caption)) {
IntPoint childPoint = flipForWritingMode(toRenderBox(child), IntPoint(tx, ty), ParentToChildFlippingAdjustment);
child->paint(info, childPoint.x(), childPoint.y());
}
}
if (collapseBorders() && paintPhase == PaintPhaseChildBlockBackground && 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.
info.phase = PaintPhaseCollapsedTableBorders;
RenderTableCell::CollapsedBorderStyles borderStyles;
RenderObject* stop = nextInPreOrderAfterChildren();
for (RenderObject* o = firstChild(); o && o != stop; o = o->nextInPreOrder()) {
if (o->isTableCell())
toRenderTableCell(o)->collectBorderStyles(borderStyles);
}
RenderTableCell::sortBorderStyles(borderStyles);
size_t count = borderStyles.size();
for (size_t i = 0; i < count; ++i) {
m_currentBorder = &borderStyles[i];
for (RenderObject* child = firstChild(); child; child = child->nextSibling())
if (child->isTableSection()) {
IntPoint childPoint = flipForWritingMode(toRenderTableSection(child), IntPoint(tx, ty), ParentToChildFlippingAdjustment);
child->paint(info, childPoint.x(), childPoint.y());
}
}
m_currentBorder = 0;
}
// Paint outline.
if ((paintPhase == PaintPhaseOutline || paintPhase == PaintPhaseSelfOutline) && hasOutline() && style()->visibility() == VISIBLE)
paintOutline(paintInfo.context, tx, ty, width(), height());
}
void RenderTable::subtractCaptionRect(IntRect& rect) const
{
if (!m_caption)
return;
int captionLogicalHeight = m_caption->logicalHeight() + m_caption->marginBefore() + m_caption->marginAfter();
bool captionIsBefore = (m_caption->style()->captionSide() != CAPBOTTOM) ^ style()->isFlippedBlocksWritingMode();
if (style()->isHorizontalWritingMode()) {
rect.setHeight(rect.height() - captionLogicalHeight);
if (captionIsBefore)
rect.move(0, captionLogicalHeight);
} else {
rect.setWidth(rect.width() - captionLogicalHeight);
if (captionIsBefore)
rect.move(captionLogicalHeight, 0);
}
}
void RenderTable::paintBoxDecorations(PaintInfo& paintInfo, int tx, int ty)
{
if (!paintInfo.shouldPaintWithinRoot(this))
return;
IntRect rect(tx, ty, width(), height());
subtractCaptionRect(rect);
paintBoxShadow(paintInfo.context, rect.x(), rect.y(), rect.width(), rect.height(), style(), Normal);
if (isRoot())
paintRootBoxFillLayers(paintInfo);
else if (!isBody() || document()->documentElement()->renderer()->hasBackground())
// The <body> only paints its background if the root element has defined a background
// independent of the body.
paintFillLayers(paintInfo, style()->visitedDependentColor(CSSPropertyBackgroundColor), style()->backgroundLayers(), rect.x(), rect.y(), rect.width(), rect.height());
paintBoxShadow(paintInfo.context, rect.x(), rect.y(), rect.width(), rect.height(), style(), Inset);
if (style()->hasBorder() && !collapseBorders())
paintBorder(paintInfo.context, rect.x(), rect.y(), rect.width(), rect.height(), style());
}
void RenderTable::paintMask(PaintInfo& paintInfo, int tx, int ty)
{
if (style()->visibility() != VISIBLE || paintInfo.phase != PaintPhaseMask)
return;
IntRect rect(tx, ty, width(), height());
subtractCaptionRect(rect);
paintMaskImages(paintInfo, rect.x(), rect.y(), rect.width(), rect.height());
}
void RenderTable::computePreferredLogicalWidths()
{
ASSERT(preferredLogicalWidthsDirty());
recalcSectionsIfNeeded();
recalcBordersInRowDirection();
m_tableLayout->computePreferredLogicalWidths(m_minPreferredLogicalWidth, m_maxPreferredLogicalWidth);
if (m_caption)
m_minPreferredLogicalWidth = max(m_minPreferredLogicalWidth, m_caption->minPreferredLogicalWidth());
setPreferredLogicalWidthsDirty(false);
}
void RenderTable::splitColumn(int pos, int firstSpan)
{
// we need to add a new columnStruct
int oldSize = m_columns.size();
m_columns.grow(oldSize + 1);
int oldSpan = m_columns[pos].span;
ASSERT(oldSpan > firstSpan);
m_columns[pos].span = firstSpan;
memmove(m_columns.data() + pos + 1, m_columns.data() + pos, (oldSize - pos) * sizeof(ColumnStruct));
m_columns[pos + 1].span = oldSpan - firstSpan;
// change width of all rows.
for (RenderObject* child = firstChild(); child; child = child->nextSibling()) {
if (child->isTableSection())
toRenderTableSection(child)->splitColumn(pos, firstSpan);
}
m_columnPos.grow(numEffCols() + 1);
setNeedsLayoutAndPrefWidthsRecalc();
}
void RenderTable::appendColumn(int span)
{
// easy case.
int pos = m_columns.size();
int newSize = pos + 1;
m_columns.grow(newSize);
m_columns[pos].span = span;
// change width of all rows.
for (RenderObject* child = firstChild(); child; child = child->nextSibling()) {
if (child->isTableSection())
toRenderTableSection(child)->appendColumn(pos);
}
m_columnPos.grow(numEffCols() + 1);
setNeedsLayoutAndPrefWidthsRecalc();
}
RenderTableCol* RenderTable::nextColElement(RenderTableCol* current) const
{
RenderObject* next = current->firstChild();
if (!next)
next = current->nextSibling();
if (!next && current->parent()->isTableCol())
next = current->parent()->nextSibling();
while (next) {
if (next->isTableCol())
return toRenderTableCol(next);
if (next != m_caption)
return 0;
next = next->nextSibling();
}
return 0;
}
RenderTableCol* RenderTable::colElement(int col, bool* startEdge, bool* endEdge) const
{
if (!m_hasColElements)
return 0;
RenderObject* child = firstChild();
int cCol = 0;
while (child) {
if (child->isTableCol())
break;
if (child != m_caption)
return 0;
child = child->nextSibling();
}
if (!child)
return 0;
RenderTableCol* colElem = toRenderTableCol(child);
while (colElem) {
int span = colElem->span();
if (!colElem->firstChild()) {
int startCol = cCol;
int endCol = cCol + span - 1;
cCol += span;
if (cCol > col) {
if (startEdge)
*startEdge = startCol == col;
if (endEdge)
*endEdge = endCol == col;
return colElem;
}
}
colElem = nextColElement(colElem);
}
return 0;
}
void RenderTable::recalcCaption(RenderBlock* caption) const
{
if (!m_caption) {
m_caption = caption;
m_caption->setNeedsLayout(true);
} else {
// Make sure to null out the child's renderer.
if (Node* node = caption->node())
node->setRenderer(0);
// Destroy the child now.
caption->destroy();
}
}
void RenderTable::recalcSections() const
{
m_caption = 0;
m_head = 0;
m_foot = 0;
m_firstBody = 0;
m_hasColElements = false;
// We need to get valid pointers to caption, head, foot and first body again
RenderObject* nextSibling;
for (RenderObject* child = firstChild(); child; child = nextSibling) {
nextSibling = child->nextSibling();
switch (child->style()->display()) {
case TABLE_CAPTION:
if (child->isRenderBlock())
recalcCaption(toRenderBlock(child));
break;
case TABLE_COLUMN:
case TABLE_COLUMN_GROUP:
m_hasColElements = true;
break;
case TABLE_HEADER_GROUP:
if (child->isTableSection()) {
RenderTableSection* section = toRenderTableSection(child);
if (!m_head)
m_head = section;
else if (!m_firstBody)
m_firstBody = section;
section->recalcCellsIfNeeded();
}
break;
case TABLE_FOOTER_GROUP:
if (child->isTableSection()) {
RenderTableSection* section = toRenderTableSection(child);
if (!m_foot)
m_foot = section;
else if (!m_firstBody)
m_firstBody = section;
section->recalcCellsIfNeeded();
}
break;
case TABLE_ROW_GROUP:
if (child->isTableSection()) {
RenderTableSection* section = toRenderTableSection(child);
if (!m_firstBody)
m_firstBody = section;
section->recalcCellsIfNeeded();
}
break;
default:
break;
}
}
// repair column count (addChild can grow it too much, because it always adds elements to the last row of a section)
int maxCols = 0;
for (RenderObject* child = firstChild(); child; child = child->nextSibling()) {
if (child->isTableSection()) {
RenderTableSection* section = toRenderTableSection(child);
int sectionCols = section->numColumns();
if (sectionCols > maxCols)
maxCols = sectionCols;
}
}
m_columns.resize(maxCols);
m_columnPos.resize(maxCols + 1);
ASSERT(selfNeedsLayout());
m_needsSectionRecalc = false;
}
int RenderTable::calcBorderStart() const
{
if (collapseBorders()) {
// Determined by the first cell of the first row. See the CSS 2.1 spec, section 17.6.2.
if (!numEffCols())
return 0;
unsigned borderWidth = 0;
const BorderValue& tb = style()->borderStart();
if (tb.style() == BHIDDEN)
return 0;
if (tb.style() > BHIDDEN)
borderWidth = tb.width();
if (RenderTableCol* colGroup = colElement(0)) {
const BorderValue& gb = colGroup->style()->borderStart();
if (gb.style() == BHIDDEN)
return 0;
if (gb.style() > BHIDDEN)
borderWidth = max(borderWidth, static_cast<unsigned>(gb.width()));
}
RenderTableSection* firstNonEmptySection = m_head ? m_head : (m_firstBody ? m_firstBody : m_foot);
if (firstNonEmptySection && !firstNonEmptySection->numRows())
firstNonEmptySection = sectionBelow(firstNonEmptySection, true);
if (firstNonEmptySection) {
const BorderValue& sb = firstNonEmptySection->style()->borderStart();
if (sb.style() == BHIDDEN)
return 0;
if (sb.style() > BHIDDEN)
borderWidth = max(borderWidth, static_cast<unsigned>(sb.width()));
const RenderTableSection::CellStruct& cs = firstNonEmptySection->cellAt(0, 0);
if (cs.hasCells()) {
const BorderValue& cb = cs.primaryCell()->style()->borderStart(); // FIXME: Make this work with perpendicualr and flipped cells.
if (cb.style() == BHIDDEN)
return 0;
const BorderValue& rb = cs.primaryCell()->parent()->style()->borderStart();
if (rb.style() == BHIDDEN)
return 0;
if (cb.style() > BHIDDEN)
borderWidth = max(borderWidth, static_cast<unsigned>(cb.width()));
if (rb.style() > BHIDDEN)
borderWidth = max(borderWidth, static_cast<unsigned>(rb.width()));
}
}
return (borderWidth + (style()->isLeftToRightDirection() ? 0 : 1)) / 2;
}
return RenderBlock::borderStart();
}
int RenderTable::calcBorderEnd() const
{
if (collapseBorders()) {
// Determined by the last cell of the first row. See the CSS 2.1 spec, section 17.6.2.
if (!numEffCols())
return 0;
unsigned borderWidth = 0;
const BorderValue& tb = style()->borderEnd();
if (tb.style() == BHIDDEN)
return 0;
if (tb.style() > BHIDDEN)
borderWidth = tb.width();
int endColumn = numEffCols() - 1;
if (RenderTableCol* colGroup = colElement(endColumn)) {
const BorderValue& gb = colGroup->style()->borderEnd();
if (gb.style() == BHIDDEN)
return 0;
if (gb.style() > BHIDDEN)
borderWidth = max(borderWidth, static_cast<unsigned>(gb.width()));
}
RenderTableSection* firstNonEmptySection = m_head ? m_head : (m_firstBody ? m_firstBody : m_foot);
if (firstNonEmptySection && !firstNonEmptySection->numRows())
firstNonEmptySection = sectionBelow(firstNonEmptySection, true);
if (firstNonEmptySection) {
const BorderValue& sb = firstNonEmptySection->style()->borderEnd();
if (sb.style() == BHIDDEN)
return 0;
if (sb.style() > BHIDDEN)
borderWidth = max(borderWidth, static_cast<unsigned>(sb.width()));
const RenderTableSection::CellStruct& cs = firstNonEmptySection->cellAt(0, endColumn);
if (cs.hasCells()) {
const BorderValue& cb = cs.primaryCell()->style()->borderEnd(); // FIXME: Make this work with perpendicular and flipped cells.
if (cb.style() == BHIDDEN)
return 0;
const BorderValue& rb = cs.primaryCell()->parent()->style()->borderEnd();
if (rb.style() == BHIDDEN)
return 0;
if (cb.style() > BHIDDEN)
borderWidth = max(borderWidth, static_cast<unsigned>(cb.width()));
if (rb.style() > BHIDDEN)
borderWidth = max(borderWidth, static_cast<unsigned>(rb.width()));
}
}
return (borderWidth + (style()->isLeftToRightDirection() ? 1 : 0)) / 2;
}
return RenderBlock::borderEnd();
}
void RenderTable::recalcBordersInRowDirection()
{
m_borderStart = calcBorderStart();
m_borderEnd = calcBorderEnd();
}
int RenderTable::borderBefore() const
{
if (collapseBorders())
return outerBorderBefore();
return RenderBlock::borderBefore();
}
int RenderTable::borderAfter() const
{
if (collapseBorders())
return outerBorderAfter();
return RenderBlock::borderAfter();
}
int RenderTable::outerBorderBefore() const
{
if (!collapseBorders())
return 0;
int borderWidth = 0;
RenderTableSection* topSection;
if (m_head)
topSection = m_head;
else if (m_firstBody)
topSection = m_firstBody;
else if (m_foot)
topSection = m_foot;
else
topSection = 0;
if (topSection) {
borderWidth = topSection->outerBorderBefore();
if (borderWidth == -1)
return 0; // Overridden by hidden
}
const BorderValue& tb = style()->borderBefore();
if (tb.style() == BHIDDEN)
return 0;
if (tb.style() > BHIDDEN)
borderWidth = max(borderWidth, static_cast<int>(tb.width() / 2));
return borderWidth;
}
int RenderTable::outerBorderAfter() const
{
if (!collapseBorders())
return 0;
int borderWidth = 0;
RenderTableSection* bottomSection;
if (m_foot)
bottomSection = m_foot;
else {
RenderObject* child;
for (child = lastChild(); child && !child->isTableSection(); child = child->previousSibling()) { }
bottomSection = child ? toRenderTableSection(child) : 0;
}
if (bottomSection) {
borderWidth = bottomSection->outerBorderAfter();
if (borderWidth == -1)
return 0; // Overridden by hidden
}
const BorderValue& tb = style()->borderAfter();
if (tb.style() == BHIDDEN)
return 0;
if (tb.style() > BHIDDEN)
borderWidth = max(borderWidth, static_cast<int>((tb.width() + 1) / 2));
return borderWidth;
}
int RenderTable::outerBorderStart() const
{
if (!collapseBorders())
return 0;
int borderWidth = 0;
const BorderValue& tb = style()->borderStart();
if (tb.style() == BHIDDEN)
return 0;
if (tb.style() > BHIDDEN)
borderWidth = (tb.width() + (style()->isLeftToRightDirection() ? 0 : 1)) / 2;
bool allHidden = true;
for (RenderObject* child = firstChild(); child; child = child->nextSibling()) {
if (!child->isTableSection())
continue;
int sw = toRenderTableSection(child)->outerBorderStart();
if (sw == -1)
continue;
else
allHidden = false;
borderWidth = max(borderWidth, sw);
}
if (allHidden)
return 0;
return borderWidth;
}
int RenderTable::outerBorderEnd() const
{
if (!collapseBorders())
return 0;
int borderWidth = 0;
const BorderValue& tb = style()->borderEnd();
if (tb.style() == BHIDDEN)
return 0;
if (tb.style() > BHIDDEN)
borderWidth = (tb.width() + (style()->isLeftToRightDirection() ? 1 : 0)) / 2;
bool allHidden = true;
for (RenderObject* child = firstChild(); child; child = child->nextSibling()) {
if (!child->isTableSection())
continue;
int sw = toRenderTableSection(child)->outerBorderEnd();
if (sw == -1)
continue;
else
allHidden = false;
borderWidth = max(borderWidth, sw);
}
if (allHidden)
return 0;
return borderWidth;
}
RenderTableSection* RenderTable::sectionAbove(const RenderTableSection* section, bool skipEmptySections) const
{
recalcSectionsIfNeeded();
if (section == m_head)
return 0;
RenderObject* prevSection = section == m_foot ? lastChild() : section->previousSibling();
while (prevSection) {
if (prevSection->isTableSection() && prevSection != m_head && prevSection != m_foot && (!skipEmptySections || toRenderTableSection(prevSection)->numRows()))
break;
prevSection = prevSection->previousSibling();
}
if (!prevSection && m_head && (!skipEmptySections || m_head->numRows()))
prevSection = m_head;
return toRenderTableSection(prevSection);
}
RenderTableSection* RenderTable::sectionBelow(const RenderTableSection* section, bool skipEmptySections) const
{
recalcSectionsIfNeeded();
if (section == m_foot)
return 0;
RenderObject* nextSection = section == m_head ? firstChild() : section->nextSibling();
while (nextSection) {
if (nextSection->isTableSection() && nextSection != m_head && nextSection != m_foot && (!skipEmptySections || toRenderTableSection(nextSection)->numRows()))
break;
nextSection = nextSection->nextSibling();
}
if (!nextSection && m_foot && (!skipEmptySections || m_foot->numRows()))
nextSection = m_foot;
return toRenderTableSection(nextSection);
}
RenderTableCell* RenderTable::cellAbove(const RenderTableCell* cell) const
{
recalcSectionsIfNeeded();
// Find the section and row to look in
int r = cell->row();
RenderTableSection* section = 0;
int rAbove = 0;
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 {
section = sectionAbove(cell->section(), true);
if (section)
rAbove = section->numRows() - 1;
}
// Look up the cell in the section's grid, which requires effective col index
if (section) {
int effCol = colToEffCol(cell->col());
RenderTableSection::CellStruct& aboveCell = section->cellAt(rAbove, effCol);
return aboveCell.primaryCell();
} else
return 0;
}
RenderTableCell* RenderTable::cellBelow(const RenderTableCell* cell) const
{
recalcSectionsIfNeeded();
// Find the section and row to look in
int r = cell->row() + cell->rowSpan() - 1;
RenderTableSection* section = 0;
int rBelow = 0;
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 {
section = sectionBelow(cell->section(), true);
if (section)
rBelow = 0;
}
// Look up the cell in the section's grid, which requires effective col index
if (section) {
int effCol = colToEffCol(cell->col());
RenderTableSection::CellStruct& belowCell = section->cellAt(rBelow, effCol);
return belowCell.primaryCell();
} else
return 0;
}
RenderTableCell* RenderTable::cellBefore(const RenderTableCell* cell) const
{
recalcSectionsIfNeeded();
RenderTableSection* section = cell->section();
int effCol = colToEffCol(cell->col());
if (!effCol)
return 0;
// If we hit a colspan back up to a real cell.
RenderTableSection::CellStruct& prevCell = section->cellAt(cell->row(), effCol - 1);
return prevCell.primaryCell();
}
RenderTableCell* RenderTable::cellAfter(const RenderTableCell* cell) const
{
recalcSectionsIfNeeded();
int effCol = colToEffCol(cell->col() + cell->colSpan());
if (effCol >= numEffCols())
return 0;
return cell->section()->primaryCellAt(cell->row(), effCol);
}
RenderBlock* RenderTable::firstLineBlock() const
{
return 0;
}
void RenderTable::updateFirstLetter()
{
}
int RenderTable::firstLineBoxBaseline() const
{
if (isWritingModeRoot())
return -1;
recalcSectionsIfNeeded();
RenderTableSection* firstNonEmptySection = m_head ? m_head : (m_firstBody ? m_firstBody : m_foot);
if (firstNonEmptySection && !firstNonEmptySection->numRows())
firstNonEmptySection = sectionBelow(firstNonEmptySection, true);
if (!firstNonEmptySection)
return -1;
return firstNonEmptySection->logicalTop() + firstNonEmptySection->firstLineBoxBaseline();
}
IntRect RenderTable::overflowClipRect(int tx, int ty, OverlayScrollbarSizeRelevancy relevancy)
{
IntRect rect = RenderBlock::overflowClipRect(tx, ty, relevancy);
// If we have a caption, expand the clip to include the caption.
// FIXME: Technically this is wrong, but it's virtually impossible to fix this
// for real until captions have been re-written.
// FIXME: This code assumes (like all our other caption code) that only top/bottom are
// supported. When we actually support left/right and stop mapping them to top/bottom,
// we might have to hack this code first (depending on what order we do these bug fixes in).
if (m_caption) {
if (style()->isHorizontalWritingMode()) {
rect.setHeight(height());
rect.setY(ty);
} else {
rect.setWidth(width());
rect.setX(tx);
}
}
return rect;
}
bool RenderTable::nodeAtPoint(const HitTestRequest& request, HitTestResult& result, int xPos, int yPos, int tx, int ty, HitTestAction action)
{
tx += x();
ty += y();
// Check kids first.
if (!hasOverflowClip() || overflowClipRect(tx, ty).intersects(result.rectForPoint(xPos, yPos))) {
for (RenderObject* child = lastChild(); child; child = child->previousSibling()) {
if (child->isBox() && !toRenderBox(child)->hasSelfPaintingLayer() && (child->isTableSection() || child == m_caption)) {
IntPoint childPoint = flipForWritingMode(toRenderBox(child), IntPoint(tx, ty), ParentToChildFlippingAdjustment);
if (child->nodeAtPoint(request, result, xPos, yPos, childPoint.x(), childPoint.y(), action)) {
updateHitTestResult(result, IntPoint(xPos - childPoint.x(), yPos - childPoint.y()));
return true;
}
}
}
}
// Check our bounds next.
IntRect boundsRect = IntRect(tx, ty, width(), height());
if (visibleToHitTesting() && (action == HitTestBlockBackground || action == HitTestChildBlockBackground) && boundsRect.intersects(result.rectForPoint(xPos, yPos))) {
updateHitTestResult(result, flipForWritingMode(IntPoint(xPos - tx, yPos - ty)));
if (!result.addNodeToRectBasedTestResult(node(), xPos, yPos, boundsRect))
return true;
}
return false;
}
}